pyramid-1.6/0000755000076500000240000000000012642137501013553 5ustar michaelstaff00000000000000pyramid-1.6/.gitignore0000644000076500000240000000040612524266531015550 0ustar michaelstaff00000000000000*.egg *.egg-info .eggs/ *.pyc *$py.class *.pt.py *.txt.py *~ .*.swp .coverage .coverage.* .tox/ nosetests.xml coverage.xml nosetests-*.xml coverage-*.xml tutorial.db build/ dist/ bin/ lib/ include/ .idea/ distribute-*.tar.gz bookenv/ jyenv/ pypyenv/ env*/ venv/ pyramid-1.6/.travis.yml0000644000076500000240000000132412642137120015661 0ustar michaelstaff00000000000000# Wire up travis language: python sudo: false matrix: include: - python: 2.6 env: TOXENV=py26 - python: 2.7 env: TOXENV=py27 - python: 3.2 env: TOXENV=py32 - python: 3.3 env: TOXENV=py33 - python: 3.4 env: TOXENV=py34 - python: 3.5 env: TOXENV=py35 - python: pypy env: TOXENV=pypy - python: pypy3 env: TOXENV=pypy3 - python: 3.5 env: TOXENV=py2-cover,py3-cover,coverage - python: 3.5 env: TOXENV=docs install: - travis_retry pip install tox script: - travis_retry tox notifications: email: - pyramid-checkins@lists.repoze.org pyramid-1.6/BFG_HISTORY.txt0000644000076500000240000074200512520062551016140 0ustar michaelstaff000000000000001.3b1 (2010-10-25) ================== Features -------- - The ``paster`` template named ``bfg_routesalchemy`` has been updated to use SQLAlchemy declarative syntax. Thanks to Ergo^. Bug Fixes --------- - When a renderer factory could not be found, a misleading error message was raised if the renderer name was not a string. Documentation ------------- - The ""bfgwiki2" (SQLAlchemy + url dispatch) tutorial has been updated slightly. In particular, the source packages no longer attempt to use a private index, and the recommended Python version is now 2.6. It was also updated to take into account the changes to the ``bfg_routesalchemy`` template used to set up an environment. - The "bfgwiki" (ZODB + traversal) tutorial has been updated slightly. In particular, the source packages no longer attempt to use a private index, and the recommended Python version is now 2.6. 1.3a15 (2010-09-30) =================== Features -------- - The ``repoze.bfg.traversal.traversal_path`` API now eagerly attempts to encode a Unicode ``path`` into ASCII before attempting to split it and decode its segments. This is for convenience, effectively to allow a (stored-as-Unicode-in-a-database, or retrieved-as-Unicode-from-a-request-parameter) Unicode path to be passed to ``find_model``, which eventually internally uses the ``traversal_path`` function under the hood. In version 1.2 and prior, if the ``path`` was Unicode, that Unicode was split on slashes and each resulting segment value was Unicode. An inappropriate call to the ``decode()`` method of a resulting Unicode path segment could cause a ``UnicodeDecodeError`` to occur even if the Unicode representation of the path contained no 'high order' characters (it effectively did a "double decode"). By converting the Unicode path argument to ASCII before we attempt to decode and split, genuine errors will occur in a more obvious place while also allowing us to handle (for convenience) the case that it's a Unicode representation formed entirely from ASCII-compatible characters. 1.3a14 (2010-09-14) =================== Bug Fixes --------- - If an exception view was registered through the legacy ``set_notfound_view`` or ``set_forbidden_view`` APIs, the context sent to the view was incorrect (could be ``None`` inappropriately). Features -------- - Compatibility with WebOb 1.0. Requirements ------------ - Now requires WebOb >= 1.0. Backwards Incompatibilities --------------------------- - Due to changes introduced WebOb 1.0, the ``repoze.bfg.request.make_request_ascii`` event subscriber no longer works, so it has been removed. This subscriber was meant to be used in a deployment so that code written before BFG 0.7.0 could run unchanged. At this point, such code will need to be rewritten to expect Unicode from ``request.GET``, ``request.POST`` and ``request.params`` or it will need to be changed to use ``request.str_POST``, ``request.str_GET`` and/or ``request.str_params`` instead of the non-``str`` versions of same, as the non-``str`` versions of the same APIs always now perform decoding to Unicode. Errata ------ - A prior changelog entry asserted that the ``INewResponse`` event was not sent to listeners if the response was not "valid" (if a view or renderer returned a response object that did not have a status/headers/app_iter). This is not true in this release, nor was it true in 1.3a13. 1.3a13 (2010-09-14) =================== Bug Fixes --------- - The ``traverse`` route predicate could not successfully generate a traversal path. Features -------- - In support of making it easier to configure applications which are "secure by default", a default permission feature was added. If supplied, the default permission is used as the permission string to all view registrations which don't otherwise name a permission. These APIs are in support of that: - A new constructor argument was added to the Configurator: ``default_permission``. - A new method was added to the Configurator: ``set_default_permission``. - A new ZCML directive was added: ``default_permission``. - Add a new request API: ``request.add_finished_callback``. Finished callbacks are called by the router unconditionally near the very end of request processing. See the "Using Finished Callbacks" section of the "Hooks" narrative chapter of the documentation for more information. - A ``request.matched_route`` attribute is now added to the request when a route has matched. Its value is the "route" object that matched (see the ``IRoute`` interface within ``repoze.bfg.interfaces`` API documentation for the API of a route object). - The ``exception`` attribute of the request is now set slightly earlier and in a slightly different set of scenarios, for benefit of "finished callbacks" and "response callbacks". In previous versions, the ``exception`` attribute of the request was not set at all if an exception view was not found. In this version, the ``request.exception`` attribute is set immediately when an exception is caught by the router, even if an exception view could not be found. - The ``add_route`` method of a Configurator now accepts a ``pregenerator`` argument. The pregenerator for the resulting route is called by ``route_url`` in order to adjust the set of arguments passed to it by the user for special purposes, such as Pylons 'subdomain' support. It will influence the URL returned by ``route_url``. See the ``repoze.bfg.interfaces.IRoutePregenerator`` interface for more information. Backwards Incompatibilities --------------------------- - The router no longer sets the value ``wsgiorg.routing_args`` into the environ when a route matches. The value used to be something like ``((), matchdict)``. This functionality was only ever obliquely referred to in change logs; it was never documented as an API. - The ``exception`` attribute of the request now defaults to ``None``. In prior versions, the ``request.exception`` attribute did not exist if an exception was not raised by user code during request processing; it only began existence once an exception view was found. Deprecations ------------ - The ``repoze.bfg.interfaces.IWSGIApplicationCreatedEvent`` event interface was renamed to ``repoze.bfg.interfaces.IApplicationCreated``. Likewise, the ``repoze.bfg.events.WSGIApplicationCreatedEvent`` class was renamed to ``repoze.bfg.events.ApplicationCreated``. The older aliases will continue to work indefinitely. - The ``repoze.bfg.interfaces.IAfterTraversal`` event interface was renamed to ``repoze.bfg.interfaces.IContextFound``. Likewise, the ``repoze.bfg.events.AfterTraversal`` class was renamed to ``repoze.bfg.events.ContextFound``. The older aliases will continue to work indefinitely. - References to the WSGI environment values ``bfg.routes.matchdict`` and ``bfg.routes.route`` were removed from documentation. These will stick around internally for several more releases, but it is ``request.matchdict`` and ``request.matched_route`` are now the "official" way to obtain the matchdict and the route object which resulted in the match. Documentation ------------- - Added documentation for the ``default_permission`` ZCML directive. - Added documentation for the ``default_permission`` constructor value and the ``set_default_permission`` method in the Configurator API documentation. - Added a new section to the "security" chapter named "Setting a Default Permission". - Document ``renderer_globals_factory`` and ``request_factory`` arguments to Configurator constructor. - Added two sections to the "Hooks" chapter of the documentation: "Using Response Callbacks" and "Using Finished Callbacks". - Added documentation of the ``request.exception`` attribute to the ``repoze.bfg.request.Request`` API documentation. - Added glossary entries for "response callback" and "finished callback". - The "Request Processing" narrative chapter has been updated to note finished and response callback steps. - New interface in interfaces API documentation: ``IRoutePregenerator``. - Added a "The Matched Route" section to the URL Dispatch narrative docs chapter, detailing the ``matched_route`` attribute. 1.3a12 (2010-09-08) =================== Bug Fixes --------- - Fix a bug in ``repoze.bfg.url.static_url`` URL generation: if two resource specifications were used to create two separate static views, but they shared a common prefix, it was possible that ``static_url`` would generate an incorrect URL. - Fix another bug in ``repoze.bfg.static_url`` URL generation: too many slashes in generated URL. - Prevent a race condition which could result in a ``RuntimeError`` when rendering a Chameleon template that has not already been rendered once. This would usually occur directly after a restart, when more than one person or thread is trying to execute the same view at the same time: https://bugs.launchpad.net/karl3/+bug/621364 Features -------- - The argument to ``repoze.bfg.configuration.Configurator.add_route`` which was previously called ``path`` is now called ``pattern`` for better explicability. For backwards compatibility purposes, passing a keyword argument named ``path`` to ``add_route`` will still work indefinitely. - The ``path`` attribute to the ZCML ``route`` directive is now named ``pattern`` for better explicability. The older ``path`` attribute will continue to work indefinitely. Documentation ------------- - All narrative, API, and tutorial docs which referred to a route pattern as a ``path`` have now been updated to refer to them as a ``pattern``. - The ``repoze.bfg.interfaces`` API documentation page is now rendered via ``repoze.sphinx.autointerface``. - The URL Dispatch narrative chapter now refers to the ``interfaces`` chapter to explain the API of an ``IRoute`` object. Paster Templates ---------------- - The routesalchemy template has been updated to use ``pattern`` in its route declarations rather than ``path``. Dependencies ------------ - ``tests_require`` now includes ``repoze.sphinx.autointerface`` as a dependency. Internal -------- - Add an API to the ``Configurator`` named ``get_routes_mapper``. This returns an object implementing the ``IRoutesMapper`` interface. - The ``repoze.bfg.urldispatch.RoutesMapper`` object now has a ``get_route`` method which returns a single Route object or ``None``. - A new interface ``repoze.bfg.interfaces.IRoute`` was added. The ``repoze.bfg.urldispatch.Route`` object implements this interface. - The canonical attribute for accessing the routing pattern from a route object is now ``pattern`` rather than ``path``. - Use ``hash()`` rather than ``id()`` when computing the "phash" of a custom route/view predicate in order to allow the custom predicate some control over which predicates are "equal". - Use ``response.headerlist.append`` instead of ``response.headers.add`` in ``repoze.bfg.request.add_global_response_headers`` in case the response is not a WebOb response. - The ``repoze.bfg.urldispatch.Route`` constructor (not an API) now accepts a different ordering of arguments. Previously it was ``(pattern, name, factory=None, predicates=())``. It is now ``(name, pattern, factory=None, predicates=())``. This is in support of consistency with ``configurator.add_route``. - The ``repoze.bfg.urldispatch.RoutesMapper.connect`` method (not an API) now accepts a different ordering of arguments. Previously it was ``(pattern, name, factory=None, predicates=())``. It is now ``(name, pattern, factory=None, predicates=())``. This is in support of consistency with ``configurator.add_route``. 1.3a11 (2010-09-05) =================== Bug Fixes --------- - Process the response callbacks and the NewResponse event earlier, to enable mutations to the response to take effect. 1.3a10 (2010-09-05) =================== Features -------- - A new ``repoze.bfg.request.Request.add_response_callback`` API has been added. This method is documented in the new ``repoze.bfg.request`` API chapter. It can be used to influence response values before a concrete response object has been created. - The ``repoze.bfg.interfaces.INewResponse`` interface now includes a ``request`` attribute; as a result, a handler for INewResponse now has access to the request which caused the response. - Each of the follow methods of the Configurator now allow the below-named arguments to be passed as "dotted name strings" (e.g. "foo.bar.baz") rather than as actual implementation objects that must be imported: setup_registry root_factory, authentication_policy, authorization_policy, debug_logger, locale_negotiator, request_factory, renderer_globals_factory add_subscriber subscriber, iface derive_view view add_view view, ``for_``, context, request_type, containment add_route() view, view_for, factory, ``for_``, view_context scan package add_renderer factory set_forbidden_view view set_notfound_view view set_request_factory factory set_renderer_globals_factory() factory set_locale_negotiator negotiator testing_add_subscriber event_iface Bug Fixes --------- - The route pattern registered internally for a local "static view" (either via the ``static`` ZCML directive or via the ``add_static_view`` method of the configurator) was incorrect. It was regsistered for e.g. ``static*traverse``, while it should have been registered for ``static/*traverse``. Symptom: two static views could not reliably be added to a system when they both shared the same path prefix (e.g. ``/static`` and ``/static2``). Backwards Incompatibilities --------------------------- - The INewResponse event is now not sent to listeners if the response returned by view code (or a renderer) is not a "real" response (e.g. if it does not have ``.status``, ``.headerlist`` and ``.app_iter`` attribtues). Documentation ------------- - Add an API chapter for the ``repoze.bfg.request`` module, which includes documentation for the ``repoze.bfg.request.Request`` class (the "request object"). - Modify the "Request and Response" narrative chapter to reference the new ``repoze.bfg.request`` API chapter. Some content was moved from this chapter into the API documentation itself. - Various changes to denote that Python dotted names are now allowed as input to Configurator methods. Internal -------- - The (internal) feature which made it possible to attach a ``global_response_headers`` attribute to the request (which was assumed to contain a sequence of header key/value pairs which would later be added to the response by the router), has been removed. The functionality of ``repoze.bfg.request.Request.add_response_callback`` takes its place. - The ``repoze.bfg.events.NewResponse`` class's construct has changed: it now must be created with ``(request, response)`` rather than simply ``(response)``. 1.3a9 (2010-08-22) ================== Features -------- - The Configurator now accepts a dotted name *string* to a package as a ``package`` constructor argument. The ``package`` argument was previously required to be a package *object* (not a dotted name string). - The ``repoze.bfg.configuration.Configurator.with_package`` method was added. This method returns a new Configurator using the same application registry as the configurator object it is called upon. The new configurator is created afresh with its ``package`` constructor argument set to the value passed to ``with_package``. This feature will make it easier for future BFG versions to allow dotted names as arguments in places where currently only object references are allowed (the work to allow dotted names isntead of object references everywhere has not yet been done, however). - The new ``repoze.bfg.configuration.Configurator.maybe_dotted`` method resolves a Python dotted name string supplied as its ``dotted`` argument to a global Python object. If the value cannot be resolved, a ``repoze.bfg.configuration.ConfigurationError`` is raised. If the value supplied as ``dotted`` is not a string, the value is returned unconditionally without any resolution attempted. - The new ``repoze.bfg.configuration.Configurator.absolute_resource_spec`` method resolves a potentially relative "resource specification" string into an absolute version. If the value supplied as ``relative_spec`` is not a string, the value is returned unconditionally without any resolution attempted. Backwards Incompatibilities --------------------------- - The functions in ``repoze.bfg.renderers`` named ``render`` and ``render_to_response`` introduced in 1.3a6 previously took a set of ``**values`` arguments for the values to be passed to the renderer. This was wrong, as renderers don't need to accept only dictionaries (they can accept any type of object). Now, the value sent to the renderer must be supplied as a positional argument named ``value``. The ``request`` argument is still a keyword argument, however. - The functions in ``repoze.bfg.renderers`` named ``render`` and ``render_to_response`` now accept an additonal keyword argument named ``package``. - The ``get_renderer`` API in ``repoze.bfg.renderers`` now accepts a ``package`` argument. Documentation ------------- - The ZCML ``include`` directive docs were incorrect: they specified ``filename`` rather than (the correct) ``file`` as an allowable attribute. Internal -------- - The ``repoze.bfg.resource.resolve_resource_spec`` function can now accept a package object as its ``pname`` argument instead of just a package name. - The ``_renderer_factory_from_name`` and ``_renderer_from_name`` methods of the Configurator were removed. These were never APIs. - The ``_render``, ``_render_to_response`` and ``_make_response`` functions with ``repoze.bfg.render`` (added in 1.3a6) have been removed. - A new helper class ``repoze.bfg.renderers.RendererHelper`` was added. - The _map_view function of ``repoze.bfg.configuration`` now takes only a renderer_name argument instead of both a ``renderer`` and ``renderer``_name argument. It also takes a ``package`` argument now. - Use ``imp.get_suffixes`` indirection in ``repoze.bfg.path.package_name`` instead of hardcoded ``.py`` ``.pyc`` and ``.pyo`` to use for comparison when attemtping to decide if a directory is a package. - Make tests runnable again under Jython (although they do not all pass currently). - The reify decorator now maintains the docstring of the function it wraps. 1.3a8 (2010-08-08) ================== Features -------- - New public interface: ``repoze.bfg.exceptions.IExceptionResponse``. This interface is provided by all internal exception classes (such as ``repoze.bfg.exceptions.NotFound`` and ``repoze.bfg.exceptions.Forbidden``), instances of which are both exception objects and can behave as WSGI response objects. This interface is made public so that exception classes which are also valid WSGI response factories can be configured to implement them or exception instances which are also or response instances can be configured to provide them. - New API class: ``repoze.bfg.view.AppendSlashNotFoundViewFactory``. There can only be one Not Found view in any ``repoze.bfg`` application. Even if you use ``repoze.bfg.view.append_slash_notfound_view`` as the Not Found view, ``repoze.bfg`` still must generate a ``404 Not Found`` response when it cannot redirect to a slash-appended URL; this not found response will be visible to site users. If you don't care what this 404 response looks like, and you only need redirections to slash-appended route URLs, you may use the ``repoze.bfg.view.append_slash_notfound_view`` object as the Not Found view. However, if you wish to use a *custom* notfound view callable when a URL cannot be redirected to a slash-appended URL, you may wish to use an instance of the ``repoze.bfg.view.AppendSlashNotFoundViewFactory`` class as the Not Found view, supplying the notfound view callable as the first argument to its constructor. For instance:: from repoze.bfg.exceptions import NotFound from repoze.bfg.view import AppendSlashNotFoundViewFactory def notfound_view(context, request): return HTTPNotFound('It aint there, stop trying!') custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) config.add_view(custom_append_slash, context=NotFound) The ``notfound_view`` supplied must adhere to the two-argument view callable calling convention of ``(context, request)`` (``context`` will be the exception object). Documentation -------------- - Expanded the "Cleaning Up After a Request" section of the URL Dispatch narrative chapter. - Expanded the "Redirecting to Slash-Appended Routes" section of the URL Dispatch narrative chapter. Internal -------- - Previously, two default view functions were registered at Configurator setup (one for ``repoze.bfg.exceptions.NotFound`` named ``default_notfound_view`` and one for ``repoze.bfg.exceptions.Forbidden`` named ``default_forbidden_view``) to render internal exception responses. Those default view functions have been removed, replaced with a generic default view function which is registered at Configurator setup for the ``repoze.bfg.interfaces.IExceptionResponse`` interface that simply returns the exception instance; the ``NotFound`` and ``Forbidden`` classes are now still exception factories but they are also response factories which generate instances that implement the new ``repoze.bfg.interfaces.IExceptionResponse`` interface. 1.3a7 (2010-08-01) ================== Features -------- - The ``repoze.bfg.configuration.Configurator.add_route`` API now returns the route object that was added. - A ``repoze.bfg.events.subscriber`` decorator was added. This decorator decorates module-scope functions, which are then treated as event listeners after a scan() is performed. See the Events narrative documentation chapter and the ``repoze.bfg.events`` module documentation for more information. Bug Fixes --------- - When adding a view for a route which did not yet exist ("did not yet exist" meaning, temporally, a view was added with a route name for a route which had not yet been added via add_route), the value of the ``custom_predicate`` argument to ``add_view`` was lost. Symptom: wrong view matches when using URL dispatch and custom view predicates together. - Pattern matches for a ``:segment`` marker in a URL dispatch route pattern now always match at least one character. See "Backwards Incompatibilities" below in this changelog. Backwards Incompatibilities --------------------------- - A bug existed in the regular expression to do URL matching. As an example, the URL matching machinery would cause the pattern ``/{foo}`` to match the root URL ``/`` resulting in a match dictionary of ``{'foo':u''}`` or the pattern ``/{fud}/edit might match the URL ``//edit`` resulting in a match dictionary of ``{'fud':u''}``. It was always the intent that ``:segment`` markers in the pattern would need to match *at least one* character, and never match the empty string. This, however, means that in certain circumstances, a routing match which your application inadvertently depended upon may no longer happen. Documentation -------------- - Added description of the ``repoze.bfg.events.subscriber`` decorator to the Events narrative chapter. - Added ``repoze.bfg.events.subscriber`` API documentation to ``repoze.bfg.events`` API docs. - Added a section named "Zope 3 Enforces 'TTW' Authorization Checks By Default; BFG Does Not" to the "Design Defense" chapter. 1.3a6 (2010-07-25) ================== Features -------- - New argument to ``repoze.bfg.configuration.Configurator.add_route`` and the ``route`` ZCML directive: ``traverse``. If you would like to cause the ``context`` to be something other than the ``root`` object when this route matches, you can spell a traversal pattern as the ``traverse`` argument. This traversal pattern will be used as the traversal path: traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for ``path``. For example, if the ``path`` provided is ``articles/:article/edit``, and the ``traverse`` argument provided is ``/:article``, when a request comes in that causes the route to match in such a way that the ``article`` match value is '1' (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``1`` during the traversal phase. If the ``1`` object exists, it will become the ``context`` of the request. The Traversal narrative has more information about traversal. If the traversal path contains segment marker names which are not present in the path argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``path``. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in its path. The ``traverse`` argument allows you to associate route patterns with an arbitrary traversal path without using a ``*traverse`` remainder marker; instead you can use other match information. Note that the ``traverse`` argument is ignored when attached to a route that has a ``*traverse`` remainder marker in its path. - A new method of the ``Configurator`` exists: ``set_request_factory``. If used, this method will set the factory used by the ``repoze.bfg`` router to create all request objects. - The ``Configurator`` constructor takes an additional argument: ``request_factory``. If used, this argument will set the factory used by the ``repoze.bfg`` router to create all request objects. - The ``Configurator`` constructor takes an additional argument: ``request_factory``. If used, this argument will set the factory used by the ``repoze.bfg`` router to create all request objects. - A new method of the ``Configurator`` exists: ``set_renderer_globals_factory``. If used, this method will set the factory used by the ``repoze.bfg`` router to create renderer globals. - A new method of the ``Configurator`` exists: ``get_settings``. If used, this method will return the current settings object (performs the same job as the ``repoze.bfg.settings.get_settings`` API). - The ``Configurator`` constructor takes an additional argument: ``renderer_globals_factory``. If used, this argument will set the factory used by the ``repoze.bfg`` router to create renderer globals. - Add ``repoze.bfg.renderers.render``, ``repoze.bfg.renderers.render_to_response`` and ``repoze.bfg.renderers.get_renderer`` functions. These are imperative APIs which will use the same rendering machinery used by view configurations with a ``renderer=`` attribute/argument to produce a rendering or renderer. Because these APIs provide a central API for all rendering, they now form the preferred way to perform imperative template rendering. Using functions named ``render_*`` from modules such as ``repoze.bfg.chameleon_zpt`` and ``repoze.bfg.chameleon_text`` is now discouraged (although not deprecated). The code the backing older templating-system-specific APIs now calls into the newer ``repoze.bfg.renderer`` code. - The ``repoze.bfg.configuration.Configurator.testing_add_template`` has been renamed to ``testing_add_renderer``. A backwards compatibility alias is present using the old name. Documentation ------------- - The ``Hybrid`` narrative chapter now contains a description of the ``traverse`` route argument. - The ``Hooks`` narrative chapter now contains sections about changing the request factory and adding a renderer globals factory. - The API documentation includes a new module: ``repoze.bfg.renderers``. - The ``Templates`` chapter was updated; all narrative that used templating-specific APIs within examples to perform rendering (such as the ``repoze.bfg.chameleon_zpt.render_template_to_response`` method) was changed to use ``repoze.bfg.renderers.render_*`` functions. Bug Fixes --------- - The ``header`` predicate (when used as either a view predicate or a route predicate) had a problem when specified with a name/regex pair. When the header did not exist in the headers dictionary, the regex match could be fed ``None``, causing it to throw a ``TypeError: expected string or buffer`` exception. Now, the predicate returns False as intended. Deprecations ------------ - The ``repoze.bfg.renderers.rendered_response`` function was never an official API, but may have been imported by extensions in the wild. It is officially deprecated in this release. Use ``repoze.bfg.renderers.render_to_response`` instead. - The following APIs are *documentation* deprecated (meaning they are officially deprecated in documentation but do not raise a deprecation error upon their usage, and may continue to work for an indefinite period of time): In the ``repoze.bfg.chameleon_zpt`` module: ``get_renderer``, ``get_template``, ``render_template``, ``render_template_to_response``. The suggested alternatives are documented within the docstrings of those methods (which are still present in the documentation). In the ``repoze.bfg.chameleon_text`` module: ``get_renderer``, ``get_template``, ``render_template``, ``render_template_to_response``. The suggested alternatives are documented within the docstrings of those methods (which are still present in the documentation). In general, to perform template-related functions, one should now use the various methods in the ``repoze.bfg.renderers`` module. Backwards Incompatibilities --------------------------- - A new internal exception class (*not* an API) named ``repoze.bfg.exceptions.PredicateMismatch`` now exists. This exception is currently raised when no constituent view of a multiview can be called (due to no predicate match). Previously, in this situation, a ``repoze.bfg.exceptions.NotFound`` was raised. We provide backwards compatibility for code that expected a ``NotFound`` to be raised when no predicates match by causing ``repoze.bfg.exceptions.PredicateMismatch`` to inherit from ``NotFound``. This will cause any exception view registered for ``NotFound`` to be called when a predicate mismatch occurs, as was the previous behavior. There is however, one perverse case that will expose a backwards incompatibility. If 1) you had a view that was registered as a member of a multiview 2) this view explicitly raised a ``NotFound`` exception *in order to* proceed to the next predicate check in the multiview, that code will now behave differently: rather than skipping to the next view match, a NotFound will be raised to the top-level exception handling machinery instead. For code to be depending upon the behavior of a view raising ``NotFound`` to proceed to the next predicate match, would be tragic, but not impossible, given that ``NotFound`` is a public interface. ``repoze.bfg.exceptions.PredicateMismatch`` is not a public API and cannot be depended upon by application code, so you should not change your view code to raise ``PredicateMismatch``. Instead, move the logic which raised the ``NotFound`` exception in the view out into a custom view predicate. - If, when you run your application's unit test suite under BFG 1.3, a ``KeyError`` naming a template or a ``ValueError`` indicating that a 'renderer factory' is not registered may is raised (e.g. ``ValueError: No factory for renderer named '.pt' when looking up karl.views:templates/snippets.pt``), you may need to perform some extra setup in your test code. The best solution is to use the ``repoze.bfg.configuration.Configurator.testing_add_renderer`` (or, alternately the deprecated ``repoze.bfg.testing.registerTemplateRenderer`` or ``registerDummyRenderer``) API within the code comprising each individual unit test suite to register a "dummy" renderer for each of the templates and renderers used by code under test. For example:: config = Configurator() config.testing_add_renderer('karl.views:templates/snippets.pt') This will register a basic dummy renderer for this particular missing template. The ``testing_add_renderer`` API actually *returns* the renderer, but if you don't care about how the render is used, you don't care about having a reference to it either. A more rough way to solve the issue exists. It causes the "real" template implementations to be used while the system is under test, which is suboptimal, because tests will run slower, and unit tests won't actually *be* unit tests, but it is easier. Always ensure you call the ``setup_registry()`` method of the Configurator . Eg:: reg = MyRegistry() config = Configurator(registry=reg) config.setup_registry() Calling ``setup_registry`` only has an effect if you're *passing in* a ``registry`` argument to the Configurator constructor. ``setup_registry`` is called by the course of normal operations anyway if you do not pass in a ``registry``. If your test suite isn't using a Configurator yet, and is still using the older ``repoze.bfg.testing`` APIs name ``setUp`` or ``cleanUp``, these will register the renderers on your behalf. A variant on the symptom for this theme exists: you may already be dutifully registering a dummy template or renderer for a template used by the code you're testing using ``testing_register_renderer`` or ``registerTemplateRenderer``, but (perhaps unbeknownst to you) the code under test expects to be able to use a "real" template renderer implementation to retrieve or render *another* template that you forgot was being rendered as a side effect of calling the code you're testing. This happened to work because it found the *real* template while the system was under test previously, and now it cannot. The solution is the same. It may also help reduce confusion to use a *resource specification* to specify the template path in the test suite and code rather than a relative path in either. A resource specification is unambiguous, while a relative path needs to be relative to "here", where "here" isn't always well-defined ("here" in a test suite may or may not be the same as "here" in the code under test). 1.3a5 (2010-07-14) ================== Features -------- - New internal exception: ``repoze.bfg.exceptions.URLDecodeError``. This URL is a subclass of the built-in Python exception named ``UnicodeDecodeError``. - When decoding a URL segment to Unicode fails, the exception raised is now ``repoze.bfg.exceptions.URLDecodeError`` instead of ``UnicodeDecodeError``. This makes it possible to register an exception view invoked specifically when ``repoze.bfg`` cannot decode a URL. Bug Fixes --------- - Fix regression in ``repoze.bfg.configuration.Configurator.add_static_view``. Before 1.3a4, view names that contained a slash were supported as route prefixes. 1.3a4 broke this by trying to treat them as full URLs. Documentation ------------- - The ``repoze.bfg.exceptions.URLDecodeError`` exception was added to the exceptions chapter of the API documentation. Backwards Incompatibilities ---------------------------- - in previous releases, when a URL could not be decoded from UTF-8 during traversal, a ``TypeError`` was raised. Now the error which is raised is a ``repoze.bfg.exceptions.URLDecodeError``. 1.3a4 (2010-07-03) ================== Features -------- - Undocumented hook: make ``get_app`` and ``get_root`` of the ``repoze.bfg.paster.BFGShellCommand`` hookable in cases where endware may interfere with the default versions. - In earlier versions, a custom route predicate associated with a url dispatch route (each of the predicate functions fed to the ``custom_predicates`` argument of ``repoze.bfg.configuration.Configurator.add_route``) has always required a 2-positional argument signature, e.g. ``(context, request)``. Before this release, the ``context`` argument was always ``None``. As of this release, the first argument passed to a predicate is now a dictionary conventionally named ``info`` consisting of ``route``, and ``match``. ``match`` is a dictionary: it represents the arguments matched in the URL by the route. ``route`` is an object representing the route which was matched. This is useful when predicates need access to the route match. For example:: def any_of(segment_name, *args): def predicate(info, request): if info['match'][segment_name] in args: return True return predicate num_one_two_or_three = any_of('num, 'one', 'two', 'three') add_route('num', '/:num', custom_predicates=(num_one_two_or_three,)) The ``route`` object is an object that has two useful attributes: ``name`` and ``path``. The ``name`` attribute is the route name. The ``path`` attribute is the route pattern. An example of using the route in a set of route predicates:: def twenty_ten(info, request): if info['route'].name in ('ymd', 'ym', 'y'): return info['match']['year'] == '2010' add_route('y', '/:year', custom_predicates=(twenty_ten,)) add_route('ym', '/:year/:month', custom_predicates=(twenty_ten,)) add_route('ymd', '/:year/:month:/day', custom_predicates=(twenty_ten,)) - The ``repoze.bfg.url.route_url`` API has changed. If a keyword ``_app_url`` is present in the arguments passed to ``route_url``, this value will be used as the protocol/hostname/port/leading path prefix of the generated URL. For example, using an ``_app_url`` of ``http://example.com:8080/foo`` would cause the URL ``http://example.com:8080/foo/fleeb/flub`` to be returned from this function if the expansion of the route pattern associated with the ``route_name`` expanded to ``/fleeb/flub``. - It is now possible to use a URL as the ``name`` argument fed to ``repoze.bfg.configuration.Configurator.add_static_view``. When the name argument is a URL, the ``repoze.bfg.url.static_url`` API will generate join this URL (as a prefix) to a path including the static file name. This makes it more possible to put static media on a separate webserver for production, while keeping static media package-internal and served by the development webserver during development. Documentation ------------- - The authorization chapter of the ZODB Wiki Tutorial (docs/tutorials/bfgwiki) was changed to demonstrate authorization via a group rather than via a direct username (thanks to Alex Marandon). - The authorization chapter of the SQLAlchemy Wiki Tutorial (docs/tutorials/bfgwiki2) was changed to demonstrate authorization via a group rather than via a direct username. - Redirect requests for tutorial sources to http://docs.repoze.org/bfgwiki-1.3 and http://docs.repoze.org/bfgwiki2-1.3/ respectively. - A section named ``Custom Route Predicates`` was added to the URL Dispatch narrative chapter. - The Static Resources chapter has been updated to mention using ``static_url`` to generate URLs to external webservers. Internal -------- - Removed ``repoze.bfg.static.StaticURLFactory`` in favor of a new abstraction revolving around the (still-internal) ``repoze.bfg.static.StaticURLInfo`` helper class. 1.3a3 (2010-05-01) ================== Paster Templates ---------------- - The ``bfg_alchemy`` and ``bfg_routesalchemy`` templates no longer register a ``handle_teardown`` event listener which calls ``DBSession.remove``. This was found by Chris Withers to be unnecessary. Documentation ------------- - The "bfgwiki2" (URL dispatch wiki) tutorial code and documentation was changed to remove the ``handle_teardown`` event listener which calls ``DBSession.remove``. - Any mention of the ``handle_teardown`` event listener as used by the paster templates was removed from the URL Dispatch narrative chapter. - A section entitled Detecting Available Languages was added to the i18n narrative docs chapter. 1.3a2 (2010-04-28) ================== Features -------- - A locale negotiator no longer needs to be registered explicitly. The default locale negotiator at ``repoze.bfg.i18n.default_locale_negotiator`` is now used unconditionally as... um, the default locale negotiator. - The default locale negotiator has become more complex. * First, the negotiator looks for the ``_LOCALE_`` attribute of the request object (possibly set by a view or an event listener). * Then it looks for the ``request.params['_LOCALE_']`` value. * Then it looks for the ``request.cookies['_LOCALE_']`` value. Backwards Incompatibilities --------------------------- - The default locale negotiator now looks for the parameter named ``_LOCALE_`` rather than a parameter named ``locale`` in ``request.params``. Behavior Changes ---------------- - A locale negotiator may now return ``None``, signifying that the default locale should be used. Documentation ------------- - Documentation concerning locale negotiation in the Internationalizationa and Localization chapter was updated. - Expanded portion of i18n narrative chapter docs which discuss working with gettext files. 1.3a1 (2010-04-26) ================== Features -------- - Added "exception views". When you use an exception (anything that inherits from the Python ``Exception`` builtin) as view context argument, e.g.:: from repoze.bfg.view import bfg_view from repoze.bfg.exceptions import NotFound from webob.exc import HTTPNotFound @bfg_view(context=NotFound) def notfound_view(request): return HTTPNotFound() For the above example, when the ``repoze.bfg.exceptions.NotFound`` exception is raised by any view or any root factory, the ``notfound_view`` view callable will be invoked and its response returned. Other normal view predicates can also be used in combination with an exception view registration:: from repoze.bfg.view import bfg_view from repoze.bfg.exceptions import NotFound from webob.exc import HTTPNotFound @bfg_view(context=NotFound, route_name='home') def notfound_view(request): return HTTPNotFound() The above exception view names the ``route_name`` of ``home``, meaning that it will only be called when the route matched has a name of ``home``. You can therefore have more than one exception view for any given exception in the system: the "most specific" one will be called when the set of request circumstances which match the view registration. The only predicate that cannot be not be used successfully is ``name``. The name used to look up an exception view is always the empty string. Existing (pre-1.3) normal views registered against objects inheriting from ``Exception`` will continue to work. Exception views used for user-defined exceptions and system exceptions used as contexts will also work. The feature can be used with any view registration mechanism (``@bfg_view`` decorator, ZCML, or imperative ``config.add_view`` styles). This feature was kindly contributed by Andrey Popp. - Use "Venusian" (`http://docs.repoze.org/venusian `_) to perform ``bfg_view`` decorator scanning rather than relying on a BFG-internal decorator scanner. (Truth be told, Venusian is really just a generalization of the BFG-internal decorator scanner). - Internationalization and localization features as documented in the narrative documentation chapter entitled ``Internationalization and Localization``. - A new deployment setting named ``default_locale_name`` was added. If this string is present as a Paster ``.ini`` file option, it will be considered the default locale name. The default locale name is used during locale-related operations such as language translation. - It is now possible to turn on Chameleon template "debugging mode" for all Chameleon BFG templates by setting a BFG-related Paster ``.ini`` file setting named ``debug_templates``. The exceptions raised by Chameleon templates when a rendering fails are sometimes less than helpful. ``debug_templates`` allows you to configure your application development environment so that exceptions generated by Chameleon during template compilation and execution will contain more helpful debugging information. This mode is on by default in all new projects. - Add a new method of the Configurator named ``derive_view`` which can be used to generate a BFG view callable from a user-supplied function, instance, or class. This useful for external framework and plugin authors wishing to wrap callables supplied by their users which follow the same calling conventions and response conventions as objects that can be supplied directly to BFG as a view callable. See the ``derive_view`` method in the ``repoze.bfg.configuration.Configurator`` docs. ZCML ---- - Add a ``translationdir`` ZCML directive to support localization. - Add a ``localenegotiator`` ZCML directive to support localization. Deprecations ------------ - The exception views feature replaces the need for the ``set_notfound_view`` and ``set_forbidden_view`` methods of the ``Configurator`` as well as the ``notfound`` and ``forbidden`` ZCML directives. Those methods and directives will continue to work for the foreseeable future, but they are deprecated in the documentation. Dependencies ------------ - A new install-time dependency on the ``venusian`` distribution was added. - A new install-time dependency on the ``translationstring`` distribution was added. - Chameleon 1.2.3 or better is now required (internationalization and per-template debug settings). Internal -------- - View registrations and lookups are now done with three "requires" arguments instead of two to accomodate orthogonality of exception views. - The ``repoze.bfg.interfaces.IForbiddenView`` and ``repoze.bfg.interfaces.INotFoundView`` interfaces were removed; they weren't APIs and they became vestigial with the addition of exception views. - Remove ``repoze.bfg.compat.pkgutil_26.py`` and import alias ``repoze.bfg.compat.walk_packages``. These were only required by internal scanning machinery; Venusian replaced the internal scanning machinery, so these are no longer required. Documentation ------------- - Exception view documentation was added to the ``Hooks`` narrative chapter. - A new narrative chapter entitled ``Internationalization and Localization`` was added. - The "Environment Variables and ``ini`` File Settings" chapter was changed: documentation about the ``default_locale_name`` setting was added. - A new API chapter for the ``repoze.bfg.i18n`` module was added. - Documentation for the new ``translationdir`` and ``localenegotiator`` ZCML directives were added. - A section was added to the Templates chapter entitled "Nicer Exceptions in Templates" describing the result of setting ``debug_templates = true``. Paster Templates ---------------- - All paster templates now create a ``setup.cfg`` which includes commands related to nose testing and Babel message catalog extraction/compilation. - A ``default_locale_name = en`` setting was added to each existing paster template. - A ``debug_templates = true`` setting was added to each existing paster template. Licensing --------- - The Edgewall (BSD) license was added to the LICENSES.txt file, as some code in the ``repoze.bfg.i18n`` derives from Babel source. 1.2 (2010-02-10) ================ - No changes from 1.2b6. 1.2b6 (2010-02-06) ================== Backwards Incompatibilities --------------------------- - Remove magical feature of ``repoze.bfg.url.model_url`` which prepended a fully-expanded urldispatch route URL before a the model's path if it was noticed that the request had matched a route. This feature was ill-conceived, and didn't work in all scenarios. Bug Fixes --------- - More correct conversion of provided ``renderer`` values to resource specification values (internal). 1.2b5 (2010-02-04) ================== Bug Fixes --------- - 1.2b4 introduced a bug whereby views added via a route configuration that named a view callable and also a ``view_attr`` became broken. Symptom: ``MyViewClass is not callable`` or the ``__call__`` of a class was being called instead of the method named via ``view_attr``. - Fix a bug whereby a ``renderer`` argument to the ``@bfg_view`` decorator that provided a package-relative template filename might not have been resolved properly. Symptom: inappropriate ``Missing template resource`` errors. 1.2b4 (2010-02-03) ================== Documentation ------------- - Update GAE tutorial to use Chameleon instead of Jinja2 (now that it's possible). Bug Fixes --------- - Ensure that ``secure`` flag for AuthTktAuthenticationPolicy constructor does what it's documented to do (merge Daniel Holth's fancy-cookies-2 branch). Features -------- - Add ``path`` and ``http_only`` options to AuthTktAuthenticationPolicy constructor (merge Daniel Holth's fancy-cookies-2 branch). Backwards Incompatibilities --------------------------- - Remove ``view_header``, ``view_accept``, ``view_xhr``, ``view_path_info``, ``view_request_method``, ``view_request_param``, and ``view_containment`` predicate arguments from the ``Configurator.add_route`` argument list. These arguments were speculative. If you need the features exposed by these arguments, add a view associated with a route using the ``route_name`` argument to the ``add_view`` method instead. - Remove ``view_header``, ``view_accept``, ``view_xhr``, ``view_path_info``, ``view_request_method``, ``view_request_param``, and ``view_containment`` predicate arguments from the ``route`` ZCML directive attribute set. These attributes were speculative. If you need the features exposed by these attributes, add a view associated with a route using the ``route_name`` attribute of the ``view`` ZCML directive instead. Dependencies ------------ - Remove dependency on ``sourcecodegen`` (not depended upon by Chameleon 1.1.1+). 1.2b3 (2010-01-24) ================== Bug Fixes --------- - When "hybrid mode" (both traversal and urldispatch) is in use, default to finding route-related views even if a non-route-related view registration has been made with a more specific context. The default used to be to find views with a more specific context first. Use the new ``use_global_views`` argument to the route definition to get back the older behavior. Features -------- - Add ``use_global_views`` argument to ``add_route`` method of Configurator. When this argument is true, views registered for *no* route will be found if no more specific view related to the route is found. - Add ``use_global_views`` attribute to ZCML ```` directive (see above). Internal -------- - When registering a view, register the view adapter with the "requires" interfaces as ``(request_type, context_type)`` rather than ``(context_type, request_type)``. This provides for saner lookup, because the registration will always be made with a specific request interface, but registration may not be made with a specific context interface. In general, when creating multiadapters, you want to order the requires interfaces so that the elements which are more likely to be registered using specific interfaces are ordered before those which are less likely. 1.2b2 (2010-01-21) ================== Bug Fixes --------- - When the ``Configurator`` is passed an instance of ``zope.component.registry.Components`` as a ``registry`` constructor argument, fix the instance up to have the attributes we expect of an instance of ``repoze.bfg.registry.Registry`` when ``setup_registry`` is called. This makes it possible to use the global Zope component registry as a BFG application registry. - When WebOb 0.9.7.1 was used, a deprecation warning was issued for the class attribute named ``charset`` within ``repoze.bfg.request.Request``. BFG now *requires* WebOb >= 0.9.7, and code was added so that this deprecation warning has disappeared. - Fix a view lookup ordering bug whereby a view with a larger number of predicates registered first (literally first, not "earlier") for a triad would lose during view lookup to one registered with fewer. - Make sure views with exactly N custom predicates are always called before views with exactly N non-custom predicates given all else is equal in the view configuration. Documentation ------------- - Change renderings of ZCML directive documentation. - Add a narrative documentation chapter: "Using the Zope Component Architecture in repoze.bfg". Dependencies ------------ - Require WebOb >= 0.9.7 1.2b1 (2010-01-18) ================== Bug Fixes --------- - In ``bfg_routesalchemy``, ``bfg_alchemy`` paster templates and the ``bfgwiki2`` tutorial, clean up the SQLAlchemy connection by registering a ``repoze.tm.after_end`` callback instead of relying on a ``__del__`` method of a ``Cleanup`` class added to the WSGI environment. The ``__del__`` strategy was fragile and caused problems in the wild. Thanks to Daniel Holth for testing. Features -------- - Read logging configuration from PasteDeploy config file ``loggers`` section (and related) when ``paster bfgshell`` is invoked. Documentation ------------- - Major rework in preparation for book publication. 1.2a11 (2010-01-05) =================== Bug Fixes --------- - Make ``paster bfgshell`` and ``paster create -t bfg_xxx`` work on Jython (fix minor incompatibility with treatment of ``__doc__`` at the class level). - Updated dependency on ``WebOb`` to require a version which supports features now used in tests. Features -------- - Jython compatibility (at least when repoze.bfg.jinja2 is used as the templating engine; Chameleon does not work under Jython). - Show the derived abspath of template resource specifications in the traceback when a renderer template cannot be found. - Show the original traceback when a Chameleon template cannot be rendered due to a platform incompatibility. 1.2a10 (2010-01-04) =================== Features -------- - The ``Configurator.add_view`` method now accepts an argument named ``context``. This is an alias for the older argument named ``for_``; it is preferred over ``for_``, but ``for_`` will continue to be supported "forever". - The ``view`` ZCML directive now accepts an attribute named ``context``. This is an alias for the older attribute named ``for``; it is preferred over ``for``, but ``for`` will continue to be supported "forever". - The ``Configurator.add_route`` method now accepts an argument named ``view_context``. This is an alias for the older argument named ``view_for``; it is preferred over ``view_for``, but ``view_for`` will continue to be supported "forever". - The ``route`` ZCML directive now accepts an attribute named ``view_context``. This is an alias for the older attribute named ``view_for``; it is preferred over ``view_for``, but ``view_for`` will continue to be supported "forever". Documentation and Paster Templates ---------------------------------- - LaTeX rendering tweaks. - All uses of the ``Configurator.add_view`` method that used its ``for_`` argument now use the ``context`` argument instead. - All uses of the ``Configurator.add_route`` method that used its ``view_for`` argument now use the ``view_context`` argument instead. - All uses of the ``view`` ZCML directive that used its ``for`` attribute now use the ``context`` attribute instead. - All uses of the ``route`` ZCML directive that used its ``view_for`` attribute now use the ``view_context`` attribute instead. - Add a (minimal) tutorial dealing with use of ``repoze.catalog`` in a ``repoze.bfg`` application. Documentation Licensing ----------------------- - Loosen the documentation licensing to allow derivative works: it is now offered under the `Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License `_. This is only a documentation licensing change; the ``repoze.bfg`` software continues to be offered under the Repoze Public License at http://repoze.org/license.html (BSD-like). 1.2a9 (2009-12-27) ================== Documentation Licensing ----------------------- - The *documentation* (the result of ``make `` within the ``docs`` directory) in this release is now offered under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License as described by http://creativecommons.org/licenses/by-nc-nd/3.0/us/ . This is only a licensing change for the documentation; the ``repoze.bfg`` software continues to be offered under the Repoze Public License at http://repoze.org/license.html (BSD-like). Documentation ------------- - Added manual index entries to generated index. - Document the previously existing (but non-API) ``repoze.bfg.configuration.Configurator.setup_registry`` method as an official API of a ``Configurator``. - Fix syntax errors in various documentation code blocks. - Created new top-level documentation section: "ZCML Directives". This section contains detailed ZCML directive information, some of which was removed from various narrative chapters. - The LaTeX rendering of the documentation has been improved. - Added a "Fore-Matter" section with author, copyright, and licensing information. 1.2a8 (2009-12-24) ================== Features -------- - Add a ``**kw`` arg to the ``Configurator.add_settings`` API. - Add ``hook_zca`` and ``unhook_zca`` methods to the ``Configurator`` API. - The ``repoze.bfg.testing.setUp`` method now returns a ``Configurator`` instance which can be used to do further configuration during unit tests. Bug Fixes --------- - The ``json`` renderer failed to set the response content type to ``application/json``. It now does, by setting ``request.response_content_type`` unless this attribute is already set. - The ``string`` renderer failed to set the response content type to ``text/plain``. It now does, by setting ``request.response_content_type`` unless this attribute is already set. Documentation ------------- - General documentation improvements by using better Sphinx roles such as "class", "func", "meth", and so on. This means that there are many more hyperlinks pointing to API documentation for API definitions in all narrative, tutorial, and API documentation elements. - Added a description of imperative configuration in various places which only described ZCML configuration. - A syntactical refreshing of various tutorials. - Added the ``repoze.bfg.authentication``, ``repoze.bfg.authorization``, and ``repoze.bfg.interfaces`` modules to API documentation. Deprecations ------------ - The ``repoze.bfg.testing.registerRoutesMapper`` API (added in an early 1.2 alpha) was deprecated. Its import now generates a deprecation warning. 1.2a7 (2009-12-20) ================== Features -------- - Add four new testing-related APIs to the ``repoze.bfg.configuration.Configurator`` class: ``testing_securitypolicy``, ``testing_models``, ``testing_add_subscriber``, and ``testing_add_template``. These were added in order to provide more direct access to the functionality of the ``repoze.bfg.testing`` APIs named ``registerDummySecurityPolicy``, ``registerModels``, ``registerEventListener``, and ``registerTemplateRenderer`` when a configurator is used. The ``testing`` APIs named are nominally deprecated (although they will likely remain around "forever", as they are in heavy use in the wild). - Add a new API to the ``repoze.bfg.configuration.Configurator`` class: ``add_settings``. This API can be used to add "settings" (information returned within via the ``repoze.bfg.settings.get_settings`` API) after the configurator has been initially set up. This is most useful for testing purposes. - Add a ``custom_predicates`` argument to the ``Configurator`` ``add_view`` method, the ``bfg_view`` decorator and the attribute list of the ZCML ``view`` directive. If ``custom_predicates`` is specified, it must be a sequence of predicate callables (a predicate callable accepts two arguments: ``context`` and ``request`` and returns ``True`` or ``False``). The associated view callable will only be invoked if all custom predicates return ``True``. Use one or more custom predicates when no existing predefined predicate is useful. Predefined and custom predicates can be mixed freely. - Add a ``custom_predicates`` argument to the ``Configurator`` ``add_route`` and the attribute list of the ZCML ``route`` directive. If ``custom_predicates`` is specified, it must be a sequence of predicate callables (a predicate callable accepts two arguments: ``context`` and ``request`` and returns ``True`` or ``False``). The associated route will match will only be invoked if all custom predicates return ``True``, else route matching continues. Note that the value ``context`` will always be ``None`` when passed to a custom route predicate. Use one or more custom predicates when no existing predefined predicate is useful. Predefined and custom predicates can be mixed freely. Internal -------- - Remove the ``repoze.bfg.testing.registerTraverser`` function. This function was never an API. Documenation ------------ - Doc-deprecated most helper functions in the ``repoze.bfg.testing`` module. These helper functions likely won't be removed any time soon, nor will they generate a warning any time soon, due to their heavy use in the wild, but equivalent behavior exists in methods of a Configurator. 1.2a6 (2009-12-18) ================== Features -------- - The ``Configurator`` object now has two new methods: ``begin`` and ``end``. The ``begin`` method is meant to be called before any "configuration" begins (e.g. before ``add_view``, et. al are called). The ``end`` method is meant to be called after all "configuration" is complete. Previously, before there was imperative configuration at all (1.1 and prior), configuration begin and end was invariably implied by the process of loading a ZCML file. When a ZCML load happened, the threadlocal data structure containing the request and registry was modified before the load, and torn down after the load, making sure that all framework code that needed ``get_current_registry`` for the duration of the ZCML load was satisfied. Some API methods called during imperative configuration, (such as ``Configurator.add_view`` when a renderer is involved) end up for historical reasons calling ``get_current_registry``. However, in 1.2a5 and below, the Configurator supplied no functionality that allowed people to make sure that ``get_current_registry`` returned the registry implied by the configurator being used. ``begin`` now serves this purpose. Inversely, ``end`` pops the thread local stack, undoing the actions of ``begin``. We make this boundary explicit to reduce the potential for confusion when the configurator is used in different circumstances (e.g. in unit tests and app code vs. just in initial app setup). Existing code written for 1.2a1-1.2a5 which does not call ``begin`` or ``end`` continues to work in the same manner it did before. It is however suggested that this code be changed to call ``begin`` and ``end`` to reduce the potential for confusion in the future. - All ``paster`` templates which generate an application skeleton now make use of the new ``begin`` and ``end`` methods of the Configurator they use in their respective copies of ``run.py`` and ``tests.py``. Documentation ------------- - All documentation that makes use of a ``Configurator`` object to do application setup and test setup now makes use of the new ``begin`` and ``end`` methods of the configurator. Bug Fixes --------- - When a ``repoze.bfg.exceptions.NotFound`` or ``repoze.bfg.exceptions.Forbidden`` *class* (as opposed to instance) was raised as an exception within a root factory (or route root factory), the exception would not be caught properly by the ``repoze.bfg.`` Router and it would propagate to up the call stack, as opposed to rendering the not found view or the forbidden view as would have been expected. - When Chameleon page or text templates used as renderers were added imperatively (via ``Configurator.add_view`` or some derivative), they too-eagerly attempted to look up the ``reload_templates`` setting via ``get_settings``, meaning they were always registered in non-auto-reload-mode (the default). Each now waits until its respective ``template`` attribute is accessed to look up the value. - When a route with the same name as a previously registered route was added, the old route was not removed from the mapper's routelist. Symptom: the old registered route would be used (and possibly matched) during route lookup when it should not have had a chance to ever be used. 1.2a5 (2009-12-10) ================== Features -------- - When the ``repoze.bfg.exceptions.NotFound`` or ``repoze.bfg.exceptions.Forbidden`` error is raised from within a custom root factory or the ``factory`` of a route, the appropriate response is now sent to the requesting user agent (the result of the notfound view or the forbidden view, respectively). When these errors are raised from within a root factory, the ``context`` passed to the notfound or forbidden view will be ``None``. Also, the request will not be decorated with ``view_name``, ``subpath``, ``context``, etc. as would normally be the case if traversal had been allowed to take place. Internals --------- - The exception class representing the error raised by various methods of a ``Configurator`` is now importable as ``repoze.bfg.exceptions.ConfigurationError``. Documentation ------------- - General documentation freshening which takes imperative configuration into account in more places and uses glossary references more liberally. - Remove explanation of changing the request type in a new request event subscriber, as other predicates are now usually an easier way to get this done. - Added "Thread Locals" narrative chapter to documentation, and added a API chapter documenting the ``repoze.bfg.threadlocals`` module. - Added a "Special Exceptions" section to the "Views" narrative documentation chapter explaining the effect of raising ``repoze.bfg.exceptions.NotFound`` and ``repoze.bfg.exceptions.Forbidden`` from within view code. Dependencies ------------ - A new dependency on the ``twill`` package was added to the ``setup.py`` ``tests_require`` argument (Twill will only be downloaded when ``repoze.bfg`` ``setup.py test`` or ``setup.py nosetests`` is invoked). 1.2a4 (2009-12-07) ================== Features -------- - ``repoze.bfg.testing.DummyModel`` now accepts a new constructor keyword argument: ``__provides__``. If this constructor argument is provided, it should be an interface or a tuple of interfaces. The resulting model will then provide these interfaces (they will be attached to the constructed model via ``zope.interface.alsoProvides``). Bug Fixes --------- - Operation on GAE was broken, presumably because the ``repoze.bfg.configuration`` module began to attempt to import the ``repoze.bfg.chameleon_zpt`` and ``repoze.bfg.chameleon_text`` modules, and these cannot be used on non-CPython platforms. It now tolerates startup time import failures for these modules, and only raise an import error when a template from one of these packages is actually used. 1.2a3 (2009-12-02) ================== Bug Fixes --------- - The ``repoze.bfg.url.route_url`` function inappropriately passed along ``_query`` and/or ``_anchor`` arguments to the ``mapper.generate`` function, resulting in blowups. - When two views were registered with differering ``for`` interfaces or classes, and the ``for`` of first view registered was a superclass of the second, the ``repoze.bfg`` view machinery would incorrectly associate the two views with the same "multiview". Multiviews are meant to be collections of views that have *exactly* the same for/request/viewname values, without taking inheritance into account. Symptom: wrong view callable found even when you had correctly specified a ``for_`` interface/class during view configuration for one or both view configurations. Backwards Incompatibilities --------------------------- - The ``repoze.bfg.templating`` module has been removed; it had been deprecated in 1.1 and never actually had any APIs in it. 1.2a2 (2009-11-29) ================== Bug Fixes --------- - The long description of this package (as shown on PyPI) was not valid reStructuredText, and so was not renderable. - Trying to use an HTTP method name string such as ``GET`` as a ``request_type`` predicate argument caused a startup time failure when it was encountered in imperative configuration or in a decorator (symptom: ``Type Error: Required specification must be a specification``). This now works again, although ``request_method`` is now the preferred predicate argument for associating a view configuration with an HTTP request method. Documentation ------------- - Fixed "Startup" narrative documentation chapter; it was explaining "the old way" an application constructor worked. 1.2a1 (2009-11-28) ================== Features -------- - An imperative configuration mode. A ``repoze.bfg`` application can now begin its life as a single Python file. Later, the application might evolve into a set of Python files in a package. Even later, it might start making use of other configuration features, such as ``ZCML``. But neither the use of a package nor the use of non-imperative configuration is required to create a simple ``repoze.bfg`` application any longer. Imperative configuration makes ``repoze.bfg`` competetive with "microframeworks" such as `Bottle `_ and `Tornado `_. ``repoze.bfg`` has a good deal of functionality that most microframeworks lack, so this is hopefully a "best of both worlds" feature. The simplest possible ``repoze.bfg`` application is now:: from webob import Response from wsgiref import simple_server from repoze.bfg.configuration import Configurator def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world) app = config.make_wsgi_app() simple_server.make_server('', 8080, app).serve_forever() - A new class now exists: ``repoze.bfg.configuration.Configurator``. This class forms the basis for sharing machinery between "imperatively" configured applications and traditional declaratively-configured applications. - The ``repoze.bfg.testing.setUp`` function now accepts three extra optional keyword arguments: ``registry``, ``request`` and ``hook_zca``. If the ``registry`` argument is not ``None``, the argument will be treated as the registry that is set as the "current registry" (it will be returned by ``repoze.bfg.threadlocal.get_current_registry``) for the duration of the test. If the ``registry`` argument is ``None`` (the default), a new registry is created and used for the duration of the test. The value of the ``request`` argument is used as the "current request" (it will be returned by ``repoze.bfg.threadlocal.get_current_request``) for the duration of the test; it defaults to ``None``. If ``hook_zca`` is ``True`` (the default), the ``zope.component.getSiteManager`` function will be hooked with a function that returns the value of ``registry`` (or the default-created registry if ``registry`` is ``None``) instead of the registry returned by ``zope.component.getGlobalSiteManager``, causing the Zope Component Architecture API (``getSiteManager``, ``getAdapter``, ``getUtility``, and so on) to use the testing registry instead of the global ZCA registry. - The ``repoze.bfg.testing.tearDown`` function now accepts an ``unhook_zca`` argument. If this argument is ``True`` (the default), ``zope.component.getSiteManager.reset()`` will be called. This will cause the result of the ``zope.component.getSiteManager`` function to be the global ZCA registry (the result of ``zope.component.getGlobalSiteManager``) once again. - The ``run.py`` module in various ``repoze.bfg`` ``paster`` templates now use a ``repoze.bfg.configuration.Configurator`` class instead of the (now-legacy) ``repoze.bfg.router.make_app`` function to produce a WSGI application. Documentation ------------- - The documentation now uses the "request-only" view calling convention in most examples (as opposed to the ``context, request`` convention). This is a documentation-only change; the ``context, request`` convention is also supported and documented, and will be "forever". - ``repoze.bfg.configuration`` API documentation has been added. - A narrative documentation chapter entitled "Creating Your First ``repoze.bfg`` Application" has been added. This chapter details usage of the new ``repoze.bfg.configuration.Configurator`` class, and demonstrates a simplified "imperative-mode" configuration; doing ``repoze.bfg`` application configuration imperatively was previously much more difficult. - A narrative documentation chapter entitled "Configuration, Decorations and Code Scanning" explaining ZCML- vs. imperative- vs. decorator-based configuration equivalence. - The "ZCML Hooks" chapter has been renamed to "Hooks"; it documents how to override hooks now via imperative configuration and ZCML. - The explanation about how to supply an alternate "response factory" has been removed from the "Hooks" chapter. This feature may be removed in a later release (it still works now, it's just not documented). - Add a section entitled "Test Set Up and Tear Down" to the unittesting chapter. Bug Fixes ---------- - The ACL authorization policy debugging output when ``debug_authorization`` console debugging output was turned on wasn't as clear as it could have been when a view execution was denied due to an authorization failure resulting from the set of principals passed never having matched any ACE in any ACL in the lineage. Now in this case, we report ```` as the ACE value and either the root ACL or ```` if no ACL was found. - When two views were registered with the same ``accept`` argument, but were otherwise registered with the same arguments, if a request entered the application which had an ``Accept`` header that accepted *either* of the media types defined by the set of views registered with predicates that otherwise matched, a more or less "random" one view would "win". Now, we try harder to use the view callable associated with the view configuration that has the most specific ``accept`` argument. Thanks to Alberto Valverde for an initial patch. Internals --------- - The routes mapper is no longer a root factory wrapper. It is now consulted directly by the router. - The ``repoze.bfg.registry.make_registry`` callable has been removed. - The ``repoze.bfg.view.map_view`` callable has been removed. - The ``repoze.bfg.view.owrap_view`` callable has been removed. - The ``repoze.bfg.view.predicate_wrap`` callable has been removed. - The ``repoze.bfg.view.secure_view`` callable has been removed. - The ``repoze.bfg.view.authdebug_view`` callable has been removed. - The ``repoze.bfg.view.renderer_from_name`` callable has been removed. Use ``repoze.bfg.configuration.Configurator.renderer_from_name`` instead (still not an API, however). - The ``repoze.bfg.view.derive_view`` callable has been removed. Use ``repoze.bfg.configuration.Configurator.derive_view`` instead (still not an API, however). - The ``repoze.bfg.settings.get_options`` callable has been removed. Its job has been subsumed by the ``repoze.bfg.settings.Settings`` class constructor. - The ``repoze.bfg.view.requestonly`` function has been moved to ``repoze.bfg.configuration.requestonly``. - The ``repoze.bfg.view.rendered_response`` function has been moved to ``repoze.bfg.configuration.rendered_response``. - The ``repoze.bfg.view.decorate_view`` function has been moved to ``repoze.bfg.configuration.decorate_view``. - The ``repoze.bfg.view.MultiView`` class has been moved to ``repoze.bfg.configuration.MultiView``. - The ``repoze.bfg.zcml.Uncacheable`` class has been removed. - The ``repoze.bfg.resource.resource_spec`` function has been removed. - All ZCML directives which deal with attributes which are paths now use the ``path`` method of the ZCML context to resolve a relative name to an absolute one (imperative configuration requirement). - The ``repoze.bfg.scripting.get_root`` API now uses a 'real' WebOb request rather than a FakeRequest when it sets up the request as a threadlocal. - The ``repoze.bfg.traversal.traverse`` API now uses a 'real' WebOb request rather than a FakeRequest when it calls the traverser. - The ``repoze.bfg.request.FakeRequest`` class has been removed. - Most uses of the ZCA threadlocal API (the ``getSiteManager``, ``getUtility``, ``getAdapter``, ``getMultiAdapter`` threadlocal API) have been removed from the core. Instead, when a threadlocal is necessary, the core uses the ``repoze.bfg.threadlocal.get_current_registry`` API to obtain the registry. - The internal ILogger utility named ``repoze.bfg.debug`` is now just an IDebugLogger unnamed utility. A named utility with the old name is registered for b/w compat. - The ``repoze.bfg.interfaces.ITemplateRendererFactory`` interface was removed; it has become unused. - Instead of depending on the ``martian`` package to do code scanning, we now just use our own scanning routines. - We now no longer have a dependency on ``repoze.zcml`` package; instead, the ``repoze.bfg`` package includes implementations of the ``adapter``, ``subscriber`` and ``utility`` directives. - Relating to the following functions: ``repoze.bfg.view.render_view`` ``repoze.bfg.view.render_view_to_iterable`` ``repoze.bfg.view.render_view_to_response`` ``repoze.bfg.view.append_slash_notfound_view`` ``repoze.bfg.view.default_notfound_view`` ``repoze.bfg.view.default_forbidden_view`` ``repoze.bfg.configuration.rendered_response`` ``repoze.bfg.security.has_permission`` ``repoze.bfg.security.authenticated_userid`` ``repoze.bfg.security.effective_principals`` ``repoze.bfg.security.view_execution_permitted`` ``repoze.bfg.security.remember`` ``repoze.bfg.security.forget`` ``repoze.bfg.url.route_url`` ``repoze.bfg.url.model_url`` ``repoze.bfg.url.static_url`` ``repoze.bfg.traversal.virtual_root`` Each of these functions now expects to be called with a request object that has a ``registry`` attribute which represents the current ``repoze.bfg`` registry. They fall back to obtaining the registry from the threadlocal API. Backwards Incompatibilites -------------------------- - Unit tests which use ``zope.testing.cleanup.cleanUp`` for the purpose of isolating tests from one another may now begin to fail due to lack of isolation between tests. Here's why: In repoze.bfg 1.1 and prior, the registry returned by ``repoze.bfg.threadlocal.get_current_registry`` when no other registry had been pushed on to the threadlocal stack was the ``zope.component.globalregistry.base`` global registry (aka the result of ``zope.component.getGlobalSiteManager()``). In repoze.bfg 1.2+, however, the registry returned in this situation is the new module-scope ``repoze.bfg.registry.global_registry`` object. The ``zope.testing.cleanup.cleanUp`` function clears the ``zope.component.globalregistry.base`` global registry unconditionally. However, it does not know about the ``repoze.bfg.registry.global_registry`` object, so it does not clear it. If you use the ``zope.testing.cleanup.cleanUp`` function in the ``setUp`` of test cases in your unit test suite instead of using the (more correct as of 1.1) ``repoze.bfg.testing.setUp``, you will need to replace all calls to ``zope.testing.cleanup.cleanUp`` with a call to ``repoze.bfg.testing.setUp``. If replacing all calls to ``zope.testing.cleanup.cleanUp`` with a call to ``repoze.bfg.testing.setUp`` is infeasible, you can put this bit of code somewhere that is executed exactly **once** (*not* for each test in a test suite; in the `` __init__.py`` of your package or your package's ``tests`` subpackage would be a reasonable place):: import zope.testing.cleanup from repoze.bfg.testing import setUp zope.testing.cleanup.addCleanUp(setUp) - When there is no "current registry" in the ``repoze.bfg.threadlocal.manager`` threadlocal data structure (this is the case when there is no "current request" or we're not in the midst of a ``r.b.testing.setUp``-bounded unit test), the ``.get`` method of the manager returns a data structure containing a *global* registry. In previous releases, this function returned the global Zope "base" registry: the result of ``zope.component.getGlobalSiteManager``, which is an instance of the ``zope.component.registry.Component`` class. In this release, however, the global registry returns a globally importable instance of the ``repoze.bfg.registry.Registry`` class. This registry instance can always be imported as ``repoze.bfg.registry.global_registry``. Effectively, this means that when you call ``repoze.bfg.threadlocal.get_current_registry`` when no request or ``setUp`` bounded unit test is in effect, you will always get back the global registry that lives in ``repoze.bfg.registry.global_registry``. It also means that ``repoze.bfg`` APIs that *call* ``get_current_registry`` will use this registry. This change was made because ``repoze.bfg`` now expects the registry it uses to have a slightly different API than a bare instance of ``zope.component.registry.Components``. - View registration no longer registers a ``repoze.bfg.interfaces.IViewPermission`` adapter (it is no longer checked by the framework; since 1.1, views have been responsible for providing their own security). - The ``repoze.bfg.router.make_app`` callable no longer accepts the ``authentication_policy`` nor the ``authorization_policy`` arguments. This feature was deprecated in version 1.0 and has been removed. - Obscure: the machinery which configured views with a ``request_type`` *and* a ``route_name`` would ignore the request interface implied by ``route_name`` registering a view only for the interface implied by ``request_type``. In the unlikely event that you were trying to use these two features together, the symptom would have been that views that named a ``request_type`` but which were also associated with routes were not found when the route matched. Now if a view is configured with both a ``request_type`` and a ``route_name``, an error is raised. - The ``route`` ZCML directive now no longer accepts the ``request_type`` or ``view_request_type`` attributes. These attributes didn't actually work in any useful way (see entry above this one). - Because the ``repoze.bfg`` package now includes implementations of the ``adapter``, ``subscriber`` and ``utility`` ZCML directives, it is now an error to have ```` in the ZCML of a ``repoze.bfg`` application. A ZCML conflict error will be raised if your ZCML does so. This shouldn't be an issue for "normal" installations; it has always been the responsibility of the ``repoze.bfg.includes`` ZCML to include this file in the past; it now just doesn't. - The ``repoze.bfg.testing.zcml_configure`` API was removed. Use the ``Configurator.load_zcml`` API instead. Deprecations ------------ - The ``repoze.bfg.router.make_app`` function is now nominally deprecated. Its import and usage does not throw a warning, nor will it probably ever disappear. However, using a ``repoze.bfg.configuration.Configurator`` class is now the preferred way to generate a WSGI application. Note that ``make_app`` calls ``zope.component.getSiteManager.sethook( repoze.bfg.threadlocal.get_current_registry)`` on the caller's behalf, hooking ZCA global API lookups, for backwards compatibility purposes. If you disuse ``make_app``, your calling code will need to perform this call itself, at least if your application uses the ZCA global API (``getSiteManager``, ``getAdapter``, etc). Dependencies ------------ - A dependency on the ``martian`` package has been removed (its functionality is replaced internally). - A dependency on the ``repoze.zcml`` package has been removed (its functionality is replaced internally). 1.1.1 (2009-11-21) ================== Bug Fixes --------- - "Hybrid mode" applications (applications which explicitly used traversal *after* url dispatch via ```` paths containing the ``*traverse`` element) were broken in 1.1-final and all 1.1 alpha and beta releases. Views registered without a ``route_name`` route shadowed views registered with a ``route_name`` inappropriately. 1.1 (2009-11-15) ================ Internals --------- - Remove dead IRouteRequirement interface from ``repoze.bfg.zcml`` module. Documentation ------------- - Improve the "Extending an Existing Application" narrative chapter. - Add more sections to the "Defending Design" chapter. 1.1b4 (2009-11-12) ================== Bug Fixes --------- - Use ``alsoProvides`` in the urldispatch module to attach an interface to the request rather than ``directlyProvides`` to avoid disturbing interfaces set in a NewRequest event handler. Documentation ------------- - Move 1.0.1 and previous changelog to HISTORY.txt. - Add examples to ``repoze.bfg.url.model_url`` docstring. - Add "Defending BFG Design" chapter to frontpage docs. Templates --------- - Remove ``ez_setup.py`` and its import from all paster templates, samples, and tutorials for ``distribute`` compatibility. The documentation already explains how to install virtualenv (which will include some ``setuptools`` package), so these files, imports and usages were superfluous. Deprecations ------------ - The ``options`` kw arg to the ``repoze.bfg.router.make_app`` function is deprecated. In its place is the keyword argument ``settings``. The ``options`` keyword continues to work, and a deprecation warning is not emitted when it is detected. However, the paster templates, code samples, and documentation now make reference to ``settings`` rather than ``options``. This change/deprecation was mainly made for purposes of clarity and symmetry with the ``get_settings()`` API and dicussions of "settings" in various places in the docs: we want to use the same name to refer to the same thing everywhere. 1.1b3 (2009-11-06) ================== Features -------- - ``repoze.bfg.testing.registerRoutesMapper`` testing facility added. This testing function registers a routes "mapper" object in the registry, for tests which require its presence. This function is documented in the ``repoze.bfg.testing`` API documentation. Bug Fixes --------- - Compound statements that used an assignment entered into in an interactive IPython session invoked via ``paster bfgshell`` no longer fail to mutate the shell namespace correctly. For example, this set of statements used to fail:: In [2]: def bar(x): return x ...: In [3]: list(bar(x) for x in 'abc') Out[3]: NameError: 'bar' In this release, the ``bar`` function is found and the correct output is now sent to the console. Thanks to Daniel Holth for the patch. - The ``bfgshell`` command did not function properly; it was still expecting to be able to call the root factory with a bare ``environ`` rather than a request object. Backwards Incompatibilities --------------------------- - The ``repoze.bfg.scripting.get_root`` function now expects a ``request`` object as its second argument rather than an ``environ``. 1.1b2 (2009-11-02) ================== Bug Fixes --------- - Prevent PyPI installation failure due to ``easy_install`` trying way too hard to guess the best version of Paste. When ``easy_install`` pulls from PyPI it reads links off various pages to determine "more up to date" versions. It incorrectly picks up a link for an ancient version of a package named "Paste-Deploy-0.1" (note the dash) when trying to find the "Paste" distribution and somehow believes it's the latest version of "Paste". It also somehow "helpfully" decides to check out a version of this package from SVN. We pin the Paste dependency version to a version greater than 1.7 to work around this ``easy_install`` bug. Documentation ------------- - Fix "Hybrid" narrative chapter: stop claiming that ```` statements that mention a route_name need to come afer (in XML order) the ```` statement which creates the route. This hasn't been true since 1.1a1. - "What's New in ``repoze.bfg`` 1.1" document added to narrative documentation. Features -------- - Add a new event type: ``repoze.bfg.events.AfterTraversal``. Events of this type will be sent after traversal is completed, but before any view code is invoked. Like ``repoze.bfg.events.NewRequest``, This event will have a single attribute: ``request`` representing the current request. Unlike the request attribute of ``repoze.bfg.events.NewRequest`` however, during an AfterTraversal event, the request object will possess attributes set by the traverser, most notably ``context``, which will be the context used when a view is found and invoked. The interface ``repoze.bfg.events.IAfterTraversal`` can be used to subscribe to the event. For example:: Like any framework event, a subscriber function should expect one parameter: ``event``. Dependencies ------------ - Rather than depending on ``chameleon.core`` and ``chameleon.zpt`` distributions individually, depend on Malthe's repackaged ``Chameleon`` distribution (which includes both ``chameleon.core`` and ``chameleon.zpt``). 1.1b1 (2009-11-01) ================== Bug Fixes --------- - The routes root factory called route factories and the default route factory with an environ rather than a request. One of the symptoms of this bug: applications generated using the ``bfg_zodb`` paster template in 1.1a9 did not work properly. - Reinstate ``renderer`` alias for ``view_renderer`` in the ```` ZCML directive (in-the-wild 1.1a bw compat). - ``bfg_routesalchemy`` paster template: change ```` declarations: rename ``renderer`` attribute to ``view_renderer``. - Header values returned by the ``authtktauthenticationpolicy`` ``remember`` and ``forget`` methods would be of type ``unicode``. This violated the WSGI spec, causing a ``TypeError`` to be raised when these headers were used under ``mod_wsgi``. - If a BFG app that had a route matching the root URL was mounted under a path in modwsgi, ala ``WSGIScriptAlias /myapp /Users/chrism/projects/modwsgi/env/bfg.wsgi``, the home route (a route with the path of ``'/'`` or ``''``) would not match when the path ``/myapp`` was visited (only when the path ``/myapp/`` was visited). This is now fixed: if the urldispatch root factory notes that the PATH_INFO is empty, it converts it to a single slash before trying to do matching. Documentation ------------- - In ```` declarations in tutorial ZCML, rename ``renderer`` attribute to ``view_renderer`` (fwd compat). - Fix various tutorials broken by 1.1a9 ```` directive changes. Internal -------- - Deal with a potential circref in the traversal module. 1.1a9 (2009-10-31) ================== Bug Fixes --------- - An incorrect ZCML conflict would be encountered when the ``request_param`` predicate attribute was used on the ZCML ``view`` directive if any two otherwise same-predicated views had the combination of a predicate value with an ``=`` sign and one without (e.g. ``a`` vs. ``a=123``). Features -------- - In previous versions of BFG, the "root factory" (the ``get_root`` callable passed to ``make_app`` or a function pointed to by the ``factory`` attribute of a route) was called with a "bare" WSGI environment. In this version, and going forward, it will be called with a ``request`` object. The request object passed to the factory implements dictionary-like methods in such a way that existing root factory code which expects to be passed an environ will continue to work. - The ``__call__`` of a plugin "traverser" implementation (registered as an adapter for ``ITraverser`` or ``ITraverserFactory``) will now receive a *request* as the single argument to its ``__call__`` method. In previous versions it was passed a WSGI ``environ`` object. The request object passed to the factory implements dictionary-like methods in such a way that existing traverser code which expects to be passed an environ will continue to work. - The ZCML ``route`` directive's attributes ``xhr``, ``request_method``, ``path_info``, ``request_param``, ``header`` and ``accept`` are now *route* predicates rather than *view* predicates. If one or more of these predicates is specified in the route configuration, all of the predicates must return true for the route to match a request. If one or more of the route predicates associated with a route returns ``False`` when checked during a request, the route match fails, and the next match in the routelist is tried. This differs from the previous behavior, where no route predicates existed and all predicates were considered view predicates, because in that scenario, the next route was not tried. Documentation ------------- - Various changes were made to narrative and API documentation supporting the change from passing a request rather than an environ to root factories and traversers. Internal -------- - The request implements dictionary-like methods that mutate and query the WSGI environ. This is only for the purpose of backwards compatibility with root factories which expect an ``environ`` rather than a request. - The ``repoze.bfg.request.create_route_request_factory`` function, which returned a request factory was removed in favor of a ``repoze.bfg.request.route_request_interface`` function, which returns an interface. - The ``repoze.bfg.request.Request`` class, which is a subclass of ``webob.Request`` now defines its own ``__setattr__``, ``__getattr__`` and ``__delattr__`` methods, which override the default WebOb behavior. The default WebOb behavior stores attributes of the request in ``self.environ['webob.adhoc_attrs']``, and retrieves them from that dictionary during a ``__getattr__``. This behavior was undesirable for speed and "expectation" reasons. Now attributes of the ``request`` are stored in ``request.__dict__`` (as you otherwise might expect from an object that did not override these methods). - The router no longer calls ``repoze.bfg.traversal._traverse`` and does its work "inline" (speed). - Reverse the order in which the router calls the request factory and the root factory. The request factory is now called first; the resulting request is passed to the root factory. - The ``repoze.bfg.request.request_factory`` function has been removed. Its functionality is no longer required. - The "routes root factory" that wraps the default root factory when there are routes mentioned in the configuration now attaches an interface to the request via ``zope.interface.directlyProvides``. This replaces logic in the (now-gone) ``repoze.bfg.request.request_factory`` function. - The ``route`` and ``view`` ZCML directives now register an interface as a named utility (retrieved from ``repoze.bfg.request.route_request_interface``) rather than a request factory (the previous return value of the now-missing ``repoze.bfg.request.create_route_request_factory``. - The ``repoze.bfg.functional`` module was renamed to ``repoze.bfg.compat``. Backwards Incompatibilities --------------------------- - Explicitly revert the feature introduced in 1.1a8: where the name ``root`` is available as an attribute of the request before a NewRequest event is emitted. This makes some potential future features impossible, or at least awkward (such as grouping traversal and view lookup into a single adapter lookup). - The ``containment``, ``attr`` and ``renderer`` attributes of the ``route`` ZCML directive were removed. 1.1a8 (2009-10-27) ================== Features -------- - Add ``path_info`` view configuration predicate. - ``paster bfgshell`` now supports IPython if it's available for import. Thanks to Daniel Holth for the initial patch. - Add ``repoze.bfg.testing.registerSettings`` API, which is documented in the "repoze.bfg.testing" API chapter. This allows for registration of "settings" values obtained via ``repoze.bfg.settings.get_settings()`` for use in unit tests. - The name ``root`` is available as an attribute of the request slightly earlier now (before a NewRequest event is emitted). ``root`` is the result of the application "root factory". - Added ``max_age`` parameter to ``authtktauthenticationpolicy`` ZCML directive. If this value is set, it must be an integer representing the number of seconds which the auth tkt cookie will survive. Mainly, its existence allows the auth_tkt cookie to survive across browser sessions. Bug Fixes --------- - Fix bug encountered during "scan" (when ```` directive is used in ZCML) introduced in 1.1a7. Symptom: ``AttributeError: object has no attribute __provides__`` raised at startup time. - The ``reissue_time`` argument to the ``authtktauthenticationpolicy`` ZCML directive now actually works. When it is set to an integer value, an authticket set-cookie header is appended to the response whenever a request requires authentication and 'now' minus the authticket's timestamp is greater than ``reissue_time`` seconds. Documentation ------------- - Add a chapter titled "Request and Response" to the narrative documentation, content cribbed from the WebOb documentation. - Call out predicate attributes of ZCML directive within "Views" chapter. - Fix route_url documentation (``_query`` argument documented as ``query`` and ``_anchor`` argument documented as ``anchor``). Backwards Incompatibilities --------------------------- - The ``authtkt`` authentication policy ``remember`` method now no longer honors ``token`` or ``userdata`` keyword arguments. Internal -------- - Change how ``bfg_view`` decorator works when used as a class method decorator. In 1.1a7, the``scan``directive actually tried to grope every class in scanned package at startup time, calling ``dir`` against each found class, and subsequently invoking ``getattr`` against each thing found by ``dir`` to see if it was a method. This led to some strange symptoms (e.g. ``AttributeError: object has no attribute __provides__``), and was generally just a bad idea. Now, instead of groping classes for methods at startup time, we just cause the ``bfg_view`` decorator itself to populate the method's class' ``__dict__`` when it is used as a method decorator. This also requires a nasty _getframe thing but it's slightly less nasty than the startup time groping behavior. This is essentially a reversion back to 1.1a6 "grokking" behavior plus some special magic for using the ``bfg_view`` decorator as method decorator inside the ``bfg_view`` class itself. - The router now checks for a ``global_response_headers`` attribute of the request object before returning a response. If this value exists, it is presumed to be a sequence of two-tuples, representing a set of headers to append to the 'normal' response headers. This feature is internal, rather than exposed externally, because it's unclear whether it will stay around in the long term. It was added to support the ``reissue_time`` feature of the authtkt authentication policy. - The interface ITraverserFactory is now just an alias for ITraverser. 1.1a7 (2009-10-18) ================== Features -------- - More than one ``@bfg_view`` decorator may now be stacked on top of any number of others. Each invocation of the decorator registers a single view configuration. For instance, the following combination of decorators and a function will register two view configurations for the same view callable:: from repoze.bfg.view import bfg_view @bfg_view(name='edit') @bfg_view(name='change') def edit(context, request): pass This makes it possible to associate more than one view configuration with a single callable without requiring any ZCML. - The ``@bfg_view`` decorator can now be used against a class method:: from webob import Response from repoze.bfg.view import bfg_view class MyView(object): def __init__(self, context, request): self.context = context self.request = request @bfg_view(name='hello') def amethod(self): return Response('hello from %s!' % self.context) When the bfg_view decorator is used against a class method, a view is registered for the *class* (it's a "class view" where the "attr" happens to be the name of the method it is attached to), so the class it's defined within must have a suitable constructor: one that accepts ``context, request`` or just ``request``. Documentation ------------- - Added ``Changing the Traverser`` and ``Changing How :mod:`repoze.bfg.url.model_url` Generates a URL`` to the "Hooks" narrative chapter of the docs. Internal -------- - Remove ``ez_setup.py`` and imports of it within ``setup.py``. In the new world, and as per virtualenv setup instructions, people will already have either setuptools or distribute. 1.1a6 (2009-10-15) ================== Features -------- - Add ``xhr``, ``accept``, and ``header`` view configuration predicates to ZCML view declaration, ZCML route declaration, and ``bfg_view`` decorator. See the ``Views`` narrative documentation chapter for more information about these predicates. - Add ``setUp`` and ``tearDown`` functions to the ``repoze.bfg.testing`` module. Using ``setUp`` in a test setup and ``tearDown`` in a test teardown is now the recommended way to do component registry setup and teardown. Previously, it was recommended that a single function named ``repoze.bfg.testing.cleanUp`` be called in both the test setup and tear down. ``repoze.bfg.testing.cleanUp`` still exists (and will exist "forever" due to its widespread use); it is now just an alias for ``repoze.bfg.testing.setUp`` and is nominally deprecated. - The BFG component registry is now available in view and event subscriber code as an attribute of the request ie. ``request.registry``. This fact is currently undocumented except for this note, because BFG developers never need to interact with the registry directly anywhere else. - The BFG component registry now inherits from ``dict``, meaning that it can optionally be used as a simple dictionary. *Component* registrations performed against it via e.g. ``registerUtility``, ``registerAdapter``, and similar API methods are kept in a completely separate namespace than its dict members, so using the its component API methods won't effect the keys and values in the dictionary namespace. Likewise, though the component registry "happens to be" a dictionary, use of mutating dictionary methods such as ``__setitem__`` will have no influence on any component registrations made against it. In other words, the registry object you obtain via e.g. ``repoze.bfg.threadlocal.get_current_registry`` or ``request.registry`` happens to be both a component registry and a dictionary, but using its component-registry API won't impact data added to it via its dictionary API and vice versa. This is a forward compatibility move based on the goals of "marco". - Expose and document ``repoze.bfg.testing.zcml_configure`` API. This function populates a component registry from a ZCML file for testing purposes. It is documented in the "Unit and Integration Testing" chapter. Documentation ------------- - Virtual hosting narrative docs chapter updated with info about ``mod_wsgi``. - Point all index URLs at the literal 1.1 index (this alpha cycle may go on a while). - Various tutorial test modules updated to use ``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown`` methods in order to encourage this as best practice going forward. - Added "Creating Integration Tests" section to unit testing narrative documentation chapter. As a result, the name of the unittesting chapter is now "Unit and Integration Testing". Backwards Incompatibilities --------------------------- - Importing ``getSiteManager`` and ``get_registry`` from ``repoze.bfg.registry`` is no longer supported. These imports were deprecated in repoze.bfg 1.0. Import of ``getSiteManager`` should be done as ``from zope.component import getSiteManager``. Import of ``get_registry`` should be done as ``from repoze.bfg.threadlocal import get_current_registry``. This was done to prevent a circular import dependency. - Code bases which alternately invoke both ``zope.testing.cleanup.cleanUp`` and ``repoze.bfg.testing.cleanUp`` (treating them equivalently, using them interchangeably) in the setUp/tearDown of unit tests will begin to experience test failures due to lack of test isolation. The "right" mechanism is ``repoze.bfg.testing.cleanUp`` (or the combination of ``repoze.bfg.testing.setUp`` and ``repoze.bfg.testing.tearDown``). but a good number of legacy codebases will use ``zope.testing.cleanup.cleanUp`` instead. We support ``zope.testing.cleanup.cleanUp`` but not in combination with ``repoze.bfg.testing.cleanUp`` in the same codebase. You should use one or the other test cleanup function in a single codebase, but not both. Internal -------- - Created new ``repoze.bfg.configuration`` module which assumes responsibilities previously held by the ``repoze.bfg.registry`` and ``repoze.bfg.router`` modules (avoid a circular import dependency). - The result of the ``zope.component.getSiteManager`` function in unit tests set up with ``repoze.bfg.testing.cleanUp`` or ``repoze.bfg.testing.setUp`` will be an instance of ``repoze.bfg.registry.Registry`` instead of the global ``zope.component.globalregistry.base`` registry. This also means that the threadlocal ZCA API functions such as ``getAdapter`` and ``getUtility`` as well as internal BFG machinery (such as ``model_url`` and ``route_url``) will consult this registry within unit tests. This is a forward compatibility move based on the goals of "marco". - Removed ``repoze.bfg.testing.addCleanUp`` function and associated module-scope globals. This was never an API. 1.1a5 (2009-10-10) ================== Documentation ------------- - Change "Traversal + ZODB" and "URL Dispatch + SQLAlchemy" Wiki tutorials to make use of the new-to-1.1 "renderer" feature (return dictionaries from all views). - Add tests to the "URL Dispatch + SQLAlchemy" tutorial after the "view" step. - Added a diagram of model graph traversal to the "Traversal" narrative chapter of the documentation. - An ``exceptions`` API chapter was added, documenting the new ``repoze.bfg.exceptions`` module. - Describe "request-only" view calling conventions inside the urldispatch narrative chapter, where it's most helpful. - Add a diagram which explains the operation of the BFG router to the "Router" narrative chapter. Features -------- - Add a new ``repoze.bfg.testing`` API: ``registerRoute``, for registering routes to satisfy calls to e.g. ``repoze.bfg.url.route_url`` in unit tests. - The ``notfound`` and ``forbidden`` ZCML directives now accept the following addtional attributes: ``attr``, ``renderer``, and ``wrapper``. These have the same meaning as they do in the context of a ZCML ``view`` directive. - For behavior like Django's ``APPEND_SLASH=True``, use the ``repoze.bfg.view.append_slash_notfound_view`` view as the Not Found view in your application. When this view is the Not Found view (indicating that no view was found), and any routes have been defined in the configuration of your application, if the value of ``PATH_INFO`` does not already end in a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this will *lose* ``POST`` data information (turning it into a GET), so you shouldn't rely on this to redirect POST requests. - Speed up ``repoze.bfg.location.lineage`` slightly. - Speed up ``repoze.bfg.encode.urlencode`` (nee' ``repoze.bfg.url.urlencode``) slightly. - Speed up ``repoze.bfg.traversal.model_path``. - Speed up ``repoze.bfg.traversal.model_path_tuple`` slightly. - Speed up ``repoze.bfg.traversal.traverse`` slightly. - Speed up ``repoze.bfg.url.model_url`` slightly. - Speed up ``repoze.bfg.url.route_url`` slightly. - Sped up ``repoze.bfg.traversal.ModelGraphTraverser:__call__`` slightly. - Minor speedup of ``repoze.bfg.router.Router.__call__``. - New ``repoze.bfg.exceptions`` module was created to house exceptions that were previously sprinkled through various modules. Internal -------- - Move ``repoze.bfg.traversal._url_quote`` into ``repoze.bfg.encode`` as ``url_quote``. Deprecations ------------ - The import of ``repoze.bfg.view.NotFound`` is deprecated in favor of ``repoze.bfg.exceptions.NotFound``. The old location still functions, but emits a deprecation warning. - The import of ``repoze.bfg.security.Unauthorized`` is deprecated in favor of ``repoze.bfg.exceptions.Forbidden``. The old location still functions but emits a deprecation warning. The rename from ``Unauthorized`` to ``Forbidden`` brings parity to the name of the exception and the system view it invokes when raised. Backwards Incompatibilities --------------------------- - We previously had a Unicode-aware wrapper for the ``urllib.urlencode`` function named ``repoze.bfg.url.urlencode`` which delegated to the stdlib function, but which marshalled all unicode values to utf-8 strings before calling the stdlib version. A newer replacement now lives in ``repoze.bfg.encode`` The replacement does not delegate to the stdlib. The replacement diverges from the stdlib implementation and the previous ``repoze.bfg.url`` url implementation inasmuch as its ``doseq`` argument is now a decoy: it always behaves in the ``doseq=True`` way (which is the only sane behavior) for speed purposes. The old import location (``repoze.bfg.url.urlencode``) still functions and has not been deprecated. - In 0.8a7, the return value expected from an object implementing ``ITraverserFactory`` was changed from a sequence of values to a dictionary containing the keys ``context``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, ``virtual_root_path``, and ``root``. Until now, old-style traversers which returned a sequence have continued to work but have generated a deprecation warning. In this release, traversers which return a sequence instead of a dictionary will no longer work. 1.1a4 (2009-09-23) ================== Bug Fixes --------- - On 64-bit Linux systems, views that were members of a multiview (orderings of views with predicates) were not evaluated in the proper order. Symptom: in a configuration that had two views with the same name but one with a ``request_method=POST`` predicate and one without, the one without the predicate would be called unconditionally (even if the request was a POST request). Thanks much to Sebastien Douche for providing the buildbots that pointed this out. Documentation ------------- - Added a tutorial which explains how to use ``repoze.session`` (ZODB-based sessions) in a ZODB-based repoze.bfg app. - Added a tutorial which explains how to add ZEO to a ZODB-based ``repoze.bfg`` application. - Added a tutorial which explains how to run a ``repoze.bfg`` application under `mod_wsgi `_. See "Running a repoze.bfg Application under mod_wsgi" in the tutorials section of the documentation. Features -------- - Add a ``repoze.bfg.url.static_url`` API which is capable of generating URLs to static resources defined by the ```` ZCML directive. See the "Views" narrative chapter's section titled "Generating Static Resource URLs" for more information. - Add a ``string`` renderer. This renderer converts a non-Response return value of any view callble into a string. It is documented in the "Views" narrative chapter. - Give the ``route`` ZCML directive the ``view_attr`` and ``view_renderer`` parameters (bring up to speed with 1.1a3 features). These can also be spelled as ``attr`` and ``renderer``. Backwards Incompatibilities --------------------------- - An object implementing the ``IRenderer`` interface (and ``ITemplateRenderer`, which is a subclass of ``IRenderer``) must now accept an extra ``system`` argument in its ``__call__`` method implementation. Values computed by the system (as opposed to by the view) are passed by the system in the ``system`` parameter, which will always be a dictionary. Keys in the dictionary include: ``view`` (the view object that returned the value), ``renderer_name`` (the template name or simple name of the renderer), ``context`` (the context object passed to the view), and ``request`` (the request object passed to the view). Previously only ITemplateRenderers received system arguments as elements inside the main ``value`` dictionary. Internal -------- - The way ``bfg_view`` declarations are scanned for has been modified. This should have no external effects. - Speed: do not register an ITraverserFactory in configure.zcml; instead rely on queryAdapter and a manual default to ModelGraphTraverser. - Speed: do not register an IContextURL in configure.zcml; instead rely on queryAdapter and a manual default to TraversalContextURL. - General speed microimprovements for helloworld benchmark: replace try/excepts with statements which use 'in' keyword. 1.1a3 (2009-09-16) ================== Documentation ------------- - The "Views" narrative chapter in the documentation has been updated extensively to discuss "renderers". Features -------- - A ``renderer`` attribute has been added to view configurations, replacing the previous (1.1a2) version's ``template`` attribute. A "renderer" is an object which accepts the return value of a view and converts it to a string. This includes, but is not limited to, templating systems. - A new interface named ``IRenderer`` was added. The existing interface, ``ITemplateRenderer`` now derives from this new interface. This interface is internal. - A new interface named ``IRendererFactory`` was added. An existing interface named ``ITemplateRendererFactory`` now derives from this interface. This interface is internal. - The ``view`` attribute of the ``view`` ZCML directive is no longer required if the ZCML directive also has a ``renderer`` attribute. This is useful when the renderer is a template renderer and no names need be passed to the template at render time. - A new zcml directive ``renderer`` has been added. It is documented in the "Views" narrative chapter of the documentation. - A ZCML ``view`` directive (and the associated ``bfg_view`` decorator) can now accept a "wrapper" value. If a "wrapper" value is supplied, it is the value of a separate view's *name* attribute. When a view with a ``wrapper`` attribute is rendered, the "inner" view is first rendered normally. Its body is then attached to the request as "wrapped_body", and then a wrapper view name is looked up and rendered (using ``repoze.bfg.render_view_to_response``), passed the request and the context. The wrapper view is assumed to do something sensible with ``request.wrapped_body``, usually inserting its structure into some other rendered template. This feature makes it possible to specify (potentially nested) "owrap" relationships between views using only ZCML or decorators (as opposed always using ZPT METAL and analogues to wrap view renderings in outer wrappers). Dependencies ------------ - When used under Python < 2.6, BFG now has an installation time dependency on the ``simplejson`` package. Deprecations ------------ - The ``repoze.bfg.testing.registerDummyRenderer`` API has been deprecated in favor of ``repoze.bfg.testing.registerTemplateRenderer``. A deprecation warning is *not* issued at import time for the former name; it will exist "forever"; its existence has been removed from the documentation, however. - The ``repoze.bfg.templating.renderer_from_cache`` function has been moved to ``repoze.bfg.renderer.template_renderer_factory``. This was never an API, but code in the wild was spotted that used it. A deprecation warning is issued at import time for the former. Backwards Incompatibilities --------------------------- - The ``ITemplateRenderer`` interface has been changed. Previously its ``__call__`` method accepted ``**kw``. It now accepts a single positional parameter named ``kw`` (REVISED: it accepts two positional parameters as of 1.1a4: ``value`` and ``system``). This is mostly an internal change, but it was exposed in APIs in one place: if you've used the ``repoze.bfg.testing.registerDummyRenderer`` API in your tests with a custom "renderer" argument with your own renderer implementation, you will need to change that renderer implementation to accept ``kw`` instead of ``**kw`` in its ``__call__`` method (REVISED: make it accept ``value`` and ``system`` positional arguments as of 1.1a4). - The ``ITemplateRendererFactory`` interface has been changed. Previously its ``__call__`` method accepted an ``auto_reload`` keyword parameter. Now its ``__call__`` method accepts no keyword parameters. Renderers are now themselves responsible for determining details of auto-reload. This is purely an internal change. This interface was never external. - The ``template_renderer`` ZCML directive introduced in 1.1a2 has been removed. It has been replaced by the ``renderer`` directive. - The previous release (1.1a2) added a view configuration attribute named ``template``. In this release, the attribute has been renamed to ``renderer``. This signifies that the attribute is more generic: it can now be not just a template name but any renderer name (ala ``json``). - In the previous release (1.1a2), the Chameleon text template renderer was used if the system didn't associate the ``template`` view configuration value with a filename with a "known" extension. In this release, you must use a ``renderer`` attribute which is a path that ends with a ``.txt`` extension (e.g. ``templates/foo.txt``) to use the Chameleon text renderer. 1.1a2 (2009-09-14) ================== Features -------- - A ZCML ``view`` directive (and the associated ``bfg_view`` decorator) can now accept an "attr" value. If an "attr" value is supplied, it is considered a method named of the view object to be called when the response is required. This is typically only good for views that are classes or instances (not so useful for functions, as functions typically have no methods other than ``__call__``). - A ZCML ``view`` directive (and the associated ``bfg_view`` decorator) can now accept a "template" value. If a "template" value is supplied, and the view callable returns a dictionary, the associated template is rendered with the dictionary as keyword arguments. See the section named "Views That Have a ``template``" in the "Views" narrative documentation chapter for more information. 1.1a1 (2009-09-06) ================== Bug Fixes --------- - "tests" module removed from the bfg_alchemy paster template; these tests didn't work. - Bugfix: the ``discriminator`` for the ZCML "route" directive was incorrect. It was possible to register two routes that collided without the system spitting out a ConfigurationConflictError at startup time. Features -------- - Feature addition: view predicates. These are exposed as the ``request_method``, ``request_param``, and ``containment`` attributes of a ZCML ``view`` declaration, or the respective arguments to a ``@bfg_view`` decorator. View predicates can be used to register a view for a more precise set of environment parameters than was previously possible. For example, you can register two views with the same ``name`` with different ``request_param`` attributes. If the ``request.params`` dict contains 'foo' (request_param="foo"), one view might be called; if it contains 'bar' (request_param="bar"), another view might be called. ``request_param`` can also name a key/value pair ala ``foo=123``. This will match only when the ``foo`` key is in the request.params dict and it has the value '123'. This particular example makes it possible to write separate view functions for different form submissions. The other predicates, ``containment`` and ``request_method`` work similarly. ``containment`` is a view predicate that will match only when the context's graph lineage has an object possessing a particular class or interface, for example. ``request_method`` is a view predicate that will match when the HTTP ``REQUEST_METHOD`` equals some string (eg. 'POST'). - The ``@bfg_view`` decorator now accepts three additional arguments: ``request_method``, ``request_param``, and ``containment``. ``request_method`` is used when you'd like the view to match only a request with a particular HTTP ``REQUEST_METHOD``; a string naming the ``REQUEST_METHOD`` can also be supplied as ``request_type`` for backwards compatibility. ``request_param`` is used when you'd like a view to match only a request that contains a particular ``request.params`` key (with or without a value). ``containment`` is used when you'd like to match a request that has a context that has some class or interface in its graph lineage. These are collectively known as "view predicates". - The ``route`` ZCML directive now honors ``view_request_method``, ``view_request_param`` and ``view_containment`` attributes, which pass along these values to the associated view if any is provided. Additionally, the ``request_type`` attribute can now be spelled as ``view_request_type``, and ``permission`` can be spelled as ``view_permission``. Any attribute which starts with ``view_`` can now be spelled without the ``view_`` prefix, so ``view_for`` can be spelled as ``for`` now, etc. Both forms are documented in the urldispatch narraitve documentation chapter. - The ``request_param`` ZCML view directive attribute (and its ``bfg_view`` decorator cousin) can now specify both a key and a value. For example, ``request_param="foo=123"`` means that the foo key must have a value of ``123`` for the view to "match". - Allow ``repoze.bfg.traversal.find_interface`` API to use a class object as the argument to compare against the ``model`` passed in. This means you can now do ``find_interface(model, SomeClass)`` and the first object which is found in the lineage which has ``SomeClass`` as its class (or the first object found which has ``SomeClass`` as any of its superclasses) will be returned. - Added ``static`` ZCML directive which registers a route for a view that serves up files in a directory. See the "Views" narrative documentation chapter's "Serving Static Resources Using a ZCML Directive" section for more information. - The ``repoze.bfg.view.static`` class now accepts a string as its first argument ("root_dir") that represents a package-relative name e.g. ``somepackage:foo/bar/static``. This is now the preferred mechanism for spelling package-relative static paths using this class. A ``package_name`` keyword argument has been left around for backwards compatibility. If it is supplied, it will be honored. - The API ``repoze.bfg.testing.registerView`` now takes a ``permission`` argument. Use this instead of using ``repoze.bfg.testing.registerViewPermission``. - The ordering of route declarations vs. the ordering of view declarations that use a "route_name" in ZCML no longer matters. Previously it had been impossible to use a route_name from a route that had not yet been defined in ZCML (order-wise) within a "view" declaration. - The repoze.bfg router now catches both ``repoze.bfg.security.Unauthorized`` and ``repoze.bfg.view.NotFound`` exceptions while rendering a view. When the router catches an ``Unauthorized``, it returns the registered forbidden view. When the router catches a ``NotFound``, it returns the registered notfound view. Internal -------- - Change urldispatch internals: Route object is now constructed using a path, a name, and a factory instead of a name, a matcher, a generator, and a factory. - Move (non-API) default_view, default_forbidden_view, and default_notfound_view functions into the ``repoze.bfg.view`` module (moved from ``repoze.bfg.router``). - Removed ViewPermissionFactory from ``repoze.bfg.security``. View permission checking is now done by registering and looking up an ISecuredView. - The ``static`` ZCML directive now uses a custom root factory when constructing a route. - The interface ``IRequestFactories`` was removed from the repoze.bfg.interfaces module. This interface was never an API. - The function named ``named_request_factories`` and the data structure named ``DEFAULT_REQUEST_FACTORIES`` have been removed from the ``repoze.bfg.request`` module. These were never APIs. - The ``IViewPermissionFactory`` interface has been removed. This was never an API. Documentation ------------- - Request-only-convention examples in the "Views" narrative documentation were broken. - Fixed documentation bugs related to forget and remember in security API docs. - Fixed documentation for ``repoze.bfg.view.static`` (in narrative ``Views`` chapter). Deprecations ------------ - The API ``repoze.bfg.testing.registerViewPermission`` has been deprecated. Backwards Incompatibilities --------------------------- - The interfaces ``IPOSTRequest``, ``IGETRequest``, ``IPUTRequest``, ``IDELETERequest``, and ``IHEADRequest`` have been removed from the ``repoze.bfg.interfaces`` module. These were not documented as APIs post-1.0. Instead of using one of these, use a ``request_method`` ZCML attribute or ``request_method`` bfg_view decorator parameter containing an HTTP method name (one of ``GET``, ``POST``, ``HEAD``, ``PUT``, ``DELETE``) instead of one of these interfaces if you were using one explicitly. Passing a string in the set (``GET``, ``HEAD``, ``PUT``, ``POST``, ``DELETE``) as a ``request_type`` argument will work too. Rationale: instead of relying on interfaces attached to the request object, BFG now uses a "view predicate" to determine the request type. - Views registered without the help of the ZCML ``view`` directive are now responsible for performing their own authorization checking. - The ``registry_manager`` backwards compatibility alias importable from "repoze.bfg.registry", deprecated since repoze.bfg 0.9 has been removed. If you are tring to use the registry manager within a debug script of your own, use a combination of the "repoze.bfg.paster.get_app" and "repoze.bfg.scripting.get_root" APIs instead. - The ``INotFoundAppFactory`` interface has been removed; it has been deprecated since repoze.bfg 0.9. If you have something like the following in your ``configure.zcml``:: Replace it with something like:: See "Changing the Not Found View" in the "Hooks" chapter of the documentation for more information. - The ``IUnauthorizedAppFactory`` interface has been removed; it has been deprecated since repoze.bfg 0.9. If you have something like the following in your ``configure.zcml``:: Replace it with something like:: See "Changing the Forbidden View" in the "Hooks" chapter of the documentation for more information. - ``ISecurityPolicy``-based security policies, deprecated since repoze.bfg 0.9, have been removed. If you have something like this in your ``configure.zcml``, it will no longer work:: If ZCML like the above exists in your application, you will receive an error at startup time. Instead of the above, you'll need something like:: This is just an example. See the "Security" chapter of the repoze.bfg documentation for more information about configuring security policies. - Custom ZCML directives which register an authentication or authorization policy (ala "authtktauthenticationpolicy" or "aclauthorizationpolicy") should register the policy "eagerly" in the ZCML directive instead of from within a ZCML action. If an authentication or authorization policy is not found in the component registry by the view machinery during deferred ZCML processing, view security will not work as expected. 1.0.1 (2009-07-22) ================== - Added support for ``has_resource``, ``resource_isdir``, and ``resource_listdir`` to the resource "OverrideProvider"; this fixes a bug with a symptom that a file could not be overridden in a resource directory unless a file with the same name existed in the original directory being overridden. - Fixed documentation bug showing invalid test for values from the ``matchdict``: they are stored as attributes of the ``Article``, rather than subitems. - Fixed documentation bug showing wrong environment key for the ``matchdict`` produced by the matching route. - Added a workaround for a bug in Python 2.6, 2.6.1, and 2.6.2 having to do with a recursion error in the mimetypes module when trying to serve static files from Paste's FileApp: http://bugs.python.org/issue5853. Symptom: File "/usr/lib/python2.6/mimetypes.py", line 244, in guess_type return guess_type(url, strict) RuntimeError: maximum recursion depth exceeded. Thanks to Armin Ronacher for identifying the symptom and pointing out a fix. - Minor edits to tutorials for accuracy based on feedback. - Declared Paste and PasteDeploy dependencies. 1.0 (2009-07-05) ================ - Retested and added some content to GAE tutorial. - Edited "Extending" narrative docs chapter. - Added "Deleting the Database" section to the "Defining Models" chapter of the traversal wiki tutorial. - Spell checking of narratives and tutorials. 1.0b2 (2009-07-03) ================== - ``remoteuserauthenticationpolicy`` ZCML directive didn't work without an ``environ_key`` directive (didn't match docs). - Fix ``configure_zcml`` filespec check on Windows. Previously if an absolute filesystem path including a drive letter was passed as ``filename`` (or as ``configure_zcml`` in the options dict) to ``repoze.bfg.router.make_app``, it would be treated as a package:resource_name specification. - Fix inaccuracies and import errors in bfgwiki (traversal+ZODB) and bfgwiki2 (urldispatch+SA) tutorials. - Use bfgsite index for all tutorial setup.cfg files. - Full documentation grammar/style/spelling audit. 1.0b1 (2009-07-02) ================== Features -------- - Allow a Paste config file (``configure_zcml``) value or an environment variable (``BFG_CONFIGURE_ZCML``) to name a ZCML file (optionally package-relative) that will be used to bootstrap the application. Previously, the integrator could not influence which ZCML file was used to do the boostrapping (only the original application developer could do so). Documentation ------------- - Added a "Resources" chapter to the narrative documentation which explains how to override resources within one package from another package. - Added an "Extending" chapter to the narrative documentation which explains how to extend or modify an existing BFG application using another Python package and ZCML. 1.0a9 (2009-07-01) ================== Features -------- - Make it possible to pass strings in the form "package_name:relative/path" to APIs like ``render_template``, ``render_template_to_response``, and ``get_template``. Sometimes the package in which a caller lives is a direct namespace package, so the module which is returned is semi-useless for navigating from. In this way, the caller can control the horizontal and vertical of where things get looked up from. 1.0a8 (2009-07-01) ================== Deprecations ------------ - Deprecate the ``authentication_policy`` and ``authorization_policy`` arguments to ``repoze.bfg.router.make_app``. Instead, developers should use the various authentication policy ZCML directives (``repozewho1authenticationpolicy``, ``remoteuserauthenticationpolicy`` and ``authtktauthenticationpolicy``) and the `aclauthorizationpolicy`` authorization policy directive as described in the changes to the "Security" narrative documenation chapter and the wiki tutorials. Features -------- - Add three new ZCML directives which configure authentication policies: - ``repozewho1authenticationpolicy`` - ``remoteuserauthenticationpolicy`` - ``authtktauthenticationpolicy`` - Add a new ZCML directive which configures an ACL authorization policy named ``aclauthorizationpolicy``. Bug Fixes --------- - Bug fix: when a ``repoze.bfg.resource.PackageOverrides`` class was instantiated, and the package it was overriding already had a ``__loader__`` attribute, it would fail at startup time, even if the ``__loader__`` attribute was another PackageOverrides instance. We now replace any ``__loader__`` that is also a PackageOverrides instance. Symptom: ``ConfigurationExecutionError: : Package already has a __loader__ (probably a module in a zipped egg)``. 1.0a7 (2009-06-30) ================== Features -------- - Add a ``reload_resources`` configuration file setting (aka the ``BFG_RELOAD_RESOURCES`` environment variable). When this is set to true, the server never needs to be restarted when moving files between directory resource overrides (esp. for templates currently). - Add a ``reload_all`` configuration file setting (aka the ``BFG_RELOAD_ALL`` environment variable) that implies both ``reload_resources`` and ``reload_templates``. - The ``static`` helper view class now uses a ``PackageURLParser`` in order to allow for the overriding of static resources (CSS / logo files, etc) using the ``resource`` ZCML directive. The ``PackageURLParser`` class was added to a (new) ``static`` module in BFG; it is a subclass of the ``StaticURLParser`` class in ``paste.urlparser``. - The ``repoze.bfg.templating.renderer_from_cache`` function now checks for the ``reload_resources`` setting; if it's true, it does not register a template renderer (it won't use the registry as a template renderer cache). Documentation ------------- - Add ``pkg_resources`` to the glossary. - Update the "Environment" docs to note the existence of ``reload_resources`` and ``reload_all``. - Updated the ``bfg_alchemy`` paster template to include two views: the view on the root shows a list of links to records; the view on a record shows the details for that object. Internal -------- - Use a colon instead of a tab as the separator between package name and relpath to form the "spec" when register a ITemplateRenderer. - Register a ``repoze.bfg.resource.OverrideProvider`` as a pkg_resources provider only for modules which are known to have overrides, instead of globally, when a directive is used (performance). 1.0a6 (2009-06-29) ================== Bug Fixes --------- - Use ``caller_package`` function instead of ``caller_module`` function within ``templating`` to avoid needing to name the caller module in resource overrides (actually match docs). - Make it possible to override templates stored directly in a module with templates in a subdirectory of the same module, stored directly within another module, or stored in a subdirectory of another module (actually match docs). 1.0a5 (2009-06-28) ================== Features -------- - A new ZCML directive exists named "resource". This ZCML directive allows you to override Chameleon templates within a package (both directories full of templates and individual template files) with other templates in the same package or within another package. This allows you to "fake out" a view's use of a template, causing it to retrieve a different template than the one actually named by a relative path to a call like ``render_template_to_response('templates/mytemplate.pt')``. For example, you can override a template file by doing:: The string passed to "to_override" and "override_with" is named a "specification". The colon separator in a specification separates the package name from a package-relative directory name. The colon and the following relative path are optional. If they are not specified, the override attempts to resolve every lookup into a package from the directory of another package. For example:: Individual subdirectories within a package can also be overridden:: If you wish to override a directory with another directory, you must make sure to attach the slash to the end of both the ``to_override`` specification and the ``override_with`` specification. If you fail to attach a slash to the end of a specification that points a directory, you will get unexpected results. You cannot override a directory specification with a file specification, and vice versa (a startup error will occur if you try). You cannot override a resource with itself (a startup error will occur if you try). Only individual *package* resources may be overridden. Overrides will not traverse through subpackages within an overridden package. This means that if you want to override resources for both ``some.package:templates``, and ``some.package.views:templates``, you will need to register two overrides. The package name in a specification may start with a dot, meaning that the package is relative to the package in which the ZCML file resides. For example:: Overrides for the same ``to_overrides`` specification can be named multiple times within ZCML. Each ``override_with`` path will be consulted in the order defined within ZCML, forming an override search path. Resource overrides can actually override resources other than templates. Any software which uses the ``pkg_resources`` ``get_resource_filename``, ``get_resource_stream`` or ``get_resource_string`` APIs will obtain an overridden file when an override is used. However, the only built-in facility which uses the ``pkg_resources`` API within BFG is the templating stuff, so we only call out template overrides here. - Use the ``pkg_resources`` API to locate template filenames instead of dead-reckoning using the ``os.path`` module. - The ``repoze.bfg.templating`` module now uses ``pkg_resources`` to locate and register template files instead of using an absolute path name. 1.0a4 (2009-06-25) ================== Features -------- - Cause ``:segment`` matches in route paths to put a Unicode-decoded and URL-dequoted value in the matchdict for the value matched. Previously a non-decoded non-URL-dequoted string was placed in the matchdict as the value. - Cause ``*remainder`` matches in route paths to put a *tuple* in the matchdict dictionary in order to be able to present Unicode-decoded and URL-dequoted values for the traversal path. Previously a non-decoded non-URL-dequoted string was placed in the matchdict as the value. - Add optional ``max_age`` keyword value to the ``remember`` method of ``repoze.bfg.authentication.AuthTktAuthenticationPolicy``; if this value is passed to ``remember``, the generated cookie will have a corresponding Max-Age value. Documentation ------------- - Add information to the URL Dispatch narrative documentation about path pattern matching syntax. Bug Fixes --------- - Make ``route_url`` URL-quote segment replacements during generation. Remainder segments are not quoted. 1.0a3 (2009-06-24) ================== Implementation Changes ---------------------- - ``repoze.bfg`` no longer relies on the Routes package to interpret URL paths. All known existing ``path`` patterns will continue to work with the reimplemented logic, which lives in ``repoze.bfg.urldispatch``. ```` ZCML directives which use certain attributes (uncommon ones) may not work (see "Backwards Incompatibilities" below). Bug Fixes --------- - ``model_url`` when passed a request that was generated as a result of a route match would fail in a call to ``route.generate``. - BFG-on-GAE didn't work due to a corner case bug in the fallback Python implementation of ``threading.local`` (symptom: "Initialization arguments are not supported"). Thanks to Michael Bernstein for the bug report. Documentation ------------- - Added a "corner case" explanation to the "Hybrid Apps" chapter explaining what to do when "the wrong" view is matched. - Use ``repoze.bfg.url.route_url`` API in tutorials rather than Routes ``url_for`` API. Features -------- - Added the ``repoze.bfg.url.route_url`` API. This API allows you to generate URLs based on ```` declarations. See the URL Dispatch narrative chapter and the "repoze.bfg.url" module API documentation for more information. Backwards Incompatibilities --------------------------- - As a result of disusing Routes, using the Routes ``url_for`` API inside a BFG application (as was suggested by previous iterations of tutorials) will no longer work. Use the ``repoze.bfg.url.route_url`` method instead. - The following attributes on the ```` ZCML directive no longer work: ``encoding``, ``static``, ``filter``, ``condition_method``, ``condition_subdomain``, ``condition_function``, ``explicit``, or ``subdomains``. These were all Routes features. - The ```` ZCML directive no longer supports the ```` subdirective. This was a Routes feature. 1.0a2 (2009-06-23) ================== Bug Fixes --------- - The ``bfg_routesalchemy`` paster template app tests failed due to a mismatch between test and view signatures. Features -------- - Add a ``view_for`` attribute to the ``route`` ZCML directive. This attribute should refer to an interface or a class (ala the ``for`` attribute of the ``view`` ZCML directive). Documentation ------------- - Conditional documentation in installation section ("how to install a Python interpreter"). Backwards Incompatibilities --------------------------- - The ``callback`` argument of the ``repoze.bfg.authentication`` authentication policies named ``RepozeWho1AuthenticationPolicy``, ``RemoteUserAuthenticationPolicy``, and ``AuthTktAuthenticationPolicy`` now must accept two positional arguments: the orginal argument accepted by each (userid or identity) plus a second argument, which will be the current request. Apologies, this is required to service finding groups when there is no "global" database connection. 1.0a1 (2009-06-22) ================== Features -------- - A new ZCML directive was added named ``notfound``. This ZCML directive can be used to name a view that should be invoked when the request can't otherwise be resolved to a view callable. For example:: - A new ZCML directive was added named ``forbidden``. This ZCML directive can be used to name a view that should be invoked when a view callable for a request is found, but cannot be invoked due to an authorization failure. For example:: - Allow views to be *optionally* defined as callables that accept only a request object, instead of both a context and a request (which still works, and always will). The following types work as views in this style: - functions that accept a single argument ``request``, e.g.:: def aview(request): pass - new and old-style classes that have an ``__init__`` method that accepts ``self, request``, e.g.:: def View(object): __init__(self, request): pass - Arbitrary callables that have a ``__call__`` method that accepts ``self, request``, e.g.:: def AView(object): def __call__(self, request): pass view = AView() This likely should have been the calling convention all along, as the request has ``context`` as an attribute already, and with views called as a result of URL dispatch, having the context in the arguments is not very useful. C'est la vie. - Cache the absolute path in the caller's package globals within ``repoze.bfg.path`` to get rid of repeated (expensive) calls to os.path.abspath. - Add ``reissue_time`` and ``timeout`` parameters to ``repoze.bfg.authentication.AuthTktAuthenticationPolicy`` constructor. If these are passed, cookies will be reset every so often (cadged from the same change to repoze.who lately). - The matchdict related to the matching of a Routes route is available on the request as the ``matchdict`` attribute: ``request.matchdict``. If no route matched, this attribute will be None. - Make 404 responses slightly cheaper by showing ``environ["PATH_INFO"]`` on the notfound result page rather than the fullly computed URL. - Move LRU cache implementation into a separate package (``repoze.lru``). - The concepts of traversal and URL dispatch have been unified. It is now possible to use the same sort of factory as both a traversal "root factory" and what used to be referred to as a urldispatch "context factory". - When the root factory argument (as a first argument) passed to ``repoze.bfg.router.make_app`` is ``None``, a *default* root factory is used. This is in support of using routes as "root finders"; it supplants the idea that there is a default ``IRoutesContextFactory``. - The `view`` ZCML statement and the ``repoze.bfg.view.bfg_view`` decorator now accept an extra argument: ``route_name``. If a ``route_name`` is specified, it must match the name of a previously defined ``route`` statement. When it is specified, the view will only be called when that route matches during a request. - It is now possible to perfom traversal *after* a route has matched. Use the pattern ``*traverse`` in a ```` ``path`` attribute within ZCML, and the path remainder which it matches will be used as a traversal path. - When any route defined matches, the WSGI environment will now contain a key ``bfg.routes.route`` (the Route object which matched), and a key ``bfg.routes.matchdict`` (the result of calling route.match). Deprecations ------------ - Utility registrations against ``repoze.bfg.interfaces.INotFoundView`` and ``repoze.bfg.interfaces.IForbiddenView`` are now deprecated. Use the ``notfound`` and ``forbidden`` ZCML directives instead (see the "Hooks" chapter for more information). Such registrations will continue to work, but the notfound and forbidden directives do "extra work" to ensure that the callable named by the directive can be called by the router even if it's a class or request-argument-only view. Removals -------- - The ``IRoutesContext``, ``IRoutesContextFactory``, and ``IContextNotFound`` interfaces were removed from ``repoze.bfg.interfaces``. These were never APIs. - The ``repoze.bfg.urldispatch.RoutesContextNotFound``, ``repoze.bfg.urldispatch.RoutesModelTraverser`` and ``repoze.bfg.urldispatch.RoutesContextURL`` classes were removed. These were also never APIs. Backwards Incompatibilities --------------------------- - Moved the ``repoze.bfg.push`` module, which implemented the ``pushpage`` decorator, into a separate distribution, ``repoze.bfg.pushpage``. Applications which used this decorator should continue to work after adding that distribution to their installation requirements. - Changing the default request factory via an IRequestFactory utility registration (as used to be documented in the "Hooks" chapter's "Changing the request factory" section) is no longer supported. The dance to manufacture a request is complicated as a result of unifying traversal and url dispatch, making it highly unlikely for anyone to be able to override it properly. For those who just want to decorate or modify a request, use a NewRequestEvent subscriber (see the Events chapter in the documentation). - The ``repoze.bfg.IRequestFactory`` interface was removed. See the bullet above for why. - Routes "context factories" (spelled as the factory argument to a route statement in ZCML) must now expect the WSGI environ as a single argument rather than a set of keyword arguments. They can obtain the match dictionary by asking for environ['bfg.routes.matchdict']. This is the same set of keywords that used to be passed to urldispatch "context factories" in BFG 0.9 and below. - Using the ``@zope.component.adapter`` decorator on a bfg view function no longer works. Use the ``@repoze.bfg.view.bfg_view`` decorator instead to mark a function (or a class) as a view. - The name under which the matching route object is found in the environ was changed from ``bfg.route`` to ``bfg.routes.route``. - Finding the root is now done *before* manufacturing a request object (and sending a new request event) within the router (it used to be performed afterwards). - Adding ``*path_info`` to a route no longer changes the PATH_INFO for a request that matches using URL dispatch. This feature was only there to service the ``repoze.bfg.wsgi.wsgiapp2`` decorator and it did it wrong; use ``*subpath`` instead now. - The values of ``subpath``, ``traversed``, and ``virtual_root_path`` attached to the request object are always now tuples instead of lists (performance). Bug Fixes --------- - The ``bfg_alchemy`` Paster template named "repoze.tm" in its pipeline rather than "repoze.tm2", causing the startup to fail. - Move BBB logic for registering an IAuthenticationPolicy/IForbiddenView/INotFoundView based on older concepts from the router module's ``make_app`` function into the ``repoze.bfg.zcml.zcml_configure`` callable, to service compatibility with scripts that use "zope.configuration.xmlconfig" (replace with ``repoze.bfg.zml.zcml_configure`` as necessary to get BBB logic) Documentation ------------- - Add interface docs related to how to create authentication policies and authorization policies to the "Security" narrative chapter. - Added a (fairly sad) "Combining Traversal and URL Dispatch" chapter to the narrative documentation. This explains the usage of ``*traverse`` and ``*subpath`` in routes URL patters. - A "router" chapter explaining the request/response lifecycle at a high level was added. - Replaced all mentions and explanations of a routes "context factory" with equivalent explanations of a "root factory" (context factories have been disused). - Updated Routes bfgwiki2 tutorial to reflect the fact that context factories are now no longer used. 0.9.1 (2009-06-02) ================== Features -------- - Add API named ``repoze.bfg.settings.get_settings`` which retrieves a derivation of values passed as the ``options`` value of ``repoze.bfg.router.make_app``. This API should be preferred instead of using getUtility(ISettings). I added a new ``repoze.bfg.settings`` API document as well. Bug Fixes --------- - Restored missing entry point declaration for bfg_alchemy paster template, which was accidentally removed in 0.9. Documentation ------------- - Fix a reference to ``wsgiapp`` in the ``wsgiapp2`` API documentation within the ``repoze.bfg.wsgi`` module. API Removals ------------ - The ``repoze.bfg.location.locate`` API was removed: it didn't do enough to be very helpful and had a misleading name. 0.9 (2009-06-01) ================ Bug Fixes --------- - It was not possible to register a custom ``IRoutesContextFactory`` for use as a default context factory as documented in the "Hooks" chapter. Features -------- - The ``request_type`` argument of ZCML ``view`` declarations and ``bfg_view`` decorators can now be one of the strings ``GET``, ``POST``, ``PUT``, ``DELETE``, or ``HEAD`` instead of a reference to the respective interface type imported from ``repoze.bfg.interfaces``. - The ``route`` ZCML directive now accepts ``request_type`` as an alias for its ``condition_method`` argument for symmetry with the ``view`` directive. - The ``bfg_routesalchemy`` paster template now provides a unit test and actually uses the database during a view rendering. Removals -------- - Remove ``repoze.bfg.threadlocal.setManager``. It was only used in unit tests. - Remove ``repoze.bfg.wsgi.HTTPException``, ``repoze.bfg.wsgi.NotFound``, and ``repoze.bfg.wsgi.Unauthorized``. These classes were disused with the introduction of the ``IUnauthorizedView`` and ``INotFoundView`` machinery. Documentation ------------- - Add description to narrative templating chapter about how to use Chameleon text templates. - Changed Views narrative chapter to use method strings rather than interface types, and moved advanced interface type usage to Events narrative chapter. - Added a Routes+SQLAlchemy wiki tutorial. 0.9a8 (2009-05-31) ================== Features -------- - It is now possible to register a custom ``repoze.bfg.interfaces.INotFoundView`` for a given application. This feature replaces the ``repoze.bfg.interfaces.INotFoundAppFactory`` feature previously described in the Hooks chapter. The INotFoundView will be called when the framework detects that a view lookup done as a result of a request fails; it should accept a context object and a request object; it should return an IResponse object (a webob response, basically). See the Hooks narrative chapter of the BFG docs for more info. - The error presented when a view invoked by the router returns a non-response object now includes the view's name for troubleshooting purposes. Bug Fixes --------- - A "new response" event is emitted for forbidden and notfound views. Deprecations ------------ - The ``repoze.bfg.interfaces.INotFoundAppFactory`` interface has been deprecated in favor of using the new ``repoze.bfg.interfaces.INotFoundView`` mechanism. Renames ------- - Renamed ``repoze.bfg.interfaces.IForbiddenResponseFactory`` to ``repoze.bfg.interfaces.IForbiddenView``. 0.9a7 (2009-05-30) ================== Features -------- - Remove "context" argument from ``effective_principals`` and ``authenticated_userid`` function APIs in ``repoze.bfg.security``, effectively a doing reversion to 0.8 and before behavior. Both functions now again accept only the ``request`` parameter. 0.9a6 (2009-05-29) ================== Documentation ------------- - Changed "BFG Wiki" tutorial to use AuthTktAuthenticationPolicy rather than repoze.who. Features -------- - Add an AuthTktAuthenticationPolicy. This policy retrieves credentials from an auth_tkt cookie managed by the application itself (instead of relying on an upstream data source for authentication data). See the Security API chapter of the documentation for more info. - Allow RemoteUserAuthenticationPolicy and RepozeWho1AuthenticationPolicy to accept various constructor arguments. See the Security API chapter of the documentation for more info. 0.9a5 (2009-05-28) ================== Features -------- - Add a ``get_app`` API functions to the ``paster`` module. This obtains a WSGI application from a config file given a config file name and a section name. See the ``repoze.bfg.paster`` API docs for more information. - Add a new module named ``scripting``. It contains a ``get_root`` API function, which, provided a Router instance, returns a traversal root object and a "closer". See the ``repoze.bfg.scripting`` API docs for more info. 0.9a4 (2009-05-27) ================== Bug Fixes --------- - Try checking for an "old style" security policy *after* we parse ZCML (thinko). 0.9a3 (2009-05-27) ================== Features -------- - Allow IAuthenticationPolicy and IAuthorizationPolicy to be overridden via ZCML registrations (do ZCML parsing after registering these in router.py). Documentation ------------- - Added "BFG Wiki" tutorial to documentation; it describes step-by-step how to create a traversal-based ZODB application with authentication. Deprecations ------------ - Added deprecations for imports of ``ACLSecurityPolicy``, ``InheritingACLSecurityPolicy``, ``RemoteUserACLSecurityPolicy``, ``RemoteUserInheritingACLSecurityPolicy``, ``WhoACLSecurityPolicy``, and ``WhoInheritingACLSecurityPolicy`` from the ``repoze.bfg.security`` module; for the meantime (for backwards compatibility purposes) these live in the ``repoze.bfg.secpols`` module. Note however, that the entire concept of a "security policy" is deprecated in BFG in favor of separate authentication and authorization policies, so any use of a security policy will generate additional deprecation warnings even if you do start using ``repoze.bfg.secpols``. ``repoze.bfg.secpols`` will disappear in a future release of ``repoze.bfg``. Deprecated Import Alias Removals -------------------------------- - Remove ``repoze.bfg.template`` module. All imports from this package have been deprecated since 0.3.8. Instead, import ``get_template``, ``render_template``, and ``render_template_to_response`` from the ``repoze.bfg.chameleon_zpt`` module. - Remove backwards compatibility import alias for ``repoze.bfg.traversal.split_path`` (deprecated since 0.6.5). This must now be imported as ``repoze.bfg.traversal.traversal_path``). - Remove backwards compatibility import alias for ``repoze.bfg.urldispatch.RoutesContext`` (deprecated since 0.6.5). This must now be imported as ``repoze.bfg.urldispatch.DefaultRoutesContext``. - Removed backwards compatibility import aliases for ``repoze.bfg.router.get_options`` and ``repoze.bfg.router.Settings`` (deprecated since 0.6.2). These both must now be imported from ``repoze.bfg.settings``. - Removed backwards compatibility import alias for ``repoze.bfg.interfaces.IRootPolicy`` (deprecated since 0.6.2). It must be imported as ``repoze.bfg.interfaces.IRootFactory`` now. - Removed backwards compatibility import alias for ``repoze.bfg.interfaces.ITemplate`` (deprecated since 0.4.4). It must be imported as ``repoze.bfg.interfaces.ITemplateRenderer`` now. - Removed backwards compatibility import alias for ``repoze.bfg.interfaces.ITemplateFactory`` (deprecated since 0.4.4). It must be imported as ``repoze.bfg.interfaces.ITemplateRendererFactory`` now. - Removed backwards compatibility import alias for ``repoze.bfg.chameleon_zpt.ZPTTemplateFactory`` (deprecated since 0.4.4). This must be imported as ``repoze.bfg.ZPTTemplateRenderer`` now. 0.9a2 (2009-05-27) ================== Features -------- - A paster command has been added named "bfgshell". This command can be used to get an interactive prompt with your BFG root object in the global namespace. E.g.:: bin/paster bfgshell /path/to/myapp.ini myapp See the ``Project`` chapter in the BFG documentation for more information. Deprecations ------------ - The name ``repoze.bfg.registry.registry_manager`` was never an API, but scripts in the wild were using it to set up an environment for use under a debug shell. A backwards compatibility shim has been added for this purpose, but the feature is deprecated. 0.9a1 (2009-5-27) ================= Features -------- - New API functions named ``forget`` and ``remember`` are available in the ``security`` module. The ``forget`` function returns headers which will cause the currently authenticated user to be logged out when set in a response. The ``remember`` function (when passed the proper arguments) will return headers which will cause a principal to be "logged in" when set in a response. See the Security API chapter of the docs for more info. - New keyword arguments to the ``repoze.bfg.router.make_app`` call have been added: ``authentication_policy`` and ``authorization_policy``. These should, respectively, be an implementation of an authentication policy (an object implementing the ``repoze.bfg.interfaces.IAuthenticationPolicy`` interface) and an implementation of an authorization policy (an object implementing ``repoze.bfg.interfaces.IAuthorizationPolicy)``. Concrete implementations of authentication policies exist in ``repoze.bfg.authentication``. Concrete implementations of authorization policies exist in ``repoze.bfg.authorization``. Both ``authentication_policy`` and ``authorization_policy`` default to ``None``. If ``authentication_policy`` is ``None``, but ``authorization_policy`` is *not* ``None``, then ``authorization_policy`` is ignored (the ability to do authorization depends on authentication). If the ``authentication_policy`` argument is *not* ``None``, and the ``authorization_policy`` argument *is* ``None``, the authorization policy defaults to an authorization implementation that uses ACLs (``repoze.bfg.authorization.ACLAuthorizationPolicy``). We no longer encourage configuration of "security policies" using ZCML, as previously we did for ``ISecurityPolicy``. This is because it's not uncommon to need to configure settings for concrete authorization or authentication policies using paste .ini parameters; the app entry point for your application is the natural place to do this. - Two new abstractions have been added in the way of adapters used by the system: an ``IAuthorizationPolicy`` and an ``IAuthenticationPolicy``. A combination of these (as registered by the ``securitypolicy`` ZCML directive) take the place of the ``ISecurityPolicy`` abstraction in previous releases of repoze.who. The API functions in ``repoze.who.security`` (such as ``authentication_userid``, ``effective_principals``, ``has_permission``, and so on) have been changed to try to make use of these new adapters. If you're using an older ``ISecurityPolicy`` adapter, the system will still work, but it will print deprecation warnings when such a policy is used. - The way the (internal) IViewPermission utilities registered via ZCML are invoked has changed. They are purely adapters now, returning a boolean result, rather than returning a callable. You shouldn't have been using these anyway. ;-) - New concrete implementations of IAuthenticationPolicy have been added to the ``repoze.bfg.authentication`` module: ``RepozeWho1AuthenticationPolicy`` which uses ``repoze.who`` identity to retrieve authentication data from and ``RemoteUserAuthenticationPolicy``, which uses the ``REMOTE_USER`` value in the WSGI environment to retrieve authentication data. - A new concrete implementation of IAuthorizationPolicy has been added to the ``repoze.bfg.authorization`` module: ``ACLAuthorizationPolicy`` which uses ACL inheritance to do authorization. - It is now possible to register a custom ``repoze.bfg.interfaces.IForbiddenResponseFactory`` for a given application. This feature replaces the ``repoze.bfg.interfaces.IUnauthorizedAppFactory`` feature previously described in the Hooks chapter. The IForbiddenResponseFactory will be called when the framework detects an authorization failure; it should accept a context object and a request object; it should return an IResponse object (a webob response, basically). Read the below point for more info and see the Hooks narrative chapter of the BFG docs for more info. Backwards Incompatibilities --------------------------- - Custom NotFound and Forbidden (nee' Unauthorized) WSGI applications (registered as a utility for INotFoundAppFactory and IUnauthorizedAppFactory) could rely on an environment key named ``message`` describing the circumstance of the response. This key has been renamed to ``repoze.bfg.message`` (as per the WSGI spec, which requires environment extensions to contain dots). Deprecations ------------ - The ``repoze.bfg.interfaces.IUnauthorizedAppFactory`` interface has been deprecated in favor of using the new ``repoze.bfg.interfaces.IForbiddenResponseFactory`` mechanism. - The ``view_execution_permitted`` API should now be imported from the ``repoze.bfg.security`` module instead of the ``repoze.bfg.view`` module. - The ``authenticated_userid`` and ``effective_principals`` APIs in ``repoze.bfg.security`` used to only take a single argument (request). They now accept two arguments (``context`` and ``request``). Calling them with a single argument is still supported but issues a deprecation warning. (NOTE: this change was reverted in 0.9a7; meaning the 0.9 versions of these functions again accept ``request`` only, just like 0.8 and before). - Use of "old-style" security policies (those base on ISecurityPolicy) is now deprecated. See the "Security" chapter of the docs for info about activating an authorization policy and an authentication poicy. 0.8.1 (2009-05-21) ================== Features -------- - Class objects may now be used as view callables (both via ZCML and via use of the ``bfg_view`` decorator in Python 2.6 as a class decorator). The calling semantics when using a class as a view callable is similar to that of using a class as a Zope "browser view": the class' ``__init__`` must accept two positional parameters (conventionally named ``context``, and ``request``). The resulting instance must be callable (it must have a ``__call__`` method). When called, the instance should return a response. For example:: from webob import Response class MyView(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return Response('hello from %s!' % self.context) See the "Views" chapter in the documentation and the ``repoze.bfg.view`` API documentation for more information. - Removed the pickling of ZCML actions (the code that wrote ``configure.zcml.cache`` next to ``configure.zcml`` files in projects). The code which managed writing and reading of the cache file was a source of subtle bugs when users switched between imperative (e.g. ``@bfg_view``) registrations and declarative registrations (e.g. the ``view`` directive in ZCML) on the same project. On a moderately-sized project (535 ZCML actions and 15 ZCML files), executing actions read from the pickle was saving us only about 200ms (2.5 sec vs 2.7 sec average). On very small projects (1 ZCML file and 4 actions), startup time was comparable, and sometimes even slower when reading from the pickle, and both ways were so fast that it really just didn't matter anyway. 0.8 (2009-05-18) ================ Features -------- - Added a ``traverse`` function to the ``repoze.bfg.traversal`` module. This function may be used to retrieve certain values computed during path resolution. See the Traversal API chapter of the documentation for more information about this function. Deprecations ------------ - Internal: ``ITraverser`` callables should now return a dictionary rather than a tuple. Up until 0.7.0, all ITraversers were assumed to return a 3-tuple. In 0.7.1, ITraversers were assumed to return a 6-tuple. As (by evidence) it's likely we'll need to add further information to the return value of an ITraverser callable, 0.8 assumes that an ITraverser return a dictionary with certain elements in it. See the ``repoze.bfg.interfaces.ITraverser`` interface for the list of keys that should be present in the dictionary. ``ITraversers`` which return tuples will still work, although a deprecation warning will be issued. Backwards Incompatibilities --------------------------- - If your code used the ITraverser interface directly (not via an API function such as ``find_model``) via an adapter lookup, you'll need to change your code to expect a dictionary rather than a 3- or 6-tuple if your code ever gets return values from the default ModelGraphTraverser or RoutesModelTraverser adapters. 0.8a7 (2009-05-16) ================== Backwards Incompatibilities --------------------------- - The ``RoutesMapper`` class in ``repoze.bfg.urldispatch`` has been removed, as well as its documentation. It had been deprecated since 0.6.3. Code in ``repoze.bfg.urldispatch.RoutesModelTraverser`` which catered to it has also been removed. - The semantics of the ``route`` ZCML directive have been simplified. Previously, it was assumed that to use a route, you wanted to map a route to an externally registered view. The new ``route`` directive instead has a ``view`` attribute which is required, specifying the dotted path to a view callable. When a route directive is processed, a view is *registered* using the name attribute of the route directive as its name and the callable as its value. The ``view_name`` and ``provides`` attributes of the ``route`` directive are therefore no longer used. Effectively, if you were previously using the ``route`` directive, it means you must change a pair of ZCML directives that look like this:: To a ZCML directive that looks like this:: In other words, to make old code work, remove the ``view`` directives that were only there to serve the purpose of backing ``route`` directives, and move their ``view=`` attribute into the ``route`` directive itself. This change also necessitated that the ``name`` attribute of the ``route`` directive is now required. If you were previously using ``route`` directives without a ``name`` attribute, you'll need to add one (the name is arbitrary, but must be unique among all ``route`` and ``view`` statements). The ``provides`` attribute of the ``route`` directive has also been removed. This directive specified a sequence of interface types that the generated context would be decorated with. Since route views are always generated now for a single interface (``repoze.bfg.IRoutesContext``) as opposed to being looked up arbitrarily, there is no need to decorate any context to ensure a view is found. Documentation ------------- - Added API docs for the ``repoze.bfg.testing`` methods ``registerAdapter``, ``registerUtiity``, ``registerSubscriber``, and ``cleanUp``. - Added glossary entry for "root factory". - Noted existence of ``repoze.bfg.pagetemplate`` template bindings in "Available Add On Template System Bindings" in Templates chapter in narrative docs. - Update "Templates" narrative chapter in docs (expand to show a sample template and correct macro example). Features -------- - Courtesty Carlos de la Guardia, added an ``alchemy`` Paster template. This paster template sets up a BFG project that uses SQAlchemy (with SQLite) and uses traversal to resolve URLs. (no Routes areused). This template can be used via ``paster create -t bfg_alchemy``. - The Routes ``Route`` object used to resolve the match is now put into the environment as ``bfg.route`` when URL dispatch is used. - You can now change the default Routes "context factory" globally. See the "ZCML Hooks" chapter of the documentation (in the "Changing the Default Routes Context Factory" section). 0.8a6 (2009-05-11) ================== Features -------- - Added a ``routesalchemy`` Paster template. This paster template sets up a BFG project that uses SQAlchemy (with SQLite) and uses Routes exclusively to resolve URLs (no traversal root factory is used). This template can be used via ``paster create -t bfg_routesalchemy``. Documentation ------------- - Added documentation to the URL Dispatch chapter about how to catch the root URL using a ZCML ``route`` directive. - Added documentation to the URL Dispatch chapter about how to perform a cleanup function at the end of a request (e.g. close the SQL connection). Bug Fixes --------- - In version 0.6.3, passing a ``get_root`` callback (a "root factory") to ``repoze.bfg.router.make_app`` became optional if any ``route`` declaration was made in ZCML. The intent was to make it possible to disuse traversal entirely, instead relying entirely on URL dispatch (Routes) to resolve all contexts. However a compound set of bugs prevented usage of a Routes-based root view (a view which responds to "/"). One bug existed in `repoze.bfg.urldispatch``, another existed in Routes itself. To resolve this issue, the urldispatch module was fixed, and a fork of the Routes trunk was put into the "dev" index named ``Routes-1.11dev-chrism-home``. The source for the fork exists at `http://bitbucket.org/chrism/routes-home/ `_ (broken link); its contents have been merged into the Routes trunk (what will be Routes 1.11). 0.8a5 (2009-05-08) ================== Features -------- - Two new security policies were added: RemoteUserInheritingACLSecurityPolicy and WhoInheritingACLSecurityPolicy. These are security policies which take into account *all* ACLs defined in the lineage of a context rather than stopping at the first ACL found in a lineage. See the "Security" chapter of the API documentation for more information. - The API and narrative documentation dealing with security was changed to introduce the new "inheriting" security policy variants. - Added glossary entry for "lineage". Deprecations ------------ - The security policy previously named ``RepozeWhoIdentityACLSecurityPolicy`` now has the slightly saner name of ``WhoACLSecurityPolicy``. A deprecation warning is emitted when this policy is imported under the "old" name; usually this is due to its use in ZCML within your application. If you're getting this deprecation warning, change your ZCML to use the new name, e.g. change:: To:: 0.8a4 (2009-05-04) ================== Features -------- - ``zope.testing`` is no longer a direct dependency, although our dependencies (such as ``zope.interface``, ``repoze.zcml``, etc) still depend on it. - Tested on Google App Engine. Added a tutorial to the documentation explaining how to deploy a BFG app to GAE. Backwards Incompatibilities --------------------------- - Applications which rely on ``zope.testing.cleanup.cleanUp`` in unit tests can still use that function indefinitely. However, for maximum forward compatibility, they should import ``cleanUp`` from ``repoze.bfg.testing`` instead of from ``zope.testing.cleanup``. The BFG paster templates and docs have been changed to use this function instead of the ``zope.testing.cleanup`` version. 0.8a3 (2009-05-03) =================== Features -------- - Don't require a successful import of ``zope.testing`` at BFG application runtime. This allows us to get rid of ``zope.testing`` on platforms like GAE which have file limits. 0.8a2 (2009-05-02) ================== Features -------- - We no longer include the ``configure.zcml`` of the ``chameleon.zpt`` package within the ``configure.zcml`` of the "repoze.bfg.includes" package. This has been a no-op for some time now. - The ``repoze.bfg.chameleon_zpt`` package no longer imports from ``chameleon.zpt`` at module scope, deferring the import until later within a method call. The ``chameleon.zpt`` package can't be imported on platforms like GAE. 0.8a1 (2009-05-02) ================== Deprecation Warning and Import Alias Removals --------------------------------------------- - Since version 0.6.1, a deprecation warning has been emitted when the name ``model_url`` is imported from the ``repoze.bfg.traversal`` module. This import alias (and the deprecation warning) has been removed. Any import of the ``model_url`` function will now need to be done from ``repoze.bfg.url``; any import of the name ``model_url`` from ``repoze.bfg.traversal`` will now fail. This was done to remove a dependency on zope.deferredimport. - Since version 0.6.5, a deprecation warning has been emitted when the name ``RoutesModelTraverser`` is imported from the ``repoze.bfg.traversal`` module. This import alias (and the deprecation warning) has been removed. Any import of the ``RoutesModelTraverser`` class will now need to be done from ``repoze.bfg.urldispatch``; any import of the name ``RoutesModelTraverser`` from ``repoze.bfg.traversal`` will now fail. This was done to remove a dependency on zope.deferredimport. Features -------- - This release of ``repoze.bfg`` is "C-free". This means it has no hard dependencies on any software that must be compiled from C source at installation time. In particular, ``repoze.bfg`` no longer depends on the ``lxml`` package. This change has introduced some backwards incompatibilities, described in the "Backwards Incompatibilities" section below. - This release was tested on Windows XP. It appears to work fine and all the tests pass. Backwards Incompatibilities --------------------------- Incompatibilities related to making ``repoze.bfg`` "C-free": - Removed the ``repoze.bfg.chameleon_genshi`` module, and thus support for Genshi-style chameleon templates. Genshi-style Chameleon templates depend upon ``lxml``, which is implemented in C (as opposed to pure Python) and the ``repoze.bfg`` core is "C-free" as of this release. You may get Genshi-style Chameleon support back by installing the ``repoze.bfg.chameleon_genshi`` package availalable from http://svn.repoze.org/repoze.bfg.chameleon_genshi (also available in the index at http://dist.repoze.org/bfg/0.8/simple). All existing code that depended on the ``chameleon_genshi`` module prior to this release of ``repoze.bfg`` should work without change after this addon is installed. - Removed the ``repoze.bfg.xslt`` module and thus support for XSL templates. The ``repoze.bfg.xslt`` module depended upon ``lxml``, which is implemented in C, and the ``repoze.bfg`` core is "C-free" as of this release. You bay get XSL templating back by installing the ``repoze.bfg.xslt`` package available from http://svn.repoze.org/repoze.bfg.xslt/ (also available in the index at http://dist.repoze.org/bfg/0.8/simple). All existing code that depended upon the ``xslt`` module prior to this release of ``repoze.bfg`` should work without modification after this addon is installed. - Removed the ``repoze.bfg.interfaces.INodeTemplateRenderer`` interface and the an old b/w compat aliases from that interface to ``repoze.bfg.interfaces.INodeTemplate``. This interface must now be imported from the ``repoze.bfg.xslt.interfaces`` package after installation of the ``repoze.bfg.xslt`` addon package described above as ``repoze.bfg.interfaces.INodeTemplateRenderer``. This interface was never part of any public API. Other backwards incompatibilities: - The ``render_template`` function in ``repoze.bfg.chameleon_zpt`` returns Unicode instead of a string. Likewise, the individual values returned by the iterable created by the ``render_template_to_iterable`` function are also each Unicode. This is actually a backwards incompatibility inherited from our new use of the combination of ``chameleon.core`` 1.0b32 (the non-lxml-depending version) and ``chameleon.zpt`` 1.0b16+ ; the ``chameleon.zpt`` PageTemplateFile implementation used to return a string, but now returns Unicode. 0.7.1 (2009-05-01) ================== Index-Related ------------- - The canonical package index location for ``repoze.bfg`` has changed. The "old" index (http://dist.repoze.org/lemonade/dev/simple) has been superseded by a new index location (`http://dist.repoze.org/bfg/current/simple `_). The installation documentation has been updated as well as the ``setup.cfg`` file in this package. The "lemonade" index still exists, but it is not guaranteed to have the latest BFG software in it, nor will it be maintained in the future. Features -------- - The "paster create" templates have been modified to use links to the new "bfg.repoze.org" and "docs.repoze.org" websites. - Added better documentation for virtual hosting at a URL prefix within the virtual hosting docs chapter. - The interface for ``repoze.bfg.interfaces.ITraverser`` and the built-in implementations that implement the interface (``repoze.bfg.traversal.ModelGraphTraverser``, and ``repoze.bfg.urldispatch.RoutesModelTraverser``) now expect the ``__call__`` method of an ITraverser to return 3 additional arguments: ``traversed``, ``virtual_root``, and ``virtual_root_path`` (the old contract was that the ``__call__`` method of an ITraverser returned; three arguments, the contract new is that it returns six). ``traversed`` will be a sequence of Unicode names that were traversed (including the virtual root path, if any) or ``None`` if no traversal was performed, ``virtual_root`` will be a model object representing the virtual root (or the physical root if traversal was not performed), and ``virtual_root_path`` will be a sequence representing the virtual root path (a sequence of Unicode names) or ``None`` if traversal was not performed. Six arguments are now returned from BFG ITraversers. They are returned in this order: ``context``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. Places in the BFG code which called an ITraverser continue to accept a 3-argument return value, although BFG will generate and log a warning when one is encountered. - The request object now has the following attributes: ``traversed`` (the sequence of names traversed or ``None`` if traversal was not performed), ``virtual_root`` (the model object representing the virtual root, including the virtual root path if any), and ``virtual_root_path`` (the seuquence of names representing the virtual root path or ``None`` if traversal was not performed). - A new decorator named ``wsgiapp2`` was added to the ``repoze.bfg.wsgi`` module. This decorator performs the same function as ``repoze.bfg.wsgi.wsgiapp`` except it fixes up the ``SCRIPT_NAME``, and ``PATH_INFO`` environment values before invoking the WSGI subapplication. - The ``repoze.bfg.testing.DummyRequest`` object now has default attributes for ``traversed``, ``virtual_root``, and ``virtual_root_path``. - The RoutesModelTraverser now behaves more like the Routes "RoutesMiddleware" object when an element in the match dict is named ``path_info`` (usually when there's a pattern like ``http://foo/*path_info``). When this is the case, the ``PATH_INFO`` environment variable is set to the value in the match dict, and the ``SCRIPT_NAME`` is appended to with the prefix of the original ``PATH_INFO`` not including the value of the new variable. - The notfound debug now shows the traversed path, the virtual root, and the virtual root path too. - Speed up / clarify 'traversal' module's 'model_path', 'model_path_tuple', and '_model_path_list' functions. Backwards Incompatibilities --------------------------- - In previous releases, the ``repoze.bfg.url.model_url``, ``repoze.bfg.traversal.model_path`` and ``repoze.bfg.traversal.model_path_tuple`` functions always ignored the ``__name__`` argument of the root object in a model graph ( effectively replacing it with a leading ``/`` in the returned value) when a path or URL was generated. The code required to perform this operation was not efficient. As of this release, the root object in a model graph *must* have a ``__name__`` attribute that is either ``None`` or the empty string (``''``) for URLs and paths to be generated properly from these APIs. If your root model object has a ``__name__`` argument that is not one of these values, you will need to change your code for URLs and paths to be generated properly. If your model graph has a root node with a string ``__name__`` that is not null, the value of ``__name__`` will be prepended to every path and URL generated. - The ``repoze.bfg.location.LocationProxy`` class and the ``repoze.bfg.location.ClassAndInstanceDescr`` class have both been removed in order to be able to eventually shed a dependency on ``zope.proxy``. Neither of these classes was ever an API. - In all previous releases, the ``repoze.bfg.location.locate`` function worked like so: if a model did not explicitly provide the ``repoze.bfg.interfaces.ILocation`` interface, ``locate`` returned a ``LocationProxy`` object representing ``model`` with its ``__parent__`` attribute assigned to ``parent`` and a ``__name__`` attribute assigned to ``__name__``. In this release, the ``repoze.bfg.location.locate`` function simply jams the ``__name__`` and ``__parent__`` attributes on to the supplied model unconditionally, no matter if the object implements ILocation or not, and it never returns a proxy. This was done because the LocationProxy behavior has now moved into an add-on package (``repoze.bfg.traversalwrapper``), in order to eventually be able to shed a dependency on ``zope.proxy``. - In all previous releases, by default, if traversal was used (as opposed to URL-dispatch), and the root object supplied the``repoze.bfg.interfaces.ILocation`` interface, but the children returned via its ``__getitem__`` returned an object that did not implement the same interface, ``repoze.bfg`` provided some implicit help during traversal. This traversal feature wrapped subobjects from the root (and thereafter) that did not implement ``ILocation`` in proxies which automatically provided them with a ``__name__`` and ``__parent__`` attribute based on the name being traversed and the previous object traversed. This feature has now been removed from the base ``repoze.bfg`` package for purposes of eventually shedding a dependency on ``zope.proxy``. In order to re-enable the wrapper behavior for older applications which cannot be changed, register the "traversalwrapper" ``ModelGraphTraverser`` as the traversal policy, rather than the default ``ModelGraphTraverser``. To use this feature, you will need to install the ``repoze.bfg.traversalwrapper`` package (an add-on package, available at http://svn.repoze.org/repoze.bfg.traversalwrapper) Then change your application's ``configure.zcml`` to include the following stanza: When this ITraverserFactory is used instead of the default, no object in the graph (even the root object) must supply a ``__name__`` or ``__parent__`` attribute. Even if subobjects returned from the root *do* implement the ILocation interface, these will still be wrapped in proxies that override the object's "real" ``__parent__`` and ``__name__`` attributes. See also changes to the "Models" chapter of the documentation (in the "Location-Aware Model Instances") section. 0.7.0 (2009-04-11) ================== Bug Fixes --------- - Fix a bug in ``repoze.bfg.wsgi.HTTPException``: the content length was returned as an int rather than as a string. - Add explicit dependencies on ``zope.deferredimport``, ``zope.deprecation``, and ``zope.proxy`` for forward compatibility reasons (``zope.component`` will stop relying on ``zope.deferredimport`` soon and although we use it directly, it's only a transitive dependency, and ''zope.deprecation`` and ``zope.proxy`` are used directly even though they're only transitive dependencies as well). - Using ``model_url`` or ``model_path`` against a broken model graph (one with models that had a non-root model with a ``__name__`` of ``None``) caused an inscrutable error to be thrown: ( if not ``_must_quote[cachekey].search(s): TypeError: expected string or buffer``). Now URLs and paths generated against graphs that have None names in intermediate nodes will replace the None with the empty string, and, as a result, the error won't be raised. Of course the URL or path will still be bogus. Features -------- - Make it possible to have ``testing.DummyTemplateRenderer`` return some nondefault string representation. - Added a new ``anchor`` keyword argument to ``model_url``. If ``anchor`` is present, its string representation will be used as a named anchor in the generated URL (e.g. if ``anchor`` is passed as ``foo`` and the model URL is ``http://example.com/model/url``, the generated URL will be ``http://example.com/model/url#foo``). Backwards Incompatibilities --------------------------- - The default request charset encoding is now ``utf-8``. As a result, the request machinery will attempt to decode values from the utf-8 encoding to Unicode automatically when they are obtained via ``request.params``, ``request.GET``, and ``request.POST``. The previous behavior of BFG was to return a bytestring when a value was accessed in this manner. This change will break form handling code in apps that rely on values from those APIs being considered bytestrings. If you are manually decoding values from form submissions in your application, you'll either need to change the code that does that to expect Unicode values from ``request.params``, ``request.GET`` and ``request.POST``, or you'll need to explicitly reenable the previous behavior. To reenable the previous behavior, add the following to your application's ``configure.zcml``:: See also the documentation in the "Views" chapter of the BFG docs entitled "Using Views to Handle Form Submissions (Unicode and Character Set Issues)". Documentation ------------- - Add a section to the narrative Views chapter entitled "Using Views to Handle Form Submissions (Unicode and Character Set Issues)" explaining implicit decoding of form data values. 0.6.9 (2009-02-16) ================== Bug Fixes --------- - lru cache was unstable under concurrency (big surprise!) when it tried to redelete a key in the cache that had already been deleted. Symptom: line 64 in put:del data[oldkey]:KeyError: '/some/path'. Now we just ignore the key error if we can't delete the key (it has already been deleted). - Empty location names in model paths when generating a URL using ``repoze.bfg.model_url`` based on a model obtained via traversal are no longer ignored in the generated URL. This means that if a non-root model object has a ``__name__`` of ``''``, the URL will reflect it (e.g. ``model_url`` will generate ``http://foo/bar//baz`` if an object with the ``__name__`` of ``''`` is a child of bar and the parent of baz). URLs generated with empty path segments are, however, still irresolveable by the model graph traverser on request ingress (the traverser strips empty path segment names). Features -------- - Microspeedups of ``repoze.bfg.traversal.model_path``, ``repoze.bfg.traversal.model_path_tuple``, ``repoze.bfg.traversal.quote_path_segment``, and ``repoze.bfg.url.urlencode``. - add zip_safe = false to setup.cfg. Documentation ------------- - Add a note to the ``repoze.bfg.traversal.quote_path_segment`` API docs about caching of computed values. Implementation Changes ---------------------- - Simplification of ``repoze.bfg.traversal.TraversalContextURL.__call__`` (it now uses ``repoze.bfg.traversal.model_path`` instead of rolling its own path-generation). 0.6.8 (2009-02-05) ================== Backwards Incompatibilities --------------------------- - The ``repoze.bfg.traversal.model_path`` API now returns a *quoted* string rather than a string represented by series of unquoted elements joined via ``/`` characters. Previously it returned a string or unicode object representing the model path, with each segment name in the path joined together via ``/`` characters, e.g. ``/foo /bar``. Now it returns a string, where each segment is a UTF-8 encoded and URL-quoted element e.g. ``/foo%20/bar``. This change was (as discussed briefly on the repoze-dev maillist) necessary to accomodate model objects which themselves have ``__name__`` attributes that contain the ``/`` character. For people that have no models that have high-order Unicode ``__name__`` attributes or ``__name__`` attributes with values that require URL-quoting with in their model graphs, this won't cause any issue. However, if you have code that currently expects ``model_path`` to return an unquoted string, or you have an existing application with data generated via the old method, and you're too lazy to change anything, you may wish replace the BFG-imported ``model_path`` in your code with this function (this is the code of the "old" ``model_path`` implementation):: from repoze.bfg.location import lineage def i_am_too_lazy_to_move_to_the_new_model_path(model, *elements): rpath = [] for location in lineage(model): if location.__name__: rpath.append(location.__name__) path = '/' + '/'.join(reversed(rpath)) if elements: suffix = '/'.join(elements) path = '/'.join([path, suffix]) return path - The ``repoze.bfg.traversal.find_model`` API no longer implicitly converts unicode representations of a full path passed to it as a Unicode object into a UTF-8 string. Callers should either use prequoted path strings returned by ``repoze.bfg.traversal.model_path``, or tuple values returned by the result of ``repoze.bfg.traversal.model_path_tuple`` or they should use the guidelines about passing a string ``path`` argument described in the ``find_model`` API documentation. Bugfixes -------- - Each argument contained in ``elements`` passed to ``repoze.bfg.traversal.model_path`` will now have any ``/`` characters contained within quoted to ``%2F`` in the returned string. Previously, ``/`` characters in elements were left unquoted (a bug). Features -------- - A ``repoze.bfg.traversal.model_path_tuple`` API was added. This API is an alternative to ``model_path`` (which returns a string); ``model_path_tuple`` returns a model path as a tuple (much like Zope's ``getPhysicalPath``). - A ``repoze.bfg.traversal.quote_path_segment`` API was added. This API will quote an individual path segment (string or unicode object). See the ``repoze.bfg.traversal`` API documentation for more information. - The ``repoze.bfg.traversal.find_model`` API now accepts "path tuples" (see the above note regarding ``model_path_tuple``) as well as string path representations (from ``repoze.bfg.traversal.model_path``) as a ``path`` argument. - Add ` `renderer`` argument (defaulting to None) to ``repoze.bfg.testing.registerDummyRenderer``. This makes it possible, for instance, to register a custom renderer that raises an exception in a unit test. Implementation Changes ---------------------- - Moved _url_quote function back to ``repoze.bfg.traversal`` from ``repoze.bfg.url``. This is not an API. 0.6.7 (2009-01-27) ================== Features -------- - The ``repoze.bfg.url.model_url`` API now works against contexts derived from Routes URL dispatch (``Routes.util.url_for`` is called under the hood). - "Virtual root" support for traversal-based applications has been added. Virtual root support is useful when you'd like to host some model in a ``repoze.bfg`` model graph as an application under a URL pathname that does not include the model path itself. For more information, see the (new) "Virtual Hosting" chapter in the documentation. - A ``repoze.bfg.traversal.virtual_root`` API has been added. When called, it returns the virtual root object (or the physical root object if no virtual root has been specified). Implementation Changes ---------------------- - ``repoze.bfg.traversal.RoutesModelTraverser`` has been moved to ``repoze.bfg.urldispatch``. - ``model_url`` URL generation is now performed via an adapter lookup based on the context and the request. - ZCML which registers two adapters for the ``IContextURL`` interface has been added to the configure.zcml in ``repoze.bfg.includes``. 0.6.6 (2009-01-26) ================== Implementation Changes ---------------------- - There is an indirection in ``repoze.bfg.url.model_url`` now that consults a utility to generate the base model url (without extra elements or a query string). Eventually this will service virtual hosting; for now it's undocumented and should not be hooked. 0.6.5 (2009-01-26) ================== Features -------- - You can now override the NotFound and Unauthorized responses that ``repoze.bfg`` generates when a view cannot be found or cannot be invoked due to lack of permission. See the "ZCML Hooks" chapter in the docs for more information. - Added Routes ZCML directive attribute explanations in documentation. - Added a ``traversal_path`` API to the traversal module; see the "traversal" API chapter in the docs. This was a function previously known as ``split_path`` that was not an API but people were using it anyway. Unlike ``split_path``, it now returns a tuple instead of a list (as its values are cached). Behavior Changes ---------------- - The ``repoze.bfg.view.render_view_to_response`` API will no longer raise a ValueError if an object returned by a view function it calls does not possess certain attributes (``headerlist``, ``app_iter``, ``status``). This API used to attempt to perform a check using the ``is_response`` function in ``repoze.bfg.view``, and raised a ``ValueError`` if the ``is_response`` check failed. The responsibility is now the caller's to ensure that the return value from a view function is a "real" response. - WSGI environ dicts passed to ``repoze.bfg`` 's Router must now contain a REQUEST_METHOD key/value; if they do not, a KeyError will be raised (speed). - It is no longer permissible to pass a "nested" list of principals to ``repoze.bfg.ACLAuthorizer.permits`` (e.g. ``['fred', ['larry', 'bob']]``). The principals list must be fully expanded. This feature was never documented, and was never an API, so it's not a backwards incompatibility. - It is no longer permissible for a security ACE to contain a "nested" list of permissions (e.g. ``(Allow, Everyone, ['read', ['view', ['write', 'manage']]])`)`. The list must instead be fully expanded (e.g. ``(Allow, Everyone, ['read', 'view', 'write', 'manage])``). This feature was never documented, and was never an API, so it's not a backwards incompatibility. - The ``repoze.bfg.urldispatch.RoutesRootFactory`` now injects the ``wsgiorg.routing_args`` environment variable into the environ when a route matches. This is a tuple of ((), routing_args) where routing_args is the value that comes back from the routes mapper match (the "match dict"). - The ``repoze.bfg.traversal.RoutesModelTraverser`` class now wants to obtain the ``view_name`` and ``subpath`` from the ``wsgiorgs.routing_args`` environment variable. It falls back to obtaining these from the context for backwards compatibility. Implementation Changes ---------------------- - Get rid of ``repoze.bfg.security.ACLAuthorizer``: the ``ACLSecurityPolicy`` now does what it did inline. - Get rid of ``repoze.bfg.interfaces.NoAuthorizationInformation`` exception: it was used only by ``ACLAuthorizer``. - Use a homegrown NotFound error instead of ``webob.exc.HTTPNotFound`` (the latter is slow). - Use a homegrown Unauthorized error instead of ``webob.exc.Unauthorized`` (the latter is slow). - the ``repoze.bfg.lru.lru_cached`` decorator now uses functools.wraps in order to make documentation of LRU-cached functions possible. - Various speed micro-tweaks. Bug Fixes --------- - ``repoze.bfg.testing.DummyModel`` did not have a ``get`` method; it now does. 0.6.4 (2009-01-23) ================== Backwards Incompatibilities --------------------------- - The ``unicode_path_segments`` configuration variable and the ``BFG_UNICODE_PATH_SEGMENTS`` configuration variable have been removed. Path segments are now always passed to model ``__getitem__`` methods as unicode. "True" has been the default for this setting since 0.5.4, but changing this configuration setting to false allowed you to go back to passing raw path element strings to model ``__getitem__`` methods. Removal of this knob services a speed goal (we get about +80 req/s by removing the check), and it's clearer just to always expect unicode path segments in model ``__getitem__`` methods. Implementation Changes ---------------------- - ``repoze.bfg.traversal.split_path`` now also handles decoding path segments to unicode (for speed, because its results are cached). - ``repoze.bfg.traversal.step`` was made a method of the ModelGraphTraverser. - Use "precooked" Request subclasses (e.g. ``repoze.bfg.request.GETRequest``) that correspond to HTTP request methods within ``router.py`` when constructing a request object rather than using ``alsoProvides`` to attach the proper interface to an unsubclassed ``webob.Request``. This pattern is purely an optimization (e.g. preventing calls to ``alsoProvides`` means the difference between 590 r/s and 690 r/s on a MacBook 2GHz). - Tease out an extra 4% performance boost by changing the Router; instead of using imported ZCA APIs, use the same APIs directly against the registry that is an attribute of the Router. - The registry used by BFG is now a subclass of ``zope.component.registry.Components`` (defined as ``repoze.bfg.registry.Registry``); it has a ``notify`` method, a ``registerSubscriptionAdapter`` and a ``registerHandler`` method. If no subscribers are registered via ``registerHandler`` or ``registerSubscriptionAdapter``, ``notify`` is a noop for speed. - The Allowed and Denied classes in ``repoze.bfg.security`` now are lazier about constructing the representation of a reason message for speed; ``repoze.bfg.view_execution_permitted`` takes advantage of this. - The ``is_response`` check was sped up by about half at the expense of making its code slightly uglier. New Modules ----------- - ``repoze.bfg.lru`` implements an LRU cache class and a decorator for internal use. 0.6.3 (2009-01-19) ================== Bug Fixes --------- - Readd ``root_policy`` attribute on Router object (as a property which returns the IRootFactory utility). It was inadvertently removed in 0.6.2. Code in the wild depended upon its presence (esp. scripts and "debug" helpers). Features -------- - URL-dispatch has been overhauled: it is no longer necessary to manually create a RoutesMapper in your application's entry point callable in order to use URL-dispatch (aka `Routes `_). A new ``route`` directive has been added to the available list of ZCML directives. Each ``route`` directive inserted into your application's ``configure.zcml`` establishes a Routes mapper connection. If any ``route`` declarations are made via ZCML within a particular application, the ``get_root`` callable passed in to ``repoze.bfg.router.make_app`` will automatically be wrapped in the equivalent of a RoutesMapper. Additionally, the new ``route`` directive allows the specification of a ``context_interfaces`` attribute for a route, this will be used to tag the manufactured routes context with specific interfaces when a route specifying a ``context_interfaces`` attribute is matched. - A new interface ``repoze.bfg.interfaces.IContextNotFound`` was added. This interface is attached to a "dummy" context generated when Routes cannot find a match and there is no "fallback" get_root callable that uses traversal. - The ``bfg_starter`` and ``bfg_zodb`` "paster create" templates now contain images and CSS which are displayed when the default page is displayed after initial project generation. - Allow the ``repoze.bfg.view.static`` helper to be passed a relative ``root_path`` name; it will be considered relative to the file in which it was called. - The functionality of ``repoze.bfg.convention`` has been merged into the core. Applications which make use of ``repoze.bfg.convention`` will continue to work indefinitely, but it is recommended that apps stop depending upon it. To do so, substitute imports of ``repoze.bfg.convention.bfg_view`` with imports of ``repoze.bfg.view.bfg_view``, and change the stanza in ZCML from ```` to ````. As a result of the merge, bfg has grown a new dependency: ``martian``. - View functions which use the pushpage decorator are now pickleable (meaning their use won't prevent a ``configure.zcml.cache`` file from being written to disk). - Instead of invariably using ``webob.Request`` as the "request factory" (e.g. in the ``Router`` class) and ``webob.Response`` and the "response factory" (e.g. in ``render_template_to_response``), allow both to be overridden via a ZCML utility hook. See the "Using ZCML Hooks" chapter of the documentation for more information. Deprecations ------------ - The class ``repoze.bfg.urldispatch.RoutesContext`` has been renamed to ``repoze.bfg.urldispatch.DefaultRoutesContext``. The class should be imported by the new name as necessary (although in reality it probably shouldn't be imported from anywhere except internally within BFG, as it's not part of the API). Implementation Changes ---------------------- - The ``repoze.bfg.wsgi.wsgiapp`` decorator now uses ``webob.Request.get_response`` to do its work rather than relying on homegrown WSGI code. - The ``repoze.bfg.view.static`` helper now uses ``webob.Request.get_response`` to do its work rather than relying on homegrown WSGI code. - The ``repoze.bfg.urldispatch.RoutesModelTraverser`` class has been moved to ``repoze.bfg.traversal.RoutesModelTraverser``. - The ``repoze.bfg.registry.makeRegistry`` function was renamed to ``repoze.bfg.registry.populateRegistry`` and now accepts a ``registry`` argument (which should be an instance of ``zope.component.registry.Components``). Documentation Additions ----------------------- - Updated narrative urldispatch chapter with changes required by ```` ZCML directive. - Add a section on "Using BFG Security With URL Dispatch" into the urldispatch chapter of the documentation. - Better documentation of security policy implementations that ship with repoze.bfg. - Added a "Using ZPT Macros in repoze.bfg" section to the narrative templating chapter. 0.6.2 (2009-01-13) ================== Features -------- - Tests can be run with coverage output if you've got ``nose`` installed in the interpreter which you use to run tests. Using an interpreter with ``nose`` installed, do ``python setup.py nosetests`` within a checkout of the ``repoze.bfg`` package to see test coverage output. - Added a ``post`` argument to the ``repoze.bfg.testing:DummyRequest`` constructor. - Added ``__len__`` and ``__nonzero__`` to ``repoze.bfg.testing:DummyModel``. - The ``repoze.bfg.registry.get_options`` callable (now renamed to ``repoze.bfg.setings.get_options``) used to return only framework-specific keys and values in the dictionary it returned. It now returns all the keys and values in the dictionary it is passed *plus* any framework-specific settings culled from the environment. As a side effect, all PasteDeploy application-specific config file settings are made available as attributes of the ``ISettings`` utility from within BFG. - Renamed the existing BFG paster template to ``bfg_starter``. Added another template (``bfg_zodb``) showing default ZODB setup using ``repoze.zodbconn``. - Add a method named ``assert_`` to the DummyTemplateRenderer. This method accepts keyword arguments. Each key/value pair in the keyword arguments causes an assertion to be made that the renderer received this key with a value equal to the asserted value. - Projects generated by the paster templates now use the ``DummyTemplateRenderer.assert_`` method in their view tests. - Make the (internal) thread local registry manager maintain a stack of registries in order to make it possible to call one BFG application from inside another. - An interface specific to the HTTP verb (GET/PUT/POST/DELETE/HEAD) is attached to each request object on ingress. The HTTP-verb-related interfaces are defined in ``repoze.bfg.interfaces`` and are ``IGETRequest``, ``IPOSTRequest``, ``IPUTRequest``, ``IDELETERequest`` and ``IHEADRequest``. These interfaces can be specified as the ``request_type`` attribute of a bfg view declaration. A view naming a specific HTTP-verb-matching interface will be found only if the view is defined with a request_type that matches the HTTP verb in the incoming request. The more general ``IRequest`` interface can be used as the request_type to catch all requests (and this is indeed the default). All requests implement ``IRequest``. The HTTP-verb-matching idea was pioneered by `repoze.bfg.restrequest `_ . That package is no longer required, but still functions fine. Bug Fixes --------- - Fix a bug where the Paste configuration's ``unicode_path_segments`` (and os.environ's ``BFG_UNICODE_PATH_SEGMENTS``) may have been defaulting to false in some circumstances. It now always defaults to true, matching the documentation and intent. - The ``repoze.bfg.traversal.find_model`` API did not work properly when passed a ``path`` argument which was unicode and contained high-order bytes when the ``unicode_path_segments`` or ``BFG_UNICODE_PATH_SEGMENTS`` configuration variables were "true". - A new module was added: ``repoze.bfg.settings``. This contains deployment-settings-related code. Implementation Changes ---------------------- - The ``make_app`` callable within ``repoze.bfg.router`` now registers the ``root_policy`` argument as a utility (unnamed, using the new ``repoze.bfg.interfaces.IRootFactory`` as a provides interface) rather than passing it as the first argument to the ``repoze.bfg.router.Router`` class. As a result, the ``repoze.bfg.router.Router`` router class only accepts a single argument: ``registry``. The ``repoze.bfg.router.Router`` class retrieves the root policy via a utility lookup now. The ``repoze.bfg.router.make_app`` API also now performs some important application registrations that were previously handled inside ``repoze.bfg.registry.makeRegistry``. New Modules ----------- - A ``repoze.bfg.settings`` module was added. It contains code related to deployment settings. Most of the code it contains was moved to it from the ``repoze.bfg.registry`` module. Behavior Changes ---------------- - The ``repoze.bfg.settings.Settings`` class (an instance of which is registered as a utility providing ``repoze.bfg.interfaces.ISettings`` when any application is started) now automatically calls ``repoze.bfg.settings.get_options`` on the options passed to its constructor. This means that usage of ``get_options`` within an application's ``make_app`` function is no longer required (the "raw" ``options`` dict or None may be passed). - Remove old cold which attempts to recover from trying to unpickle a ``z3c.pt`` template; Chameleon has been the templating engine for a good long time now. Running repoze.bfg against a sandbox that has pickled ``z3c.pt`` templates it will now just fail with an unpickling error, but can be fixed by deleting the template cache files. Deprecations ------------ - Moved the ``repoze.bfg.registry.Settings`` class. This has been moved to ``repoze.bfg.settings.Settings``. A deprecation warning is issued when it is imported from the older location. - Moved the ``repoze.bfg.registry.get_options`` function This has been moved to ``repoze.bfg.settings.get_options``. A deprecation warning is issued when it is imported from the older location. - The ``repoze.bfg.interfaces.IRootPolicy`` interface was renamed within the interfaces package. It has been renamed to ``IRootFactory``. A deprecation warning is issued when it is imported from the older location. 0.6.1 (2009-01-06) ================== New Modules ----------- - A new module ``repoze.bfg.url`` has been added. It contains the ``model_url`` API (moved from ``repoze.bfg.traversal``) and an implementation of ``urlencode`` (like Python's ``urllib.urlencode``) which can handle Unicode keys and values in parameters to the ``query`` argument. Deprecations ------------ - The ``model_url`` function has been moved from ``repoze.bfg.traversal`` into ``repoze.bfg.url``. It can still be imported from ``repoze.bfg.traversal`` but an import from ``repoze.bfg.traversal`` will emit a DeprecationWarning. Features -------- - A ``static`` helper class was added to the ``repoze.bfg.views`` module. Instances of this class are willing to act as BFG views which return static resources using files on disk. See the ``repoze.bfg.view`` docs for more info. - The ``repoze.bfg.url.model_url`` API (nee' ``repoze.bfg.traversal.model_url``) now accepts and honors a keyword argument named ``query``. The value of this argument will be used to compose a query string, which will be attached to the generated URL before it is returned. See the API docs (in the docs directory or `on the web `_) for more information. 0.6 (2008-12-26) ================ Backwards Incompatibilities --------------------------- - Rather than prepare the "stock" implementations of the ZCML directives from the ``zope.configuration`` package for use under ``repoze.bfg``, ``repoze.bfg`` now makes available the implementations of directives from the ``repoze.zcml`` package (see http://static.repoze.org/zcmldocs). As a result, the ``repoze.bfg`` package now depends on the ``repoze.zcml`` package, and no longer depends directly on the ``zope.component``, ``zope.configuration``, ``zope.interface``, or ``zope.proxy`` packages. The primary reason for this change is to enable us to eventually reduce the number of inappropriate ``repoze.bfg`` Zope package dependencies, as well as to shed features of dependent package directives that don't make sense for ``repoze.bfg``. Note that currently the set of requirements necessary to use bfg has not changed. This is due to inappropriate Zope package requirements in ``chameleon.zpt``, which will hopefully be remedied soon. NOTE: in lemonade index a 1.0b8-repozezcml0 package exists which does away with these requirements. - BFG applications written prior to this release which expect the "stock" ``zope.component`` ZCML directive implementations (e.g. ``adapter``, ``subscriber``, or ``utility``) to function now must either 1) include the ``meta.zcml`` file from ``zope.component`` manually (e.g. ````) and include the ``zope.security`` package as an ``install_requires`` dependency or 2) change the ZCML in their applications to use the declarations from `repoze.zcml `_ instead of the stock declarations. ``repoze.zcml`` only makes available the ``adapter``, ``subscriber`` and ``utility`` directives. In short, if you've got an existing BFG application, after this update, if your application won't start due to an import error for "zope.security", the fastest way to get it working again is to add ``zope.security`` to the "install_requires" of your BFG application's ``setup.py``, then add the following ZCML anywhere in your application's ``configure.zcml``:: Then re-``setup.py develop`` or reinstall your application. - The ``http://namespaces.repoze.org/bfg`` XML namespace is now the default XML namespace in ZCML for paster-generated applications. The docs have been updated to reflect this. - The copies of BFG's ``meta.zcml`` and ``configure.zcml`` were removed from the root of the ``repoze.bfg`` package. In 0.3.6, a new package named ``repoze.bfg.includes`` was added, which contains the "correct" copies of these ZCML files; the ones that were removed were for backwards compatibility purposes. - The BFG ``view`` ZCML directive no longer calls ``zope.component.interface.provideInterface`` for the ``for`` interface. We don't support ``provideInterface`` in BFG because it mutates the global registry. Other ----- - The minimum requirement for ``chameleon.core`` is now 1.0b13. The minimum requirement for ``chameleon.zpt`` is now 1.0b8. The minimum requirement for ``chameleon.genshi`` is now 1.0b2. - Updated paster template "ez_setup.py" to one that requires setuptools 0.6c9. - Turn ``view_execution_permitted`` from the ``repoze.bfg.view`` module into a documented API. - Doc cleanups. - Documented how to create a view capable of serving static resources. 0.5.6 (2008-12-18) ================== - Speed up ``traversal.model_url`` execution by using a custom url quoting function instead of Python's ``urllib.quote``, by caching URL path segment quoting and encoding results, by disusing Python's ``urlparse.urljoin`` in favor of a simple string concatenation, and by using ``ob.__class__ is unicode`` rather than ``isinstance(ob, unicode)`` in one strategic place. 0.5.5 (2008-12-17) ================== Backwards Incompatibilities --------------------------- - In the past, during traversal, the ModelGraphTraverser (the default traverser) always passed each URL path segment to any ``__getitem__`` method of a model object as a byte string (a ``str`` object). Now, by default the ModelGraphTraverser attempts to decode the path segment to Unicode (a ``unicode`` object) using the UTF-8 encoding before passing it to the ``__getitem__`` method of a model object. This makes it possible for model objects to be dumber in ``__getitem__`` when trying to resolve a subobject, as model objects themselves no longer need to try to divine whether or not to try to decode the path segment passed by the traverser. Note that since 0.5.4, URLs generated by repoze.bfg's ``model_url`` API will contain UTF-8 encoded path segments as necessary, so any URL generated by BFG itself will be decodeable by the traverser. If another application generates URLs to a BFG application, to be resolved successully, it should generate the URL with UTF-8 encoded path segments to be successfully resolved. The decoder is not at all magical: if a non-UTF-8-decodeable path segment (e.g. one encoded using UTF-16 or some other insanity) is passed in the URL, BFG will raise a ``TypeError`` with a message indicating it could not decode the path segment. To turn on the older behavior, where path segments were not decoded to Unicode before being passed to model object ``__getitem__`` by the traverser, and were passed as a raw byte string, set the ``unicode_path_segments`` configuration setting to a false value in your BFG application's section of the paste .ini file, for example:: unicode_path_segments = False Or start the application using the ``BFG_UNICODE_PATH_SEGMENT`` envvar set to a false value:: BFG_UNICODE_PATH_SEGMENTS=0 0.5.4 (2008-12-13) ================== Backwards Incompatibilities --------------------------- - URL-quote "extra" element names passed in as ``**elements`` to the ``traversal.model_url`` API. If any of these names is a Unicode string, encode it to UTF-8 before URL-quoting. This is a slight backwards incompatibility that will impact you if you were already UTF-8 encoding or URL-quoting the values you passed in as ``elements`` to this API. Bugfixes -------- - UTF-8 encode each segment in the model path used to generate a URL before url-quoting it within the ``traversal.model_url`` API. This is a bugfix, as Unicode cannot always be successfully URL-quoted. Features -------- - Make it possible to run unit tests using a buildout-generated Python "interpreter". - Add ``request.root`` to ``router.Router`` in order to have easy access to the application root. 0.5.3 (2008-12-07) ================== - Remove the ``ITestingTemplateRenderer`` interface. When ``testing.registerDummyRenderer`` is used, it instead registers a dummy implementation using ``ITemplateRenderer`` interface, which is checked for when the built-in templating facilities do rendering. This change also allows developers to make explcit named utility registrations in the ZCML registry against ``ITemplateRenderer``; these will be found before any on-disk template is looked up. 0.5.2 (2008-12-05) ================== - The component registration handler for views (functions or class instances) now observes component adaptation annotations (see ``zope.component.adaptedBy``) and uses them before the fallback values for ``for_`` and ``request_type``. This change does not affect existing code insomuch as the code does not rely on these defaults when an annotation is set on the view (unlikely). This means that for a new-style class you can do ``zope.component.adapts(ISomeContext, ISomeRequest)`` at class scope or at module scope as a decorator to a bfg view function you can do ``@zope.component.adapter(ISomeContext, ISomeRequest)``. This differs from r.bfg.convention inasmuch as you still need to put something in ZCML for the registrations to get done; it's only the defaults that will change if these declarations exist. - Strip all slashes from end and beginning of path in clean_path within traversal machinery. 0.5.1 (2008-11-25) ================== - Add ``keys``, ``items``, and ``values`` methods to ``testing.DummyModel``. - Add __delitem__ method to ``testing.DummyModel``. 0.5.0 (2008-11-18) ================== - Fix ModelGraphTraverser; don't try to change the ``__name__`` or ``__parent__`` of an object that claims it implements ILocation during traversal even if the ``__name__`` or ``__parent__`` of the object traversed does not match the name used in the traversal step or the or the traversal parent . Rationale: it was insane to do so. This bug was only found due to a misconfiguration in an application that mistakenly had intermediate persistent non-ILocation objects; traversal was causing a persistent write on every request under this setup. - ``repoze.bfg.location.locate`` now unconditionally sets ``__name__`` and ``__parent__`` on objects which provide ILocation (it previously only set them conditionally if they didn't match attributes already present on the object via equality). 0.4.9 (2008-11-17) ================== - Add chameleon text template API (chameleon ${name} renderings where the template does not need to be wrapped in any containing XML). - Change docs to explain install in terms of a virtualenv (unconditionally). - Make pushpage decorator compatible with repoze.bfg.convention's ``bfg_view`` decorator when they're stacked. - Add content_length attribute to testing.DummyRequest. - Change paster template ``tests.py`` to include a true unit test. Retain old test as an integration test. Update documentation. - Document view registrations against classes and ``repoze.bfg.convention`` in context. - Change the default paster template to register its single view against a class rather than an interface. - Document adding a request type interface to the request via a subscriber function in the events narrative documentation. 0.4.8 (2008-11-12) ================== Backwards Incompatibilities --------------------------- - ``repoze.bfg.traversal.model_url`` now always appends a slash to all generated URLs unless further elements are passed in as the third and following arguments. Rationale: views often use ``model_url`` without the third-and-following arguments in order to generate a URL for a model in order to point at the default view of a model. The URL that points to the default view of the *root* model is technically ``http://mysite/`` as opposed to ``http://mysite`` (browsers happen to ask for '/' implicitly in the GET request). Because URLs are never automatically generated for anything *except* models by ``model_url``, and because the root model is not really special, we continue this pattern. The impact of this change is minimal (at most you will have too many slashes in your URL, which BFG deals with gracefully anyway). 0.4.7 (2008-11-11) ================== Features -------- - Allow ``testing.registerEventListener`` to be used with Zope 3 style "object events" (subscribers accept more than a single event argument). We extend the list with the arguments, rather than append. 0.4.6 (2008-11-10) ================== Bug Fixes --------- - The ``model_path`` and ``model_url`` traversal APIs returned the wrong value for the root object (e.g. ``model_path`` returned ``''`` for the root object, while it should have been returning ``'/'``). 0.4.5 (2008-11-09) ================== Features -------- - Added a ``clone`` method and a ``__contains__`` method to the DummyModel testing object. - Allow DummyModel objects to receive extra keyword arguments, which will be attached as attributes. - The DummyTemplateRenderer now returns ``self`` as its implementation. 0.4.4 (2008-11-08) ================== Features -------- - Added a ``repoze.bfg.testing`` module to attempt to make it slightly easier to write unittest-based automated tests of BFG applications. Information about this module is in the documentation. - The default template renderer now supports testing better by looking for ``ITestingTemplateRenderer`` using a relative pathname. This is exposed indirectly through the API named ``registerTemplateRenderer`` in ``repoze.bfg.testing``. Deprecations ------------ - The names ``repoze.bfg.interfaces.ITemplate`` , ``repoze.bfg.interfaces.ITemplateFactory`` and ``repoze.bfg.interfaces.INodeTemplate`` have been deprecated. These should now be imported as ``repoze.bfg.interfaces.ITemplateRenderer`` and ``repoze.bfg.interfaces.ITemplateRendererFactory``, and ``INodeTemplateRenderer`` respectively. - The name ``repoze.bfg.chameleon_zpt.ZPTTemplateFactory`` is deprecated. Use ``repoze.bfg.chameleon_zpt.ZPTTemplateRenderer``. - The name ``repoze.bfg.chameleon_genshi.GenshiTemplateFactory`` is deprecated. Use ``repoze.bfg.chameleon_genshi.GenshiTemplateRenderer``. - The name ``repoze.bfg.xslt.XSLTemplateFactory`` is deprecated. Use ``repoze.bfg.xslt.XSLTemplateRenderer``. 0.4.3 (2008-11-02) ================== Bug Fixes --------- - Not passing the result of "get_options" as the second argument of make_app could cause attribute errors when attempting to look up settings against the ISettings object (internal). Fixed by giving the Settings objects defaults for ``debug_authorization`` and ``debug_notfound``. - Return an instance of ``Allowed`` (rather than ``True``) from ``has_permission`` when no security policy is in use. - Fix bug where default deny in authorization check would throw a TypeError (use ``ACLDenied`` instead of ``Denied``). 0.4.2 (2008-11-02) ================== Features -------- - Expose a single ILogger named "repoze.bfg.debug" as a utility; this logger is registered unconditionally and is used by the authorization debug machinery. Applications may also make use of it as necessary rather than inventing their own logger, for convenience. - The ``BFG_DEBUG_AUTHORIZATION`` envvar and the ``debug_authorization`` config file value now only imply debugging of view-invoked security checks. Previously, information was printed for every call to ``has_permission`` as well, which made output confusing. To debug ``has_permission`` checks and other manual permission checks, use the debugger and print statements in your own code. - Authorization debugging info is now only present in the HTTP response body oif ``debug_authorization`` is true. - The format of authorization debug messages was improved. - A new ``BFG_DEBUG_NOTFOUND`` envvar was added and a symmetric ``debug_notfound`` config file value was added. When either is true, and a NotFound response is returned by the BFG router (because a view could not be found), debugging information is printed to stderr. When this value is set true, the body of HTTPNotFound responses will also contain the same debugging information. - ``Allowed`` and ``Denied`` responses from the security machinery are now specialized into two types: ACL types, and non-ACL types. The ACL-related responses are instances of ``repoze.bfg.security.ACLAllowed`` and ``repoze.bfg.security.ACLDenied``. The non-ACL-related responses are ``repoze.bfg.security.Allowed`` and ``repoze.bfg.security.Denied``. The allowed-type responses continue to evaluate equal to things that themselves evaluate equal to the ``True`` boolean, while the denied-type responses continue to evaluate equal to things that themselves evaluate equal to the ``False`` boolean. The only difference between the two types is the information attached to them for debugging purposes. - Added a new ``BFG_DEBUG_ALL`` envvar and a symmetric ``debug_all`` config file value. When either is true, all other debug-related flags are set true unconditionally (e.g. ``debug_notfound`` and ``debug_authorization``). Documentation ------------- - Added info about debug flag changes. - Added a section to the security chapter named "Debugging Imperative Authorization Failures" (for e.g. ``has_permssion``). Bug Fixes --------- - Change default paster template generator to use ``Paste#http`` server rather than ``PasteScript#cherrpy`` server. The cherrypy server has a security risk in it when ``REMOTE_USER`` is trusted by the downstream application. 0.4.1 (2008-10-28) ================== Bug Fixes --------- - If the ``render_view_to_response`` function was called, if the view was found and called, but it returned something that did not implement IResponse, the error would pass by unflagged. This was noticed when I created a view function that essentially returned None, but received a NotFound error rather than a ValueError when the view was rendered. This was fixed. 0.4.0 (2008-10-03) ================== Docs ---- - An "Environment and Configuration" chapter was added to the narrative portion of the documentation. Features -------- - Ensure bfg doesn't generate warnings when running under Python 2.6. - The environment variable ``BFG_RELOAD_TEMPLATES`` is now available (serves the same purpose as ``reload_templates`` in the config file). - A new configuration file option ``debug_authorization`` was added. This turns on printing of security authorization debug statements to ``sys.stderr``. The ``BFG_DEBUG_AUTHORIZATION`` environment variable was also added; this performs the same duty. Bug Fixes --------- - The environment variable ``BFG_SECURITY_DEBUG`` did not always work. It has been renamed to ``BFG_DEBUG_AUTHORIZATION`` and fixed. Deprecations ------------ - A deprecation warning is now issued when old API names from the ``repoze.bfg.templates`` module are imported. Backwards incompatibilities --------------------------- - The ``BFG_SECURITY_DEBUG`` environment variable was renamed to ``BFG_DEBUG_AUTHORIZATION``. 0.3.9 (2008-08-27) ================== Features -------- - A ``repoze.bfg.location`` API module was added. Backwards incompatibilities --------------------------- - Applications must now use the ``repoze.bfg.interfaces.ILocation`` interface rather than ``zope.location.interfaces.ILocation`` to represent that a model object is "location-aware". We've removed a dependency on ``zope.location`` for cleanliness purposes: as new versions of zope libraries are released which have improved dependency information, getting rid of our dependence on ``zope.location`` will prevent a newly installed repoze.bfg application from requiring the ``zope.security``, egg, which not truly used at all in a "stock" repoze.bfg setup. These dependencies are still required by the stack at this time; this is purely a futureproofing move. The security and model documentation for previous versions of ``repoze.bfg`` recommended using the ``zope.location.interfaces.ILocation`` interface to represent that a model object is "location-aware". This documentation has been changed to reflect that this interface should now be imported from ``repoze.bfg.interfaces.ILocation`` instead. 0.3.8 (2008-08-26) ================== Docs ---- - Documented URL dispatch better in narrative form. Bug fixes --------- - Routes URL dispatch did not have access to the WSGI environment, so conditions such as method=GET did not work. Features -------- - Add ``principals_allowed_by_permission`` API to security module. - Replace ``z3c.pt`` support with support for ``chameleon.zpt``. Chameleon is the new name for the package that used to be named ``z3c.pt``. NOTE: If you update a ``repoze.bfg`` SVN checkout that you're using for development, you will need to run "setup.py install" or "setup.py develop" again in order to obtain the proper Chameleon packages. ``z3c.pt`` is no longer supported by ``repoze.bfg``. All API functions that used to render ``z3c.pt`` templates will work fine with the new packages, and your templates should render almost identically. - Add a ``repoze.bfg.chameleon_zpt`` module. This module provides Chameleon ZPT support. - Add a ``repoze.bfg.xslt`` module. This module provides XSLT support. - Add a ``repoze.bfg.chameleon_genshi`` module. This provides direct Genshi support, which did not exist previously. Deprecations ------------ - Importing API functions directly from ``repoze.bfg.template`` is now deprecated. The ``get_template``, ``render_template``, ``render_template_to_response`` functions should now be imported from ``repoze.chameleon_zpt``. The ``render_transform``, and ``render_transform_to_response`` functions should now be imported from ``repoze.bfg.xslt``. The ``repoze.bfg.template`` module will remain around "forever" to support backwards compatibility. 0.3.7 (2008-09-09) ================== Features -------- - Add compatibility with z3c.pt 1.0a7+ (z3c.pt became a namespace package). Bug fixes --------- - ``repoze.bfg.traversal.find_model`` function did not function properly. 0.3.6 (2008-09-04) ================== Features -------- - Add startup process docs. - Allow configuration cache to be bypassed by actions which include special "uncacheable" discriminators (for actions that have variable results). Bug Fixes --------- - Move core repoze.bfg ZCML into a ``repoze.bfg.includes`` package so we can use repoze.bfg better as a namespace package. Adjust the code generator to use it. We've left around the ``configure.zcml`` in the repoze.bfg package directly so as not to break older apps. - When a zcml application registry cache was unpickled, and it contained a reference to an object that no longer existed (such as a view), bfg would not start properly. 0.3.5 (2008-09-01) ================== Features -------- - Event notification is issued after application is created and configured (``IWSGIApplicationCreatedEvent``). - New API module: ``repoze.bfg.view``. This module contains the functions named ``render_view_to_response``, ``render_view_to_iterable``, ``render_view`` and ``is_response``, which are documented in the API docs. These features aid programmatic (non-server-driven) view execution. 0.3.4 (2008-08-28) ================== Backwards incompatibilities --------------------------- - Make ``repoze.bfg`` a namespace package so we can allow folks to create subpackages (e.g. ``repoze.bfg.otherthing``) within separate eggs. This is a backwards incompatible change which makes it impossible to import "make_app" and "get_options" from the ``repoze.bfg`` module directly. This change will break all existing apps generated by the paster code generator. Instead, you need to import these functions as ``repoze.bfg.router:make_app`` and ``repoze.bfg.registry:get_options``, respectively. Sorry folks, it has to be done now or never, and definitely better now. Features -------- - Add ``model_path`` API function to traversal module. Bugfixes - Normalize path returned by repoze.bfg.caller_path. 0.3.3 (2008-08-23) ================== - Fix generated test.py module to use project name rather than package name. 0.3.2 (2008-08-23) ================== - Remove ``sampleapp`` sample application from bfg package itself. - Remove dependency on FormEncode (only needed by sampleapp). - Fix paster template generation so that case-sensitivity is preserved for project vs. package name. - Depend on ``z3c.pt`` version 1.0a1 (which requires the ``[lxml]`` extra currently). - Read and write a pickled ZCML actions list, stored as ``configure.zcml.cache`` next to the applications's "normal" configuration file. A given bfg app will usually start faster if it's able to read the pickle data. It fails gracefully to reading the real ZCML file if it cannot read the pickle. 0.3.1 (2008-08-20) ================== - Generated application differences: ``make_app`` entry point renamed to ``app`` in order to have a different name than the bfg function of the same name, to prevent confusion. - Add "options" processing to bfg's ``make_app`` to support runtime options. A new API function named ``get_options`` was added to the registry module. This function is typically used in an application's ``app`` entry point. The Paste config file section for the app can now supply the ``reload_templates`` option, which, if true, will prevent the need to restart the appserver in order for ``z3c.pt`` or XSLT template changes to be detected. - Use only the module name in generated project's "test_suite" (run all tests found in the package). - Default port for generated apps changed from 5432 to 6543 (Postgres default port is 6543). 0.3.0 (2008-08-16) ================== - Add ``get_template`` API to template module. 0.2.9 (2008-08-11) ================== - 0.2.8 was "brown bag" release. It didn't work at all. Symptom: ComponentLookupError when trying to render a page. 0.2.8 (2008-08-11) ================== - Add ``find_model`` and ``find_root`` traversal APIs. In the process, make ITraverser a uni-adapter (on context) rather than a multiadapter (on context and request). 0.2.7 (2008-08-05) ================== - Add a ``request_type`` attribute to the available attributes of a ``bfg:view`` configure.zcml element. This attribute will have a value which is a dotted Python path, pointing at an interface. If the request object implements this interface when the view lookup is performed, the appropriate view will be called. This is meant to allow for simple "skinning" of sites based on request type. An event subscriber should attach the interface to the request on ingress to support skins. - Remove "template only" views. These were just confusing and were never documented. - Small url dispatch overhaul: the ``connect`` method of the ``urldispatch.RoutesMapper`` object now accepts a keyword parameter named ``context_factory``. If this parameter is supplied, it must be a callable which returns an instance. This instance is used as the context for the request when a route is matched. - The registration of a RoutesModelTraverser no longer needs to be performed by the application; it's in the bfg ZCML now. 0.2.6 (2008-07-31) ================== - Add event sends for INewRequest and INewResponse. See the events.rst chapter in the documentation's ``api`` directory. 0.2.5 (2008-07-28) ================== - Add ``model_url`` API. 0.2.4 (2008-07-27) ================== - Added url-based dispatch. 0.2.3 (2008-07-20) ================== - Add API functions for authenticated_userid and effective_principals. 0.2.2 (2008-07-20) ================== - Add authenticated_userid and effective_principals API to security policy. 0.2.1 (2008-07-20) ================== - Add find_interface API. 0.2 (2008-07-19) ================ - Add wsgiapp decorator. - The concept of "view factories" was removed in favor of always calling a view, which is a callable that returns a response directly (as opposed to returning a view). As a result, the ``factory`` attribute in the bfg:view ZCML statement has been renamed to ``view``. Various interface names were changed also. - ``render_template`` and ``render_transform`` no longer return a Response object. Instead, these return strings. The old behavior can be obtained by using ``render_template_to_response`` and ``render_transform_to_response``. - Added 'repoze.bfg.push:pushpage' decorator, which creates BFG views from callables which take (context, request) and return a mapping of top-level names. - Added ACL-based security. - Support for XSLT templates via a render_transform method 0.1 (2008-07-08) ================ - Initial release. pyramid-1.6/builddocs.sh0000755000076500000240000000003112627241405016056 0ustar michaelstaff00000000000000#!/bin/bash tox -e docs pyramid-1.6/CHANGES.txt0000644000076500000240000004246212642137241015375 0ustar michaelstaff000000000000001.6 (2016-01-03) ================ Deprecations ------------ - Continue removal of ``pserve`` daemon/process management features by deprecating ``--user`` and ``--group`` options. See https://github.com/Pylons/pyramid/pull/2190 1.6b3 (2015-12-17) ================== Backward Incompatibilities -------------------------- - Remove the ``cachebust`` option from ``config.add_static_view``. See ``config.add_cache_buster`` for the new way to attach cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 - Modify the ``pyramid.interfaces.ICacheBuster`` API to be a simple callable instead of an object with ``match`` and ``pregenerate`` methods. Cache busters are now focused solely on generation. Matching has been dropped. Note this affects usage of ``pyramid.static.QueryStringCacheBuster`` and ``pyramid.static.ManifestCacheBuster``. See https://github.com/Pylons/pyramid/pull/2186 Features -------- - Add a new ``config.add_cache_buster`` API for attaching cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 Bug Fixes --------- - Ensure that ``IAssetDescriptor.abspath`` always returns an absolute path. There were cases depending on the process CWD that a relative path would be returned. See https://github.com/Pylons/pyramid/issues/2188 1.6b2 (2015-10-15) ================== Features -------- - Allow asset specifications to be supplied to ``pyramid.static.ManifestCacheBuster`` instead of requiring a filesystem path. 1.6b1 (2015-10-15) ================== Backward Incompatibilities -------------------------- - IPython and BPython support have been removed from pshell in the core. To continue using them on Pyramid 1.6+ you must install the binding packages explicitly:: $ pip install pyramid_ipython or $ pip install pyramid_bpython - Remove default cache busters introduced in 1.6a1 including ``PathSegmentCacheBuster``, ``PathSegmentMd5CacheBuster``, and ``QueryStringMd5CacheBuster``. See https://github.com/Pylons/pyramid/pull/2116 Features -------- - Additional shells for ``pshell`` can now be registered as entrypoints. See https://github.com/Pylons/pyramid/pull/1891 and https://github.com/Pylons/pyramid/pull/2012 - The variables injected into ``pshell`` are now displayed with their docstrings instead of the default ``str(obj)`` when possible. See https://github.com/Pylons/pyramid/pull/1929 - Add new ``pyramid.static.ManifestCacheBuster`` for use with external asset pipelines as well as examples of common usages in the narrative. See https://github.com/Pylons/pyramid/pull/2116 - Fix ``pserve --reload`` to not crash on syntax errors!!! See https://github.com/Pylons/pyramid/pull/2125 - Fix an issue when user passes unparsed strings to ``pyramid.session.CookieSession`` and ``pyramid.authentication.AuthTktCookieHelper`` for time related parameters ``timeout``, ``reissue_time``, ``max_age`` that expect an integer value. See https://github.com/Pylons/pyramid/pull/2050 Bug Fixes --------- - ``pyramid.httpexceptions.HTTPException`` now defaults to ``520 Unknown Error`` instead of ``None None`` to conform with changes in WebOb 1.5. See https://github.com/Pylons/pyramid/pull/1865 - ``pshell`` will now preserve the capitalization of variables in the ``[pshell]`` section of the INI file. This makes exposing classes to the shell a little more straightfoward. See https://github.com/Pylons/pyramid/pull/1883 - Fixed usage of ``pserve --monitor-restart --daemon`` which would fail in horrible ways. See https://github.com/Pylons/pyramid/pull/2118 - Explicitly prevent ``pserve --reload --daemon`` from being used. It's never been supported but would work and fail in weird ways. See https://github.com/Pylons/pyramid/pull/2119 - Fix an issue on Windows when running ``pserve --reload`` in which the process failed to fork because it could not find the pserve script to run. See https://github.com/Pylons/pyramid/pull/2138 Deprecations ------------ - Deprecate ``pserve --monitor-restart`` in favor of user's using a real process manager such as Systemd or Upstart as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/2120 1.6a2 (2015-06-30) ================== Bug Fixes --------- - Ensure that ``pyramid.httpexceptions.exception_response`` returns the appropriate "concrete" class for ``400`` and ``500`` status codes. See https://github.com/Pylons/pyramid/issues/1832 - Fix an infinite recursion bug introduced in 1.6a1 when ``pyramid.view.render_view_to_response`` was called directly or indirectly. See https://github.com/Pylons/pyramid/issues/1643 - Further fix the JSONP renderer by prefixing the returned content with a comment. This should mitigate attacks from Flash (See CVE-2014-4671). See https://github.com/Pylons/pyramid/pull/1649 - Allow periods and brackets (``[]``) in the JSONP callback. The original fix was overly-restrictive and broke Angular. See https://github.com/Pylons/pyramid/pull/1649 1.6a1 (2015-04-15) ================== Features -------- - pcreate will now ask for confirmation if invoked with an argument for a project name that already exists or is importable in the current environment. See https://github.com/Pylons/pyramid/issues/1357 and https://github.com/Pylons/pyramid/pull/1837 - Make it possible to subclass ``pyramid.request.Request`` and also use ``pyramid.request.Request.add_request.method``. See https://github.com/Pylons/pyramid/issues/1529 - The ``pyramid.config.Configurator`` has grown the ability to allow actions to call other actions during a commit-cycle. This enables much more logic to be placed into actions, such as the ability to invoke other actions or group them for improved conflict detection. We have also exposed and documented the config phases that Pyramid uses in order to further assist in building conforming addons. See https://github.com/Pylons/pyramid/pull/1513 - Add ``pyramid.request.apply_request_extensions`` function which can be used in testing to apply any request extensions configured via ``config.add_request_method``. Previously it was only possible to test the extensions by going through Pyramid's router. See https://github.com/Pylons/pyramid/pull/1581 - pcreate when run without a scaffold argument will now print information on the missing flag, as well as a list of available scaffolds. See https://github.com/Pylons/pyramid/pull/1566 and https://github.com/Pylons/pyramid/issues/1297 - Added support / testing for 'pypy3' under Tox and Travis. See https://github.com/Pylons/pyramid/pull/1469 - Automate code coverage metrics across py2 and py3 instead of just py2. See https://github.com/Pylons/pyramid/pull/1471 - Cache busting for static resources has been added and is available via a new argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. Core APIs are shipped for both cache busting via query strings and path segments and may be extended to fit into custom asset pipelines. See https://github.com/Pylons/pyramid/pull/1380 and https://github.com/Pylons/pyramid/pull/1583 - 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 - Allow an iterator to be returned from a renderer. Previously it was only possible to return bytes or unicode. See https://github.com/Pylons/pyramid/pull/1417 - ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 - Overall improvments for the ``proutes`` command. Added ``--format`` and ``--glob`` arguments to the command, introduced the ``method`` column for displaying available request methods, and improved the ``view`` output by showing the module instead of just ``__repr__``. See https://github.com/Pylons/pyramid/pull/1488 - Support keyword-only arguments and function annotations in views in Python 3. See https://github.com/Pylons/pyramid/pull/1556 - ``request.response`` will no longer be mutated when using the ``pyramid.renderers.render_to_response()`` API. It is now necessary to pass in a ``response=`` argument to ``render_to_response`` if you wish to supply the renderer with a custom response object for it to use. If you do not pass one then a response object will be created using the application's ``IResponseFactory``. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``). However, when invoking ``render_to_response`` it is not expected that the response object being returned would be the same one used later in the request. The response object returned from ``render_to_response`` is now explicitly different from ``request.response``. This does not change the API of a renderer. See https://github.com/Pylons/pyramid/pull/1563 - The ``append_slash`` argument of ```Configurator().add_notfound_view()`` will now accept anything that implements the ``IResponse`` interface and will use that as the response class instead of the default ``HTTPFound``. See https://github.com/Pylons/pyramid/pull/1610 Bug Fixes --------- - The JSONP renderer created JavaScript code in such a way that a callback variable could be used to arbitrarily inject javascript into the response object. https://github.com/Pylons/pyramid/pull/1627 - Work around an issue where ``pserve --reload`` would leave terminal echo disabled if it reloaded during a pdb session. See https://github.com/Pylons/pyramid/pull/1577, https://github.com/Pylons/pyramid/pull/1592 - ``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 - Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no valid request object. In this case it will not wrap the object in a callback and thus behave just like the ``pyramid.renderers.JSON`` renderer. See https://github.com/Pylons/pyramid/pull/1561 - Prevent "parameters to load are deprecated" ``DeprecationWarning`` from setuptools>=11.3. See https://github.com/Pylons/pyramid/pull/1541 - Avoiding sharing the ``IRenderer`` objects across threads when attached to a view using the `renderer=` argument. These renderers were instantiated at time of first render and shared between requests, causing potentially subtle effects like `pyramid.reload_templates = true` failing to work in `pyramid_mako`. See https://github.com/Pylons/pyramid/pull/1575 and https://github.com/Pylons/pyramid/issues/1268 - Avoiding timing attacks against CSRF tokens. See https://github.com/Pylons/pyramid/pull/1574 - ``request.finished_callbacks`` and ``request.response_callbacks`` now default to an iterable instead of ``None``. It may be checked for a length of 0. This was the behavior in 1.5. Deprecations ------------ - The ``pserve`` command's daemonization features have been deprecated. This includes the ``[start,stop,restart,status]`` subcommands as well as the ``--daemon``, ``--stop-server``, ``--pid-file``, and ``--status`` flags. Please use a real process manager in the future instead of relying on the ``pserve`` to daemonize itself. Many options exist including your Operating System's services such as Systemd or Upstart, as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/1641 - 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 ---- - Moved the documentation for ``accept`` on ``Configurator.add_view`` to no longer be part of the predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. See https://github.com/Pylons/pyramid/pull/1487 for this PR - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too early. - 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 - Add documentation of command line programs (``p*`` scripts). See https://github.com/Pylons/pyramid/pull/2191 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. - You can now run the scaffolding func tests via ``tox py2-scaffolds`` and ``tox py3-scaffolds``. pyramid-1.6/contributing.md0000644000076500000240000001040712621241570016605 0ustar michaelstaff00000000000000Contributing ============ All projects under the Pylons Projects, including this one, follow the guidelines established at [How to Contribute](http://www.pylonsproject.org/community/how-to-contribute) and [Coding Style and Standards](http://docs.pylonsproject.org/en/latest/community/codestyle.html). You can contribute to this project in several ways. * [File an Issue on GitHub](https://github.com/Pylons/pyramid/issues) * Fork this project and create a branch with your suggested change. When ready, submit a pull request for consideration. [GitHub Flow](https://guides.github.com/introduction/flow/index.html) describes the workflow process and why it's a good practice. When submitting a pull request, sign [CONTRIBUTORS.txt](https://github.com/Pylons/pyramid/blob/master/CONTRIBUTORS.txt) if you have not yet done so. * Join the IRC channel #pyramid on irc.freenode.net. Git Branches ------------ Git branches and their purpose and status at the time of this writing are listed below. * [master](https://github.com/Pylons/pyramid/) - The branch on which further development takes place. The default branch on GitHub. * [1.6-branch](https://github.com/Pylons/pyramid/tree/1.6-branch) - The branch to which further development on master should be backported. This is also a development branch. * [1.5-branch](https://github.com/Pylons/pyramid/tree/1.5-branch) - The branch classified as "stable" or "latest". Actively maintained. * [1.4-branch](https://github.com/Pylons/pyramid/tree/1.4-branch) - The oldest actively maintained and stable branch. Older branches are not actively maintained. In general, two stable branches and one or two development branches are actively maintained. Prerequisites ------------- Follow the instructions in HACKING.txt for your version or branch located in the [root of the Pyramid repository](https://github.com/Pylons/pyramid/) to install Pyramid and the tools needed to run its tests and build its documentation. Building documentation for a Pylons Project project --------------------------------------------------- *Note:* These instructions might not work for Windows users. Suggestions to improve the process for Windows users are welcome by submitting an issue or a pull request. Windows users may find it helpful to follow the guide [Installing Pyramid on a Windows System](http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/install.html#installing-pyramid-on-a-windows-system). 1. Fork the repo on GitHub by clicking the [Fork] button. 2. Clone your fork into a workspace on your local machine. git clone git@github.com:/pyramid.git 3. Add a git remote "upstream" for the cloned fork. git remote add upstream git@github.com:Pylons/pyramid.git 4. Set an environment variable as instructed in the [prerequisites](https://github.com/Pylons/pyramid/blob/master/HACKING.txt#L55-L58). # Mac and Linux $ export VENV=~/hack-on-pyramid/env # Windows set VENV=c:\hack-on-pyramid\env 5. Try to build the docs in your workspace. # Mac and Linux $ make clean html SPHINXBUILD=$VENV/bin/sphinx-build # Windows c:\> make clean html SPHINXBUILD=%VENV%\bin\sphinx-build If successful, then you can make changes to the documentation. You can load the built documentation in the `/_build/html/` directory in a web browser. 6. From this point forward, follow the typical [git workflow](https://help.github.com/articles/what-is-a-good-git-workflow/). Start by pulling from the upstream to get the most current changes. git pull upstream master 7. Make a branch, make changes to the docs, and rebuild them as indicated in step 5. To speed up the build process, you can omit `clean` from the above command to rebuild only those pages that depend on the files you have changed. 8. Once you are satisfied with your changes and the documentation builds successfully without errors or warnings, then git commit and push them to your "origin" repository on GitHub. git commit -m "commit message" git push -u origin --all # first time only, subsequent can be just 'git push'. 9. Create a [pull request](https://help.github.com/articles/using-pull-requests/). 10. Repeat the process starting from Step 6. pyramid-1.6/CONTRIBUTORS.txt0000644000076500000240000001577712642137120016267 0ustar michaelstaff00000000000000Pylons Project Contributor Agreement ==================================== The submitter agrees by adding his or her name within the section below named "Contributors" and submitting the resulting modified document to the canonical shared repository location for this software project (whether directly, as a user with "direct commit access", or via a "pull request"), he or she is signing a contract electronically. The submitter becomes a Contributor after a) he or she signs this document by adding their name beneath the "Contributors" section below, and b) the resulting document is accepted into the canonical version control repository. Treatment of Account --------------------- Contributor will not allow anyone other than the Contributor to use his or her username or source repository login to submit code to a Pylons Project source repository. Should Contributor become aware of any such use, Contributor will immediately notify Agendaless Consulting. Notification must be performed by sending an email to webmaster@agendaless.com. Until such notice is received, Contributor will be presumed to have taken all actions made through Contributor's account. If the Contributor has direct commit access, Agendaless Consulting will have complete control and discretion over capabilities assigned to Contributor's account, and may disable Contributor's account for any reason at any time. Legal Effect of Contribution ---------------------------- Upon submitting a change or new work to a Pylons Project source Repository (a "Contribution"), you agree to assign, and hereby do assign, a one-half interest of all right, title and interest in and to copyright and other intellectual property rights with respect to your new and original portions of the Contribution to Agendaless Consulting. You and Agendaless Consulting each agree that the other shall be free to exercise any and all exclusive rights in and to the Contribution, without accounting to one another, including without limitation, the right to license the Contribution to others under the Repoze Public License. This agreement shall run with title to the Contribution. Agendaless Consulting does not convey to you any right, title or interest in or to the Program or such portions of the Contribution that were taken from the Program. Your transmission of a submission to the Pylons Project source Repository and marks of identification concerning the Contribution itself constitute your intent to contribute and your assignment of the work in accordance with the provisions of this Agreement. License Terms ------------- Code committed to the Pylons Project source repository (Committed Code) must be governed by the Repoze Public License (http://repoze.org/LICENSE.txt, aka "the RPL") or another license acceptable to Agendaless Consulting. Until Agendaless Consulting declares in writing an acceptable license other than the RPL, only the RPL shall be used. A list of exceptions is detailed within the "Licensing Exceptions" section of this document, if one exists. Representations, Warranty, and Indemnification ---------------------------------------------- Contributor represents and warrants that the Committed Code does not violate the rights of any person or entity, and that the Contributor has legal authority to enter into this Agreement and legal authority over Contributed Code. Further, Contributor indemnifies Agendaless Consulting against violations. Cryptography ------------ Contributor understands that cryptographic code may be subject to government regulations with which Agendaless Consulting and/or entities using Committed Code must comply. Any code which contains any of the items listed below must not be checked-in until Agendaless Consulting staff has been notified and has approved such contribution in writing. - Cryptographic capabilities or features - Calls to cryptographic features - User interface elements which provide context relating to cryptography - Code which may, under casual inspection, appear to be cryptographic. Notices ------- Contributor confirms that any notices required will be included in any Committed Code. Licensing Exceptions ==================== Code committed within the ``docs/`` subdirectory of the Pyramid source control repository and "docstrings" which appear in the documentation generated by running "make" within this directory is licensed under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License (http://creativecommons.org/licenses/by-nc-sa/3.0/us/). List of Contributors ==================== The below-signed are contributors to a code repository that is part of the project named "Pyramid". Each below-signed contributor has read, understand and agrees to the terms above in the section within this document entitled "Pylons Project Contributor Agreement" as of the date beside his or her name. Contributors ------------ - Chris McDonough, 2010/11/08 - Tres Seaver, 2010/11/09 - Ben Bangert, 2010/11/09 - Blaise Laflamme, 2010/11/09 - Chris Rossi, 2010/11/10 - Casey Duncan, 2010/12/27 - Rob Miller, 2010/12/28 - Marius Gedminas, 2010/12/31 - Marcin Lulek, 2011/01/02 - John Shipman, 2011/01/15 - Wichert Akkerman, 2011/01/19 - Christopher Lambacher, 2011/02/12 - Malthe Borch, 2011/02/28 - Carlos de la Guardia, 2011/03/29 - Joel Bohman, 2011/04/16 - Juliusz Gonera, 2011/04/17 - Philip Jenvey, 2011/04/24 - Michael Merickel, 2011/5/25 - Christoph Zwerschke, 2011/06/07 - Atsushi Odagiri, 2011/07/02 - Shane Hathaway, 2011/07/22 - Manuel Hermann, 2011/07/11 - Richard Barrell, 2011/11/07 - Chris Shenton, 2011/11/07 - Ken Manheimer, 2011/11/07 - Reed O'Brien, 2011/11/07 - Klee Dienes, 2011/10/30 - Michael Ryabushin, 2011/12/14 - Mike Orr, 2012/02/14 - Paul M. Winkler, 2012/02/22 - Martijn Pieters, 2012/03/02 - Steve Piercy, 2012/03/27 - Wayne Witzel III, 2012/03/27 - Marin Rukavina, 2012/05/03 - Lorenzo M. Catucci, 2012/06/08 - Marc Abramowitz, 2012/06/13 - Brian Sutherland, 2012/06/16 - Jeff Cook, 2012/06/16 - Ian Wilson, 2012/06/17 - Roman Kozlovskyi, 2012/08/11 - Domen Kozar, 2012/09/11 - David Gay, 2012/09/16 - Robert Jackiewicz, 2012/11/12 - John Anderson, 2012/11/14 - Bert JW Regeer, 2013/02/01 - Georges Dubus, 2013/03/21 - Jason McKellar, 2013/03/28 - Luke Cyca, 2013/05/30 - Laurence Rowe, 2013/04/24 - Julian P. Glass, 2013/08/10 - Junaid Ali, 2013/08/10 - Chris Davies, 2013/08/11 - Jonathan Villemaire-Krajden, 2013/08/13 - Charlie Clark, 2013/08/15 - Tom Lazar, 2013/08/15 - Andreas Zeidler, 2013/08/15 - Matthew Wilkes, 2013/08/23 - Takahiro Fujiwara, 2013/08/28 - Doug Hellmann, 2013/09/06 - Karl O. Pinc, 2013/09/27 - Matthew Russell, 2013/10/14 - 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 - Ilja Everila, 2015/02/05 - Geoffrey T. Dairiki, 2015/02/06 - David Glick, 2015/02/12 - Donald Stufft, 2015/03/15 - Karen Dalton, 2015/06/01 - Igor Stroh, 2015/06/10 - Jesse Dhillon, 2015/10/07 - Amos Latteier, 2015/10/22 - Rami Chousein, 2015/10/28 pyramid-1.6/COPYRIGHT.txt0000644000076500000240000000041112234375161015663 0ustar michaelstaff00000000000000Copyright (c) 2008-2011 Agendaless Consulting and Contributors. (http://www.agendaless.com), All Rights Reserved Portions (c) Zope Foundation and contributors (http://www.zope.org/). Portions (c) Edgewall Software (http://edgewall.org) Portions (c) Ian Bicking. pyramid-1.6/coverage.sh0000755000076500000240000000006012524266531015706 0ustar michaelstaff00000000000000#!/bin/bash tox -epy2-cover,py3-cover,coverage pyramid-1.6/docs/0000755000076500000240000000000012642137501014503 5ustar michaelstaff00000000000000pyramid-1.6/docs/.gitignore0000644000076500000240000000001112234375161016466 0ustar michaelstaff00000000000000_build pyramid-1.6/docs/_static/0000755000076500000240000000000012642137501016131 5ustar michaelstaff00000000000000pyramid-1.6/docs/_static/latex-note.png0000644000076500000240000000740412234375161020727 0ustar michaelstaff00000000000000‰PNG  IHDR<<:üÙrbKGDùC» pHYs.#.#x¥?v vpAg<<™ IDAThÞí[[o\×uþÖ>×¹r†q4¼H2u+åK »©'œ‰° K΃‘<ô©ÿ ?¢?¡oi?Ôe6Š 5lÀ±“ÚMÑØ±)%”(Û%EʼÏðÌ9ûì½Wxöø EJ3$km0ÐÚ—õ­û^{øýß&:ê_~ùe˜Ì 0ÆìÏ@6„½ßÙß¿öÚkÿû¿òÊ+}?¯¬¬€™ÉuÝ*Ñ€€ €0·/ˆtl3ó3ßWJm7¾u_}õÕCóêà……ŒŒŒ ŽcO1â8ÎH†£•Jål†O9ŽsZÑ$¢q#»o2ó=cÌ}­õ|Ç¿i·Ûsq¯­­­mc6Ã0L777‚ÕÃiøù矇”•JÅó<¯R,Où¾ÿm!ÄŸú¾¾^¯5›ÍjµZ- ¿T*¾ï ¢m™RJE‘ìv»Éææfwyyyk}}ý¾”òwƘ—Rþ*Š¢ÏÓ4m·ÛíÔ÷}¼ûî»ß,àK—.A)×uÏó Žã̸®û—Õjõ¹‰‰‰éF£q¢T*Õ …‚†!\×…ŽãôüÔ’1Zkc ”BÇèv»z{{{ceeeiaaáÎÖÖÖJ©·µÖ³išv•RÚu]¼õÖ[ÿs€m0ŠãB0sˆžò<ï;ÇŽûV³Ù|ftttºÑh8ÕjA€ˆ ”‚RªÊÓ RD!DO®ëÂu]03’$ÁÖÖVVVôÚÚÚååå_¯®®~”¦éûÌü1u1Ãp¨à6°W*Ü»wårÙÙÞÞ®—J¥§FFF~T«Õ~866ÖšœœtFGG¤iŠN§Ó@ßwû³1D„4Mû„àyŽ;†cÇŽ9kkkgJ¥Òér¹üÜÆÆÆùÍÍÍÜÞÞþ¸T*­·ÛmÝjµÖ°3È k×®YfC¥Ô¹r¹üýãÇ¿<55õÂ… ZSSSn”¦)â8Fš¦ÐZ÷RS>EíE»ÇYóNÓ™éøñãN­V«Ñ37Œ1~’$]ÏóÚD¤.\¸€Ï>ûì‘XjÒW®\é™Z’$!=áyÞOÆÏž=;6>>^) ÂÓc.ΧÝÀúÈÆäÇæçç5.„@·Û5‹‹‹í¹¹¹û+++ÿœ¦é?0óoƒ ˆ­ëܸqcx _»v Æ$Iì_8qâÊôôô©ñññÐjUJ٧ѽ4gƒ“ֺϧó÷›oç23|ß§b±zž7ª”šêv»cÌ¢Rj-I%„ÀO<±¯¶÷õaf†ëºð<Ï‘RNAðãf³yùôéÓVë=FFÌ ¥ …*• ˆÝnëëëh·ÛÐZ?Áód¤µ†çy˜˜˜ Ì|yii)M’äïŠÅâ-fÖãg_ÀRJA€(Šjårù¹z½~éäÉ“­V BˆÀÚòÐFÓÉÉIœ9s£££ "t:,..b~~ÞVg6컦ôžç¡ÕjAk=!¥¼´¾¾þÛN§³\(V¥”Ùô‹/¾)%Ç)…aøL½^ùôéÓÜjµBÏó ¥„Rj ÍZ³œœÄùóçÑh4zy9T*(¥°¼¼Ü‹Øƒ¬iŒëº‚B'Žc£”ZPJÝO’$}üñÇqëÖ­G¾rå ˆ®ëºBˆgªÕêOšÍ榦¦êa %;öäÉ“˜œœ„ïû}àº.¢(Âââ"”R¯k¤çy‚ÀK’äx’$A’$÷ǹ'„0333¸yófߌì!Åq”Ëe·Z­NÇqü]Çq¾dæ¯ú0;æl™B<æûþ_?~üOFGG}"ê%ýaý×~ìüz½Ž .àÉ'ŸÄ™3gP¯×¡”B·ÛíEóƒî‘oÅq\ÜÜÜJ©_ÑWŽãàâÅ‹˜ýZÃA@Jéû¾?îûþt%ÇqúÀDòvn½^ǹsçpîÜ9Ôëu@»ÝF¹\ÆÍ›7±´´4Páñ°½¬•APò}:MÓñ$I~ÌJä¯Çq "ñ}ÿ±B¡Pð}¿§+ùƒ0aç·Z-LOO£V«õ˜«T*˜žžFEXZZ²M…C ×qø¾B¡PHÓô1)åÆqü•ÝÓÍO ¢ºâl†ÅÃj7˜ˆP©TP.—ûêe"B©TÂÈÈ„¶¨y˜–Ã0,v:³DT'¢ž÷g ªDtÒó¼ÐJì( !zÁi¯1¶õsÐÔ´0!Ãp@5Ÿ]v®w]7È«Ã0`ç?Lpùúø0€íZYÉPÙp&á"€†·þ{˜Td¤luÀYðó4ó–Õìy”R€"1HkfÀܬŽÂ¬ˆ ‹çy^ï ЧaìäeÏ2zX°ƒ>J ïšïpöÔpþ¤rØ`5,`;î([þóq¤p–4€ÔöžŽ‚ÕðQ™ô.<éÎׯ±ì>§Ìi­ÖZÅÆƒžqRÃYÀ5Ìe œmÒ°ª”J•Rã Ô¶°ed?mƒî(g§»À*€n~½ଽÒfæÅ4M¥TpT¦µÛöl…sØ}³ÎLÂ̋ڶùÐ8cl‹™¿È.¬ªŽã*pY ƒÄƒ|÷â0dŠišv™ù [Zk¸®Û8£5cÌœ”2RJÁó¼CmnA<êt”¶í$)edŒ™°–ÿÿ>“öZk$Ib¤”Ÿ2ó{ƘvEhµZ}.Ûg«7nܰiA2ógJ©¥”÷¤”&]™÷Íý>y!cÐíví]sI)ÑétÇqßøaöÐZCJi¤”÷”R2ógD$‰èËñ=3»“Y3Ƽ£”úy’$÷•Rz†ò‡­5>ÿüsܽ{Ýn··O’$øòË/1??$I†›ßC)¥3nŒyÀÚ^÷JÀ·‡³³³˜™™1ÆÑ3¯hÑI" ázÇÖd×ÖÖÇ1 …‚ @Ǹ{÷.>üðC|òÉ'ˆ¢¨¯æ¦ç¦i[Jù¶Öú§~mŒIˆ¯¿þúƒþ¾Ÿ_]¹rŶ}Žã¼ä8Îßø¾ƾµ†¬Ù‹EŒ¡Z­:VWWÑétzÁg˜ eß–H)ok­ÿVkýºbųï;}_ä:Ƙ÷™ùM×L¸®;t4B Š"ÌÍÍõLݾÏÊ®x†j)åJÈ¥Ô›Ìü¾bc¿îè#ÛE1JqÀÏ”R>€1ã®ëÒ°šÎÚ§ûî5(e~ËJ©E¥Ô?øÝÎx}èÜ}ëÆÙÙY\¼xÑš™°`Ñ0ó±l®€†ñçýÀá³F)µ¥”ú/­õ›~ àS"Š­ÕåÓÐBD¢W¯^µ&2óy"úŽâ{Žãü¹ã8cBˆ#éŽ<Œ2k3ZëûZë÷Œ1ï0óûDô;"Š™ù¡@©á½´E‘ò}•™ïø€cŒi2s‘™iP 4k9)­õ¢1æMcÌß3ó»DôEEÒ÷ýÀC¼µdf‹EPî3óZëýÞó-!ÄÓBˆi"rR{ïEY®ÕƘ;Ƙÿ`æ˜ù>ÆN; Åbq(ÿ?F®^½j7q‰hÀ_ÑsèQÏ‚yŽ”ûn˜Ù>¿ÃÌx›™gD4 ¬ÕC¶ôÒK/Ù¯>•œðmFDç²çÿU"*dc|ß-=ˆŸþþ?Ó_&†PGPæ%tEXtcreate-date2010-01-03T06:17:37-05:00ÎpA†%tEXtmodify-date2010-01-03T06:17:37-05:00‘Á7²tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚pyramid-1.6/docs/_static/latex-warning.png0000644000076500000240000000672612234375161021435 0ustar michaelstaff00000000000000‰PNG  IHDR<<:üÙrbKGDùC» pHYs.#.#x¥?v vpAg<<™ ÚIDAThÞí[ko\Çy~æzfÎu/t)J¤­Z®ãX¶iK•eYR\]¬:M hQ´ýý |ù`K$%Ãÿ ¿ @¿¶ý& E6 ÉNê¶v}k)”-Ñ\rCr÷œ33ý Å’Ü¥H.•Ø€^à€$öìÌûÌóÞÏ!ð@Èù. ¹ßœ;wÆ(¥¥´  8- ι…V«ÕbŒáÒ¥Kß=À/^¥”R4›M.¥Œ•Rû)¥Ç!‡!{çÜ çÜ/ʲ|¯Ýnÿw»ÝnEaëõ:^}õÕïàééiBàœ œsA«×ë?¬×ëÏeY6Aìœsív»9??ëÎ;ׯ?çy~Å97˳ÖZ¼ùæ›;®Ûéß}÷]äyÎ9ò<ß•$ÉŸíÚµëoÆÇÇ0::º»Z­Ê,ËEÑZaÖ¥”)Ër¥ÕjýFJùÛ²,qêÔ)\¹reGõã; ¸ÕjÁCʲ¬FQt$MÓ?;4::š0ÆÐn·±¼¼|wsΑ$ ”R !äp»Ý6EQ|SÅUçÜ"îúù·0PJ€Zë¿®Õj‡³,‹­µþ0àÜ]eY¢,KH)Q­Vã………íVëvžçw¬µÿA)5;®ÛN.öÖ[oá›o¾c,᜿†áKõz=eŒ‘v»¢(`ŒµÖZcç9Úí68ç¤^¯gZë—9çG…áÒÒÞ~ûío'`¨²,S”ÒišªV«Õ0 ‰1eYv™íç\÷³$IH¥R©Çqü<¥tR)²³quGÏÌÌÀÆáœ?Ä{%MÓgëõ:¥”¢,KXkáœë{ùaŒadd„¥izRú§”Ò„1†`ffæÛØ9k-Š¢ÐœóýqNÓtg×û­s„nަ”Â3X–%Œ1ˆã˜¤i:†á‹RÊ'ò<Ξ=Û×:¶#C§¥™™¯<%„Lh­ÿrttôøÈÈH&¥DQ°ÖŠ¢@£ÑÀÜܰ¼¼ k-8¿?c „²,ÉòòòŠ1æƒ X(ŠgΜÁåË—‡Òwè(Ýc–I”R§²,Û†a7@y³½qã>øà|öÙg(Š„T*<ýôÓ˜œœD† ”"ŽcT*•=óóó/EñÓ<Ïo3Æ–v‚塞ššÂ¹sçðÞ{ïçÜ£QýÅÈÈȱZ­–pÎQEììì,®]»†÷ß³³³h4X\\D£Ñ€sõzqƒRêYfEQð<Ïïäyþ)!d~rrû÷ïªÚ‡/^¼H!5)åóJ©cÕj5•Rv}ÒG᯾ú _|ñ!àœƒ1†<Ïqûömܺu ívÖZ”e‰ P¯×3¥ÔI!ÄABHzýúõ¡CöP€;¹”XkÓZŸHÓôÂ0”º©Æ´••äyèM5„Ef³Ùõ÷²,aª$I¾¯µ~ÉZ»·³×ïð… àœ#BˆTñ\EkµšbŒuÙí—Š\oêòiŠsŽZ­¦£(:Ä9?(¥L©©©ß=àŽHJé0 OÅq¼7 CîM¸¬ÿ}#Y{¿·8ŽyEk­OBö;çÄ0,o ðùóç}*J(¥? Ãðxš¦¡7ÏÞ¼»°þó~Å¥”dYÅq|€ò<MÁùóç7€}ÅcŒÑBˆg”RÏÇq<Ú[døZ¹—5Î98çèW*ú"Ä¿|s†!M’dBk}LJù”sN÷êrß¿óÎ;½ŒŒRJœeÙ¾4MAY™{!8ç}}™R !Ä@–cˆã8N’äcì,€ú¶èÝ*`ߨB!ÄãRÊ?I’dLkµ%äÚ‹Ò½ú1ìÙ_èÅ9?EѨ¢;cTd *<|àò†gRk8Žƒ.Ÿ…$Š¢QÎùYJéSƱ¸¸80/÷Ü À£Bˆ#J©q¥°ev{¯4MQ¯×»¾L)E’$ȲlÔ´ËKäJ©q!Ä1EQ4Ðr×E‘K—.y„W”R›eÙn¥ëÝd+âÙô ‚1I’ࡇÂÞ½{±{÷nläwƒÄZÛ-FÐ<Ïã¢(nø/J©9qâ®^½ºê;ë‚VgÒÈ8çœó#A<©”½ih»?¢(ÂÞ½{†!¬µ‚•JZëU³•ƒ,ËBh­EOäyþBY–ÿVÅçýÆu5Ÿ™™ÁÊÊŠOE)c쯢(ú»,ËFQD{{ÝaŸ÷ôFéNQ³-ñºøÔ¶´´d®---ý½1æ›Íf#IPJñú믯gXc ãœ3ÆNK)÷A@{'Ûa¢¨_«°/<¶ €Ø4‚'[­ÖËιŸÇq¼äœ+zïïúðÉ“'ý«œó3J©GQ4Þ;cFœs˜››ÃG}„?üŸ~ú)>ÿüs,,,€1­õ¶-ǃfŒÁ9'Œ1®,Ë›ÖÚϬÀéÓ§qùòå» ÏÌÌt[° öPJÏ(¥–R®*Ú·+ÖZ4 |òÉ'øøã1??ßí~Ò4ÅÊÊ (¥¨Õj;ª{‰1”RŸú&Úíökí5kíç¼ôÖÙ]½ssÊ{’sþŒ"[[dôæÏÍŠÊ·o߯—_~Ù}´âSS³ÙÄ7P©TDZgiKë{Ýü,[‘ !ž³Öþ±µöÿ¬µ_ûû×çJé)eRº®„ÆwÛí6ò<ïôò<Çòòr×m¶²ÏÚ¯bRÊZžçGü@ðÚH1 àYÆXÒ;PVÖöÃý¦–Þ”‡ÙÏ÷ç–6ð€?è½g-à €1ÁvN{”RT*¤iй¹¹n.÷¡µFµZç|Kó©~Ò\9€a_À.†`Î92l òâY­T*Ø·oŸÏ—]ÐJ)Œ¡^¯w{âØ³³†`{;´.àÎC°%_[k'Œ1r'^(ñÊK)111J¥‚ÅÅÅ.à0 ‘e‚ è6ÃJ'³äfü–1¶pG¾vÎýº,Ëï1Æjƒ¦ŒÛÎ9²,C𦫨ðEÇN½ÇÑ)ƒ›Î¹÷ÜZ¥C/„›ÖÚŸEqœ1V%weGA÷>6ú÷犢pyže­ý !äfoÑ´ 0î¾ÊûKcÌ¿æy^°‹s¾ã wdïšeYº<ÏcŒ¹j­ý%!d¡ó@§´¼|ù²/-€eΛ°£Î¹=MÆý?,HŸŽŠ¢pEQÜ,Ëò_œsÿàä>^¹reðÖ.QJj­-Š¢˜3Ƽ`Œ™`ŒBb܇7p‡ëœ[²ÖΕeyÃóïιð çÜŠw?º]G—BBH À÷ !/xÀ8€;üŽæ² à€s?ð+çÜ×ι6€uï\´Ï©©)È4€J‡ÝwýþÛd×wÿ•` ÀBç§4Œ¨øÅ‹WýýÚk¯ý¾ÝS¾‹:?2¤ü?c`¡“¶×%tEXtcreate-date2010-01-03T06:18:18-05:00‹mŸ%tEXtmodify-date2010-01-03T06:18:18-05:00Ô§«tEXtSoftwareAdobe ImageReadyqÉe<IEND®B`‚pyramid-1.6/docs/_static/pyramid_request_processing.graffle0000644000076500000240000071436412520062551025146 0ustar michaelstaff00000000000000 ActiveLayerIndex 0 ApplicationVersion com.omnigroup.OmniGrafflePro 139.18.0.187838 AutoAdjust BackgroundGraphic Bounds {{0, 0}, {576, 733}} Class SolidGraphic FontInfo Font Helvetica Size 12 ID 2 Style shadow Draws NO stroke Draws NO BaseZoom 0 CanvasOrigin {0, 0} ColumnAlign 1 ColumnSpacing 36 CreationDate 2014-11-18 08:33:33 +0000 Creator Steve Piercy DisplayScale 1 0/72 in = 1 0/72 in GraphDocumentVersion 8 GraphicsList Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169389 ID 169504 Layer 0 Points {344.41667175292969, 402.88506673894034} {375.5, 402.27232108797347} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169428 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169382 ID 169433 Layer 0 Points {155.00000254313238, 459.27667544230695} {238.5002713470962, 456.52468399152298} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169370 Position 0.28820157051086426 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169383 ID 169432 Layer 0 Points {155.00000254313238, 482.12574895537085} {238.52297468463752, 508.35839132916635} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169370 Position 0.5668826699256897 Class Group Graphics Bounds {{238.8333613077798, 284.99999999999994}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic ID 169425 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 412.15071036499205}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169426 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, 0.5} {-0.49999999999999911, 0.49999999999999289} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 303.65604172230951}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169427 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 393.55704269887212}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169428 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 374.90099016834085}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169429 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 341.36561209044055}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169430 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.75000762939453, 322.26348241170439}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169431 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 ID 169424 Layer 0 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169422 Info 4 ID 169423 Layer 0 Points {155.00000254313238, 470.25295298442387} {238.33861159880226, 482.4262543949045} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169370 Position 0.42701038718223572 Bounds {{238.83336130777977, 471.22620192028251}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169422 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169420 Info 4 ID 169421 Layer 0 Points {154.99998733539806, 128.68025330008533} {239.83340199788393, 128.59152244387357} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169386 Position 0.35945424437522888 Bounds {{239.83340199788395, 117.31920169649808}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169420 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class TableGroup Graphics Bounds {{102.1666056315114, 148.28868579864499}, {105.66669464111328, 33.08929443359375}} Class ShapedGraphic ID 169418 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.1666056315114, 181.37798023223874}, {105.66669464111328, 17.244049072265625}} Class ShapedGraphic ID 169419 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169418 169419 ID 169417 Layer 0 Class TableGroup Graphics Bounds {{102.16666158040482, 272}, {105.66666412353516, 33.08929443359375}} Class ShapedGraphic ID 169412 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.16666158040482, 305.08929443359375}, {105.66666412353516, 17.244049072265625}} Class ShapedGraphic ID 169413 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169412 169413 ID 169411 Layer 0 Class LineGraphic Head ID 169407 Info 7 ID 169410 Layer 0 Points {238.75000762939462, 430.80675844512831} {207.66666666666765, 385.656005859375} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169426 Info 6 Class LineGraphic Head ID 169407 Info 8 ID 169409 Layer 0 Points {239.33336141608385, 285.57837549845181} {207.66666666666777, 353.07514659563753} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169425 Info 6 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169381 ID 169408 Layer 0 Points {155.00000254313238, 386.66442959065108} {155.00000254313238, 422.21209462483216} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169407 Bounds {{102.16667048136482, 353.07514659563753}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169407 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {-0.49211360058019871, -0.49251945318722434} {-0.49211360058019871, 0.49470854679786669} {0.4984227008620481, 0.48463479169597612} {0.49842270086204898, -0.5} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169380 Info 4 ID 169399 Layer 0 Points {154.9999936421724, 258.44082431579938} {238.8333613077798, 258.45536063967575} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169372 Position 0.51973581314086914 Class Group Graphics Bounds {{383.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169393 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{383.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169394 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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)} VerticalPad 0 Bounds {{383.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169395 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{383.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169396 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{383.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169397 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{370.9999504089372, 42.910746256511771}, {132.66667175292969, 184.08924865722656}} Class ShapedGraphic ID 169398 Magnets {1, 0.5} {1, -0.5} {-1, 0.5} {-1, -0.5} {0.5, 1} {-0.5, 1} {0.5, -1} {-0.5, -1} Shape Rectangle Style fill Draws NO shadow Draws NO stroke CornerRadius 5 Text Align 0 Text {\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} VerticalPad 0 TextPlacement 0 ID 169391 Layer 0 Bounds {{233.5000012715667, 20.000000000000934}, {116, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font Helvetica Size 12 ID 169390 Layer 0 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\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 <%Canvas%>} VerticalPad 0 Wrap NO Bounds {{375.5, 391}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169389 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169418 Info 2 ID 169386 Layer 0 Points {155.00000170434049, 119.22767858295661} {154.99995295206804, 148.28868579864499} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169378 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169378 ID 169385 Layer 0 Points {155.00000254313238, 67.727678571434836} {155.00000254313238, 96.18303707668386} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169377 Info 1 Bounds {{102.16667048136482, 509.6179466247504}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169384 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 497.23589324949899}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169383 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 445.23589324949717}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169382 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.16667048136482, 422.21209462483216}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169381 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83336130777977, 247.18303989230026}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169380 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.16667048136482, 222.18303707668389}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169379 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.16667048136482, 96.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169378 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{102.16667048136482, 45.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169377 Layer 0 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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 } VerticalPad 0 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169379 ID 169373 Layer 0 Points {154.99995295206804, 198.62202930450437} {155.00000254313238, 222.18303707668389} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169419 Info 1 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169412 Info 2 ID 169372 Layer 0 Points {154.9999936421724, 245.22767856643924} {154.9999936421724, 272} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169379 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169407 ID 169371 Layer 0 Points {154.9999936421724, 322.33334350585938} {155.00000254313238, 353.07514659563753} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169413 Info 1 Class LineGraphic ControlPoints {0, 6.9839935302734375} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169384 Info 2 ID 169370 Layer 0 Points {155.00000254313238, 444.75673611958314} {155.00000254313238, 509.6179466247504} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169381 Class LineGraphic Head ID 169444 Info 6 ID 169503 Layer 1 Points {272.4166717529298, 537.32234122436705} {420.4999504089364, 515.08928491955714} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169494 Info 5 Class LineGraphic Head ID 169444 ID 169502 Layer 1 Points {272.50004831949906, 391.51558277923863} {420.4999504089364, 472.78869058972316} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169493 Info 5 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169450 ID 169501 Layer 1 Points {83.000002543132396, 592.81693102013151} {239, 583.78422005970799} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169438 Position 0.28820157051086426 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169451 ID 169500 Layer 1 Points {83.000002543132396, 629.80996162681686} {239, 640.78422005970981} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169438 Position 0.5668826699256897 Class Group Graphics Bounds {{166.8333613077798, 391.51558277923863}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic ID 169493 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 518.66629314423074}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169494 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, 0.5} {-0.49999999999999911, 0.49999999999999289} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 410.17162450154819}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169495 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 500.07262547811081}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169496 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 481.41657294757954}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169497 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 447.88119486967923}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169498 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 428.77906519094307}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169499 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 ID 169492 Layer 1 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169490 Info 4 ID 169491 Layer 1 Points {83.166643778483959, 611.77452873049333} {238.8333613077798, 611.77452873049333} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Bounds {{238.83336130777977, 600.50220798311784}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169490 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169488 Info 4 ID 169489 Layer 1 Points {82.999986314263907, 140.3328574622312} {239.83340199788393, 141.59152244387357} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169454 Position 0.35945424437522888 Bounds {{239.83340199788395, 130.31920169649808}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169488 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class TableGroup Graphics Bounds {{30.166605631511416, 166.28868579864499}, {105.66668701171875, 33.08929443359375}} Class ShapedGraphic ID 169486 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166605631511416, 199.37798023223874}, {105.66668701171875, 17.244049072265625}} Class ShapedGraphic ID 169487 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169486 169487 ID 169485 Layer 1 Class TableGroup Graphics Bounds {{420.5000406901047, 338.15028762817326}, {105.66668701171875, 33.08929443359375}} Class ShapedGraphic ID 169483 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{420.5000406901047, 371.23958206176701}, {105.66668701171875, 17.244049072265625}} Class ShapedGraphic ID 169484 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169483 169484 ID 169482 Layer 1 Class TableGroup Graphics Bounds {{30.166661580404835, 335}, {105.66667175292969, 33.08929443359375}} Class ShapedGraphic ID 169480 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166661580404835, 368.08929443359375}, {105.66667175292969, 17.244049072265625}} Class ShapedGraphic ID 169481 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169480 169481 ID 169479 Layer 1 Class LineGraphic Head ID 169475 Info 7 ID 169478 Layer 1 Points {166.75000762939462, 537.32234122436694} {135.66666666666765, 485} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169494 Info 6 Class LineGraphic Head ID 169475 Info 8 ID 169477 Layer 1 Points {167.33336141608385, 392.09395827769049} {135.66666666666777, 452.41914073626253} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169493 Info 6 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169449 ID 169476 Layer 1 Points {83.000002543132396, 485.50842372576449} {83.000002543132396, 548.10604731241608} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169475 Bounds {{30.166670481364818, 452.41914073626253}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169475 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {-0.49211360058019871, -0.49251945318722434} {-0.49211360058019871, 0.49470854679786669} {0.4984227008620481, 0.48463479169597612} {0.49842270086204898, -0.5} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {51.333333333333314, 0} {-0.66666666666662877, 58.666666666666686} {0.66673293066804717, -58.666850540458825} {-16.306719354194399, 0.26652623861849634} Head ID 169443 Info 4 ID 169474 Layer 1 Points {369.66666666666669, 541} {404.00000000000023, 362} {420.36749776329049, 302.42112495959606} Style stroke Bezier Color b 0.75663 g 0.756618 r 0.75664 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Class LineGraphic ControlPoints {69.833333333332462, -0.72767857143483639} {-0.66690523835279691, -51.044605218028948} {0.66666666666674246, 51.044637362162291} {-24.333271383961971, -0.13425428344572765} Head ID 169443 Info 4 ID 169473 Layer 1 Points {310.66666666666754, 118.72767857143484} {399.33333333333417, 216.62202930450439} {420.37955338188368, 301.45369961823752} Style stroke Bezier Color b 0.75663 g 0.756618 r 0.75664 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Class LineGraphic ControlPoints {-3.9999491373696401, 78.910715080442856} {92.666683130060392, 0.22547126950667007} Head ID 169490 Info 3 ID 169472 Layer 1 Points {473.33328247070392, 515.08928491955714} {344.50002543131501, 611.77452873049333} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169444 Info 1 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-32.166667938232536, 10.244050343831077} ID 169471 Layer 1 Points {344.96346869509995, 346.26317428240412} {389.8333346048999, 328.08928298950207} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169456 Info 3 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-28.500001271565793, 8.3333333333333144} ID 169470 Layer 1 Points {344.98861594084059, 323.71068461220347} {391.1666679382335, 313.6666666666664} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169455 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-40, 1.5446373167492311} ID 169469 Layer 1 Points {344.9995533451783, 301.1612218744512} {394.50000127156665, 299.00000000000045} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169442 Info 3 Class LineGraphic ControlPoints {8.5833282470703125, -10.244596987647753} {0, 0} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169457 Info 4 ID 169468 Layer 1 Points {272.41667175292969, 509.40064951817902} {285.5, 503.81699882234847} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Tail ID 169496 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169448 Info 4 ID 169467 Layer 1 Points {82.999997456869679, 300.27229985501288} {238.34892458824362, 260.57913893040109} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169440 Position 0.51973581314086914 Class Group Graphics Bounds {{419.66662216186666, 214.61452811107179}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169460 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169461 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169462 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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)} VerticalPad 0 Bounds {{419.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169463 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169464 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169465 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{406.9999504089372, 42.910746256511771}, {132.66667175292969, 207.81692504882812}} Class ShapedGraphic ID 169466 Magnets {1, 0.5} {1, -0.5} {-1, 0.5} {-1, -0.5} {0.5, 1} {-0.5, 1} {0.5, -1} {-0.5, -1} Shape Rectangle Style fill Draws NO shadow Draws NO stroke CornerRadius 5 Text Align 0 Text {\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} VerticalPad 0 TextPlacement 0 ID 169459 Layer 1 Bounds {{233.5000012715667, 20.000000000000934}, {116, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font Helvetica Size 12 ID 169458 Layer 1 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\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 <%Canvas%>} VerticalPad 0 Wrap NO Bounds {{285.5, 492.544677734375}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169457 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83337529500417, 335.17855853126167}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169456 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83337529500417, 312.54463200342093}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169455 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169486 Info 2 ID 169454 Layer 1 Points {83.000001850648417, 128.2276785555359} {82.999949137370791, 166.28868579864502} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169446 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169446 ID 169453 Layer 1 Points {83.000002543132396, 67.727678571434836} {83.000002543132396, 105.18303707668386} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169445 Info 1 Bounds {{30.166670481364818, 671.51189931233432}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169452 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 629.51189931233432}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169451 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 572.5118993123325}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169450 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 548.10604731241608}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169449 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83336130777977, 249.18303989230026}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169448 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 240.18303707668389}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169447 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 105.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169446 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 45.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169445 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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 } VerticalPad 0 Bounds {{420.49995040893634, 472.78869058972316}, {105.66666412353516, 42.300594329833984}} Class ShapedGraphic ID 169444 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {-0.49999999999999956, -0.5} {-0.49999999999999956, 0.5} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{420.49995040893634, 290.66666666666691}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169443 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83336512247806, 289.91071033477789}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169442 Layer 1 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169447 ID 169441 Layer 1 Points {82.999949137370791, 216.62202930450437} {83.000002543132396, 240.18303707668389} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169487 Info 1 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169480 Info 2 ID 169440 Layer 1 Points {82.999997456869679, 263.22767855635425} {82.999997456869679, 335} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169447 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169475 ID 169439 Layer 1 Points {82.999997456869679, 385.33334350585938} {83.000002543132396, 452.41914073626253} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169481 Info 1 Class LineGraphic ControlPoints {0, 6.9839935302734375} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169452 Info 2 ID 169438 Layer 1 Points {83.000002543132396, 571.15068879140836} {83.000002543132396, 671.51189931233421} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169449 Class LineGraphic ControlPoints {0, 7.055999755859375} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169483 Info 2 ID 169437 Layer 1 Points {473.33328247070392, 313.2113088426139} {473.33338419596407, 338.15028762817326} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169443 Class LineGraphic ControlPoints {0, 6.9840011596679688} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169444 ID 169436 Layer 1 Points {473.33338359264764, 388.98363112285512} {473.33328247070392, 472.78869058972316} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169484 Class LineGraphic Head ID 169358 Info 6 ID 169359 Layer 2 Points {272.4166717529298, 537.32234122436705} {420.4999504089364, 515.08928491955714} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169206 Info 5 Class LineGraphic Head ID 169358 ID 169360 Layer 2 Points {272.50004831949906, 391.51558277923863} {420.4999504089364, 472.78869058972316} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169205 Info 5 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169044 ID 169130 Layer 2 Points {83.000002543132396, 592.81693102013151} {239, 583.78422005970799} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169128 Position 0.28820157051086426 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169045 ID 169129 Layer 2 Points {83.000002543132396, 629.80996162681686} {239, 640.78422005970981} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169128 Position 0.5668826699256897 Class Group Graphics Bounds {{166.8333613077798, 391.51558277923863}, {105.66668701171875, 18.656048080136394}} Class ShapedGraphic ID 169205 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 518.66629314423074}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169206 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, 0.5} {-0.49999999999999911, 0.49999999999999289} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 410.17162450154819}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169207 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {0.50000000000000089, -0.49999999999999645} {-0.49526813868737474, -0.4689979626999552} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 500.07262547811081}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169208 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 481.41657294757954}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169209 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 447.88119486967923}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169210 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{166.75000762939453, 428.77906519094307}, {105.66666412353516, 18.656048080136394}} Class ShapedGraphic ID 169211 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.637876 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 ID 169204 Layer 2 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169085 Info 4 ID 169086 Layer 2 Points {83.166643778483959, 611.77452873049333} {238.8333613077798, 611.77452873049333} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Bounds {{238.83336130777977, 600.50220798311784}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169085 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169083 Info 4 ID 169084 Layer 2 Points {82.999986314263907, 140.3328574622312} {239.83340199788393, 141.59152244387357} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169048 Position 0.35945424437522888 Bounds {{239.83340199788395, 130.31920169649808}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169083 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class TableGroup Graphics Bounds {{30.166605631511416, 166.28868579864499}, {105.66668701171875, 33.08929443359375}} Class ShapedGraphic ID 169081 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166605631511416, 199.37798023223874}, {105.66668701171875, 17.244049072265625}} Class ShapedGraphic ID 169082 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169081 169082 ID 169080 Layer 2 Class TableGroup Graphics Bounds {{420.5000406901047, 338.15028762817326}, {105.66668701171875, 33.08929443359375}} Class ShapedGraphic ID 169355 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{420.5000406901047, 371.23958206176701}, {105.66668701171875, 17.244049072265625}} Class ShapedGraphic ID 169356 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169355 169356 ID 169354 Layer 2 Class TableGroup Graphics Bounds {{30.166661580404835, 335}, {105.66667175292969, 33.08929443359375}} Class ShapedGraphic ID 169075 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166661580404835, 368.08929443359375}, {105.66667175292969, 17.244049072265625}} Class ShapedGraphic ID 169076 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 GridH 169075 169076 ID 169074 Layer 2 Class LineGraphic Head ID 169070 Info 7 ID 169073 Layer 2 Points {166.75000762939462, 537.32234122436694} {135.66666666666765, 485} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169206 Info 6 Class LineGraphic Head ID 169070 Info 8 ID 169072 Layer 2 Points {167.33336141608385, 392.09395827769049} {135.66666666666777, 452.41914073626253} Style stroke Color b 0.755269 g 0.755239 r 0.75529 HeadArrow 0 Legacy Pattern 11 TailArrow 0 Tail ID 169205 Info 6 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169043 ID 169071 Layer 2 Points {83.000002543132396, 485.50842372576449} {83.000002543132396, 548.10604731241608} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169070 Bounds {{30.166670481364818, 452.41914073626253}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169070 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {-0.49211360058019871, -0.49251945318722434} {-0.49211360058019871, 0.49470854679786669} {0.4984227008620481, 0.48463479169597612} {0.49842270086204898, -0.5} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {51.333333333333314, 0} {-0.66666666666662877, 58.666666666666686} {0.66673293066804717, -58.666850540458825} {-16.306719354194399, 0.26652623861849634} Head ID 169344 Info 4 ID 169345 Layer 2 Points {369.66666666666669, 541} {404.00000000000023, 362} {420.36749776329049, 302.42112495959606} Style stroke Bezier Color b 0.75663 g 0.756618 r 0.75664 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Class LineGraphic ControlPoints {69.833333333332462, -0.72767857143483639} {-0.66690523835279691, -51.044605218028948} {0.66666666666674246, 51.044637362162291} {-24.333271383961971, -0.13425428344572765} Head ID 169344 Info 4 ID 169346 Layer 2 Points {310.66666666666754, 118.72767857143484} {399.33333333333417, 216.62202930450439} {420.37955338188368, 301.45369961823752} Style stroke Bezier Color b 0.75663 g 0.756618 r 0.75664 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Class LineGraphic ControlPoints {-3.9999491373696401, 78.910715080442856} {92.666683130060392, 0.22547126950667007} Head ID 169085 Info 3 ID 169361 Layer 2 Points {473.33328247070392, 515.08928491955714} {344.50002543131501, 611.77452873049333} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169358 Info 1 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-32.166667938232536, 10.244050343831077} ID 169341 Layer 2 Points {344.96346869509995, 346.26317428240412} {389.8333346048999, 328.08928298950207} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169340 Info 3 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-28.500001271565793, 8.3333333333333144} ID 169337 Layer 2 Points {344.98861594084059, 323.71068461220347} {391.1666679382335, 313.6666666666664} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169336 Class LineGraphic ControlPoints {31.999987284342428, -14.081351280212308} {-40, 1.5446373167492311} ID 169333 Layer 2 Points {344.9995533451783, 301.1612218744512} {394.50000127156665, 299.00000000000045} Style stroke Bezier HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169332 Info 3 Class LineGraphic ControlPoints {8.5833282470703125, -10.244596987647753} {0, 0} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169051 Info 4 ID 169062 Layer 2 Points {272.41667175292969, 509.40064951817902} {285.5, 503.81699882234847} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy LineType 1 Pattern 1 TailArrow 0 Tail ID 169208 Class LineGraphic FontInfo Color w 0 Font Helvetica Size 12 Head ID 169042 Info 4 ID 169061 Layer 2 Points {82.999997456869679, 300.27229985501288} {238.34892458824362, 260.57913893040109} Style stroke Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow 0 Legacy Pattern 2 TailArrow 0 Tail ID 169034 Position 0.51973581314086914 Class Group Graphics Bounds {{419.66662216186666, 214.61452811107179}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169054 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 130.51770718892479}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169055 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 91.940789540609359}, {105.66666412353516, 33.089282989501953}} Class ShapedGraphic ID 169056 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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)} VerticalPad 0 Bounds {{419.66662216186666, 158.54998334248924}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169057 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 186.58225949605369}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169058 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{419.66662216186666, 63.908513387045019}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169059 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{406.9999504089372, 42.910746256511771}, {132.66667175292969, 207.81692504882812}} Class ShapedGraphic ID 169060 Magnets {1, 0.5} {1, -0.5} {-1, 0.5} {-1, -0.5} {0.5, 1} {-0.5, 1} {0.5, -1} {-0.5, -1} Shape Rectangle Style fill Draws NO shadow Draws NO stroke CornerRadius 5 Text Align 0 Text {\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} VerticalPad 0 TextPlacement 0 ID 169053 Layer 2 Bounds {{233.5000012715667, 20.000000000000934}, {116, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font Helvetica Size 12 ID 169052 Layer 2 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\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 <%Canvas%>} VerticalPad 0 Wrap NO Bounds {{285.5, 492.544677734375}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169051 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83337529500417, 335.17855853126167}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169340 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83337529500417, 312.54463200342093}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169336 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169081 Info 2 ID 169048 Layer 2 Points {83.000001850648417, 128.2276785555359} {82.999949137370791, 166.28868579864502} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169040 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169040 ID 169047 Layer 2 Points {83.000002543132396, 67.727678571434836} {83.000002543132396, 105.18303707668386} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169039 Info 1 Bounds {{30.166670481364818, 671.51189931233432}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169046 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 629.51189931233432}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169045 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{239, 572.5118993123325}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169044 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.327428 g 0.81823 r 0.995566 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 548.10604731241608}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169043 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83336130777977, 249.18303989230026}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169042 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 240.18303707668389}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169041 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 105.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169040 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{30.166670481364818, 45.18303707668386}, {105.66666412353516, 22.544641494750977}} Class ShapedGraphic ID 169039 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999208 g 0.811343 r 0.644457 shadow Draws NO ShadowVector {2, 2} Text Text {\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 } VerticalPad 0 Bounds {{420.49995040893634, 472.78869058972316}, {105.66666412353516, 42.300594329833984}} Class ShapedGraphic ID 169358 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} {-0.49999999999999956, -0.5} {-0.49999999999999956, 0.5} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{420.49995040893634, 290.66666666666691}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169344 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Bounds {{238.83336512247806, 289.91071033477789}, {105.66666412353516, 22.544642175946908}} Class ShapedGraphic ID 169332 Layer 2 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style shadow Draws NO ShadowVector {2, 2} Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169041 ID 169035 Layer 2 Points {82.999949137370791, 216.62202930450437} {83.000002543132396, 240.18303707668389} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169082 Info 1 Class LineGraphic ControlPoints {0, 7.05596923828125} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169075 Info 2 ID 169034 Layer 2 Points {82.999997456869679, 263.22767855635425} {82.999997456869679, 335} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169041 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -8.9999999999999432} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169070 ID 169033 Layer 2 Points {82.999997456869679, 385.33334350585938} {83.000002543132396, 452.41914073626253} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169076 Info 1 Class LineGraphic ControlPoints {0, 6.9839935302734375} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169046 Info 2 ID 169128 Layer 2 Points {83.000002543132396, 571.15068879140836} {83.000002543132396, 671.51189931233421} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169043 Class LineGraphic ControlPoints {0, 7.055999755859375} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169355 Info 2 ID 169357 Layer 2 Points {473.33328247070392, 313.2113088426139} {473.33338419596407, 338.15028762817326} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169344 Class LineGraphic ControlPoints {0, 6.9840011596679688} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169358 ID 169362 Layer 2 Points {473.33338359264764, 388.98363112285512} {473.33328247070392, 472.78869058972316} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169356 GridInfo GuidesLocked NO GuidesVisible YES HPages 1 ImageCounter 3 KeepToScale Layers Lock NO Name no exceptions Print YES View YES Lock NO Name exceptions only Print YES View NO Lock NO Name all Print YES View NO LayoutInfo Animate NO circoMinDist 18 circoSeparation 0.0 layoutEngine dot neatoSeparation 0.0 twopiSeparation 0.0 LinksVisible NO MagnetsVisible NO MasterSheets ModificationDate 2014-11-23 07:19:11 +0000 Modifier Steve Piercy NotesVisible NO Orientation 2 OriginVisible NO PageBreaks YES PrintInfo NSBottomMargin float 41 NSHorizonalPagination coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG NSLeftMargin float 18 NSPaperSize size {612, 792} NSPrintReverseOrientation int 0 NSRightMargin float 18 NSTopMargin float 18 PrintOnePage ReadOnly NO RowAlign 1 RowSpacing 36 SheetTitle Request Processing SmartAlignmentGuidesActive YES SmartDistanceGuidesActive YES UniqueID 1 UseEntirePage VPages 1 WindowInfo CurrentSheet 0 ExpandedCanvases name Request Processing Frame {{35, 93}, {1394, 1325}} ListView OutlineWidth 178 RightSidebar ShowRuler Sidebar SidebarWidth 163 VisibleRegion {{-231, -226}, {1037, 1186}} Zoom 1 ZoomValues Request Processing 1 2 pyramid-1.6/docs/_static/pyramid_request_processing.png0000644000076500000240000035774612520062551024333 0ustar michaelstaff00000000000000‰PNG  IHDRP*Iž pHYs%%IR$ðÕiTXtXML:com.adobe.xmp 5 2 1 m ž@IDATxìœTÕÇÝÝÝRÒÝ]‹ ˆ…€b` ŠˆÊFTDB: iéé’îî®ýßß]îãÍ0»;3»³ìÎþŸÝW7¿ïíðΜsω¨D($@$@$@$@$@$@¡ˆj       Ш@ñA      7 Pr‹‘ (>$@$@$@$@$@$à&*Pn‚b1      Åg€H€H€H€H€H€Ü$@ÊMP,F$@$@$@$@$@T ø €›¨@¹ ŠÅH€H€H€H€H€H€ Ÿ     p“(7A± Pâ3@$@$@$@$@$@n å&(#     *P|H€H€H€H€H€HÀMT ÜÅb$@$@$@$@$@$@ŠÏ ¸I€ ”› XŒH€H€H€H€H€¨@ñ      7 Pr‹‘ (>$@$@$@$@$@$à&*Pn‚b1     ˆM$@$ð,ìÙ³GŽ?l×1bÄĉK¦L™$kÖ¬Á–ã…ð#Ò=ÁýH:µäÎ[’$I~Fó–öïß/«W¯ÖÀ¸eË–Ñœ§O$@‘Ÿ@Œ@%‘˜! €¿hݺµ :Ô­i=÷Üs2räH©T©’[å£{¡ 6È£G4°K™2¥[Hܽ'%J”~ýúIõêÕÝj—…‚'€çºU«Vº@̘1åáÇÁæ  HA€.|‘â6p$@!À·ôÕªU“ñãLJTŒ×¨\¹²”+WNÿ,]º4ܹlÞ¼YjÔ¨!cÇŽ ÷¶Ù Dvtá‹ìwˆã#h@ vìØOY3._¾,»wï–›7ojøf¾mÛ¶R¯^=I“&M4 òì§X»vmkW®\(Nv îÊd̘Ñ*ÇÏÀ=õ…^ЕàÂG! ˆü¨@Eþ{Ä’€ßÀZ§ <5O¼¬·hÑB&Ož¬¯á%~É’%òúë¯?U'îÝ»'<„ º¼îê$\Ýîß¿/ñâÅsuÙgç¼ë¥K—^שR¥òÙ¸LÃx™w¾'Pj?þøcë~\»vMþùçiÖ¬™©ærã7nÈùóç%[¶l76OäÖ­[Ýo´ç(yòäžt#ްdzxöìY­ðÇ×eP@튪ËB.NÞ½{×£gÖÓò.ºä)  Ç<ûŸŠØH€H  ÄŠK¾øâ ‡÷îÝëp|çÎùꫯ¤páÂ:èDÒ¤Iõþ{ï½'‡r(k?X³fÔªUK¿HC!iܸ±L:U¿ðV­ZUÌÏÅ‹u5(<åË—·~æÌ™coNï7jÔȺ>zôè§®{3ÖÿþûOš4i¢×1aœä.tÓ§Owè£{÷îZ¹„BhdРAúÜâÅ‹Í)¯·)R¤víÚ9ÔGà #UªT±æó°X}øá‡ÚBõË/¿˜bz … Êq®\¹÷,gΜúþ•*UJ~þùg‡²Î&LÐó‡%2Q¢DúÂe.žö¹Ûë­]»VêÔ©#éÒ¥ÌaÍ iž'ìM_Pú‡ &yóæ• HæÌ™%~üøzûå—_Ê©S§LQ½ý믿,f+Vt¸6sæLë9|ÿý÷ÏÏ÷߯çŽ@økëŠ+ꙃ۷oKûöíõ߃á„g Á[þ÷¿ÿYýöíÛ×Tá–H€HÀ"A! ˆ&ðÑG!€þQÖ€`»ß·oŸUå•’c•ŵüùó;\7mb«^–ÕK¼UÞìŒ7.PY\ÖëÙ³§Ãyõ««©—Q‡ó£F2ÍY[õ²l•Q/¨Öyìx3Võbì8Í<{õêeõ£«sÝlÕK½U.¸û=Q(—Å”Bˆk¦ÝO>ùÄ*gg >ê¥Ý*§^ü­r˜W† ¬k¦-ûV)£W¯^µê`÷@YC¬× Aƒ@¥Ä8ÔûöÛo•2l=¥ä*k‘COÙ£²²´Ûæ—={ö@óL¡Î¯¿þj•WÖ7œ²dÈ!Öµ¢E‹¾úê«Ö±î‡ZëfÕÃŽ²|–.]Úey¥´Ö¯_ߺ¦”3‡º<  ™ÜA($@$áì/ë!)PŸ}ö™õ¢‡ÅsçÎYcUQù¬kæåTEˆs8§¾¥<|ø°UçÈ‘#O)%èß¼ìã%Öþrj^vê@y:Veñ T!Ü­±( [`§Nñ²«,(ÖyŒõÀz~:tTV–@ûŠ)¢Ï)‹™Å ¸û= N‚"fçóÃ?XÍÙ(ç1J¹ý*Ë‘ÕúÁ=S뀞Rª>ýôS«mì@Y´÷þòåËçp× `ÕS–7‡ëj½] ²Úâ¹°·eïËöèPYÙ¬6•Kh`óæÍ•U0°lÙ²Öyô‰óFÜU À u•»k ž%g÷Ù.*ºCŸ¨‹:ögÃÌŸ ”÷I€H tT BgÄ$@> `Y'N rçrøÁ7äÊ­Ëá%P­¿±FK”yÄv̘1–åaË–-x5×íÖ û‹%^¦U$9m9PkDô¾yQ5uOž<©û ‹åÍXa93cÀÖ(IŒZ3¨ÜÞ¬ëxq·‹}îüñ‡ýRˆûö{‚>ÿý÷_ëG­u „‚«ž}\óçÏ·Ú´+P(“'OžÀnݺþþûïÊõR—ûú믭ú° Íš5˪ư<™öñ\(7:}]¹ü9ÌÖ(c7ªpíV=åΧÏÃU°`Aë|™2e,ë Nh ˜€+Ä[ö°~™±ãy¶ )s a­‚¸«@¡®r+ Äs ÁǦM°Rî‹úšr] ijm®/^<_@ðHs [F]_ã/  ÷PrK‘ „3ç—uû ó>¾5ÿñÇܬÞzë-ë%°@Oî7Þ°®Ãe ‚—Vµ.Å:6œEåB²®cá¡@y3V(&vp ›7o^àõë×õÏœ9xôèQý£‚;8L#¼({ÿ®öÕšKiÅì ” á`-4´[Õpœ¼ííåwÊ”)< $ÛŠ0ÜÜð£ÖQi–Gû¸íëìªÖVé&½eo·4Á«ú€@94÷ [o¨­[·ê¶Ì/»Å ó4J¬‚öyåÕÔSk S2d¸% ÷0 Ÿú_†B$¹ ¨—MÁb{D{3è°Àߨõ!æPoÕˤu|ðàAE ‹ðÕ‹¬u¾aÆ־٩Y³¦8<0×¼Ýz3ÖbÅŠé f¼Jyü °®)…J‡PkÀ¼V˜ê)EEçRÊ­ËvÜÀ9ܼRþD)HVyWÑç]YDY¿t94÷ÐÂÞ*TÈê­RR?v±sÇyDsœ6mš½ˆÈH~$ Vîuš/@xÊAFÖ­[§ÛÂ\•û©þI›6­xæTÂSÁœï5¢Ú, vV–€vA` ]Ù´i“ý4÷I€H€Ü$@ÊMP,F$à;c¾páB«(L.\3fˆ ø Ï#|ùÛo¿­)œ°GØÃ‹²óË²ÕØã¼$+‹„ÃiWù‹œ_R*xyàÍXñ’«1È| x7‚—d¼øâ§G¢ÖîèÈlP¬Â[”›œÕ¤rmµ†FTQÁ*äÅ_œ N 9‹®¹âoÎÊ(f‹ëY²d(¡‰sˆj’E 7ì•»¢VüÚÝ.ÊmN”£þAÔÁß~ûMìlíeƒÛO™2¥(ËœÃåà”×cÇŽY唥µoßÁ½¤ xG ôÿ¼k—µH€HÀmxFjgÁKúöíÛ-k„=7^(•›®‚úÐ^HaQpþæ¡´E¹W9Ÿry¬ŒüOG^$WâÍXÑNÓ¦MµåbÒ¤Iò矊ZëãÈcøé§Ÿ´"‚Ùá)PŽÖ¯_ïu“˜³³¤OŸÞár+¹ûyS÷ψŠÎgvCÜÚÇ€g !½CµVȺì {„WÑðD¹je ¡îŸ1(‚w$Ñžä¢ IYµýxÇnùCÈrW¥ŽB$@$à*PÞqc- "w5c€K’®Âb·$¼„BrçÎ-ýúõ uDÎVÔW ÿ꙾Nªµ®H»Ï7)$HµË‰'Ä®@Ù,oÆŠ¶{JÐV(ÀA 8,_¾\Tà™8q¢¾Ž1À5-¼(ûÜÂk9˜ 4 ‘-D­c’×^{Í¡y¸Y·=\0îg°Ü‚…»‚¥Å$\Æ}VÁ¬º¨‡¤¶mÚ´Ñù˜L;Ám½aöͼJ–,©sŒÁB´sçNY´h‘¨ 'z}ž>}Z–-[¦©àÆ–óvVx.a‘Êš5«Õ$ØíÚµË:æ €g\;¯{ÖK“ €Ï8K¯¢é¾Tà«O¬Â‹¢]ºvíª­7U«VÕ–\C[öu#HØj·@)>|¸½k$`5‚D¼v iÝ”7cÅúXðƒõFdÉ’é54p/³¯÷1®göñ˜}($‘I°ÆÌȈ#žºoHü«‚b˜"ZÁÖìØÅù>!,éâ |¡DÁ}Íîê§ò*Ù›ÙPªW¯n=' ,Ð×½ak¨¹_ØÂjˆgkµÐ<»)¤{æ0H/T˜s‡ZX×…/ èÉͱCA ¸E€(·0± À³"à¼îÃ(PmÛ¶¼€CÂOåÊ•u‰9rhw7\0òÕW_™]Á:¬¥‚@邵@…LGDR™;w®çò„ò* ¶.ƒ}”E=(7xa7/߸æ,ÞŒ.P” XË£"¥éùA‰ƒå J£uÎìê-¬e† XÍО õíPîY|÷Ýw‚µHÆbþ˜Öä¬\¹RT´=kXP¶jÕª¥kÔ¨!˜'Ü!ß|óV€°Ö îv…¼!pûSaÓEEòÓÇ}úôÑ,ëÖ­+*:ž¨¨{¢ò|ékPNaí„xÃkÖðcy(ð(P°LÁ:f·JbÞ¾’ *ègÏ(\a­ÃXð,K™¯úg»$@$à÷Ô:…H€"œ€=Œ¹² Û?BA«bëGE9³Êªg—‰AíåÕú¨@e…±ê(e"çìeìûΉYMs4 Ü®òKÙë©hgÊÝÌjW)jVŸØñt¬Èé£^„­öì}Ù÷‘JÐpè«qãÆOÕ:t¨CWö{¢¬%®Š„xÎ~\)Á–UÖ¢@´oŸ‡ó>Â+73‡6À_­c ±žZ»äZIq•²b´9}út«/oÙ+…%Ôyaž•oÄݔ˗/Ëÿý'óæÍ“_ýUΞ=«ÿþ,~ø¡OGDʧxÙ8 €/ lÛ¶M*V¬(7nÜJ•*i Tîܹ}Ù%Û&ˆdnÞ¼)ݺu“è‘A‰úä“O|6JŸ)Põë×סÏFΆI š¹sçF³Ysº$@$<¬o*^¼¸;vLš5k&cÆŒ‘8qâ_WH€üš>Þyçm‰Z¶l™T¨PÁ'óõ™#F Ÿ ˜’@t&À%‹Ñùîsî$@ÎÚ¶m+?ýô“~IZºt©Ä×¹ˆ>Þºu«lܸQï-ZTJ•*岜?Ÿ}ºÜ¿_š4i¢òäëq°} ÈEAcZ·n­5kÖ,Ÿ Ž ”O°²Q   _˜3gŽn …H€ìŒÕéŸþ±Ÿ·}&Ò 7”lˆH€H€H ¢™2ÿš|‚•’ ø’Ö7@2fÌîÝ>|Xð 6rÌ9zô¨,X°@f̘!ÿý·dÊ”I_‚ÂT«V-­¬˜²»wï–}ûöɹsçûiÒ¤qP ° !—±~ËÈŽ;tÁï¿ÿ^Úµk§O#Ï͸qãôþµk×dñâÅrýúuSEÖ¯_¯•%|Ë3fS§?þØJ&ŒþÁµZµjV=H”(‘ž"ÒøBèÂç ªl“H€H€HÀ§°Pâ‹$¹ˆîg”§òåËkKT•*UtPtL0œèÕ«—¥|¸. —:¸Î,XP+B={ö”o¾ùæ)põ3 ÕäÉ“¥nݺº Üñï‚\6°jÙ ”²´iÓêÓ ñ믿ê}X»DÉCoß¾­Ï½ùæ›Ö«çŸ^àH!;Z ÂÎ- ø ;wZ3Éš5«Œ5ÊúÉ’%‹u A,`¥ÂZ)HåÊ•µò„}äÂ|ë­·°û”˜ö¡pA¡1í#,{¼xñty Ã^k¤Œò„ó… ².ý‚pîF^y峫×H}úé§Ö1wH€ÂF€¨°ñcm   ?"`‚S`JÈ„WrõêUÁº(#yòä1»z å °žÊÖiÞ±37®_¹$ÿ®^(³?'¹ ±Ö©#ÿÉŽ ˤX…Ú’6“cäWWÌ"±ãÄ•òµ_Ò—÷lY#çO“²5IÜxñ]Uñësž°öká4¹yƒûèÏ5œFÉfH€H â˜÷ _~." 'B•ãÊTþüù­PãÁÍÁ,uÄ$ .,pt–‹/ š#)0Â’g̘ѹˆ×Ç'OžÔV® è¼^7ÄŠ$E øò3"Z¹ð>vPúÖTê7û$TjÏ¿käç®ïKçŸg¸¥@ø•$LœÌR þÿ£¬úûw¿ê´ÄM“>Š>zÞÛÖÞ÷š$@$@$à;°AiÂ+’„5RÈ…—5„<‡Â+‰ÂÊ• 9n•*U\] ó9X±¼µd…¹s6@~N Z)PIS¨ªÞ<…Júùm}öÓ#ëg8  ߀‚…HwãÆÓ,XPòåË'7nܰÖ!@D›6m|;¶N$¡¢••>KNé0pR„Ž®‘ut½óœ7 D/#FŒøñã뀈¬·wï^ ÜòÏX¥¬ Ü!ˆÒ¢ŒuùüY:k‚ä+ZVrä+"—Í•mk—è}¬3J“!‹\<{RVÌ*‡÷n• YsKù:M$[ž‚Ö ºrñœ¬Yð§d{®,ù$C÷ÙGdÓòyrìÀ.Éž·°¯XǪã¼_kô5N"R¨tU)R®†s±`ï«(;+çM•;7ÉÝ;·Õ8*I‰Êõ$YÊԺιSÇôõÌ9óI™êOÞ»M¶¬Rë·²å‘rµYí_8sB–Ï™,ù‹•—%*èóð¿>´[EZ½HŽîß!¹Ôz¯%*J®ÅÕ:­8VÝõKgˉC{¥É{_È©£dÁÔR¸lu5žººLhcµr±ãŠõjÅþÂéãòbËv²ûÍ®~`^±î+’.sö§Zë–K¼‰ô¸ò*%Ô¸ãÆO îSm]>´y Ð‘};tGÿÛ)™rä•ÂeªY¼ì‚ÿü)Ã5—‰’¨gåy©ýò»’8Y {1q·œC% øäq6l˜ôíÛW¯9:sæŒ^ËK”=‘­ßMœ"hL Ê(PçÕ‹÷Øþ¥z£·dß¶uròð>´aÑ´Q2aPù¬ßxú¿åÒ¹“úüƒû÷dÖØAÒ{â2­dáŸ;yT•i­×@jÇúeÒ³u#¹uãªÄ‹ŸPî«zq ¢ÎËO=÷ïÝ•A_¾¥\L(©LÑGJWo(è/4¢Ö·Ý«òߎz½ü¥þñ«¤L›Qº›­×e%LœTÆßY2©@vj¾Rnæýö‹R2r:(PPÁå›ást÷÷îÞ‘.-k*oµ$O•N’¦L#+ÕZ¬G(E5DI’é²Ëþš¨×i¥Í˜Múµ] ”ÔJ-!uű†4_W¬M­”¡e3V,Ù«½ÄQÁ5*ÿqŒã¯q?JßßVJú,9¬yüÐémÅ:(ŸEÜx dòÏݤb½Wµò•:}fK ihlÖØdŒb„¾ÀzÙ_“d’škýfK«¯”Xj<WÈ7ïÔ‘û÷îh% ÷{éÌq2gâÏÒ}ÔÉœ3(¹¢»åt£üE$@$-$K¦ÖA—/-æÊI’@t'å˜ã…¨Ám—1Ëk…êöÍkòÝÇ$o‘22húf™ºùª¼Öº«Ü¼~Eü>2Ø{|ãÚéñQCBÁ"¦n¹¦ë¼ÑZþ™5þ©zS‡~§•'X¼&¬9+¿o¹*ÿØ ,^Û”âvê©òÎ'Àâà®-JÙ› S6]–É/I1‹åöÍÒ¹EU¥Ä]“ÄI“++[99~p\½ô$w=ÈÙ‡Tt¿ãz¿`…‹7¾¶áxÉŒqZyz齎2~õi2g‡žSÙšµenÍÂé(æ ƒ»´’&­:J¿É«¥rý¦úš;cuhÄ̓»wni…·mïÑ2më ÅðšÔ{ýC­ø.ù„ùôQ´òT)à5ùuñ!™¶í¦ôš°Lvo^¥á#úž9wéj›WÌ—‘½Ûk¦ãW’q+Ohö•^—¹“†ÈÔ_zXÍ íÖZ/±è€ ›¿WF-=,ÿSŠÓùSGµe º[Δç–H€H€H€HÀD9*²–@Éž·²°¤Õî`¸‰“¥”ÏLÔV„ ñ­vêe8¦Vn‚»]°,ܾy]š·í®­:°DÄOPÞù²¿|¨B­/WÊS5e=K ¯ÁjR¤\Må–×Q÷“˜Ó Í?Ñ×OÙ¯·ö_ek¾(-;ô‘üÅËkWBOÆjoÇÝý·>ï­ß7uñ8Êõ¡ñ»ô>Ü !`=}d?emË!mzÖV)°*TºŠ¼ßå']ÆÕ/çy Ìè¾Am¿×yºwAÑaû¼ÿI”4…À*—GX§ ´&R ,,wFà&زC_I“1(ˆ»åL}nI€H€H€H€ü‹@”S `eÂK·‘Té2ëݬ¹ XJN$IžR•‹¢eÖHµ[è­ýW…ºŽ.|woÑ­ ʵÏ9¯SÉ*õô{}ç}(: j­,_öÌ bƤ@¹”á<ÖA^û¨‹¶6!?®€°¾™ò8×ðÍ6Òsì"A¼;·oi¥ë»þÞ—]ZnœçïÉXu£þš/» àDÌX±åÌñCú4ÖeA±Í_¼‚Vþìeaý‹3ÈåÎ~ûÎó¸vù¢^×–5wA¥„åt`~ûÖ ) Ú¿pæ¸rÝ/±T$¥œù‹Êåó§¥[«½® ë¸ °ÎaÄÝrº0‘ ø(³ÊÇ»À2Áz$OåŒ `?ab­l9×Å» ,$¸¤ºÎåíu±o,?ƒ¾ ²¼8_Ç1’ÏBrä+¬Öêd²'(4IS¤‘ÜÏ—ÐŒjûº¥º|É*õõ¿`!Y·d–^/µ}Ý?êL ¾–6Sv½uõ+» ªaOÆj¯çî>”;»àB)‚%ræxk¬sX “§vWËy'[Û¤i)Ç övo\ âÞyÈ ûk« ¾±{sõëÆ LÂgÖŽ¹[ÎÞ÷I€H€H€H€üƒ@”S b)KEx ^Üï*+ ¬AX{d—;·nÚEùÃécDús%(µHÁI ••òvÇþ‚  ®$™rI4«d`l;7.—Beªj—{’ÊuUZZ~ÑW[U`CÔ¸Ž¯—·ÜúL?®¶žŽÕU! ΂dê˜{O¹: "^½tÎeÄ>ç²ÈAä¾—Þ ² 9—ÁqÆìyôé´ÊM¯û¨ù:šãºÅ³dëšÅ²eå|™øCù¯ß”Õ:à„»å\õÅs$@$@$@$@Q›@øi#QCFeá8ú˜¶ô`ýŒ]àFfc ²êد`ÍÎ5íú•$¹£eÅ^ õñ2[Eø«Xïû%]aÑíkoàˆ}ë•5éèþÐô#]Ç„Lß¼âoJ½~³ÖV[7®^ÖÊS–\¤?^ö•[š‘ °ëæ(ä­§c ¹5ϯ¦SîvÓ*´º³ÀšôðÁ}çÓ.Í=;{â°io*Sø€ èqU¹é%Vk¡._8«C¿cm]ªt™T„>Dkl-PŒÛ7)¥"ÿ­—cÿíRÖ¯tn•ƒ‘B$@$@$@$à¢Ü¨ð¼&ßÖÙA –Ìk?¥-9PpŽìÛ®-ö‹ˆæöèÑCû©§ö‹W Ê-…FÎ2ùçîjÝM=õ‚¾Óº„@±bǑ߇õRç­({¹ –P®dÉåÏ_ûépÛv÷½kW.êúÈ_dWžàÖg"Ü!UhâéXCkÏÓë°ªÁeqÅÜ)‚uLFàâ7éÇoÌa¨[än‚õéÚå ²÷ßµå‘W¬S³Ê:ÈXÁ²ˆ{€H‹v‰Ÿ0‘X‚s¿în9{Ü'   ðOLþ3'·g‚u-s'ý¢B«,âêEQî`Múí§o•¥(žC;¸Þ¼]½Fæ«Õ¤Y›ÿé(q[V.Ðã’¦HíPÞù.y%*Èæódp—÷¥jÃfjÝOLAÄ»Ù*º& @‚‘„‰“è$»Xç„È&ÖÁoÝâ™jÝW ×ÔA „Ôé³hkÉĺªüT T…Ý:´ùÙÇ`EAЄL9ž^_dÚñt¬¦^xm5ðíŽýäǯޖ¯•Ó1 øÁ‡ñÎ÷&¤~?è:X:¼ZNåßzMGܑ̢C |ྠ—JBãgÍó¼,þsŒJœK'S~¨rgáþ®úûì7K®ü:‡;åB¯‘ D]ÑZB”¾^þ‘ÞŸ6ÑIw‘xRªê ÒL…6ïña‡;[çÕVr÷ÎmÕçsðùúZ¼‰A¦ é®"ÇÝp(ï|ðåSeÄwmµkÜó °2ÁU¬é'ݬF¸E Tá2Õph Üø @ÁJeH…¬ã )Ò§í+Ê’ÒSÿ º]ùÚ/Iס³T²Ü¦²qÙÁ'‡$žŽ5¤¶¼¹Vó¥–*_"TxxOµëcÎüÅT"àåÒþ¥’OEB ®D8ì1f¡N€ü}Ç'ÑáæØE1€«$ŠiGž~@‡fÚ*eŸãÖ“!ì¹qt·êRH€H€H€H€ü‹@ õÍ~è>]^ÌÙDÇ›½Ï'Í{1¢à«À5 –„ÑÎU°¸¤É%øÂê ¢åíÛ¶^GeCd<(bžÈ¥s§aÑÑ/Bg‡ÖŸ'm£,Öí Ä9nís…Kkëši¹Ž°V 9´Ü_Õ1ܼ~U…¨O¨”¨8:4û+E ìv4Ùêº ‚O Ïî1òAå+V^+MÎ ·Öá=[u9<Ãé•5 ÷È<Ϧ¼»åLù°nä bâ£?×°õI€H  ˜Ïe~.F8zvHQ‚€/?#¨@E‰G z ÊIß¶¯êðòŸö ²Ô«ü)}Ú¼,­:ÿ ßjkNûý– ”ßßbNHÀC¾|9òp(,N$ øò3"Z»ðEÂ{Í!)p§»{ç–¬ýcº¶é%+Ö/–7ÛÖ—ûîá´Ä‰Wš7ù@z~ñ“>毰ÈTÒ÷³öš¿__|FÐ…ÏNšû$@$@$@$@áN u禲cïù©ûÙóÏeÙýÏ%™òËb¹yû†4y¿ª\¿qM’'M!µ*7PûWe¥Rfì²lÍ|}þå€ZyÂ5wÚ´·1zÊOÊr•^¶Ì?©û_0é_¥H¥a(¥é¾T.SSެ»+Ù3çVV±4zŸÊ“ ÷ *P†·$@$@$@$@áN®w[v®“¤I@se?•J×ÏZ}+7o]—¿MÕý¾¬Üó s—LÓ[ókÖÂ)z÷Õ-õÖ“6MÓe‘‘ý§+«W}êù¼E¥FÅúò@)Oì3Ÿ%P P  xK`í–åºê j]ÓÕëW~Š?_F_[µa‰ÞV/_OR¥H+ –ÏRŠÍ}îö[²pÅl)R ”^Ä“ž´©Q¿Ê—¬&ñ•{ ]reË«žäR;î‡L€A$BæÃ«$@$@$@$@a pèØ~]»í·AÖ%WM]¾vIŸÆÚ¦FušÊ¨)?ÊêMK¥JÙÚ²hå¹}ç¦ë zÒ¦é/E²Tf×Ú¢?H`à#ëwH 4T B#Äë$@$@$@$@^ˆ#Èá©KÛþ’9C6—í¤VV'#¯¼ð–V à¦jÖ‚)* D<­X™2ž¶‰z±bÆ2Õ¹%0 &|¬L$@$@$@$œÙž“ekçë¨zÎáÉNnâ«Rx=g/œÖ u=z$Ÿ+*™Òg sÓ¾hÏB›Ãúåú<Ìަ d*éû¿Y;Zó÷ë‹Ï*PvÒÑpŸ T4¼éœ2 ø_¾<¦*P†·$à=R ¸Êûç€5I€H€H€H€H€¢*PÑì†sº$@$@$@$@$@Þ å=;Ö$    ˆf¨@E³Îé’ xO€ ”÷ìX“H€H€H€H€H š Ín8§K$@$@$@$@$à=*PÞ³cM     hF€ T4»áœ. €÷¨@yÏ.JÔ¼yóf˜Çm„yl€H€H€H€H€"*P‘à&ør‡’™3gzÝê¢ €(? *$Æ óJ‰‚ò„ºhƒB$@$@$@$@$@*Z<7–:x¤DAyBÔ¥ ˆ¨„0ü›À¹sç¤B… òèÑ#8p 4jÔÈšpΜ9õ¾ÝMÊÓçŸ.1cƔիWKÚ´i­òÜ!  È@ FŒz¾|1}D†ùr $à/|ù7kgdþ~}Ñ]øì¤ýt PÅŠ#(HÁ‰QžPu¨<GŠçI€H€ü@@@€¿O‘ó#%P¿~ýíÏWÅöUÃl7rxùå—eùòå–…ÑÙ-Q8¶+O8F DWsçήSç¼I€B @*8þt©V­Z’$I¹~ýºƒeæè¬<¡,êPH€H€H€H€H€ž  ß~½7n\iذ¡5GãÎgNÀµÏî#в¨C!    xB€ Ô~¿çì’gW˜ìûá\Öïáp‚$@$@$@$@$à*Pn@ò—"EŠ‘\¹r…:”AY €#*PŽ<üþÈË’;eü'H$@$@$@$@.PrÅŸO!1.ò;™Øøö¹â®1y® ÷I€H€H€H€Hà *POXD‹=äuªT©’CÀ3q¬ƒÂ5æ~2D¸%    GT yD‹£\ôBº-àp’$@$@$@$@$ÊêÂu^òC÷îÝ“R¥JéœPöé!÷ÓÆ¾Ü…û$@$@‘’€qE÷åkLýúõeÞ¼y‘rþ DEQ ª}ùA TT|úÂ8fçœP¦9æ~2$¸%  ¡ò怙€¿|!;œ¹°¹(B®z“&Mr-Ý÷pð€H€H€4Ùûè¬ÃGÂJ AÞam"ÒÔ§*ÒÜŠˆˆsN(æ~ŠXþìH€H€H€H j 5ï[¸ŒÚnq²ï‡Kãl„H€H€H€H€ü(?¼©îNÉä„bî'w‰± @t'À5PÑø 09¡€€¹Ÿ¢ñƒÀ©“ ¸M€ ”Û¨ü³ ]÷üó¾rV$@$@$@$@¾!à3й|sûU¸ïA=zÞM³½p&‘¹ÂyèlŽH€H€H€ü†€ÏéšäU~CŠ!H@À— ##Áô8 p›€yÏðåç¢éƒaÌݾ-,HÁ0aÌ}ù7kïÜüýú¢?ŸY Ìø¡cHpKÞ0:Þ·Àš$@$@$@$@áA€QøÂƒ"Û     ˆ¨@E‹ÛÌI’ „*PáA‘m D T ¢Åmæ$I€H€H€H jسe¬˜;Eîݽµ'ÂÑGyT ¢ü-äH€H€H€HÀ3«ü)ËçLö¬Ò3.ý×ø¥ÿgMåæµ+Ïx$ì>ºðy¾è˜ó'   ÈF`Ü€NróúU©òBÓÈ64އ"=Z "ý-âI€H€H€H€H€" Z "Ëà8H€H€H€H ÷ïÝ“•ó¦Ê›äîÛR°d%)Q¹ž$K™Z׸ï®Ì™ø³v@&þÐEÜ¿§¯c¿îkX Ô¬±?ȘþåჺýeM’I(õ›},­¾þQbÅŠ¥ë-ûk¢¬úûwI›1›ôkÿº:(©3d‘RWM-;7,“˜ªìÈ^í%N¼øº=ÔùkÜÒ÷·•’>KÝBtiYSölY-ÉS¥“¤)ÓÈJÕ$G¾¢Ò{â2I”$™.ëî¯É?ÿO Pþ»j¡,9N)™iåú•‹zñŸ£åÛsÕ¹4º¹æ6~úú=A™¸ñ¨1çT˼ß~‘25^”/˜ª¹£!(µßw|SÖ,˜¦æ[’¦H­ú_ ïSÓOºÉŸ~k ß]Æ;7®oÞ©£ærG+²æE¹û¨’9g^ݦ»å¬p'ÌÉ÷ju@IDATèÂf„l€H€H€H€|GîÚ"Ÿõ› S6]–É/I1‹åöÍÒ¹EU¹uãšîüÍϾ“,¹ Ȇ¥éhu8¹à÷‘Zy*Y¥¾Ô{ý)Z¾¦ÌØyW2dÍ­^òÓèýºþ¤ëo^1_Fön/ùŠ–“ñ«Nɸ•'t•^—¹“†ÈÔ_zèrö_ƒ»´’&­:J¿É«¥rý'ë©îÞ¹%u‘¶½GË´­7ä÷-×Tÿ*%ï¤RÆ[M,™1N+O/½×QƯ>-Cæì©›¯* Oc9¼w«¬Y8Ý*ëÉÎ9eÚ´|® ús“L\{V&­;/5›¼£•PŒËY\Ícæ˜AZyªÑ¸¥Ó¹;õ¶Jƒf²~É,ùcxo«™™c¾×ÊSù:/Ëoë/h~}[¥-VS†t—3Ç鲞0Ú­µV–G,: Ãæï•QKËÿ”âtþÔQ­D™ÎÝ-gÊsvT ÂÎ- €OÀõnß¶uR²J€T{±¹~¡†ªhùÒô“o•u]»ö¡sX¡>ë?AbÅŽ#Ã{´‘ƒÊ-nTŸÏµ¢Ô¦×¨PÇ7ºo]æ½Îƒ$EšôzÖŸÏU›‰’¦Ð–%¸ÛÙ®l-;ô‘üÅË[î„æú[Ÿ÷–êÞÔ‡qâÆ•Æïµ7=#°ª)WS¹vÔsÃùø Ê Í?ÑENÙoŠz¼ý ë`Éý| ].o­»ý¢šœj£”Eê’C{Îó€r:mDÅ4¾|Ü}¸åö/~ißg¬$N–R)P}”uèžR`¯ËŸ¿ö•$ÉSI»Þc´Å ÷nÞþL¹>Ò®„èÐ]ư?¸G»]Â2g¤xÅÚŠw_I“1«>ån9SŸÛð!@*|8²   w;6,×mVPk‡n¨ðÝöŸ¼EÊèkÛÖ.±ú…;ß뭻ʵËçå‹×Ê+ëš|ÚóWI‘úÉK¸UضsíòE9v`—dÍ]P»ªÙû¹}ë†(^A.œ9.';*4Õ^lakÅqk´ì8¸·k ®5|³ô»H)y©äÎí[ZiÀú+cÝÁZ&oŠ™] `BÑ{ôè¡^Ïd¿æ<ãw+Åèªä+VN)QqíE•‚[¹M–PnwÕ\ªõI{´"›31I(±CÙ€7Zk«UõF-Ô=qŸ1úÀÚ´ËçOK·V:äü•‹çtÛ°øaíÄÝrº0…® 7”lˆH€H€H€—€±À ú2È’ãªugkÊ+|¥ÖÞŒ”ó§I©ª/ðÀÞÖÉÇ–(QMK9(°—»qÕÑr“ý¹BöËûPŠì«L̘±t  s”uÊ늶¯ûGR˜ÒfÊnŠxµM”$ùS14”9g>ÝÞ…3'Ä( 8á<Æ€<_ªŠÞ:ÿÊ’+¿vÄú´›×ƒòRåÈWع˜šoLµn-©>@¹aBÜeÜyÈ ù¹ëû²Uר½y¥®‹ua꾬-tfm˜»åtü.¨@… ƨ×^:wJÊÕzé©oV¢Þl8b  ðO1Ô 8äíŽýuÀW³L–*­ÃéM+þÖÊNêèqGþ“ŒÙó8”q>À‹>$O¡RòÒ»AÖ ç28­{(K¡É N-eùìIªßÒÒò‹¾Úê’5w}®ãëå-·¾ÐÚq¾k“+¹së¦>F¼pGL° 粈ÈI®,{F© ÍZæ)ã´ÊM¯û¨ùrñìIA$@°@DAþØ ¢ ö›²Zõp·œóxì=*P6v0)ÃTŽÅ“Yr}Ca»ìW»ÓFô•Í+æ©E•”åø ‘_M”“!  (L SöçôKsì8q¥b½Wf‚à{¶¬ÑÑëÌ…«—.ÈÏ*°C¼‰äå÷;ɤ»êèp}'¯²"虲ö-úœ=qXÊ×i¢-'öë°ž\U.d‰ÕZ¨ð’W/kå /úCPnkF0¯°È•‹gUèò OY¡z’ññ|ƒë#c¶ …J ‚sØŠÒ¾mëõ© Ys©ˆõþŽõËìÅô>¾°ž®BÅ×yí})¨B³CÜa|ùÂYÚ={ÞB’*]&±µþؾI)~½ûo—Và>´r®¬cz0ü宲aÛûïZù©ó;úÛÛi¿Ü-T¦ªÊMð†Rž\Cã—“æ¤H€H€H Š(^©Ž1,Î2ùçîj}L=õ"½Óº4ä›ÊC‹v=ÕZ¨.Ú…A(ÁY°È‚,ÀútíòÁû].Ÿ?#šUÖìJ޽Œ7û×ThqòÙÛ…[Ÿ‰ÔšU'¤~—Ìëpù”²Ämøg¶ŽT˜XåÄ Io)Eš *úáfÅ3hí‘)>°:•®Ö@Œ€r‚œO‡÷nÓ¡äM9l'þÐU6.›£®gQ'Üg «îíÔ¡ŽÊ[ü„‰´²„¶&ÞÝr(O ?OTýðk“-Efña*‡H$@$@Ñ–¢ï•¨ ½Fwy_ª6l¦­CˆÎ7{Â`¡‚ZãÔHóùgÖDY»hºv‡k ‚3@Aîãú¹‘JU­¯]äpkx LLúé[á뵮ëåTΩ×td½,Ê•îäá}Z ¸«<À0<A%R§Ï¢­)P4JWo Öí„6?û8ì7¬,\‘)G…ÌÝþá>8þûÎrëúUàëÁp¬Bâ‰; mJèO_¿+_5¯*ÍÛõP¡ßsi%ilÿ/UT¾¸: "ƃ/£ßTvh¦sZ½úÑ×úÜâ?ÇÈ®M+ß'ÜÝeœ#_ÉšçyAè·xÅ:òPåÆÚ²rÊ¿õ‡NJŒ{Órîrc9÷PzÌ ‰â6ü3G!ÙÜÛ7ÕrCK ˨?j#ø†a‹JÎón¹ZAZ¸†‰È~¿XyºÒ”w7Û4Ê»Sß-5A •®ªþh *ó÷oúÃæz˜‡±¸04A„›s'J­—ßÑäÞ´‰o=Ÿ®p{,¦þ¸ï¨H=Hà‡<Šˆ3‹TÒ:0y®pi]Ñ‚>í9ÂbhÙÕMA|H¬˜;E[‘QîET?éiwËÙëpŸH€H€"+$lñ][” t!U·.$i…õï Ã{|ªÏ·ùn¤å‚—*]Fy÷«ïµ‡ÍÀ/ZÈÓ7éÿ÷áRvxßvAŽ"üŸ  ?=Æ,”A_¾¥ÜþžD׃‹]•˜·dǨzaå…5AM‘>m_Q––žúQúÊ×~I'î×¾©¶Þ`˜=1°;ý"‘m³¶ÝU^«Ït»¨“ QRùH…2ÏW´¬;Mèw$(-¿~×Nú´yòn•2m&•àw¹"Umð†VΆ|û¡V¤‚:ˆ¡½}>üæg«?w#aqÇï'Ë¥”!ô¹ ކ°^ ¡åÍš*wËYƒàN˜ ÄP/›ÞLJ ¡{DZÌÞç“æCèÙ»KšUщÜ`ÎÆ·0‹™»K>ª—_¿¨ÿ2o—ÕðÐÿ}¬£Å —ÀÈ%­óÈ®ýk¯vòÍð9ú[\pÎ6À ˆ0ãœÑÛ“²û·o”Ï_)- Z´Qßl¬”C{¶ê<7¯]F3Êçù+yëó^z?¸_ÝZÕ·Ö@!Jާmn]³DeMgöŽ'žÎhŽÐU6—¿'Õ þ£9 Z$ÈÃBÖ;7éÜ “7™í½Æ9»:¢Ùس«cÈ;ÑãÆ:)>ÓeήàBaƒ7rMxRNŽB¿ä ú{òÑŸk"Á¡’ @óžáËÏEÓGdy—¹tî´Êí´EÿŸˆ×îBî™;¬ÿÆ›¸ñâ[ÅðÅ&r5á¾ ͧ¾Å ½¯ëzþÛ±Q[Sðe+ÜÔŒà=ù•’;Ê0×]m?¬›OnªïÖœQï'÷õz%ƒÈ¥B¼‡æºçª=äy—çp̦¬B¤a”çòp?D”=¬OË”#o°ãv—ñCµ¾ê°zÏýÀó˜^Y£pïͳiúw·œ)ÿ,¶ý.cùâ3‚ ”í ‚jàÍåÃo†èoupéË7*k‹ ²X'K™F—nPP)»õþèeǬ0¼àÿ»z‘ÎÓ/²Mõ`ÉÊ‚oð!tS™’‡|ó¡Jz7E›~ßø´›nÇ“²FÙAEäXhóÝ(¥pdЊTç7«k+д­7­¤oº§_Á)Pî´‰9´ª™Kîݹ-_¨oŽŠU¨¥ÉÝ•q¿ÒÊÚè1f‘¶B*~ÂÄÚ?ßÁ¢—MYÎ pÀ‡f»>cµ[Îm[»Tz}ò’Ú ”±+NèðŸpUÀ7Tȧð~—Ÿô9’ÜõVßmS <Ç©Œéøpu·ú‰JÑ:Q‰ ÇJ$= øòåÈ5}DÊŒ‹Û بKòjDˆèwó÷ë ŠA$Byrà{ŒyXz 0sCy*SãE}l"®@óG¿Âe« ”'ˆ1·º“ÑÛ“²ºqõ ~Þ®•'œÃ7ðo~øà¾v«3å<ÙºÓ&¬j×ÕÂÏw¾ ]ñ­r|ôíí“ëª?Xо>[*¨È>Fyò$»ú‘ÇYËÓgÉe} ‹×Û*äi÷کDsgt·î–s5Fž#    ÐP …P%‚0”3‚¿öQåC_)MËôyXQíÛ”÷$Û´'eug*SÍÁäŽÓ0C`êõFÜisÿö ºébNk é—«ÕØe·0ÿc±¨]<É®žûù’ºêoƒ¿U–®ÎÚåJ+Y¾Ûi€Ú%¯s·œ}Ü'    w 0ˆD(¤ðbŽÅ‚FqÂKÒiôÂÁ%*ˆ±@a}‘V ždô¾ñxí’»™©uê—s†oœ¥_B°fÈq§Í3Çêè3®²„§J—Ùe·Î¾QÈ“ìêâÑêëåÏ_ûªP¬½õ²ŒUîƒXOV¨tP¦pw˹$O’ DyX‹nr3EùÉp‘’(7n ¬J‹¦’jAâÎË9”`m)R®†Z¯ÓYeû>®¶KôÚžôYrèÍâBw2zk‘;eíÃu'÷½¼;ûî´ % á;]-(½xö„;Ýè2žfWo¨B²BYë­j­ò*¬žÿ‡¬Yð§|=d†r«l¨Ûu·œÛeA   (C c¶ÜQf¬hÔ$@Êû†°º~É,•f§4ýHׂÙ¼âo áDx’ÑÛ“²¦ýg¹…+¬eÈ’"u:‡¡TÙ°ÝÌ{ËÊùZ -»úîÍ«uþXš¶?ï|Ù_)¶£uކÅÓÇjÊÝråH€H€H€H€HÀN€k ì4ïÛ3sãT‘r5u^…߇!4x NÈ†ó¹ –PÁ’+·²~* ÝË}×<É6íIY´ý¬¥`©Êzs'>Ék€»6­Ò ‘»ãó$»:\÷¾VM°ÓG¾båô.ÂÎCÜ-§ ó xH€ ” Xf•Ñ«Æ"•ùùÞÝ;ú8aâ$*y%½f'Eš ÊU/(P\Øà·5A %QÉs+êòæ²M‹Äн‘üÀ®-:ÑnǦÅ9£·'eMûÏj‹üS•õhêÐïtHö¥3Ç«üWíå»éõaîŽ ®‘È®¾míbAvu¬/C´Ã=ÛÊÌ1ß;dWG~©ÀÀG:lùöuÿ(æ‡eÝâY*1໺»Ê¯é­»åÜ#Ë‘ €]øl4r(&ek6’ ÿÌÑ»Ü$–ÃË>EV‘ïì7¾u‹gj+Uœ¸qí—<Êèínfj‡žÑæÙoò*™ûŸYdþÔá:ñmÝ×>Ð9›Æ ì¤#º3ïm½ûÍäÂq"L¤ë&Š#1lªt]\õü”»Ù¦Ñ²'e=Iø×@´?dûN’<¥n|Lÿ/eúÈ~2ôï½–¥Î^ÝÍ®ŽrX…mÊ´uør“àØÞ»åìu"ó~D'Ÿ‹Ì,86 _&É4„Má‘Hwú¨2ï·_dø‚ýjY@ø|Ý­U}µ{žLZwÁed^3l—Ì'?tj©òGÎP W#û¥§öË?¸GZ|Qúq÷aO•Î' 4ùŠ– 7ÅøYólߤ”Zk^GZ´ï®C‰èwó÷ë‹Dºáó®xŸ}c®By‡eTøV(÷ó%ôOhíxR6´¶|u}å¼ß–§[¶W–·ê–ò„o×þ]µP}ˆ§– FÀI™6ƒRˆ‚BÀ‡4î rB*¢¯¹[.Ô†X€H€H€ÂHàâÙ“2ùçÿ©¼…ÃMy°Œ qÒäÊú/Œ#duo ìýw­öZúð›!~£@5ýä[éýiÁ²ˆð²–zË7²Ö£YïL$WšŒYUñ¹rúØyùýNR´|-9wê¨ü5î9¼w«ŠŽ§þƒxÔ!OƒC#  !ðÛàÿi%§Fã·Âµ¿&ï}®í±1,aI“1›ŒîÛA¾1‡P\ å O…L _ѲòÑ·Cë‘à`$¦ÊÕ¼]OiüÎgæ·$@$@$­ `MÉ’c¥A‹O,Eˆ*»TysàÿÔùŠè/&‘çûX&CåjÅÜ©úËI¬?*_§‰dËSÐâ‰uIçN•Z/¿ãÐöÙGôš%¸¼gÏ[X»cY•œv<)ëTUÙ·C÷uô¿’)G^½V¼@‰ VÑs§ŽÉÊySu®L$»7rxï6Ù¢¼V2fËãàR^ËçL–üÅÊ«à\Aí`i…ιf±Ä‹Ÿ@íÊ_¼ÂSK-Ö/-'í(–§ŽSGèÈÉ%*×ÕÝb™Ær`ç&¹{ç¶V¢r=I–2µ–GÛeMÒëæQiëšErçöM)]½¡lXúW˜æ‹öBãŠ2FÜ)»Zå̼ ò–¾Ø²ìß¾Aß³ÓÇêç£bÝW$]æì¦9í[ûåweÜÀ¯Ô²‰Ý’5wëw‚Pâ“à€7>üàjßÖu’>k.É™¿¨þ`óªAV"  ?$°vÑ yøà¾äS ]Ϋ—Ù±ý;JõFoɾmëääá}:/â¢i£d .òY¿ñ2ô«5¿'õù÷ïɬ±ƒ¤÷ÄeZÉB[s&Ñk *Ö{ÕR v¬_&=[7’[7®ªÿ“Ê}U/Nœ¸Jù ¶dƒ'eíõÌþ¬±?È5¸ðc]2ŠI*Ý ’Þ·úúGí’0qRÿ}gAîG»5_)7X–.sNjzÑ—o†Y>Ö.š)}Ú¼,HWeëê¥ó2{ÂOj1¤mïÑRó¥–f8ªÿ‰²êïß%­²žôkÿº:(©•"ZBê ží^•ÿvlT¯’i%9>1î®ÃfKî‚Å­vÜÝÁölY­‹oPÊÚrµ‡i¾hÌ®fŒî–E„é–iŽ#Uää8ñâëûfûQúþ¶RÒgÉašU©zÀ#PV(e¶y»Öyî`s> a"€lßÕ^l®¾)*Gå)L$Y™H€HÀ l^1_O+ƒú¢Ñ•,9N+DƒÿÚ.c–× Õí›×tjDè4}³LÝ|U^kÝUn^¿" ~éª}î† êÔ㣆*íG 1uË5]7àÖjíòx‡zž”u¨øøóÙ»½ž0~Õ)·ò„LÙtYEÍ}]æN"S zéÆ-X@ (?F ¼AΞ8$P&ÀÒ'n|+çæ/Ý>’¤)Ó¨àT{dÈÜ2qíY 8Hiùí§oM5‡íà.­¤I«Žº\åúMõµþŸ5•ƒ*Ìgý&èqNÞxIzŒY,·oÞÎ-ª*…óšCîô™´\Ú÷§‹¾ßå'™±ó®Ròr‡i¾îrE§ž”Eù»wniåŠç´­7äwõ|Ô{ýC­¤#%]`M„lYµÀ~šû Pâ£@$@$@$@>"‹.x®kMðRŸ=o!Iž*­v±B¹ÄÉRÊç&jËRª¼øV;e5‰©Üù¶¹jFŸ›£Üß¾y]š·í®­:X?ABµ6¹¿ã/3~Oçë WOÊÚ§ƒ5_vbŠ5ìÇn(˜•Ûe`à#¹rá¬ý÷®âc@$@$@$@> pýÊ%ýj·ž8wƒ58vÁ‹+$A¢¤öÓníŸQAâ'Ll¥±WJ>H13ç<)kê˜íÉ#AŠ”¨¦¥R˜ÓOmo\½¤ÏåÈWX­5Êd)NPh’¦H£Ó» P„±@m_·T—/YåIZ“3ÇËü©Ã•Ek”\»|A_O(‰^öT‡Od®Ã¥SÇ;èË Ë‹ÃÅǸWá%ÞÎ×®7®]ÖÃu÷˜¹9§êÁó3f,1ÖBS[¤¤Ate| `2a/]÷©@E×;Ïy“ ø”@̘AŽ>×Ô hpK}û^‚—á»·oi+ ¬7v¹së¦ýP»°¹[Ö¡¢:0óÊS¨”¼ônð¡Ô3fÏcU…UJ,?;7.×9¬0Þ"åj(ëOg½ V Ì9óYÁ Žîß)í^*©sgUmÐL­‹ª¦­"pëëñaCùwõB=«“`vb<¾owì¯L¸*–L¹O†§x3_O¸‹‘'÷óƒ²ä®Ü~¼. +Å‘@øýÕ:¶Ë#   ˆÖ)%&vœxÚ=êÁýûj?ŽOyÀå ë‚à"‡u@vAxo»xRÖ^ûˆ¨9{â°­n^üõIõë€ Öpõâ9•ä÷‰uªd•z‚¨wë—Ì(FM?ÒÅ¡@A6¯ø[E´[£"øµÖÇøµà÷_åÁý»Ò®Ï©òBP0sáÎÝŒwËÊùÚjU±Þ+Õ<ýÂÍ2<Å›ùzÂÕ“²ÞÎ ‘–!ÉTŠ#®räÁ#   P,°ÆæÑÃO­1 —œ1ùŽNÛ.·n\×¹¨ìç<)k¯‡ýÄÉR,p©Û«Ö$Ùù­:5«¬ƒ!ÄŠýä{ú"åj*KRù}X/U<Њ²—«` I”$¹üùk?âŽZÿôÄ}ÏXîìŠúÚ·m½B}Wk pÞ.Å+Õч5î,“î.ÝZÕ“c*•äß‚ëཻwÌ©P·=t(ãÍ|=áêIY‡¹y€ÐôçOÕ!ßñEÅ‘(G<"ˆ„êÕ·¢&Áåƒ?dÀgÀûg ~ý'/¨‘ðÏÝï†ô|é*zNÈóäky¡ù':¯Ò‚ßG¨ãŸéˆvH.ûUó*Úfïß“²özfÿƒ®ƒÕn •[é5"}¢¶:!nǦµ!Üåì’0q¸ë‘R¤É \õ‚Âd#R`¡2U•ÂrP­ûJ¢£ë™z…JWÕ»ctäƒÚ¶v©LüñéÒ²¦J¢´¦ëßÕ*­“{¢©o¶p§+Q9@Õ_,ƒ»¼¯¹ "àˆžmU@ŠïU´¾ :y±)ߥe-iU3§VÔ̹඙så×—giÑŸc,¥Ë›ù¢!O¸zR6¸ñwþìÉ#j]ÔC©°¦[n’Áµã¯ç©@ùëå¼HÀü=ßñÛT?š§BN`Þ<þ=E$t¼€BND€…(}½&ü£‚3”ÔIw‘ߨ§Ê •*]&ù¸û0‡i{RÖ¡âãXÖzŒY¨ŽåûŽ-¤ýK%dÀçoèH]†ÎR–$Çho¨f¢Ó.Síq+AãÆ«="aíWÞU‰rß–#û¶I¯O+Å©†^GÕaàoÒ¦×(.ó3Áu:øò‡©R³É;Ú\`%›7y¨vüzÈL½ÎÊ©Š[‡¹ ÓÊ×QeÁú©ó;ù¤{>~¡^Ô÷·ò` ¢Ú±ÿvi·Á\‹ rú'ž”uÕÆý{÷Ôš¦º/äƒÊW¬¼Àªž‚pèP@³(kÕÁú¥³'ŽHVTÂÝ> ýàî-:ê\ÎüEƒeÓ¾I)é:ô/=0ƒé.Ä-Š߿wW)«ŽQC¬ÂEO¸zR6„.. èÐ\þ]µPF-=¬";&r¸æíAƒ¼ûùcþ~}ñá3 .ü–ËÛG,âê%L˜PwvëÖ­ˆë”=yESsæÌñªnT¯d>©@Eõ;Éñ?kT <»æ³'¬ ÔŠ¹S¥ÿg¯Ë࿶넹ž‚¥#šÀÚE3dÜÀ¯dØ|ÇÀ=ŽgÕßäí*Y¥y»*‰qðQ=Ÿ?)POV÷yJ!”òsçÎ ¥/G ÔÃØµkWdÇ@$@$@~G rý×”ëØH@á;é8hŠßÍÏß&„µVÝG-ð·i¹=¬ CÞ§†o¶u»Nt+È5PÑíŽ;Í÷öíÛ‚ €ï` Ò†æÈ±»}× [~3XÒfÊ.mEµFPxÞo¿È‡ßüì°-ªÍÃ×ãõ™Ê×gû$@$@$@$UdÈšKÆ­<©ƒ,D•1sœÑ@ü„‰eä’Ã’4Eªè7yfLÊX,J$@$@$@ÞH”$™·UY"„¢ ƉKå)4Øtá ¯“ ÀcT ø( €›¨@¹ ŠÅH€H€H€H€H€H€ Ÿ     p“(7A± Pâ3@$@$@$@$@$@n å&(#     æâ3@$@$@$@!h7FWy‰H º Ýî8çK$ðLì?´[vÿ·]âÅ'õª5v ó–N—˜1cJݪ‚-ã‹ /Ÿ—•–¸l:~¼ø’'GÉ‘%·›ËB~¼ÒêöòeëžbîIˆ¢øÅ£'ÉÚ-Ë¥dár’;{¾(>› ág*ô·äÿõ›gÐæâ'A‘Œ€/?#hŠd7›Ã!ðݾo/ÕÊוdI’GºÉÊWBÚ¼¤è™ÁݽwG`AûmæH<¦—Ü¿Oº¶ëo.ûívó޵òy÷wä»/‡øå·7‹# $@*a³+ Ș>«œ:sLzÿü•ôùjh¤’.u ¨þ’Ëq5¨õªÔjZDf/þ#Z(P.!ð$ @´'@*Ú?@$‘Z·øB†M(þ./×S»‡¹Óÿ=eõùkáTÙ¶g“ܹs[Ê«$Õ+Ô“”ÉSëê'•R6K]‡«YíÊ ¬&wíß&Ë×-”œYó8¬«:uö„Ì\0YJ./¥ŠV°Ê‡´S OaI2œoô5âůÎñ(@ ¸®(×}öl9(ðâÇO¨zžšw®‰^¿|…u³"V¬Ðÿ)¾rý"¾êÝGNìC¢„Ià¦ýoîòÉH“2=fŒ^…¹‹jA@b5Ç*[¦\fÔœ¥“0cñïÈä‘Í,€òÛ´?é©]$ØBñöí[èx¤Íl<ÙR?½üû·àËïêiØ}-XIŽE«gB©hôikÌ\“òÚ´ic5ߤI“ÌîÛ¶m[«ù&NœhVÞW_}e5ßøñã|•*UB‡¬æ7n¶mÛ¦>“|_ýµÕ|¿ýö›Yyß|óÕ|£F2òI{»téb5߯¿þjv_ùù²–†n–¯GÖ²9Ý9™ÿT瓦X¡õMš3[õ µû4űS1fà,Ô«ü;°}ß&ˆÔo÷ø]UM±e°÷ðvÈŠz)’¥Reî<°E½J@"½:i3ª÷Ûµ÷â¸ÇE¹âUÔû°¾Ý¼sfWA‡å ¶Ô/QÂÄxúì Úõj  X0a#Ê—¨‚ûƒ0lB?…U‡Ð>·µRÆ÷C:ª:ìX~Y2dWÅJ/UÓŽÃÏ=Çbéä­^¨oú7ÇOßAë†C»=?£(@`åB;´¦._¾{öì 1€=z4nÞ ^lß¾}!Pò‡¸žïСC!PcÇŽ5ò9r$Äj„ ¸qㆪz@@@ˆ”(ׯ_WùNž<b5yòd#_```¨”^Þ™3gR%Ùµk×Ô}/\¸b5kÖ,#ߥK—R5gÎ#ßÕ«WR%ª|.I¼C  -Z„+W®¨|wîÜ 1€Z¶l._¾¬ò!¤jåÊ•úKzøðaˆ”¯¯/.^¼¨ò=}ú!Pk×®…¸Izþü9\%€’öì>[w­Ã¯“ÿ™[”1}9ý^Z½a1ìFõ Ÿ¢¾Wsãó %«¢[Û1ptw¬\¿>ŸµEÕò^ZåYQOæ1I uF&÷q¥:økë ìÒ‚©Þ- =I²º\Ù╵•xF™r ½.µ?/kvNʹxõ¬:çóY;üØõWãóðÔoúÂñZÀtƒ¾«‚')$Y’äê½~§Î3Ê ÏAxêðæÍÍä$R¥HƒTÚpD=U*ý1ú~3Zd¥Ÿâ+(@ PÀª(«,®wräÈ‘(T¨Pˆ —?ì%€‘T°`ÁóIÏÒñãÇÕç 1ŸRž,1Z>éùÒËËŸ?ˆåI> œ$åË—/Ä|‹/Ɖ'Ô}CË·téR£¼¼yó†Xž§NRŸçÎò2Ç J>iohùV¬XÓ§O‡Yž2ØIÊ•+—zµömÕªUª<¹oXùΞ þ9GŽÖŠR礼sçΩã°òÉÒør_}©|k…Jyz •5kVkYœö\Êä©ÑïÛáèñSôú5fýækµ­èHªU­¡šwdš©hþRê­ô&IU¥œ—ZœB†¨Iµë@ðµ]ÚôSÁš@Ö†>yúUµü–Iæ=xdvúéó'Æ{ >nß»‰t©=Ô¹ðÔoÜIªý±yÏt̘1Q¢P¹Pᩃ —ÌçYZ^óÎ^hÕ ÊkÁ¨<°zUåù \^€”ËÿÔ­ú¦òÇ­-àfΜòVʘ1#ä+¬äááù +¥OŸòVJ›6-ä+¬”:ujÈWX)eÊ”jhaXùR¤H¡†H†•/yòä(]ºtXÙ4iR”(Q"Ì|‰'FñâÅÃÌ—(Q")R$Ì| & 5ÐÖ H A¨±ž/~üø-@Õó9ëkÓ:_`‰ß,lÚá§V¶ûT ’,ÓùËÁõ·?Zn*ùïÿðÈ"iSy`÷?A—É“¦BÁ<ÅT€¢çÛ¡ ÿ“TU›Cd™Êi½R2¯Ê2É¢û·À†í«1eÞoZð7Le Oý.^=§† êà Mïá™-äÿðašOŽß½{gv* Z“‚øqD|Tú“÷îÃ-†:'½UÒYÿ)“ýìK/Ô¼STo•R²‚ôJ/‹lä+ó ¤Ç*»¶Z_&Û{ýdϪní~T”Þã# OýþÖŸ×o^ãÅËjáÓÆÊ0A[“¬ 'Iß@5B½¶Õ¾I2ôlÚ¯ËßÛˆ·ŠtI’eÊMS…†ñI/• ŒHJ—&ƒ òÆN“®àdkýdÉô6@ö«’ž³&«©Í„Ój+úuþüýá‹S´Åҧɨ‚¦¯z7ÄÄY#àS¯šÖm£ßÚxµµHþþó<œý4ºª6)€O|Šà—q½Q8_ ü1t‘ P¥àüžEðÉGuqê\ºüB­\hÜ (à²nÚ$Ü¿]¶õl¸±Ä´,9ÍDGÞ I×ö»ö?W·îÞPêÊ*t²jœ¾)®£<7[ë'ÿ·sáÊY\¹~Aõø¤N™µ€J šL{Ô¤m2dï¶ÖöZÏìZ²µ²ÖñÓ‡qIÛ`ØMûŸôFɰBýgÍôAîá•6+mªô¦§£å±Gñàß%gø¿~ýYÙ³-ú=8„/Zþ¸³Ò& á³çï¬i“õß_{ÜCøL¥yL PÀÒhCòÒXYzÜQªlkýäÿÔ²eÊ©¾l©{æ Ù´'›-Yak¤'Jæ…ÉWXI†2Q€ táÓ%øJ P€ (@ P Pañc P€ (@ P€º‡ðé|¥(@&ШVkT.SC­°÷Á*ÁS€ l`e³P€ €}džçÙט¥S€ @äp_ä8² P€ (@ PÀ@9ùC–åŽÿkŠŒ2þkx=(@ P€ A€”#<;ÖáСCˆðäZ)ƒ‰ (@ P€@9ùOAá…ѵk×Q<ɵR(@ P€ (ÀÊéd³È*Uª yóæá ¢$x’käZ)ƒ‰ (@ P€`å?õë×Ç£GàããcS%Á“ä•käZ& P€ >¼À¾mûà;߯^½ú𕉢G®\¹P°`A<~ü8Ì Jž$o ×2Q€ (ðá& ›„n>ÝðôñÓUfÝ’uX=ou„®ýPݺvKµyÑ”Eª ¼/Þ`õ‰sžÐ{’B ¢Lƒ'QЯqN¶Š (½JU*…ZMkÁ=Ž{„*>âûÔeP„®åE À¿ÜH÷_ §>ª]»6 „ׯ_=QsæÌ1Úl<ÅŽuêÔ1>çAÀ£¸›#Tƒu (ðA¾üîËr_Þ”0`eîá´ï’$I‚êÕ«ÃÏÏOµQï‰Ò,sžäœžªU«¹†‰Ž àååeüì:B}X Dgooïè\}—®ûV¿­¸vé|Ñ@õBݹy+f­@ÉJ"g¾œX5wŽî=Šx â¡X¹b¨Ñ †òºzñ*Ö.Z‹G÷áå‹—øsøŸÈS8ÊU/gxʼ*¿~8€Ï_ xùâ¨X³"’§Lnä¹ï>–L]‚"e‹ `É‚øké_صiý12<ðÆ•hÝ¥µªÃÖ5[qùÜexðD†5!K£9=&O:ëwàtÀiä+’ÅÊCÞ¢y!ÿ7¼éÒÙKª¬²Õʪöo^µûü÷!}æôðjè¥Ú«—Z;ô<VlÀîÍ»qéÌ%dÈšAYU«SMÿØìÕo¡ŸÊ{íâ5¤Ë”µšÔBéÊ¥ÍòÈ›ÀcgxæødÍ•¥*—RÏÉ2ãßÿ­æ}Éýƒî!K®,([µ,Ê\Þ,«­ùÌ.â›HpÓðÿŽ”’XˆÃ lÞ¼mÚ´±©žS¦LAåÊ•mÊËL (@¨ps î‘¶çŸ1ú=ßFuó¬Þ¯m­¶Ø¶fvßÞd)’áè¾£hXº!Z|Óûý÷ãäá“Hœ41=x¤®o×»ºÿÜ;7ìD»ZíÔ(ù@”Æ_5Fÿßú«|`uiܲPCÂÄ !í~üð1R§K‰+'"_Ñ|*ß¹“çà•ß Ò¶{Ónl%I–{ïî…Ômï–½è1¸~éö âă7oÞàõ«×H>5æn›‹ŒY3ªr$ˆk]½5î<ˆ©S yªä8ê<Þ¾}‹Ü…rcöæÙH”$‘Ù=·kŒªsÖ¾Éâ2?¬y§æX9{%ž=y¦Ê`IÚûÃØШm#³2­µãÕËWèÛ¶/VÎY©Ú1[F\<}Qµ¥jíª=´1„RÍž­zªàQV,N–2îÞº«îÑé‡NøæÇoŒªN=Ã{ WåˆÇíë·Õg>}Ðwt_cÅc ,Û×i¯­‰¨ÀóÊù+xöôÚõÒžç/ÝÕu¶æ3*àž1=U-ìù;kÚLý÷×÷ã(Si'?®X±"R¥Jf+%äe¢(@ PÀñf…TiSÁÿª?öÝۇ喫@jꈩ*h’^™€È”=“ú#_ŽõàIZ×­Y7?xÃf Ãþ ýªŒiMÃÓ'OÑ¢r ÈJx¦iîïsñüÙsŒY8s¶þ;@ŠQýGað”Á8üø0><ˆ&_5QÁÂò™Ë"–ÍX¦‚' `v\ßÕGWãÀƒ¨V·N9¥z¶ŒÌá<˜=n6궬«‚Ì]·v©ÀÍ=®;v M“µvL5MOŸµúLÕÉ÷˜¯zý´Ù§Ø¸r#þò‡QÄ´‘ÓTðôIýO°çÎl¿¶]ÝOzÛÆÿ4øHÚ¶vwŒÂe «<þWü•³Wc/Ìù}~ô»Q¦ôêI/U³ÍÔsXyx%¶_ß®zŸ¤çðÞí{*¯­ùŒ‚y©  "•Ó± “ÿ:R·nÝ0+)y¸÷S˜LÌ@ P€p´ÒbÜ’qªÇH*$Ãó>òþHõv\¼j×.^‹#{Ž ’W%Ôi^Gõ>ɹ—!cÒ‹"A” í3MÒòÇÊ? ƒ 4MÒC"Œ$www´é<òE†ééIzyÊT-£z²ô^‚xñã¡ù×ÍU–‹g.êYÃý*õé5¼—ê}’²e(cïá½UOØÌ13Íʳl‡´uÒÐIª‡iàÄÆP¸ñâbÈ´!Þ6  d¸ã“ÇO0yØd$Mžƒ§6»ßç]?‡ôzÈðFIC¿ª^ûŒì£]y#=lÃgWîâ©‹ÕF9¯;IÏ—þ·X‚„ ðÝïЪK+ȰÍðäS™ù-Ò8*ÒI»À `òäÉ¡V’«ï…ÊÃ)@ P€% sidÈœi’96’¤$Wþ·$Ù»m¯Ê'ó¥ô¡ê„ö­P©BêP}ø›œ(R¦ä|kIæM™&=Ð{cä³–[ª/9–ž¬ë—®ãê…«˜6zšœRÁ‡:ˆÀ·’•J"V,ó?oKW ž$ÃM“e;Î8§zÛJ}TJ¦y¥Ì|Åò©áWÎ]QKÉKÀ% 8¦©YÇf*ˆ”ÕeáÙg‘#oefi\´\QlñÝ‚ §/ {îìÈ_,¿*j쀱êÚêu««!”2´±w¡ÞÆmlÍg\ÀƒH0ÿ ‹Ô¢Y˜# äÌ™Sí uôèQ«ÕãÞOVYx’ (à°2Ê2ÅŒS’¹2¡%™ß#©Wë^êÕÚ·AÌNç*r@fYéŠ3†ÑÃ"Éܨ+6bjñ½pÌúa„_3çÈüÞµ²„̃ºyõ¦Ùg–í…($•¨XÂ,Ÿþ&{žì*€’¡€/¼•»`nýcã5FŒj.™œ¡‘’$ˆ*‘Âz¹òùà‡ò‚*ŸVAßQ}UïÖ¤!“ _Ò[% ~È|) ÓOeæ·H`餎_ ô0…@±÷ÉñŸkH P€0%¢IþØ—ÔshOµbµrd¡[“-uéýyoµb`ÔÐ4r(=4×/_G“òMÔ0B[ïg™ÏÚYo^k‹YhÛ¸¤Ë˜Î2»Õ÷’×Z’ù$¥L“ÒxÂZ @÷-P¼Úôy!¯,9³¨²å›ôÐù|íƒ]wadžزz d¨¥¬t8~éxÈbáɧ2ó[¤ 0€ŠTÎèQ˜éžP¦5–ÿ:ýŸLExL P€pnY"Û?b»ÇF͆5Í+‹G¨•òÒØ@™`åÍÃûUð$½9ó·Ï7n'÷ú¯I–·Lúн¬žÁÃ-?×ßgÎÜ{%«vÔM?­^%P’¹b’d1Y1PÒž-{Ô«é·}ÛöáÏ¢qÛÆjivùL†(Êœ1= ÒóK•, ‘8YbuêÀŽxûæ­êi’eËå«×°^yR²:àÒKUek>ý>|\ˆÿ'‹È­K‹B}O(Ë[rï'K¾§(@ 8—À»·æCú*|\A5pÓêMï5tÜOãÐÖ»-μ”¼—ÙÆî”%ºMç*ɰ¾å³–«RÂêÕ íV›Vm‚å飦ªKdÎShIæŠÉj†Ç5¦yí: þ*ת¬†ÔÉÐ=éÑ’Uoß^’\Ï?ú‡Ñj^“|. OHï“Ì…’2L“,áSÉG-2¡[ÈÂ-«¶4‹Ðóëu×–°5Ÿ~=_#W€TäzF›Ò¬ Õ“&˜(@ P€pNéõ‘ `Ì€1FoЬ¾' ?Èp±~_õÃÞ­{ÕžRƒº ‚,Ó]´lQµ¼xd‰È¢²j lø+†¼.¾Ÿü9ï·%½H²¨BD’ì¯${^ɲ鲸EŸ/û¨Í†¥–=l–åËj{]~ê¢æk5¯Ü\ ™“½µdÙõNõ;©yT²2¡$*(+J°'{ZÉ&Æ2Įݧ픟ÜO†&Jê?¦¿zíÒ¤ VÌ^¡æE­ž·M+4U‹hôÖS}.ßjûÔVevnØYÍ»rá dS_i‡$¯F^êÕÖ|*3¿Eº‡ðE:iô(PßêÎàå0¹÷Sôxn¬%(@ P ¢2¤L‚Ù£(èN±Êžlûs—Ÿ±èÏEêKÊ—Y´ ÓÌzŠ"zoý:Â6jÞ(|Ûè[Løy‚ú’^•ë}Œ Ë' k³®ª÷FòÉûð¦¯û­†$öúüßE1dá…Ÿ'ÿlSQ ¾h †ÐýÜõgtnÔÙ¸F6¿½e¶±Jž| {CIõc‡Ñ£E#o­¦µÔƽú YÍpÚºij¡ÙxWOÐJ+Õ¬¤ŸR’,e>yèd´ªÖÊ8/«,ʰBY-Q’R¶ä3 àA¤ ¸iþïH-‘…EÁƒKš·mÛßÿ}´©;+J P€®- ïdÏ?cô{¾ îqqñ’%ÅÓx¤yoésŠvãøŸ@IDATâà Õ#½'¶.ºÙƒéؾc*)X² â'ˆosîä9$M‘áY¼Âw¾/ºùtÀñд}Sܺv '€GP—q7njq û<Éð¼{·î!gþœE,ç/é—ÈðóÇϪ%ÐežUHõ–ý£N;­üe¨`‘²EŒ½žô²ôWyR¦¼Jð&C“§J®l¼ÚšÏ¸àxÆôTw·çï¬ióôß_{Ü”©´‹Ÿ>}5jÿ—Œµk×"W®—%u16— \ÀžéM×ïáL”Þ6g{µ  œ­}ÎÐg  8„Ï~"#Ø ˜dß'ù?ODäe (@ P€.%ÀÊ¥÷û•Å$ôÿÂöþ§XËxc 8¡€=ÿK«r±I € ègØóßEýìrâ$6-ÊØjþ£,f¥@ú?:!|ÌÓ (@ P€Q$#ŠîÃÛP€ (@ P€ˆö  ¢ý#d(@ P€ (@¨`UÒ¼(@ P€ (í@EûGÈP€ (@ P€Q%À*ª¤y P€ (@ P Ú 0€Šö  (@ P€ ¢J VT݈÷¡(@ P€ÑQ€[IDǧÆ:SÀ~첟-K¦(@ P  xyyEãÚ³êp<oooÇ«Tjä¦íàýw® óîÞ&3PÀfý¿~Úé×Õæz0#(@GÐÿÎà¿‹ŽòDX 8–€=ÿ`”c=kÖ† (@ P€p`PüpX5 P€ (@ PÀ±@9Öó`m(@ P€ (@`åÀ‡U£(@ P€ K€”c=Ö† (@ P€p`PüpX5 P€ (@ PÀ±@9Öó`m(@ P€ (@`åÀ‡U£(@ P€ K –cU‡µ¡(@ P€Ž!àíí ???Ǩ kA'ðòò‚¯¯o´o‰›¶ƒ÷ßöh…¾ûoàÛ@{Ï2)àRž1=U{íôëêR–l,(àúßöüwQ¿‡sˆ±p {þΚ¶PÿýµÇýØe*Íc P€ (`!ð÷›,Îð-(^·XÃ{‰Ãæç(‡}4¬(@ P€ (àh  퉰> (@ P€ €Ã 0€rØGÊQ€ (@ P€Ž&À9PVžÈõË×qhç!xdñ@áÒ…­ädBšß?ÄŒ5Ô0òìÞ¼÷nÝ3Þ›¤ñHƒ\r!qÒĦ§ÕñÙgx4¹ åFö<Ùßûü¿œðï‹Øî±ñq½U1û¶íÃíë·Q½^u¸»»ÿ—¢âÚ ;Aذ|< z¢P©BQ'V‚ (@ PÀ9@Yy®‡wF7Ÿn¨Ó¢NˆÔ»wïTž¸ñâšP~ž ¢BK9òæÀ˜…cÌ¥+6bd¿‘èþKw³ó¡•cëg¿öù “$4¨IÃ&aÛšmØ}{7ÜS8FµnÉ:¼~õµšÖ²µYF¾«¯¢ûþø¢û   P€ (@ ØC€”=Tµ2¿ò2eÏd”.=VwnÜÁÎ;±qåF4.׫®FÚ iÿ}HŸ9=¼z!Oá`þ×é^´•³WâÙ“gH”$$h™öë4ü0ö4jÛȸÕúåëѹagÄŒ™sfVÙ¬±³Ô烧 V½o—Ï^Æèþ£ñúõku^ŽÕzå¿ÎÝ}ºãáý‡H‘:=„Ì—š5n–X†TiS÷»~é:”l Ê’&OŠA0sÌL|óã7èôC'#(@ P€ (Q.cQ¹\'AÂ~ÿý˜=~6âŧzr"PŒÕK&ü2AOÕêTÃÎ;qðÁA,Ú½§ŽœÂí·­^£Ÿüké_*xjÖ¡öÝÛ‡•‡Wbûõíª÷Iz„îÝ6_Upö¸Ù¨Û²®Z„b×­]˜»m.Üãºc`ç*xÑËÐq’§JŽ5'ÖÀ÷˜/vÝÜ…yþóÔÇcŒQ¯Ò›ð"@ÍK–2™:îÿ[õ™Ì‰êѼž?{Žéë§«vI½Úöl‹;7ï¨ySú½äuíâµÈ[4¯j÷ž;{Ô}åþOÄ›7oL³ò˜ (@ P€`!¶°/êÓ¦Z(B‹¯¥ DòðùÈÒã3{ËìH[@âÉã'žé9w¤ê©‘,Q~feOœVy2f˨z‹äM‚„ ÔB­º´RÁŠi!9óåD¯á½Tï“›››–×{xoÕÓ%=>’=xÉ'ó­¤\=-[ÅÊÃ+7T¯™~ÞÚëôÑÓU/’ô•©<1YŠdèü¿Îª­2‡Ì4¥Ë˜#fPí–óÙ<³©!Œ²ºß…À ¦YyL P€ (@ p_„Øþ½HkIzOdXžÞ¾}kô‚<}üT ‡³¶È„ž?<¯'žP+ØÉ²ëqâÆ1»´RÍJª·Ëì¤Å½cŒUÃñª×­Ž|Eó©=©zêm‘;xnU¬Xæ?:²h†¤ó§Î«WÙëJz$©µ£k¯A–ˆ?y(xî—œ-Ý{T}\¼Bq³l²wÕú3ëñöÍ[³ó²”eû³äÌ¢ò\>wYtfð (@ P€ Â)`þWp8/vÖì2¯GÒë—Áór¬µSz5$%LœÐÚÇ»xì{«ðIÏ“ÌééT¿~3P-ÙÒõV ᤒ<2yXÍÖRé2Oªï¨¾˜ >uæøu óvlM1bÄ€ôÉÊxÒ#u`Ç[/ 5ŸÞ vëú-«ùž=}fõ¼éÉ–[bË¥-˜²fŠÚV†Êœ¢–U[ª}«Lóê+÷™ž{óúZB†ÑI’a5óÖ„Ì—ª^¯:FΉ¥û–ªySù‹/ï®×Û´Óc z$XÙ’bÆ –lÉË< (@ 8¶@PÐsÌ_€ƒo8vEY;—`eå‘gõ̪zIdÙî »AVrÀ†æYðýÞ«˜œÌš+«ztÛzÙ&Ym:”%Ç%Éä–éÉ£'j_Ëó¦ï%Û»u¯šÿ$Ë–÷ÖK-ÀðóäŸÕð»¥3–šf‡<šžÔ‡î‰¤….Ts¢M„Ÿ&þï&ÞjX _¦½T¦eXK»d˜Ÿ,¼a™~èðºùt³<Í÷ (@ 8‰À¹sAhê³Ó¦v’±Î"ÀÊÊ“L˜(¡±ÿѨ~£Ôñ¦Ùdhš¬z'é“zŸ˜~dÓqü„ñU>Y].2’ì¿$K| Ä­kæ½PsÆÏ søš Ý“ž&YÙÎ4)SD½Õ‡ÁéŸmZµI-î ¿—ש£¦ª·ú5úR퉓%6͆#{ŽàÜÉsêœå¨wo͇ÙU©UEå“a¦IEøâÙ ÓÓ<¦(@ PÀ‰R¤ˆfMò£xñôNÔ*6Å8*„§(='Í?jŽ…“âʹ+j>P’dIpüÐqlX¾woÝÅçÝ>Ð&­‰ϱºqõÆ{w— cesZk)o‘¼èúS×÷>ŠŸ >º ì‚þíû£E•è< 3dN’² ¹¬\ZªíS›WoV{6};ð[xdñPÁ˜V’¼y™]~ûúm´«ÕÍÚ7Cš i°jî*¬˜µ•¼*¡fÚ*¯lâ+çG|?Bm+s½¤—kú¨é¹Jèɦ¼²ø„Ô?{žìª²¼¹ s”!~Ù3ÇÎļ‰óÔÆ½å«—WÃå™HP'+ü1Q€ (àœÙ²%ÜÙõœ³qlU´`ÂãK™&%fl˜¡†‰íß¾»6í2rÊÞB²QÁ=Œsá9 CÒü‰óñE·/Ìœ §/@¾¬%}á kŸÉ¶/ž¿ÀCнyw•EZ¿t<Æÿ4OŸ„¼jƒH2giòÐÉhU­•Q¼¬h×mP7ÔhPÃ8'_÷ÿZg½>ïeœ—€I†üé©a›†8´ë$ ”E3$¥N—¿ÎùUmÞÛµiWt¨ÛËö/ƒ†Û6VA›Ô5èN  d¥¿9[ç¨t§þ:ò%IÅ5odIt& P€ ¢ÀÙ³AX¶ü²dIІ ò¾WñÅ‹OàÂÅhÙ¢ š½déIÈŸåËg2Ë{ìØ-ø­9‹€€ÛðôLÊeA¹rÿæ¹|ù!,<ŽÜÚgŸ~êi\{äÈMüµþ5rÌ­6ÂÕóûÎ÷Uå€ñд}SÕ‹tâÐ Õc•+.=›Ù« µ=˜¤‡Étõ>™›uõâUµ´¸>DP~¯œ¿¢z¨,—#—Í|eî•¶™sfV+þ™ÝÈÉßxÆ þÇßN¿®N®ÇæQ€Î( ÿaÏ{¼ùÁ ?H›îߎt#7n,ܾÙîîÿ.õòå¤I÷«úìêå®8tèJ–™‚NK`ì˜à.RéÑ¿íFÏ^´íaÞ!}úD¸~ý1äÙ¯;Ço£kh£TbàÁƒH•f8råJãÇ:mýº“~Ÿ¸Ù²&Ź3ó¿Ùƒ.ÝÖaõŠ&ðö¶þ7‘™p‹5P]gÏßYÓŠ¿¿vuØe*±ì;$›Ò¢DètÒäIÕ¸ˆTIzˆäËÖ$Cñ¬-nz½,˜¡/šaz^†ôI€fšä=Söÿë‘ég2ÇK¾˜(@ P€ˆ¾É’ÅÓz„rañ’“ذá<¼¼rY»ö,>z‰¶_E¬XÖ§éKž®ÝÿBÅ ™°`^¤M›¾@û¾?a?R¦Œ?~„¤Iã¢L™ŒØ¾ý2îÜyŠT©¨ûlÙzQ½ž¿ðW®æÞ½g8~âòåMY`B†éé_Ož¼Òæ.eÄm.ÓéÓ÷Te½´ûHÚæ(mý'`ê×§‚êmÚ²å¢ú|ß¾ëxôø$àb¢€-Âg‹ó(Š5+bõÑÕùQL (@ P "2<¯©¶<ùoc÷b“6tîã³cµïi<Õ‚¨ÐzŸôÀH‚¨d)‡…xkÙ€WRÁ‚ià¡Í‘ÚºíŸJ{M¥ ñ+V,Z(Bηió•ßÛd8¡:ÁoA€T0<ý¾€,L!_L (@ Pà¿´jYHP2ŒO(¾G[PB«RŒnê#é¥ú®GÙ²!gÎäÆgÒ«4eê!ÕS%ÔGeQ«ûUÕæ:õéwA̓’ùO²Z_Ö¬¡oûbÊ—`åò? (@ P€Q+P¤H:äÏ— ËWbèjðó;ƒºu,ŸN/Àž\§Äl (@ P€ÑD€‹HD“ÅjR€ (@ P€^€Ô‡¬(@ P€ (M@E“ÅjR€ (@ P€^€Ô‡¬(@ P€ (M@E“ÅjR€ (@ P€^€Ô‡¬(@ P€ (M@E“ÅjR€ (@ P€^Àîû@}ø&²öðï‹Øî±ñq½ÕmömÛ‡Û×o£z½êpww·ç­Y6(@ PÀ®ÞÞÞðóó³ë=X8\IÀËË ¾¾¾Ñ¾ÉnÚÞÛ£úî¿®º‘î•óW°wë^.SÙsg·±C”Y%[$L’+­Tõi[«-¶­Ù†Ý·w#YŠdv«£«øê€úFºvúuÕoÃW P€ÑF@ÿ;Þÿ.ê÷ˆ6(¬(¢€=gM›¯ÿþÚã~ì2•ŽÄãC»¡Ï—}ðÃØœ:€²$+U©'M ÷8öí}rU_Ko¾§(@û ü}ªýoÂ;PÀÉÜrOrš2€ršGé ùò»/£"¬(@ P€ ì àÔ¦U›pþÔyÈ÷—Î^Â‚É PºriT¬QQ‘Þ¾qËf,ÃÉÃ'ñöí[ä.˜Û5FÊ4)ÍÈe¾Ï+7ЪK+ÄŽÛøìù³ç˜3~²zfEÕÚU±rÎJlöݬ>ß¹a'ž?}Ÿ¯}/~‘-²|¥MòLwoÞ­ ³äÊ‚²UË¢üÇå{ñ€ (@ P€¦.1ªK“.X³h FÍ…®M»ªö÷Ý-¾i¡þx–s„xdöÀëW¯!U’dI0|æpTòªdxµ¨ÒBÍk:ôèâ'ˆoœ¿wûʦ+‹ jà·¿Áç#ÜqïÞ½CŒ13fLl»² ÉS%ÇÕ‹WÑ¥qÛ '„ŒÏ|üð1R§K‰+'"_Ñ|F¹Öjä©W/_¡D¥X>s¹*óÁ½ê^ŠÀ¤Õ“Ô9¹öÜÉsðÊï¥ÇÝ›v«€MÚµ÷î^UôôÑÓ1¼×p¼yó©Ó§V‹?È>}Ðwt_Uoy/÷ëÕºüO¤M(ž>~Š*ŸVAÀþ$K•,Ô9Päu÷鎇÷"EêxôPÝ3UÚTXv`äUÒúåëѹagußÌ93«g"ÏEÒà)ƒQ¯u=u¾òlÚ×i¯‚GiO†, óªž=}†v½Ú¡û/ÝÕ½åç@9Ê“`=(@G°çü½Æ=8„O'á+", á³Çœ$k•2~í°ÜƒK-cÞ¯]?´íÙóüçÁ»©7ž<~ ®Þ¾y‹…;bÓùMð¿ê¹ÛæâÍë7*hºü¼µÒ¹9[æ`èô¡êã~¿õCÀ‹#¨éÖ¬Ž<Ža3†aÐ~ì»·Óþš†§Ož¢EåxòèIHÅ祇g«ïV,Ù»»nîR 6Ôÿ¼¾ ÊFõeäÓæþ>ÒK6fáÌÙ:GÞ¶vw¬¹Ø~m;ü¯ø«úx5öœßçà÷A¿ë—cÂ/TðT­N5ì¼±Ģ݋pêÈ)l­HpØ£yuÿéë§«ë·_ß®žÃ›w0âûÆU:PNkN¬ï1_Õ6yV’Æ cä‹ _é“Þ²fš©g°òðJH½¤÷éÏáB‚b& P€ (@ X ¸T%@Á=P´lQ5\nÆè¸÷>Z}Û …J2ldÛ7¾QÃÉ$øˆ¬´vñZÙsDõjÕi^Gõ>It,ÃÆ:ýÐIQ2´Ï–ÔLä/–_e•^¥ã CÖ X©ÿ ræË©>ú]p€×gd£(Q’Dª×MXeÒª³'ÎZ~á÷{·“¡~0˜&ý~2¯©QÛF¦Y=.SÍ|þ‘¬z'ÁÆÕ WqéÌ%$-•Ô¸®H™"fA‰Ì3’våÈ›C·¬‹Ì9’ùNN_À½[÷ÔÃ:-ê NÜ8F™rP©f%c^—Ù&oŽî=ªÞ¯PÜä,ÔQëϬW½òmÒC%Iºv%€¹vñäWFæâ˜®Â'CÝlIÈŠtÒ£R³aM³Kdñˆƒ;"Eš°(Yà@·°ì…’¥Ú%É}BKúç2ÜOæEé‡~,r!÷Hœ,±Q–,õm™¤ÎwnÜAÒÿ´Ì#÷’^¨ýþûß H~èðƒZ4c䜑XøçBµâ©CÔí¦åHš-)<¾vPÃ¥§Iޝ^Ãz©¹_}ÛöÅÒKß«¯-u` P€ (@çp©E$,eùêÁûýl]³Õì#éIÚâ·E«íSÛø,[žà!r²_”žd pùã?¤$=\zªðqu¸iõ&ý”ñ:î§qhëÝgÎçB;X6}™ÙÇÏ\ÄæU›!Ce>QhIÞ'&wh×!³¬2÷ȧ’d‘ é ÊS8Z8"ðh n]3ï…’½¯Â¾X¥VU¾ަI2®÷âÙ uZ–b—$A›i’E7d9vIÖzø"ê+C÷ZVm©æZ™ÞOæ‹IÒƒeÓÏxL P€ (@—î’\g›¥6Á•¹6² ®,÷½|Ör5d¬úgÕ•îäG¥h™¢*o¿¶ýоO{õÓ#ûKÉþA–½8úªÅÓ#~ÂøªWEö”’ s·­Ù†~_õCífµÕuk—¬U+ÝÉ"ÕêV ó§Rî5²ïH<~ôXm,C彿üùK˜×KYůQÙFjwY™P”áz£­ zë©Ê‘ž¶.» ûþ}°:è¬Vû“€H–û–õBK ¿lˆ™cgbÞÄyˆ— $h•ÞŸ…“ª EVÝ“$=A«æ®RËš¿|ñR푵wë^L5i<Ò¨àM†ñ•®RZõþýW_ Œ7¯Þ¬öúvà·ðÈâ %°’äÕÈK½ò(@ P€ L\j#]ÙïHß´UGáaÝ›wWCÌôsòÚä«&j3Yw÷ç÷H€"{'Í3Óè ‘9:#fÀgÅ>ƒ\²×’$ÉÛ©~'õGºÌÿ‘ý“d~“ì÷ôs—Ÿ±dÚ•O¾IOOãvÑéÇNï Ë32ýs éʪyßþï[U ø$Éf°2MÊÑ“¾‘®œ8a ~Úx•%ÄeÁÙ8XO˜H@%‹,˜&ióCŒ¹L²Ä¸%ã0þ§ñªM+­TÙÛÖj«ÄÝ·wÁ• 7”tõ%Ë%£,>!K¨ËBIâÕçË>X:}©z/ßdsáÿMøŸZèC6;–v/Û¿ y‹ä_ '¬æÀé7••¿î÷5¾úþ+ý”C¼r#]‡x ¬(à@öÜ$So¦qn¤«“ð•p¦t]"€ ëIËïÒ‹$=I’',eÚ08 ‚N; Yµ-[îlïõ>™ÞO†ÉÉœ©4éÓ˜žVAˉƒ'Ô8&ÖÂúÅz%ÙëׯվR¯_½VKp‡VgýzË×W¯^©¶Hû%¸,R¶HˆÃ×d…@R'í–eÀCZÌÂòú{™Wuæøµ0‡Ì?‹[[]Ï"É|2é “@.sŽÌƧ2ßJVD”€ÕtxÝõ•àñìñ³êy¤NŸ¹ æ66=6nî  à!°  €C ÁöÿáöJÆ=@Ù‹˜åº(¶þNàÛ@r3‹­¦”­×0_ô`ýŸ![@ D®€þw†üGP{%ã  ìEl”ûèÉ+øm½Œ™’ xTêü΃7qùÆÔ­–qãĹ˰i÷5T.•92'1®Ê¹ÿý‡/Qÿ“àyñQyïè~/g  bD÷‡ÁúS€ (@ Dok·ž¢i÷Møsñ)£!¿Í PçTýO²¢dÁÔïñîÝß8tâ.Ök÷>v:HÕQî/u•:[¦…kÎió¬®ãâµÇÈ”.!šzç@åÒé-³½÷~ÎÊ3¸~û*•Lgµï]ÀÑZ€T´~|¬<(@ P€ˆzuþWЬÇ&i *¤IO½.^wcgÇÁ¥õ6U|U©å.¢AçõÚ ºn*йôcfÀMûtê/•кžg¸+/‹M´ÿÑ_Û†ä Ö–i÷øuÚQ ïY ][4Ê{ñò ªµöÅŽC·TS% Þ¼ý…s§À–YŸ"I¢àíjž¿xƒ–½6CÚK«kÊdq±nûUL^t :ÃÚWH©ï¨½øåèS%3¾m™?¤l<ïDï‡ÞNÔ86… (@ P r>~Ÿï6áÙó7Ø8Ý7w´À ÿæèõe!ܸó ½ÝkܰÃHàrÒ¯V7Ä­-±cnmõùcùÂs0Ï÷º´*€G>W÷Þ»¨.R$ƒžÃ÷àô…FQ3–VÁSÏ6Z½¶7DZU ñpÿçøL[Õïð©{Xú×#ïH-“à©Ö“uww+\×Ú³]«gVD8þÎ_ydä5=è¥ÝS‚§úgŢߪÃÝ=¦éÇÖ–Púç„Mûå#Õ+%ùËiõìöyh£Õ°>£àºÙ…aSŽ ‰WvÌYÕê@ËkøÞ9ì>„Oß¿Æ9¸Ø P€ (àÚ{þYF¼Bñ´fÒûrö¯&Z óN—¹P¦×RDz_×Í;ÏÕÜ¢ÝGnáÐÉ»Æy³Blx#ûBY& ä¤GKŠäóÎÚp:ù’$½e—®?Æ…«1jú1uNßCìäùxüôµ ¶&ˆ­>Ó¿ul–-ëæB“ž%¹®Ë/;!ˬË<©ÙÃ+kCÙ'¡›¹Â«Ý(///øùù¹‚!ÛH(ðööŽ’ûð& (@ÐÎý3œ­@®äïe“Ótáê#ü1ÿ$¦,ÄÝ/ÔG‰´ Å=¶ u{cšÕæãŒiß_|¢”¶€„Ì]’ý¤ô$s¤Vl¼ˆßçžÀæ=סo¹œÅ#¡žE½Ê½’ z¾ßž1Ü8að<)•IûöH ¶$xJªÍŸ’^´9«Îª Kÿœ¯Î/`þS‰íõõõÄÒX”½²e˦Š>þ¼½nÁr)@ P€p"YÙN‹+´mUôÄzã´UïŠ×_ŠX±bÀçSm5»Ré‘+KRäË™ µ;¬Ã_;®BZg½ëgoÞ}þÞWn>QCødA =µþ~‹ nJH…¡=J¡pžÈ« )”ÕË6]aÜ[Ú#IëX²9uÑz¶¤wª`íÅè¦ å«Q!#R›ÜÛæ‚˜1Z °¿1Z>6Vš (@ |\Y’¨yAþûo¼WY¯Y÷꼬`÷òõ;Lþ©"þXM´%Á‹æK©†Ã]Õžˆ¦SÚ;Ëä¿ÿ¦:•=Sð\«ûÚê€Ò3”7{Rì˜Wßió°ªks§<´¥Ó¯šôRÉE¹²&Q×nÙ{]½š~Û¶ï>m¿«6]2N'ÖzÐFõ)‹œšCŸöEÔ|°ÎƒvŸóÀù@9ÿ3f )@ P€ @¤ |Z9³*k­ÿU³27kû'ý±à¤šo$ÜûgÈ^²$æûBɪ炃 }’YAa¼™ï{²ºiZ³í²zÛ¼vNõªß;QwÕ¦ç•a}3—ŸVoõ{ôL¡æ2ÑV满íådšúÿ¶«·\FÆtÿ4í5“•=µlÁšóX© dr » ás >¶’ (@ ¸–À— s«½œ&Î?ñcAVÅÛ~à¦Ú3Iæ!ɪ{’>*™^õõ±/^¾ÕæÅÆ–=70jÆQdø§'hýŽk¨bÃFµ¦Â²àÃ'mü õÈœ>–®¿ ˜OÊg@…âéTVYõOæJí9zIÐwâì}ÌЂ§óW«<ǵ÷²ìy®¬I1¸[Iµ4{µÏW£oû¢Z/Y L[zÛ´ž-ïJ™´á)M«`ËÂ~¬€*­W£ãÿ¶ké¦7ö–22ñÀé@9Ý#eƒ(@ P€ €ýdNÓ¶9µÕP½SB¾$IÀ"ËyË2å’Ú4ðÄÎC7U òY§¿Ô¹ô©ãcj_“nP»ã:µñnÜ8¶ïŸôûå1aÞ t¸ÃXBöoš1¤²º‡|“Ť. »lÀ  ‡Ô—wõªgÅŠß?AÓnUÏ’ä“÷Í´9ZÒ#Õ~€¿ ¤¤ Ùì·Y­׿œ¼ 1UÖÀZÏ׬•g´½¨v«áŠ!fæN!à¦ý°„cÊœS´™0à"&<¤(@h# £²çŸ1Æ=Nµ‹6.Q]ÑÛ÷žãø™ûH“2rfNbu/$éå ¼ðy´ùH9´TÖÒÓg¯±ïص@DÉ‚©´³—)?yî¾¶÷S\³ÅdˆŸôLIÝ<µž). aM5bçÜrORÚówÖ´fÆï¯BP¦Ò.xÌÊ:›L PÀ ìùÇ‘Îc܃”NÂW DXÀ™(."á^H P€ (@ ¸š(W{âl/(@ P€ (aP¦ã… (@ P€ €« 0€rµ'ÎöR€ (@ P€`a:^H P€ (@ ¸š(W{âl/(@ P€ (aP¦ã… (@ P€ €« 0€rµ'ÎöR€ (@ P€`a:^H P€ (@ ¸š(W{âl/(@ P€ (aP¦ã… (@ P€ €« 0€rµ'ÎöR€ (@ P€ˆá+y!(@ P€p·Ü“\ •l"(`«{ l•b> P€ \JÀËËË¥ÚËÆRÀÞÞÞÞö¾E””ïö·–¢äN¼‰C dË–MÕëüùóY?VŠ (`MÀÍÍMæŸ1Ötxްç¿ìròŸ¯§OŸþçFFÿ¹,€ (@ P€ ÀÊ‚=« =KË—/ð-äZöNE˜R€ (@ 8™('{ –Í)P &Nœ¡ J‚'¹VÊ`¢(@ P€ P.ðSðÙgŸ¡Gá ¢$x’käZ& P€ (@ P X€” ü$H$éºwïnS%Á“ä•k@¹À›H P€ (`³(›©¢oÆÔ©S£|ùò•ŠÂ ¢ôàIòÊ5r-(@ P€ (,ÀÊE~4h ZZeVŠ\ÀžÿF°êƒ?Þ¨¯€åžPz ¸÷“.ÁW P€ (@ X`eÝÅéÏZªgíœÓC° (@ P€‡¨p`9SVË=¡¸÷“3=]¶… (@ PÀ^  ì% Ê5íq2=ŽUg)@ P€ (ðA¸ˆÄawŒ›Þ¾}eË–U•Ù¹s'—/wŒÇÂZP€ € öœ nÃí™…pp{þËÁÛÎêÙQ@ßJnÁ½ŸìÍ¢)@ P€ œF€”Ó<ʈ5„C÷"æÆ«(@ P€ \SÀn”··7üüü\S•­¦€¼¼¼àëëk‡’Y$(@ P€ €­v›¥;´µ"ÌG „-À #Ã6b PÀ5dó÷'Ož¨Má&Lèf+)@›Þ¼yƒØ±c#Nœ8xñâ…Mׄ'“Ýz ôJ¾ ÔùJ DPÀ3¦g¯äe œS }úô8}ú4®]»OOþéœO™­¢@Ä?~¬.L AÄ ã*.c?¦(@ PÀñòæÍ«*µmÛ6Ç«kD |PÇ«ûgÉ’Å.õ`eVJ P€ €=êÔ©£Š_°`=oò)@h( Ï¯Zµª]jo÷9PÂg—çÆB]L@ÂÇ9P.öàÙ\ P Dû÷ï#kÖ¬xøð!¶lÙ‚J•*…˜—P€®#éy’a|ûöíCñâÅ#½ñìŠtRH P€ €½’%K†^½z©Û´mÛP1Q€èر£ ždEp{O"ÌŠ?g (@ DK®]»ª?Μ9ÒÇ *Z>FVš‘" £t¾ÿþ{ȰÞ$I’`Ô¨Q‘R®µB@YSá9 P€ ^ nܸX¾|9<<<àïïR¥JaëÖ­_oVˆ\Y³Aƒ2dbÅŠ…Ù³g#gΜ‘{“Ò8ʃ‡pTÎrÔ'ÃzQ€Ž <Õ­[û÷ïWÕ‘‰ã7FÅŠUpÅ}¢á)±ˆ<ׯ_ãÆ8vì˜ú(sæÌÁóçÏUÏÓ¢E‹P½zõÈ»™•’@YAá) 8š(G{"¬(àh²Y¦ Ù:t¨ZXÂÑêÇúP€öpssC“&M0xð`dΜÙ~7ú§dPv'æ (ðß@ýwC–@ ¸†€ÌƒZ±b…ú:qâ®_¿Ž'Ož¸FãÙJ ¸ˆ@ìØ±‘.]:,}òÉ'¨_¿>rçÎe­geÔ¼".À*âv¼’ (@ D¦‘ˆLM–E P€ (@ 8µ(§~¼l(@ P€ (™  "S“eQ€ (@ P€N-ÀÊ©oôiÜ“GOà;ßÇö‹>•fM)@ P€ \N€T>òuKÖaõ¼ÕQxÇès«[×n¡›O7,š²(úTš5¥(@ P€p9X.×âØàßÀã‡Q«i­X Þš (@ P€ˆ¨{ "*Çë(@ P€ (@—`T<ò«¯bí¢µxtÿ^¾x‰?‡ÿ‰<…ó \õrØ´jΟ:/¿û—Î^Â‚É PºriT¬QQÕìþ½ûصqvn܉¸ñâ¢X¹b(Z®(Ò¤O£>—kÖ/[,¨Ù°æ{­Y»x-®^¸Šº-ë"eš”êóW¯^ÁoàÅó(^¾8*Ö¬ˆä)“›]ZÝþþûo5gi÷æÝº„,¹² lÕ²(ÿqy³2äMXmxïž (@ P€ €ƒ 0€Š‚sùìeŒî?¯_¿Vw“ãÆ_5VÔÊ9+±fѤϜ]›vUŸ§Ë¨¬_¾vF̘1‘9gf¨Ì;K夺½ûøÚ×i­~[‘ QdÈ’AySFLA»^íÐý—îF¶´ÁÈÌ P€ (@ 8¸‡ðEÁ*[­,^ SöLH–2™:îÿ[³;÷k×m{¶Å<ÿyðnê­>Ðq’§JŽ5'ÖÀ÷˜/vÝÜ¥>—Ç £ò$I–U>­YÅn׆]êœþmÛÚmê|u+Vp¬Ü­Y7?xÃf Ãþ ýØwo¦ý5 OŸy@И¦ÄIcúúéê”Ì7’ äÚÅk8¼û0N:iœ—5Ië©Ú¸b#ÞüñFõ6=ö›WmFâT &ùönÛ+/¨Ñ $¸1M…JRowmÚ…Fm™~¤‚-Óù‹åWoÇ«æ7U¯[] ýË](7zêmd OŒ‹x@ P€ (@`å 'W\ïÕäÊ…+X0iOY¬É ==±ÝcOÿÍ.Ãódiô™cfb÷¦Ýj(ÝæÕ›!AÔg­>32^<}Q÷jÝË8gyð èå)XÖM† öÕ“‡MƤ!“ÔW¢$‰Ôœ.ŸŽ>(Y©¤Q†­m0.à(@ P€ X€”ƒ>&W¯D=Õ›ô©Ï§(ýQiµÒ ë“v¬ß¡†àéÕÿ¬åg*€’a|²ž ß“@ËtÏ©1‚GlöÚS-Z¡_kúš"u Ó·!·ìÜ>_û¨Å#vlØ-«·@Vü“Í‚Ç/ªµ«"¼mñfü€ (@ P€"ÀÊA„e5þ¹¯_½Æ©CÌ‚ ÉwóêMËìÈ[$¯ª·aù5IVÈ“¡u²È„žd©qÿuþ*°²\ò\¡8¸ó R¤ ;€:°ãÞ¾y«zš$X“¯^ÃzañÔÅjÕ¿¥3–ª*¼mÐëÉW P€ (@ 8ª‘ˆâ'óîí;›îøà^ðPºÄÉ›å—E Î<§ÎÉÜ(Ó$‹IÜ¿{¿tûE_¦Ã÷$_…+¨ì›Vo2½LûiÚz·Å™€3ï}fyB†îµ¬ÚÒX,Bÿ¼H™"êP_X""mÐËâ+(@ P€ Q€T>•ìy²ãáý‡j r „BKú<¢ßPûAÉâ¿ýøZWo4Á›èÊ0¾gOŸÅÈP?¦·|ær¤NŸZÍI2>Ôdõ=YpB6æí÷U?ìݺûý÷cP—A˜6ršZ°ZÝj¦—X=®íS¼ÉU²‘®ÌsÚ°bú|ÙGå÷jä¥^#Ò«7äI P€ (@ 8ˆ‡ðEáƒhܶ1büOãÕ¦¸úÊw֪аMCÚuK§/E§úTÙìö×9¿Â=Ž»Út·CÝX¶™¾'Ò¤OÙsjû_ÛÕÒæzOiù£çÆÏ]~V›çʺ’d Yü¡ÓŒý¢L¯±<–Iæ7M:­ªµ2>–Í|» ê¦Vù““áiƒ\ËD P€ (@GpÓzÌÇERe!Io#©Dç(F¸¯œ¿¢z‘l .œ¾€  ½W™sd6dÎÒÕ‹WÕ¼'k’‘1„ƒÛ7nãÄÁx÷îò΃tÓ…3äÓRÆÙãg!¯Òã•»`nµñ¯åöjƒå}œù½gn˜y@IDATLOÕ<;ýº:3ÛF P€ "U€T¤r²0 ØG€”}\Y*(@ P€¯ç@…WŒù)@ P€ (@—`岞 §(@ P€ Â+À*¼bÌO P€ (@ ¸¬(—}ôl8(@ P€ (^Pác~ P€ (@ PÀe@¹ì£gÃ)@ P€ (@ð 0€ ¯óS€ (@ P€.+ÀÊe=N P€ (@ „W€TxŘŸ (@ P€pYP.ûèÙp P€ (@ P ¼  Â+Æü (@ P€ €Ë 0€rÙGφS€ (@ P€ápû[Ká½È–ünnn¶dc P vúu G ˜• (@ ¸¶€Ýz ¼¼¼\[–­§@$ x{{Gr‰,Ž (@ P ¼vë oE˜ÿÃäË—OÝøøñ㦼+(@ P€ ¢‘@¬hTWVÕÏŸ?·C©,’ (@ P€Î)`·!|ÎÉÅVQ€ (@ P€®,À(W~úl;\@æ}ùùù9x-Y= D™›ìëë=*ËZR€p`Îrà‡UË–-›ºÍùóç£âv¼Â%ÀÕ<ÃÅÅÌS€+y†IÄ  Â`T˜DÌ@ |hkûí²Û‡nïO(ð(έE¢ ›7¢œ^€s œþ³ (@ P€ @d 0€Š,I–C P€ (@ 8½(§Äl (@ P€ (Y  "K’åP€ (@ P€N/ÀÊé1H P€ø?{gUöÅñ£Xˆ]ØÝŠØµ*v­««k×Z×Z»u]cívíîVìV00Q@°ùßsÙ7Î Ìà Lü®Ÿá½wßy÷žû½Ιsî¹   `,0 ŒE퀀€€€X=PV?Å €€€€€€±À€2I´     `õ`@Yýc€     Æ"ÊX$Ñ€€€€€€Õ€eõSŒ‚€€€€‹ (c‘D;     VO”ÕO1     `, ŒÕÚøNàÊ­ótóÎer»w>}þH¹²å£EÊR½êM)^¼xß­øÌ÷Õ :õT´#Ì’1;•-î­œ9ì<´‘%LD.5››ƒ:Ð@@ À€Šèè@Àz |þò‰†OîIÛö¯U 2^¼øöM^—)æL³Ç®¤<9ò«î›ê䉷'¿vŠÊ«Hys4U7²Ý}ǶQHH05­×VÕû½ëÔçïתZ'ë´¶jÊüÿQŠd)a@iÍ!.A@À–À€²¥ÙÆXALJàã§Ôè—ŠäñÈœ –¦‘}§P±ÂeÈ!i2züôÍ_5UV6¤k¯Pòd)LªÏU·óôûø®4iø“P“ÿAï>¼Õ0 ”Á+T†Ú4éª\F8æÊš7B*@@@À\ À€2×™^ GàŸµ3¥ñôSµ&´xêfê¥ "îÂôן«ÉëÙCbÃæïUS¤¥Ü·æc®ìù¨sË^ÖIRY,ÂívÞD7ï^¡/_>Sù’U¨f¥ú”&U:y?(8ˆVnžOß¾}£.È1]&YÏ?ž¿|FûŽm¥ÉSQ¥25hÏÑ-ôöýkúô…®™AEó— ªê¨ä =чë°ëðF*Q¸,9—©®ÑÅé GÈýþ jÓ¸«OZò{õ’¶ŠJçÒÕ©@ž"´ãàºæ~‘’&q r%*SÃÚ-5žç‹°°0:zf]¹yŽÂÄ?~¶rÙZäP  `{`@ÙÞœcÄ & ÀI#>}þ@uE’ˆYsGÚCqÒ÷ðÌûg¯œ ^ÿkC¯ý(k¦œr-ÑþãÛhÙ†¹4oüZªUÙE%¿q÷ :wõ$ÙÅ·£±³QâDI(4ô+m?°Ž–ÿûíXæJÙ³ä¢uÛ—Ðå›gåsGNï¡ãgöSëF]¤õÌÇ‹~ñ³Lr‘\¬ç‰'þmعTI™iõÜ="ü°”h7±0<^Ðba^¾q†VÎÞ¥Òá÷ Ý„x„¦þïz,Ÿwhñ[Œ (}Y<}îI“æ £ž†D0 œÜAk¶.¢Ú•HŠ N–íÚ¦?]ºîJî7(¥0þØè[¾ñ/êÛåô¿¾“UãcãqàØÎ´ûÈ&Y—Ì!-á—?Um,çF%ˆ›$€4æ69ííãã#>„…F¼ñ_Íׯ_éÅ‹òe,¹—/_¿øîÈ ÷kL9ÖÝ××W¾¢ê×9???âWtíé#ÇmøûûËWTí"÷êÕ+â£YáöŒ)Ç}éÓž¾r‘émNõŸ=꺞çÃÇ÷ÔsDkiíYu.îyL×>§ËΣ$„ŒëLo^i õ‹HT1mÑ(š=f…4Ær¬Sùòåå+ªöÔåZ´h¡= ªkn£\¹ròU{1‘‹ª_ÖOé7*9õ~£’S ÈŒOž=,µËš)‡AZ.ýw®0üéW’WªhyÕ³eKT¢!¿ýI¯…ñ´jËBU½rò?þתa'yÉiµ{v"Ïï=tSDt÷Ý*Â×.P­J.24à ùU¥\-Ü},}üô^åya/ÔßÖʰÄÑ3ú £ë:ý9çwi(ͽ\gûº*]/¥¦¿Š¿1:^œ|B)1e¡<Ý1³c6Z6c»*±h»׀¾ Cõ‘—‡|œ¸ÿ¦ "dqá¤)]š ²žSÐOýߢèºÀ} €>˜d}†˜%K*Q¢D¤¢Å‹§7oÞÈû|Y)V¬½~ýZÞæóÈŠ““$W´hÑÈš#n/ @ÞJ®H‘"Ò3‚|YÑW®páÂÒ[Äíðyd¥P¡BÒK\Á‚¥wÌ9~&²’?~é5äûúÊ(P ²æ(_¾|ªö¸íÈŠ¾ryòä!ö~rág,¹¤ý¡c¸*Bÿ¸T,U-£%ÿ3¨î?ºá^Mçúu9²ä&;»Ä¡mQNkÎ¥¡X/õö}øï´"¯pg.£öͺËjçøëhšùÏjÒÕ™‚‚¿ÐŠ™;#¬ÍRÚÐu,”·5©ÛF×-¹?–r#¦,”ç£;:‹õZI'ÑË“#üýþDp+˜·¨ô̽ûð†Z4èA–׈ًuS(  ¶M”mÏ¿jô®®®ªs]'“&MÒU¡N_¹É“¿¯7ˆÐˆZ…±å¦Lù¾h_­›§úÊM:5³º*ô•›6mš®Ç#Ôé+7}úôÏêªÐWnÆŒºP§¯ÜÌ™3#ឨ¨ÆÁálŸ¾| jʬ|¼ŽI1–ÔŸË—«¼|öÂK½Zž§N™V£Ž½H¼.*ªÐO~Àóé}ùÜ€±áÞ+Fþ»xý.üË å^?±FˆC÷|^>늊u^M”[zó‹Ä ý~ù_´²œ¡0&,´ŽŒ63~.A‚ðÿ•}º8!—È<‰™³Êûø  `»`@ÙîÜcä F$ lT{ÍíB”­¾ð{NÍ{T£ôiåZæ¬{œ‚(±Æ³¼w–Õ.vvvÚUz]Ç›úr5`F¤FBºÔáakJƒÇÏÆ_Ÿ¹r\a(·HMnŠÚz<†íul`FWØåòRÌ“®òéóGiä麇:Û €5PV>Ï=ñýüÁ,f…Ÿå6P@¢&À¯±¹rë¿&§ë‰Í{V /Q(UɸðI¼çµÓÄy­—YóD¸ÓŠÜ9ÂC/yݧ=WU¯P—’%M®žÇ ,†Nì.C׆öœ Rž¢c:E™t&¦ºÂB1 ƒB‚"t§xÙ"ÜУ"Oöp>çDfDíÂÉ58Å: €€€m€eåóŸ2eJêß¿ŒŒ(6žøYn@ jœpaƨ¥RhП¿Ð]ÉN]8LóE:ì$b/&^WÄ¥Z…Ÿäñ˜ØÏI½°÷„÷!âÂû0ýHù¦–a“$.œÚ\»ÌY6ž:ô¯O÷º«n ›ô½ ô¥á½' GÉ>6ìxÚ… Ã)†°È›3<¼ñ¶HI®^Nž?DO¼£^¦.¯}^D$–H'<~wÜ"öª—U[HãW½ç  ¶G!|V>çéÒ¥£àà`êÓ§-X°@ïݰÙxâg87· =êëR›&Ýhã®åÔ¨KEr©ÙœŠ(IñãÇ'íÛ'övâ=—¦ý±X$OÈ+ìÔ²­Øô7­Ú¼€’&£ºÕšÐgáåÙ*Ò‘ŸWýÍ©X¡ÒÑw®CBYCÅ{G%m7­ÛVî)USdàã x‡NìAÍë·—úíãrö¹2Å+Q=‘¾›Ë¶ýëèÀ‰íT¢H9ê&öPâº×hU„f/ýSf°ãLv\¸¯#®{D²‰±TSì[¥$¤7õüa Þ 7·ð]ºáJ#¦ô’›Üº{\§µÛþ¡,³‹Í~ŸèÙ«¦XR{Ök ›ÔƒZýVCfBÌ&öÕbÃlášé”ú¿†5ŸÂ€€€-€e³Íé¡ÙÒ׈RŒ§#GŽH£Ëaˆ `43…ªL1gâôÜÛö¯•/¥q6¦f]IEòÏdÉ¡t[Ÿ¤¾£ÚËÍZyÃV¥ðÞNã‡ü¥\|äþx/££®{é÷ñ]寲œ…îŸ)›hÌÌró\Þ@—K‚ ©K«Þôûoãä->¾Þ4jz?Y?sô2id±\Æô™iìàÙ²½þc:ÒµW䦻í›÷^öbˆ´ì11  e±|æj߯ž4šØpJ˜ õè0XnÌYcZ8á— Ï2e{ŸQíd3œ}o…èoöÒñô铿FÈ1íÏ€X&x"L$Ì2U‡Öú ‘û qò:uêhx¢rçÎ-›ñô yaã©wïÞtôèQJ•*]¼x‘&L¨oW£Pô?¿by¦øw‰3º=ôº+7k-˜×‰R§L)þSÌ©´ïŠÐ±T)ÒPaad¥Lž*RyCn¾  `±VˆõÂëyÜî]“™ûŠä/!<7ÙÔo|®Œ!cú,R€Ò˜Ò޾,8dÏOŒ%¯ð‚EÅØXöõÛ@ºî~‘’'K)½€¦i©%K™ðäø/ßRgzƒ˜Pæ4&Ôeܸq´fÍÙƒº¥n@©{žX°S§NÄÏ¡€@\°d*®˜¡_ÐE”.*¨˜@‰˜q³¸§8ŒO)šÇá|l0)EÛxâzõg9A@@@@À– À€²‘Ùwrr¢üùÃÓóò#J>T\§–ågP@@@@@¾€õ…ÕŸµlÙRcŒê“ú9 iËj<ˆ °Q0 lhâ›6mªÊ¤Õ°9å2Ë¢€€€€€h€¥Éê¯x?§êÕ«G;F–ÁÞOÑb‚€€€€€ €ec“®Ob}dl †     ’ ({#Ô®][îïÙ°yï'–AˆHTD&V]Ûâ6nÜ8Ò1ò=lœ)ܰq0 lð Uˆ^T÷l†     `@ià° í=¡”Qcï'…Ž      › (Ý\¬¾V×>Oºê¬    €e,kÕÞ {?YÓìb,    ¦"ÀT £]ó& ì uüøq©(ö~2ïù²uí²”‰gë0~3!”™LD\¨¡ž0Bý<.tAŸ  ‹@ýº.ºªQ  4ˆÁSx@@@›@¼0Q´+qmBBB¨|ùòr°/^DúrÛ˜vŒ@@@@à „ïàYú£ê{Baï'KŸMè    `@Åe3î¡{f<9P @@@@Àì˜Ì€âXëýû÷›Ý€¡X*Ú·oŸ¥ª½A@@@À*˜l T¼xÈšeï ¬`É¢YM”°A&ó@),=B=”SAbH €]>‰Ç@@@@ŒIiÌIm€€€€X5PV=½€€€€€€1 À€2&M´     `Õ`@Yõôbp     Æ$ʘ4Ñ€€€€€€U€eÕÓ‹Á€€€€“ (cÒD[     VM”UO/     `L0 ŒIm€€€€X5PV=½€€€€€€1 À€2&M´     `Õ`@Yõôbp     Æ$ʘ4Ñ€€€€€€U€eÕÓ‹Á€€€€“ (cÒD[     VM”UO/     `L0 ŒIm€€€€X5PV=½€€€€€€1 À€2&M´     `Õ`@Yõôbp     Æ$ʘ4Ñ€€€€€€U€eÕÓ‹Á€€€€“ (cÒD[     VM Uƒ°h 4 ýû÷[ô <˜ Ú·oŸ¹¨=@@Àb Ä ÅÚÇ‹O6ëêaŠæÍ¾Í7oèìá³”3N*RªHœë{íÜ5zñôÕnZ›'IlR}Î?Oï^¿£º-êš´[j¼€]9\ýºš-JåïˆÙ*Å@ÀÂØÚß ›¨  `!à2ÑD=}ô”·Líû´7 jͼ5t`Ë:óü ¥Ï˜ÞD£ováÄ…tçúP&¥l[‡…î¶­c´ `dñì¹E4 ¶Kk L4÷©Ó¦¦†m’Si'õ€fAÀº |ûöͺˆÑ€€X$x L4mÙrg£Yëf™¨u4      `@@ýÉÃ'tdÇÊ’3 ÕoU?“·$ïÇÞÔ´SSⵇ¶¢üNù©Lå2²ntjÿ)zpûåÊŸ‹Ê×(O¥+•VÉìÛ¸^<{A-»µ¤TiR©ê×/\OŸ?~¦æ]šSšôiTõû6í#_o_ê2¨ Åo˜SÑï…íX½ƒîÞ¸K¡¡¡T°XAjÝ£5¥sL§j_91DVyFý¸{ýnòóñ£rÕÊQ±rÅHgç)a„*ÑÏŸ>Óúë)W\T«q-Y¿÷߽ıûìÕã5V®‡\)Ð?J”/!9©?¯j'MÀÐ÷²Eʃ€€X $‘0`ªÞ¾~K•2W’IÎûž§D‰©ž ¦Š+Ê{§Ÿ–k€ZUh%×@™7F%·jî*š1|}ýú•2dÎ ¾Ù¾w{úcîdggGÓ†M£³VÐÜsU†Z€_9gr–íL[5švl*Ï9Ì©B† įm—¶É:]?¶a Ô…hPÛAÒÉ’# …‡I)S§¤kfP5—jª¦ ‘íX³£ÿÕ×WUÏÏ5›OY, ¢¹›æJv,wéÔ%ºþî:%uHª’UÆZ¯e=úkÓ_²¾^¡zÄŒËV+K;×ì”䛀7Äãw*ãDKö.Ñ0*UYɉ­'‘À(+y#cqF@Y…$q6è@ÀŠæ®°¢Çd(lXÔlT“>¼û@çž×hâôÁÓ²¾IÇ&” nÇËLù} •¨XB&sp}æJW¯Kkbï'_àRµ^Uy¼ìzYùJQ?gÏvÕTWnëuüðþ±Qú5”6ŸÛLÇ=“«·+m8½¾†|¥á]†Sà«@Ù–!²º:Ÿ1b†4ž~jþýµù/ ÃS—|duÏŸ<§SûNICñüËótÁïµø¥¹]q£9£çDöêA@@@@Àh`@ˆRñüÜvPãI£ãÒ¼sszõ‹iC§ÉË‘³Gª2á%O™\z{R¤JA[Wl••2UÊC2º|ê»uùôeâÄ¥œKÑÅ“UÍž?nÈU«ÿÝ[¤ºÅÉ깫éõ«×Ôy@g*^¾¸J’C ûëG¯^Ó†…d½!²ª†þ;™:d*-›±L‰sþ£ª§-«Ïõèy£©hé¢R” Úq ÆQÖ\Yiëò­Ä©ãQ,ŸÀ§OŸÈ×××ò‚€€€X%PNkÕúUe¨Ø±]Çd?ÎkvNì9!CÉòɧ³E6HÞyHy ç•ávïÞ¼#åõéÃ'*U©½ô~Iï?–FFÅÚé¾û}•QÀF‡¯9×v–ë¬x^”:]j*Z&ܨÐÙ¹ŽÊnÈZ^¤]ƒŠõåbˆ¬Ò‡‰L4‰VÎYI™²e¢™kgFê™SžÑçÈ\ÔK¢Ä‰¤QÉ¡|O¦TSîBá¡w£ Õ+¨î9× ßŠëc¾×¶W[™Ì‚7¬5r–LÁ­]GÐÙ#g©N³:ªlw†Èª”T;áý²Æ-'kÆõGïß¾—ç¥*–’ÇQÝGÑ–å[ä«GÃr¼JÈ£øï×Íþc6Í3—x_*Þ¸cŽrƒÝÉË&«‹â¬žÀùó÷¨ë¯óèøñ[q:Ö´i“S»¶U©Lé¼&ÕC×xc«o“ ƒ€X§±™(ì˜ÙQfÄ;søŒÜÔV G‹J=NذòÐJ¹ÇÒ°Îß%öž,Ú¹(‚1ÄûEqâ68Ô=PŠ™÷ئ¾ÙmT}kßc£f݉uô{‡ßiÉ´%ò¥È´ù­ÜÔW¹6DVyFûÈë·šth" Çéçӄ&Pƒ6 èÖå[r=Ù¨£ä#É›â6+1!gð繞jѤERžC9•y‰ %´»ÄµàŒ{ì}…÷É‚&í?UsçÎHë× ‰Åã²ï80:0 ñDÈV˜)4QB¸ô¡%b­UÍÅä¶m;GNN9嘸ٳw©wá¼_¿†"Óbxò}ØD6Þ>“zßêíÚuŽŸ¸EøP®\ŽôS’Ô¤ÉwO>Ëò³Ïžù ÞMèÒ¥û´ÿÀâ°Àbb,­ZU¢œ9Õ›´èóxvá!ÙÖø7Ä¢'ʃX$P9m¶§´ºe{£'²fÊËË‹(C† ”-[6éµêË—`ª]g4=w—3¤¢ôéSHCä«È‚Y¢x.:yb2¥Lé Ç]£æH:yÊ>¼ÛLIT,üüÞc¦NÔªe%Ú¼i8U«þ?:{ö.… ÃÌN„¶ÚÙÅ'ïg+‰×@±ÅrW®<¤Ç^¾”6Mr ‰¸ü9¶ÓFÕî a`´i;ƒüDò‰œ92PpðWòUšÔÉhíšÁäâRF%ÛºÍtÚ¼å múw¨|†¿y›7·;U¨PPöÙWpÏûx¼ö-UÏé: ð_Oi„^ú²‰n¼JßÜWPPýÚýoZ'ŒL{a¤±—êþýçò5”šãsÓÆa”8qB©Vƒ†ÒIa`NÒ™ ^.Œº„"¹N( Y2§!×ÓS…ñ•Q×,®”ÅM0cXeÆ“Õ@À¼ÿžØPrt´žoûÕç½;l< ž›>«ÉíÖ|zûf5kZnÜ|LÛ·ŸW×ëüÔÉ)´zÕ@);ï¯ôe»0ÌRªžÝ²õ¬ð å¦KfÑ+a¬Ü»³2ˆû“§l– ¾ÿ‰Ø(bƒá¹ôØs=÷^Eg„ÑJ»Ì¡W¯Þ©ÚTNº÷˜OƵ ³®Ó¨mÛjJµêÈž¥gOVh¼žz-§F ËJ™ŽªKã‰/ôeÝxU‹“9svIã©KçZ‚óFrwcÞ©}»j´k÷Eš2u‹º8}úL£F¯£ËûÓ‡÷[èÝÛMÔó·zôÜ'Ö¬9¡!‹ `0 ð>ˆSyòä¡iˆ×ÚYcaHíZÅ¥¥xÔ’&MLì5árÿÁs£;{¶t´níïT¶l>ÙvY©–Ð=+áYûæÎÝMþÂ@8 1•/"Ê•*¦?ǵ£WïiáÂýtk"BÙcãì\ˆÒ¥Óù2kÖt¤þâP¹="œ°‚ègé’~ª6͆Cú¦NÛJI„‡iñ?½Uaöö‰iÕÊÒ³6uêVái QéÀ'S&w¦NjʺDbO¿!¿‡¯ÁäpKÐ&ð=ø^û®AÀŒ,Þ³X®Ï0#• Š‘$MšÔH-™g3ýû7"~qáµLOžøÑãǾ4gnø:!S¬BeƒHYc¤PÉ—/|£i^ãS¤H:áž¼U­ZQEDu,_>|-Úí;OUuÊIÇ5”S½Ž‡]£!CWRÖ,iiÇö‘ªð9~ØØlîÜyFoß}¢Õ„A¦§(™ •™½!×9*ô=\´~ýðŒ Š,‡ý%a‘žž¾JŽ    "J…'æL GÞæ¬tH pˆÜ®]iá¢ýÄkŽxí^s¤oùöMyJ¿'²cE»$øoãj¥­‡_HOº÷IyF1.¼Ä*íÂÉ"ô-ìíâ0ÁĉЮ£(cFÍÄÆ`£® ‰ 'ÌÐU Ê* (^+¦Œ‘åÒ¦Õô¤±§×•q‚ Ð&J›®Ab…gSBÚb¥Ã8ê¤Ë/siý†STN„ÓM›Ú…J”ÈE… g§§Oýɹò0Á zÅX–‹¾Ô$Ð/:;T$²`#FIª h™ë¸8:¦Rª >¾~ý5ž@ï„Gˆ7pÆAíb 6ÚmòupÈW]Õ2#ßÐK(    /Pú’‚€€ÑpÖ½'OžPÞ¼y)E ÍoÿÖ‰4ÄFO…E¸ØÙ3ÓÅ^Wv*­Î‰ÄÚEù ÏkƒÔ³ðq9c—|ù2Óá±9}ú6Õ¯_Z£ù ·ŸÈ“'<ìO㦜ʜ=OÜþ˜Ñ­EJðÊž2”M„tTð˜¸az“&vÔ`ãóâÅû².OëȬ§1@\€€Ä|ík¨Ñ€à²>>>òhí¨€€ð,vÉ“ÛkOìõY³6<Ûú(%¬ì†Ø“I),»tÙaå2‘•˜Þ‰ ï}¤^x~öí¿,«:´¯®~KïsN ÎkZ4¯HãDêt]ÅP6JQ·hÑì”I„ ^½úˆ8õ»z9þ²WLdTÒÆ«ßÇ9€€èK(}IA@À(8m9o&ÍÉ#’'On”6͵NFMd¤»(6i=føð^Nlü”V¯9.¼”jóæ·ìaÊŸ?‹Ø· ÍæþÚ}>ý1²•¼Ï{/±,ï÷¤^ckÅÊ£bb{‘R¼ªúíhÏ{õªOÏß+7èM–,‰Ü#‰“\°awXløÛ\lºËI -›7»Êv92‘ •—Fh¢_߆Ä^ CØè3^ζ7qBê&öª^c$MßAösS¤‹>b5%›Ó6‚>¨CÀ€2„dA~˜€ƒƒƒØ³(½|ýpcfÞ@|aôl϶úyMœ´Y¾8»[óæÎ2©BÛv3hï¾Ë?~H‰MrcR<<Âà 9íwº ïÅáv†°Ñw¼]»Ö‘ëºZF-žªêž7Æ=urrŒŒBU#8A ž×0,½“žØ”ÐÐðXz=ƒ€€ìÂ÷é1ѯ«ŽÍ£Jù;ºÛ<Š¡?~¡ËÂ0âù+W.¿Æú¦»wŸ‰,pÉ)C†ï x?#7·'ƒ“” Ì* ,M Çk¦2gŽ˜yO].²sÖ‰=\·nyÉ n‹ÏI©R%‹LÜèõ†²Ñw¼¼QðÍ›^äëû†8´ ¶¨8}`fÖ`<»ÆR#[ûbfÓu@¬„ (+™H ú À€²lʺß%€e ³A,…@ä_kZÊ '€€€€€ÄP±Ý€€­ðöö¦ÇÛ:Œ@@@À ‰„…O ÔK ðéÓ'±Å—ìì¾ïƒd zCGÐ&”6\ƒ€¿¿¿l“³ï¡€€€€€%€eɳÝAÀB$I’Ddžs GGG Ñj‚€€€€náÓ͵ F$À†Œ'#ES   qF¨8CŽA@@@@, (K›1è      gÂgèÑ1X7°°0zñâ¥Nšìííh°Ê& ?Ô#€ÊÑ€@D/_¾””’/¢Dô5‰'Œ^    ‹àŠEØè l…{ŸüüüäpÓ¤Iãa…ÈgÃBwǸ < @/.Þ  `<&7  Ø0ž¶h @À"|ûö؈J‘"%K–Ì"t†’     “Pú(ë"`ggGNNN?>¢„­kf1“P¡   ðƒ,Ñ“ËF €€€X|=lm3Šñ€€€€€˜Œ (“¡Eà `{¾~ýJïß¿·½cÄ   6C”ÍL5 ¦'ðèÑ#ºÿ>}ùòÅô¡ˆ0 â:ºk$ðáÃâW‚ (Q¢DÖ8DŒ @@@Þ F!ÀÆ— 2 ûžQˆ¢s$`ò,|æ8hè `|l8%I’„R¥JeüÆÑ"€€€˜ x Ìd" X:ÞóÉZŒ§|héÒCô䉟¥O‹Åèøž6n´D¬Ñª)ÚªW¯´” xGǎݤ£âeoŸˆ* *U*D™3§Uµ³~ýIڻﲼ>rô:}üø…úôi@I“&–u/^ÒêÕÇéú O ¥âÅrQuÉÑ1µª îgÅŠ£äì\Ê•ËOÛ·Ÿ§cÇoÒ’Å}¥ÌÓ§þ´xÉA©kòäöT´HêÖ­¥NLÕ†®?¿7´mÛ9rrÊI•+‡óäëgÏüiàÀ&téÒ}ÚàŠ0Ö_ŠyÊI­ZU¢œ9#4õüy€ÐéyÜN+óYJ¤×ÿLgÎܡڵKPÆŒ©)º1p£úγ¾ãÕW.€P  `vâ‰ð“Äß(¨ÖúµöŽ ØC4ѯkŒñ¹»»SPP*TH|Oãv"{Pù;º;2‘ª?yÒš6›Doß}¢¤Âèþ*ö°J@-[V¢5kOÐŽm#©iÓï†Ìܹ»hØðUôõk¨0LÒO ñÐ>½]诹ÝÉÎÎNêóùsuê<‡¶  ]|J—.½ô}#ïÓ–ÆŽm+σ‚Bè×îÓ:aØØ'IDì…¹/>ø‡ˆö›4.O›6£Ä‰JÙÖm¦Óæ-ghÓ¿C©MÛ²ßy¢Ï~ýÑΨe«©¢ÿø”/_fò÷K~âÅfíŠåý©K—Ú²jÕÿGgÏÞ¥ÐoßÈN$ý`yïg+)}ú”tBzÜ.?—3GÉÂGTi„á³vÍ`rq)#Û¸{÷.Ú‡† CŽÃ¯\}(e^m Ó§Ý©n½±ôEŒ‹KŸ—HÄ‘C‡þI d•mèúqùò*Wáwê+ º¿çý&E4ü“xަNéLƒ/YJöAbž²þ®§§ ƒ3£ª96 ™S€HH‘XÌ#Ïg²döÔ¡CuZôÏ:zxÕªUœ¢7¦ï<ë;^}åT1ÁI<»Æ²Usûb‚¡¢I09„ð ñ“‡Ohâ Äk|}|iãâô{‡ßiÖ³è¼xMËf,£«g¯Êoï÷mÚG£~¥!L;×'Ê{|ø*PCF¹ðöò¦õ ×ÓŸ}ÿ”ýòµv ð ú]9sEûíß¼ŸÆôCÝêw“}]8q!‚Ì7ñËýª;-žºXŽkŬtóâM©¿"ÌÆÓ‰}'äå¹£çä?ú¬Ü–G7Z2m‰4²N\(hüwáóÔ‡fšM}[ö¥á¿ §å3—ÓÛ×ou‰¢. dÊ”IxV²›Äx2õ°Þ¼ù@›L$þ@Ɇһ·›èí›MÔ[ClÑÖÍ#èô©)ò^ï>ÿHoäÃû‹éÞÝE²Cþ¤'Â+5Á>çõ½øô9˜F^' Áï·Èyêù[=z.Œ×5k¾ÏÓÛ·åØxݽs½y½QÎb¡a‡Ñ¡m‡ä7ç©Ó¥¦3‡Ï÷ÕwL_ê7¶Ÿl#èKu©Ó…®»Fi3¤¥4éÓÐÍdˆOÁâi݉ur¬›–n¢kg¯ÉgŽï9N§öŸ¢æ]š“}R{Y·jî*š1|†øù+eÈœØà¢±Dì©úcîªoï/Ÿ¾L]ëu¥à `Ê•?—<î\³“Ö-XGË.§ÜrËöð#î ¤Mû=<,îµ1Lƒùó÷Ñ{Ú5wö¯*/SÒ¤v4czWru½CEȘz2t¥¼œ#ä9 ŒKÊ”Ò;sèÐ5Ö6ft7mú6J›&9­\1@z@X–ÃüjBý,‘ž› RÒÔi[)‰ð0-þ§7%LþçØÞ>1­Z9¸JS§n¥‘ÿk%¼bá^(n§I“òÒ#Ãç\Ø,R$;5p)+=Xáµ$Bì ÉP8WºÆž Å“¥ÜW?λ›ü…4~\;*_>ÜÛÉ÷Yç?EÝà!Ë¥Á4fLÕcDøßžÝ£U}²ñÅžGÇTò¥þôSIš&ïÜy&CÙ£¥n ±l‚vTZd£ãP5^ó£^:vÐÔ7UªdtôÈD$Œ3ö°ð:&δÇÞ-% »¶çE½=>?ឬªV­¨ö-aPå—u<~õâ,ôæC¥°Î%Jä¢/_“Kƒ?éßO¯kâÂ3^»ÓR¿~)G¹_ôôôUÕ_ºnð²Á¦^ØpkÖ¬‚z•ê\{ †Ì³¾ãÕWN¥N@@Ìž (#OQ¾"ùhøŒáÒ#Ãÿq—®TšFÌ!=Ckæ­ÑèíÓÇO´x÷bb†ŸãrpëAWÍ¥5éÐDÜŽs-géb#jÿ¦ýR–5¾ðç¹Î‰×_°·gøôáT¬\Ä…êò¡ÿ~|xÿ–N_J©Ò¤¢)+¦hèûË _ä±óÇÏKiöU¬UQPÊ·ÈÜO‡>ä}[Œ®LnXœ=’ÒgL/ÅÙC7cÍ J‘*m]±•8L½Sî>’ui¿{7*ÿT™†LB™³eŽ®+ܽ°a’Ì! ¥ž"í’5ë÷÷ßcÈ ©Ó¶‹ðR3ðf°½²ÅŠå”Gõ¼ÙpŠI¥7èáÃp¹jU#-üL¡BY壜€B½p’íòøñKñ¿Õ”Á±eÎÚ…œ+“¡{¼žKŸÂº°'LÝû¤ /^Μ”*Õ1k–tªsõí12ÏÜŽ¾ãÕWN]7œƒ€˜/ýþw5_ýÍN³rÕʉo5±V¨þí§ç=ÍM9KV,IÙr‡8Qréô%yZ¯e=éaRêùX¼|qyɆ ‡*N“ŽMÔÅäy½õèÖ¥[ê• Ï»žÒøbÃÈ!™ƒR-ìÁb/Z¢Ä‰äu§þˆ_\Øcæóć¼{Óʹá!MÑ}»ÍaŒï<¤¼…óÊñ²çL½”ªTŠNî;Iï?–™û •($Ó¯woÐÚõl'7ì>¬»úc8#>|™ÑžQŽ9,rí“‚M|—kxØ‹Ä^õÂÙéÔKüøBX”²eòÒÐ!‘{R8ÃãÇá†Ft¿JûÁ!_•S#gäãÂ!qQw÷'T¦ì éµjß¾:Õ¨îDùEëkÜd>rC¯ð¹ÐPþ#4B¨Ÿ¾z°Žœið XóÄ™ðvíº(2Þ ƒ¯Ñ¨1ëhÏÞKtöÌ4U¨nTcÒ¾ÇÆRt…==l0*‰<Ô彟¿R¿ŒôÜyæFô¯¾r‘*†  fE@ó“¾Y©f™ÊäÈñ[Ù¬¹²Šõ 饷f(N~§ðÐõ‘zÝ÷’—û W¯Ö8çMq¹ðÞNI’J/’†€¸È˜õ{hö=¾æg¹,VPÕð·ä¼öJ)ì:¶ë˜LB¡ž`"KŽ,ŠH”GeLlD•M[6RÙ·oå½ÛÐèßFÓÙ#géꙫ²Ž×Z±QÈkÌØs…wØxúôé“̾gŠÌ{±5262ž>{E§N¹GH~ÏÃ[C –åÂÆQ ‘‚GÔ˵kd¸§êVdOžtW‘çœmÆÌÔ£»ð: c‹ËÑ£7hÒÄŽò\ùÁÆ×Å‹á!iyòDý»¼tÙ!êÇë­”¤J;ÞÞÊi”GÖåðB>}›ê×/­!{áBx&Õ˜ ›6Œ2çЮÆÞ.ÆÂ!öèpœzáðÀ¨Šú¢Ï·ä#~A{6ì!§²N4têPb{“xMRÖjÍ”@IDAT›Êm¢ýv[“S'ê6¤[¤jåÌ—SÞËœ=³\óäûÜ—Žî:JçŽ#׃®4wÌ\:¾÷8m<³Qç·Ì‘6ŒF#ðñãGi<ñ©REí1Z§&j¨žØˆ÷LÚ/’5¨ï¹ÄéV­:¦Ñ+Fì}º|å¡\_¤¾ïÓK±æ§jµ"tÌQfÀãнìÙÒÑÍ›åz¤L™Ò¨Ú=f=v½MÆ·)½³P&‘ŒâêÕpã+C†ïKê?·oø¾Q„w+ªòüy ÕkŸzõ¬O ôR‰:ˆ0I6îÞó–awªF>©Z¥¨ðr]–Ùþ&Œ/æ.xÿ'ö‚éS ™g}Ç«¯œ>úA@@À<À€2ò<<¸ý B‹Jè^®Ñóš3Nr=ä*3éÕoU_£-δ'3áý·6ˆe_<{A—N]¢ÚMjkÈzzh† jÜü,Îâ§]8 Þ²™Ë¨u÷ÖTºrii<å)”G.êቬ‹>Eé‹Ãþx½—bP)ÏÞ¾v›8ÍzŠÔ)è•ï+¾WÀ©9fq”ú8K„-ʵa‰ÌX—çLiGÓ`ɽN3fŒÖp6Æi¹o߆´pÑ~¹!­ƒCb±ïRz'öƒâ´ßº2ÖñÞD‡ÊTÙ¼/Qa‘PÂCxªØ(âtÞ3¦ÿ"ãg9k\û޳¨vÑôÇÈŸe{+W•ÆS±ŸR‰¹¥ìÄ ¨›Øªz‘¨ê@ìmbÃkøˆÕr=ÏX±gTt¥z5'Z¿á”\õåKˆXceO'…W“Hdž oNwD„ñÕ¬YŒØ˜QÖ4­XyT&¹hÛ¶*õêUŸþž¿—ˆTãÉ’%‘{Pñ˜8;‡6oVQ&µˆJ—âÅsŠMs³ÓJÑn‘ä7°å@ÎP¸Eì_Å›ß*}GÕNLïõïߨÇë­x,犅ä†ÀkÅ8Å;§=×§è;ÏúŽ—Ð¸ä¢Ï˜!  `Í8Þ…´œ¾[ ±Sn¯˜³Bžòš§èJ•ŸªHö´h—ùæ¯ zàn¤U­[UŠœ:pJC”DìXµC£Nû‚ öˆq=¿š‹ÔÙÓÃk’øþ›€ðpA‡äk»8¬÷¦â¢ëÛíob=…R8E;{Ÿx-ÔõóוjyôéOí«µ'N2ÁÆ{xŒ‹&/ÒãPE6ª¸èZã !Œ “H”(‘Ü47uêÔ&ë#¶fCçÄñÉTFd»›#R`W¯9’7(ÃÏþYÔ;‚œ`áð¡ñâý.2÷‰MrK‹uGœ(ÛÙ%öR÷Ü´û8­›Ï>û±!Õòç©´ÿj'ŒÞ”V)]»Ö¡Å¢/NÁ2%K¤.]ÿ¿ ñÅžM“£5Z¸nÝêÐ/]jÑÍ[^Ô¬Ådª%Œ¶åËІõChù²þ”:•ƒoÐË…³6©ÐyíT×_çI£‘³ž<1™ªT)"R«o£Š•†ÉvV¯9N¼çÒ¿†Èg£úÁ¿—ÿn*C‡ [INÅûÉñŒ¹†Ê–ÍG[6ðåITízÇpFî% £©ó/sióæ3ô›Ð¿Ÿ0–¹pZó芾ó¬ïxõ•‹N/Ü0ñć_ñqÀøEÉÖæêaüÆÍ°Å}÷Ñàöƒå‡û¢eŠÊäŽY¥÷fÛÊmÄYõ–ìY"5ç,s.E]¨uÖ4~Ñø£éÞ°;>pšZýÚŠ·k,?tÜvÖþ½–J9—¢µ'ÖJcƒ³ãÕ/R_&tè2° Õj\K¦8Ÿ7nžôèøùø§7¯Ó´ݺ|‹ZUh%÷3oŒì“Ãò†tBì]ê5²—L±mÕ6¹“¢/‡úÕÈUC®ßêõG/ªÙ°¦L±cÍ™æœ=`ÕT§3GÈ=›Ø›Ô¼lsâ5Kúu¢†mRâ$‰efÁŸ–û?q6=|ìñX†åqºô%{—PµúÕäÞRMJ6!Þ˜xЄAT¹ne ßb³WnÖÈYÒˆÚym§I?ˆE˜3¨(`n<šè×Õ F¨[åïHXènÝF¨å÷8¯ÍáÍmK•ÊCÙ²…g‰Œ¬é`±×š›Û)ÏûA9;ŒÔ¨g ·Íž-ÙSÓSoŸÃoÞ›pû¾¡¢E³ËõQÚžZuy]çl yx<—ÙûòæýþË}s=N*¡þå§ìæý¡2g æ6ùýÅn cŒ³²—E;Á†®¾Õë84ðÆÇ²ž?öª±ÇM™KuYSóœ¾yóQ•aqøˆU4}Ævºwg¡˜‡ðì†Ñõ­ï<ë;^}å¢Ó+¦÷ãÙ5–ÚÚߘòÂs  PQÑ1àžb@õ×_~ØW÷´pf¾Ùf«ÒwGg@qjòI'^Jaï \}Çö¥4龯©àuHýZö“›ñ*²lдéцz6é¥Åò¼™íØ^ceˆœò<=cþCì9â¡z~ òTñ‡°ŸšÿD—ˆ½gÚ ’Æ^ÍF5iÑÎEòXß}éÄÞÒ:÷âœÜ|—Û9wôqr uolPñóJ¹ï~Ÿ†tBnšÆ7¯Áâ´ç¼¹®­P¦3 lí½d­ãݼٕ֮;Iƒ6áŠÅUÃd#–“Xx‹}º^¾X£aDª„là” L2† k`@ µb@[0ŽÚöl+CÑî\¿CYrf¡üE#fÛÓ§[64î\»#÷:áÄ ‘%¡ào[y]{rŠ”*©\d}r8ÞÃÛ¥÷Š×i)I*Ôåy ’Ûe7i ñSR§6S¥M¥ñ‡ë±‡Ì1³£"&ÁÁÁtßí¾Ô•÷ƒ*é\Rçþ¶öî»RŽ¿¹Îž'»L^›ßbk(Çqm@½~ýZ&BÉ!â;¦D£Ì·)=P¦ÔmÇ b3`gzÈž¾Ã[R:%èÉšû×nÚ,Ö`͚ѕn{ ™YO0 ÌlB €€E€e¤éÓ6 ŒÔ,šI . (Î éææ&õ(UªT¬Î ¨XÅmñ-IA¦MßFOÄÚ3¥${HÛŽþøãg¥Ê&0 lrÚ1h@>E³ `-|}}¥ç1Mšï¡£Ö26ŒÃºôêå"2 ºÐÇ>ÄûWñÞU%Jä"{ûÄÖ5PŒ@@ N À€ŠSüèÌŸ/:çuo™2e2e¡!œDC=‘ €€€€1 „ÏH4ß¿}/3ÕeÈœA•|ÁHM£ ¸ ácü¼ÎÎЬpƘ6„ðƒ"Ú±ñ:²ðám F#”‘P&O™œø…ÖH .Œ'käˆ1€€X>l¤kùsˆ€€€€€ÄP±Ý€€¥xûö-qŠ{ï`@}g3ÿˆLfÉÇÇL@@@@Ô˜| ”²ø]­Oœ‚˜9—/_J “%Kfš* àÍB(   `ÓLærqq±i°<›@ƒ ŒÝ¤ÎöBCC)((ˆ'NLØûI'"T‚€€Ø0“¥1·a¦5ôܹsK}===-Jo(kZ?~¤„ R¢D‰LÛQ4­#y4€pô$ xqy_7#`ò¾SOƒÄ‡¸è}‚€€€€Ù0YŸÙ ‚€€€€€€`@ â `Íxý €€€€@ä`@EÎw@À¦sôfG€÷{Ê’%‹Ùé…@@@@ÀÜ „ÏÜfú€€€€€˜-Pf;5P @ÀÔNŸv§OSppˆ©»BûF"€93H4  c0 bŒ‚€eà¤< oooËÈh?mú6jÛ~&½ÿùZÁ£±Is›´Ñ€€€.X¥‹ ê@À¼zõŠÞ½{'³ïÙÀpu±z5'J•Ê'N¨ó>*ÍæÌü怀­ˆ'¾…³µAc¼ß äÎ[^xzz~¯Ä™M¸sç}þü™òäÉ#ŒˆTf9æxñâI½ÂBw›¥~P ,…@<»ÆRUü—o)3=A̙ә3w$ŒSKYf•nú¼O¸!6ZøýÂóæïÿŽòçÏLµk• Ÿ~*©ÒÉ9‡p  ûØ…b»råÊÆ/0Gâ/"{ÈÄJï×°¡Íå3Ó¦tŽðÌÔÉ使ÿê!ï¹Ô/-¯_ù­SÉ>~´4¬l™¼²>eФa©R:Èó̙҄]½S&…ë™8QU{½{Õ8 ±¼N”ð{}—ε4Úܱmd˜]üøa,S¤pö° éSÊg„//låòþ*Ùבõýú6 KÊ!,a»°ti“«Ú_òOŸÉ*ãš3«›l“ûÍ’9M™KŸÞ.a_ƒw¨ÚÆš¬ßôïP•̼¹ÝU÷•ö”£ÂH™³KfÉçû÷kV²DnÙGyïüoDK¶Žž–6Mø8™/ë•<™}X¯žõå3|_é+*Ýô}ŸðÜ7p)#ÛN‘Ü>¬˜SΰdIäõˆá-T}é+§èfèQáaŽ¿çÐ @,€î¯AÅ_Z°D:ÖjoÝv6‚ú…HÔ®]µ÷”жífÒµkž´võ zø/l ñ¡Zz(ª×)Ö}’¢õÿóœv½­&¼TïTÍõê½Px¾RÐÝ; ÉÝm>ù¾\Kg]§ÉûcÇmPÉ)'Ïß+¼X5Éßoùù®£3§§R’$ ©_ÿÅäå嫈ɣ¾²^¥A¿/§Š ’ÏóÕäýl•K›ÖUhðÐM˜¸I£]¾èÞc> ÖBêÚ¶mäóáÁÿ*æý½WlŠž{¯sö/]¿:—„!E3gî ¯RêíÛÔºÍtáU ¢Ý;GÑ›×¥^:T§Eÿ2â?ñ]èÒMß÷ÉöíçiŸðt X¾—nÞ˜G/|VS]á}š1cùù½‘ýé+A9T€€Ä:P±Ž‚˜’@‘"9¨TÉÜtùÊCž§ôåááM7n>¦ÆËi„Ü)÷ù¸uëYºpу\\JS‡5ˆ×`ñ«V­â4vL[z/¼8K•*EHx.4Œ¦S"-ºðâP%çBtòäwŠÃ鸸Ô×Ï€š0¾ƒ üI„ß%FWYríqÞYŸLô›7ofúùçʲþ¦—7o>È0¶aC[PîÜeÿàç+W.LOEÈaPfÊö¢"ìm挮”2¥ƒ+‡ØñuPðWúkÞU|¢¯ì¡+åssfÿ*Œšðp8níšÁÒ¨Y±â(}ûöM£í&MÊÓÔ)¥®ê¡’BQ\dËšŽ¶oI™2¥‘R‚ÈìB¾†’‡ÇsY7wîn |/ÇרQ9a(&’ã^0¿' ÏP¤­këfÈûÄÍÝK¶›'w&U¨%Ïß´©]hàÀÆÄ!ˆ\ô•“Âø  §`@Å)~t±K 88˜ž>}*¾‘×ü»Z˜¾·ÎjÉNxM‘R6n 7|~éR[©ŠpdˆK«–•¥1‰ò*_>¿¼wìx¸1”Px²j×.NîîO(P|(çrò¤;U«VTÖ{>ök¤üUϰ©L™¼ò:ºU«ÑÉš5­¼.]ZóyV(ë}|å1UªdtôÈD¹ÎŠ=)¼æëüù{4gÎ.º~=²ŠaÄFTê´íÔ×8WŒN¥Ò) "Õ1­ðüiÅ0üö-<,ïÑ£—r\9sfÐ¥¬Y"ÿ}ÐÖÍ÷ {ºþšÓxÿª)S·Ê—XWGuê” >½Hƒ›•ÑW.‚⨈u0 b9:¸!Doß¾•âÍ5m¹±Èpö;ΚÇYÔ8ã{Eî oŠH0¡ £ÒÕ—’oÆ´_D6ºôºD(C†ï)ß¹.¼*4ô›Ì’Àž|ù2“CÒÄÄë øÃúG‘1Oßð=nOùàÏç†ö†•);H>ß¾}uª!2Óå^ÎN׸É:,²î)ƘҮ®=°BBB庡lÙ4 }dãÇ'›‰8hèæJ7ŽÌȘÅÎ.ú€ æÊ†¤¦Çõð~®i,F¥›¡ï“þýQŸ>."3â-:rô:íÝ{™¶ˆpQöîØ>R„•–—Ýé+•n¸  `z0 LÏ=€€YPÖœd̘Q|À¶þ_ýNkÒna@ñ‡Ô—¾áëLDƺ(ç‚ÓK²l°qy,BuyÝ®]{$'¤NL£íظàBöŒùŠ÷ƒ£cøÚ,¥_%ÄQ¹ŽêhÈûäìÙ;ôõë7éiâÐA~͘ޕV¬8Bݺÿ-“€°¥¯\Tzက@ìˆþ+»ØÑ½€˜˜€½½=•,YR,²ÏdâžÌ£ù† ËÊ„ÆÇ‰Ê—ËO… e‹R¹º?•’÷÷ì½Anü„T¿ÁŸrÍ“úMN8ÁYöx"ö>)…÷ùáuP¼ï”³sAŠ ƒ÷Œâ¢Ý×E‘ãÎÝgòžö(öÒi‡Óq@.Î"‹žzÑG–ûfï‡òú+õ ªV!Â+WÆØË¦Þž¡çU«•Ì_°OãQÞÿéàÁkuQ]ò>áнšµþP%‹PÚå …\Ï™¾rÊó8‚€ÄPqÇ=ƒ@¬Ðߊub±C7kÓ¦*¹ŠÇlÈD• (3Ÿ ¨g1`@YÌTAQ €} ,`’ "€€€€€€y€eó-@À¨8t@@@@Àø`@Ÿ)Z8%ðáúuë–Ø×çyœêÎA@@@À À€²ÆYŘlšÀ‹/ˆ7ËLI6mú€Áƒ€€˜„>a™+¸!JïÞ½“{Ù¤K—.n”0A¯Êx4&A@@@À ð@„Ëò„}||~Xic´ñÃJ ½ØÙÙQöìÙEjæ}ôBˆ– ¨hY¶'˜4iRŒÁÏ"!AŒñÅɃü!)yòäqÒ·±;õöö5v“hl–€¿¿¿ÍŽc@Ÿ1iša[ìà„'N¤Q£F¤!?ãææ&==a02°¯cŒÜ"šÛ"/ÁxÛ0F  &$” ášKÓ-Z´ +VH#J_ØxâgZ¶l©ï#«'Ê꧘¨Aƒdoo¯·¥OüŒ‹‹‹ ²ü!úùùÑ;w($$Äòƒ€€€€€€eÆ“c,Õ¨^½z²¹è‡ñ)%2#JÝxbYõg”gq4?ÄFTÊ”))qâÄæ§ 4+"€$V4™Q ÅÙÙ™²dÉBÏŸ?—blD©mã‰eùó'4iR™u/kÖ¬æ¯,4 '”…O !ê7kÖL%/^<¹&J©`ƒŠë”Ò¼ysåG3'Àa–ùóç§$I’˜¹¦P@@@,Ÿ (ËŸC½G žQ/,,LÃ`bã‰ë”‚ð=…Ž     ð ¨ï,¬þŒ÷„*[¶¬jœê“ú9˰, €€€€€€&Pš<¬þJÏ’>2VÊÈ™÷,@S¨   ÖC”õÌ¥^#á=¡¢Z+Ã÷Xż ¼}û–^¼xAþþþæ­(´+#ÊÊ&4ºápÂúõëG*Æ÷°÷S¤xÌæ†¯¯¯Ô%Mš4f£[ ÊfYkŒQ…èEuO«\Æ!NúÁÞÂtéÒÅ¡è@@@lö²½9—û;©ï ¥ ÀÞO ó?æË—Ïü•„†    `…à²ÂIÕgHê{B)òºê”{8‚€€€€Á€²ÑwúžP ]uÊ=A@@@@`@Ùì{@{O(ìýdþo…o߾ɴå|Dˆð@Å w³èU=a¼Of1%Q*áííM^^^Øû)JJ¸    ¦%Ê´|ͺueO(ÎææââbÖºÚºr_¿~•†gßKž<¹­ãÀøA@@@ Î _œ¡ûŽÕ÷„ÂÞOq?QiDº÷öξ¦óã¿Ø› bï½kï]#ŠþÍÚº´è@QÕ¢F[£­Q´ÕÚÔ¨Q³ö¦öŒ½b$Iì‘ ÿ÷yã\÷&7ɽäʽÉïõÉ=ç¼çy×÷Drž<ã•}Ÿ¢Ú9ª>xH€H€H€HàÕ Pzu†.݃¸ñ‰UƒÅ¹ ˆ‚[¸pa$OžÜ¹'ÊÙ‘ @'à0JÜÃV­ZÇñqy$ðúˆ›åÊ•+_߀‰H€H€H€H ·PU"ÔÆ@­1‘]@8úïnç¹4~Ž„>ä<“âLHÀ ¸%ªgß~†¸à£â”I€\€€Ã,PÆÚùâcà‘^ž€ñòóò=°% @L`¾˜ È>H€H غõ"æÍ÷FpðÓ8¸:.‰H€H€^ލ—ãÆV$@$ç Œ½mÛ/ÆÝ»ãüZ¹@  °•€Ã]ølåH€H€œ‹@­š¹‘.m2$MÊ_Îõd8  Ø$Àߊ±IŸc“ €èÛ§ŠÏŽS#  Ø!@*v¸sT ˆ3g†¿ÿ}´oWY³¦¶˜ÃÕ«w1{ÎQäËëŽæÍ‹¨­(Îàâ¥ÛxïÝÒV(‰‰š¿àöíóÃÇ!¨^=5ÌŒSèþ.©6r¿p¡ hÒ¤iŒÃ‡¯aíºó(?=Þ~»°©þÊ•;˜;ÏU*gGÕª9Mõ}†-J±ª];’'O¬ëøA$@$@ÎL€ ”3?ÎH€@ s§RºWq3ʼùaŠÏ»KUŽ[Ô¾PRZµ, q·3ÿªX!›¾·aÃy}ôjT@·n k³å¹ÂôÍ€êÚÚ´y³¾¿w¯îÜ Ö —®à 89&‘pòÄé‘ @LhûNqôé»âÆ×»WeݽX޲dN… òE:Üé3úžÄ?EVƒÂâ˜J–ôD6•¤ÂPºäè¡’L”-›E'Š0Üù6nº »j¬¬Z,$@$@$à ¨@¹ÂSâI€H  xx¤D#åb·|ùiøúÞA@ÀCœ8yýTÚò„ #wLHÀMÏbôÈzÈ•+ÕeòËÄ'7ÅoÊÔƒÚR% T­Z¹Ã\þT¬Ó€o.è8(‰’l}yò¸[í•$@$@$àl¨@9Ûá|H€Hà5èÔ¡$–)JÜø®]»§GìÒ9̵/²á È€ÕkÎ!I’„ÚÏ\N’GìÜyžž©LÕ’Úü)±tÙ)x{_G·Ëé{uŸ'‹øwõYݦ{·ò¦6}rSÛI¿îÅýû!èÒ¹<xM$@$@$@$@¶pS/Ú¡¶Ú+#J…”Ð'ƒìmêòAA‘%ÛÏH–,®_ëƒ$I^(?g–Ÿô½+—záàÁ«¨Py >é^¿ŒodšÿØq»ÐïËõxòä²fM ?¿»Ø=º•ø± ‘0aôí·?þüÌkiRÔD±ðÌú“îgæô·Ñ±CI}þìY(<Êòf[×iÈÛr|ðð‰šãFLÒ÷î|…;·úãã®eá«ЙJ!4Š#Æ6úæ‘H€H€H€H€¬°þöoM2ÖuznùY¨ÜÊÌ˼ùÇô¥¸×EVú(Ë’”1?5ÐÖ9O«’5Ìšù?¸§K†©ÓA,JÕ«çDêTI`®4mÙ⃌’£j•جβA¹ãIñj½%rb +[6,±…(„“&z!ožt˜2õ $…xT¥x1ü8º¾ž³XÄíP®Å$ÙôŒbë: y[?|_W)pa|Åú×ç‹ÊºéQå iGmôÏ# „'@*<³ëFJQ÷·¥ËNi7<¹õàAˆŽ W¸bÅ2™I¿8 x€cÇo XQín'i»/q‰«Z5‡Žó9}:T¯^^#e(5›·\DÍš¹uÒù ·TŒÔmݹ(PSØœí¯^ݼ/&¥Î$Ó^•Ê9´+ß™3÷Â_ˆû_xëZÚ¹µØÉS7õÑžuêv|–9£‰¸-&Jè†óçƒ>¶1&$@$@$@$@$ž“H„'bv- „$b÷ËŒ¡~ý|X±ò4î+%**ë“(FRD‰rÏ8ʬGËSCaòj”_'¬Ø¶í¢¶ôWíº}\¥Tâ„!ö*+ÔE¼Ó¦¶o¿„Í‹Dš<¼÷t*n)£R¶Â—Â…3ê*IDQ±bø»/® Èðââù™ÄI”´•bï:ŸwcÓAâ±Ì‹XÁ$fL¬vR9¶ù¸<'    sT ÌiX9ïÜ©”V ÄO(qßKª\ÊD±Š¬ÙñÄJUšðÒë. kËÖm—ðôi¨N4!Ùúä~ʉuR…Ü* ž(n^6Ä?I§‘íëtÿ~°3GŽ´úÙGÒ¤/’f2!!Oò F[{×iôcË1¡²6EU9vTãò @ü&@*šçÿÆY ñ@ÿ,=…‘#êAÒ~¿Ý¬$¦(²R°`˜õæÂ… «£®B2í}dË–%KdÒqP¢¤xfJ‰¢ÊýOJ åJ'qP9r¤AB•¶¼þ›–ny‘ÍÁ_õóæƒV¨“§Â¬cÆ#k/i×ד'ojå®Ðóõ}غÎðý½ÊulŽý*óf[    ×&À(žŸ$3¸¡”I«-Iºt.e+QŒÄú$©¿ÿûï²…ìµk÷P£ÖtHó#I !YödÏ(±>Eöc’8(I)^E%•0”.ã~TÇé3YÜ–¸§åËO¡h‘ŒH§YDUdÿ'ÃÅÐûyÌ.}*óò2ëÔ cà#6ÇŽé³     %@ʆ×¾] mý‘=•d/¢7m°ý2®!Ä ­MÛEJù9±:É·ÕjLÓ‰(F¬g1²ÄAÉ>MçT’„Úµs›îÕ­“GŸK½ÈØZÄZ5àë8h6mº€3£Vjƒ]`ÊM£íFö¬’=™fÎ:¬ã¿Þÿ`fÎ>¢S¨›o,lï:£ØØÛŽiR”H€H€H€H   Ÿ S6Á•LykÖžÓ›ÚJ2ƒèJÅŠÙ±vut~÷tìüI\¬?K—´dø3/•UvÎ85ΉH€H€HÀÉPr²Âé Ä ³gý°dÉ.äÎ ­ZU‹ÐéÂ…;pá‚?:uª 777,Z´%JäFµjE-dõÁªUûà}ì ̆ڵK jÕ2;uùòM¼ÿþ›HŸ>µ©í¤I«pÿþ#téRiMõóçoÕ+7Ñ«W3$H`Ý @ærþü5ôîÝ GŽø`é²Ý¸‹&oU@:%‘(QB,_¾7ÁƒQ©b!½Ž„ 𯑓€€;ذá0Ö«¯äÉ“ ššwÕªE5k ¹¹s· 4h«¬p7Áê5ûqãÆݯ¬+qâ¿*ì‘5‰Ž¡ÈÉzNž¼‚¾}›CžÝä?Ö Ní’hذ¬ÑÕcppæÏߎ}ûÏàáÃ`T¯V •EÆŒi"Èûú`ñâ8uÚ•+FƒepïÞClß~õê•FæÌîšÙÔ©ëQ¥JaT¨PPÉÿ‡ còú³uÌPU¾?ä9 Ï‚³¢^ÝÒ¨_ÿ S_rb«œE#^ Ä7õƒ[ýÚŒù"/$RBŸ ŠùÎÙ# Ä3n‰†ê;è¿«ÓÒ4ýyºÌî9ÝC–¬,Y\÷Ÿ…$I›úxü8ž™;ê{W.OÃÁƒçQ¡Òø¤Gcü2þ#“ÜØ±KÑïËéxòä©R:ÒÃÏ/ò³Gw/Œû!DaéÛo*~üé,˜×Ϥ¨]¿~ žY:é~fNï…ŽkëógÏžÁ#SˆÅkMã„?iüÖ¬úw?~ø®¾úz&’&I„ÇÁO´X÷nôZÆŽ[†$J±  «ïÒ¹.¦MýÜÔÕ?ÿìBËV#Ô @¬êþ6®«/ùÉ_K6zn!ٺ혩›-[¼Mç›·5:tJ±kܸœ©.ª“ŸÇüƒ?'¢Ç]¡eU™ô뿘>}ƒ®¸9gNý†,Êr2}Æm=1úëÖ}’RzÒàÄñIð>:þ×faǶ‘úöàoÔCVŽ>¯cåʽا;‘½q}6Þ{·žVN¾8Û\ÔfY[šwþa× èׯ…žkÛ¶5ÍoE8oÛîG•9ñ/¯²ÊŠæ†)K±" Ôw•%î’RÆŽ]¦-O?Ž~O+Õb•”g9qÂÇ(©Ü6­±‰«E‹*Še.-bϘG½}t›|y³hK¥\Èó9¢ zölŠkׂô}[å´0?H€H€œ‚(§x œ €#H,Œ¸£-]º[»áÉbUxqM3^ŒÃ-±CÇŽ_B±¢9•â QHŒ/±nH ÔeÇtú´ŸŽªW¯¼½/š”ŒÍ›½µ;œÔŸWqV—/ßÐCH,X‘Ê©±m)5j³Ëž=ƒ¾6”㦸¬IC)éÒ¥ÂúuÃuœ•XV®^ ÄÿĘ1Kµ»¢È„·¸HÄ™—¤IkEF\ùΜñ3¿­¬= Í;![Š(©RZµ¬fz6Æ3ªX± ¾'¼¥ìÙ{ZÃljÂõ¿ÿUÒ÷ÂTQ–Gqµ4/öŒY®lÝT¬}”æÞ½gðôéS”*•¢È•,™Gß·UÎ|<' ˆ]/"ƒcwH€bœ€$[ÄãÆ/×ñ,ò½bÅÜWJT—Î/b€Â,Š‘Q¢Ü3´ ÛtmXeÄ¢´DÅmSn|¢\Wíº}ÜH¿,:OY¡¼ñÎ;Õu²±hD–<ÂÔñó‰»2/FLXï]¹pá~Ÿ¼FYÖ⦊g’"–²$*þ÷#¶N§,2Ö,>… e×ÂW® bŰv¶È>{ª…mehÌHyØRN«DR:u©x`à=}Oöù’x1I(¾dÏ–1|•¾.Q"Ìêd~Óž1Å}tܘ1rÔ"ü0b¡þno¾YZÅÐ5Ö ¶ôm«œùÍ#ÓÉä¦Xº¤HÔÓ§ÏtÉÖ'ÉR¦H ‰ƒ’—wQÜluß“þD|™"Ö°rå{éöíÛ×BíZ%T¸lÚ­¯i³aX»îN¸`Þ·X›¬•ûéê9^(¶È.±ôÙÂÐÚØQÕJèè‘ï"W.«¢™2¥ÓõÂQÇðY åæß›VÛZ«´gLiÿÙgMУ‡—Ê„xëÖTÊû^ü­\%Ëâ’Åдi˜Fj«œµ9±ŽH€Hàõp˜ååå¥Rÿ®zý+âˆvH“&,Õï;wìjGá×O qãÆ¯Ð80âoäCq$Yé$þDR’¿ýv%HŒOdE” )’æÜšÅèÀs: €ÑG¶lt,ÄA…„<…§zq/ªÜÿ¤ˆžÄA‰’P¥-ïF¦…bøã?×è¬}’•/|"±$Y+þ*©ÁÍ›w"X¡$µ¸IÁm[dü®¶24ú¶õ(óY½æ€VŒÂ§©—ä;wž€§g˜%n˜b ó÷RuîCHF[‹=cîØq\¹>Ó–&yæò5zÔ{*vnÞÿðôC([ål#åH€H€OÀa1P+W®Ô>öòWH~9/ƒÌ™3«ùÌmôZ@IDAT|F.ð}ú÷ßãÒ¥Kê¥,,mµã<Ä$™Ä ¥ôêý§V,¢J!«ÅH,'âú&±CæE‚ÿkÔìI`n!’d’eO4±>Eöý‘8¨Ù³7«x¢ÂQ*nF›W=J r)†‚gô·[%]8®ÒtK1,DÆ=9Jv?ó"qO/V´HWe~/:Ù—ahÞtç ê—Ñ"Ë•Kfø2tØ<4j Yw™2ù"¸îÙ#kŒçh†’¦],„’ù¯té<*ÞÌzR ™ìÝ%kMŸ>ìÿÏ—ý§cÔèÅ8©6.ô<ã 1﨎¶Ž)rÇŽ]R©äƒ YµB­Rë‡/¶Ê…ogëµ[¦Z”¿òm%F9 ˆœ¨ÈÙÄ‹;T âÅcvÙE:ƒå²ð옸¹R]3{d£ëëuÝ_°`f©´^jÛ:uÂ\*eì'Ož¢|…Þ¸¢öôåÑUã—áFêe¨± X'@>ë\XK$@$GäÌé•+÷âìY?ôÿ²¥Ž»xñÆŽ[†C‡/à'µ±m\VžâÈcä2H€HÀiPršGÁ‰ 8‚€ÄpMœð±Îx×å½q¦!©LxÇv@ïÞo›êxB$@$@Ñ  _t„âø}ºð¹ööööVò‰TìF¡£ºöÊÂfO¾×óÅ2#Ûdì뙽}£Èüwí:…|ù²èx©äÉ“Ú׋JÓ…ÏE§M$à”hrÊÇÂI‘€mÄíèþýûðõõEöìÙmkD)G þ›ä†»áÒÙ BæïêkpŒ.3Ù€|ÕªU.3_N”œ™€———r‡¶ÜOÏ™çëȹq(GÒeß$à`¹råÒ–'ÙŠ…H€HÀ’•'K¼"W!ÀÿO/èÑõ‚ÏHÀå¤H‘ PîWO]nîœ0 ¼.¾û¸cËëbÍqâ&lådkyƒ(ƒ$à¢âÃ~P.úh8m   8H€.|qð¡rI$@$@$@$@$@Ž!@ ”c¸²Wˆâʈ 2 A‚¸ó÷#ƒX¬@å $@$@$@$`F î¼a™-Ч$_ àÒ¥K¸páB|EÀu“ €C ÐåP¼ìœ^/www\½z·nÝBPPä:.”ЧËâÂ2¸ˆ5´âÆzL$ Ð*— $Nœ9sæÔ©ÍŸk6/Åö}qáÒäÌšG+q j53Ý—“À[˜·l*Ê—¬‚ÒÅ+`ÕÆÅØ®b™F3Ù$wâìQlܾ 'Ïy#_®B¨Z®6Ê—®jºoëIÑ%‘QÅiù^»ˆÛwo!mêt¦¦Ñ­WÅr·pÕ,T)[ …òÃ’Õá€÷n¤H–JWÃ[õZšú3NdÏÄõÛWbßáUÿ¤mµòuÛ޶¬uíÖå—ÅîúâÂ峘³x2ª–¯ƒÚUFè®A€ ”k<'Î’bŒ@’$I={v\¼x÷ïß§eFvùò=8yò úöm޳gý0ù5¨S»$6,«¥Ä•kþüíØ·ÿ > FõjÅШQYd̘Ƭ—°ÓK—nà÷É«u©S'Gñb¹ðþûoÂÝý…ëäܹ[ÔÇPÖظñV¯Ù7î RÅBZ6qâˆ?¢—.Ý…›ŽàÌ?äÉã‰úo¾fÍ*YŒ/ng—/ß@ϞͰgÏi¬úwλ†’%r£U«ªÊòèi!oË\Gú`Õª}ð>v … fCíÚ%PµjQãv´GI¥ðày¬[wG½}Pæ|¨V­(ʔɇðë•™•+÷bç'õFеj–@ݺ¥¬Žak¿ò\eìzõJ#eʤX¾|/¶nóV™*3¡u«j(]:¯îßÇÇÿ½Cí¡vO½ŒcôèÅê^¼©xÅÑ}O}ñH¶Ø}p›kR¯•…øe?|Ô¿5ß‹Ô*NÇMýûëŸ?”«_VÌ»% —Á%¿ ücO„< Ñmå¼zÅzZzü}†}€ÅÿÎVI*’#g¶¼*ÁÃzÌX8 õk6Ão?ÌGÒ$Iu»€ ëøn|?tëÔƒ~ê‰#JaK—&½Iú㯱®î?yò™=²ª>ç`´RDº´ê¡}Æé?àYL>Š‹§OŸâî½ÛÈ–9—…òdËz¥[?ÿËz®ï½óö(vÞ§é~nß Â”yãðI—¯ðÕ'ß›f zîŒeëæëºT)Ó`âô¨_£©v#4 >?±u­‹UÜÙòõ =K.tûêÕ:T+¯T Âu눿]gîœ) ÀKȘ1£zyL‰¤IÃ~!¾d7q®Ùì9›±àïíêeÚï´•_ù@Žìµ%/Ô­ÛŒÄÞ}g‘6M ÐüÇŸk‘5Kz,_6P+­[½Ñ á`ü°R ¯â›³6–œôQC“e+‹¨¾§ŒùóF 88òœP jѵö¯º‚²%*Ág×c4{¯ª–=¿ó¡©ÿ?þ£•§ÖouÁ(eI×¾‡ÊŠÕwø‡Êj3¦ý€/>úÖ$/'3þž¤›œ˜>V €¯_ fÎÜd’µu®«WïG¯/¦ råÂðó+—§kï´©Ž‰“VaØð°¿Øš:¶r2cÆF­<õS/uWýfàèYÃ|üïíJ8tø/þÏÔê»ïhåéífáu¦–Û³ë'Vr~WMrrbO¿FÃ_&¬@§Nu4ïëþ³±])•¢h~úÙïeY,T-Fþ|Yà¡,Œr>~\WÝüeXXûž2æò:މ%ÒŠÉ®]»¢T(¶oߎiÓ¦áÏ?ÿÔ±’‘Íí·ß~Ã_|¡¬œ=±eË«bbüä“OРAųžú¾›iUîÑ£GÊ’Y… F±bÅ0hÐ «rwîÜAõêÕѲeK´iÓŸ}ö™U9‰óìØ±#¾ýö[¤OŸ®¸Ãä9?£ÃgL_}†½é &hëQ‡ÿuÕÙàŒÅ¯X¿P¹¥íBݪ^háÕAÿìqssCõ uÑûÃÁ¸ÿà®É¢b´1?ÞpOeþ¡,LÉ0òëßMÊDòdÉ1öÛéÚº$÷Åeμ^f½âªø§²"K¼•”â…J£nµÆêA!8çsJ×Ý»W%éøEÍ; &}7W+Or£t±òñÕ¯ZÆüãeÖÚ@Yó¾þtÊ—ªbr­4ï“ç®C€oO®ó¬8S ×D ™ziñCgT©RD»ç-\¸»vŸ‚—WYtèPÛô‚"îdƒµU.&•kß6=;±ôœ8qY¹F¦„§ç ýúõßÀHÕgÎ/þ‚i,ç—ñ¡lÙüúR\ü&M솼Ê=oÊ”u ¼«•´꯼ɔ…é÷ߺ›\Ý’'OŠéÓz"½j3bÄB-ÎèSŽ?|ßY+ rž$Ibôù",ã–¸ÎI±g®}úNÓmÆüü2gv×çb-š5³7ÜÕZ§N]ð/FZÈìC¬Jõ3Q äOJŠIñIÆúüô™°àö»wà—_V ‹gî_}‘IY«¤”/_¿Nê®ÏÍ?lí×¼Mñb9ñãè÷´ÅKæ"nˆrýXY·Æ_n.áüeX„ÿžŠÐ©+îÞ}ˆ9r¨ç–íÚµ‹Tñ¸}û6:wîŒaÆáûï¿Ç!C¬ÎJä~üñG,Y²Ë–-Ãôéӭʉ‹ðæÍ›•»é­Œk¢^Þ›è«Ô ™²¾]Pk£\¥H<˜±ôÝR™ï:vT/=Ê]ѼHÌYJ¥t™[û5oS³fqÈúÍK:%õ¥ÄÁEV^–Eøï©ÈúwD½ÄàÉÖò2,– æÍ­o*în¢4ùøø(%=±VV¬ÍGä,X ]é$¦R”kEž;wâúõ뺿\¹rYÓŠÓ¿ÿþkõžy¥ü?ÅÍ–Ò¥K-&V(W,E•¢T·šW„©wSIZt­‰-»Ö`ÃŽU¨§¬(†BòùàNäŠ ¥ DV|®œÕ·*•©iUD6ïݶ{$îHÎ"®„æÅ˜‡(QEk‡ý‘Åü¾qtÛr.’`Bâ´ÂQÀ>ØQ%tX)sÇá›ÏGic{Öëž6Cøîõÿ© }¦ïù)æ.ƒr-ÊRtÅ–¹&HöbQ¾\~ôícÝz!ã(Q)1¿Ë»c1ç¯-¨ \ñFŽè¢-uE•UK²V©ÖO¹õ…I/2¾~æÍMç÷ï?ÒñJF…­ýòr´Æ=$ä©Ê´õD¹»Y*hæíbŠ…yŸ<'{œ»³#±ERòªd›ÿ[­ÝïÂgç»{ïöÙ¥ò“'G˜B#Y÷ú÷øÎb*’ S”5)¹²ç³¸þ"oÎ0k¼dûk\§…IÉ0äÄEîfàu eÔEw4ú”vFyÕõý„?æ{>ÿû6…¿¥\ï¨$WaX²ŒyÅäZ# Ê §&@Ê©'G¯‡€ü…X”(‰0^^_ÏÈ®1JÁ‚YUŠñÊ}.‘J^ÍbÒ’ôò‚³{÷iÝW¾|aAÛŒâÂÖ¹TéÊ¥ˆ»]‹U"¼8pN¹iݲHÑ~XI.ÊSQåb¸cû( ÷9ág^dÍR6m:j^­Ï…÷Õ«A*1@j}mO¿æy»h~©Ï…¹XÅŒ,‰TEL°°Ö/ëHÀ’Æ|íÖeêÿObÔVq9RjUj ’ŒÇ:µ×Ðû*]·yóçPü>û'ü®’Ì™òÌï˹lÚ+‰ŽœØ¯É{öÔÔÂk5uΗ ýŸ']bœ$ À«ó‚ Fx9~Õ~ãBûõÃ\•Äz¾ 6T¬“·wØ ¹¯o ¾–,ræ%eÊd0ܸÂ[†¦Oß`.ªãždÿ Q6Ò¥K…âÅsê„ û÷‡)*æÂÿ©=’•rÒä­ò®æ2‘Û:WIl!Ö'Iõ-ã™—kׂP£fHb…ð1Eær;$EâqÌåÄýn欰¿ø1P²“¤9?rÄG[ñÌû™¨²>UÙÝŒbO¿F9 ßð.‡?ùG‹TQ™ÍËS•fÝ(1ÁÂè‹GˆŒÀÌ…¿âãþmL_ômzï”BËj«íêý”r?·I¬T•O,H}‡wÅû·@­?~®”§ŸQN%oŸÜ|\ɶׯÛp­ HÊó•éý’¬˜¡Ó 'N”D»Ò™·‰ì|x¿_Ô-7|üU,R{‰ÕéŸÕsñöÕTZô¨Ü í)býO‘<•Ú¼÷¦Zw˜UþU×Ùø)’§T†é˜¨VгÌ{ÿÑ]øiòŒ2\%˱´LÇôZ#›ë“-PÎù\8+ '"àåU^*yÁª÷ëØ¦öíj)EÓ íÐÙ⪪„o«TÜRJ•Ê­6ÍÍ©RA¯G>•´¡Aƒ2:ÛÝeÁú[í1%›ÙšÇ)%T/¾ViºïÜ×›öŠ;Û€¯gé„ SþüT÷)Ùö†ë€÷Õ>P’2}ØÐk“¤ôþ²ÿ ·#Ùí-öÌU2V®ÒmÞ¥3ŠëÝ©SW0pÐ?÷£G½åð’ÀBöÔ Ü@åÊØä­ 8®bªd¬óç¯é¶b™“x3±ò Ú]?žˆÚu`È·íÔ¾UžX³æ F^„ŒÏ­OÒÈÞ~Iú©tîߪ“|dÏžA[ÇD‘k¬žµ¹•QâË–¯Ø‹ÁßÎQßåTÒBxUÆx$È:¶Gí?ôâ6 &‚¸‰k\×½ad×3ÚËF·ƒ”Â$›çÊ—±RuiÕ]ïß$ñ­Q•¶ÍÞS{k=Á`µ1n×/[šD3{dâ?¶ ¤ÊÄgK‘y͸VoFûÙ ÖòyŠbÚÏK•¢f5³¥/C&‹JÞpÎç$~™ö½ÊD8TW¿êz¾ÃÛÿïC­ óz|ÓNß–ì{S\‚Ÿÿª~ÖÝ35qÄZMóÄé ¸)÷#Ž×é'Ë Æ<¼yóêNÏŸ?ó³G—& Z&L}à­#i¸®…>]æÈaL}‹r é^Uû©º›²ßÓç=ÿÀT¥%±ÊâÖµk|;¸… žX£Úwø GŽú¢ú(±?’öÛp+\¤›ÊhwC‡´CoµÇÒ}¥ˆH‘McE!éÚµ¡¾6>&O^ž½þTÉ 6ªMmh»ðïþ*Ú «Iã·†heïÞjÃäd&Y9I–¼¹Î”wèàx]oë\EX\;wk±“XÉ$å{µÁmtE\õZµij/›7¯‚?&‚¶íFë9ËF¹KÿùFw5^¥ÿ¢Ï½Ñ®THö½Å‹@¬~ò<Œ5ØÓï¼y[Ѷýª”2qËÜifQ«¥2óIÚtóg¿BY?ùôw½‡—dT”óRleÕ÷”îè5}¸%lªGŠo¿òŸ!¾ûâÇ«ŽÄéˆÕGÅ+XZmt›Ã®ï0Ù éø™Ã¸à¯]û$áƒjOG’š\6–á™2dÖ)ÏñûäU×Ùš$S Ä~¥Vqf¢íº'2á‹ì‘tø°üýoi×>IÜð2/8æýÚ2WC^æwôèE½.Q4ªT)l—¢-±`{÷žQ¶PT¨PÐBÁ“ý³$¶É °‰À½{÷póæM›d)D$@$@$@q•-PqõÉ>_—dS“„¯Rb¢WŸmƒ€ŸŸîÞ½«'“1£åŽìÎ1C΂H€H€H€O€(Ç3ŽÕd/Ÿõë_lüiïd¤­ôÁB™2eÒ®\¹‚'Ož ÄK´@ÅñÇ^ªT)Ô¯__¯²^½zv­V”§Q£FaíÚµvµ£pÜ$.]:xxx 00Po‚7WÉU‘ @Ô¨@EÍ'NÜmÚ´)zôè‰'ÂV%J”'ióé§ŸÆ \DÌÈ™3'äëucÐ×=.Ç#   ðèžH¼nÞ¼¹v¹êÞ½»Mî|¢<‰¬¸iI[     0´@ŃY³¢råÊØ¹s§VŒ&Mš©%Ê\yªR¥ŠÚ+…i–ãÁ·ˆÓ/1ôé2§Ÿ#'HÎL€V\g~:œ €« åjOì%çÛ²eK­@‰UI¬KÖ”(såI†‘6,$œ:uJÇEyzzF&Æz ˆuÙʹÅú8 ¸C€.|qçYF¹’† "eÊ”ZÆP¢Da2JxåId¥ DF 44ÁÁÁðõõ…ìÅB$@ÎFÀËËËÙ¦Äù€ËhìÕØeçÓwS/A¡1Ý)ûsNýû÷Ç‚ L“K”(‘)µù¹´nÝ#FŒ0Éò„¬½¡®^½Š´iÓ"þüÖD^©ÎÍ-ì¯Ætá{%ŒlL0\øø+Ÿß $@$ðêhzu†.ÓC‹-,æj¾—ù¹…—µhÈ xN K–,:NÎØ#Š`H€H€H€H ® ן°ÙúÊ—/\¹r™ÕX?‘e!舅H”¨4iÒD'Êû$@$@$@$'PŠÑöEØbY²EÆö)I$@$@$@$@q‡¨¸ó,mZ‰-û:Ù"cÓ`Šw$3JÄ»ÇΓ @¼"@*^=nèxÙß)²Â½Ÿ"#Ãz[\¾|Y§6 ´Eœ2$@$@$@$àr¨@¹Ü#{õ Gµ¿ST÷^}dö× H6>)¢H…OL××Îõ‘ ÄT âÇs¶X¥ùžPæ7¸÷“9 ž¿ 2ÀÝÝ]+O?~™.œ®Í¼y[±xñN§›'D$@$@$;¨@Å÷X5Y²dhÜ8âfhR'÷XHàUä͛ŋ7mÜü*}9CÛ¯ÌÄÐaóœa*1:‡óç¯aÚ´õ8yòJŒöËÎH€H€H ® ןp$ë³–iÏZ]$ÍYMQHš4i”÷y3ö ü÷ßI¼÷ÁxlÜx$ö'à €  åB+&§~O(îý“tÙ @\%(®.ŒëŠž€Xœ~þùg-HëSô¼(a?ÐÐP\¸péÒ¥Cúôéíïà5¶¹®\¹;•eFÎkÕ,ºuKE:ƒ£G}°jÕ>x»„B³¡ví¨Zµ¨Uù ¶aã¦#ðñ¹Žœ9=ÐöJ¾dÙ¥Kwi¹3gü''ê¿ùš5«d!·|ùív×·osœ=ë‡É¬AÕWÆeµÜ³gÏpðày¬[wG½}Pæ|¨V­(ʔɇĉÃ~äÏ™³+ÔZ¥¬[÷ï?B‘"E˜åðêÕ@̘±ÇÓ§OQªdtíÚžžîº|ÜÁÔ©ëQ¥JaT¨PPʼný‡ còïŸha(ñc²î7î `Á¬¨W·4ê×ÃÔGt'ö0–g·e«7R¦H¦X”Q›€°Jž<©iÌèØÉ|lóÒ¥ø}òjý,R§NŽâÅráý÷ßT1€©,–e«œE#^ 857õK.Ô©gÈÉ9Œ€ŸŸŸz±ª¦ûß¾}»Nqî°ÁØq¼$ ooo½öÂ… «ôvqpssÓò¡O—ÙÕÎ^áÇCйËÌ_°]7M£^ˆïÜ}ˆ¦M*`ß¾³ððHƒCÇ›º;v)ú}9]%Ëxªþߤ‡Ÿ_ äiî^7öC$L˜PË>|ø:ÁÂE;‘(ad̘×üoé{ßj‹ÁƒÛêsÿƒÁl¥Ø$O–yófÆéÓ¾Qý7kZóçõCÒ¤‰µl›wFaÁßÛ1n_¼Óv´w¼óÓO›àÑ£`Ô{s vì<ÏLéô¼%ÆéÉÓg(]*6oúiÓ¦DÍZ_aÇŽxª”­„ ¨ù&À•ËÓ”|ZlR ô{ýÆmäΕ ÁÁO৪ôJ1˜5³7¼¼Êéyœ8qE‹÷@?¥È‰à¾ýgµLÀÍ¿ J\ÓfñR)˜Â2wnOHÌÕ=¥¨õÿ²~ø¾³î#ª[Ëšß}oæÍߦ»~U]ëVÕ°gÏidÏžÛ¶ŽÔ÷¢b'¶Ž¹U)j Æ#õÜDy–ççsñ:r)åxÍê!(T(»ÏV9-ìà·„Mõü•ï`ÐìžH ^  _¼xÌÖ™5kVõ×ã*úKÎYH ¦ $I’™2eÒ+Wœ7YÁwß/ÐÊÓÛÍ*ÂÿêLܾ5{vý„Ç/håÁœËêÕûÑë‹)¨\¹0ü|g(Åc:‚çâ6Õ1qÒ* >ß$þóÏKµòÔ²Eܼ1GËoß:y”B!‰)D©2fÌR­þ÷v%Rk+‘”-›ÀŒé=õùøq]ñøÑb­<ݽû¢dˆb¸kçh\8ÿ'|¯L‡Ì9$ä©V2oÞ¼£Û“ÔšQîyRNŸñÕÇÈ>ÆŽ]†JAêùyST¬XÈ$&sòm;Ü ¸ Q˜Ì‹X•–/ˆJI,¦Üؤˆë ”|y³˜¬q©R%ÇÈ]гgS\»¤ïGöaãQ£k…tê”Ï•Ûcf½îš5‹CÃÈŠ5v¶Ž)Ê¥XßÒ©gíéö½"ãˆkâHõkù’"V¡‹Ê­ìÂŒQ.‡R¬Ycôçÿí:©ÏD _*V,¨«dý楊²Ä‰Ë¡y)W¶€¾üí_J麣,`•U V^”Rn„òU±‡ñ]åf)ŒE!2â·Œ¾ß~»¢vO4®ÍáÙÙ3fáÂÙQºtPqf^‡ ÛÇt¬œ(Þb4Š<;[ä yI€H€\‡€Ã(*O®ñM@ÅÉ5ž“Ì’ÿ§ó¬Î»ª;–øk%{öŒ¦jQŒ¤ˆáž¡©>üI`à]­¼H}É’¹Ã߆üá"Mš°x°³gÃÆ¯Y#¢Ò" ‹É®(I@a®@•(±_±z,]º“~]¥c™BŸ,±L¶™‹XÂÌ­OF;cl£JK”³:™W6Q±cãÆ|¨-p?(ë™|¥SVº7ß,­âÄÚ‚f´·‡±$hR°@Dd‰CËœù……Èè_ŽáÙÙ3¦´_²øktýh‚NÔ±M¹ J‘³–-ªâ“Okk£ÔÙ*'²,$@$@®CÀa ”`ù)ãW¸QÃ# €½šr³·‰Sˇ„„(W5KkKlMØpuóõ °:ÉP—,YØ\${åËåGß>Í­ÊKeõB/–)¶í‡<Ñòá?$#Ÿsw±ð2Æu—wÇbÎ_[PA¹Š»œX@Š*k™(UªõSîm†däǧ*á„(bFÒ CÒžyH±„õèá… ŽèL+VìÅß w`‘J¨±dñ4UÉ1¬{Kò)á­OR'.ׯ«D*Þ,ºbϘҗdR\­bž|}´Âº~Ã!¬^}ß šå+ö`Çö‘ÚuÑV¹èæÇû$@$@ÎEÀá ”s-—³!ˆm’LâúõëÈ—/ŸúK}ÚØžŽN¯-“Ø´éh„¹ˆÞÕ«AÈ!µ¾WPe\“"Ê‘Äü.°ºR}8pN'HTÖ†ìæÍaY 9Jv¶Ñ*ùA×heKêÖ+7½ï†w”SSåk÷îÓú:_>K79“Ðó“  {Zy*ªÜüvlq!3ÊN•X–"Šße…ÚºõÄ}ѼìÚuJ_æË—żÚêùŽÇ•öL[š$6H¾FzOŇ­Ãû*Ûàô"U  n¶06\Ï>·"šOæ˜J//Y m)öŒéï¤ÓÄ‹+[¶ wLùE»|…ÞØ­2ÿÉØ¢ðJ:ùèäJªñ,$@$@®E€I$\ëyq¶$àò’%K¦­2/^T/ÙÖ­.¯s‘¥KçÕ)¿ñÑó±'ªŒj’êÛ(¢‰õI’)ü§ö‹2/’¡FÍþd¢¼ˆë^Îu&?ÙWɼ 4GÇJåP÷‹Ï©Xìߦ|™ËÉJ1jòVy“[˜ù}ós‰ã‘"{™+ObMš9k“¾g-Jöy2Šì;%eÕ¿ûŒ*}En媽ú¼CûZ÷¬]HòŒ:u¿Ž,B2J‘´é‘{K<’‡J ?oÞ6Ÿfô)®Éƒÿe\F{´gL_ß@4R±O’¹Ñ¼¤L™Ìä(ë³Uμž“ ¸È‹¹Æü9K #1cFÿ“F¥ÅQè,“0ÄÆRäÅwØÐö:+^í:0wîìRɆ ™‹áßÍGÆçÖ'cn¿Œÿâ 'é¾gÏÞ¤­NÒ¦Zõ/uâ†Ñ£ÞÕ¢â'ûI¶=Ù›é/åZ'îko5Š­ÛŽ¡±ÚOI”7ɶ7|X­¨Õª=@ËRØÎPVšæ-~@µùí`µgTtE¬19T¼–X@*W2Ùiúôõx³þ@9rA7ˈì/%ňiš:m=¦©/ÙO©›J;žOõ#ŠãWfhÕæ¸²ÏÒZµ1oóÿUÖI-tQ|ˆ’%ënÙj„ŽÅºpášruÛ…÷?¯[µi]=ŠÖ€­ŒÅuoÔÈ.: Hå*}1a •Qq9ê7¤çž4‰íN¶ŽYªTnµinNÍì§Ÿ–¨}Î.Bž×È‘ ñ·ÚŸ«¤²L [[å¢Á›$@$@NIÀaéqŒrÊçÎI¹#ÊÖxg_žXnß¾­RA§3¥Û¶6gã爣7Ò•±Ç_Ž/úLÑ›Îʵdß[¼h€Þ¯IR›o¤+îv»ŒµØ#J\ç$­¸$P0/sÔæ¸w›¤7‘•zQ¾Ú¶­ ¿| ±|eòäÕèÙëO½ ¬Q—MmÒ»ðïþ¨T)Ìr#õÆf°W}g˜Ò¨òâª×ªõHÓ¼dóÞæÍ«àÉŸ m»Ñʲ´_o¼ôŸo´°y‹ï!±I²Ñ®ì%™ä®\¹‰ö~ÒJžÑ¯?þ¨¡Þ$ØÈhl¤ûQ×øí׿¢ú\”¸‘#Y¸ÑÉ&·¿iƒ¯¾jA>|…=ŒEqðõ,ˆ+Ÿ(œo¼‘S§|†rå{«4ï…±~ÝpÝ}TìDÀÖ1EiFGŽúè~‰=“tö†K ­rF{G¹‘®#é²o øF€ T|{â\¯Kˆk ”­áu*P2'Éž'1GiӦЖ–ð‰Ìç-I ޽¨7Õý äE]2¿Y+âF'Ö‰©*T(›i¯©ð²²wÔáÃ>ð÷¿¥]û$&)|œUø6á¯%gïÞ3ZAªP¡ ÄÂfQz$žËØëJêÅõOöÊš5ƒ!¦ÛÊ&¿âÖ˜^íc%Ö”té^({&ÁhNÄuQÖ-qdY•2(n¶Ç½ÙÃX¦r[¥£«Tb¥DI÷T©[¡ÚàXö÷²µØ:¦¸>:tA?ù>•5±(ß³Æx¶ÊòŽ:RrYöK$ PŠOkv9T –¹Ü3ã„O@”“ÖmF)%/•²²}j1 ¸K¶l=cՆǟ«ã{¡ß¿¸~ ˜$`»ƒxLŽÊ¾H€HÀŒ€¿¿¿ŠJ®c£ÌªyJQ‹ŸXš/ùO[žÚµ­©Ý·«½™º÷øyóx¢]»šQöÁ›$@$@$`/Z ì%FyˆqÙæâtH»ª)RDí¹ôÂåÌp‡z1P±ðX9d ´â;Ñ©á¿ÈêXH¥œ_»f¨Þ³)†qù.hrùGÈ 8Z œèap*$ ˆA2óݼySmøzIàŒ¸æ—$àé鮥‡cÛ¶ã:ÎLb¿d[  p*PŽ Ê>I€ì"#G<|ø0Ò$ vuFáxI@ÒÁˆ½,$@$@$àhT M˜ý“ DK@2Í.ü"Uw´ (@$@$@$@±D€éÆxK$@$@$@$@$àz¨@¹Þ3ãŒI€H€H€H€H€b‰]øb <‡%ˆœ€$”ðóó‹\€wH€H€H€H –Ð økaËŠ¹‘Üe5 €# „††"$$Ä‘C°o   x)´@E‚mÆýqÿîmÔ|«m$¬&pšº7ö°1Uð„H€H€H€b‰-P±žÃ’ DM oÞ¼¨S§NÔB¼K$`3ÆÛ,KA  È ÐŽÿl_ý7îÝ BðãGXôçhä-RwoàÆÕ˨ßò}¤N—ÞÔjåœIxôð>ê5ï‚´é_lܸuå|ø_A³.½ )š¥„cÛªù8ë½=D±rÕQ¶F#Õ.£©?ãÄÙ ×°qé,”¨P 9 Öåáô‘ÝHš<%Š•­†ª [ÝE{ô9uû¶¬ÂÅ3ÞÈ–§JV¬¢e«Zmàï‹kÃ÷Â).]oTk€Gîáøþí(]¥Ü=2ãNPÖ-šŠ"oTAÁ’´üáÿ6àÓá“M}Ú:æu¿KX=ïw\9ÉS¦F®‚ÅõsH•ÖÝÔ—œØ*gшNK qâÄØ°aƒÓÎ#  ˆŸÜT¬A¨#–îææ¦»]~Ê!Ý;bʺÏC;×cHׯx¬¯%N‚†m>Bâ$I±dêørìTkÔJß»p«xêó^#g¢ÎÛõù³gÏо’2çÈ‹1‹öê:QÌFöl3G÷"Eª´>÷ïÞBúLY1ð·åÈ_¬Œ–“[eOÙ‹/ZU@“ŽŸáØ¾m8âR¦I‡ûJù“Ò²ëWèüÅ÷ú<ª¥ÓÇbÚè~xú䉞Oàu ÞEãö=ðá×ã,67=´sFõj£ÊD‰“jNÉS¦B­¦ðïÜ_1lÚz¥DÕÅås'ÐÝ«(šÐGvmÔJcª´é1wO€žŠ­czïÝŠAï5PÊç#­Ø…?Æu_xdÍ…¡SÖ {ÞBº?[å¢âàÌ÷š ûÿä ÿ®Î¼tÎH€H€H€œŠ]øÂ=± ,ñ~Œ,9ó#»‡>ÿhàxm)QyQ7Š÷ž-Æ)ŽîÙl:EæÞí@”¯õÂ]btï¶8wìzš…yû‚0wo V6Þ¿‡káÁ½;¦ööÈJ£å³Æk«ÏŒm¾˜§ú÷ÏA¥H¹k…ïI4øû·®ÆŸ?ôÒ–¤™Ûý0cÛ=¿ê^ï`圉˜?i˜i^&ÊS°²ž}óë2ÌßKËÊS˜ ¥Â¼ê¯Ixüðú_ˆsÂØÙ3æ¯ßv×ÊæäugñÛꓘ²ñ†(Åé†ßE¬˜=Á47[åL xB$@$@$@$@/A€ ”ÐÄÝNÜǼ÷¾Pšä<{F)SGwo6õ$®jRÊÕôÒÇ«âÔá]úºv³Z! ”XjÚ~2ïßÕ®}"l¬î\}dÌœ&,VÖ£,ºJ\Ey{ú$D»ÙrÖŽSGöÑÕ £•0¹H™:-¾=K+aëN…XÔ¤ˆÕH\ßûòGT¬ÓI’&Ó²ÝODîB%µLøqíôûrTmй”›¡[Ç‹˜X²Äª–.C˜¥OÚ—©V]úŒTV¨œr©-g¶Èia~ À+ e#¼D*£Tåz¸xÚ[)aÙÁDi*^¾¦Žûñ¿r^ÇHIw¢@‰õ*ñrº÷£Ï-UU¶R±U·,¾ •ª¨e ¥ËYÝP}”PñJ¢Ì˜‰c’ríòyój‹s‰Sºtöræ/¦Ý ÍçöP)>E•bxóÚe¥„ÖíNÙ£o(Ƽˆ2XùÍÿ™W™Î «(qe4Š=c&L”HÇŸݸŠo?ôÒiåÅmRJ‹û¡Å}õ¹­rZ˜$@$@$@$@$ð ˜DÂxbQÚµ~‰Ž7«Ó¥³ÇѨm7ä)\ s' ÑV¨ßÑɪÔoaJá禀Œù²S¤£J™=²FgiÜ3§¦c„aÖ°™n˜ø>Ÿ—(QmË[&d0Óîˆr}íò9HLX¦l¹Íoëó žÙ#ÔIEî‚%,êísÀÄ%˜0°+íX§¸nÓ}å)\Z'Èx«Ã'Ú&•¶ÊYL†$@$@$@$@$`'*Pv+W³‘–>¶o«rk{ªÎCQ²RmdÍU@g¾“8(Ïì¹UÌÏ}“ûž4p{ž…ïÝ~£‘I%?°VÒfȤ«í‘5úI ¡qj×ÑÈX Dy4?Ìšc­ƒ¬¹ èjQÊDJ˜0âx’qЖb”›ÞÐ)«UFC_¥¼.…$ù8°m5fý{6.Ǩy;ô|l•³eŽ”!    ÈPŠŒŒ•ú žÙt¬¸ÙIr‰ËÉ™¿¨–,^¾†¶@ydÉ¡,O uœŽÑE¶ÜõK¿(F?ãž$8q`§)ÆÇY£—=ÊXRü¯\@£d(7FgUÒ‹ÛÊe.•JH!E\ñÄZtÓî_Ä$ɽsÇÊ!ÚbϘ2ÎyÕoîB% ì·ï®¿=¸^-Êë”í—ÎC:5[äò¶§í¤)@$@$@$@$@Ï 0*Šo…0+“¥€¸ñ]PYödÏ$‰=2ŠÄGIä“Nò@IDATÔ¦¥³!q?æ{•©Þ@‹‰Å$|™;a¨Šïi„Kjÿ%)öȆïËÞk™£XŸîÝÄɃÿY4—=¦ú·¯¡>HŒ‘”bJI”²Ò,û\Û·]+ˆr]±gL±: ›ù¿~gÑm²)µR%• ”5ÌV9‹NxA$@$@$@$@/A€¨H åÈW{6-Çœñƒµ;ž‘ìA¨…ÿoï,प¾8~–Xº»»aAº;d—Ri¤þ *""""‚ H ¢€¤‚H,ݱtw÷²äÒ]ÿû»Ëf–™efÙ˜ø?³ïÍ{÷ÝøÞ‘ßžsÏ7H¯B"ÃQ†uB5}l\ÖGé5Jº6.£ö¨j¢2Û ’LÊ£† rÿÑK§GØ¡aØojÙ?ãµ ¹|Uõ»¬ö<­Q›ù"iFð•@£h˜G{ÛÄڲ̹ ÊÊ'©´ò9”W¯–<{öT‰µe²qÉlí ÄêüÕT8RÖ¨?¼GŒ«ß¤å2üÛÖ2¬[ÈFÀ¨+SŽüj¯§ùj,!ë¾p-¶··üûÞ¸Äc *‚²:   ' ¨p‚óôÇ.ž=!G÷l1­GŠ7ž§#‰ÔñS@E*^VN$@$@$@v`ŸÝ¨XМ@ú,9/ xn¤ëI³Í±’ ¼ ¨·ÂLJI€H€H€H€H€<‰”'Í6ÇJ$@$@$@$@$ðV( Þ &    ð$Pž4Û+ À[ €z+||˜H€H€H€H€HÀ“P@yÒls¬$@$@$@$@$@oE€ê­ðña     O"@åI³Í±’ ¼¯ÊÞª{yyÙ¸ÃË$@á%Iÿ»†·;‘þœŸŸŸ,^¼8ÒÛa$à |}}eÑ¢Ež0TŽ‘H€"•@¤y ð5H â@LxšQyC÷gñúóQÝ4Û# ˆbPQ œÍ‘ ¸. (×;öœH€H€H€H€H Š ÄŠâöØ D+ë7ÊÄJÙ¢i¤d¡Ô2wÅiYµ9PÆõ«hê×þ£Á²xý99p,XòdK*UJ§—rEÓšî'ç.Þ•?f’#§nJ¢ÞR0W2ù¨a^I–$ŽQDføŸ/DšÕÉ!«·\”¥ÎËÕà‡Rºpj]6vì×ÿŽtå¾L™wLvº&Ïž¿ÂySHûÆy%MÊø¦z/]½/Óæ—Ê¥ÒIœÉeºjgëÞ+’ ~,)¯úÚðÝ즲8y¡:1sÑI݇«Á$w¶$R½LF©Y>£E9¼yüø™ÌRë‰v¸&>• ÅÓJ튙%e²¸¯•µuë‘0Þ3w$sº„ÒÌ/§æh^þ¹ƸbS ìW¬‹æO)å‹¥ÕGk\ÌŸµuŽ:§üwLÖï’gÏ^H‰B©¤b7Îë¿îðYXµ%PVªöãÅUÜTÛåÞI#éÓ$x­zÌõäÿŽÊ®ƒ×}C_?k–_’&~5ׯ=¤.œ¾p[æ,=­ê)Ÿ6Í/±bÅph.¬ÕÉk$@$@ÑKÀKýRU¿ÚižJ {ö/Y§NòT·ðòòÒ½{q¤}„õkUòûÍ–n–Õ[µHH®Ïõ­­u#¦ì—nC¶ÊÓ§Ï%}êørQ‰ü#ùyóü2òû²3fˆàY¿=Hj}´X*±‘G‰‘Gêx&ð®dIŸP–Mð•<Ù“êúòÖž¥ïU*‘^‹¢ÔÉãÊõ›´0*Q0•,÷®¤JÏ4¾5Jt4í²R®(‘•5CByüä¹îú8íç*â[)³.»}ÿ)ÙhžtjYP6(±°çðuõeÞ[n¼\ô]û"2 KI]¢¢ÞgËdѺs’8AlUo"9¥¾Øß½ÿTº·+"¿)‡Âg.Ü‘ÆWÊöW%IBoÁܼóX³X8ö])Z ¥®ÓÖ®Vß®‘9ËNK¬˜^Zt]ºö@ïÓ±˜ôV/’?To³Hv_–4)â)qµ}ªDO%×N«+Iyë²Æœ}Ò$Ÿüþc}­‰êã?*qDІ%mªøÎwî=ÑÏbRÚ”ñ$øÖ#ͯPîä²d|mÉ`&Œæ­<# ;­Póé%¹²$Q¢öfŽOÜÄ•¤Íûyt;ø±L‰Þæ]WëúÐWÔûD}>Ò©vwÍ}_·¿nÛE©ÜÊ_†WF:·.¤Ÿ=yî¶TiµPÏ÷ÂßkIÕÒÄ‘¹0u N¼òŽÓµðW~Àd$@Oàõ?}z< °E_¾nÞ¼)—/_ÖE·Unÿþý2}út™uøq£'–7•#KšÈéU͵p:«<£ÿ>h*‡«EkÏÉŽ9ïÉåM­äêæVò¿òh‘ÒsÄ«ÄwT¶¹&_­ˆˆ-³è:U6N¯§¿°·þv­\S^sûuÚ%â Êok#»ÿ{_’)!5tâ>y¢ÄlîòÓZ´h;¼¸±ðo¤ç%@õÖ{ÔNSÙ[J<¶øfµþ¬¬šì'—ZjÑöíǪßÊ Ø=T½ÆƒÇÏÜ’J.P‚ö‘oO0GæÂ¨‹G  ç"@å\óÁÞ€]ž?.ׯ_—‹/ª¿h‡|I¶öà¾}ûdÚ´i2aÂ9pà€µ"ú„LóæÍåƒ>Ðe­„xòõõ•¢E‹J™2e¤uëMè²Ïž=“¦M›JÏž=¥oß¾òõ×_‡.¢ß£Ü¤I“$ @vìØ!‹-²Z.².Âû²ð÷wåƒZÙ¥@®äº™®ƒ·èãðïÊj¯ÞÀ 1Q‚Ð?xà‚Wá[ðH†p¸Á]Kê5ãšqÕ«¬S'Bü~ë]^²gL$Î9*Á7CDÑ•YîªHð`”R!~†!|ðÇ/ŠÉ5Uî·é–â,SÚ2wt I§¼e°"ùRŠŸòRÁCrôeJm„ÇÁrdNlò %Tž¨Á]Ké¶.]»¯ïÏQ-* зR&ù°^.%½ô«Z™ ÚsϬŶS…Cž°WR$#“TÖìPúߥm!QètX{ôø¹ !Ì Ý”1<ñãÅ’ŽÐ}9¦HxlòÀÊšžãSFô(+ñâÄ”¿7‰Ï›JÔÈ™L·=SbS3eU?Æw.è®öâ<’ðÂsfˆ *”±o§âÚµrÓÓóÆÉQÒY©åBíƒ7²b‰tÆ--æðæMsaz€'$@$@NGàõ p§ë";DÎKÀ2=’téÒ©/§1­vvïÞ½²gÏA¹R¥JIáÂ…­–ƒÙ²e‹.W³fMùì³Ï¬–óóó“£Gê{%K–”™3g¾V}kÑ¢…Ü»wOßË;·,]ºÔj9xnÝzõ…õã?~­¾ä¦I“F®\¹"qãÆµ90èÕ«—l(W£F×ê”[²d‰œ9sFâĉ# „|q¶Z8.–Uk]Ì¿éÓ§`Ì2•+W}[¿‡‡BæôéÓº¾b)ߊ™Ô&Âç¥}¯õ‚ð6„6bO§Q*]:6™mP=«.kíª½§ö»z[ùþÓ¢*1†Lš‹m/éì€H¾°8d„§ "ɰyð©ó!ávX‹vL±‡WÌ^K¨6F¢ ì鄤H3ŽÔóØêG•5ϰÊÊX÷¡[Õ~TÏÔڬزvk Ÿ²O2*â–¹" PeÝK/7Ê+HÿûÌCz“bˆÜjͼX¨YmÖ?á3²v[Êæw@¾VÞ){çÂV¼N$@$ý( ¢œ¢;w³‰%Ÿ0Ëð& ¸:$oX>ÑWZw_+-»­1 ¡fó«e «Ãfº3TØV µ¹jן·¨—©¨^Óƒ´çÆš*ÜAèÒ^w´Y­‰ ñZaC[¤2/]äÕÚ3dv[;­Ž®w7x†ýŒ"<ÖX $¤2G8YÕ6þ¦*Þ{ÀW%¤¡Y¸Þ¬áÕåË›´@€H€ÅV^·j#á>_·ð™*2;i®D¼7ŸöÙ …n©%DÒ\…ÊV63‡U“FJ¨‚^#ï×Ȧ97ë²J{«PÜí5xÀ ú¾Wb)áaH?¿èÚ:lШ磆yT8â%-ìÞë¸\_ÆÚ¥éC«éµgØÈ¸^‡ez“Üwò§”õדæj,ì­… ceOß4û1¶O)\ŽŠŸŽÌ…Y5<% p"^ê]øâ$œhìJø ÔvêÔ©ðWÂ'I ’áP/Ž´¤¬Wûøñ3-8b<­JÚ€tãM¡ aY{ÔZ(”ƒH€7 ÉŒ~£|ÞÚ³TJðÇzVll ¯Ëã'Ïtq#t/t½øgž˜}G¯KrZˆTã¶Ê†~6¬÷«;x"X{fÒ§N ³å¥Jn™”Âxe‘¦Þ"ŒÉ<Ñ…Q&¬#ÂñàEÂÚ)¤UGŠ÷І5cÛ÷_Õž¸’>©”‡'¶©öÙB†AkÏ™ Ù8Á¼ õT‰!¬Í…‡ëèé[Ê[˜Trªõg†¡ÏÈvˆ½¢ÌŸÅ†ÃH#DX¯;öëŸ £Ž7™‹7ÕeÏ}¯¼ãt1þÊ·‡Ë @Ø( Âæãöw) Ü~Š]z€†‰j‘ÐÌTDÖ˺HÀPŽÐbY ›@øÿ|v½¼K$@$@$@$@$@nG€Êí¦”"    ˆ,L"YdY/ (þ¿¿k÷&¬F$@$@$àü( œŽØC &`žœÀ…‡Á®“ ¼$À>~H€H€H€H€H€HÀNPv‚b1      €âg€H€H€H€H€H€ì$@e'(#      (~H€H€H€H€H€HÀNPv‚b1      €âg€H€H€H€H€H€ì$@e'(#     n¤ËÏ €ÓðÊ;ÎéûÈ’ xz ½a_Æ}I€H€H€H€H ¢P@EIÖ.¶Ä*«V­šÌš5K:$ÁÁÁR¿~}«mÌ™3Gºwï®ï%NœX‡úøøX-Ë‹$@$@$@$@$ð6( Þ†Ÿt%J”¼Â2ܯX±¢D¢‹'OžX-¾iÓ&Ô"eÊ”…tìÞÞÞVËò" X#@e ¯¹¤LŸpÿ™z¯€ý2÷Ò…ù+ß~f,I$@¶Ðe‹ ¯“€2fÌ(x…eýû÷9{ö¬M‘…çß{ï=9zô¨öT5kÖLZµjVµ¼G$@$@$@$ ( ¢:›ô,¥J•¼ÞdØÃêáÇZDÍ›7Ϧ€š6mš\»vM}{e1$ðMdyŸH€H€H€"ŽCø"Ž¥KÖÄ>çš6ìcuþüyA¨Ÿ5atçÎÁ-êT©"þù§ñÖ∺` $°¸îJoÂçJ³Å¾:3†ð9óì°o$@®F€(W›1ö×­ @ìäÍ›×æ!ª¦OŸ.›7oÖÉ+|}}­– ’ªU«Ê£G$uêÔÒ³gO©S§ŽÕ²¼øvŽ:$‡Žï“8Þq¤v•÷lV¶xõ\‰#†¼[¹Í2‘qãú«²aÛ*«UÇWreË/Ù2åÔ}³ZˆI€H€H€,P@Yààp~¥K—¼Â2„b£àÝ»w ²8pÀª€‚GkÀ€;vl½iðûï¿/ & «jÞ E`Ùºù2hL}uLÿÒ VÓP%BÞ~Ý÷#ñV"+ªÔ‰3Gäóï›Yí“q1nœxÒ®ùWòm‡ŸÄðú÷x$  °$@eɃïHÀ-À“OìÆ’,Y2«ã:|ø°Ìš5Ëtk«ºtébzo~räÈV˜>}z~É6cvÞgØWR¥ì»’$QR³«ÎqZ(o1éô¿¡gôèÑã‡ÚôydÔ¤òäÉcéÕyˆq›G   +( ¬@á%p'¶ÄÆX²dI™={¶öPA<5nÜØêÐ/^,;vÔ÷F8aÂ)Q¢„Õ²žz1}ÚÌrñÒ98ú;ôÝX§Ã&e:ñ­ú¾Õ~Õ­ÑXj4+, WΦ€²JˆI€H€Hà ¨W,xFI X±b‚WXæãã#•+Wx¡nß¾-<°Z!ƒC† Ñûb(P@o.7n\«eÝíb‡–ßÈïý"ÓþýCúµ’â>eìâcåõY°|–ì=¼Cea| ¥Þ© UËÕ–äISêç•(›¯îçÌšWjV¬kªóà±½²nËrÉž9—EXàÅËdÞ²R§¬”(RÎT>¬“ü¹|$eò4xé¬ÜºsÓƒö¦þõb¡ùËfÊÆ«ë®rdÎ-JU—J¥kEäôù²~ë ©P²ºÄ—@V¬_([v¯—Œi³D\Á}ªêI/—®^T÷^H›FŸKß®#%f̘¦²Î|Â,|Î<;ì €«ˆájf#‡ÀÒ¥KåñãÇ‘S9kõ~ø¡,Z´HFŒ!”J•*YûðáÃ¥}ûöR¿~})S¦ŒÎ(hµ ‹]Äú§úµšÉå±÷÷°7ö¾Cf²ÿÈ.-¯¹!‡ÖËÌßVjañAûÊrçîmí *V¨Œ?}X{vŒJ!`ç”P ¼t^ŸãÇF•qâ¡\ñª¦ka@ôÑU‹‰Ð .ìéêF†Aˆ§Ö ;È¡ÕÁ²ræ^Ù³,HyŸjÉØ©CäZð‹.Lš5J{éö¯¼*ûW\‘y6J•°çÏ_Dìî½;òi÷&Z-œ¼E¶.<-»–ê²Ož>‘/û´–`%¼ÌíáÃû2xlOöÃD9±ñ®][Z~ð©=2{ÑTSÑïuÐëøæ)Ø¿GtÝNðÀMúg´©ÜšMKëÚÀ÷Ò‹²sÉ9¼ö†Ô¯ÙT&Ï##&ô3•å €ç €òœ¹s¤:tÐ_hÃ,Ä›$`|ùòI½zõ¤I“&óG±ÖÊÏÏOP6S¦L/^<óÛ.}Þ÷ëÚCòËøMbÀÚ€üWΑ]¶Hµr¾òï‡ú =V!yE ¾Úˆe»~ò£öZMžý›.kþã;åíjT§•¾äÛ[>mÙUŸ}„' b4±Jô‘J…-†PÃ↓¼P™KZXâÍ_—Ô)Óêëè3¼_I%“™ &jï›éž x (˜æ7²lÙ²R´hÑ7d ˆØëjÔ¨QÚ[åïï/)R¤ˆ€Z£Š”ÉÕ¾[_Që™îKÁŸÛ섬ŽZŸƒuGæ/C0À›«ªD ¡g°Í;CžíüQOím2Ô x÷Þm-ÊtA³XËtóv°Å !t†AT˜ Gúç“¿¸®fè½U²çàvå9z&Ôz%#¬±2·2E+½&®Ë•¨ª‹`Mlç¾Íúˆ²¡í—‚êØÉW!F™ªek§ú˜E…8ÆŒK{êpa•ÔZ«+jmÕ‡|eÞÒ&Y‡ÖݤC«oôsÁ7¯« …%wö:LÒ|~À­DárzÍÕ©sÇtyþ  ð¯ô=gÌ©X»bË.^¼¨÷ Ê;·¼û#HÀ6fõÿ'ÿ.ž¦ ³’„6ã‹÷—½C¼%¡ïãý %x` Xdzå¥è‚¸Iž4•øä+¦¿Èá|ÛWëòÕÔڧІÄXWÚ :õj)+7úËŸÊûÓóËŸuGú‡äX4fò`=y ~%N˜T*–®¡× •)f)‚²©Ä¡-súlzTЕ úÖ•p¡ˆ†X2/Ÿ+[>ýö|ÐóËú{é•2Ú0?" "H€HÀ³P@yÖ|‡k´5kÖ”iÓ¦iuòäIÉ!C¸êáC$àIrdÉ-?”Cà„¶ìê>¾´cNh’GìØ·É"U7R›OŸ7^á?r‴jø™®²|‰jú¸fÓýLëFB7õÆ÷Èð3OöàHÿ¶ï §JèÀÓ„µDxaCÞó'J×~É? '[¨£*xh Ý{¡³âá¼T§Ï×a‹»¹aí k®µ«×/Ë£»u¦Ät©3(Ñ×A¿î?¸'µ[–P)зÊQåu2˜œ»xZüª~`TF{Hþ^X E# ð,!bó¬1s´á P®\9éß¿¿Ìœ9S²e Iºš©S§J—.]dþüùrï޽зùž<ŽÖÔ` Í”9cå®J an†W {!…¶áúêtèJ†aï¤X±b˨‰Ô¥¦,{ãC¸ÜoSÖ™ô¬…ïuØ:"áìZ·e˜#ý3e°4ú´ªZWd™,ÂØ +´Wh¹òò„}3²/\VwÁØ?j•ÚÏÉܰßÔJ•ñ†äŽ2ò!Õü¯û[<Š}©ò©ôó0„üa?(xŸbÝXe<€q¾÷qEdÂë§÷:||Ÿ@XÁêÕhb7N.«Ôé­Ô¾Mð¢¥K“Qþ[ò·ÌQiÆ!þ oîMTéÎ'ÿ3F+¡ò`Õ×åÖ« „kWy_¯³¨ØŽ7ùsÖYg-˜¤=XŠðž­Û¼L°^-_N1ÖXýÔm”ÔmSF>ý®‰ôøbÃùå䙣òóï½t_z©ðK €ç €ò¼9´O˜0A ¢V¯^ÍD‘F™»xaZ+1€MoCÛïgÉC¿Ô¡yσÁË„°²¯?éóZ¦ºªJtA@!M¹¹UPa|PðR!$0<!sReÀ5i€|ói_]…½ý«W£±Þû ‚©±òD7N<éþù•i°¡qI¿j÷ƒ¬U{,uV{9V¦XeÚs‚ñVcÎk¥cÏ*9Å ý2nbo'$­ApþÖ†®·ŸÚÿªŸt5US¤@I¢ÜXÿ„lˆ3Æ,—ν[K§ZšÊåÊ–_& ›¯°eh¡©OH€H€Üš€—ú‹è«?‰ºõP98k²gÏ®/Ÿ:uÊÚí¿,½{÷Ö{ÿ £Ÿezãoº4dPƒîpï¦.«”ÚXSóüùs•ú»ˆÚ‹èU"g˜@{û‡rGUjq¤O“*½Î˜"Y*Óæ-›)ŸßLv«¼OŸê$X”)]Vµ&© ©œù ~EU›ã•4qrɯR£#ñÅÛÒ¬<¶G×í%^ÚUP¥77>sæõ#<ö‘B?R§H+Å}Êšö»2/çÌçЇü¿Ä_ùη™Êð Ä•³ð…oÄÖŸzüø±ÎèqU³fM½¯‘Îú¼Œ9Üñ"*šc$඘…Ïm§–#ˆ á‹èlÒùܾ}[¯“š>}º´iÓF°H€H€H€H€B`_h"|ï‘°YïâÅ‹µˆÚ¾}»*TÈ#9pÐ$@$@$@$@a`_Ø|Üþ.Cø›â7ÊÔ©S›ö"£_úôé«€¥"`„ð9ô “ Ø$ðâÃamÂá  °“CøìÅb$[¶l‘•+WÊ€tF¿«W¯L$€H¥‘ D ??¿ˆ©ˆµ €‡`Ÿ‡8|ÇtéÒE ,¨3ú!z¢D‰«€¥"°hÑ"‡Ê³0 D6†ðE6a'¯Ÿ!|?AÏŸ?×{NA`aß©*UªH¬Xü[EÄ“f$@$@$@$õ( ¢ž¹SµHñÓñèÑ#)Q¢„ܽ{WWÞ´iSòñ-±F    ¨&À?‹G5q¶çöâĉ#K—.•… ʦM›´˜rûAs€$@$@$@$à!èò‰¶5Lz l‘‰üë»wï–ß~ûMŠ)¢3úeË–-òu±°èéåi$@oOIY¸®ðí9²  €òðÏTô}&Mš$ýúõÓˆ;¶þb“3gÎèë¶Ì4æN8)ì’K`s—ž>vžHÀI0„ÏI&"²ºqýúuI‘"Å[Uu¼UÜôá¶mÛ ¼NË—/—k×®IªT©Üt¤o?¬GÚ¿}%¬<˜€WÞqø@†ªØ;\ˆ'<ƒgiÑKÙü~øá½ŸTÙ²e£·3lH€H€H€<œ“HxÀàÖ­[RªT)yüø±tíÚÕ"œÏZ C­3úM˜0Aà‰\±bET5ÍvH€H€H€HÀé P@9ýElëÕ«'ÞÞÞ6+Å=”¡y.òåËËÿý'íÚµÓ‰DræÌé¹08r   E€YøBq÷·ÆžPFJóÐãÅ~QÜ874Ï{_¸paÁ+,›8q¢ÞhÙ}}}%Y²daç=   p ô@¹Å4:6ˆ°BôºçX+,íîÖ­['+W®”^½zéP?w/ÇG$@$@$@ @åŸcO¨ÐCçÞO¡‰ð}XÆŽ+¿üò‹ö>!Ù„»Ø[dܬÃòÝ/Ûä|Ð]yôø™Ì\tBv] ××oÒÏ?Võ8bWƒÈøËÖ½Wyì­Ëb¬s—Ÿ~ëz­àöÝÇšÓŽý!{ÕáùMŠ9úóðÑS]ÝÉs·5“go9Z}„•_½%Pþ]v*ÂêcE$@$@®G€Êõæì­{lmO(TʽŸÞ­GU?~|yï½÷dôèÑòÍ7ßXûõë×õZªÞ½{K@@€Õ2ÎtñÅ‹R²ÑòIï 2hü¹péžÜºóXš}½Z†OÞ®®Võàù;÷ž8ôü™À;Òþ‡ 2'Š¿¬7l›ô³Ó¡¾FDáÀË÷4§ sŽ˜ª9õ€¾vóöc}mûþ+šÉ–(•¦©“~¿í’¾_o~‰ç$@$@F€ÊÃ&Ü®µ}ž¬]3ÊóHá!$k×®•iÓ¦IË–-eÖ¬Yá©&Êž9|ò¦œP^ŽòEÓÊñeM¤D¡T7NLi^'§T(–6\ý¨\2½~>ŽwÌp=χH€H€H€œ‹“H8×|DYoråÊ%ØïÉØ ç¸F#ˆ$P°`Añ÷÷$-Ù¿¿(P "«ðºàU,‘VrfI¢Ï'ô–¿‡VÕçáùñÍÇa'ãO|†H€H€H úP@Eûho # ÅäÑ>nÛã¼W#«$Iä-“ç“ì™KÍòõ}¬9¯Âû:·.$Ûö]‘ÅëÎ ÖçøäI!ÞÍ.Y3&2õ÷Î^¼+ÿû ^(„ Î\tRVo¹(Xë”;[©^&£©~ÓÃ/O®ßx(þkÏÉš­%ibo©T"ê[¶ÐÅë¬f-9);\“ŸJ…âi¥vÅÌ’2YÜ×Ê¢‹T›v_u*•K¦“je2¼V.¬ çÔ¸&ÿwTv¼&±cÇ¢ùSÊgÍò«>Ʊx ý_¥Ö­T|ãÅ%å•7¯Ü;i$}šåysøä ™áR–\Jì~P+›”ôyýsãhÛÿ(~˜„PfN—Pšùå”*¥Ó¿±k/8.¯Ü—JŠ£ÑðùcÖ!9rê¦$Jà-s%“æ•dI,ù¼±r  p*^ê—¨úÕIóD·nÝ’’%KŠ———lݺ•éË=ñCà$cþꫯdþüùº7I“&•;wêÏ%>›°GÚëcdþðk¿D–\§Ï^HÌ^3¦—¬›VWrdN,©ËN“†ê úì‘5tPví¶ ôuIùjàfæ÷ôésyôä¹dH_6L¯'Ù2&6•]¼þ¼\ÛÜJR(!óüù ©÷Ù2Y¤„Uâ±%k†DrêÂm¹{ÿ©toWDª:aXïS²Ñ<-ÈXá´úBŸ"i¹~ó‘¾ÿãÅä‡Ï‹ésü8sáŽ4î¼R¶¸*I”× ènªõ[éUŽ}WŠHi*‹Ä­¿]£ÄVH2ôã¶Z£U¯j%¾®J*ÕÏ=óšÊ[;Y¶á¼4ïºZ‚UÒ4)âéãÅ ]ªø²kîû’VaóVž‘†Vhž:ŒW‚ fvâ€JÒæýq A”ßo¶|Ò$Ÿüþcýl5ž–ž’  êúPëÉšùå«Ï ˜C¬]¾þ@ÏÙn¥ä«6>úYü°·m”…àl¥˜ÌYvZb©¹‡è¼tínIŸŽÅ¤·zÁª´},cIDATZ(»]—›;Úè÷øñýðm2à=R_ñûgDuµ×^LAòZ-–‡Šu%ÁüLà]É’>¡,›à+y²'5='^yÇéfø+?*h³  w'À5Pî>ÃaŒû=Õ¨QC¸÷Sx+J $•êïaÕïþäò¦V &¬÷¨ð%«˜¡¼wðþ9ÚFß!.» Ù*ÇNß4šGÚ6iŸOÊà|Qg#„°¸HªqêümS½æ'ߪ6!ž>¨"°!ž`~ܨEì‰åMåÈ’&rzUs-œàý÷Aó*xN$@$àb( \lÂ"º»Ýcø^DSe}ŽðööÖéЇ &ÈØç*oQ«¹uwñŹëÿBÖ;í?lsÆ=x¶bÆ ù'8¡ò îZJ‹‚K×î[<‹0²¿†TQ -BÂÓà¹@¨¼]G_Š…9Jd!3o¥Lòa½\&ïÊÁs‚ €³ŸÔõÞQBjÔ_´§hưª’Zy`¨lŸòúüM?FLÙ¯=a¨»jé°?x×úv*®ë]¹é‚®âæíGR g2馄B +«’t ŒïÜË4ñÆu{5Ëe”Ÿ:—О<ƒ¾Þ¢€öâÄ‘¶Ádð„½Z„MPY‡mÂûYNõ³KÛB¢œ†:¬/tÿ¾´Y~V¢·©o™9¬šcDxÆàQƒw Þ9Ã:¸kIh\ã‘H€HÀõDÚ(???½pÜõ°Ç$àœ|}}eÑ¢EÎÙ9íU튙,Fž=S"þeË[ÂÅ ¦ÒÏôµC®©µAXg…µC…ó¦Ð/‹ Õ›R…S«A˪ ú«¹’Ë:.Ãú+sÃó°U›¥]ã|ÚK…о–õs½V/ÖK%ˆgÙ–y]ƹ±7ÖX™D$<.OŸ=×—! VN®£Ï:véê½¶hËÞ˲ûð5Óuó:ì9oP=ëkÅ äàÑ:v&d(GÚ>¬Ö(AdVW‚bÖÜ:4/ E²±~ ÷0–Î6 Ò¬×ø+V )’/¥fí«Â=±. bbµÛÇEP„F$@$àÂÞü›2œƒCÖ- @ÄàÿSÇ2¢jJ‘Ô29¼X?…uN¶¬®Z'3²GYåñØ#Ç…¼’ªD5”WåóæùUË„XSÚðf4cˆ†Vß®Õ×­ýÀZ%D,KúW‰.ô…—?2¦}sb‡“/ÃÙ åNnþ¨>J€Vë»þ˜yXþœsT®Ý|¨Ë$R"Å;6BÝB6È}­’7\Èd¥¥T ¬]22)¢ {Û6˜øäy}<1Ô|"£¹a½Äæ ^´¿ž0y"rÿ®©ö¬Z/+Ôºº ;C6a.¢D2B;~XP{¹Œ²<’ ¸HP†…Gm‘0ÊðH$6ºyB’)„]Šw£šÄRx¬S«‚*ä,¿ò ]T™ÿ.ˆÿšs‚µNÈî‡/ÞõÔÚ$à ±d¼·v4º1ä›R’E­Ù±fX73s˜ óò÷T2‹¸/×ñ˜_7?‡@D›ÏTÂ°ì€ e,þÁ\í‘iQWe³+•^rgMª¼fÉt" $í0úV=¡ïÉ̯Ÿ¿tW‡ð!sŽ´m^GR*uVsï”O½9ÒE…ò½[!“)ýʬ’E,UÉ"Àyþª3:áR•x£çȲPÍwÀŒz¦NóqðœH€HÀù p ”óÏ{H$àFv]’uÛ.ê/ÏX3¤[i9¼¤±üùSEíQšüß1‡G›;[HF7xuÆgþBr x|Ò¤ ñd!e: )ÑCM Ä›,wÖ$º¯v„„š—ÿ´÷i®ZÀÆÏ>¢×jïWQþè[Qšª”àHˆp¸ Jð„×<´mØâåÁÚ2˜#mLÖªy mȦW÷Ó¥²Peý3 Y ‡+/b.šǧïèõ`~ 0nËeµŽ b â)ƒJÕ¡5W ã«*c>µ†m«J}ðÄ Syž ¸ (ך/ö–HÀÅ ¿Gª¶öWë,…Jµ/ éÓ5ˆ$ØBµ—Uhë;f—Ôn·Dà‘!Œ ^š}G¯[„»áޕ 6¬n•,úÎÒ ,J¬Qû'ý1ë°Î·×_†ì…Þ÷k¨ AáI«tæH‡nnKÖŸÓo‘DæHÛØ¿ k™…0HeQ4·^#·ë=¸2¥{Úhî5CæA¤)GJøÊÓ ¼|_3G¶CsK?¶aáõ^š×Çs  è!é!|Ñ3,¶J$@ÎI_ðÂ…½‘ú}YBmº›P‰™`°‚5©ÃáŽûVÊ,¾*¡ö›jßk½ \kw°§Ñ¨iô¦µFâ|‰ï÷eqµ>gƒÞÓèÇ/Š«=«ɲäg•‰.e¨u]Ö:óq£¼ò«ª÷÷™‡$AüX‚¬xÕ:x}° Y÷`•Õz.¬ê>t«<|ôL­%Š-k·Éð)û$£òÌ\Pš*“Ÿåº/kmš_CÂ채~`-×ܧµ€¬P<.êHÛ`2°KIš½z[ùþÓ¢ÊKC&© ”×+Ï–Ÿâ‹¤Ö ‰3Æö® UÛøëÔåXÃV8or½iGõ>bèk€1B5}ÔÚ±|9’Y«Ž×H€H€\€” L»H$à>+„TæƒÇïÕ_º‘Å‹S¨ý§ª¼ðجáÕåK•"/Xl•l¢ƒJLÑG‰$óµTÈÆ÷àá3ùzðf½.Ê"ûÖ_aÏ£»÷Ÿà’MC]ëÿ®§Cõ†NÜ'xÁÜ鼑¦öQÃ<²i÷%-DÞë¸\_ÃÆ¾Ó‡VÓa|M»¬”z–éwãªñÛk¿õ./cg’Ž}ÄX……ä SU1UáHÛï¨,ˆÍ•è„7ìÓ>´BEð6¯“SF÷*gª×ÚI%[*a¨ŸÃ±?~.‹§ÿ&i2f— «Nš®/˜2RÆè,?üá/%*û ’VôlS]‰ÁIš"$NžJ‹çÏžJ¶¼Edà_k%A¢$úùó'KßüòþÇÝ´ˆ€¨K˜$¹ÌØv]ߟ?y„LÒMž=}ªû|墺þBñû\Ú}?Ò$LM ubÏxGÐïÝÛªy˜©/ylj§Æò@Ê×n,Çöm“”i3Êàéô=ƒm·á³ä篚ªk/¤}Ï_¥nË/ÄÞ6!2û}ZO‰ÃEzÎÓd̪ÅùÃûw¥aûîÒúëº-{Ëé‘ð£nžÿŸ"é×HèqÄTiü;1µ± Oû7„3N$@‘AÀ¥×@Í›4LyI&ÈÌ7´p€È¸X5w²¾>}ëuùcùq%\Òɪÿ&k/•-ˆ³Æö×â©tõ2mÓeùg×-ùeö6Ç'D0¼zÒ‘²¯žzu†'UÀ.?OÓ}Ÿ±=XúMZ©Û]éѲ²Ü¿{[&N*y‹”ˆxE Û¿u­>½|á”\ :o\xŒb{ÇÕ\\õß-ž ЦÉÿý2kç-)]ý=5¦=V³ ‚c-q¤¬Åƒ/ß,#G÷n‘â•|¥Jýµ÷ e/R¶š4ëØ[‰¨;&/ÊÀ«rpGˆÇBêüÉCRªZ}]›!¦ž={&ûU¸›Oé*šn£V¸LuÞÖM·kqãÅ—:vÄ©\£¼µcŒ—}E¼÷?úÆZ}-}Ö\6ï92^c>ã„ò>¡rdÔ»|E…÷eµÙ–qÑ6ñ ¼uHˆŽ{TÆÂíkýUàl©ñû1ÿ©Ézºj{Ëýà‘H€H€H€HÀýDqm0†Ç#ÄSc9$t¸q5H%M¡o8RÖ²¦wx~׆¥:C`ùÚ,Š -¤PGÖ<ÃB¸|öxªýì±âÛì3}ËH¯¾sýýŒ_‹Æ#r÷Ö -ž2åÈ/Cfè´îÆÍíÆ»°ÆX/_8-eÕº(CPOP‰0n©$ ÕZ([æÈxÓ¼  R)ÉC¼`Ïž> }Ùê{GÚ<´3@…ì=U)ë+é´åH‘þ¿o‡èµ]¿~ÿ‘¬œ;Y ({ËYí/’ ¸ —^Q³ï D ö” í…B¦¹çÏŸ™šr¤¬é!³“¢jéwØÓ(´ÍÝWú´«-çÔ^K†!DÌX±åŸß¨K/LYö°Î ¡fÿŽÿYygZ„ïݾ’~<^‚Dâ a}«çMÕU[[e´i&I¦÷_º}ãšٽٸ¬H®Ð½EEA’ ì»eË/¼hQÄžLXe’Tü=òã펴‰Ð½ï[U5%‹0*ÏûN}ãåÞaö–3žç‘H€H€H€HÀ= P@©y? £ß‹Ïå»–Uô†´Göl‘é£~TiºR_êSšfß‘²¦‡ÌN’W¬¢¯ [)£z¶×ëšeoÜO_ Ò²#&?a"½É.Öõ {Æìyô-¬ B8Ö A(aƒ\Ã"eÚL*!ÂVùkD/½<)=ÛÖÐ"åÎ?fò£®OzR§^jߪ&²fþ_¯6ìíÖ¬¼NyŽPİ̑ñ"qDÛn?ë„]›”ÿ¿FËB•ñ‡ÿÕÔ™ ±ß—=æH›•ë}¨ç}`§†z­Ø¥ó§eËÊùòktSU:u˜½åtaþ    p[¶]n;dë«Õ¸S}ÙHfýI¿b¨õbek¾/½ÆÎW›Î6Óë|°VïÃ2dì7i¹ÞdxX·–¦¢쩞Eˆá›Ì‘ñV¿ÊÀ—@mÜCþè÷…uÌžïµñï:ùêýâ¯eI´Õ¶½mVðm,gU*ó9ðDµ®jªø¶ê2@eKl¨¯Ù[ÎTOH€H€H€H€Ü’€— å IÏÁÃÃÞF°…G#¥úîí«êïèÞ­*<.‰ÎvÛÛ¶×Ѳ¯Zxu|%H!j L•.Ó«›p†äHqŽ)ÎíSR{ÚŒj‘Ö뺌=¯Œë¶ŽHâ¡qéü)½RžÃ æˆ9:Þ{wn©}­â+[§boT$¡`ƒÝnÃgØÝ¬½m¢ÖYáˆìŽÈ€˜$yª×Ú±·Ük¾å…ºyBþФÿ]ß²w|œH€H€H€<‡”ç̵Óû/ þ²±vÉ募BP I€H€H€H z 0„/zù³uâħ7¶µq›—I€H€H€H€H Zp¨hÁÎFI€H€H€H€H€\‘”+ÎûL$@$@$@$@$-( ¢;%    pEP®8kì3 @´ €Šìl”H€H€H€H€HÀ P@¹â¬±Ï$@$@$@$@$@ÑB€*Z°³Q     W$@劳Æ>“ D ¯Ê"£e//¯È¨–u’€Gˆ¤ÿ]=š)O$@$@$@Žˆ4”¯¯¯#ý`Y 7ðóó{C Þ&   ˆl‘æŠì޳~     ˆj‘æŠê°=     ˆlP‘M˜õ“ ¸  (·™J„H€H€H€H€H ² P@E6aÖO$@$@$@$@$à6( Üf*9     È&@Ù„Y? €Û €r›©ä@H€H€H€H€H€"›Tdfý$@$@$@$@$@nC€Êm¦’!    ˆlP‘M˜õ“ ¸  (·™J„H€H€H€H€H ² P@E6aÖO$@$@$@$@$à6( Üf*9     È&@Ù„Y? €Û €r›©ä@H€H€H€H€H€"›Tdfý$@$@$@$@$@nC€Êm¦’!    ˆlP‘M˜õ“ ¸  (·™J„H€H€H€H€H ² P@E6aÖO$@$@$@$@$à6( Üf*9     È&ð=ªkVìÌòÍIEND®B`‚pyramid-1.6/docs/_static/pyramid_request_processing.svg0000644000076500000240000004300112520062551024316 0ustar michaelstaff00000000000000 2014-11-23 07:19ZRequest Processingno exceptionsmiddleware ingress tween ingresstraversalContextFoundtween egressresponse callbacksfinished callbacksmiddleware egressBeforeRenderRequest ProcessingLegendeventcallbackviewexternal process (middleware, tween)internal processview pipelinepredicatesview lookuproute predicatesURL dispatchNewRequestNewResponseview mapper ingressviewview mapper egressresponse adapterdecorators ingressdecorators egressauthorization pyramid-1.6/docs/_static/pyramid_router.graffle0000644000076500000240000010741612520062551022534 0ustar michaelstaff00000000000000 ActiveLayerIndex 0 ApplicationVersion com.omnigroup.OmniGrafflePro 139.18.0.187838 AutoAdjust BackgroundGraphic Bounds {{0, 0}, {576, 733}} Class SolidGraphic ID 2 Style shadow Draws NO stroke Draws NO BaseZoom 0 CanvasOrigin {0, 0} ColumnAlign 1 ColumnSpacing 36 CreationDate 2014-12-01 08:25:13 +0000 Creator Steve Piercy DisplayScale 1 0/72 in = 1 0/72 in GraphDocumentVersion 8 GraphicsList Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169413 ID 169414 Points {202.04165903727232, 501.05557886759294} {202.04165903727232, 528.77776209513161} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169412 Bounds {{104.41666666666686, 528.77776209513161}, {195.24998474121094, 29}} Class ShapedGraphic ID 169413 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.999449 g 0.743511 r 0.872276 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{104.41666666666657, 471.55557886759294}, {195.24998474121094, 29}} Class ShapedGraphic ID 169412 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.422927 g 1 r 1 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{291.21562524160186, 379.55555343627816}, {26, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 169411 Line ID 169410 Offset 7.3333320617675781 Position 0.4865129292011261 RotationType 0 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Text {\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} Wrap NO Class LineGraphic ControlPoints {34.791667904111534, 0} {-33.999994913736998, 0} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169409 ID 169410 Points {280.85416589389337, 398.88888549804574} {327.47912214508739, 398.88888549804574} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169404 Bounds {{327.47912214508739, 384.38888549804574}, {156.62496948242188, 29}} Class ShapedGraphic ID 169409 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{175.11595161998204, 438.9999954213917}, {30, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 169408 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Text {\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} Wrap NO Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169412 Info 2 ID 169407 Points {202.04165267944353, 437.33333079020139} {202.04165903727204, 471.55557886759294} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169404 Info 1 Bounds {{171.708317756653, 329.24978243601743}, {30, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 169406 Line ID 169405 Offset -15.333334922790527 Position 0.45895844697952271 RotationType 0 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Text {\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} Wrap NO Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169404 Info 2 ID 169405 Points {202.04165267944353, 326.72223360222029} {202.04165267944353, 360.44446818033811} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 3 Bounds {{123.72916793823259, 360.44446818033811}, {156.62496948242188, 76.888862609863281}} Class ShapedGraphic ID 169404 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Diamond Style fill Color b 0.422927 g 1 r 1 shadow Draws NO Text Text {\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?} VerticalPad 0 Bounds {{283.07625736262997, 281.88889694213805}, {26, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 169403 Line ID 169402 Offset 7.3333320617675781 Position 0.4865129292011261 RotationType 0 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Text {\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} Wrap NO Class LineGraphic ControlPoints {34.791667904111534, 0} {-33.999994913736998, 0} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169401 ID 169402 Points {265.20833208871704, 301.22222900390562} {327.47911580403627, 301.22222900390562} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 3 Bounds {{327.47911580403627, 286.72222900390562}, {156.62496948242188, 29}} Class ShapedGraphic ID 169401 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.756045 g 0.75004 r 0.994455 shadow Draws NO Text Text {\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} VerticalPad 0 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 3 Info 2 ID 169400 Points {202.04165903727255, 251} {202.04165776570633, 276.22223154703772} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169393 Bounds {{139.37498982747391, 276.22223154703778}, {125.33333587646484, 50}} Class ShapedGraphic ID 3 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Diamond Style fill Color b 0.422927 g 1 r 1 shadow Draws NO Text Text {\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?} VerticalPad 0 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169393 Info 2 ID 169396 Points {202.04165903727255, 196.77777862548834} {202.04165903727255, 222} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169392 Info 1 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169392 ID 169395 Points {202.04165903727255, 142.55555725097662} {202.04165903727255, 167.77777862548834} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 169391 Info 1 Class LineGraphic ControlPoints {0, 6.9840087890625} {0, -9} FontInfo Color w 0 Font Helvetica Size 12 Head ID 169391 ID 169385 Points {202.04165903727255, 82.666667938232479} {202.04165903727255, 107.88888931274418} Style stroke Bezier Color b 0.0980392 g 0.0980392 r 0.0980392 HeadArrow SharpArrow Legacy LineType 1 TailArrow 0 Tail ID 19 Info 1 Bounds {{104.41666666666708, 222}, {195.24998474121094, 29}} Class ShapedGraphic ID 169393 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{104.41666666666708, 167.77777862548834}, {195.24998474121094, 29}} Class ShapedGraphic ID 169392 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{104.41666666666708, 107.88888931274418}, {195.24998474121094, 34.666667938232422}} Class ShapedGraphic ID 169391 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{104.41666666666708, 48.000000000000043}, {195.24998474121094, 34.666667938232422}} Class ShapedGraphic ID 19 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style fill Color b 0.815377 g 1 r 0.820561 shadow Draws NO Text Text {\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} VerticalPad 0 Bounds {{229.04165903727255, 20.000000000000934}, {90, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font Helvetica Size 12 ID 169390 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\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 <%Canvas%>} VerticalPad 0 Wrap NO GridInfo GuidesLocked NO GuidesVisible YES HPages 1 ImageCounter 1 KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo Animate NO circoMinDist 18 circoSeparation 0.0 layoutEngine dot neatoSeparation 0.0 twopiSeparation 0.0 LinksVisible NO MagnetsVisible NO MasterSheets ModificationDate 2014-12-01 09:19:51 +0000 Modifier Steve Piercy NotesVisible NO Orientation 2 OriginVisible NO PageBreaks YES PrintInfo NSBottomMargin float 41 NSHorizonalPagination coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAhOU051bWJlcgCEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAFxlwCG NSLeftMargin float 18 NSPaperSize size {612, 792} NSPrintReverseOrientation int 0 NSRightMargin float 18 NSTopMargin float 18 PrintOnePage ReadOnly NO RowAlign 1 RowSpacing 36 SheetTitle Pyramid Router SmartAlignmentGuidesActive YES SmartDistanceGuidesActive YES UniqueID 1 UseEntirePage VPages 1 WindowInfo CurrentSheet 0 ExpandedCanvases name Pyramid Router Frame {{96, 20}, {1076, 1286}} ListView OutlineWidth 142 RightSidebar ShowRuler Sidebar SidebarWidth 120 VisibleRegion {{8, -10}, {532, 754.66666666666663}} Zoom 1.5 ZoomValues Pyramid Router 1.5 1 pyramid-1.6/docs/_static/pyramid_router.png0000644000076500000240000035350312520062551021712 0ustar michaelstaff00000000000000‰PNG  IHDR&\o)… pHYs%%IR$ðÕiTXtXML:com.adobe.xmp 5 2 1 m ž@IDATxìÝ ¼LõÿÇñ,ÑŠ´iÕ‚’¢i!‘H¥(Y"E«JZø•–Ÿ´—hOýÒ‚´i³µËZ¡É®(J!ÒFÜÿ¼¿þßÓ™1wîÌ]æ.óú>÷Ιs¾ç|¿çyær>ó]N©¬H2 € € PˆÛbÙ € €8> € € Pè&…~ ¨ € €˜ð@@@B 0)ôK@@@@€À„Ï € €ºI¡_*€ € €&|@@@ ÐL ýP@@ 0á3€ € €….@`Rè—€ € € € Ÿ@@(t“B¿T@@Lø € € €@¡ ˜ú%  € € @`Âg@@ ]€À¤Ð/@@@> € € Pè&…~ ¨ € €˜ð@@@B 0)ôK@@@@   €@^¾ùæ[¶lYÜÔ*UʪT©btí¸ãŽqódÂÊùóçÛäɓݩÊä‚ .Hú´gÍše«V­rùwÝuW«U«VŽûnذÁÆŸ0_™2el—]v±êÕ«[… æe# €-P*+’ ºŽ%[àòË/·Ç<Ç“<ꨣìÞ{ïµ&Mšä˜·¤exúé§­{÷îî´¶ÙfÛ´iSÒ§xúé§Û¨Q£\þ³Î:ËFŽ™ã¾+W®´Ýwß=Ç|ʰí¶ÛÚ…^h<ð@‘P–/_nK—.uußn»íìðÃOê<È„ P¼èÊU¼¯µG X ̘1ÃN>ùd{öÙg‹U½KzeÿþûoX6hÐÀþüóÏB?ÝçŸÞŽ=öX÷Ó¡C‡B¯@H]¹ÒãL)d”À)§œœï¯¿þj HÂ-W_}µ)OÕªUƒ|%}aß}÷µV­Z¹ÓTW®t§5jØ~ûí«ë±páB×2áÎgΜiwÜq‡õïß?ÈÇ €é 0I—4å !ºé~çw¢ÎvÍš5vÅWØ‹/¾èÖ¯[·Î>úè#ëØ±cT¾üzóǘºÅKÚ¦ñ¹ tŸh?Ýì—.]:^±. lq3ýÿJ)W®\¢,)o»ì²ËLalš6mšëZ·~ýz·éÕW_M˜(ßÏ?ÿì‚uI+ I^ÿüóO¶×<§:&ú¼ä´/Û@òO hü¯’çÑ@  TªTÉzöìU3 ˜ÿé§Ÿì¸ã޳† ºŸDåÑ›¯¿þ:Ø®|~¹ºûøý®»î:ÓÍé Aƒ¬Q£F¶÷Þ{GGã_´^c.¶ß~{+_¾¼ðÝ»woûöÛo£òêMãÆƒŸ¯¾úÊÞÿ}7X]û«•§K—.öù矻ý4¢sçÎvàºãÖ®]Û^~ùå­ŽùÖ[oõ=þøã·Ú®ú÷éÓÇêÔ©ãê¸×^{ÙùçŸï‚€­2çãŠzõêEùQ+J¸uKE)Q]tŽ;í´“pÀ¶Ã;˜ö}ä‘GâÖFæþúh\Llzûí·ƒíú (Í™3ÇÎ;ï¼ €Õ:Mª u—^z©Þ鯿þ²ÿüç?nü‰ê¢zi,J·nÝlñâÅA>¿ÊçÅïÃ+ €@š4ø„äE òm¼&Ñp?‘…¸‡ŠÌ*•¥m>_=\¾ºuëë9ä­ö½ë®»‚í‘ЬßÿÝå¹óÎ;ƒõ-[¶ÌŠÁûÈMªËù=KÛ|™ñ^÷Øc¬HðUn8ßÍ7ßœ™½j«cD‚Ÿ¬HÐ’¹Ißj›öYQÇ|ê©§‚|‘–†¨m‘-+2¾#Ø._&‘ عÉÚ7»7:fø8Ì.kVûö탼±×`„ Y{î¹g°=|L¿Üºu묵k×F?ôûì³Ï>QÛô&2@°Ýf"­hÁ:lÿªëäÓ¼yó²TO¿-ö5@fEºúìî5™ÏKÔ¼AH»-&‘ÿÑH Pð¯¼òŠEþ… ÒôÁJáÁÍjE‰ý¶;Ü-ìÌ3ÏŒÛ]gêÔ©¦oÄc“¦Ë3fL°ZSã¶mÛÖ4;˜ï†ôã?&캤ñªwµjÕLÓëúôý÷ßÛ‘Géê¹iÞª•Fc5’H~ã7Ú'Ÿ|âíº‹í¶Ûnî½LÔÝʧ°¡_——×E‹¹!ŒðŒi¿ýö›µiÓÆV¬Xá6«›ì4V&¬ø]ì7Þ°¾}ûïs»P¹rekÞ¼¹|ðÁÁ!ÔÂ¥ujÅòI­"rñiÿý÷wõòïÕ§üñZÔ'»Ï‹ßŸW@ 0)wJE Ä èÆùË/¿ ~\ýõvë­·FsÍš5Ý{uÓÑ ¯O~Z\½×±ïº¥÷‘oöõ²UÒ%u©zòÉ'ƒ 套^ òžqÆî[ÒôéÓ­S§NÁ¶ð°òÿt“¬ DÓ’%KLAˆOêòù&ÞW]Žzõêå7¹çŽhÐNIÁsÏ=dÓÌX ts­îbzŸièСÎGFúQ@¨cqÄAw±Š+FC¹çž{‚m;£Dvê†%HKIPµ'žx ¦VäbA]±Æç¦.ö»+èÐ:?>IÝä&Nœè7Û!CL†ª—¼4õ±’>;ñ‚UmËîó¢m$@BˆÜD@< „»rEþ9˶‹ßc‘¹©ÊŒŒÿö‰ ÖGn„ƒõ‘ÖŽ¬È8Œ`[¸kŽŽ¹‰¶ù…È¸Ž¬ÈÍ©ûY°`_íŽsâ‰'Çö]¿|_O½FÆ1øÕîõÜsÏ ö‹ PÏÚ¸qc°ý‹/¾¶ißÈ |°-»®\‘g‡Dí3iÒ¤`-DÆ|dEn¶ƒ<ê6•LŠíÊ>§xËêJ6wîܨCGƹåF™¨mzóÃ?dÉÀï¿ÿýo'7]¹üÎáî{‘‡IúÕî52¾'(ïÐCÚ¦7ª§¯OdLL°=™ÏK™@Bø·_Bä_r PÐÜ­ç˜ø®T*OßÞüñÇ®hµ°hæ' hwãR¬²eËf[½víÚmµM&TÒ7é|®ìúv=@˜fbJ&© W8iÚ_Ÿ4<ܽK]ÅÂIÏÉ)©Æ'Í$vÌ1Çø·îUÎõ¤w?Ø>jc>¾‘ÊдÂJjqˆA ñfÓDª›<•"M¿ "fpèHPh‘@1x¯…p÷-]ëÕ«W›ºˆÅ¦xŸ—Ø<¼GH¯Iz½) Œß\«›–Æ#¨KŽfÆÒ8‘p×-(èˆ †7Ýhjvª÷Þ{Ï4““ºðø”]7.mWü> >ÔÝHÇ‹M»H»z«÷±Óÿ†ß‡ƒ’­vŒ¬ˆ=ÏxyÂÉÑG7øÒxœ¼&š*8|3.›ùóç[¤uÂÍ|¥ñ0 «W¯îÆkÄŽõÉî™3ZïÂõRTâ•­óŒ L²û¼ÄÛŸu €é 0IŸ5%!ºÿôÓOS:WÝ8žzê©nì‚vÔ8}ïoÚ5uî 'œí15q¼ @JDh@´Rë©â³fÍ2=ÛC)Þ¾Ù–†dŽëÇDè°áoûÃÅäÇ ¿Z~tÞátòÉ'»éÃcE>øà˜„ÇÒhµ<ÄKáõ±ûøü‘þ~1xÌâ,§² ÏŠ&,PR ‚ãGϬ‰MÙ}^bóñ@ ½&éõ¦4ÈF@ßÖkPµ’fÒÒ@hŸôM¸ë—_ŸèUœÃ-.zæE¿~ý‚]Ôe¬($µPø¤g¢(8Që’O‘i–ƒ ¿.?_5³X8­\¹Ò½Õ3[4þ×_uïÕbnqÑJ=K$Ü}ËwÓ¶ð.ùåÛ¼ysÔ5Ô3K|Џøm±¯*Ãï«–¤{ï½76 ï@Š©³rÓ Gµ(iš5KÝ«”ôøƒ>œ¢‚–T“f ßð†¡õzh¢Oá|~]~¼&ÓbŒ1"ªhͦ¥›ú‚J >Â)<.¦iÓ¦Á¦Áƒ»ÙÉ‚‘]#?ÕÖ7kÖ,Øo£&ÜMXxöµ`§8 Ú7œ"Þƒ·7¤ÓÂ)òÜ×2¦Ö±DÝÿÂû°Œ P4h1)×Z ñú†]]І æ,¾ûî;÷ªézõìŒT“º…Sdö+ÓMëìÙ³Ýô¼á“Ø'‡÷ËËr2‰žrØa‡¹z©,=ÓdæÌ™n_½úiróRDûFfÕŠÚLô,MyP¥k9јîÝ»»1Cš²7D)ˆ &õë×:®&"P  1 J4rv)ܽMÝùzè!×åL]ó4VFA’žF¯ŸÈìjÖ±cG÷œ™×^{-ê¹5j%#!€“âs­¨)%^@­>0ñ'›Ûo½ÕÍG–HäI㦟4†%òÄw÷V¿uÃî~äóô«º¨=üðÃÖ¢E ×5J­7 F|@²ãŽ;šf2 ?»#?ë¤ 2]²­[·ÎVÏcñIÝÌœôéÓǵ>)˜Ð'c“‚ÀAƒE­–½Æ ùît±­`‘)£-25rÔ>þƾø¤Ö"#êZ¦Àd×]w5=3åœsÎq-I \âÕIãObŸãÉ+ €@Ñ +WѼ.Ô ŒÐ7îá.@BwÁJE7üúF_ßò‡“O롈á'Âëæ÷¾ûî gË—ådZLTºé&}¿ýö‹*Wu;vlT‹Q²ÇŒ:Po¤ù¤@"üún¸ÁMÛ~һϫWµ„hªáp+­W=GŽéfa ·€(ÐR ï{N_Ô³gO‹mÍÑq•Î>ûl7ÁB¼‡OjJéÛo¿Ý4ˆ?\î–=ù P”JE¾Ûzº”¢\cê†%Z@Ýr†îÎQ7žS§NÍÓùêŸ8=<ò B;äCÜ úTÒç©)ì¬é›o¾qݺt£¯€¡¨ÔUŽ˜¯ÙÌ4@¾fÍš® Zì•x§«) Õ-M†ºæÅ›)+»ýÔ]K­9jÍŠ Ü´ê¤îy¤//]ãòåËÇ;ë@ЏI¿@TLÐ@gÝûi‚5–@cH € PòLJþ5æ (òS¦LqÝ~&L˜`Ó¦MsõÕCð–/_nêúCB@’/Àà÷’9C숦’Õ¬Yátýõ×”„AXF@ „ ˜”ð Ìé!PÜJ—.m;w¶ë®»®¸Uú"€ €@èÊ•׊š"€ € PbLJì¥åÄ@@(>&ÅçZQS@@J¬I‰½´œ € €ÅG€À¤ø\+jŠ € €@‰ 0)±—–C@@ ø˜ŸkEM@@(±&%öÒrb € €“âs­¨) € €%V€À¤Ä^ZN @@â#@`R|®5E@@ Ä ˜”ØKˉ!€ € P|LŠÏµ¢¦ € €”X“{i91@@ŠIñ¹VÔ@@+@`Rb/-'† € €@ñ 0)>׊š"€ € PbLJì¥åÄ@@(>&ÅçZQS@@J¬I‰½´œ € €ÅG€À¤ø\+jŠ € €@‰ 0)±—–C@@ ø˜ŸkEM@@(±&%öÒrb € €2YÕÓN;ÍÆŒSEpl@b&вeK=zt1«5ÕE(hRY‘TP…”*Uª Íq@б@þ×SŒU¨: ÙÚbâiçmšçyEÈ`¥kdðÙsê €‰c’H‡m € € “´0S € €$ 0I¤Ã6@@H‹IZ˜)@@ ˜$Òa € €¤E€À$-Ì‚ € €‰Lé° @@Ò"@`’f A@@D&‰t؆ € €i 0I 3… € € €@"“D:lC@@´˜¤…™B@@@ ‘I"¶!€ € €@ZLÒÂL! € € H€À$‘Û@@@ -&ia¦@@H$@`’H‡m € € “´0S € €$ 0I¤Ã6@@H‹IZ˜)@@ ˜$Òa € €¤E€À$-Ì‚ € €‰Lé° @@Ò"@`’f A@@D&‰t؆ € €i(“–RR,ä·µ¿ÙWŸ~e³¦Í²9_αʻV¶š‡×´CêbµëÕ¶Ò¥KGq颥öɇŸXý“êÛ~íµ­ ÞL›0ÍV._iÍÎnfåÊ•+ˆ"2þ˜c_kYYYÖòÜ–9ZøëѼms+S¦è|¤×®Ykã^gßû½u¸¬ƒí¹Ïž9žKQÌ0ñ‰¶bé ;»ëÙEÊ·¨Y}<æc[4w‘­YµÆÚvm›–‹ŠšõA@ /Eç.îÿÏâËO¾´KϼÔÖü²&îyÛäXðâ«\¥r°]ÌÍ—Þl÷>woR7ï¼öŽmܰÑZµo#•…Á÷¶ c'Ø'+?±r»dv`²lñ2ûìãϬαuìÀš¦Â˜0ï [Ù?ÿü“T`â¯Ç§Q 7Ωœ«‚ª¶õÛš‚f¥&§7)I¼¿ç~Þ}Þ[uhU ¾ ?iܨàâ­ao¹µhdG6<2ÇÒ{uìe£GŒò}ÜÑIý[ìâBnê˜bdG@ íE*0ywä»vÝù×Ùßýmçt;ÇÎìx¦Õ<¢¦©eÎsì‘~ØÔ§ÚÙGŸmOyÚ:ô \ÝÿŸûÝ1s˜ÔoTßvª¸“•Û6³ƒá1õ »±ÛvË÷äk`’Ê…­Û ®k¹ŠmIKåÉäMå\}³È%Gw”ÝõÌ]¶×þ{%SD¡çÉëßF¡Ÿ@>T`ɼ%öÄO¸#U¬\1ÇÀD-b>(Ùf›m¬ÖQµl§J;åCM²?DªuÌþHlA@ è™ÀdÕÊUAP2è¥AvjÛS¥wÞѪî[ÕN<õDëݵ·yiŒõïÙßž}÷Ù O:º]ß-ÅQV—÷½<‡éßüÓ?¹B>±`¿9Oÿ™Qb¬ÀË~ Vµ»¸ÝöèmÁ{@@ y"˜<;èY×RÒ²]˨ $|*j¡Ð7óêF5õƒ©¦n_uÔ g1}S=jÄ(›?{¾íðþÖüìævø1‡»<úfS}þ×­YçÊzú¾§Ý¸•ãšç¶oÞ¼ÙµÌL~o²Û¿VÝZvÔñGÙ¡GjeË– ÊQ_ò¾ûÁÚ^ØÖµšüüãÏöæ oÚ1±ƒkloÛf~6Ó*l_Áôy8È g!ÙòãìêV}øö‡¶xîbSàôÝÂï쥧^²'5p2¬\±Ò^îuûæËolÓ¦MnÜŽn¤ªì^e«C&“WÝ]>ý‘ÛwÊûSìÏßÿ´ŽWt´ ÛUØêxáï¿ù¾}òÑ'öÝ‚ïlïj{›ü›žÙ4œ%X–‰êº¶Ûý\Û¶ü¶A=´><ÆdÆ .ˆ=c¶ýõç_vôñGÛ‰-NŒê$²0æå1®^?|ûƒí¹ïžÖê¼VÎOyR9×F}d“ߟì=oæ<{ñ‰­ÙYͬt™ÒöÚ3¯Y݆uÝgR-„jìÿd—W¿’±Qw+u+ëÚ««éø¼õ×ФUkФ3ÐgAÆþñ§Õ©_ÇZwn½Õج ÐÈBN>ïŒÉ3Lß.øÖ­s¨vÞiq»¨Í›5Ït]|½ÀªU¯æÆéo!™ôäÝOº¿¿íwÜÞn}äVùìH“•>#ú¼*-_ºÜëÿ˜û,«.¿S½vu»°×…vØQ‡Å-æå§^¶÷ßzß™mø{ƒÕ¨]Ãê7®oÝnèü}?7è9ûtü§ÁþrüiùOvѵÙ®{ì¬÷ Ê/ŸôY»ûú»íôö§[­#k¹ÕªëS÷>e ç,tcÓTOµò5lÖÐý ú}ýëËO¿lÆM°…_/tÿ†xÈÖþÒöîßåI¶ŽÉœ¯Ž—È[ŸŸYÓg)›sÑ9Á߃ÞëßXí«tÄ1GXç«:»e~!€ kH_øK‘JeégÞ¦y9þìVu7—wÔÌQ9æízMW—÷ªÛ®ry àÞGn’²"7ÅYeË•ÍÚe·]ܺH׊¬>÷÷qù†¼3$+`¸õª—–;õèä¶Íü}fV¤/¹Û¦}#FV¤k{éN–5}õô ^‘›[·>2ÆÄ­{å“WÜû󯃷?q{T³þœ•uÊÙ§¸c†óúå#ê‘5eÅ·¿.~›Íîߥìòß÷ü}îxºîúüùã„_õyäµG‚ºê3à­Ãùür¤%&©:¦r¾rOä}û“·u×ßnø:éoÃ×íΧïŒÚλì÷)°ÿx80 €@±(ÓëÛlÍrùÚªÕ¨ù+qÒ7ˆJË–,‹Ê¨~Þ]zv±Ï×~n‘ ‹ Vq—Šv_ïûlÉü%ùØfÿ5Ûö=p_«T¥’[¾yÐÍîúVþó)Ÿ»Ö†ÉË'[äFÄfü:Ú¶njs¿šë¾±*,Λ~Á}«:ñû‰6mÕ4{cÆn,Ê3÷?c7nŒ³Ç¿«ò£|´¾÷µî7t·'¾h§µ?ÍÖÿ¶Þzž×Ó6ý³É^žò²}¸øCS‡OnÿlüÇz_ÐÛVÿ²ÚížJÞaã‡Ù=ÏÞãöë;¨¯óÔ jÙ¥!q-‘›dg;zÖh÷zz‡ÓÝ·þþÛW¿¿Zm–.\êuMt]Ï»ä<÷Mí7>à³Å}íÕ¡—}ýù×nB„HPé®Çw‡Øïë·óO:ßÖ¯[ì7dÀS+D$8±OþÔ&ý0ÉÙì½ÿÞöèíºÖ‰TÎõ©QOÙS£ŸrÇ¿¦ÿ5Î%ܲ7ü±á®%ã¡—²asùRµÑNÚ§ÿàþ¦ó{ò­'ÝgzøãÃ] ƒÖú˧öî¼wÝgRŸ/Í•]Jô·á÷yåéW,rÓïþ¶t-"_¸–‹7žÃgqßôßuí]n29N\6ÑÕO-¡ÃæZ9‚Ì9,¬ûu ¾{°Ë „¬LÙ2öÇï¸Ï«¶)iÖ¶›ºÙµ(”*UÊýõ»²Ÿ»f.Cä×°G‡¿»ïµ»õì×Ón¸ç׺©<špÀM\vµ¸¨%Ô'µÒÜ8àF«²ÇÖ­ŠÊ£¿³ z^à³»Ö?µêjö@%³ÿ¬©u©ßãý‚ã«ÕR×Ч¡u­Áz¯zê¸áV¦þW÷·Õ?¯v­B‰ê˜Êùú²õÏ[‘`ÏeSKµêì“fkSŠÆ üüj^@ȵ@‘LÔuFIÝgÂ]q²;+?øÇïÿíÛ­¼êtÍí×Sø^ïp‹|Këþ3Õú‰’ºv{ò±.0Ñ Ž’º$uº¢“[Vw‘œÒ{ïa‘o@-ò­¼ËªéŸÖØÍ.¥Áª‰R~”ﯛ©ëîºÎ ÚÕìeÏ |ÎÍrù&Ý"ßûlî¦çÊÛ®t7¬ºYVJ%op $ ¾g°ëúÖï‰~ÁÍŽn8ïr·EZ\·u¿ 'm“£’ºòÝôàM®—ºVù`*œ_Ëã^çn6µldgv:Ót=õÓðä†ù–×'§¤¤@LÝl4ÈYƒÔ5žIyuC¨ïÈW®»•ËœO¿ts­@B7}êú—[Ý`«{ õ9ó³G]Òç·~û¶w3Cµ8·…«¹켤ž·÷tÝè"-îKß­jîÌ{Ïõ÷¸"t3ï»>É4Ò‚à‚ôWŸyÕÔ=/™¤|úB_|ùÛ—®«Ðˆ'F˜ºN*iòŠHë ûÕ¸?ÖH_Oßñ¸[Ö¯Hk ]vÓevÑuÙKS^ º¾6ä5÷E‡‚‡“Zä´”™þn*íR)X^P7Íf­›«Ô}Kÿæ¨ûšê¯£­{¨µéÚÆ"-»®+š]¥³¸Wåõîu£iÁ²HË“ 5ˆ’f©S7³DuLõ|ÝÿÿW÷JßÎÿÖ½f÷Ký£5˜^7 êW­±*ê#ï¿Å× jNIÏQ }P~Ý (iL@¢”åûãëf<œ4Gé˜FÇ„W»e¨¨ÿ»R*yÝIþZ4g‘ûæ¸î±[fÐ ï¦`T3i çe‹þuRë‹ò‡“‚Ý€ëz¨5%^úlÂgnµnõ-pøÇŸ¯Æv(-þf± ©{Hp³è6D~u¸¼ƒÍX3ÃZŸßگʗWÓ>ì+76Ú¹Þ‰õ‚chA±’,ÃÉÊj•ÌKŠýüøgù)‘Õ"£Ï‘fËÓù…ÝÿXÿ‡yÜ‘îF]­—ɦHw5¼ù/ ¾ütËgYû7nÙ8ê0á÷jQÒ8&ÝD+©¾þïQïxC}žfOŸ­Õù–ôïÙ ¾`¯OÝ®¿çz0«î‚f¸Ïœ R‹¥’&KðõÔL„¾UXÛÔºóöWo»‰I”òz¾±Þ*+<{áøÑã]ñg¤@F)¼Ý­à €¹(“Ëýòu7͸¥›Óåß-w­êÒ•(}¿ä{·Y]²Â)Þìt#ªãùY’ÂùÃËú6òƒ7?0u…Ñ aŸ"ã1übޝñ¾UÕ€g%ÿŸxvÉòý±5¸6œ4^7ôþ¦<¼Íßið³R*yÃÇÉiYÇUò7‚±ùU  W=|â]Oí§\Ù]S„ª‹ZvIßþ*ù›j=À36éÆR­ùâ]•‘ŠòGÆoèåß´¥¡¯@ê¬BÔ½(œä£€Á¶½»‚“z»DMáýÖ®Þ(„×e·¬¥pòŸ#­‹õR@¦Ö#u UЪ`#Q~C"h ¹’nêó;ɢυ}\Ðã¿Ü™þ½Óß¼Oþs¨÷á Uï55¹~’Iy=ßXo•©gðl·ýv®“kï¼Ö|7.­·0%SGò € @‘L8(ÈX‡p°ã—ÓùÆzk?{šUîÍ¡oºñ]š‘pÒ;“Ü!5OÛI €ä‡@îï¤ó£ôÐ1|?êGû?Z»õ¢¦æÔô•ê’ÑäŒ&QØÄ¦é§»Uûøo×™Ø<êB¡)~õMýˆI#Ü8Ý@èâØq,±ûæÇû‚.¿ƒ÷sAÉ´ Ó¶ª®ïºå[ŸRÉ»ÕÁ¬Ðq•Ô*›ø®7¾Ê£®G›‚kêγõýÝ[õÕÌTõsBóLÓÐú Æç Oë%¯KθÄ4elA¦ÜØd}r{lo©ÀQãgbíumeŸ—‡úÖ4ÕÑOÉìë«)Ä}òù¨y€_å¾ôP7Íp ý>áíyYÖß–‚%M×|ïs÷ºàD-ıÿ®„?÷je ÊÏ xÆšUoæ~Ô…*Q*¨ó w×ÒX1ML¡^Ÿ¨^lC@ "˜hÀ¨¾ýÔÂ×Üw+ ޽ꜫÜy]yë•[=—A³riÖšpòÝ4ôùpR«€O¿®ÚÒ­G7MáÁ÷jaxã…7\¶ð‚ß/¿^ ºüã›ïªúñØ£ª¬s?f¼[™ Ô½¦’7|°°gx½_ŽLñëC=ãëàfÍoÓÕœ©KHì Ú¡õÙÜ뢹‹Ü3dô|–Ý«Fw-òO8å·øá¨­ŠGnĺŸÖ=t¬V7uÓgËßlùã ¼e ©ëJl—²œÎÕïŸìknm’=~ªùr{~(­Ö5Ñ5 'µ~ulÔÑ48>ü7Γ̲û«õAI³„©Û¥Zštýß»e/mÓ3m”Ô½ÒÏ¥|š1L“&¨5ãÉ»žtŸ%åS&=_&6©u <UìöDï}P¢<Ûí°Ëªc=;ðYûý·ßÝ{ÿïŠZðd§¤Àî¡Û²ßÖþfŸ}ü™i¶?µ~è߉ØîkÊ®c^ÏWÇ‹—4k›oÑóQ”T–¾À!!€ _E&0Ñ ñ†>àn^Ÿèy;ûè³]€¢‡é?ò^{Y‡FÜìRš™I3ÓÄ&ýg᩺):uCyc·ÝCóŽ?åø¨îaúfT7ÂúÏ_ßÔ«O·‚"=Q7£zÕCݺž²åv*GŠKeÐnlݽ/èòÛ_ÖÞ£¦Õ4»ú&Wc4Ô÷]ßÀª›†(]*yuNþ[æW‡¼jšÙèï¿þŽ{ªêî¡YÔuªÓIÜô¼zУ¦±íѦ‡›¥K3f…“ú¯ëÝÙëNÓC uCtaó ]@ªÙIJKšK3 éô¾—ôu7wjeéß³¿ij` žW%u¡QŸyÝ jP²ZÎ4uðŧ_lÚGÇò³‚%{®ÙÕ+»õ¹±ÉîXy]û·‘êñ4u¯’¦§V×MÙ<êÅQÖþ„önR‰î½!ÕCFåW ©)£•tãߥi;¬üavæ‘gº‡‹j½nÞ5°Oš¡N_:(½4ø%;v÷cí¨JGÙ€¾Ü:u%Õ X~l›ºpú¤  v…Úîï߯KöUSDûcêo¯mƒ¶vܞǹàÈwÒ„>ðÑÔÒ>éá‘GW>ÚÎor¾ëNªõšÌwùJTÇTÏ×—™èUÁ¤7õÝ›·mž§ 3QylCÈL2Eé´5¶ä­/ß²›ºß侩֓…ÃIÝ·n{춨§‡·kÊP=e[Ï1ðI]Jü³6üºvÝÛ¹§?k†µ°hP¸¦½úÜ«ÝÔ¢š^T7ú¦õñ7·k:\ãê£>ôzŸßIÇ-ÈòË•+gC?j×vºÖMÙ«®>é&ï¦7ù·nªådój'M…ªÙÐ8(Ô´µ±3“ùƒëÙ z–ŠZÄ®:wKË—¶i÷ÐñCƒàÈç¯qx Ó3Nìû {ÚµÖkðàQƒÝ eŸ/ÞëÀ펞w¸oÕõͺ’n®Ô2×ãÖQ7T*CÉ­—Ýj×]p8uSÑÔ®>¥r®~Ÿd_SµIö¸©æ‹÷·‘Ê1ô·¤©n5ñÀ ]þ BðèoGSËæ5éiðêú¤gèÆ>œ"LµÈƒƒVmÓl\o~þ¦û;öSÜú}ô¬š†=`áçÌh Œ®‡m}.8ø– ¿_2¯*WuU`­ù*[­ ú÷HÝ5u²Ze¸ùñ'‘‡:»ð3gô‘ëî¾.êy)‰ê˜êù&s.Ê£¿=—Å'=Ýž„ €@~ ”Šü‡›ó<¸¹,Ñw¹ˆ<ù7å#è›wµR¨¿µZ3ô|€Øn>ÙT]r~½ÐjÖ©izŽG¼¤ÓÖ¾Gâo¤õ| ÝǵԨå#¶[–?†^u“¨ÏºùèAŸÙ]ëTÎ5|üd–SµI昩æñ×>ü·‘ê1ô<ù³æ»¿1Më­nR±×-ÕcÆæW‹ç.v³iéºTë ­®ì>êR¦g‡èßÍŽ¦ñþߩؼʣ/.ÔB ‹ÜvAS×±¹_εʻU¶µkd[ž/_]Î4Ùž}¤g;)@×— ñRNuLå|ã?¼NŸ‹:;ÖqAÖÞÕö¶÷¼Ÿã¹„÷÷Ë5J×p‹: @°@‘ L•dDwtÝ®fý1+èê’(?Û@ uusô-Šz¸§T™›D`’5öA2C HuåÊ rÎ2¿ÔâõÁ[˜ÔPã‚’ü‚å8„4íÒ3. ¦b®°]k{QÛP@È"3ø=N‡£d’ÀŒI3Ü@b==û’>—dÒ©s®¤M@³´iŒŒ’ºãi¼‹ºç‘@Èoºrå·(ÇK›€fV[±l…£âg9J[á„@†hLÕ—S¿t3›iìU^ƒºreȇÓDr!@`’ 4vAÈIîÜØ ȺreÂUæ@@(â&EüQ=@@2A€À$®2çˆ € €@ 0)âˆê!€ € &™p•9G@@ЏI¿@T@@L 0É„«Ì9"€ € PÄLŠø¢z € €d‚I&\eÎ@@".@`RÄ/ÕC@@ L2á*sŽ € €q“"~¨ € €™ @`’ W™sD@@ ˆ ˜ñ Dõ@@È“L¸Êœ# € €E\€À¤ˆ_ ª‡ € €@&˜dÂUæ@@(â&EüQ=@@2A€À$®2çˆ € €@ 0)âˆê!€ € ¥²"© N´T©RuhŽ‹@±Øa‡¬téÒ¶víÚbUo*‹@A à=UeŽ‹ PÀÚbÒ¢e‹®>‡G xT®\Ùô£à„„@¦ ´8ÿ2ý3Àù#€ñ ´Å$^¬C êÕ«g«V­²éÓ§»% 8g@@ ‘@¶˜$*˜m € € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"€ € €@¡ ˜=#€ € €€ 0ñ¼"ùóçÛ²eËr}„ß~ûͦM›–ëýÙ@(î&Åý Rÿ"!pÐAÙÕW_«àDAI×®]­ZµjEâ\¨ € P&…¡N™%N`›m¶±cŽ9ÆÚ·oŸRp¢ ¤sçÎV±bE«R¥J‰sá„@@dL’•"9´mÛÖ–/_žtp⃒¯¾úÊÚ´i“ÃÑÙŒ €”l“’}}9»4 ¨;×G‘TpJÔZÒ´iÓ4Ö”¢@@¢'@`Rô® 5*ƾå#QËI8(Ñ©žqÆV¶lÙb|ÖT@È»IÞ 9€‚Œråʹ÷ñ‚“õë×»1%ê¾å“fü{^@@L(•I™xâœ3%УG3fLpøªU«ÚŸþikÖ¬±ZµjÙ×_l«^½º7.xÏ € ©´˜dê•ç¼ L@ƒàÃI-'k×®u«ÂA‰VÄæ ïÇ2 € I´˜dÒÕæ\Ó"°yófkذ¡­\¹2ayšbø“O>ašà„JlD@L Å$S®4ç™6­[·Î±¼Æ”ä¨D@È“L¹ÒœgZ|­R¥Je[.ƒÞ³¥a €d I^tN¹àü3M²›[‚g—ü5 @(^&ÅëzQÛb$¨E„g—£ IU@@ -&ia¦L?Ó$öü-±yy €d‚I&\eαPvÚi'kÚ´éVeëÙ%µk×Þj=+@@L 0Éä«Ï¹¸€.(Þºðv–@@Là9&™xÕ9ç´ Ä>Ó„g—¤ž‚@@ ˜ ÐbRÌ.Õ-^±Ï4áÙ%ÅëúQ[@HŸIú¬))CÂ]·ôž¡N@r 0É‘ˆ äM@Ï4©S§Žñì’¼9²7 €”l2%ûô8;І€ZJ,X`eË–-¢ € €@`ð{» T§d ¬[·Î–/_n5kÖ,™'ÈY!€ €y(Г–§µ´±cÆæ±ŠìŽ P’Z´lacF)I§Ä¹ €äƒ@¶˜”*U*ªÈ!@Jš@VVVI;%Î@ Úbâë6oÓ<¿È+ €@ Ô(]#ƒÏžSGH$À¬\‰t؆ € €i 0I 3… € € €@"“D:lC@@´˜¤…™B@@@ ‘I"¶!€ € €@ZLÒÂL! € € H€À$‘Û@@@ -&ia¦@@H$@`’H‡m € € “´0S € €$ 0I¤Ã6@@H‹IZ˜)@@ ˜$Òa € €¤E LZJ¡\ ü¶ö7›0vBÒûÖ¬SÓ¬y`ÒùÉø¯ÀÄw&ÚŠ¥+ìì®g[™2™ógñí‚oí³ñŸÙq§g{í·×¿ I.}>åsçÖ´uSÛ¶ü¶IîUô²eêõ/zW‚!€d²@æÜë¼|érëÕ±WÒ5¿î®ëL’Ðzçµwlã†Öª}« ÷ó?ï‚ÀVZeT`òÅ”/ìæKo¶G^{$WÉó=oc_k“~˜d»î±kàYÜ2õú·ëD}@J¶I¾¾{î³§ÝûܽQ5üêÓ¯lØcÃì¨ã޲v·‹ÚvhÝC£Þó&¾Àýÿ¹ßÔLâçd- € €@ºLÒ%‹rvª¸“Ùę́=Ë”-ã“}Üg«mQyƒ € €@1 0)F+™ª~øö‡¶xîbëv}7ûnáwöÒS/Yƒ“؉§žèvß¼y³ÍùbŽM~o²ÍŸ=ßjÕ­eG”zä¡V¶lY—ççß°_~úÅNïxºí^u÷¨bW®Xio }Ëö=p_;åìS‚mófͳÇ|l ¾^`ÕªW³ú'Õw­:A†ÈB¢ºeeeÙè£í“>±Õ?¯¶ý«ïo OnhÇŸr|ønyÆ 6æ¥16{ÆlûëÏ¿ìèã¶[œh•«TÞ*oxÅ÷ß~oã^gëÖ¬³¿ÿúÛž¾ïi;¤Î!v\³ãÂÙlÆäÎGã/­s¨vÞi¦Ö«Ø”Ì9Çîãß«;Ù²Åˬk¯®6oæ<ûà­lͪ5Ö¤UkФëN&/yüùÇŸV§~kݹµ•.]Ú½êz¼þÜëöÍ—ßØ¦M›¬æá5]KZ•Ý«DåÓ¿®ÑÂ9 ­Fív|ó­mýN¹5öûg÷:~ôxûlÂgVa» î3Y»^m÷¹(_¡|p­}Nt\9Mý`ªMù`Ši?µyÜ‘QŸU}öõoØ´¡Uؾ‚}ôöG6mâ4«º_UkyNKwݳ«c²×?»ýY €¹ 0É[‘Ýë­ao¹>ÿº»¦ý5®ž{>ÕÜÍøÍ.0 XÞe·]¬ò®•mìËc·ÜÐQÓ†~4ÔvÜyG[0g»iW°Ðý†îQ窠尛ݬvà³v_ïûìŸþ±ÝªîfªƒÝjÖñòŽvÓÀ›‚›éìê¶ù”Ívé™—º›æíwÜÞöÞowãù¿ûÿg÷¾Ø®½óÚ ,Ý\÷l×ÓfMŸe;ì´ƒ•*UÊ^yúÛmÏÝ쉷ž°ZGÖ òÆ.,]¸ÔÞ<Ð6nÜè6i¹Ý%í¢“GoÔ»rëØ –ÔunØÇìê¾UƒC&{ÎÁ1 ¯yÕiQ01à¦V¶\Y7îeأìÃeÜûç=ç‚EÕ÷å§^v7Öw?swp$-ºÆ ä4p]ãfÞù®i¿ûž¿Ïµläýtü§vùY—ÛúuëÝͼòªÌæm›yüB^Œý1b_ö¹° (µM¾ô{ÄZœÓÂf~6ÓöØ{ 0Éîs¢Ïð{o¼gWs•ûLíwð~îÜ_xøWÜ]ÿ»Ëξàl·<{úlûoÿZ§\ ýÇú?Üg[A͆Ø-ßbçv?×å ÿJöú‡÷a@òG€é‚óDZÈ¥ïÅ}]PñâÄí´ö§¹úé›u%jM™¼|²š9Êfü:Ã4£Òܯ溛Zel}~k—_ßêǦÑ/v7Ë$®4aÜ»ëÚ»¬Î±uÜè‰Ë&ÚôÕÓ­e»–î†þ±þÅÂb릛i}“¯òi«¦Ù[_¾e“–Or7ªjÕXµrUpŒ^zÙןíÆÞ¨åòîû}ýïvþIç»ï sÌ‚¾=Ÿý×l×ÚS©J%·°”]AÎíOÜnSVL±Ï×~n]¯éjš„@™O¹9g¿oìë‡XÿÁýÙ“o=iª×ðLJÛÈgGºõŸþò©½;ï]7°\×O7ÖJë[o=Ïëi›þÙd/OyÙ>\ü¡Mü~¢ Ÿ0ÜþÙøõ¾ ·­þeµË»î×uvÙ™—™M r×yéºw¸¼ƒ½ù›.OøW^ŒÃÇ /+ÈT+WËs[Úû ß·¯Öe/|ø‚͘4Ã~øîW·p~-Ç~N´î¶ËoÛPÏk£g¶©?N5}Æ•ºí!÷þ5ô‘¡®¥é“•ŸØÔŸ¦:ŸråËY¿«ú¹¤p^-'sýc÷á= €äIþ8¹£4=³©i–®#tqÚð÷;öäc]`¢Ö%u©étE'·¬®KJ×:Ø4^­ºiôiñ¼Å.€irF«X¹¢[}Ïõ÷¸×ÜÌʤV}c¯12¯>óª©ûX8ÅÖM]Ê”ö9`Ÿ ueû¶·ëï¾Þºôìb?ÿø³Û>îÕq¦Áÿj ÐØƒ~Ôå«Ç-=\p¢›ß¼¤ž·÷tߤ«5FݦüsgÎ ››svŽYèÙ¯§sÑ9®£ñiÝõR–Kú\âÖËa¿ƒö³ç¶p{*€Tznàs¶æ—5Öåê.vDý#Ü:ýR·¦+o»Ò0ÃîÖëæ\ÛÕÿ½ÚšµnæÎK×½÷½½íðcöÕBA+ˆR€©–°;ÿw§íSmwÝŽitŒõÔ7ªüð›ØÏ‰,}6Xë³â“>ãꎸbÙ5ª¿j@IDAT Óg<œ”¿÷}½]k‰>+òés_׺¤ÅbS2×?vÞ#€ €@þ˜äc‘;Jì yU°óUíÙwŸµJ»Trã}³ÈµTæw—Û¶\„„7xÈ–‡lj¬ˆ’‚ªí¶ß.hå çõ_çÏ/?—-Zæ¿ÿÁûûb‚WµJUÙcëúÊû9ѺeK–ÙKƒ_²Wÿ÷jЭM­J/c¿+GtòExíÞÕövÝ4Ǧœ®l~Þ#€ €@þ ˜äŸe‘?RŸ®}ìíáo›fBR7)ÍH¥Ö¡8ïøóÜ ¸? Œ×LWš!é§~r7~¶/?3”ÿ¦»öѵí¢ë.ò»nõï†46“Zs:^ÑÑ zŸüþd?j¼ëV¤ÀèÑ‘ÚÉgœ|³~Ã=7¸Ù•b¡÷ÔŸ—TºLé„»çç9« œÊKT™Í›6»  „Ów ¾soýÌ\ ¬4³—Z—ÔêNüþGømûsŒm-QÁšýkõÊÕ®›WTEâ¼Q—¿³ëíZ@4c\ƒÆ ÜìmꮥÉ4 —Î5œbm´Mcp4¡@¼™Ö|]ÃÇ`@Ò#@`’çB/eíšµ.(Ñ·é#&ˆêÞ¢ññ’Á+0QpðóO[ÆyœÝeˬGʯ)}•¾_ò½5oÓ<¸©u+#¿4H]×wª}3ì·ûWMϪAÜjÑôÀúÑøO¹©ûM6ò¹‘.0Qyß™è¾×lNá¤Ù¦Ülc»ç-0 3Þr~s¼c§²N3R©ÕdÚ„iÖ¨E£¨]}7/M鬤:küÅgf·N7Naìǃ|·hKÀ.oá× ]p^—ÝòËO¿ìƆhf²Ø‡cÆkýÐq4}ulò]¸ªÕ¨»‰÷ € Pˆñ;wb…(º`~]µ¥‹“¦ã ÷¹W÷®7^xÃÛ?ÿ¤V'¹oØÇ½6Îͨ¤Ò¾›vعÒΦÖ»P_ÿpÒ€õŽ:ºî`áòÂyü²ºpu>¹s0Èݯ¯{l]·è[hN8å÷þÃQú,Áë#·?bÝOën fo}#d -¨µ!7)¿Î97e‡÷9¾ÙñîíÇc?¯v³[3Þ­;£ãîõÄæ'º×ؼ”þú³¯»mþW~ûcPó7Û˜Æ(ùYÅ´MÝãÝ:ÈgËñÕ†c]Mˆ ñRJ±ŸaÖ±Ýûžyð—×¾Ü~!€ €@¡ ˜ú%HOô­µÆè™oè^5%m×S¶<àOµÐ·Ëáê꣇ jJWµŠøAïáßüÐÍî­¦®}sè›®•dÔ‹£¬ý í]÷¡î½!œ=î²n uC©çShì‹Æ¼ÿæûvc·]~M1«¤Ù¸Ô½L×ë{I_×0}âtëß³¿ 0ÄÍh¥ÁÔ9%WjAÒô²º©M5åÇ9§Zflþö—µwè¹'z®ŒZI4È]Ï Q—¦fg53?©€žå¡qzЦvVˉnØ;5îde·ÝòPMüü2öÇÓ«ºp©ûÝok³v Û™f Ó³G.l~¡}õÉW[Ƈ„wÈfÙ5ºÿ?÷»ç™è|ØèÙ<~lˆÎ=Ü=M“\Üêb7ݳòë3¥)’už±­nÙËj@H“]¹Ò]ØÅhlă/>hWŸ{µ=~ÇãîG-zzûão?Î9»c'»¾\¹rî˜×vºÖß™0 òãÓy—œçléß+ÀÔ3C®l{¥éÁúQÒôÄšBXã3Â)?ŒÃÇÓ²|¨'°ëa’·_}»|~HÝClèø¡n܈®uNIÓ*«eNu6=\vÍVöÀ°ÜçS›¼¬õeöúô[®¸ù ×ý¯w×'LP€sÇSwäTÛ@@ Í¥"ßTgT™~ ê¼Mó ªŽ›¢€¾Mž5m–k¡P×,ÍÖ䓺ÃTÜ¥b®kóüYómÙâen殺 ëÏ$ñÇÏéUÓ¿jÌ^5[•¦ Ö üxIyæ|>ÇuÒ þx™ãíç×éc¯ºê›ödnŠý~á×ü8çðñr³ìÏcÞÌy¶så­æ5·àî«®SjÓy×:²VŽfy5ö対ªå¤üvå]p¢Aùuv¬ãÈùàðc³Æ}¯V½%ó–¸n…áY·4ÎH3‘i0ü¸WÆY¯Ž½ì¶Go³ö—¶w8ÌùbŽíµÿ^Vý°­g¬‹[+ D Féî¸ø_OÔ›ƒ"€¼IÁS-°iÓ&»ºÝÕnºb=å>œ4±ÂUç^ez@§™_IãYÂI~—ãä]€À$ï†(©tå*©W–óB ˆ¨Ëà_üe¯¼þŠk)9½ýéîÙ%»tÛ·¹ñ/§w8½ˆÔ–j € €@a ˜–<å"Aw¹Û®ï|½xr„øîO½ZõjöÌ;ÏdÛeÏçã@J¾]¹Jþ5æ (2ýù—i&5ѧªûV-ºi‹žm¢±Jšâ™TtèÊUt®5AŠšIQ»"Ô(Á&%øârj €@xŽIÙ@@ò.@`’wCŽ€ € €y 0É# »#€ € €@Þ˜•+:ÂgfŸOþÜvÙ}Ó­‹kšøÎD[±t…Ýõ줞º^\ϳ(Õ{Ú„i¶rùJkvv3Óà‹JÊ”ÏÂôIÓí§ïrìM[7Íõ:‹Êu£ €¿•xsè›vC—\ÉÇ5;Ξ÷L!Ô"µ"õ´pSuŽ­cÖ<0ع{«î6aìûbÝQO‘2”°…EsÙ[ÃÞrgÕ¨E#;²á‘i?CoþÉÊO¬Ò.•Ò^¾ Ôƒ7nØh­Ú· Ê÷õ* Ÿ…D×ùŠ6WØûo¼ïÎ{òòÉVe÷* 9 0ø=g#r €™*@‹I!\ùßþЕzÏ{¬éYM ¡©ùÅÔ/ìÆn7Ú-ߘ¤~¤â½Ç’yKì‰;Ÿp'Q±rÅB Lê7ªo;UÜÉÊm[x­%÷ÿç~Ó”¼áÀ¤x_ÙèÚ…ë]#Þ!€ PòL áÿôÖn §µ?ÍÊ–-[5 Èâ,ÐíúnŹúÔ@ˆ+@`—¥`VþòÓ/öîÈwíçv¼òô+¶ç>{ÚI­N2µ¢,ž»ØtÓùÝÂï쥧^²'5°O=1¨Ìûo¾oŸ|ô‰}·à;Û»ÚÞ¦n`MÏŒnqQu»êÚ««Í›9Ï>xë[³j5iÕÄ4iàÆ¨,çÏ?þ´:õëXëέ­téÒA9± êºôÑèÜê)ïO±?ÿÓ:^ÑÑ*lW!*ëŒÉ3lò{“íÛßÚ¡uµÓÎ;Í_T¦È›y³æÙÇc>¶_/0=ù»þIõí¨ãŽŠÍ¶Õû%ó—Ø#ýqëÏê|–í±÷öêWmÚÇÓìµÏ^ ò/_ºÜëÿ˜}óå7®.2®^»º]ØëB;ì¨Ã‚|~!ÙüÏ zÎ>ÿ©ßÍ]³Ÿ–ÿd]{‘íºÇ®ÁúðÂè£mŲÖ¥g—¨ TöÃfÕjT³“Ï89ØEu1x„û,l¿ãöV½Vuk{QÛ¨‡Êî‡ï~°¶¶u­&ú<½ù›vLãcìàZÛÛÃß¶™ŸÍ´ ÛWp®§¶=58¾_Pp¬Ï¢Lë4¨cÇ7?ÞþXÿ‡Í˜4Ã6m˜íù|ÿí÷6î•q¶nÍ:ûû¯¿íéûž¶Cêâ>‹þØz-èÏ‚Êxø¿»ë[q—ŠvÝ]×ÙØ—ÇÚ;#ß±U+W¹‡7^vãe[‡|Ÿº÷)[8g¡§£ÏEÝu­a³†Vóðš:¬¥r³²²lð=ƒÝgaÍ/kœÅ%}.q¯î`üB@¤c’4UÞ3~ùÉ—Ö©q'Û¸q£;˜ZKŽmz¬=5ê)ëy^OûÊX{ðÅíšö׸í}öµó¯<ß6ü½Ánê~“Û°mùmmŸö±oçkÿüó»©8b`ЭÇ÷óïuG/pÓ+[®¬  v¸¬ƒ{¯/•íëqV—³ìîgîveÆûÕ±qG7PóæÍ¶Í6Û¸ f² Vy×ÊæËS@¥›TÕ¯T©R¦'|ë©ÞÃ>õtïg>k÷õ¾ÏÕ]OåÖ n¥Ž—w´›Þ”0@ú|ÊçÖþ„ö.¿Ê{mÈk¦›Áí¶ßÎqÑF²\÷ë:—/üK笮hçv?7XJþ6Ç´±Ù3fûú…Q3G¹€À¿¿žßä|76'vÜ…nžîÙÐ4 ziÛEƒÚ/<õBw½°éº+‘ãÿÆýϨq€ËçÍý“™ÓfÚ9 ÎqŸ=U]™ºzyƒ‹û\l×ÞqmP­)L±kλÆ~]ýkðùØ~‡ííŒNgØ‹O¼hCÞb Onä/(0½¸ÕÅÁgG¦í.ig7º9­ŸÕé܆çÚWŸ~傇װIïN WÕ*U©äÆoZ÷P·^µ¸È=u>*cäóA/²f­›YN×9<ƤñimüèñQ‡ÓßÀÿÆþÏêX/j=o¶0Æ„O €@vLœL¬×7Ó³ÿšmu­ë -+( §¾÷µî7t·'¾hêê¥4äÁ!.(Q1ã×6zÖh÷zz‡Ó]‹È“w?>D°OÿÁýmúêéöä[Oº›´á·‘ÏŽ4­ÿô—OíÝyﺛº×Ÿ{ݵªluÿ_1lü0»çÙ{Ü»¾ƒúºsPPNjý¹ý‰ÛmÊŠ)öùÚÏ­ë5]MßN¿ñüA¶ ã&Ø]×ÞåÐOúa’M\6ÑÕ¯e»–6ì±a®•#ÈœÃÂC\P¢l;ì´ƒËýÇïXï z7ä-Ïmi7?t³µ¿´½ –ˆõ»²ŸkQÒ©æW‹‹Z)|RkÕn´*{äÏàçÛ®¸ÍÕó½ùïÙ¸oÆÙ‡‹?t7¸rúèP_l¶¯/<ü‚»ž¿ŸhÓVM³7f¼á”gî&$4.DA‰ÇÇßxÜf¬™á®JÜÁ³²-µ¦ès»ïûºÏ”–”„Sº? j1RP¢V7ý(àURÐ*Sµj(é3¶~Ýz·¬Ö¼~÷ ®ç¦M›Üß™6¦r”¨\ýÍ*TRKR¼¿I·‘_ € ­I¶4…³A7»ê–¢Ùž*W©l¿¯ÿÝuÑ@ç~Oô º•¯PÞîr·ë⣛  6DU¸g¿žnbåÓ·º~ö(u3ÑôÄú†|¿ƒö³ç¶pûÍýjnÔþ©¾éy{Oס Aß>·»¸Ý–ãÎü÷¸÷\¿%¸Ñͼïú´ãÎ;Ú}Ïßçnê^}æUS«L2I7’×ß}½»±Ö¸Òˆ'FÝä4([­O®èd·=z›]Þ÷r—GÁÉcw<–«üº™U·;ŸŽ>áhëru—|™K­_‹¾Yä4…´OÇŸr¼ûW?~ÿ£©õ¤M×66`Ø÷U šþ”Ì^à^S¹ÎjéS¹ú›1y„Û_¿Ô“„ €© ˜¤æUà¹ÏìtfT‹æ,rßòºV–˜gV”)SÆjUËuÕZ¶hYÔ~±ÝHtÓª¤üáäob}—ªð¶T–itLTv=JK-u¯ç¢~ýz늦nFþGcŽ<îHÓ£Æ<$“jQÓÇñ­%ÚçËO¿ vmܲq°¬…ð{ݬ*¥šßíT@¿t-HèÛÿî§u·Q/Žrc%Tœnº“ð®±:êFNꦤqGJêö¥¤€'œÔý®ÙYÍ«r½œîÏ‚*zAÏ ‚ú*èÕx*Ÿô™RÄ>|Á^Ÿþº]Ïõ6îÕqöèíÚÍ.pÁ¿òþ³ñ¿KÒ¯š2Ú'M£­n…JúL“@@ 5¿§æUà¹57œü7¯±†Ïsà!šúýkP²–}òÝYü{+µe)|#lˇ…Ý÷Ú=ê(ºÔÍ®oј%'õvɾïýÚÕk]¾œ~iwlòVZ륀L­Gê´táR×½'Õü:ŸüJÞ%|¼GG>j7_r³'£AèJ ÀNmsªuêÑÉÔ²‘(Å{¦Ié2¥Ý.¾<°²×þ{mu¨Øk¸U†$Wħ ? úLÇ>KD-„šBI¯’>{}.ìc³§Ïºwéš*(T«RnÒ!u‰ÚM-›ê"¨= €¤&@`’šW¡åöÕc+ º”boÌü ilþ‚zŸSyº9Uª}tm»èº‹²­ÆþïŸí¶ð†+ï~ë–5ØÙ'µÔøV"­ûyÅÏ.(ѲfqÒ iªùµo~¥KW¸Cùñzã¹GMkÆ,ÍÀ¦AêÇM´· ´G}h#&H89À6¥snÕuÒ„ñfaóÓXçõÓýYPK‡ÃãÒÅ[Zêt.úÛÐØ’Î'wv­Pʧ–IÍj§¶[Ën®]nÎÛ®s³/û € €@´I´G‘{·ßÁ[ºD©U¤Wÿ^QõÓ͘ÁÈE9í_}W½ï—|oÍÛ4w]kÂõýúó¯ÝMãN•¶ oKvY-FS?˜ê²O~²…»ùõÚè[–RÍŸl=Âù| ¶|7mí²¦©¤ç|1ÇóP‹ƒÆ.èGß¾k–(Mÿ«é•ý”¶á2RYÖŒnj9Py±Á¬ÊOGÊïÏ‚ZÁ4™Ÿ}KçðéGŸ§¢©µ5#—fBSÒôØáYè|‹J°  € P(9ÅZ(Õ¢P/Pý°ên ø×3¶Ü¸ûõzÕӨ׮YëdçÔÍ'¼_^–7oJnpzl;WÚÙµ–h¬‰êNWѱQGÓàxu«ÉmÒ ~ÿ­¹f†Ò³ZÔÒ¤ýƒïÖOœjþàÿ¿ ©|sê²sÀ!¸ÜºqöI݆^~úeÿÖ½ªµBcK¿óñ¨õ fü täDeHñM½¶t£‹åkú¤é®u&•åςfcSð®çÊè6 ¾”4†êˆúGA‰Öm·Ã–q ºvš à÷ß~×ê {—{ú•Ìueg@r)@`’K¸tí¦qšåHc:ÔÉõ›×M®¦øíѦ‡/Ðã–^ßÊ êù!š5Õ¤©{•ôœ‘7‡¾ij%Ñ o=›D¼áÞR=dT~µ&œwÉyn¾ïÒ´‹Vþ0;óÈ3Ý µA]w4°Rªùµïû¤éykW¨íZ2üºØ×#=Ò­êÛ½¯½ò¿WÜž¢Áèán@K¢q3²}æglþìù®@ïÓómœøk[F*ïÏ¿ê|Sw¹ÇïxÜn¹ì7ó½î´+κÂ*íúoW¸œŽ©º((~趇‚V»œö oÏÏÏ‚‚QµÄumÞÕNÜçDtë  (ýmèoHSuûÀN¶lÛ ­·çqnújmWÚ¸ach¦zƒY@@\ ˜äš.};êÙzæÂßþ`W{•µ>ªµÄ«¾üCÇû4óü®ºÉh*cM©zc·ƒçA¤R޾¹òÎÈWÓf7t¹Áήw¶]ÛéZ÷L=S#<ÃQ*Ç çÕ´·š Wã(b“?3î™ UEÛSͯ›z]ß2£oÝÃãDbËÔÔ³šRXߺë5úÑ hƒGýÛ‚£}tÓ<`øÓ,Z÷Üp~Äéî:?pãV»^m÷ð¿p [N²ïËEfvÓ3r4²žß»ko÷Ät=$ñüç»Ã”+_.ÇõëÞΉÑÌV¯?ÿzŽùc3äçgA­lz8b¸;£‚ÛŸ¼=˜¶Z³ÄéZû DS+éù<²PRK–e¥T¯³Û‰_ € 'žüž'¾ôî¼þ·õ®[ÒªŸVÙÁ‡ìnžòãf5•³PW,—ؽjô,\©CÏ\™?k¾k5ÐÔ®uÖ ¾ÍNå8‰òªŒÅs›&ØsŸ=í ZEñˆÝ7Õüj1Zýój×’¥1!9uAÓóhtÎêrw@Í¢ZKÂuQ £1µ¨(øÑͶ¦öP8o^—Õ §)›+V®èu_Ÿûìéûž¶±sÆO™OT†2ÕSç;Mq¢ýÂÛòòYðO~×,czУҢ¹‹ì¯?þ2Ín§õ±iõ/«mî—s­òn•]+TN®©^çØòx¿µO~ßÚ„5 €[Lø$ Ac^ãºÑé¹Ç6968sµhýOßÿd“WLÎ÷@1((â&ùxxU@&Ëa@ û‘Æ%àä92M@S=Þ=ËåâÞ[Ãf mùwËí¹AϹÖ8uƒóc12͆óE@ 0)\JG ­®±OÝû”§ä W0Ò³_O»°×…~¯ € €@ZèÊ•Vn C èèÉ÷z¾‡Çâ†&®É· ¾µ?ÿӿѬf¤â!@W®âq¨% P&…¡N™ €@† ˜dè…ç´@$˜.8 $² € € €@Á ˜¬/GG@@$L’@"  € €¬IÁúrt@@HB€À$ $² € € €@Á ˜¬/GG@@$L’@"  € €¬IÁúrt@@HB -XL¢dA D T®\ÙÊ–-k?ýôS‰>ONd²²²’ÍJ>@ (Г–-[f#§‰@bwÜѶÛn;+]ºtâŒlE ZžÆÿ p™9E@ em1I¹6ì€@ ¨W¯ž­ZµÊ¦OŸnj=!!€ €D h‹ItQ¼C@@â ˜Äwa- € €¤Q€À$Ø… € €ñLâ»°@@Ò(@`’FlŠB@@ø&ñ]X‹ € €i 0I#6E!€ € €@|“ø.¬E@@4 ˜¤›¢@@@ ¾I|Ö"€ € €@LÒˆMQ € € _€À$¾ k@@@ &iĦ(@@ˆ/@`ß…µ € € F“4bS € €Ä 0‰ïÂZ@@H£I±) @@â ˜Äwa- € €¤Q€À$Ø… € €ñLâ»°@@Ò(@`’FlŠB@@ø&ñ]X‹ € €i 0I#6E!€ € €@|“ø.¬E@@4 ˜¤›¢J®ÀæÍ›ó|rùqŒ·KWXÓÖMmÛòÛ&ÌËF@@ 8 ˜Ç«VHuþñûíæKoNºôžýz˜d£µjåªÀòŒŽgä˜<ÿÐó6ö•±6é‡I¶ë»fsÔô®~çµwlã†Öª}«ôLi € P"LJäeå¤(xûÿs¿ý¶ö7“‚§¦@2B€À$#.sþœdµêÕl윱QëÖ¢›ýðÝnÝãoqZ–Ì\"W/_•ÿný'£¿­.{%*–°ó9¸û ¬Z´Jï=¬ýQä•"šàÿ¨Óô‘ÓåØc’ü±ä’í¥lR¯U=y"Å®IyN$@$@$àçâZUm4ƒÇƒ÷FUÌ7† TÎQYN>¡µ˜¿s¾d˕ͮ¶_+õšž·îÒZ÷—/\–dÉ“Éö«Ûõú{¯½'‹fÛþÿ @É2î—q:ðÈ G÷Õ;PjrÈ¥ÇwîÜ‘"O‘ë×® ,4ˆÁ€2´÷Pùî³ïÄÝëýÑÀ¤y§æúüÃÖ±L@¥(A‚šŸ§í9yä¤TÊ^IŸAŒÉW¿ÒãÐ>:5ê"ÆdÊ ‚ò ¸=›ñY÷€¢‚A=òCÝŒ¬ùetnÒY°€AªÔ©äŸKÿÈÝ»wUÉš³uŽ·²tîRéX¿£¶AùȾcúÊ«Í_•uËÖIÛêmì!‰%’†o6”žC‚âÆ/_uûJóO.µœÿ뼦kòVùxðÇ6+(Ž-«´”ÛÿÝVåß°¾¥ËNÆ,LÉÕ øá²'È®íp÷Ûô‹²$@$@&Àå‚#ŒŽzC`Ü q¥òèãê7¬F)IŸ%½¼×û=é6 ›à²õ÷­ÅR³qMýÆÇêÅ«íã-k¶¨R‚  T)Ù½e·|Ûë[UJ{â1iÖ¡™¼ñîòdÊ'õ¹¾ûÊÚ¥kí<Ì»:"©cÙjeeÌâ1Ò{Doyá¥ô1X6,ß ÇÞ´Ç”Ñïkÿ^(+÷îÞ“ëfÈòcËeÍé52uõT¹{ç®tkÞM.]R(òAÓäæ›2~éxYwfüþ×ïÒ¦kùûìß‚X#½Þê%)ŸN©.{ w/”õg×Ë´5Óô6˜BŠW(.{ní‘ Y3¨2ˆc£”¬^²ZÀ7o±¼ª$®9µF¶\Ú¢}3å‡)òÃ?høèõv/µ|-=´T–ì_¢m_XQ&l§ã @Ü @Å$nôsŒ·nF]úu‘­—·êÚ±a‡äÌ—SÿM$íº·“–[J½õìúÞsXk4©a_Ãì¿ „ÔjRK‡9Ü\’ASIÁ=¤û î2`âûúìñ\ÉÌEwu„Û“Xià®Ô u0a€4hÓ@ÿ @¼iÉ3¢ßOPE Wž"yìlàvÕ¡W;ÖÔ¦êuX0®\º"ps+V®˜^K‘*…tü¬£ZJ`\½rU.X·Œrˆëù‹çX¯Îœ:£Ö \ Múw鯷À۬希Í;Kàk ,`¸–*M*;;¸Æ}Ð÷I—>}$@$@$@qƒcLâF?Çx+säÉ!ð:.LøÃ@uÏÖ=‚uÄŠÌŸ2ßNfýpUÂàV(DcP ‹$ãóíº‰7Á~gO•™cfjšÀû‚˜,q»rÁJ½æüpWÇbåƒòH‡X”Gþ(ˆ/)\º°tì4°7yxÓóLD¿ÁR¸LáYEåȾ#zoצ]ú]°TÁ`i'N,K/U« n€',*¸ÙÀšòç‰?•÷þíûíëzàæÊÊ|>çóªØ œ‚8•• Wj\OÖY5.Š_›jm¤q»ÆÖp3ƒ%‡B$@$@$÷P1‰{}#-6®O®…ê1HfŒœ¡3üæž3 Þ\Ã7,"PL È`–Áì&îJ÷0˜† 0;´}W“‚´NqWGXIÊÕ('Ëç/פ¬ccDü}öÎgòZ»×ä£A ùoÚ£Dð1*XÀ(!Îl²¾˜UO¤‹${î ß~=ùÿpÊ©ã§Tùš5f–Ý'ÉM® \w¦ y|âÐ ½å¤PªB!üÿ â[ C*=ßì©nuèW”Ã*u«HÓwš ,-   ¸C€ŠIÜéëmé)ŸQþ¨£dDßz+iA¹€UäÜŸç¤Ëë]B¤¯R¿ŠôîØ[ƒ®áÂe‚²‘Ð(&ñãÇ×Õ0[W¢–ï· ‘¹àª˜¸«#Ò"¸+T-™µD~ÿåw{ydX¦›*O¦zRÞýì]ñ¶=¦ý¾/È% ŠSN>©§fe.´ QÀU-,¥éÕB¯jœ\犖-ªË?CakW«*fA wù€=$wÁÜÒêƒVî’èµL/dÒo r·bJÐßËæ-SÆk–¬‘ÁŸ –å –Ëôß§Ûò¡fÆ$@$@$@~C€Š‰ßt¥ï5ƒQVÑ1„ºcá|ì ±ø !XmªLµ2²lî2Aœ vO‡`iabÉœ=³ìܸSݽšvhj[3p«>A¡@™ø O°»ù•‹Wt`ÿÉwŸè3§ŽÒeˆÇ ª'ÜÉ ˜xÛžðÊë>VÌ‚Õ+[9—?Æ3ÆÍË0ÁÞ2°š`¡€ò5ËËö“öŸÈµ«×1>3FÏP7·~cû…Ø4ñìé³Ážswbö°9}ü´.Ël“vï¶½Úg§x\.œ» ËÊ“æÙ4‚»ðwãú ©[¸®Àý K cùc @Ü 4Å7ÚÊVÆ2F±€¢€=, ˜=Ÿ1j†]SÜsŠY KКչj5 z7é@ +W¶=Kèbð=¬Ï0)—¥œ”ÏZ^†÷} ožs÷=Ú|Áà.a&öáu›×µ“#.‘öØ™xyP²bP±ÿ‹SÀk墕zÉX‘ÊU/§çÎEpË #fæÖ[z ŠƒS ä—9×þ€ÕÆGXKk²}ývsY¿áפLAp<Bô3bK†}9,X:,%m\ÎÌÌÁð„H€H€HÀo „?eì·MgÃbšÜ¶`}€‹Qõ—«ë^‚Ǧ~F°Ä­S°é"b°.\”0È­Ú ª3‰´û¨ÀÒX‡y“æéöÚ0ûn`ðÛ¾{û`Ï„vÒ¸}cµÐ`@þaËeȧCtÓG³w žÃÞˆ´GŒÀ6Fœôý$™2tŠ –°š;i®º\U¬SÑÞë¥~ëú2ñ»‰2mø4Iš<©@©Ùºv«*€ü›E H?ê|]>J–uÞ´j“Œÿf¼Z5 L`ùä¢åŠê^4ˆeY±`….Í « â]z~ÛSo Kcu-ÂcãL¸g¡~]tÕÖ"–.bØŒ2C– R²rI ‡ò´xæbUNL¬Lðð   $@‹‰vš¿T¹ÛWÝìàmìqWŸÒUJë^¦ §/´ \CàÞUµ~b‘öxRwil?yÅdÁJ[#û”†%JóŠÍeîĹÒèÍFºL²yÊÛ”UStÿ¸ŸaSCl@™ aùfÚ7º0ÒÖoU_•¬ƒ»Ê;ußÑü?pÊ@ùrô—ºjWûÚíŹ7lÓP7CD^s&ÎÑâ œ`SL± ]]ßèª1+›vÖ~C¬Žq;ƒB„¥œ[Ô¿k©‘§†Ô.P[v¨‹ ™1D\]ÁL{øM$@$@$àŸ¸ó»ö«Ï´ –ˆ;¨ÔË…_ÖÁodVnEØ €l¹³Ištž)$®u€bƒ™ÿ¿Nþ¥(*Yrd fÝÁ3QÝ×z¡•2DÚÐ.Àj°zÉj9²÷ˆ$MžT²¾˜U^k÷š(Q Ä#ýñ—üðŲÇ~9qø„¤MŸV²åÎ&-ßo)¹ ä²ÓŸ9uF2XÏ+Ôª é3§—ySæÉÚ_×J’¤I¤d¥’Òªs+yôñGåèþ£2sìL9ÿ×yMûÖmé×¥ŸäÈCj¿^ÛÎsóêÍ2uøTµª$L˜PrÊ-•ëV–2UËhšÀÀ@Ðm€ VR±vEAÙ]›vÉ”aSôø±'“ú­êËœ‰sÂ-Spù¸ÿ¾ìÛ¾OÖ.]+‡ö’—ò½$JœùsJ¢D‰ìÔ¿ÌþEÀ¡y§æZþªÅ«ä£HöÜÙ¥Jý*ò\¦çì´8@ýW.\)ÛÖo³ND —),ÅÊ –ÆÝ ÚÛç½>z+q’Äòåè/%^¼xvÒýFh=qáOÞ‘ÌÙ2Ëœ sdÇÆš¦ë€®’&];}xœÑfä y)ÿKÚ÷8>zਾ8Ι7§´ú åï³K¿úéq¶\ÙäÍßÔc×oÞç³x'G ¥13x‡ð>æ+šOŠW,.9^Îa'E<&II"~ý¡ü4î'ýþwë?)Y±¤¼þîëzoÜ q²~ùz¹pî‚,UPÞûâ=yô±Gí|ppóÆMÙ¤lý}«À-3KŽ,ê~Ù⽂÷‹B$@$@$à=xÖ`ÈE˜ÁÑÁ{£¦͵IÙ&²mí6Á7~üø’ AY}jµ Ƥ~ÑúR¥^Ù³ež?™òI¹r銶´Ã§t`išôv’Ý[vë¼ÿýç_I6µ ÿy¸MZwß·ÿ»-o×}[V/^íî¶ôÚKsñN:ÉÕ+WÍ%ûr¸¥5hÓ@¯Øu@jå«¥ÇjWõËÖËõk×íô8À oÒòIZþ›5CV¡t|;ã[}fhï¡òÝgßéà=X&ÖÉG?ÒÁ?®#vâýÆïk’T©SɯU×°zEê©ò†}Fõ‘§Ò<%á•©™¸|`Û¼bsÙ¶n› ÿ”O§”cŽÉ½{÷$Gž2yÅd{`Ú¦zÙ´r“|Ð÷ùòý/uÐ{÷î]¹sûޤN—Z¦®žª Š@_tkÞMÍX¤%Âíú¿×¥\rú.¤x:…ü¼ýg—Ú<8 È  ®ÌÞ4ÛVïܹ#Ež*¢ìS<•B~ÿówR‡~41&‹ö.²Ý =áŒw¬ÈÓE´ÍÏf|V–[®™:lª|öÎgz …u剕z ­cƒŽzܱWGy»çÛzìúáÍ;ƒß ddžÒªj+¹võškvú»2cˆ*¨¸Ù¬\3ëz$é#ª/›·,Ø3u[ÔÕ¾ÁÄS HÎÙ:GóÃõSÇOI«*­ää‘“Îdz Åkô¢Ñ’æÙŠ^ˆDqüBöÙ•@þ×Ç ³ù$@$à»èÊ}7eåé?¾¿–ÜcHÙskpMU–ÌZ¢³ï37Ì”o”Åûëýá}‡ ¶F0ß»m¯ ˜0@¶\Ú"›/n–q¿ŽÓAh³W𹬙gñ=yèd[)Á@ 3ûN+Éï~a[n\¿¡g£”4žßöTÅ ÀŸwø\geà+¥y.*UÛ7¶gó·¬Ù¢ Úó/=/Ýu×Á:ÒcÖçu›×Å©*^ßöúV•ÌF7ëÐLÞx÷ ÒéÛ¹¯Z/p\­a5Û²tñüEù¡Ï‚&,JX“구'á•©‰Ý|ÀÒ¥¤u—Ö²ö¯µ²`×Ùze«@ùB|̯?ýì)X±¾éùôÓWvü»C¶ý³M½ÙH-5s'εÓûr˜*%°ð¬;³N¶]Ù&è䉓škÚIœŠ&…0 a€*%vB—(¸žpF`ÑÈŸ'ÿTËŽñ.õ–ÈÎM;Íe)dݲ/„rÞ;cÃâF)©Ö¨š|>ìsí_܇²8î›q&©ý>R‚wÖA#³ÇÍ–ùSçK­¦µôž¹~p÷AY>?Hùµ>úØJI¾bù¤ó—¥PéBšÖ¤w¾kå7 €¨˜x+º’b¶ùëI_ËË…^Ö"³dÏ¢.=˜i?~ð¸^ƒò²sãNèa åÅËWƒQ¸x…&°Ö ÿr¸ÞÆR´ã~§–Ìâ×o]_¯C Ú¸r£O>ÝhV­º|3íiúvSµª˜´ œ@p(°$Àâóé÷ŸJ½Võì$p­K X ¨ÎÍ ÖÔ÷M$=÷PÅeÀĸ¤2{üls¨u2JËÄ!e@× tp‰Cl$¼2íÌ\`Ù€{ð†$M–TYàîm®‚«qIKœ8±º°! ±kÿ^“IßM’§ŸyZÛK ýßë‡^zÞG&5ì$k~YcÃEÏH­&A,sîúí çÒ•KÛã=„@±¤ËN¿á>U+\¿<‘ðÞäwøìé³’3_NµcДAÒ°mCµÜ%4¹sxÏa·Åáýµ`”Ìß9_Š”-b§rE÷Úvkk_?¶ÿ˜CQ\±`…¿\øe™þûtM‡÷®…,lqüPÐïT/ðƒH€H€HÀ#TL<½‰òÉ£n?ÎRM€2b ›VoÒo¸}ÁŠáüÃóøÉ‡&çþ<'ÿ\þGoÃÉ9sܵW°9m&” («Ï™ç¹¤š{øÆ€Í ¶qž=W+ŽÍl7ŽC3€GlÀÙSge昙úwîô9UbðâqŒ` kÊb þ8X,…^ôòãõŽ¯Ëø_Çëòºˆ3@|̪E«ì˜ wî)˜™wJú,éÕ-± }Ûö© ^åz•Cô;âg ø„'p©Bœ Š€±l¡nŒÏg w jo8—¬\RóÅÞ ¸¸!Fé‰OWȮͻÔ·wk%Œ2§ Âøðä;\çl™#]úw(ëpEƒ«±ݽóÀÂè,ΰ‚%Þ#æ:Îá–eäÆzhá ˜yñ #Fy1çü&  ŸƒßÃgí)Üù§cÁ,1äÄ¡ú¸„ÐÄĦ¸»oÜÃ@Ù)°,àÏ)Nzã¶bî¿Tà%ß>\dþ8òGˆ8tƒfÐMzXD<´÷Ïjr ~±Ï‹;Á@iMìÇ㇌—Ý›ƒfñ'P»Ymwzu V¤ßæý&ˆ§Ø°bƒý,ƒÐÄuóB Îã'ˆo÷¥é‹g3¸Ïã™çž -ë`×aA068`Q î¡8Aj6yàêì¡ÿŸxË–X¸x¿sÃN9¸ë ºNA)6Š1,&‡vÒ÷ŘE Ü•ïzÍÓw›D~ØòCÃ1J!ø"ŽÆéöèš?”\#ÎãdÉ“™Ëö»d_°L_á!ü¹OnwÏñ ÄeTLbaï'H¤„„U53‡uÃugžsZ)Ì5ó8#7®Í›swßœ6‚Á™s°ü÷™¿íÁç“©ž 1+îé,¹Éßùv"¸:aõ¯ÐÄ©˜À Aœ\œJU.e.EèûÃjý]úu‘ó¾(Ïç|^°2T£’B´…@ K Ÿss› ñ=‰yÐ_nY±ÒWÕJ.³:Ò‡§˜xˬ¬¦ñ;gc)s*&P ëK„Võ× “70ø½üë‚X"¤‡K#Ê€âÜ: µ­”9yèC§Â^ç:’í¥Vgæpó¢ xG€Š‰w¼bMêLÙ2é@Ö‡ªõ««lºjTšTÁ®;O2dÍ`ŸbÖ³Íf08vÐX™6|šÞÇÊ\ìÁÕkýoëõÚÚeku)[“¹Žs§K˜¹ÿ°ß™³gÖ/”“¦š â4Œ øuÇ@Xn @à:ÜPX =Úö…»ê fzÁ˸¾!8mDl)Ù€wD} qZ`ô‚õ¾„â…/<U™jet±(a°CòÍ+Îþ-o9CɃbkÕÜIs5[(&P áÖ„øy“çéõ‘íà@IDATü%òÛ«•…V¾·×¡ô˜6"†§ßØ~vˆ=‰L1¿ 02‚%°[v~ (cµ2ãéTäMz~“ „M ì©Ü°ŸåÝH pÿ^РÙÛ¬JU šù_¾`yˆG¿ïý½´©ÖFB üۦä.¬{úøi] «M«6i 6¬"W.^±W¾fp6sôLD#~ÁÀ#Œ´ë`– ¶/Dà‚Ç13+” ¬R…û°>ä\–rR>kyÁŠeF}<ÈB­ýk‚™m«XÁ˸–é. x@`Áq*%p2sãNäîùЮÁê‚þ€;bœ2eè[±r^íØ¬Î…ý<Ìê\°$x"Þr6é‘·Yõ̸qA‚‹‰7n\ú F)AÒd¹`a%®ñƒÇëR˸‘þÀs®bÞý\sÙnŽcŽÑÕàÐÿhƒâ ô]¬’Êûº|Õ5 ž“ „C€ŠI8€¢ê¶±,Ì7K°L)ÝÞV¬B01¬=Þì¡ –†ý¢Ó‚ âòϯK؆•'6Ž3‚M ¦,¨{=À- ‚•±ŒË6ªÃ2· ߨð†äz$—ÔÊ_K÷ñÀu¸Ð`ሠ\¢ L–HWBÞoò¾ž·û¨ºKádÞ¤yRê¹RR4uQ{GÄ´ïÞ^ÓbsÀ C&è1,Ø7ãý>˜5v–n&© ¬ÐÊ4÷߈Šb'°y$¾ÿ“´¨ÔB• ¤=¼÷°×+2¡n>ï¤íÆ^ ¦-Ð=ömÁ*g®1*Î:¹¿RýÛ2K¨ª ‚[Ô\Ÿ1çÞpÆ3ˆ1q®²…Õ㌫“«+SÙjeM1‘ö åÇÄ^A«W´ž”H[B•OÄ–Wê®kÿ¿Õ8X |d•Í|H€H€HÀß P1‰¡Æ§Ø³Vî­»{´:•kUO¬Ë¤Â‚-6nœ6lš4y«‰ 34ج¾ë³8‡‹ÖÈù#C |±Y"6-Ä.áNÁR¿Ý¾êf¯„å¼×ô¦2vÉXÛªâ¼çé1–v®l„A%ÊÑ„elË®ÁZA¬6¿Ã‚ˆÅ@ ´(%XvNbÐmVX‡ ¡•iÒ:¿á†ef‘¬5õ‹ÕW ¬ØÏŠâÊ…+íå‰Ï†w KÓÇß|,°^unÚY–h(c¾#C*N÷¡ðò+¬ÔfuòT±ñ”³Éߥª<ˆÙÉS4h58\7–£O_xéFª`¥1¼“F 1 ` ³2¬PôVŒÅù ^gÈCìwÕ¼§p«DÌ~;   ï pçwï™Eê—/^Ö]¿Ó¤‹øNÑpmÂ’³˜!‡[spïIe1ÈÇ*_Ø#%m†´’ýåìÁâ8\ó¸}û¶ÎŸ<|RËÂf…Æ"ášÖÛsXK°šØµ®ÉcO>fo¢hò/(s°0eËí¡—ÿE¾á•iÊ6ßP€0Æs° 8ÛŽU°ÖÂ&wßh;ɱ‰a®¹l‹•»´Qy-*8GU}/]¸$v”©S V_s*QU&ò…Òƒßö®ÁÒÁ°‚ cT–ëëysçw_ïAÖŸH€¢Ž“¨cËœI€H€\P1qÂS  ›]¹l<     ˆ)TLbŠ<Ë%    ° P1±Qð€H€H€H€H€H ¦P1‰)ò,—H€H€H€H€HÀ&@ÅÄFÁ     ˜"@Å$¦È³\     ›H ê;pLvmÜu0g   ðqѲ‰3bõIà¡ ¤OŸ^&L('OžÔ0:Cf@>N”RH€H€HÀI J-&βxLq–@üøA?µèÚ•<΂fÃ}‚@µjÕ|¢ž¬$ @ôˆR‹Iô6…¥‘@ì%P¨P!¹xñ¢lÙ²ER¦L{+Êš‘ @ ˆR‹I µ‰Å’ ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«l ø*&>Öa¬. ø#*&þØ«lS´8yò¤Ü¼y3ÂåÊÁƒ#ü<$   _'@ÅÄ×{õR§N-:tˆr¥¤[·n’ A‚XÑV‚H€H€H€b‚“˜ Î2ýŽ@Ò¤I%eʔҲeK¯”£”9rDžþy¿ã‘ €§¨˜xJŠéH õêÕ“7z¬œ¥dÖ¬YR·nÝprçm   ðoTLü»Ùºh$P¸paIŸ>½GʉS)I’$‰Ô¨Q#kÊ¢H€H€H€H ö bûú„5òaÆò–åÄ©” ©*TÇ܇[ͪ“ Ààbòð ™ ØŒb‚ î”W¥éàF!   ¸N ž5P ŒëØ~ˆL7– 6ØY)RD>,—.]R—­ùóçÛ÷°š×ºuë$~|ÎØPx@$@$@$' p4'»ŽJ®XN®^½ªE:•\¨S§•’¨ì æM$@$@$à3¨˜øLW±¢¾B jÕª’,Y²`Õ½{÷n°óxñâé¹Óõ+Xž @#@Å$Žu8›õ°§I@@@˜Áƒ2o޼ܻ$LJ¼I$@$@$—P1‰K½Í¶FWw.wÓZ⎠¯‘ ÄUTLâjϳÝQJÀìiZ!‰'æÞ%¡Ááu   8I€ŠIœìv6::„e©X±"÷.‰ŽN`$@$@$@>C€Š‰Ït+êkÂRLKaxöÙg¥hÑ¢!R`ï’R¥J…¸Î $@$@$@$— P1‰Ë½Ï¶G9w–î]åØY € bâƒÆ*ûw{š„åâå;-cMI€H€H€H r P1‰\žÌ‚pÝÓ„{—Ãà   ° P1±Qð€¢†€Ó‹Ö’¨aÌ\I€H€H€|Ÿ@F$@$@$@þO J“jժɢE‹üŸ"[H$@$à1€€Y¸p¡Çé™H€H nˆÒ“xñâÅ Šl% €WÞè.&& 8A J-&†àÁ{Í!¿I€H€â0ì ²ÇáÖ³é$@$@aàrÁaÑá=     h!@Å$Z0³     °P1 ‹ï‘ D *&Ñ‚™… „E€ŠIXtxH€H€H€H€H ZP1‰Ì,„H€H€H€H€H ,TL¢Ã{$@$@$@$@$@ÑB Zö1‰ì–,š±H°ycÕúU#;kò»xþ¢lX¾AræÏ)™³eö虸”h霥rdßmr£v$Eª~Ýü+7ÊÉÃ'µ äÑÇ•kW¯É¢鵌/d”"e‹xÍ`Å‚rùÂe‰Ÿ ¾ÔnV;Üç#£Lw…\¹tEÖþºV2eË$/åÉ]’H»¶æ—5ræ3òj‹W%aBŸüçÉcqíwâ1&$ ˆ³|òþ®¯w•xñcN19ºÿ¨¼ßä}ùhàGTLÜütþ¸PÏ\¬w*Õ­ä÷ŠÉÌ13eþÔùÚޢ努båµg»žz­f“šRL¾ûì;Ù»m¯$J”È#Å$2ÊtÓòÇÑ?ô}oòv“HUL~™ý‹Ü¹}Gª¿VÝ.vâweõâÕR½qu¿WL"ãwrôÀQùyÊÏʯLÕ2’¿x~›%H€H€HÀ×ø¤bâkY_ðe°xAyÈ] w¤6ãë¾–ÿù7˜b©ÄÌŽ<.ÿ®-}2å“TLâ@Ÿ³‰$@$àϨ˜øsïÆPÛ¾ù…|òÝ'Zú)Ÿˆ¡Z°ØÈ">Kz8y`deÇ|þO€¿¾ $@$@$œ@œPLΟ9/s&Ì‘ý;ö˽{÷$ÇË9¤aÛ†òTš§‚ӰμIëúð©ã§ä—Y¿È#IÄV„æ#¿púB9sꌼÑé uÓ1ùܼqS¦ "™³g–ò5Ëëå“GNÊÚ¥k¥x…â’4yRY1…l^³YÒeL'õäż/šÇÃü¾ÿ¾ìÛ¾Oó:´ç¼”ï%)P²€ÆÉÀU(4¹ô÷%éó^½8Ibùrô—ßcÒè7BäOÞQ×6°Þ±q‡^ë: «¤I—Fñ±yõf™:|ªØy@ùä.”[*×­,pC /ä AýþSùiüOòëO¿J…Zôߤ÷†Ýë7dØ—ÃdÛÚmT°TAùø›õÆoñGŸýð™V#´ß ~«x÷æMž§ntO?ó´*SH •*¤ïj’G’èó†LÄ7Y>¹œû뜴êÜJcœ¾ÿü{½Uçõ:òÌsÏȬq³dóªÍòýìïÖ*&>ù6hA/Xxîø¡ãzÚµ¿õû}öÁïפá7 D À(«Âø;xï`¤þYé@kìQž–MLùtJ­Ç³Ÿ L6µ?‘â‰À‘óGËÃÓ´“–OÒ<¬ûù¥‡–ZÿùZJIàø¥ãíëîÚ^¸La}~ûÕíÁÒ­;³N¯W©Wž>hÊ ½Öô¦?ùx ¥ìZ®5z zïm§uW®íº¾+Ðò=×gR¥NøÂK/&H@ÏsäɸåÒ–0óÈúbVM‹¾œ½i¶vÏ­=ÉM®÷R<•"pï{õžµ(~ÑÞEvú޽:Z‹Ø÷ÌûoÃu1uC™6YƒFû9kpj_ÿvÆ·öuäoÒ»ûñóˆ@kj§w–o-bŒC@ƒ·éðŒ¥Ð)SSFÆ5ì´xpý׃¿Ú׬»^?®ý1МÚ÷œu@»­A£ÖRÌ4®+W,Ä3`îìÐÊ\vdY`Æç3†xe[Ê\àê?VÛeš69¿gn˜©ÏZ1&v:kà¯ïzÁ=ãÇh)j‰'Òt©Ó¥D™Î<œÇã~ˆw×´Çx¿‘ùâzë.­õÛ„k9¸f)‘+ޝ–ïoG ´”4M ®–’¥Çøÿ´ù§`iu0ǖ¯é-7¨À¶¶Õc”Õm@7}ÖvëÏ­ÄïÉ´Ë|ã÷n~Cè3Svh¿“°Þ½2e÷ÝÞ§yä*+DY(sÁ®ÓÖL³ï%ÊŽdÉ“¸{@ÿ­2õ3ï,ê…+À÷ÀiM}#ëÛ”…ÿõ0k  %à×Ëcf¹S£Nrïî=™±n†,?¶\Öœ^#SWO•»wîJ·æÝäÒ…KÖÿ“¢³Ðž¦Õ˜ÑmR¶‰Î^4Z¬A¤ãnäNþ~²Ô~½¶l8¿A¬¶!ñ#‰åóŽŸ f ÃÌÌn[·M¬Ьýk­XÙze«T¨]A-˜!Kj6®ißF`²‘-k¶Èõk×õ«Q…f!B‚Ý[vË·½¾ëw¢3ôÍ:4“7Þ}CàéÛ¹¯ÎŽcö>o±¼zíÏ“ ¬ÁL=,%›všËb Úìc×ÌdÜæc¹þoP}aõ@07f°!˜!Fý ˜ùÇÊo¸1½×û=±ªzŒk°:€gDdÊSt6ÏÂðù°Ï¥^Ëzš¬yã¾"[\‡¥³ï¤·”KMƒ»>ïðyˆô®útê#˜­‡ÀêÓùËÎR¨t!=‡…ê݆ïê±·°N|Óóé;¦¯ìøw‡lûg›4z³‘À 4wâÜP³ƒõÏRj%CÖ b ˜õ¸ç …ÌC3GÏKéKY×|aÙ¥É5ß÷¿¯ïÆ€ ÄRjeóÅÍ2î×qú^6{¥™ÍÚäÚ÷Õ+Wed¿‘zÏ„‰‚ŒÉÞ°Ñw„þž Þkô•¥È¦U› f¨`ȆÀ:jÞ½²ÕÊʘÅc¤÷ˆÞbM&èS«­Òqk¢ywp+O÷AÝå©g‚[‚Ç §Ö¤ÁûŽ ñîÁêoFPWX± Hƒ´  ˆ.~­˜LbºæW«É+'k_-ܽP,K^(¥†¼ wK#–EDàÞÖ uâøÃ¤ Šl#P\ñoëòÜPl»ôë"[/oÕ‰¤¯ñÚƒßôŠ…“ßýÝd'Õ=X-;È  ˆB~cbl–+E„FQ1ûmx“ÖdvdÿÀ€³»Q¹T'Úàj‘ÀÒ´cŽ™*¹ýÆ øËÿuò/9}ü´Œ4;+FXb¹Ti< ,à„ÙeË­L0{ ±Ü„‚)~îò2U ÚΞ:+Xbx?P, U°V.X©×JV.©3ñ8A¬ b@Žì="–û®à„X‰]›wé€tïÖ K b Œ‚¡™¸|Ü}оR­áƒÙbÄ,,Þ·XË7Ïc¦¬"nVÔþ” %‘‘Ú™zp`¹<‰å¨)¡c`™t å)´|‰‡/PñÕŠe ‚<ûâN w܃Rg¸ãmß½9HáÃ<óû™qÙ+w§ÀÂEq,#®¿Y¼cX³ŒlZ½I-×G}'Íu|›ß7,MÌ{"ï÷yß¶L ½7슾RTã[ð¬QÆÂwªÎulkî‡%˜@0‚XGþ¨ñ%…KVñ&ÞŠå^¦“Î盆8#üÛ±eõµ?úØ£b& ¤á9 D'¿VLྂ€m3Hq‚żãåMZ“†Æƒ|Ì£¼ˆ ¡‰˜9ï?—ù9 žÇ,|Xr÷î]ùmÞo2uØTÙ°bƒ ‡§‹Ôqݲu:ÛnÜS0ˆKðÌŸ'þÔ$p1û{¸>ƒÁ9ÒÂòaÅ ïwnØ)wÔE Ц/a19´û=4Áó®yšs瀃g§¸Û$sPA2cä µª™´P V ØòCÙ³eºµ!? ^¡t¢ŸÜ ³]jÈüÂ%"¬þw¶ŠþÜ 6gŒˆ¸ÎΣ-Ø2¬wÙ“r\®¡Ô!og¾'Ьà’š`sHOÅ(&½7ìàvhj: !0ÿÖ8ï¹ÃJR®F9A ;.‹°úàï³w>ÓE>ô‘Z§\Ÿ íܵ]&¬&C>"wîÜ‘uK×I®‚¹ìIZK %~“ D'¿VLòþ½û:àsUÌNÝΟ7i‘·`+pß‚+f¢èóƒtú¼nEH°ã5ÄõþH‡Ùu *Ü­T„ûF>lñ¡n׸t`¶ôùœÏ«Ï~£’´4˜<°ÒRµ<̪Bi0žb‚A%×°´`Æ×¬´ežw~cà‰A:¬$Ø8xãÎåTL0Óo¬\˜¡w]ÑÉ™'Žün\»áz;Ø9VÌB¼J Ú‡ÌÎýyNº¼Þ%XZoN0ø½üë‚U®0ȆûêöÖ­Ä!¸dŠúÂÂâTΜzWÔù»<ª–-s 3÷Ù^ÊfNƒ}ÃÍ+"%$*$AÂáf‹÷ ‚•£°J;1ñ8îî¹^s]ÚVA#á±sþfÿ>ó·yÌþFŒ“§2lî0Y÷Û:U"ÿåw1JÊÀä“©ž”w?ó<.ȵ]¦Ø›Š î\W.>P✛^šôü&  ¨&à׊ f.a Áµ®3êfP‹\ˆ7iM§ îKÔ~ÐïY6o™Œê?JàV‚åˆÃ ¤!ˆý°Vɱ“š%:í ŽƒÃ{CÆ4.,/šüsùUJ`!B|ÓÌ›n¸Q•©VF–Í]&ˆ3Áà"Ã0´:à:êÊIÓMƒÍøbà…AêfêWªr)UL`E™;i®f ÅŠ 1Àc9UHþùÃ]òu41$p ÃÌ4®mõŠÔÓ¾#Äž /!¨Ëˆù#ÔU çcÅW„ïœáëZ¿±ýì¼Â²z€Í†åÄZÅÉN¿þ·õöqú¬Á-@ö ëÀùn¤Ïœ^ceÌ}lnˆ÷‚ t_“LÙ2é»K–“ Ú%ïwª4A D¤mÞ°sZœ°(,5faÔéȇU,à Š+bðÂ-Klc™k\(½QLB+¿ (¤ºÜ³•'–o†`Óßthùò: D”@ÔLwF´6‘ü\ÉŠ%5GìEá ôV.Z©—Ìl¿7iM^<@0A :\q>ný±º™4î¾³¼˜E/c_#xvÆèæ4Ä7\;\ÝRÆ~4PÁJK¡‰™…Å ú‘å™?xx"fu.¬ºdV笿' ®\=ÚöÐýb0˜Ög˜”ËRNÊg-/Ãûí`t&=Žç1n\&žÂ(—®J§&vù(PÖ¾2ø“ÁØŒÁ"fŒá^·³G‡QÀÜ °–Ìõ º|ÁòY|ßû{iS­M„)0™yÃ{õ˜ø¼W(¿ç/:}!­ª´2Y†û=Ú|ù¤ý'êîhbšàzX·y]ûÙЬ@Pðñ>y#&ï¦q£—7™–H€H 2 ø¬ÅäÎí;Ò¦z›PY`y××Ú¿&“¾Ÿ¤›bÕ#lZˆr È1{^±NE{æÜ›´î Å 8V•Âr›Xá Ö”Ð$±üZ§mzèŠOH·xæb5î)®ÏBh[½­4n×XÒ<—F­ ó&ÍÓp×Ùbç³Ô X³¢—«^Nµs&α”aµÆ]¬…3/¬„å|1Ón\®ª6x0‹ïLëzÜî£vºIÔ¨7þ¬ý+Ô5 ia9jß½½ýbLkb– Î’=‹=ÀÆ,/xÁ²ªá Ò`%3lÚkK­üÁ*¸C™Õ¥à¶…Yj òª¿\]‹ƒ<–‘5‚÷È[BkòÅFšè“ÓÇN«›òÆò»x¯qßXÕPJ,VP¿hýEbSKp MàþÕó۞ꂆ¥’[Vii/6`žiüVãpÝMÚÈü†… A÷Pº \ÅÓÓ2°‚ï¡$÷x³‡@qÆïgÉì%2é»Iº–ÄŽ¨xË“Ë4V%}‹?¬)’¦°—ì «>Û7V«$TÄ"AqÆ zΕó^mþª\2 ÍXepÞö K¢¹Ö7~¿Xª¿güa²KSH€H€H &ø¬Åÿqc@Ú\T°|)–EÅ2šXÉ©a‰†Ò¼bsÝ û-`ÙZ#Þ¤5ϸ~÷ú¡—¡8g]Óa™O,ë‰NXÔ‚`)#í£àšço÷|[]ÝZtÓ6`gh¬\ÔgTwÉík¨}3í‡u¢~±úZf]±4*v+®”]ØÏ„v€8 ¸ªÁ³Î¸sÝÝ7žµ6°ÔŠÌ}ÄÇ@à:2gëœ;L—ª4#Ž4yŠæÁ—Šs ‹AhÁ½&½ù_wƒ.(@³6βc5°,³){…@9*]¥´îcòZ8}¡­T™ká}cì.n³"Vÿñýuµ1<kÆ‚i ‚e…¬$ƒñÁ`+Á9÷±öãVÁ!?±•(?¸@!>ÃÚÜÒ‘:ú¶i¨+… í=T (GDO,Xz Q4+×L÷š6lš4y«‰ 34˜•0"ù{ëXÍÙ2G ãwK~ëØÛÈ,’^ì –:8y ÝWP¢±$9”(ëpïrNDdz!“¾Æz ¥64«›»öÃ5Ò¬î‡ûEÊÑ+Ü¥å5  ˆjñ¬ÿÄ<óã‰@MÌ–ÖŽÁx:òA1ŽÕŠ„qoq-Å›´®Ïz{Žø ¬,+–îtg-Áøý&ïK¯¡½tE¸a¯ƒg3=«ñ-ž–‰à[ „Ñ>Xœ±-X] µ¡¹ˆxZ†§éÈý:àÖ•-w6Lt \m`%BPyÎü9Ý–NØ..àÚû‘zcSÏ;HÊÔ)%{îì:Kíi>Pxaùx!× ^º¡ô`Ñ̾c¥(X,œ‹xZ‡ÈLg~oX… KIGTΟ9/û¶íÓ<woAoˉ;(øMãÙ‚) ªµýýóŽŸÃ-JûñƒÇuio(hþ0J­kø-á½F™`étÛtMëzŽI›ÝêåþãúÛ–C×t‘už=AvÍ }O!  '8¡˜8ìkÇ®Š‰¯ÕŸõ%¸@ÊGƒb TùÀ„ ,\ÆÍ Aëý»öW °À`“ÌØ"f¥8XOµö¯µê:•õ£b•t™7 ø6Ÿ1ñmì¬= €?€…V¬¹ƒV«–»š . VQX9!p¿kÖ¡Y¬ivóJÍeãŠjeA¥°Ã<êH!  ˜"@Å$¦È³\ ¿"Ð{DouGƒr×*ĆÁ²âˆõ2Vs=&¿oݸe+%¨W»îíb²:,›H€H€„®\±ü%À XØã"uºÔººO,¯.«Gqž~³ˆaºpö‚þn™1J‘Ëmÿsé­cDb–"ZºrE”Ÿ# ÿ'@ÅÄÿû˜-$ XC€ŠI¬é V„H€bŸ].8Ö‘d…H€H€H€H€H€"L€ŠI„ÑñA     È"@Å$²H2     àª\FçÙƒ›Wo–óÖ®î_­¨;Ñ{öTÔ§ZóË9óÇyµÅ«^mÆõ5c 1E/nZ¹IJT*!Ïf|6RªÍ$ñ£æ…•Ÿ*Ö©)ù2   ÿ#@Å$Šût䀑²zñjÙp~ƒ$N•8ŠKsŸý/³‘;·ïHõ×ªÛ &~7QëU½qõ8©˜LùaŠ`·ð$I’È[=Þ²¹DåAL”éM{¶¯Û.=Ûõ”ïgiŠÉå¿/ËàOk5P1ñ¦C˜–H€H€â*&QÜáEÊÑ¥B'‰¥Íûú£¯K˜:“(nv¬Ï~Æèr`çÝí:º“˜(3Öw+H$@$@$@ÿ'@Å$Š_…Ö]ZGq Ìžb/LÙ2Éú³ëµ‚1©œÇ^B¬ €!@ÅݾN_(gN‘7:½!‰%²ïܼqS¦ "™³g–ò5ËÛ×ÿúã/™>rº;pLgÞ³½”M굪l#ÄU‹VÉŸ'ÿ”z-ë fŸý[æMš'…Ë–^zAæO/»6풤ɓJ¤J½*vþ΃• Wʦ՛$i²¤RºJiÉ](·,Ÿ¿\Iúˆ”¬TÒ™Ô>>}â´,™¹D®^¾ª;Qþj´¼˜÷E)Q±„[×n•µK× b ræÍ)ÕU“´éÓKƒ“ƒ» ÚƒÍã2gË,E^)¢u‘0Œ °¬^²ZŽì=¢mÎúbVy­Ýknó™1j†,ûy™ÜuPnÿw[²çÎ.EÊ‘Ö][ëŸIßM’=Ûöh©Ø}{²bá 9~ð¸öY‹÷ZHá2…õþÑýGeæØ™÷ƒ ·oÝ–~]úIŽÜ9¤öëµ5 ú{dÿ‘²õ÷­rêØ)ÁyyŠääóØiš-k¶h>8IòHé1¸‡ö/Î~<ÐοfãšòÌsÏ„[&žs'—/^–õ¿­—u¿­Ó¾Æ;’¿D~I“.<"ïÞ ôå‘}G”kÉÊîß!»ÿ\úû’ôy¯žá}þrô—/^<;Ùˆ~#äОCzþÎ'ïH²G“É€®ô<_Ñ|Òô¦vZO8÷Ùwú^â¡÷¾xOžËôœ>?¬Ï09²ÿˆwú¼“¤Ï’^á.·mÝ6=îðiÉôB&=výÀïܸ˜U¨UAÒgN/ó¦Ì“µ¿®•$I“èoªUçVòèã{tö¸Ù²jñ*}·Iöˆä+–OòÍ+¯TE›H|ôÀQ}p\­a5µ˜ÎŸ6_ß§œùrJífµõ}Ü»m¯L1MÿO?ó´¼Þñu)Sµ  &ˆS›:|ªZø&L¨¿ýÊu+»MìAž €¯ŒB±Xâïགྷ>ñg ^µ¾Û¯nVßugÖéuKi°¯O^19Иéukh ëqº éï[l§+]µ´^·bLôÚÌ 3õ¼Y‡f–’ ÇÖ®Ðú Vm?lk? n»®ï h`ß·Áz\µ~U-Ó¨Kïd=î—q–‚e?‹ck`¨éM½,‹ŽÞG¾–’£ÇhÊã+‚åûÑÀ­A‘Þ·v¡·ólòV“À}·÷K묃9Þ}cw )Ó¼Îï^C{Ùyì¾¹;°Ò«•ì2œépl) è“w…Úì´µšÕ²ÏÒøy„ÛûÖ@Oï/;²,0ãóݦɖ+[àê?Vkº=·öZŠ’®ó—õúðyÃíkÖ 8pçµá–iÚáúmÅzÆ_ûÐ LùtJ;ï¾cújyxÆÛwjâo­·æ…>O ö½a‡r]ëâ<·”I»³7ͶӂIòG“ë½O¥ÜûßÞÀE{Ùi­;­§œ[}ÐÊ~¾ßØ~öóN–rd_·”}Mvá·ã¬·óxÞöyv¾xL½ïLÁR÷ßÙoçú;ï; ”,`—7iù$;ÞãD‰üñŒ5¹øÕįì>pæ3hê »<Ô·c¯Ž–âgççL‹ß¤³M¾rlÚ…ÿõ0k  %Àå‚­ÿ%#"½Þî¥3ÅK-•%û—ÈòcËeÌâ1+Êä¡“Ãͳü˜%]szl¾¸Yæn«3«c¿+wîܱŸóõYôã"Aà°5˜k +ÖÀGg_a‰±Þ;;­ëAñ ÅÅ,J†¬Ä(êqÏ!=ƒ%›9z¦ôÞ[¬¾lûg›ZІ¹çÚé`áèÛ¹¯ä-–W~ÿówYsjl¹´E,…I0CýÃ?ØiC;,IóliÞ©y0+Éï~!˜‡À:õëO¿ê1ÒbF¼kÿ®jaÂÅwÊ é}ן'ÿ¬–÷z¿§–s3ùç_z^ºê.–r¥ç˜õÇyÝæuõ¼O§>ròÈI=Æl¸¥pH¡Ò…ô–€w¾«Ç°¨õÛW¬A½žÿr¸ZÛúwé¯ç–B!ýÆõS+Gxeên>z½ÕK¬¸XŠ®,ܽP]¢¦­™¦)¿íõmˆ'ýÇÑ?dÑŒEz ë Þ¯nºÙ–X׌¥ÆYÞckâA+†,E]^ï"©Ó¦V RŠT)ìGÌ{Š »·ìô3~ã°ÔY“òÆ»oÈ“)ŸÔôøMÂÒI!  !@Å$=y÷î]K©Ò¤²s€KÕ}?téÓÙ×B;€{†œ@àbU¶ZYAÞpA‚` ÷+¸¯|9æKu7Û Ü’z é¡iö£SïNÒ MuYÁ »aÛ†šå]ì¬Í`x(S ”¬Y_e0kì,¹ÿ¾Þõ÷0p‡X³ÇbY/Äší•©«§JýÖõõ:Ú½qåF¹}û¶ÀMÇÈ„e¤ýÇíÅš9—×ýh»ËÀ¥æÔñS&™ý´ýÇõ—vÝÛɨE£ìëÇÓc°Äà~êƒó2eÔUfÅ‚zýåÂ/Ëôß§KÛnmŲީû nl_¿]Ž ꟗò¿$–…KÓc@Þ¸tcû^ËÎ-ÕÍ7Ã*Svó®~ˆQ2nJH–¿x~±fçU ‚{›SäòqîÏsòÏåôjŽ<9q%F` ™¿s¾þ!~ääá“vZÔÅ0AzËÝÆ¶^`yÏ– ¸“¾Ë”µO‡ñDŠ'ôüúÕ ™|û¦›[ôÎ3ÓþsÆÜ˜A5ÒaE/(Xš hþ#PzÇ/¯,´KC)÷Í8Ù¿}¿fíj-óäZ¿<(ÝrÛ Q½*uÝÇ7¹&Ä'P °ã}€ f‚~CLNhâ çĉk,ò:´ûÀÚ€Ø H£7©b…ë·nÞ’]ƒ܃¢é© ^+Uê Ùse·½võš×lRSæl™#–뚈RKá§o}jÇ’ ÑÝ;wíçÌAÎü9Õj†ó\r™Ë·d”c(ùFÉ‚µJ:Äp¿gOµßÅs§ÏÙŠÌÊ+5-?H€H€HÀ„îká­‹¤6¸³ ýi¨ô|³§ºRÀ‚A7wð5AÒ¡UÁé¾aÒ$H˜@My§Ž)(îxaÝxê™§Ì£þ†«”S ìÀ*cêpâÐ ½ å¤PªBΤÁŽÿ¹¤x»øÿ£äàÔ9ûs ÀñgÄéc\¨Ì=|,YPƒçq %ÆUÒe n­‚EÄSqÖsɬ%‚?wb«¸‡s—þ]¤mõ Ë ®ÁÍ.b+°ý8òG™5f–*ˆÈÊ™¶ÉžåÉ;…6&KžÌvrÖO¼÷xOÖ-[§%X!ć%Þr†;¸wïž*£{·)&ÅÊS÷*X`ömß'ÆÕ J´7DzúÎ ê1HfŒœa÷ÚÞûåœ|p£œâ:É®žøS“üwë?Ý_Æ™Þcâi]Ÿ7÷ùM$@$@¾D€Š‰½…Ò!ÎjÌp"¦Ö€eó–éªIk–¬Ñ•~–/X®n@&þÀ]ñ„oí0ŠŠ«µùÁåéÒùKö*EîÊðäš)#´´fÀ“»`nu§ -;åɤuÒae K cÄ9€5לî[δæ¾s…(sÍÓo§‚Tç:‚UÖ܉«ËÜ£œ2vÐX)_«üC 1[þj¡W5N®SEË,½ ëL»ZíT!vm«'ïžÕVg{Qÿ×Ãîg«Ô¯"½;öÖx(¸p™ø ¤ O1q–ë g¬Bgî\på‚B#oµÌ@1;—qõòÆZ‚|]9š²œß£Œ’}ƒâ”`ÅCa5Âïñ"‘-øÝ%,¹öÜ'[¾ß2Ô"¨˜„І7H€H€|ŒG‡E¾ûÎMW7¥ ç.è -–¯…ÅÁZ™Jÿ0°«[¸®’°¤nŽ—s8r÷þÐXN iÀr»ÆåÃûœ=ƒaÈéã§K”EE/Zp«Ûã)X=Ì=óà{#°¼@Á3ƒA â§ Ÿ¦·­•¹®/F°9ÑNÅ ³óFœ.aæÚÃ|c9h#X>1F°A¥qGs*Dp÷2±pñCŸÀê3aÈuÃ2Ï{ûe•ïܾ#ÖJT!6Æ„ë\Dý‰¥r7­Ú$X&×)&žÂy-´c¸È•©VF8Ž8ãʈ¥sýíîyo9#?üAQ]ÒÆ}(3PV FÉÑ“~`ÿ ‚±¿ b=Œ`†tȧCÌ©Gß÷ïÝ÷(k" @a-Aùˆqp blš”i"Žkpÿ}䂃•†0ÐÇà+IaÀyåâ;’µÄª¦…¿=Vºtá’À} 3ÖA`9ÈW<Ÿ?Ì,8P4!¹ æ²­cŽQ«úÏÖ=Ò x)Ÿµ¼TÉQE÷…AzÄ” ~¸é ØÞpø¦Ç7¡ÆÝ8ËÔ‡Ý|€ÄUáÃ@Õ¸L9-xn²p{©tå ëöâp Z˜3~ŽóR¸Çfu®ó·W\«Õ´V¸ÏyË–ª´:úÈùX¬ ˆÉŠl1J˜£ þÀ^;F"ÒæYç·QLÌþDpåêѶ‡Æáw€Å!Êe)§ïãð¾A‹J8Ÿç1 ø**&ŽžË_,h@Ó£M;бFwZ K‚1V†;p¬©B™Ù¤ÎèB9‰ŒÙ|X ŽA|Ãâ .CÈ·¬ÜRvnØ®»iê‚Ù~(ÆÝÅÜóä»ç·=5Y§FdÞäyj%AÀÿk¥^S‹F×]ÃÍ›ãÁòÂS”fåšÙãX˸|ae33øCŒE±4Ť@Š”>¬êõ0Êâ L–HWBÞoò¾@)2m½þïuiY¥¥ä},¯ZÁ°Ëêš@øî­» ÒA°èAñòÅ¥YÇfzŽÁd÷VÝíX\tW¦&vóa%øú£¯eéÜ¥‚ u(£Í+6W+ÁR±Þ¸_áÄ?=—ù9PC©‚rˆ:›–m*‰’xƒ¼°© ‰¥‚² ¥¬jƒª¸¦xË™•ª¤˜˜ŒMp=”wç&ˆÌ;7G5éöÛû#Î¥úËÕõ(¡´ZŸLÞ°îE¦´û¨ýÎ`SÖRÏ•’¢©‹ÚCb"¤}÷ö‘Y$ó"  %@ÅÄ;žc€ŒYyÌPê,¥5Xнßí­Ö ¼N^9YËpÔ†–uÃ6 uÕŸ¡½‡Êœ‰ÞÍŠ#O ±Ä/¶ËìúFW}èÜ´³*Ãæóhjì6?rþHü;ë‰A$” ì!a«:ÍÛ6Ï^¢×\Ç7–ÞÅ2î»×;ÓxrÜôí¦¶‚ôp›‚ v`ÈCì{æ:b $¢®¸ŸaGv‚Æ¡˜@Þîñ¶Z¹p —®ñƒÇãP%´2Í}çwýVõ}]ïß©ûŽ*$‚8e î¶Ž8öµÛ‹YœÀùlXÇPþ°VˆBÝ "¸$~>ìó° qyY›ŽÚ×­ 4Cô¯}ÓåÀSÎæ1¬8æT8ŒÅÖ¬ªe$*ܸw·¯ºÙ+Á• .Œˆ}Á»h–MçDæº·ßÆb¾X.q8FLþhóœ­sl%ÕÜç7 ø2xÖŒ±5ÜŒ1ÿÁZ;GMQ”+þX‚³Á˜‘uZKœEbö–XTÐVøÁcaÓngÚÈ8†åä‘dè ³³˜ÍÇFvØ#!l¥*¸paÕ¯‡Oh™°:‹Žk½9«Lwù ¾ ¬P¾YÊéàÒsúÄiµÜyÛxÄA¡/±×†±á^tJTqŽŠ6 ïh·.,€à âŠòœy•Òù;ÀRؾ*Ù¹»‚'…H€H€œ¨˜8iIJc(>ØiÀ}1ò‹`µûeö/Ò±AGݵV ø*&¾ÐK¬# Ä .ç3Ü=*3á·nÜ’™sfª¥¤Æk5tïìÑëí^+P£q òb"    ˆÍh1‰Í½cÕ +FaŸ„Í«7Ûq¨2â[Æþ2ÖÞ1:–7ƒÕ# %@‹ _  ÐP1 L,»~ëæ-Ù²f‹ÆÀ¿Ö,‰H€â<*&qþ  •—  o D*&ÑEšå „J€ŠI¨hxƒH€H€H€H€H ºDKŒIt5†å@l%ðôÓOëÆ˜gΜn,[{‰õŠNüD'm–E$@¾A J-&¾Aµ$(&,Y2I’$‰Ä¥?¹(n³'È!P­ZµÈɈ¹ ø(µ˜ø)6†‚@¡B…äâÅ‹²eËI™2åCäÄGI€H€H€HÀ? púÖ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­"    Ÿ"@Åħº‹•%    ÿ$@ÅÄ?û•­Šf·oß~è##‡®3    "@Å$†À³Xÿ"ðçŸÊܹs#ܨ-[¶ÈÆ#ü<$   _'@ÅÄ×{õ2gÎ,“&MŠr¥ä½÷Þ“¢E‹ÆŠ¶°$@$@$@$¨˜Äu–é—êÔ©#|ðWÊ ”’-ZHùòå%Q¢D~É…"   OP1ñ„Ó7ÞÍØ@IDAT€jÖ¬©ÊEçÎ=RN ”4oÞ\®_¿.uëÖõ &!   ÿ%@ÅÄû–-‹f?þ¸Z>%<åÄ(%7nÜlÙ²Iîܹ£¹¶,ŽH€H€H€b*&±«?X'P¯^=mAXʉS)AbóŒ7Õ'   ‡"ð¡žæÃ$@Á”.]ZR§N-çÏŸ£œ8ìØ±C:vì(°”@âÇ/µk×v&á1 ÄI´˜ÄÉng££Š€«¢a”³GI‡l¥u([¶¬<õÔSQUæK$@$@$@>C€Š‰Ït+ê+ŒkV¼xñ´ÊPNþý÷_=¾yóf°f0è=ž ÄaTLâpç³éQCàù矗DžpîiâTR<Ï)I€H€H€HÀÿ $ôÿ&²…$³°§IÅŠåðáÃ’;w K'   XJ€ŠI,íVË¿ Š …H€H€H€HÀ=ºr¹ç«$i®]»&GŽ‘+W®„ØÓ$Ò aF$@$@$@$àã¸Á¢w «{ lݺUFŽ)S§N‘k×®kE%J(µkב¶mÛJùòåŹŒpìm kF$@$@$@QO€ŠIÔ3f qˆÀ¿ÿþk)"SeĈ²}ûv»å¥Ke)’ÊÂ…‡åî½ûz=K–,Ò¦MiÑ¢…¤I“ÆNË   ˆ‹¨˜ÄÅ^g›#À–-[T™>}šmI•2©¼ñziÛ&¿dÏþ”–ù×_ÿÊØqÛeô˜íròô¬(5kÖ’7ß|S*T¨@+J¤÷3$  ðTL|¡—XÇXIàêÕ«¶udÇŽvË”Î(o¶- ¯ÖÉ!I’¸__âþý@ùõ×£2rô6™?ÿmEÉœ9³mEyæ™gìשð *“ ø*&>×e¬pL€udòäɪìܹS«Ïú,[6“ºj½ZçEIœ8ÁCU-0ðåçŸXQ2eÊ$­[·––-[JÚ´iª >L$@$@$@±•“ØÚ3¬W¬ °qãFÛ:rãÆM­ÓÓO%“æoä‘6­óË /xgñ´QgÏ^“qレ(ÇŽ_ÑÇ`E©Q£¦®èU©R%ZQ<…Ét$@$@$@>A€Š‰Ot+þùçÛ:²k×.-Ö‘W^u¤€Ô©ã¡­#ž¶V”¥Ki,ÊÏ?”;wƒVôʘ1£mEI—.§Ù1 ÄZTLbm×°bÑM`ýúõj™1ãG1Ö‘ÔOÃ:’W­#Ï?Ÿ2º«¬¼sçŒe»=vYï%H_ªW¯¡V”*UªÐŠŒOH€H€H€|‰_ê-Ö5Ò `7vÄŽ`ß‘={öhþ°Ž”/ŸY­#µke—D‰.v$²+ +Êo¿W+Êܹl+J† l+ʳÏ>ÙÅ2?   ˆRTL¢/3­Ö­[g[GnÞ¼¥ÕL“:¹;’5kÌZG<åvþüu;åÈÑV”jÕª«¥jÕª´¢x “éH€H€H€b”“ÅÏ£“ÀåË—eÒ¤IªìÝ»W‹†u¤B…,ÒÖ d¯ ­#žòeùrËŠ2j›ÌwPnß¹§¦OŸ^Zµj¥Ï=÷œ§Ù1 D;*&ÑŽœF7µk×ÚÖ‘[·þÓâŸI“\Z4Ï+­[å—,YRDw•¢´¼¿ÿ¾.ã'ì”QÖæ‡\Ò²‹PͶ¢$H»ÜÓ¢3'  ð TL|¢›XIo \ºtɶŽìÛ·O‡u¤bEcÉ! ú÷¦…°¢¬\yBFŒÜ*sæ>°¢Àrb¬(°¨PH€H€H€H 6 bzuˆ4kÖ¬QëȬY3ÅXGÒ>ó¨ZG°ïH¦LOFZY¾”Ñ… 7,+ʵ¢:dE‰?žT­ V”jÕª ­(¾Ô£¬+ ø*&þ×§q®E°ŽLœ8Q’ýû÷kûãÇ‹'•*YGjÔÈî÷Öo:V”‘£¶ÊOsÈ·ƒbQ°Š—±¢`u/ D7*&ÑMœåEÕ«WÛÖ‘ÿþ»­ù¦KûÀ:’1cÜ´Žx øâÅ2aâN ˜?xè¢>+J•*Um+J„ =ÍŽéH€H€H€Hà¡P1y(||8º \¼xQ&L˜  ÉÁƒµxXG*WΪ+kU¯žÖ‘tʪU°¢l“Ù?í·­(ØQ¾eË–º7 vš§ @T b•t™w¤Xµj•n‚øÓO³ÅXGžM÷˜´l´²V† ODZYq9£K—nª+zí?pAQÀŠR©ReyóÍ7­]æ«[Š­(qùaÛI€H€H ªP1‰*²Ì÷¡ \¸pÁ¶Ž:tHóƒu¤J•¬òf›R­Ú VÀ¶¯¬õÐ"ƒ5kNªeÖìýrë¿»šSÚ´im+J¦L™"w>J$@$@$@Á P1 ΃g1L h‰Û•êªëÈíÛw´FÏ= ëH>kß‘|’>=­#ÑÙM°¢Lœ´/ʾý¬(+VÒX”š5kÒŠ²b5¬p·hÑ¢X]GVŽH€†@@@€,\¸ða²õY*&¡¢áè$ð÷ßËøñãeÔ¨Qrøða-:.gû¼ÆŽÐ:ýZY¿ÿþ‡®è5sÖ+Ê3Ï<#-þÇÞY€IYuqüÐÝÝÝÒ­€(Ò]Òùш ( ÒH—tƒtwwwwøÿ]ßqvÙeƒÝ‰ÿyžÝ™yãÆïÎÂ=ï©Ï?—-ZHš4iüº•ÇIÀ#„Q«.…H€Ü$;B¨˜8‚*Û |©×­[g¬#óçϳYGR$)Íšæ1?Éõ=ÅùܽûT¦þqÀ¸z>rÓ û1{+J„œoà 8˜€¥˜ü³k—ƒ{bó$@$òÂäÏo:¥bòìÙ£ƒܸqÃf9uê”éÖXEZjÄŠÓ3vÄAìÑìæÍ°¢ì‘ÙsŽÈÓg^±(‰%2V”æÍ›KºtéÑ-Û$§$@ÅÄ)—…ƒ"&TL‚ $› ]Ь׬Yc¬# Ì——/½6°)SügI–ŒÖ‘Ð]¥÷ëýÞ½gÿZQvË¡ÃÿYQÊ–-gbQªT©"´¢¼cÞíü¨˜8ÿq„$@A'@Å$èìx§¸~ýºÍ:rúôi3"XGPoÖ‘ Ò ÒÑRÜ‹ÀÖ­eÖì#òä©Wƒ„ Ú¬(éÓ§w¯ »él`ÑDí H®\¹$räÈ~ÎôÑ£GrøðaÁƼ`Á‚~^çî'¨˜¸û s~$àÙ¨˜xöú»äìaY½zµ±Ž,\¸ÀfI¥µF¬Ø‘¤Zƒ„âþîß÷²¢ .ʃ7Ì„‹R¦LY›%bĈîÂEgX³fM™3gŽ}Ïž=eÀ€~ÎdÛ¶mR¸pa}ÐV^¿~íçuî~‚Љ»¯0çGžM€Š‰g¯¿KÍþÚµk2iÒ$?~¼œ9sÆŒ=¼Öù䝨‘òåiq© æÁnÛvÉdôš9ë?+J‚ ¤I“&&£W† ‚¹G6÷¾ì¸áíÙ³G²gÏîk³TL¼°P1ñõëÁƒ$@nB€Š‰›,¤»NÖ‘U«VÙ¬#¯^y=)M*–ÖÉk*³'IB눻®Pæ+Ê´é«×þ×M°¢”.]ÆXQªV­*´¢…lðßc¯˜ õ¢E‹Ê¦M›Œ»–ÏÞ¨˜x¡bâó›ÁÏ$@îDÀÑŠ Ëf»Ó·%çrõêUéß¿¿É¸T¾|y™;w®ˆ*)U«d–eÕ“Ó'ÿ'_õ*.TJBpQ\¤«X±"KÛ6dßžV²}K3iÚ$·DAÖ®]+uêÔ‘dÉ’I÷îÝåĉ.2#÷f™2eLÍÍ›7›ZCî?cÎH€H 4°ŽIhPwÑ>ß¼y#+W®4Ö‘Å‹‰eI“:¶©ÈŽÊì‰GÒìNž¼-»÷\•$zÉ’©ýlUÈW®:-p«Q#«lÜx^®\}(Õªfѧìáü¼Ï•OlÙrQ.\¼/U>ˬÁÇáåÁƒç²tÙIIŸ.®äÏŸ4ÐSCZ_XºjÕÌæï½>ûö÷† ^€9yYQv˾ý^V4UªT)cE©V­šDŠ)ˆ­ó¶ °,&]ºt1ŠÉ Aƒ$Nœ8rìØ1A2{ñÏbrïÞ=ùå—_d—Ö÷8r䈤H‘ÂÔwêÔÉ­ sÒbbÿ­à{ w#@‹‰»­¨ ÎçÊ•+Ò¯_?c©X±¢ÌŸ?_ÂÈ?R½ZY±´¾ZG:H¯/‹Y)’'O^JÝúó¤zÍÙªð¼ñ“ÒÓ˜ë&LÚk®8x³ùüðás?ïqõ¿ ßnæˆt¼Ë—˜Ïã'ì ÒÔ¾ùvôújm€îõÙw€n ÂE1cF’6­óËÞÝ­dÇÖfÒ\ lF‹AÖ¯_/õêÕ3V”nݺÉñãǃÐ:o }úô‘´iÓÊÝ»w¥sçÎjrÇŽ’'OAýõ—\ºtI6lØ Ã‡—9rȘ1cÕ/& pOtårÏu}ïYÁ:²lÙ2¿ªT)å믿–sçÎIÚ4±¥ÿeäâùN2gVMùè£t¾ú›v¹r%–œ9Êmµˆ¬Yã8ï[H? iØ §y-¥Ö•zu²ëÓôðæ3¹>’ɸ±ŸÊÕË]dô¨J’7Ob“²ö矖̙3+ÊôéÓåùs÷UFq£D‰"£G6CÄ–D ÈT¨PÁüûQ»vm9zô¨<{ö̸ê!Ó×ãÇ¥mÛ¶‹ …H€HÀ³ P1ñìõkö—/_–¾}û׊?þX,X`¬#5ªg‘•ËêË©äËžÅ$Q¢ ¹l½Õ¡ÝKÙ°”»Sæ-,p-Š=¢qkÂÁîÝŠÈ´?ªIt=Fq/1bD’V-óÉî-eç¶æÒ¢Y‰-¢yÒ^¿~}cE‹ÜŠ(!CàÃ?” ˜Î L@ÁðO‹å¤lÙ²…Ê%Ü… é‡;tè xÒªU+ÿšây  7'ÀÇÌn¾À™6Ë—/7îK–ü¥5¼\©Ò¥#-´â眜0a´€4õ^×Ô«›C¾è¹Zæ/8&£«¤U½njÌwTÈĸEU7ÈÒ¥'åü…û&û—½ÕäÅ‹×2sÖaõg¿"OµÀ_ñ⩤¢sŒ?ª¹ï‚Þƒó™3Å“O?ÍdŽá×þý×4†åŒdHWªh ¿%—.=’"…“kf¢”Öa__Ñöï“÷Éž½×taõ‰ã¦;¶÷ât·o?‘5kÏjÍ—3Eƒ¿‹K)E‹¤ Öx l{oÞü#“§ì7q:XsX*°ÞˆcñOüãëßý=Xü ù¹¼LŸá•Ñ 1IC‡5?Å‹7ÛêÕ«¿³``ûåõosXSQ|.žxñ.±Ü´àþ…'>ŠÎ¯¿þ*7n¼»âóz~& p_þï@Üwî?3øyOœ8ÑÔ¹xñ¢áQ•jU³J«yµ^š`qÓ (hlÈË–M+«t£¾J•ƒ?ö^ײ¤X–´;ò·~J¹³Úܹλ'µêÌ‘ª”ÄÒØ¤¢7a¯$M]/¬+yó&Ä4ôújdÌè]1;nŒ½Ë¸¬Ù+&PŠz¨Òô×Â:ïœÎЧ¤^ƒyrçî3I¤Ê‚õçÌ=*¿ŽØ!{vµ´Åá,På«F­Ù.žÇ“›7Ëp½F‡*'T–&s¿³Ÿ'Û”µO?›aØ%Nä5ΩÓ !.[‚˜Ž˜>»°}_ÛÅÁü–±–-ò™Ÿ=ª˜Œ·[•C&…-ÒØþï¤Q£Æ¦.JÖ¬Yƒ¹w6ñãÇ—!C†HãÆÁðˆÊ’%‹¯pPÛèáÇæß‘råÊùzMΜ9M`ý«W¯L ‘Ï z_oâA  ·$ðöã+·œ&'e@EfŸV®\YR§N%½{÷(%éÓÅ‘ʚؑYÖ0 ‚•]ƺ7$^5ôŠ™5û°·î,7®ÉcjŒAjoç|~¨[®‚»*S'W‘»·{È[=dõŠòèÑ )Uf²ÉjëEáÂ)Ôßý–Q ¬6Öo8gÞž9{O¹Ü·ËFäHጲf;èã êsÔo8ßò¯YÙP®]éjâ$¾è^D®^{$=¿\m»£M»%’ AT9z¸:ÐF®_í&›7~nÎ÷î³Þv]@ß¶½+Wéï;²W•¥«—»Êƒ{=¥µºMúµ /^Tùþ»Ò& òê5gÍ1d×Ê–-¡áVÝå,)¢n\pçºpñv¿²ûûÔö~Ÿø™äÎØ´7¸aCËKuãBÚÞ[·žøÚo`øúÚ€Š´Û[Èž-Œ‚SãSþþûoµž4R׸$Ò±cG9|Ø»²ë€¡xT“pÑB@üÆeÒ¤Iïœ;ˆø%~¿pÁëo0iÒÀ§¿ö«]' p=TL\oÍòyÚ|Þ¹s§Æµy).HGL! ð\Œ1qõÇÓÇ &˜dÙ‚ v¤fõlꛟ×<ùwæiCù@¼bJ˜7®Í›/J>uÝÉš5Á;‡~B 5B5Yàçuˆÿ€äÌ™H’i\‹µÙÆk ŽÏ§.Ap·ÜºÖ®ó²tTòóⳃӧï˜C9r$òyJ¬`}ëÄÙ³weÌØÝ2aâ^¹uÛk“Œlc¦Häã—Öe~ l{)RÄòµí¢ª˜ Ì}“Àðõíþ:·™ÖBÁϾ}×L,Ê´é‡ô{´Ùütìø?iذ‘)Þ……4¨-3cÆ ¸þå—_¾ÕH¢D‰ÔjÏ(&³fÍ’¦M›¾uÍÖ­[ͱ4iÒ˜Lko]À$@$@C€Š‰›,5ž8"vdìØ±šak™¦ßôzäžIƒ»áæÒ¸Q.[F*gŸ2Ü®¨¾hñqx%–—ÖÌËró‚E(UªØ¾NÖKàÎå–(&ˆ_1®_êŠÕëë³&ÎudïJ“æ?·+ë~ûW0‡—™•ÕÌþœýûCÇ‘¿à8 ø +pM+]:µdÔølÙHå*š¬`ê}` J{×4æÅ7‚ñ+t`ùúÖGHƒ»Ú¨‘•ä§ÁÉŸ3©’²G`ÝB&(ü.\Ø((¨±×$JÀ „^Æg¢š;äæ>Ô*éÞ½»üðÃ&.%UªT¶Ο?/#GŽ4Ÿ{õêåkÖ.ÛÅ|C$@$àö¨˜¸øã?vË:‚ íHê:„ªì°Ž”,™Ús¥_Øü"uðOC¶ª’uJàÆ^³WÕÕBŠþ 6øËWœ6–¸sÙËuCûM7R#c×ÂEÇ|T‡”ý7Fd™ö{Ú¶)`ß”¯ï‘áëÔ黚!ê‚&ðŠS±.lÝæ/t?}Zu“ùê¹ÆÏLÒŸsBZâÀ 2i¶½ËWj¶¤ç‚Z!ö²éo/_ûØûóåkoh¿‡ÕªéçyÌÏ×åµÌá‰=~:uêh³¢ 9%` ,(íÚµ3Jžow N ²ÿ¡°â| PQËŠ ê$Áª[¤H“åË·ûq ÊSh$÷pÄ\Ø& ¸Ƙ¸àj"­&þCGÄ´iÓ˜:PJðTÿçÁÊå‹MÑAWTJ¬å°¬#Æo7n\å˧P`~ùÒ™&ÿuÂjÊöú}ß Rñ“éF±–+—V"¨å¢ÿ€M&¾LéÔæܹbÇŠ$ƒo‘gÏ_‹n\¸éÓO2š{—ÛÅÆàÀ:u£OéŸ<õ hG½HuM¶o¿d‚ïqÎÇCgòS‚ÚRÛ˱c·äÜùûR¦tj?k©–¯}ûÎôn|#~ýØdôš8¾²ú ™Ü¿ÿ@FŒ¡.~9ÝOžøžÀ™æâ cA=“äÉ“û:dãÚ¾}»I+|ýúuïƒâŒ`,\mÚ´Ñ¿‘uZó'‚¯÷;â ”"ÄÜ!¾…B$@$à<h1qžµðw$çÎ35GðôñêÕ«æúȰ\½ZfSסD‰ÿ\$ümÌÉ/ÀÆ1—þXñ–¢âß°QûîY¨mÒ²Õbã* ŒUK1öõI`1(^<¥¬]wÎdÎÊ”)¾é"\¸°Æ­ ÙÁûlYþIófyeø¯;dô˜Ý‚‡>L+kl ,°øôÐ*õ¸‹MÓÚ=5-/\ÕPSñ,~Ož,†\ºüP븜6I ÐŽ”ö¢G‹ HK|óæcB:ã¾?l4ãü®w)?» ,_?r’°¢ €(~„eÀвmÛ6óÓ¹s'©_¿qõÊ•+—“Œ:ä†1{öìu#F “vܯ‹q~Ú´i2pà@ùÙg¬$¨3%0V,ßãüj+8Ž#4”ÎÏ?ÿÜÔeAaÎjÕª¹MŒËº]»äº&ðM’%L(9Ò§—غ&ž&7ïÞ•ë×KNMÊðAÄ–¡xñ„… m˜?Õ扵½<Ф3W­2‡Ò&K&eÕR2uÉy­côO*iÒqÞíÊì_!}ë|Ok(…U¿èFŸ|âg÷ûOœÿflL®‹}ëÚ'ÏžÉ4-* A{Ÿk¹……6È‘3g̱ÖúïG¼Ø¾»Ž› ø+P¨IÛ+!P·ñâ"ë2k!vdåʶؑ,™ãW­F sIܸîéÿ³ºruë±ÊIDMß*’WútºQBn]ïfRób]P¯¤c§å2ñ÷}¶e‚U®m}tÓmU·NZýÔSW±iT³ˈ‘;¤CÇ傯óæÔ²×¤ÙEE‰´µW†þü‘T¯îåZ†?¹fÍɤÉû­KLLÍhÍ&…ÌXuêÍ•»ó‚Ô·È2U[‹EÎRw¶«Z¯#qâèêsS²æøÍÁDÀ¶—9ëHeÅ(m_}½VîÝnÆ ΟӫËGÿZpÐgß8X¾¸Ç•Å'‘xÅ·l½d:ÜZ¶li\‘¢E‹f;Î7®K('–e ñF5jÔ *9ÞÈrûG•ƒÐ’²Z[f­?ýgK›Vf«¢˜E“EΨµkÞ=RX•ËÌ©S¥ ‡ÞãÛøvê´ íÖ  îÔÉ¡ý£ñ/_J$ýNYÒ bE™Ú·¯õѼžVŽé«T1ïkiÒ™?þèí|@?Ó—,]j.ÿX7×Eð %R¡BòB÷þÉfM¦öük'$Ïg«YSŽœ=«eÂʫ޽ìÇñ·>()Þ¼¹9_•‹ë+W¾ë¶H•Ϻv5×Ñïûf}8\[cçf­^mŽ3Ç)ÿìçœïÃä÷ryw”ú@Å$8W+Û:«PãÇ7¾Ù¨ž u¤Fu¯Ø‘âÅÝÇ:ŒØ¼5…4¼(´ˆ t@û•‰ÊÛMÁôáÆÇZ7ㆉgÉ é™#hV4ŸrâÄm­t}K«f'ôéãÚN#ÕÕ ËM@%(í!P»Ö}‰¦”ìÙª¿ÐäP&ï{⎠ Lýã *p^ÙÜbÆŒa³¢äÎû}»àý¡LÊI“&M¼¥Ž5ªTªTÉ() ø_f?gRLýï’ÎÎʼn«·nÉjݨá‰oìèÑåàÌ™’\³§Vð¹Á7ßÈÈ/¾¶º t6ñm|¡­˜ §ÉÖß÷f­ .Åd¡Zªh–<ÈÎ¥sýúæý»~Q1ñJÖ‘BÝâ/ß¼iPm×õ)èÚֺ3ož9?\w¨S‡Š‰Òp”bBW®wýÕ†ð¹—úteÑ¢EÆ:²jÕJ[œAÖ,°ŽäTEÇ=­#Ž@$I Ý\„Ž»B„Ñ4&æÝO",Ÿ×.¸²V‚ÒÔ. Š„&ß Œ7(÷@YþKEøc9“„®^›5Âo¿ýf~°i…¥nݺªÜÑŠÆ¡}ÖðwÝŒØ+'° À ?)S¦Tk§kºz•ѹåË’å-Äí5AuÍ”6Oc{ ¤4ÑZWÇ€{J矖-þ$uüHÞîáˆ~×á¦ä›¤LìUˆ×·s®~ jª¥j˜¦=‡,Û²å-Åd©¦™‡€O ½2îë¯e„*å¸1cšWþ TL‚‡ã{µrFý-ë‚C!¨À¬Rp?BM @èˆ%‚>Èe~޹i¬(S¦0ÁÓxâÞ¥Kg›%Ož<¡7Pö$¾)'VC¨ 5tèPó®^V»¡ýú¡º&B1Ùwü¸fîð®˜Îc¾~ïæ¬YcÎuTëƒe¡øõÏ?e÷±cæøØ¯¾’ÉZß³ãš}ãë¢V ¬“½<Õøú`aÓÞ½rçþ}óýø¶E ûKü}_ëÃmŠ ”Þú°É’Cú·pñß}Y‰¼y%É¿1BÛ6ýNBwì(I$°n‘êâø›ºw!~)Õñ÷P£lY[ü ÚĘ!ù4Cae;ØBòdÊ$Ý64ïñ÷ÐuØ0ó¬{ª…×…®\¡´º°Ž,Ô 8ÄŽ¬^½ÊfÉ®î;¨;‚M*zSH€œ“Ì™{ÄÊD’Kòë¦Á²¢D× ÅuøæÖåÛèßåêåL®\»¦Nõ¶Y´ŸK?øZ-“z÷öf16}ºô>\߈ÍÖuqÁ“þvê®õ‹º±„ NJêÆoóþýf£ 7~.©²‚`àR­ZùÃÑVc'°Y;¬…6³j|ËQuWΪmöhÔHÖªr¿KÓIãÉómUP,—«ÿé¦Nl‚¬Wƒ™!_êÆ¬ûöæ½o¿üß9Í^‰©.¡ÊV jî¥Éð”Pæb~aà Ů˜n*ñsx×s¤®¥” [2?EŠÑX`ÉÂÓú^ºI¯q>°VÀ%ÏÊV¥T)cmJ/ž?£úïtOÝ|[JõGEhüžÒÃ*Ö¸O›RR[­ Åõ{rX•Ñ1ªd!ˆ¾½®aYuLk×dßV`Þ#¿±fÓʨ®”3Õ w@­ P¨œ¥˜à½¥”$Ðïæ§%Jë ”æÀˆåÎõ³®  (Uõ5QÄrã ¯ yuU&Þ%Pv{ëÞã„V"¬;¬P~ñ·…¤ª©°ÖÊÚ¤÷ç4Ã*2è%ÒµÚm§\BI‚¥/X -Abw*&!¼ÂVþ|«ÛT)cÉœY5%þ¤Ö!¾’ ¸$0:¤¼têøÔ¨5[ví¾ª,É|uÈãbi6] }¨×rõ‚õ»·ZœIšj­–hQþ‹K„+ÑaÝÔ>V÷—„ú½ÄÞ>ð½Û¿î"C5‘åK7XS¿ÿÞlÖàz‹‚OÖÃà‘nx««œo›YXæ ¬‰_"™&s«{ R×þ¡Ö(ÙuÃ"ô‡> ´ÚÍ”:µIÓ;CR¸ YV†àæw¸:}dÜݪ+ÜÚ|XK~P‹–%kÔ²•Q­((‰µ ¬á$]¯š6•:åËKed)&P,;jÜ[`d°Z×|¸!A1­Ê'@ê©Ú´~°]¥à{µ@9Á¸'ÃßÁWÍš ¬Ä@%ûWQÀú@`…«âvÒ§ðŠ—ª©É»èw*0‚ŒhPL ËÔ Š R9[Jâµ,+Š_íöSÅJ d†*óT©†|¤ŠÈÇêÖ(&8ÅÅ£rÉ’ÆŠ‡Ï©`-QÅiŒq P4óªë—» “^aT”§ÿüªO'8 ç/Ü—‚…Æ›šˆ'©òYfSµ<„‡ÅîH€‚Hn«WŸ‘±Z¯f¡Ö½yùêi)•n$PVŸÁ…ƒâP¡ %jPDPùÁñ(xël `íx®›\K`±‡ûä¡øã ¯€˜ <ýÆ]ÔÓ@ {)ªég“qBcn|Z6ì¯ ì{Ęø¦” ÄRXʃÕ.Üt g._²b·*ŸíføwS ×*(&Žâ1P3¥!# X<|ºŽan'•ñðV0.K)Áçèš1±ÒÆÓ}¸p¥Ñõz_‰¬Öß,&p݃X›c¼‡rh/ø ÅbÿtßþšÀ¾·ï.…pó“ª@ßòô¹WªûÜ3Ú”œƒRW1(½ĹÀ­ Œ겆¿ÄOY/P(ýÄŽ@ $Â*6A aC°N‘´€,þ­Ø¬òšFú«Q£Ìy°…ò‚¿?ÌñG?ªâ Å+îÖCßÖÈ4âF¿¨˜„Âb6WS.~PÄ OÙfÎüSÖ¬=k~Ä*Mç2q&2Ü  Ó`—$àÑ®iQÊIZ+gü„=ræì=Ã"|øpZÀ³Š‰1)¯O1ƒóɲGáÉ/Ó'ñÔ½Ç?¥$™nQ ²v9«ÌUkƒ¥xXcÄÜàªRM]°Ú$uõ{S­"P8 ØÅñ#ç`ñ‹)žÆû%ˆÿð)–›•O~Ÿ×½ës2»@eë:Ÿí:Ь5Ý5&y8ÄfÓ§œÒM·%PB| \¨ ˜@ Ä‡`µ¼¡E?á–‡dP‚ 4#~ʼnÀâ n\˜'P%)¤2~ñU‘¡¤ìWßÈÁ?o•Ÿô§téÔ&îþ"FôzjJCe·$@Jÿá®ZõŸuä•Ö¤VW͆UÁ7‰¬¯ŠêÞŒÈÎ媂Mª[£ÂøÝŒÂUŸ­M)\wºk@º_’!ˆŠ|æ!Ù´ÂEÇ‚xÿÄ‘<¾hÒÄd$»tㆠ¶Ã§Ø» Á‚ãS`-²ÄþZë˜#^íûÁ˜ì]áâemž‘q OõíŸìû\sŸÖ8߯k¿oçíp´lü­LZöÇý{_[­"PL ÈJg)°f ù»ß—jÍBbÄC!îÇ/Áø £]¸%ÂriYš ©W ”’@X™¿ð… ˜''XåXúT¨mÛ¶æg»š-+ÊÚuç?ñµJw“ƹÅ·ºN0—ÂÝ»OM}гúÄ»m›ü!Z„1(ÐNž¼-ë7œ—>L+©RÅöÖ I®YsFjQ@Ý?KÎ ¥\¹´Þþƒðv?˜ŠIzYGöÊY-~ u¤ªf A®ô?4k#àFy¡Óx—RâÌ®ZïAÅÈy„'¶|† SÃ}~Ÿd|ãÎ‰óŽ šåöcï>fÕ_–Âúìì¯ÁÁï9ºð£f¬BJÄXbÅ'Ø»ÊmQÅ›~ÜcÉjÝ'X’%Më­C_Ñì!pq²OÛ»F³ªY’EÒ@¢ÛÅ6ùT`x_±Öí =¯½ ˆÜr…³?îß{X1`iâ…ø•›êÞA O@n†P$ x!™½òt^­)PР`XÖ9Ä™@1ÅÊ@¥ñ]°¬A™‡U WJÄ{y‚8æq„'sÐ?Ð4x4xìÊ•«2JýQYúÖí§òÓ­’)ëH)]v²Ìøó<þÊA#ðœfñâ{ZµY"?Ú,—.yùô:3-[/IËÖÉÞ½ÿýg†ñ^¼x_Š—œ$Uœ&]»¯’n=V™÷ÅJL¸QOŠÞò姤ZY’2õ/òÕ7ëŒR’Fÿƒî§fú‹/É<­\Aý}nâßï-¾)%pÕê ÇõšºôOÍ USÓÚ:[üÈûòB¬ÄzÒG}Ûa-A Ö­ÿh/ÈTB-‚·6UÖy{ “µ)5µQ¬ ô®cg|yòow‰ÃÞÚ/0•G@û@¹å²ãóX BPÞE³9a]ŒLT–ËPv…AŒŽOÁù ÎÛg[Öçæê¢Š Sñ;º)Èð†Zä·¤¥º8B°±¶é Wªë:6ç¸ÖRp¬óAy…åñP(_i¼gÔéÈÀwûþoµ‰WÄÞTÖl_X@ påj¡É`Iš!57Ò§Ñdýuý,Áõ^DÅÄ á½e-±¾¨{ñ7.Ì•Š (8¡ÄÔÿ$Ú´i£н²CŸRÀM$Z´¨æiy½ó$YŠ¡Ò­ûJ9~ü–ŽÞ5†tôè-9uú®+šBNk/ ¼ahͼióE³tI~ý¥‚\¿ÒUvlm&M›ä(2Í[.­a¹d¿W®<”~ý7Iº Ã¥â'Óeþ‚cFÍôˆ'@ÙÓšÕ¨W¯^’Øî?^—œ(-PJ:j¶lâઠÜj7iQ·Î;;uüÈû.Ÿõôõ¢Ýû_{ô0%Ôª@ºVXIP )n¡À œ%Ö“zdêBv¨gºÆO²‘m¨ÆêÌÑ´¼Ø0¢‡ýFÕjѯ¾/°ý†G`Û†«Ò[SŸ÷â3Z4sxŒ>I æØ¥J™€yÄ“÷!úµ¬T¨ c Ò,GÖ§ñÈÀ\’3C[ ëª m3ê äÖºGU€”ÔxÄi@àêUüߨ¬£Þ6ç=GŒ0±æ¢÷ø…‡A?uêd㇠"µ\gQWKlæi}— ܹì AŒ×Áþ¸oï{©+¯¥,Á’L•O¬êA`EB5KhŒ‰}–­L©SÛ\Æ,źÖ>€uÌ]_©˜¸ÀÊ¢*12y]½zM~Ó/x^ýã¿}ç©ü tÍ?‡]»®Èê5g»_ûv%aÂhFÉ7öSIœ(š,YzRîÝ{H:žu9¬#Ë–”ªÕgJª4¿È×ß®“sçZý5í#¬#sÔ´—-ÿ|Ÿ=‹œëÎJI'ÝØàßÓZÅþùg—Ž Ìj 6i`‘ ‚Í ØÁšÜðÛo%ŸúÉ×ÓJìÈ(„b~ˆE±•©Q/55šj:a+[ÒüŸ~2ÑnÍž=å'MC‹§èÍô‰{HŠ_ã ÌÃ#0íZ×"TC-$è› õí^U’ jK,W¯4I“Ê& ž·9€¡ÙgŸÙ¬¤Æ:§ŒÔ"?ëß ¾«u(Wp]Z¡J‡ý¿ø.À¢c)_°¸à:² +Ô<íÃRàÐ&,ÓÕ¢õŠäÒ _önbuãB_¨3ƒÔÎM´~ æ >°xA°Ž{t=“ýûwgê/ÌÁËJ‚ÏöŠ ”z+…µu­;¿²ò»‹®. ¸!eÆŒéòè‘Wú¼xq£hÅøœ&`>sæø.:³ö_UšâuøˆòI¥ Réã ‚$‰yùp"¦`ò”ýÆeêµnZsåL¤\óÚÎc”pŸúsæa) 5hJ•Jímà«V–}û¯KÓÏsK¼xQ;ÕÔ?H©’©$[¶„F‘ܾý²ZÁ"¨Å&¥Ôfõv?>œÓX†¥ºY>|ø¦äÔþË”N6lí²ÿÖûÝüäªêÕ š²µ²ºVwV-ëûòþíª‚:È„ kÔNÀæÒz2ïsNPjS‚”®ö×-¸²Àr×—ìZö×:ò½_ã LŸá˜vz-\é >{ñÂÔÁFÕúžùl–«›7„¬eÈ>æÓõÎçõAù p™BF0ÔšÁ¦Ù¾^ŽÏ6á΄ïRjU¨ü "÷yo@>c<{µø¦•í Bh ¾wökæóï#´Ç÷>ý;ºò;“÷Y'¸÷‘þÁOŸ>Ý()»wﶨDñ”FA©^-‹º(0Ç Ì¿o*}:]V®<­éùþÑâLaô?Ý0²a]Í’–\Ö­;+uêÍ•7ŸHêT±äÅ‹×råê#‰'²L\Uëd0­lØpNJ•"ݺ–Áƒ¼Ì×V?mÛ-‘߯ì–ÃÚHÖ¬ ôiìe)Xx‚ü¯}AÙô÷Ù·ïšÄŽYîþkÉøò‹¢Ò¿_YëvY¿þœT©6Sî?x.Q£„7c@f¶Õ³ÊUpìðߺõD’%‹)Q£z=ÉBC½ä)‡jüC¹u£»yµuàÁo¬ØÔ‚ Å’Nÿsm¡Åã>×ÊÏ }<Õò`\œz XFWVL9e^N$àA­˜¸¦ïŠ}ü›jtÍÒ€¬@° @1Áû1¢ËÆM¤A£ù&¥s—räÈMÿšò¨óKד¥Õ3sî÷Cyþôk£”<|ø\j׫ñÞȶÍMåìéŽrùbù{Cyùò4þ|Q‚ šÄ‰£k›åέ²wWK‰£ ÒC¿|镦nW•«üiÌðP@Üë)÷ïöÔ¬aŒRâ³ï8q¢jÞØ+%P¦êÖŸk›o¿)A¥D¡!¹Á÷}7Hê´¿H%µ$-\t\ÂiÊF6¯ZµJNj¦˜/ÔUJ‰Ïo?“ @È b2œC¤øJ3Ædô‚›W~­š{GŸ¦¾]²åüÍdm‚;ѳgÌèåׂ ûe»ÜTëC§Ž…䃒Û.+ªîVßõ)e2¤úm§íx`ߤHS橲‘$‰WNôܹ72T ?~ü¶inÄÈòP­ßWÚ¸k… Ö(°Ê|P0`ú-[-–¿–œXb0O•×ZgV(zPHz·A.ª‚’^‹º!®àÒ¥Ë2kÖ,M«L—-OýŽpÞ$@$@ÎC€Š‰ó¬E°V¸¤ ˜sæ÷n­Ù3bÆŒ!o¾(š,¤É‡H§ÎË5vÁ{Q¢`€ 7´U3[AJ–HõÖ,,¥1A•Ò‹âÓµ.S¦x¦¹3g¼ê ¬Y{Ö|nØàí45ªgñ·ë)S÷Ë䩤nílÞÜÃü½Ñ.@üÏwß{YG>U¥d±*'°ŽÔÖÀË5kÖÈ MqÙC3%ðáïF8  p9TL\nÉ7àuêŽDŽN­%o[&²dñJ(pî¼W›w!@ƒoO‹eú+˜uÏiMa]ƒâãj2Ÿ’\-.þIï>ëM6®ñã*ûw©[‡udñâãòég3$MºáÒG“K—JFͲ2hÐ cAMŠ2ZÀŠp+œ ¸8*&.¾€>Šƒ¡ j¢ 6 j¤ÀвyËE›X¨V”¡ò¿ŽËäV ÷ty­ñˆ1ñ)'OÞ1‡¬Ì]>ÏÛ¶2<ùL×·,ÿD3*Ê“'/}Mñûøñ»HÞ¼pá¾æLì-æÄ¿>]ù<æ e i~+Wi\ØÂk:Ë:uêÈÚµkÕEî¸tïÞÖW^dŽH€HÀ#ø¿Kò ž5IT“GUyÔEA•yT›¿wÿ™ü:r§äÈ=ZŠ›hRåz¢%C†¸ZÍölÔ´¼>eÛv/7¯tiã˜S–’eÀ§œ8á/âóx@>gÌOS=Ц3=÷ÖåÇŽ½» &”ª?¦V“ïz—zë^w:ëÈ" ^Gv5XG¾ÿa£\Öˆ™4wýOš×±#3fÌÒ¥K»Ó´9  pkTLÜzyß=¹¨Q£JS­BºmÛ6Ù¿¿´mÛVbÅŠ)ˆ³@­Œ$ɆH‡ÿ-“ƒ¯¿»!7:ûчéÌl–.;åmV°| P!¤AýæÕríB½{Y±Bóÿÿ/b< ï+”Oo.õ9d CÍ’wIM-Œz,°˜¸£œW7ºo{¯“”©‡ÉgšNŒ"DŒ uëÖÕËëå˜VªîÚµ«ÄÏ:>î¸þœ €{` ÷^ßÏ.§Ð©,3gÎ4uQ °ŒµÓüÒ˜‹–-òIíZÙÜÚE¨Mëüò«¦ô©óŽ=¢|V9“q«BÎÊUg¤ZÕÌ’/_RÃ…3ª…uIÚ´]"eˤ‘½ZŸdô˜]Z´-–œW£ Hûv™¿Pg1†ZÏ¥"Ez÷Ÿì×%w¾±‚:6¨Ëâ·:dÖ+N›Âa˜WæÌ™M’‡ÆkK¯î0_ÎH€H€<•-&žºò~ÌV˜Ûºu«8p@Ú·o¯…cÉ6­RÞ´ù"“Ñ«]û¥ja¹æG ®}E ׯm,Åucÿã ÍRXÝÚÊ~4Õd¹jÝ2ŸÌ˜VÝÛçÏ­-HP1ñ„I€ÜŠ·ZNÏž ªr{YQ¦ÈíÛ· Œè _·N6ãê•_ç)®KàÔ)Ë:²OnÜ|b&5j±¬#… vÝÉqä$@TLŠ—‘ ¸$*&.¹lô»<þÜfEÙ°aƒíÒ<¹7/ÍÓŠbÃâÔo^¼x-ó¨Â©Ö‘uëÎÙ¬#9sæ´YGbÅrÿú+N½H\ˆ b¢¸Ù @ bÂÀÙ]È8~ü¸Œ7N&Ož,·nÝ2G‹Á‹R @²{ “'oÛbGnÞúÏ:R§N]£|ðÁj‡‘€»°w›çC$@öPˆÚÂ‹Ž Ê6MàÅ‹6+Êúõëm÷çΕȸyÁŠ3&cQl`Bá ¬#óækRƒq{dÝús¶äÎÛdÖBìH̘1mÇù†<‘@¥J•déÒ¥ž8uΙHÀCTªXQþrпsTL<äKäJÓ}ÚÄ¢Lš4ÉfE‰9¼Ôª™Õ¸z.œÂácp厽iÙ§L= wîþg©W¯¾QHòåËçÊÓãØI€H€H€\Œ[0÷m/_¾”… ʘ1c´Òøj›%{¶ÆÍ«aZQ,jÏžÁ:rÄ($›þþÏ:%®ZuëÖ•èÑ£[—ó•H€H€H€BŒ“CÍŽB‚À™3glV”ëׯ›.aE©Y±(y¥hÑ”!1 §ëãÈXGv ¬#wï=3ã‹#ºXÖ¸mQH€H€H€H 4 P1 MúìÛa`EY´h‘‰EYµj¥ÍŠ’-kiÑ<¯4j˜Sâĉâ°þ¡á§O_Êì9^Ö‘Í[.Ú†T @ãªëH´hÑlÇù†H€H€H€B““ФϾC„ÀÙ³geüøñ2qâD¹víšé3r$XQ²W¯bÅÜËŠrøð ãª5õÿ¬#1cÆúõ…)$@$@$@$àl¨˜8ÛŠp<#ðêÕ+oV”7oþ1}eÉ߸y5j˜KâÆuM+ ¬#³fÃ:²[¶l½dcˆâ‡-[¶Ôt¿uh±Qá   g$@ÅÄW…cr8sçÎÙ¬(W¯^5ýÁŠR½ZfcE)Q"•ÃÇG€H€H€H€BŠ“"Í~Üš,'V, ,*XQª|+J^)S&±¢ìÙëÈnuä¡*'¸qãØ¬#Y³f5Çø‹H€H€H€<O[qÎסÞ¼yc³¢,Yò—ÍŠ’.m‰+’ìÙ{ÍÖñâÅu¤F´ŽØ¨ð €§ bâ©+Ïy;œ²xMœ8ÑÔF¹yó¦„ N"EŠ$5’-ZH–,Y>v@$@$@$@®B€Š‰«¬Çé²`E)X° Ü»wOvìØ¡®[q]v.8 8Š@XG5ÌvI€¼„ ÖÄ—@A¡ øN€Š‰ï\x”H€H€H€H€H  P1 AØìŠH€H€H€H€HÀwTL|ç£$@$@$@$@$@!H€ŠIÂfW$@$@$@$@$@¾ bâ;%    ATLB6»"    ðß¹ð( @ b‚°Ù €ï¨˜øÎ…GI€H€H€H€H€B“„Í®H€H€H€H€H€|'@ÅÄw.Fñ±]Ä7$@$@$@$@ÁJ ŒnÎ|ßk7lŒÞŸÀøñã¥E‹1bD9xð d̘QÞ¼y# ={ö˜À÷¥K—zë®_Í›7—x;ްÂ@¡‰9²íÜâÅ‹¥FòâÅ s ôÏŸ?7ïÓ¤I#óçÏ—\¹rÙ®èŒñöíÛ²k×.‰7n@oãu$@$@$@$à1h1ñ˜¥vý‰6kÖÌX< 4tèÐÁLAîPJìcQ¬™®^½ZjÕª%Ož<1Ó§O˵k×dÁ‚& ~æÌ™òÉ'ŸX—å¥qãÆF)éß¿¿\¾|Yž>}*çΓ:uê,) 6ôÓÒbkˆoH€H€H€H€M€“@#ã ¡IàøñãÆb+”¸eݽ{×Ä•tíÚÕ64¤Γ'±¬ >ܦÈXœ?^²gÏ.=’}ûö™6×­['eÊ”1J ”0aÂX—›ë’$Ib^q.uêÔ¶syC‹I@(ñ   O&@‹‰'¯¾ Î=S¦LÒ«W/3òV­Z¥$oÞ¼Ò©S'o³A/¸{%J”HÚµkçí> qéÒ¥Íñ©S§šWr„ HãÖ­[Í{ëWôèѵö)R¤°ó•H€H€H€H ˜°ò{0d3!GV¸a9rDÂ… 'ãÆ3¯ö#8zô¨ù˜4iRYµj•ý)Û{dð‚@-ZTG‹H±bŤxñâR¹re£ÀÀúw1 €cP1q W¶ê@~G€ú÷ß/™3gXL| \¾ {÷î• *ø<íí³5jTY¿~½´lÙRV¬X!7n4?¸–—êÕ«K=ŒµÅ[ü@$@$@$@$ðÞ¨˜¼7B6¬øëÕç`I ƒVÛ¶m}žöö9A‚¶Ï)S¦”åË—›ÀwdøBÜ Ü®_¿n =.Z´Hž8Y²d¶{ø†H€H€H€Hàý P1y†lÁ •0™µ.8lØÀ…SAñ@jbü £6”dðB•3fH·nÝœpÖ ¸.ÀíÖ\wž¹‡@Æ-È­[·dûöí¾Î)€cÇŽ-(ÞiÓ¦ù½Ô«WÏ\Ï_$@$@$@$@ÁG€ŠIð±dKNFïP& ` `?–äÎ[ à-c”–Å‹—­;wšâÖõx-Y²¤Œ=ZPÓ„B$@$@$@$¼Xù=xy²5'$7,¤†’­´iÓ%ïŒ^¸~×®]ÆÊ‚b‹¨…‚`zßÒtº¬üPR¼ŽH€H€HÀS P1ñÔ•ç¼C”“ÅÍÎH€H€H€\ƒß]pÑ8d    p7TLÜmE9    pATL\pÑ8d    p7TLÜmE9    pATL\pÑ8d    p7TLÜmE9ŸP!pæÌ¹sçNû~ñâ…ìß¿?È÷óF   puTL\}9~§ "E i×®]”(%mÛ¶•øñã;Å\8    TLBƒ:ût;"DL™2™*ó±œ@)iݺµ‚âoܸ!wïÞ•"EŠÈ–-[lçÍkÆ ¶Ï|C$@$@$@žJ€O]yÎÛao/°œ<|øP&Nœ( ëׯ{;=z4騱“tèÐA%Jäí? €§ bâ©+Ïy;„’1cÆÈÀ?Ê­[^–‘ù“Ê—_•$IbÈ·}Ö˪ÕgLßQ£F‘öí;HçÎ%qâÄ%   W!@ÅÄUVŠãtj÷ïß—Q£FÉàÁƒäîÝ{f¬E 'W…¤˜|òIFocß½ûŠQP–.;eŽGŽIÚ´i+]»v•dÉ’y»–H€H€H€HÀSP1ñ”•æ<BàÎ;2bÄ2äg¹ÿé É7_• Ò¿³Ïýû¯Iïï6È¢EÇ9»#FŒ -[¶’nݺIªT©Þy/O’ €» bân+Êù„dÙ>|¸ 6T>|dú,Y"•|ß§””Ð×ÀÈáÃ7Œ‚2þ1y£e…"D/Íš5—îÝ»KÚ´iÓ¯%   —%@ÅÄe—Ž d6l˜üúëpyüø‰BÙ2iŒBR¤HŠ÷Òñã·¤ZPfÏ9"¯ßü#áǓƛH=$cFïî`ïÕo&   '$@ÅÄ …Cr>W®\Qw­!G2Rž>}fX¡|:£(¼q!§NÝ‘ïûn–W¯ßHذa¤Aƒ†Ò³gOÉ’%‹óÁáˆH€H€H€H P1 ˆlÂ} \¼xQ~úé'Í´5Zž?!atªfÿ®wIÉ“'‰C'~îÜ=éûÃFùcÚAyñòµQPj×®#_~ù¥äȑá}³q   iTLBš8ûs ¨Ò>xð`?~œ¼xñR† #Uªd’>ß–T¥ dk\¼x_~è·I&OÙ/Ï_¼ŠÔ¨QÓ((yòäq ž$ øG€Š‰„xÞ£œ:uJ $“&M”W¯^K8u£ª^-‹ôQ I–, B•Å•+¥ÿ€M2qÒ>yúì•K•*UŒ‚R°`ÁP;'   ÷%@Åä} ò~· pìØ1-Š8P¦N"¯5®#|¸°R»VVé­’ â9Õ¯_$?Ü,ãÆï‘ÇO^š±UªTIzõê%EŠqª±r0$@$@$@$PTLJŠ×¹%ÇË€dÆŒéòF3aEVê×Ë!ß|]BSõÆqê9ߺõD Þ,£Çì–‡^˜±~ôÑGòí·ßJÑ¢Ezì ø$@ÅÄ'~öû÷ï7 ɬY3EK‡HÄšš·QNùZ #¦LË¥ܽûTÿ´EFý¶Kî?xnÆ^¢D ùî»ï¤T©R.5–H€H€HÀs P1ñܵ÷È™ïÚµË($óæÍ3ó)¼|Þ$—|Õ«¸$KÓ¥™Ü¿ÿL† Ý&#Fî”;ª¬@ .,ßÿ½”+WÎ¥çÆÁ“ ¸?*&î¿Æœ¡ضm›ôïß_/^lxDAš7Ë#_ö,&‰Gw+F>—_†oןrë¶WÈ  JÅŠÝj®œ €û bâ>kÉ™øB`ëÖ­Æb°|ùrs6z´ˆÒªe^ù¢GQI š/w¸Ï¡Ç_ëÉÐaÛäúÇfbH/ܧOùôÓO5í0ª²PH€H€H€HÀ9P1qŽuà(‚™ÀÆ¥wïÞ²~ýzÓrÌ‘¤m›üÒ­ka‰/j0÷æÜÍ=}úÒÄŸ ºU®\}d‹àSµjU-ÜÖ¹'ÀÑ‘ x*&±Ìž3É5kÖ˜ ÷æÍ›Í¤ãÄŽ,íÚ®] Kl}ïÉòüù+3v·üôóV¹xéA‘%KëF.\8OÆÃ¹“ „2*&¡¼ì>x¬X±Âl°·oßnŒ7Šü¯CAéԱČ)x:q“V^hõø ÷šTÃçÎß7³Ê!ƒI3\§N >¼›Ì”Ó   p%TL\iµ8VoþÑ<¿K–,11»wï6ç&ˆj”‘í Jôè½]ÏÞ ¼zõF~Ÿ¼OÚ,§Nß5'Ó¤I#ß|ó4hÐ@"Dˆàý~"    bâ@¸lÚ1 ,X°À($0$ÑÌZ]:Ò8’5*7Ô!J÷Sÿ8`”cÇo›[S¦L)_}õ•4nÜX"E¢Å)0}ÚŒ&mšØ¦ÉçMrk G/ÑüùG¥¿ZPví¾jºJ ¾ôèñ…´nÝZcxÜ«0¥£Y²}   w bòn>< ^¼x!S¦L‘~ýúɹsçÌ2fˆ+=¿(& äÔ¬Q¬»Ò˲xñq£ lÛ~Ùt7néÖ­»´k×N³žÅ éá°?   7$@ÅÄ ÕU§ôìÙ3™4i’ôïß_.]ºd¦‘5K|£Ô¯—C ²Ryh¯íò姤o¿²e«×úÄŽKºté*íÛ·—8qâ„öðØ? €  bâ‹ç.Cúô©Œ?^  W¯z¹ åÌ‘P¾ìYLj×Ê&aÂP!q¶µ^¿þœôþn½lÜtÁ -fÌÒ±c'ýé(ñâÅs¶ár<$@$@$@.@€Š‰ ,’»ññãÇ2fÌ8p Ü¸qÃL3ožÄÒK’jÕ²P!q…ß¼ù‚*(dÍÚ³f´Ñ¢E•þ';w–„ ºÀ 8D   g!@ÅÄYVƒÆñðáC5j”üôÓ`¹uË«nFÁIå«/‹KåÊ™<ˆ„ûLuûöKÒçû ²|…W’‚(Q"KÛ¶í¤k×®’$I÷™(gB$@$@$à0TL†– û$pïÞ=1b„ ò³Ü½{Ïœ.Z$…±|üqŸ—ó³ سçªQPþúë„ü£ã9’´jÕZå»IòäÉ]pF2 @H bR¤=¸Ÿ;wîÈðáÃeذ¡rÿþC¢XÑòí×%äÃÓy0÷úׂ²`Á1£ DŒAš7o¡©†{HªT©Üw✠@ P1 2:Þè›7oª22L•’_äÑ£ÇæòÒ¥RËw½KJñâÜœúÇÏÎÑ òß©‹×œ¹GånŒ!¼4iò¹|ñÅ’.•RwXcÎH€H€‚‹“à"Évl®]»¦îZCdäÈòäÉSsü£ÓJŸoKJáÂ)l×ñç8~ü–ôýa£ü9ó°¼~ó„ V6l$_~ù¥d̘Ñs@p¦$@$@$@~ bâ'ž,Ë—/k@ûO2zôoòìÙss{%B’?ÒÀ6ÇëÝÀéÓwä‡~›dÚôƒòòÕS›¦^½úFAÉš5«ΘS"  (*&%Åëü$pá     @ÅÄ9¸¦xìØ1éß¿¿LŸ>M^¿~#á5€¹^ÝìòõWÅ%C†xÁÕ Ûñ`7n<–Aƒ7Ëè1»åñ“—†DÅŠ‚R´hQ&é“ €û bâþküÞ3Ñ.ÈngÅŠSráâù¼In ¯ÙØœUžkqÆñöšTÃ/$sæÌ&‹WíÚµ%\¸pÎ:tŽ‹H€H€Hà_TL<諀ؑï¾ûNvìØaf?^TUF>0JIŒ‘‚ÜlZ·ùK¦N;hkSk0Šf6R¤pr™4á3ɘÑ=jŸÌ{D^¼|#uëd·Í×]ÞTútº,]vJÝï)Ñ¢Etúi½|ùZ«Èï3ÅÏž»gÆ›>}zùú믥~ýúª\…wú9p€$@$@$੨˜¸ùÊÿ£y~-Z$ßÿ½Z.ö˜Ù&LUºt.,íÛöÍ&‚“  ‡ß”|y“È€~e489©D׸•“'ïè†ño£°Àz²kG ‰©îc®.2ý*÷ï?—׺¹úTÞ¿«)&Ö^½z#S¦î7 ÊÉSwÌá4iÒH¯^½¤Q£F1¢ó+YÖ\øJ$@$@žBÀy}3–´é‡ë†ïµ;ÒNÒ¦õ½*ü.;)Uf²dÉ_vnoa†óçÌCrQcàZ%Æ+ûR&uûª¬Êdñâãrìøm“5ì”*8cÇí–2¥ÓHIeâ¤}W±‚“ɼùÇdÍš32v̧VsòâÅk™9ë°` OŸ¾”âÅSIÅ é%~ü¨¶k®]{$Sÿ8 ¥J¦’lÙÊôeûö˪ÈEbESJY͵çÔUhöœ#2pÐfAvïoKJî\‰Œ2fkÌîÍÅ‹÷åÏ™‡MœM©R©íΈ¬ZuZöí¿®›éÜO]ì,¹pᾌ»[ç{K--{ö„¦¦ ”=ŸrðàuãvuèÐ É”)ž”Ö>ŠêxíÅ/v”obo1ٷ\uF-_·%Ož$R§v6I‘âíB›·o?‘5kÏÊêÕg$Цœ.V,¥-’B’&á­ XóÀc­^‹Ô¿3Ä•reÓÊG¥óv]p|x£~„³f–~ý7Åm&MšTzöì)Í›7×q¾Í38úežG› ¸;üî¡bâª¡Ðæ«W¯dÆŒ¦ɉ'ÌR§Še,#xrQ«¶;Z-:.ŸU›)U>Ë$óçÖTw¥ËN–õοËpãÆcI”ôg©©ÊÀ¬?k˜6k×™#³T!˜9£ºÔ©;Wð§1|XÝÔ¦‘¬9~“šæxíº³²k÷U‰'²Ü¾ÙÃÜE¢–Þ‹€|d Ãþឺ`%M]/¬+yÕõ ²sçe)Xx‚ü¯}AÙô÷Á†IîÞóª<þ¥šì߯¬ÙxcãŽøHÄaµîK>þKEóÙç¯ ÎI©²S¤›f=<èCo§Û¶["¿Ù-‡´1–%œÜ¸ñ¼”¯ø‡™;X{`c–bòÏ®]8{N™HÀÝ „ÑÚaG)&tårñoÐË—/eâĉºIÍd|ç¡”¤SKŸ1ŸÈ‰cíµIþQJ€ÑòåOŸ.nˆPmÑr±Z‚ŠÈæŸ{ <õÛN¥eÎÌ‚³%uëÏ5¦N®"wo÷;·zÈê q1°àëm—B9€RÒ¶u~ÃvÿÞÖrõrW)¯Ö’ÁªA!u„`ÓX½zVÙ»»•,˜[ËX¯®_¿.]»vUå(• 8P>ôJ;ìˆþÙ& €ß¨˜øÍƩϼxñBÆŽ«O¥3H³fÍôióóTý÷‰ŸÉñ£í¤y³¼Þ\¢Bb2gÏÞ5ݤRKMHâX~PNЍ›½+žºÃ‚ (\± sÔ²Mݱ>þ8ƒ4¨ŸÓæû]V]‡à‚õP•¸xÙKŠä1eÞœZ’$‰— Rî܉¥’ÞÿR­ÇÕ•Ì‘ËÇÑ£7¥&Q¢è¶®àæ4p@YoVŠn=V™óC.o)|ˆ+²LRÕ(Rpoƒ…Â^übgýûú–¸"Y¬/° Aöð²:àý=µ(wîE½¹ña}àÎ…4¾°ú@ª»$*u–5 07¸óÁÎÑòÙg™eǶæ²dQ]÷tëÖmãÚ¥_¿~šÐྣ‡ÀöIàÿìœTÕÇÝÝ ÒÝÝ©4JwJw§„´ ‚Ò-ŠŠH§t#ÒÒ!ÝÿÿùÝå­o‡ÙegÙÙÝýÏgwfÞ»ï¾{¿wîy§H€H€HÀFÀ}¶›ðmàxüø±Ì˜1CFŽ)/^4gÕ ò>½‹©Ï6-(|þ͉­L£†9œ‚EŒ‰c|ËFu‹‚À% h»ÔxâQ°ù¶1Ž19ˆÝ€ÀíñîoCÚ»ïŠTª²PÚªe¡l™4ÆØø[‚xŽ#G¯ ¾˜³ãÜŠM)+W’“'oj]ÿ\¿|cgõëøZ²D*‡ÒiV5|ÓNŸöRFqîn¿­kdÚÁÄ åîsP÷í»üê¸y‘|y½Üæ`E¹¡ñ%5µ¶ \éræLj~¼ZÍo(«øAœÏµ.mÙzÁ¤=z”têÔY:vì¨ œÇKÍCî]=z$4H˜0¡*¢iýœÌ‰'ô;|GÞyçI‘ÂëïÒÏ x’H€H T bB–óáÇÆB2jÔ(¹|Ùk“—3GéÛ§¸Ô®•ÙX‚{*VÌÙ3^õ#üÏÖ­çåZ6ÊèfÛ¯Â}ŽOúí}fÏžÄþÑû}v' ÃI ؆4nºÌ»ã›[·}*, ¼mÕ¯q9öëŸÏÎúCœNë6+͆±.××VKR=Ã*…å$^ÂQæ½³_·n=òqØ7v>Ù>¤Ð v¬^Q¯Yb`5C°þŒ™ûäÆM¯{"hßÄ8éz[RµjFùrüû&qÀˆ/¶ ~âÆ‰b .¦dÉÔVÓ {E9ü hðÐM²á}Õº?ãÆ•öí;HçÎÕ2÷Ÿrd Á7‚%·P¡BfqãÆ•ãÇ›˜ߦ%píÚµ&½yÿþý}kÆã$@$@¡”_Øû÷ïË”)Sd̘1êwïåþ‚ú }ÕBâiÔ3½²&ìØéeÉñ í¥Kw¥dé9ºA‰¡ñ]|kfŽ#+ämƒ¬,KÒè/ÊIªTqMŸŽ¿PßÅ.–‹‘ý˜»Þ[ÕÊíóDPù)¯åšXà·õgñ$ýl+OÊÖÍͼ-dùµVLw P÷MÒkÆ«·K!ó«dËW`šQ4ÔÏ.¥K§ÖL[ Ô½+‘ tGF/(3–th_P>û4¿ZªÎȯšÁk媓&Ë VB)³²°YíƒêJÑïú³mÛUP6šLdÇ—/¿œ Ÿ~ú™‰GAÀ<Å5°„@¹[¸p¡k²5 @˜!@ÅÄC—úîÝ»2yòd;vŒÜ¼éU .GýÔBR¥J5\…—±mûEóÔÙ·§Þ³çó€Ô°–XJR Û#Y–«]@_±A^³ö´yrw.» è›P{,‡ý|`½ÿoŽ^qö~-ˇuìêÕûêþtE²gO¬.-±åÓ¶ùÍσO%Áé²s×%9¢õb2h¶.,µ>Ìì­¨XýìÝ{Ù’;K/lµ ¬×iÓ÷ÊMÇx¤æµWlvCí Kƒ£ ÑMgEãÀÿ€Ê‹/ßx)ê¶@¬4µÖk×þebV¬Ïx½téžênØ%FŒÈb¹ª!%/XKà6µ}û{SãQ¢ÔlAp¼,>.ÀÄ»@¹Ì.;Õ‚vôØ sÈJ{þÅèmR¦ÜÜׂÜQ|‚¹yŠ &ΪõåÏ]­LáÐÇËøñã5¦'ºxµ×ú;>¹{ʸ=m–2òé§Ÿj !Ÿ®…ž6VއH€H xP1 î¯ÝõæÍ›Ÿj($ÔÐÇë5˜xóÆfòþûé^»Æ`œ-´Hà z.\t¦4n²TÆOØ!_NÜ)õêÿ(Uª-2E'UI+pÿç^„§Ó–šñ øA ™G„€~à\1q‡jýÉ cÑÙ¼ùœtì´FÆßa Â5. ’9s"A| ¹± ÷MP8E/ÒöÓU&SXß~¿Ký†?ùȲ…ësj,I6uš5{¿Œ·Ý(MPÚPÌEs¨%÷…Lúòˆ^GkºÌ_pФD^ôÝa)Vb–I› ÷µ R¥R›Ûôê³^PÏ… Ü å*Ìù„ ÀVŸ† ² …¡æÉ† gŒÅgùòãÆZ‚vu>ÊŠç/û©ŽØ÷‰‰ëzöô™ZO¾ÒïqZµ ´Ñ@ÿ³5^OÌĉ5.*Ž&L8mê-¹:><´AÌOÕªUM}©R¥Lb‚3gθÚÛ“ x(ºróÂ\¿~]ݵƪÛÖWZOëv ì_Ò¤X æáèöÓ¾­jRøöê½^æ-8d~¬Žrk¦©YZhÙ—ì‚*âp™™8i§QNps<©Î“ï[{Ó¿_¼¨¶QD¦ÍØ'øDÒìWŸj°õ ¥lUh­™¼jÚ\dtBAÁ‚½žú;(b'>¨´@¦j€8~P”±KçÂ=z$ Š%pûZ´ –4hô“±xX)q¾€V¸Ÿ7§¦·Ûî·nMCiÒl™4jò_pµÐ,_ZG*VLouëÖ×Ís—¸YêªW³ö÷æ^(^¹pþ‡ÆšVWÓj5ËÞÝ­äcU<ºf­2åçy ‡^Fj;¸Ûy7ð€7HðÃâä¨&ø|Ø&­(T¾ùæÍ–7]k 5‘Þ½{«²2$%ÎäÉ“›:1PâàW¿~}?òŸºk×.©S§Ž·ò9rd“"}ãÆ&K!þ EL @È&ÀÊïÁ´~W®\1íS¦|­Oµ½Ü*~N¨{S¡Wî,Á4´@»-\±NŸ¾¥õ8n¨ËQT—H"ñ㿞éÊ~C;„ 2N!fÅ Z··yÛ÷—/ß3VdÂBJÞ”)ß¾î ‚ÖaÝA<ˆcŠagãE[ŒV¿˜ÀE –’¿5Óœ›P÷c¶ªKÛû~ªñ`‡¶I5±¬PV\‹½»ß#^æÄ‰fnv«by>ÁðÖ¸À±2—5µ0”˜ši.Q¢ M;ý¶<0_((‹¾;"Ïu½0· WL> ËräÈM«ío„¸¹˜1cJ‰%dË–-úक़lÚ´ÉÇwùƒ>x-+×íÛ·…¯PN d Êþõ×_¦¸,R§‡^¶nÝê,8™[›¬üœ«À{“ ¸‹€»+¿S1q×ÊùÒï¥K—)¿ýöyüø‰Ùl"˜ I>  „LP64oþAS„Ju:u¥oß¾þ¶ „Ì™û>jGÅ$V¬Xú â˜*×¹Ô¥ó©L›6MZ¶léÝ3Ť{÷îæ!NÙ²eeݺuF ñ¾@ßtèÐA&Mš¤Jm9pà€ýT°¼§b,ØyS "ànÅ„1&A´çÎÓT£Ÿš€YøZ?Q¥äÚ™dߟ­åçeu©”Ñ:ð6$à.(p9cz59u¢|Ò*D„;žf¡Êž=›|ôÑG±iv×Ü]é7sæÌÒ«W/sIÏž=½Ó ûÖÜä H5 ˈ£4lØÐB!G+¥ºc~& ^ÿW>dŒ;ÄŒÆZµj%éÓ§3õHž?{®½YäÐ6òã¿kb&Æ’ 8%€:9S§T‘Ó§ÚK»OóK”È5ÑÁÉ;—Öª!þù§ÓëÂÒÁ>}ú¨«f&¹uë–© ãÛÜáòzïÞ=ãîU®\9§Í`)‰1¢9‡Êñ  K€Š‰›ÖîäɓҬY3õ…Î Ó§O——/^HC-:wôp[ùNƒ°³fMì¦;³[ O ðŽÖô™4±¢üýW{éÔ¡ D‹I–/_®ÖÑ|šö»ŠìرÆ,cˆ%ŠI·§ùóçk‘ÍõNÇ8H¢D‰4B§m¢Fªqb^Yý.^¼è´ ’ „ TLyà? ׂ,Y2ËìÙ³Q²\š6Î)Ç~&óæÖTEÅ«–E ß–Ý‘ x(dÉbÉøqï˙Ӥ{×ÂSkѬZµJ .¬iÀß7à:t· AðV|IÛ¶m5æî±¯÷{¡v|“ÈùóçÍidþ¢ „\TLií:d2ÆdË–U,X`jo´j‘Ûø›£¶=;Q Ý’Ý „ ‰ÇQ_”—³w>½ŠIìXQL0wñâÅ¥L™2òÇ„ ÙÎP‘$iÒ¤rêÔ)>|øk¦Oï•êužîß¿ÿÚyؽ{·XŠË{ï½ç´ ’ „ TLÞröíÛ'~ø¡ÆŠäï¿ÿÞ«.F›|ò×Évòí7U%uê¸oy^N$š Ðæ0­ÕrîLG­WTBâjjì 6HéÒ¥Jʯ¿þš¦ëç\âÆ+_~ù¥ióÅ_˜â‹ö ’$I" $0‡ðï«3Ù¾}»9œ&MM×ÂY# !¨˜p¡Pð«Zµj’'OYºt©DQ:¶/ þäUÍ£6F‡ÆËH€B¸q£šÂžPP†.% ´Æê{T¨PÁÔãX½zu˜ÅÛñã?–Ê•+›ôÁVL êYbeðúüóÏÙ í‚Ï“'O6‡Pï,k—½=ß“ x6Ö1qq}¶mÛ&C‡•5kÖ˜+chÅî6Ÿä•îÝŠH’$1]ì-ô4¿s籬Y먚.mü O|ãÆCùF+©CPñ¼fÍÌÁwéÒcrT JBð½ÀÓñÐ&·n=’u¿ž– é¨bžÌ#¦wíÚù}ÃÉ“;™dÈàõ„Ý#æÂ P\tò×»eÜøíríúCseÞ¼yeÀ€RµjU…]è6X›:«câl@ˆAxË]kðàÁfÞhûäÉÍh–ÛÔ?E‘ÑëèÑ£²lÙ2AÀ{‘"EŒ+\¤H‘œuÿÚ1¸Þ"#,T­Ì°ŽÉk¸y€H pw¯‹¡˜»¦‚ ÅC† ñÎ+fdùLSvíRX& }›Oß8~=e·üóÏ=Sݼ_ßÞÍΟÿWê5øÉ|nÓ:o+&ׯ?~6˜û#³»ß8|·øˆ|¿ä¨G­3»bòãGåé³—R¯®Wõmï{‹7§Oß2kT¸ž¢˜;vÝŒiüØ !V1‰©ÿ¦ôìQTÚ·+ S¿Ù#£Çl3©…«W¯®®¢9¥ÿþÆmÔÚø¾ÅzÜ¥ï¾û®yàƒZ%Ž‚l\;wî”6mÚÈÂ… u ì‚Àù &ˆ•\›={vSèuTjÖ¬)µk×6Õåíý†ô÷öì‘«›ãLR$N,ÙÓ¥“¸Zð2¬ÉõÛ·e™ÆsåÐø¥‚ÙïßE¿8ÎÓd/^¾ô«‰9W¹X1I/ÞÛ¹Ú`åæÍrãÎ} /Ô:ù&Yª®¥GµÔ¤M­Z’@].ý’³ÿü#¼JƒžG€­_âj{¿úz›s4{ê®#GLïèßDÅ¢E_ëî¡&åXðË/æxxÍ"ØL=e–oÜèŸ×:å? P1ñE HœØQ¤ƒºluêXHâ«ëEX“iÓ÷ÊþW5p7²ØrðL½ú¬—ÿ}¨Š ¬@õUÑÉ—ܱêÑÕ Û¥saù´m~ÁßÛ£¶šâŒØ<êЯ_?ûS`?éwÇ\0^»[–_÷èÔ©“àÇ™ b<’Š eÿþýréÒ%Í|˜ÅT{'޳KÞx¬G¦ÍÔ©SMêbT£¯¥›0X§bÇŽýÆë=½Á皦þwUNü’¬š,àešYãs"«µjãÞ½RXkÉdJ: ]¸õgãæ¸õ°aÒM³g•bÒR½,ž>þƹn1Ã-ŠÉ -RúçñãYëýøG1ùníZùþ·ßÌxk•-ûFÅd·Z/›©•2T ¼I1qµ½éØ ¿î=|h¾ è:¡*_W×­{íßÕßô¡¾/"ú=o¡µ¨\åc.æ/ bâ *¸jA!±+ãÇ‹¦ÊHA£”ÄÑ`UŠg€ûεË]Í ¢h¼OpÉ´o«ÊW“*šÛ‡VÅÎÌÿ0¸‡™ûFÑXO>Q 䌙ûŒ‚רzõêÉ Aƒ¤oß¾R¿~}‰!B˜aòÎ;ï~KìÊ üÀU·|ùòÆŠâW¯À»ûÕ¡ƒ¤µ1ƒ¢xùÆ ùMã$ñä·ˆÖÛ:´x±¼£nr®Êvu‰k®ÿONVË“'*&ž>>Wy³}à(ª–èZ#éÒõëÆ¢´G¬V´Õ[·zß´®ÆÿQÜO øvpîŸ[€î°bÅ óRPpE@IDATBêÓá. I»ÏòK,MïRdÖìý²ú—SrâÄMÁØÂ…ÞѺ ïH•ÊÌgÌãàÁ«f³ƒ÷￟V7ʉ·F+±äÇcæ}G-K-$ØÁ òøñséÖ}>±Lâã:sR9rM† Ý${÷]Ôqx¿BZéÕ³¨n¢|æ[€ ØÐÏ7©抦 ½¥IbKöl‰‹\Þ¼>ŸÈüb‹:tÍŒ fÏ9 ?þtLjTÏhæÕ½‡W6#̳ÝgdïÞË2vœWÆk\ޝP6óç÷Ê䃱àéôQu Â<³gOb¸•/÷ž™'®…Ë_æÌ= …ó.šÛŒU^’'ÿÏU¿s½pá_éÿÊ-­FõL’&M\™¿à‰ëˆ-’T(ÿžtëZDŸêúþ}<{öŽü .e·o?6k· \9“è†+­7‚åËk\ÆY9õ×-I£ÙãÐou½ß›ñàŽu*Vì]Ó.c.Þ5–Ä]».™ïÞéÓ· ·jgñÎN÷—Þké²ãæ3Ž;Êó{ãF9¼c¶._¾'àºoßu‡øŸäÔï\ëVy¼Ï;öaÿ|æÌmó=Ž-¢ÆüäÓ á^ß¿§O_ÈâïÈž=ÿÈ£GÏ4Ö •Tü GºeFŽAÚj¦¿–š~FŒÜ¢×'¤qãÆ‚X ((5ò®~nŸ?ß¿™€]9Aë§OŸš:3¨5“XÝ;Bº«W™üù%oæ×cîÚi¬N­îÝå'uÛ’ÒT-E÷8ú× gò®¦Îö™¦VÙ¯TÙ„ÄÖCߘÂ-ö£råd¢E¦É/Cì›b‚5«­m!a…™l0ü¢b¢Ðñ A”xR†ô¿$Zs ›CÆ †D IR·ÞYüƒWœƒ5îºYœ0q§×äº5 MŒ6| ¿;lš$H͇‚qøÈuïs5kdlìÆŽÿ¯R5âð¹v­Ì>®CgTá)Qj¶ÜÒM1ä/Ý nÞr^¶ë†}åÏõÌ1üúU¨ëÔûQnkà¼%ÿê}qol|¿šTIZµÌc2›è_ûÛd/‚5bÄ^O2Ê–I£îJ½ÇûâÅK£˜\Ô²5?ïNÞ|X3“QL L|Piü{÷‰w‹“ª(a#„“$5”Ãßßö“çcÒ¿_ oÅÄ•¹B™˜3ï ÇÝ{Oå7ó= Œ¶k¹qÓ9Ù°¾‰šÿ _ÿßM| ®Ã{m·Zcb)&3Uþã³FñìÜe­ù~=þÒÜãKýÞmÞØT¬xsÍ(]°à^Øt[bÆÕz…¹¶s§Bæð h¯[ÿG ž:UÝ4¾0k2áË2oNM©T)½uùk¯ˆ…)]v®Ü¼ùPV,¯ç­”@iû¸îÙ­J \3±G˜6cŸ$OÓ´ó”¸Ç EŠA•“<Ò´I.Y k7|Äf9yê´4oÞÜXw{÷î-M›6U¦!ëß+ÇyÇgGåÄõk׌›×7ê Ú\½0Çò Åd¿*ºêÃfMÛ¼úë/Y­YâŸ>-S¥’ÒùòIQuw³~÷ˆ]€üªî.=’ÏÔÅð¦Æ2|§.1ùÕÕ®”^c—_wìýê×ß\ýô­¸…ê&}üìY鮊ö_.È·?ý$P¦reÈ ó43])M‘5mZY¨^ ;–Q£J1‡µI´÷oïÛøìm0Ö•:GÄãÄSJê½jjÚngò&ήqv ,ýë†9M3~ªuPëüð󡈫íÓùlQë%\ñ`‰ƒË]u½‹£kdɤï¾3îiøü­>œÁ˜ñ}<¡™úÀ¸Kƒf=­ö¾½~¬ÖQK1ud`ëÖÞM+Ó W¯šÏ%4k²„ Í{¿ølRׯ)K–âW"êáï ¶ºÃYñ+ès„Î’Wãq0/þ†ªû%$wÆŒÒM6A®¨u³«ÆÐA°þ½t­B»ü÷Íí3u2¿—Œ¶D¿@HC‰,-lNzt/jžÈâétHlÆ,¥$­n8›7Ëe6™StÓyZ7ÕP¶m» ÝÒ¸4µ¬YË­^'ï—ü%‚ŒQNÒ§‹ÿZ?P@âÆ‰"ÕqP-ë?cÚ¬Z}JƒX/JÁ‚ïȃO¥I³åÞJ ÖñÔ–dׂâÓ®ýjÒ³]lä+¥$º*L‘"ù´ÂXmß}7Ž4kòŸÇ¡llÕù[bYÁ J‰îQ¥n¬RªTjÁ“ÿ³öËóÿ“qvÅÄÖ=Þf®°.dΔP>V>×5S‚¢Õh ›6Ÿ—­[ÏfÖ}ì¯åÔÊóäQ?IŸq’‰1¹v¥›÷éñ:(%MçÔZ;U”_c5h¥JÁ‚E‡ÍùAKy·÷žhæŒjFY…ѱÓ™ªë9W­JJl³;~V>ùäóoºQQAãÿðM9Ax²]½nþû¯”K7Bv™ ×šdà¹ÆE`SŠM¾þ³#Ÿ}ô‘|Ù­›q„±õÀs” (1°ºü­ñ?¸1ŽŠÉÒ?þ0›6y[ŠÉ|U>Ï*Y2©«Ê5î“õk4výt¨[W6ëCl„¬ûÞ=ùR7¥½uƒ6¼];û°}¼÷m|V£sW®H~U†ÎhÌ îèw°þ hÕÊjf^ýÃÃÇoùáé³gR_7ÛP.ì²^=9ðƒ øòqã$q|¯ÿ×cP1X8¬oÙì—ëÿÿ“*Ç…>,Að÷Êÿ—I“¼-kP ¬“þúo‰¥˜€ég“ôüÅ sùÑ3gd•®;”%gâjû3úÝy_×ô”*¨–`ÖªB‹ïÎ/ú}@â”Iƒàáò\µpZåßÉušJ¼œ*ß~I!M„¥ì¼Þ®\H€xˆon\¾ñbxp¶ 㬟–q]ºH'u½M©÷ú^kUá6ýû±ÌÊ7ý[Š þƬãC4~',H˜TLð%Fv—aÐt옗»RÊwbW£Ísûù´ØÓ¿;v^Ò”©IÍ0§L®, x¹)áitßþÌñúùwU1A¡H¸tÍž£§ª˜àI7>;<Ä?~ô3oW»Ö(&S¿ùS._¹o.G µ=f!Q¢2DÝ» œ|>l“ÌœQÝÇmà΃”ÀßW[`™M\¬%W®¤>®½té®.:Ó»Ù§j « .f/µ¿‹zÜråL*3¦W3màÚö½Zž`­8|øš9æ ëFo3WÔ¶øã÷&êRÃt÷ìÙ ótNœ¼é«bbÝÛñ)i¡ÔA±üfª—R‚6PÂgϪ!¿¨r€ó}z÷aÍpìÇ·Ï#†—õ¶ á;«#“C¯øáºÆ sÅd‰ºÙd4ƒ4}¥LNør§\¿ñP† *å­”à|Ñ¢ïÊ`=Ö¥Û:Av´ýKâ°·œ8qÃ(%P×þÒPSɦô>W1üT­’A7ö9¼—-ûžQœºvÿÕlöí–:ïFön‘õëeWE:›ò<ª+›•ói§ÿ±£ŠzwuÓAF¯_m>=lø9Ô¹ªOHQ›Ê.ø?Ã’ÐàêõLŽú0î+-Z D9Ûvº³tÖMo MѼxÄIªO‰ÿ½_Úèwj²º!aÓ6H7ï§M3 KCÍ7Q¿kŸªÒò6ÒJÂP­D É O¼±I…LTe¡¢¦ƒ¾¤›6<±†‚RF7hcæÏ—Áúj·ØïïÛøüÁf¼V™2fŽù5Ià }b]B’á3gJ»ÁÓnˆy˜ÆþøÕKg™õVªdžˆ£‹Éº.–R‚lQÈŒE-¡xR ¬}tÓ=]ÙÛåŽ*m°6@¢ëº:²ÁfJ Ö›q(3W5möuÝŒ·S…cû«kM¿á­ÓرÞJ ,Y©U™\¡J6ÕŽâj{\ßqÌo¥æø.À½ °Áÿ¸W/Ùªëã(Pn›T©"4»ßbµÖÔ¶ø‹1kÖËk¬~Ÿ P¬Õ‚­ *zʼnZŠIÄIü(55‘î ë[cÜ¿`õƒÒ‹¿)$›(_¨Veh³*g/_6™ó’hÙ?_íEq(I°”àoVBK*9Éf M¯aR1AA®öíÛ›uÄ÷qcß—fMsh#æi_<Æ6ÛþùÙ¸!.®–<Ó ¿;±!öš.ˆá°¬8ØCv¨åÄ’Ê•Ó[oÍkeuÏbÛ’3þyÍPäõäÄÙyÇcxú^¹ê"spO›4Ñë¸CÁ- ·l^¿!k×öv¡‚BPy›¹æÏŸÜ[)Áý÷bÉ]›Û™uìM¯G^7V‰Òj²»Qá:<‰ÇÚÁ]–·Ì™ÿ3ß¿©_ë<â4ìk\áàgIÅŠé%q¢è²üçúDö¥¹ïÇÏdÅÊ“’_3}Yë ˤ¤ÍÚaõQð•Â}DÝþì‚ù!NèÊÕò­*^v¥íàA| ,ov±ú\¿þo.„ö6žøßß?Êjæ´L-lŸµÿEc¤þXNSëzâøCò˜]½àVçiÒ\ãbD‹æ=,¤­=¢›Ûê2“XSÓbo|ïöÊmd|×®fc„ áB3oȳi›©OaQð¯;’÷ßð¦zÉ’2òÕÿËhj)&°žü4z´>Lñ²Âº‹Ë|µâ@™È¦î-lÌç«û¶ÕoÆÔ©†Eš‘ OÜá> l£çÍs:\¸üÀUÖ’Ï5;—%ë§L1Š>CAIªÊ3ÖOáûê÷ îT–`m³h†µïGŽ”,º v¦õmÑB†jŠmÈEUÂséS|lœ¡ì@Y…Á™LÒ Ô ÒXSÏÑïä®*­Ù4^Éry2õ—«íáú%×-K›YAUXaÙÙvð œÔµâjÌɲ&Àj—â•buô|¬±#PL ¿¨;Ì˲µв¢øÖß0U˜ ”@©ÿ*Ó ªˆTRw8È,u±ƒb‚sPL P<ªéwŠ $•~/a%Â|qkIªÊ R1‡‰&é8ÇÂ… kuJ¹ æB¸ŸÀU˜±YñÍgß±OþÜ·ßïòí´?åÆÍGÞÃŒbóå÷>è¦7¹ÕRa—(útÞ¸å@aI uá²KÞ¼É #Öíð´ÒñXkój¿Î·÷P*j}ôƒ‰}A›bESÊBÍ*e_klh›µXn‚¡U§3ËO$ݬ?ÓÍóÛˆ«sµß+•º£ÙÅQ™°ŸóÏû¿Tá€8Ûìã8ÜÆ ˜ # ЉcAI¬žìCQ¶ êª|9i—À… V«•«NÊUN,k Ú‚,; þ÷¯ÕGfµ˜AÎj¼‹]+bâFô \ÖDo—9©.^ÆM—™Wg¿¬Ø(gç<ù؆ ge¼Z™,Kd´hQM:ݰ”µ+0Ö‘SêÓWÿ6èÅ‹7Y¼ÍËÓÖŽ'ºÙµä…>5Ç“sb °!²‚ãsqDëWàÉî{ºáÅx» ‹b2Nž?èY¸©ÅÀ™ÀMÈR¬óˆ!€Àm, Š 6¿Žý¦×}ä´Æ6@1q¨jùpüÿ ÷´þNO)Û[wïâ`<öMxL!A¬¬xº¿GŸ²Û\3ü³Ï¼•*|v”vd ÒK—–ic 9væŒ¯Š â\,AÀ¸%±Uim¯Š Üîìâj{X‚,Ò8ãÕ˜p Ê©Uonl](ª–Àí îl`x÷Áë°Ÿ¯x‡õ ¸ÁÍÖ$ëïÄ?Ù¸;‰¦ 4”4küX§(ZøƒVLÖûºíûõצ=(/ø»Ã¸ëi̬^˜/,$Hó ÕÐÙ÷Æœ e¿Â¤b‚jÊøcÕ4yìøy`;hðßzt;f )ë>jôV®Y{ 5….üЋ©ë ܯ6öéž`ÿ’ëߎq|šìãä>Ø7ü¾5µ¥ÄÓùwԕΒ˗寧±ÑµÓjãJ*^ÄN`³ ØÏËêšk«/XÊ”›+W5Û”‘FÊ ›e(L+/ð®änµwõõmæêlî®ÞßY{Ä8dè‚Ø-^ÎÚùv,‚ZGü#ˆ‘€b$°†”gÇB/4¾VÇp dpƒ8Ž1bpßÂÆéšEîMC‡”ö’õÝýE9I•*®÷qûXsB’@¹0èïØ©xñâÊgŸµ“®úÄ;î £…¤yÅX/ëÆé˜ý£”¤Õ+jË k²wyªü¨ÖKñ°Æˆ\V>T¬v£FI½÷ßl0¡p@°IЧVßäVÝq_ßÖg‚¸ G±Ü¬ì¾üŽmÞôibű_wðx Š_Ö&$°Jˆ£W7,(&(1ŽbYzãsì1nCv±+>5Ùƒo‚XK|À.éÕ…ÊQ\meиÙáÇ™@ÑvXìYWAð£æÎ51&¨³b¹q¡ö‹o ¬{à{ ¥«’U÷Ä:o½âAÚÂòkå5-ö K”8(AP– éŠ Ü× Vܸ0×0©˜`â í ùÝQ5xº,Ö¼ŸðÊ„€îýKßm<Ý I²l¹fWQÁ“~dÀJ÷*8}Üøí¯M•¦-AŒ…]ºjÿèïñdþ·õgL¿PJ–Lí}õºÑ²Äz2n}võu nج W)4u/¡€mdä‚RA\ɬ™Õ½O#³×ÛJPÍÕ?ã´’ü¦îJÃÔÎ.°L!9$mZŸ ìíã}îÜÉ$[ÖD‚ïë#ËÉjMŒ€´ÏöµIŸ>¾Ie¼IݯàþeË= ì‚4ÊÙ41ú„«ܺ` EZkH†ô dÍÚÓÆÍ1]1T$†pTvìý{Òûµkÿ2 É®Ý^† âë¿iM‘ÂÐP 0¨Y[JÉy‡ÍX1&(ôXM3JA!ɩփ*Ø#K\—¼ —|¶6Ìp+B–,ßÄÙFÔ·¶öãð‡X<íç|{jåîÄ ¼IÜÍÃÙýí.CöͺÕV"Kìm­c~¥÷½¯cX¬Ä¸Æî‚•äU0½Õ—ýÕ¾Ù‡ XbÅíXŸñêj{{M5>Ã7KXr”Àx€WG]ä ˜@ÈÁRþ`ͰÍñÞøŒï 2¢!1â ºjâߊ `ô wDX,­8¸ÑA9ìVÅÄÊŒ†ï*\ÀŠ„YÅÄZàHªYC9i¥>³³4Pj”>=:¥Zj£&ËdÀÀ?)_au@æ¢ ¨1Á?ü¨=AÐ÷·Óöš÷øeý§€¬U–à©ë:©È˜1yzm) ÖyÇWÄŠ\½z?À›8¤=E&,xøLW÷dßBêY+FÀº_ëVy­·.¿"PߊUÁÅÚ0Y¿ùË(n×®ÿgîµ”5¸œMúj— ]/Dq:ÿpй:Ü«ƒ–û>bÓž,iL?º,ø®XAõ8·}ûE“âáAQDJ`^¿ Ò ?Q ÒàÚ¥Bù´ZES–ê]1Á÷Þ øÛ´‹î!À¢6fty“ù­E«ŸeǶÆ Šz:u]ÏÒ¡½Ïä C†n4© Ф ™TqöD1s_uJªuw¯Öuài}GõaÆO }"Jq€oJ z¦§„áB«VhJÇŒ`áU:G<¹…à3O»nmÌÍAýµWÝJ®i°t<Û¦Ô:g½Z.Iv÷1ëœe°>{úk`ðpuŽöB•ÈÚ„'æd·•È-ɬñ$®,L¿k ºÝ iƒ-±â´ŽY¯`õ‡ Ý®8ÀíÉQ\mo¹ç¡Ÿ÷´ ¨}s+ÉíWîmΔ1Ç{ä3¬˜?”A¤ûEBH]µ&úG0~(p„k›]1;§ÖüÛ òÊ!Ί ¬(VF1(%x·²„Í{•i .”öTÉþOHnãžG!¾,PNŽë”Lú%E¡·æ-–t&É´é{MýOŸâ' Hq›=çTÉ_pš¤L5Aãi¼ÒBâ!pŸB]6àïkÔi'J¯>ëMp²9áð+Ë«€h(ISŒ“úšz5 ‚'ب«µ¢Lùy%Ú0É•÷[xŽãˆƒ@šÜ€ 6ŸvéÙ{½TÒxûϼù¥f C€6ÊRÁÂÓ%iò±Ò¹ë:ë‚ãp}²oð]ásÅ âD3ˬ!ȾõùÐÒ¦P!Rã¢0âþýWLá>¤Ë)¼ TkaP’4DPß9X´`Í*_þ=·E !XD°&½õ; ËèfÍ—k±É¿5h‹pÚ;€âSªd*Ù£J2|A¬RÅtÆZ×ú“²qãYÙ¼ùœIkÆ?\×Ï´õÍÄ á½e-±Ü¹¶¿ |Kn\àCÅÄú–¼zÅÓž¦úGpDÿpçë_6Õ`Ï_¸+­Û¬”´é'™,W¨í©2ftÝh§0ÃCªUT?ÿ@3%mÞØÌ{È‹´¨¢•ejéK‘Âïxÿ Æ¢½V¹<¨”w{û›vzî]­În RúTP½}¬>Õ¶ó-£þ@1†µ¿4p_Ðûùv¬& î1Ë5fîìÞ1€7K\å\sm­5A@˪†Cš7Ë-ßL©¬Ú;R»ÎÉï[iª›}¸-nÜÐÔÏ;5ÿÀxM®Êê­@i aǸ.ú#UrñâïÊHuÉ*\l¦”­0Ï(2mT©]´ Ö‡”ÙP¶úØ`2á‚Å‹jKs͇@ùReçJ‰Òsu~>m›O–ýTÇpxcÇAÔI~Ð,qÙrL‘šª8Ô {ïèS½ñãÇk¦³¿MJàh¶ŒKA4¬PsG¥®Z ´0ÒÿªµPÆ“ãGÞv!¬§°^¹X¡¿I=z˜ÿêhM¤aÅSòEšª·˜*%P`PpÏë‰=2u!KŠ÷ac§åÈ:ÔVÓ /ÑX¾š u9°ù Jq6>Wïï Wûö­=²“!òÖI¤渪 E0OÞÇuî¬ÿfºæÉôÁ°†Ð,W 4ý­U¸}Ô³Žé…qÜÄ` Í-}nÖÌôÑR7áÎ\À\mï XC´^AÓžÇÔ¤ù´Ðà1}øAJjÔq—ÀË.PbùÓ ÝG3¤!iÄŽuë§YÕ 1õßidQ³$‘Ƙسl!+œå2f)(V[{p¿u,4¿†Ó'qÖ~04Ï3Àsž4wûý¶ I“ÄК'ÅLJÑèÑ]² ð`üy!Æ|àÀU㪃:&È8ö&=Rº¢NǛڣÿ[·™Â}hëJ º³qÀq\Óó¢(^Ê”q4]l"}úë冿¬½»ŽÝPEÖ¸7eÏžøJQ@8Ç\1N¬mб}ý#…2¾'pɃ‹Wz¿°‚ÃÝÅ8 ýZã?xðªù®åÔZ3oúŽúç>—/ß3Š;6ÿ¨yƒïž§¬s‹¿?"ƒ‡l4EA1®Tê*€Ú$°ì†¶'÷ÁÁÝRJ.©Ï~1ÍêX®Z–¿ûÿœÔvÊy–ÕpÛÙ£OŸƒß­q æC]µ¶áIõ)UƬظ 58Pþѧ¾– -6ÌUmOñ·‰zdBðîUíEÿŽªÒü¶µâ@ŒBrpI OìkêY+H»ŽÖ¦@1¿ËªüÀÅ<ÕÇ…GkQ@» kQﯾ’¥Z÷ÂþdÞÞïn5¾õ‹yŸc¿þåáxës}:ëä…ºN9ºÈYí쯰.ÕSeÎÊFeK“<¹,Ôlö´¾4—eµ¸¹~½ÄwHO"» Ì4È(ŒV ;êØ 5t+Mâ`‰µ&ø|LÝš,÷2¸T5ÒÚ)VZ_œGMa,¯c€ Õï]?ý qµ=®" …ÉŠGÂ1dµúüÓO{—õ÷õ¡ú´ ,^ÑÏö þdê~uEë® îãî¦MèÂß«Œårˆ´Ëv·7«ßø v êÔ ÞŒ}c¸¤¦vŒÍê§™¹f‚¸šYº6­º×ă”ùsÚŸ'I¸W5ñ÷å¡bâÕŸôÉ”=¯þÃI”0ºV‰/"m>É'Vl‚ ݱ) ø KæÂE‡e¨Z¸N¿ªý—"($¨‘˜8ÊÛ€RÒ_7Xùµ>A`gÕ²6NÁ­˜¼-%ÔÓ@V [£†܆|{B€jÄ”X­{cs —X.7ËV› xõm|®ÜÛ®ôû¦¶p¡C*ÝÇš¾™Ë°Qµ¾coºÖ¯óˆ¥¸§– ¸„9ºæùuÎ]Òì]pƒ²j¯ãÛu®¶‡ ª¿#ëæ‹ï L†Á÷;fŽ!e¾“Љod‚ñø -’e»úÌBâk–§îÝ |âƒ"h8§Î[“ ¸,iˆw‚Brî¼W´ú—S’N¿[ùò%wùV¨8ŽJ”õ×:ÞûøÒë×ÿ-‡_3J<¾G¨omö|¹Ìcãû?á˪”ì’ûžšq•.]ÚüÝ×*Æ”ÐOÀú®2ø=ô¯5gHa‘€»“ðajpÍi@kÅÛcÇŽKýúõMeÐi3öIÆÌ“¥y‹årâÄUvƒkŒ®ÜÕÄgÍÞoª¶[×={GZ·Y)K~d×Ú·oŸ&»X¥ p>O&Ç@$@$@!Š@Ä5ÚP:ØT©RÉ´iÓL¯áÇˬY3eñGå{ý©Y3“ôéULòæu=FÀ¸¨µråIs‹_û[úèc£dÊ”)(‡Â{y8¿{øqx$@oE€Áïo…/d^œ,Y2™8q¢œ9sVºté"1cÆŸWœ”BEgJ¥* ~pÏlㆦ2G7ä‰ðäQ?VdtBö+Ä `Cyüȧ’X7ÕÃGl1J€5þz ~”½{/˼95äöÍrëFùmmCÁ†´T™9‚ÌR¾I¼xѤª*lÿj›ßÔš`—5kþ2Ç¡0DŒèÜcm:w]'… ¿#ÿèf ÆP÷ã¬2yÊúù&Ó%¬7X ,Ùh{ÿÇg­Ã²ÿ¹uû±T®”Þû˜³7ãÆï0JIíZ™ ÜËÆ¦’&u\2t£àÉ<– (%=4µôåK]äжòïí^R³F&Ùàªü´ô¸³î}=æjÿ\¾/°LíÛÓZïßUîÞé%mZç5Y³z÷Yïë}p•µÅZ¦×Œiv¥ÖôõпD°(%P™9ë×2C­$М5j¤Êá Y´h‘P)ñó+À“$@$@$àç;6—º`cwHœ8±Œ;VŸ<Ÿ“ž={JìØ±äÝL.6SÊ”›+›7ÿ·Qv×Ú/Ü’æÏ­iÜ~ÐGÆŒ ¥l™4ê‹ÿÂ;û\­v켤ÁÂé¥a/w+íe °6úpûB`³ý§`¦-jZø%U€%fùÏ'¼-1>“ÿ’_kwdÍšØéåˆ9rôºd͒ȸ]Ùï kMQḠq"Øø#޵4ƒW&ÈÏIÉ’©Òð÷™;ƒò¯9Å$‘ºeùU7䨱¥+wî¤j ‹l®³~}Ú6¿ü{«§·kX‡öå×µYÅ0/<Á_­ñ#¾Øb.qÕÓÕþÀ¶pá”ÖðÌ+”“"ªA‚5Å™¼íÚÎw@æÌ;(õêd•áÃÊ:»…[ŽÁåï£:?HÎÜßÈ"Un#EŽ$Ÿ|ò‰~gÿV×Ê’&M·Ü—’ ˆøÜ9’ˆGˆ/ž :Tºwï.ãÇ—I“&Êó(«1… ¦Ä&¼ÿ¾g<ÉM¡ñŽb¹TÁ 9©~ûÄZø&p‹òKÐ'Æ¿œ´Ë‘W¨VŸ²Ÿ”º‰÷ËZbY œÄK8Ê×[XŠH¥ŠéL =¬T°lÕëÚ¶É'9s$‘ÁC7E¥®n¢·l9/µ>Ìì§ÛÑéWnZ9²'yí¾¦Ž­±6– öeùòãòõÔ=šDଉÁ¹Ô©âXM\zuµ¿”)ß–"(aH—ëLÞvmúÃdãš>­š³îýØîÝ—Lý…D‹Uš7o!}úôÑØ#ÏJ<è“g‡$@$@$à!¨˜xÈB¸2ŒØ±c› Û®]»šX” &—¨*/”¼×óÊ•3¸Òe ·µ”¿:¶2Vþ¢œ¤J×iS<±“ 0¸sA1W „‡Ââ›X÷†U¥»Æoø&éÓÇ7§¼ãL6Ÿ×ÌTÿ3 ²wá| u­Bœ ”(DpMóK,ÅÌ?ÖŽ¦Í–É‚E‡¥@þäòÅÈr&à=‹Zy®¶HñYÞî]~ÝÏ~ÎÕþ|+lx挗Õ+I’˜öî½ß[|²¶p7Ãü°–ö˜ïÎñÍöíd˜Æ=!ë$FŒèÒªUkéÕ«—$Iòºâˆ·fW$@$@$@¨˜8 IcÆŒižèvêÔI&Ož,ãÆ“?5¼JõïÌ“|XPªWÏèòæ5¨dP×®5kO›lZŽé~ô¾ ™¨|ÙøÚǘ;w2É–5‘ài76ïpuª¡óF@µo’!ƒ—[6ØÎ,ÈGæ-«dŸÊ‘=±‰3yöì…ÉRBÓ #Î$¥ÆÕ CY…òïùv[s<Ã+eî`Ž‚ûÑc·Ië–yLZc(%Y2'”­››ûâGÑAW™¯\íï’f)CLL¬XÿYqpßÍj‚ û˜3y›µ…â7Þ‡’Ö—¾ÝÏÕc[·ž—!šÜ`ݯ^®‚±bÅ”6mÚJ=4CZBW»c{   @ À“@€Ü]Dݸw9sƸx¥H‘B¼*5koŠ5"C–õ”ÞcÅF2 ò¾>‡ ÄQ™ª¢f C\‡¤q£œr]±;wYkìQ¨Ï/ÂkÉ›OÍí+A‰R³ÁñvËÚ‘u 5O`-±Áéˆ3™¿à‰½°”ë¼ãkuÿBrÐDm»ô¸Á|Åꦎ ¥À>¸c!â«‹i¨¿Úߤ¯vY]˜×ãÇo˜º+eJ§6)–}œ|õámÖ6Z´ˆ&ëÒ¶À²…T×ÅJÎ6JIœ8±r£F¢RØÀÙ ¸@€`yzÓ¨Q£ ¬'mÛ¶5G­ÞçMŒj!@ÊÕ:š 5C2gòz²Äí—딳ûÁå ±Øè#SƒúÙMlÆ’ 6Èc¨¡iqý#¸ék0ø–òo°Z ÏI𿏰¦`®SïG9¢¬Z&É vï?`ƒ 4‡’]0Ö‘šM 1"È’e ²Ap¼e‹ÜÖa__<>bxYiÐh©©QÒ·Oqãz6kŽ1TW1¤ƦÊdÊwbËÎ]—̘ªVÉ G5ø)­tˆ‘A¼Œeòõ¦zÖ Wû‹#’ Þãúõ‡&«ÚeUÚFµW,åëíÞfmªR+ï·RB³¡!-u`ȯ¿ž–AC6ŠeiŠ/®´oßÁ¤ãŽÇyM`Ü—}„]V®ÿ°K€3' × ÎÕõûò 7ˆ%Š´k×N3&ý%S§NÕ é{fÃ0Šæái;žº¿­ «\¦`ÕhÞòg?kŽøv¯Å‹jKó¦¹dÚŒ}Rªì\“ilŠzª—ýTLJ¥À·>pÅ‘9 ‚Ú%þQ¾9lÝš†ÆêШÉ2É[`šÔoø“¦Ä Ë—Ödü² ²SÅãåÒd·˜À‚L\¿ÒÛûª_/»I§ŒX ¬Kí:KŒ Z}‹AMâ4¾[XË›ü|øf)Xd†´j½B³Å0õL (­\uJº÷ôJ{lïßÙû€ô‡¹¡Ðäì9û¥jï¤u›•¦ÆÌªõ«™³ûXÇkm­þòúË/§¤@¡éR¡â£”$L˜À$8wî¼ ô*6 ©~‘½îÑ¢ùž !¤Î™ã&  ÐD€ŠIhZMÎååË—²xñb£ :tÈ\•*&ž¿Fn!\^~üñG>|¸ìÛ·ÏÜ'‰ºõìQTZ·ÊãÃeÈmƒ`Ç$@pƒûnña£œzU>uêÔ&åo‹-4 õ.o¼/#  BTL‚¶'ßjùòå2lØ0eØm†™Pãºw+lª›;Ö°ðäypl¡ŸêÈ 5óçÃ6 â‚ éÒ¥“ž={JÓ¦M5a]Cÿ·€3$ ¨˜„ÆU}‹9­^½Ú((Û¶m3½Ä×z]:’vŸå× FQߢg^JoGI l¸Vj?§ÙÌ 3f”Þ½{KÆ 5 ?ÂÛÝ€W“ +*&ÁŠßso¾nÝ:“VuË–-fqbG‘N J‡ö%~|{îÊ…¾‘=~ü\fÌÜ'#FnT¢‡dÍšÕ($õêÕÓ´ÊÌzúV3" ‹¨˜„ÅUwaΛ6m’AƒɆ ÌU±bFV夀*)…´J¶WíºcSð7‡ŸÉ´é{eä[äÊÕ溜9sšJí}ô‘„ Îß}±! €ç bâùkä#„k”_õ*æ#z$ùìÓüÆÍ+I’˜1F"t¸ÿ©LýfŒ½M®ßxh&•7o^éÛ·¯Ô¨Qƒ IèXf΂H€H€^#@Åä5$<àÇCAA, $ZÔˆÒæ“¼Ò­k‘·*°è×=y.l¸{÷‰Lþz·Œ·]nÞzd&]¨P!£T©R%l@à,I€H€H  b†ÿm¦Žôƒ–Ÿ^®ÉE¢DŽ ­Zæ‘Ý‹iÕö·™¯õ ·o?’I_í’ñvÊ›A+VÌ($|ðg ’£   p;*&nGºo€C† ‘Ÿ~úQPy;r¤Ò¼Y.S %u긡{òœÝ[¸yó¡|9q§þì’»÷ž˜¾J–,)ýû÷—²e˾Uß¼˜H€H€H ä bòÖÌ#G|ìØ1£ üðÃ÷‚JÜ#„—&s˜jòéÒÅ÷È1sPÁCàÚµjÙ!_MÞ-÷<5ƒ€"AXJ($@$@$@a““°¹în›õ©S§LšáE‹Êóç/$BøpÒ ~véÝ«˜dÊ”Ðm÷eÇžOàòå{&~dÊÔ?åá£gfÀpÕ‚BR°`AÏŸGH$@$@$àVTLÜŠ7ìvþ÷ß›BóæÍ•gÏžKxMíZçã,Ò·Oq­A‘8ì‚ ƒ3¿xñ®Œ³M¾¶W?y®YµDªT©*dÛ¢ P1á÷À­Ο?/Ç—Ù³gÉ“'O•'j×V¥w1É™3©[ïÍ΃—À¹swLÊßé3öÉÓg/ŒBR³æ‡2`À]ûœÁ;8ÞH€H€HÀãP1ñ¸% ºté’Œ1Bf̘.{:רžQ”â’/_òÐ9é0:«¿ÿ¾mŠ"Ξs@ž=©•Ùé2ú‘QHP±B$@$@$@ÎP1qF…ÇÜFàÊ•+òÅ_È·ß~#zÕª¨\)½ôS¯B…ÞqÛ}Ù±û œ’:5×Î3W£"  Ï%@ÅÄs×&LìÎ;2~üx™4i¢Ü¾}Ç̽h‘”2° )_žOÝ=é˰wïe¦’¥KËÿt`Q¢D–æÍ[HïÞ½%eÊ”ž4TŽ…H€H€H  b‚+, õîÝ»2qâD™0a¼Ü¼yËL¹`FA©X1}X@à±sܵë’|>|³¬XyÒŒ1Z´¨ÒªUkéÙ³§$Oλp „TLBÈB…µaÞ¿_&Ožlܼ®]»f¦Ÿ7O2Я„T­šASÏ"ñ0%(lÛvA†Û$kÖž6·‹#º´iÓVºwï.I’$ Š!ð$@$@$@a€“0°È!yŠ>”©S§Ê˜1cäòåËf*9²'6 ʇf¦‚âÆÅÝ´éœQH~[ÆÜ%V¬˜òÙgí¤k×®’0aB7Þ™]“ „ETLÂ⪇À9?~üXS k2y]¼xÑÌ Kæ„Ò_-(”ÕÔÊÓòÈ!¯_ÿ· ù|“lÚ|ÞŒ/nÜ8Ò®]{éÒ¥‹Ä‹Ï#ÇÌA‘ „|TLBþ†©<}úT‹4Î0µPÎ;gæž1Cé×·¸Ô«›ÍÔÎS@q²k×þ%ƒ‡n’í;¼¿øñãIÇŽô§£Ä‰'ïÄ®H€H€H€HàuTL^gÂ#!€À³gÏdΜ9¦šüßÿmFœö½xÒW 56l]"EŠfáC\©Áì°ìÞóP¢D ¥sç.j%i'±bÅòŒAr$@$@$@¡ž“P¿Ä¡{‚ÏŸ?— ÈðáÃåäI¯lQ©Ső޽ŠIÓ&¹$rd*(ξÿÓBˆË–7 ÉþWM²wëÖMÚ¶m+1bÄpv‘ €ÛP1qZv”^¾|)‹-2 ÊÑ£GÍ­ßIKzõ,&-šç–¨Q#åp<ö^/_þO–üxT†ª…äð‘ëfœHõ‹ [Ÿ|ò‰D‹ÍcÇΑ „nTLB÷ú†¹ÙAAY²d‰ 6L˯ÁÝ¡ÓRðìÙ ™3÷€ ±EΜ½cæ.]:éÓ§4jÔH"F¤k[˜ùàDI€H€H „ bBŠÃ|{kÖ¬1”­[·šÎâÅ*:”í J\}äÉ“ç2sÖ~ùÅ9á®™R¦L™ŒBR¿~}M§Ìd¡a9  ¨˜„ÆUåœü$°~ýz2dˆlÚ´É´‹;Š*'TI)$ñã‡ÌàïGžÉôûä‹Q[åÒ?÷̼²eË&}ûö•?þX P†÷“ O’ @p bÜ+ÀûÍ›7ËàÁƒŠ $¦ÆÀ½ n^‰…Œt¹>“©ßì‘Ñc¶É•«Ì–I_í’ñvÊ­Û̘ .l,$•*Uòˆ1r$@$@$@$TLBׄjû÷ï7 ʲeKE ¤K­"¨…òî»q‚eî·U ùrâNýÙ%wT9/^ÜXHÊ—/,câMI€H€H€H 0 P1 Lšì+T8|ø°I3¼dÉ‚Šé‘"†—¦MrJï^Å$MšxA2×ê¦5~Âc%¹§î[Ò¥KË€¤T©Ræ3‘ @h @Å$4¬"çàVÇ7i†¿ûn‘© 1BxiØ »ôé]LÒ§Oà–{_»ö@ÆŒÝ&_OÙ#4ÀR¡B£-ZÔ-÷d§$@$@$@$œ¨˜'}Þ;D8uê” >\æÏŸ'ÏŸ¿áÃIÝ:Y¥_ß’)SÂ@™ËåË÷dÔèmòÍ·Ê£ÇÏMŸ•+W6 Iåì„H€H€H€<‘O\ŽÉ£ œ9sÆ((sæÌ–gÏžKxMÉûQíÌFAÉ–-q€Æ~ñâ]SƒµHk‘Ddù­V­ºQHòäÉ >y „$TLBÒjq¬Eàüùó2räH™9s†{a’Zµj› ö9rø«6"   Ð@€ŠIhXEÎ!X \ºtIF%Ó¦}+ye̪Z%ƒ*(Å%þNÇvúô->b‹Ì›Pž=©•ÙÃI:uMÚß,Y²8½†I€H€H€H 4 bšW—s RW®\‘1cÆÈ”)_ËÇ^5F>x?­ èWB NiÆrâÄ 6|³,\tX^h¦¯Hß ACéÛ·¯dÈ!HÇË›‘ €' bâI«Á±„ ׯ_—qãÆÉäÉ_ɽ{÷ÍœJ•L#qãD“ŸW“—Z%R¤ˆÒ¸qéÝ»·¤M›6TÌ›“    ·!@ÅämèñZðƒÀ­[·düøñ2iÒD‰+¶DŒQ._þGš6mf’T©Rùq5O‘ @Ø"@Å$l­7g îܹ#¨=òèÑ#ùùçŸ%[¶lÁ0 Þ’H€H€H€<›@xÏGG!Ÿ@ܸq%zôèf"É“'ùâ H€H€H€HÀ ¨˜¸*»$    p×x±5 €P1qTvI$@$@$@$@$à*&®ñbk     7 bâ¨ì’H€H€H€H€HÀ5TL\ãÅÖ$@$@$@$@$@n @ÅÄ PÙ% €k¨˜¸Æ‹­I€H€H€H€H€Ü@€Š‰ ²K     ×P1q[“ ¸7@e—$@$@$@$@$@® bâ/¶&    p*&n€Ê.I€H€H€H€H€\#@ÅÄ5^lM$@$@$@$@$àÝÐ'»$ !ðçŸÊóçÏ%V¬X’%K–7ÞóòåËrþüyÓ.sæÌ;vì7^Ã$@$@$@$@AC€“ áÌ»¸À‚ ¤P¡B’+W.9~ü¸Ÿwxñâ…T¬XÑ´oܸ±D‰ÅÏö}ÌØ?ù䣔äÉ“ÇXAì?~¼ùˆ xG¥'š6mjίZµÊô(äA‘ÆíÛ·›÷Ö/ôqåÊcuI™2¥u˜¯$@$@$@$@.`åw`±©ç€•dñâÅrôèQ‰!‚L›6ͼÚG~ìØ1óññãDzvíZû)óîXQ£FœGáÆÂ… KÑ¢Eq$°ˆ P¾xñâR­Z5)]º´äÎÛ¸‹½Ö ø›£bÃ@ÁïP2dˆdÊ”I`1±Ëµk×¼3hÙÝ»ìmìï[‰=ºüñÇÒºuk£ÌlÚ´IðI’$‰ÔªUKzôèa\½ÌAþ"   p‰—p±qH `ÅX¯ö1ÊbI¿~ýäM®W™3g¶š›˜”5kÖ˜ÀwdøBÜ ;^½zÕ¤þùçŸé‰S¤Há} ß øÿqb«PB A‚&]/Ò'J”ÈX@\¤&Æܾ œT©RÅÔQY´h‘tëÖÍÕ.ÙžH€H€H€Â<¿‡ù¯@Ø-[63i·;“ýû÷›BŒÈÀeÕ,iÛ¶­ÄW¾úê+—À*S®\9iÙ²¥9~îÜ9çùH€H€H€HÀ¨˜ø[…"ýû÷7³Ù¸q£V~w$ÂÏ$@$@$@$à“Ÿ<ø‰ÜB€Š‰[°²S   PD€Áï¡h19    ©¨˜„Ô•ã¸I€H€H€H€H  bŠ“S!    J€ŠIH]9Ž›H€H€H€H€B*&¡h19    ©¨˜„Ô•ã¸I€H€H€H€H  bŠ“S!    J€ŠIH]9Ž›H€H€H€H€B*&¡h19•à#ðÏ?ÿÈ‹/Þj.\x«ëy1 „dTLBòêqìC f̘ңG+'cÇŽ•;wîxÌ|8   jTL‚š8ï* ÄŽ[žLS    @#À:&†’‘€sVM“7n°v‰sDž! + †•ysž$@$@¾p«Åĺíÿž°Þò•H€H qHž=§N$@$ঠö‹Ï‘  *&A‚™7!    ð‹¿èð @ b$˜y     ¿P1ñ‹Ï‘  *&A‚™7!    ð‹¿èð ÀÿÛ; 8«ª-Œ¯¡»aè‡îîB”ð !*OQQ )ÁiP‚t#H#Ý)=tç[ßžÙ—;—¹SÌܹÌ|ë÷cî¹'öÞçî{îï¬Ø$@$@!@aâÌì„H€H€H€H€H 4Y`1´ðXøÜ»w_¦Mß)‰Å—fÍ ‡ï"œµdÉ!¹pá†4oþ´z{´‹ùó÷ËÑc—¥SÇâ’ wéìß~Û%;w3ƒ~õ•R’>}²Go íY¶ì°ìÛÞÜQËŠHªT‰åòå[2õ×f_üé¤zõܾã?þØ+ç®Küøñ¤}»¢a^}†Ù O   ˆr>Ô¢¼Õ }||ÌW~|Â7nÜ‘d)ûKútIåÜ™w¿Á(j¡F­±²yó)¹xþ½(jÑ}33fì”ÛwîKëVÏ8NjØx’Ìýs¿\½ô¾$OžÈ±ß6Z¶š.¿ª˜„íÚþº<õToV´¡]û™2qòvÓþþ=]%_¾t²_…J§¾1ûÚµñ“ñãšE¸ÿÒeGÊÆM'%QÂxrëÆ‡a^}†Ù Oˆ4»ò{4þ§'Òcã…$@$@1K€“˜åÏÞ#@àýÞ‹åÒ¥[Á„I.ç©$@$@$@$@^L€ÂÄ‹‡ödùScùæëæ&Ò©§‹F$@$@$@$àž…‰{6^äÔ©«2~Â?R½Z.)R$“Lš¼Mþþû_ iJ(•+å”-ó>ÚòÛï»%wî4ò|Ð>ç››®áF‡_”Û_ßæÐÉ“Wd츭&LëÞýR¬¨¯té\ÒqÜùú¶'NÚ&'N\‘jUsIÙ²Ù§lÛvÚ„^mß~F J/54ç ’Ž54;¬cC~Í… 7åæÍ»2hð_R¼˜¯Ô©“/Øe«W• ʾ}R¢DiÕ²ˆäÈ‘:Ø9ø™1àºÚ(+VÁ¦i»qãBföî Oû.7ßýü2ÉûïU6üÖ®=nö ú²ŽdÍšÒlãÚùþ‡ ²õŸÓ&7¦Lé¬Ò¢yaiР€9|úXe¶K•Ê"Ý»U0Û»wŸ“Ï>_a¶K”È,={T4Ûø-ôè¹ÀlÛþÍ7Àtð¿dËÖÓrúôUÉ™3µÔ«›O^}¥´É q¾ìèÑK2ðËÕš/sÖ»Ô©WŠêï"²™vñü‡}µVknÓ•+·¤t©¬ò^¯JfüáÇõëwdÀÀU²jõ19xð‚ ¯+§¿ÏîÝÊKêÔIÂÓÏ!  ˆ&&ÑÖÍ;vIz½¿HÞêZVV®:*[¶œ’4i’È…‹7eø×ëäƒ÷*I¿/jiÒuRù¨ÏRI’$<Û¤$Òzk·nÝ•—»Ì6Ǻ½SÞì^ºô´j3CΜ½.¹s¥–Û·ïÉŒ™»ä«ákeüØfâï8y¶m¸~þ÷Ã%ÒO'Õèëí·Ê9ãú^ï-’»wï›I:Ä œÞx­´ ÿª¾Invœì´aõáGKL~ vcû•.¥‚ “¾Ÿ­/U°$Õ{DjÓä©;äÛïÖËŠeƒMZ#;ô ¯Ç¤)9—uRì,Lfj¢»=öUÙz8]Vé3±9&}XÕ!L ,>ùt™¨ÞsØ6"c~Ù"C‡Ô•wÞ.¯‚*•ü:m‡Ü½÷@þZsÌ!L–j‚¹íg• 1+L Êìþ¾OUw´ÒÆœ9{¥M»™rùÊmÇáý.È’¥‡eô˜-ò÷šÿ8&éVõý'Ê%Mb·¶wßyó{HßG¦M}^š6}Ê ÷gdÚ½¯À5™lD‰íhÝúF´þ9§”R‘š:tAê5˜èHÐǹGTtÍ_pÀw´‘-[ªÐšà1   h$à]eŒ¢ñFcsÓ#¾Y'™3§u“óçzÉæ ]$­ ”ÁCÖÈ;÷$mÚ¤:‰.h&—‹ †bÞ¼ýf?ª¡ªÞB·l=ȇµ«_’CÞÖv»Ëªåµ­ûÒ¡ÓïrîÜõ`m8yO…DIóç 뤵…C¡Ÿn=H… ÙåÄñîrüh7¹ÐKZiõ¦o¿ßàð8·e·kë›y$=çÏ—V2fHf¶G  ‘²çüüA52xJ`¯û×|âƒüŽm×k×n!jE î©ëëe$µVЂíQÏÆg â¢Dµž´VÔß7”ÿt*nC4 UïEd,2í¢?T«Z%§ ‘Þ7SrÓõYý=v}ëÏ0‡ñv·ùQRQ‡úÕ2=\aø‚+ ‘ Ä “˜ce=çÈžJfNA²d *^<³4T¯ÆõLìÙ`úy1¨Ìêt­lålSÔ³ëØ¡˜ùüjøß‚‰ÞÚ—+—ÝìÄ[}úIu-ÛzC¾û~½c¿óB‰àµ€Ø˜2©¹$LøÐ3Ó³×Bsê°!õŒˆÂ„ΠJDÞÔãxdíóÏjJç—Kš0$”•…G†P)k;xœ ¸`×4$håÊ£fû¢z¨Ö¨WV§NÞPÃݾè¿Òœ;ybsã)6´žz¢šb—1xN`õëå3Ÿøƒ=؆'Íg. ½‚­ $kƒŽgöM.%Kf1ÇBúóó¨Írêô5s¿‰ÿýÞJ¾Ñ@¶m}UTS[$^ñ<Žÿ{YJjÈÊ1OÒñvé\Jófü%eŠ@†³ˆÚã´ûßÞUdùÒŽæw³a]g#èÐ?î߆ͅ4ž­[OÉl-; CèÖê•/™°eK:HÙ2ž–¿Ö7!y!]Ï}$@$@$@ÑO€¡\ÑÏ8Ú{@ž&ÍΆü âèŸy&“É]È”1™Ìúßã wñö˜¬!¿9*°5A9È q5Lè`;vœ ve?ßé6Ï„åÔ¤ ã› Ë Ð5(vì<+EžÎ(yó¦Lä­R¥òÇœ}fRÙ’º®ãͯkf`ž}@C”`Q5†vm‹Ê˜±^˜yº† ¼9˜Èãm>,¬u6ìD!gÇŽ_–Q£7›ë0YÇ5·4l,`ÈùøïGKÍöÚ¿K]ý¾cÇI—6‰©L6@ó>Ö­ÿׄÖYOJƒúù5”-Ha˜+ƒÿùÇI¨!ÇrqvíxÄíÙËã©RYº¸ƒ9ü‹´Ks\útåj`ÙÁ?$@$@$@#|6ë±nÙQT@‰«ÙÅ­ß±þrOƒI.&axóo½%hùIÇWoÉÄuÛváÂÌæá#í.ó‰° ´‹Ð$„!wäÅöÅç 1q’6׎ý®çÏßpÝîïÙ²=L,ÇE˜üb‚mï?ªÆP]E`6MbÿWûçÍ? ƒ‰Ì($RhѦϺϷÀXt»¡IÜ]^ýÃl»þÁ¤çÂó1‰\x * €àÉ*ô|0¡F2ÿõwM3þAÉó®mÚïT¨ZƒHt¶‚Ŭó¾úÌ:ýg– M‡d ž•„ú{‚G.²™vFg‹3Ø~Ç|\…ž;;p páGG!ü ÉPŽšF$@$@$3(Lb†{”öŠÐ¥ðX‡‹p.„qá-½ó‚…hçžN~‘ ž80íÀÑô>Mz†¹N±y¯k{Ñâ?HwÍ“¨_/¿d Ê€H€Á3ónÏŠf;¤? ¤ iw¸öY!æîä¨ÚiÓú¤ù;Z¨,Ï ¬™&'K–ÐÝŒXJ™2±)NEs‚ztwÿfÂ÷TW+MP¡a`óH Jl˜Ýzõ˜Ø0.$£#”,4K¬¢ÓÚÕ ¯‡ýîú‰ÔkÖ'§Ï\3a^íÛú™ßMÕ*¹¤AÉŽUí]¯ ë{dÛÅxá¹IŸ>™£‹c*„­ùj›;CQkõð"†d6¬+¤cÜG$@$@$½(L¢—¯WµŽºÏÉ(¿ÏÚ#Ô6oú›>[È$ÇÛBìS¯ ÊÙÚÒµö‰`ù\Þ´ãM6ò$`½?¨"}4yú­·ÿ”)“[˜}ö­6ª"!GÊsPÿlÒU½ÏèäIúÑeQ9„sA˜ÀúiΈÍÙÀþ° !vg{S«©9WH;¢ž(Š3‚Ä ­úša/ŠMäGHŠ l*”Œ†!(¬’·ÎÏÂÊV²BX_Ùò?›„þÂ…3šÜäl@”Àà3úY³?¡y''¹Ùˆl»ðØ r˜sÉk” ¶æ|ovŸý,ää ‚§ÈY^ºtÓ”¢Æ¹´¸H€H€H f„ïU{ÌŒ½FL0‘ÜÞ­û|“ÏбCñ`½à =ÌV™²‘G2'(d©¾9w6眬)IàÔi;åšÏƒà€·‰ó6IÜ^ªRU«ÿ"HL·“q{,¤Ï{÷">•cÀÚx0$“Ãà©U+Ùíå‹P®ÎZ¦ëÅÀƒðE¿•’;ßÉ“„;¶ºê ô7&¾cÛzKÊ'°yA¡Uã²í¡(‚5”FRøÙ³×¤ÏÇKeƒ«T\WæŒî·–"(ÙüQrÙVõ‚Ѝ=N»¨/Âþ† ôZ¡ˆ´ª!äEÙ±•Öߊ,À°~ *ŸÁ+ˆÜœ •Fî… £%0œÕË ¤mÕ3Û?I€H€H€¢…Iô±õÊ–Ûj™U”´;þ“+áúóÚ«¥Gå\?è½ØT:BNJ§—f™Å ‘llß²‡tƒð|ÿ­¿9ôz×¹‚·Ñ°¯‡×7l”"ž0ñã%™¬k‚T®:Æ$áXۜڼÉ?¯‹,brúw÷&´ó]EÅl›Ö;‚Å'a‡ OH]ï*›"¸fœz:²å&}ɇ*`ÈSAå)k3¯²‹ KrÍröz×φ êâ›eˆám)^ê'ñÍ2D† ,û‹ª^Xháƒð0~å*ü,™³1%“% t¶b›ˆŠÅȶ‹¼ˆ…²FIúLƒ¤Ç» ÍØðçã>Õ‚UsÚ@øªÁ ªê6˜()Rõ—Òå~6IóØÿúke rB„ç-ðµ´n;‡h$@$@$@ @aâÈÞÔVG%)*H¹N¦!,PBµŠ®ªO*–ZuÇ!ój—R¦ÄmX÷S£FA>ı " oùÌk§¡J¤}‡ß¥TÙ‘f‘?ä<Ìú­å#ac!õÑEË£Ln_] «ÒGÔ¢b ¶Oä™XOö¹z‘ìy®Ÿ‰'Å Û òp=dÍm]†ü†Mºë"Îeƒ­—çÛ*i؆ÇÀVVÃ÷ÐlÎì6¦¤³=Ç:=Ji²ýºµ/;rˆPÙìÛoü"çØÊUã~iêÈKB<fD,²í¦ÒµV° cv§BÉ5§ë×¼Ô©D˜C€(Ÿ6¥…a…“Q †<+c,nI#  ˆ9>:Q´ó’(… ñyp·O”·Í£—~(5ŒJPXñ¼X±ÌŽ7õÓ3Þ°£ŠÔÁCoç+VÌñˆ8 ­};.LÞ]K$‡vó±Çƒs[³Dn”¾©a]~~¾Ž•á§Íˆ\‹.”~FR9*€A´†dXPsË–S¦˜Ÿ_¦Pˇt½»}Ó.ªl]QÏ’ØÃè<„píÛ ÿÎ J£Ú£«ý¡¥´QNûG>´¨#à“ ¯i,ÿÓuƒeK$@$@%@aâQÜìŒHàI €“ 'é‚’%ÕvQƒ'áž¼eŒ&Þò$8 ð> åò¾g‘ Ä0å˘²Ðÿ¢‘ x†À£ñ žé—½ €×ðwª^浃äÀH€H€H – Ç$–=PÞ <‰(LžÄ§Æ1“ @,#@aË(o‡H€H€H€H€žD&OâSã˜I€H€H€H€H – 0‰e”·C$@$@$@$@O" “'ñ©…cÌóçï—‘?o,&–?C¦LÝ.›6 ëÔGŽG¤ŸG.æ    "@aK #¾Y']^ýCnݺæbíÖmgʘ_¶„y®ë éÇõÚ¨þ>cÆN™}2iÓê)]:«·5Ôñ½ß{±\ºtKZë½ÐH€H€H€H€ž,&OÖóŠ–ÑæÍ›V&Nx.ZÚf£$@$@$@$@$&á¡Cç ïãØ±ËòŸ—JHºtI£øîûõríÚ騡˜d̘ܱê¯;äøñËÒíòŽ}ØX½ú¨,XxPöí %²H«–E$GŽÔŽsΜ¹&3fî¿g2IåÊ9û±ñë´²dÉ!9|ä’äÌ‘Êx#jÔÈìû%¬~ìyΟ×eô˜-R±Bv)[6›Ìüm·,^|P~ú±±ã´mÛNËÜ?÷Ëöíg¤P¡ôR£zn©Téá8¾(Ó¦ï” nÊÍ›weÐ࿤x1_©S'ŸÉÃwÞ.' Æw´yýúùö»õR¨`ziÒ¤Ù?{öÙ½'@ÞíYQöï?/?Ü(5õ^‹Ï,ã'ü#Õ«å’"E2ɤÉÛäï¿ÿ•äÉJeG‹O;ÚÅÆƒ´ß@ngÏ]—‚ÒIíZy¥nÝ|ÁÎ í ¸,Vî‹”¤Iµ}.•*æ¬YS†v™96`à*ٶ팤L™H¾ýÆ_~»Õ<ߦÏ’.K™sV¬8"ßÿ°A¶þsZ$ˆ'eÔ[Ö¢yaiРÀ#í:tAú~¶B6jÒ… 7¤\¹ìR­j.©['¯> Žó?ùt™þÆÎKúôIe@ÿÚæ·ƒß~_eËd“ÿö®"™3§pœo7 5ë{ä Býü|Í3îõnÅ`ÏìØ±KòQŸ¥æ²¦Ï>%yò¤‘ ·éoû€a„ñôìQQR¥Jl›–û÷˜E$I‚ÿ_ ~à·jõ19xð‚<õT)§¿ËîÝÊKêÔIírƒH€H€H ê øè$êAÔ7Ø¢Ùxp·Otu«Û}·×BÕäcý‹H?æ—?»v•§ý¾ŒsÉÒC²aãII—6‰œíeÎüjøZéõÞ"“ÄIù‰W?Ø7^+-ÿª/ñãÇ3“wŒãöÀDÿD ãÉ+]JÉˆá ¤F­±²lù¹zé}‰½[Žà ¾°–­¦Ë¯*p¦Nn.­ZÏ0ýŒÐ>Ê—Ëfø¼Õµ¬¬\uT¶l9%iÒ$‘ ošë>x¯’ôû¢–ÙÆshÒtŠÌ™»OR©0È;™à^U!ù~¯JÒ¿_àyæd7~ÿ}·´xašÞ›(^Ξ½&gÎ^ü¯iô¨&*H‹»¹2pwÝúd¡ šô*f»t.)ý®6YGzt¯ Ÿ}¾B "t¨Á íRWEÜCa»yóI©TeŒÜPÁçj‰Å—ù¶•jÕr›C*’µ*زèä¿hQ_™¿à@°K2êoçCÃnß¾'mÚÍ4¢)؉A_À}Öo­$S¦@ñ áR¬äæh³¦O™ç~åêí`—V­’S–.î ñâþO«ÖÓeê´Áα_úÐö[šßöA€Õk0Qö©(u5ˆö?ç´‘lÙR¹â÷ðIÐ×\ÿé‰àˆx: €·`ò»·<‰ÆÑ ~~³o·­-wÚ^¶ì°Ým&ËçÕc€É–³ýµ…MØ?oÞ~éÖcTPo ú?~´›\è%­^("ß~¿ÁL°q^íÚyåÖ%¾´‚É/¶!J"k»Ì¼­_½¢S°|$úãû¿ÇºÉùs½dó†.’VÊà!käÎ{¦»™ê!€(yýÕÒæœ­›_•“ÿözúvž¢°ìµ7æ¨',™ìÚñ†lÿç59}²§ ®ûø“ea]î8~Q…Ó€ Q’,iõ>Ä“ N˜6 JÒ¤N,[ï¼UΈèð^¨k]ßúÓˆˆüŽÍo'o¾Qƈ¤[*,z¸Äžêø¬(iÔ°€ÌŸÛVFþØÈÑ>ž¼ÖÞî6ß!JàÁ "ÞØ6õÖ½ Â•F$@$@$}‚Ç1D_?l9ªèÛß”)‰³Y¾ü°dÐP„Ð,Ómkûù»„â|þYMéürI{šñ$ ýj­ áqìtÙ¸rå– ürµ™¬Žõ¬¤Ð1À>…–7ßžg¼ðÎX‹L?öZ|£0{Vkãñ±û{ªÇ6lH=GÂiÆkfÞÆ#¬ÏGÕoÇíuûù¬†v!ÉÞ¤ÃrèDxæôá?ñ‚œ0i›ìѰgô­:&°°|*’àÍßÀþµd¼N¸Oé¤ÝzÌA—?C»Î|+j¹à±A¸SâÄaÿO÷žª§ g0¡§ŸÎ¨3iÖ|ªñ¡ÛÉ›Ký ñ‹0(ÿÆ“ÍhP ap°Í›O™O„G5nTÐ<‡Z–†B„ BìÀKd=ædýagâž‹ø}'Îëן0áqŸ±Âž.‹¾(5¬öê+¥%³z¯©PÅX–'ÏÃßÎ7hÙ’–†#GmÆ!Ù³7@ªh¨ÖfõlY+U2‹ÞS^ÃÛ?ü¸ÑºäeÛºõ”Ìþc¯Ù‡Ð­Õ+_2Ûï©—«\…ŸeŽù¯5Çe¯¶mÇiÛæ' @Ô{v5ý°•H@N¼íÁZ#È3AXBgðVùÓÏVhÊ%“/aoke-ûÆ×vŸ?:ó¶ûÀÀɶÝïü¹k×9AˆLíZy¢Äýµ2òbûb:1Žow™ÏÈôãÜÞP;OÄ‘c±cçY)¢jìÇ„ÝÙ*éÛø?æì3EäD¥Ùð8×6‘Ûâš“€œò LJ— S‚gãœzrTR'ÂÅŠe6ÿ\ÛtýޱE Ú›ÝuÁ¤94˜Ð#¬ ¦»Ãmý>¯i„޽y:0„öSa1jtàdâ^xAÀÕZ-ý à;¼Ùs3y@UT Õ¯—Ïü]‰½Î9Ï ^¦š5óŽïÕ\'äç@¨À èoÒy²!WU=ªÇ ž„÷¹ “2e²:D Ú@^ŠµË—o™ÍZÚ§5üoå§‘›Œ¤šæ }úIu‡ØÅ9Ûwœµ§šGË;‘aûcÎ^é^°‚Ùæ   ¨%@aµ<£¼5ÿùa++W1‹:YMÄŠi ?&[*Hf_¥oÒ›?Wø‘7×Ù4DÆÙ0‘D® &¢îì€N²aE&{ö\\ïœ\l÷G¦{->Ãïlx3 ƒ8I›áKçCÁ¶!Ø"c¡Ý¿ó$×¹m$u»Z‚^Û^ãÆ…dø°zÆã„ÜüCÈ<È‹±ù®í8‡‡æÇŸ6Ñp. ðþà9K¤ÂAÔ³ƒ÷ÅÆ‘CÎÖ¹ É Jq.žuOÍIA(áå+·˜5k›È[Ê«ÉçcÇ4}¤`Bjõ®øúOrGâ>ò•`ð´à~¬!/ÄÕ ~ L`(ÚàjÈ“r6ÃÆy‡n—*•Uš4.(ÿ›è AˆÙ-ohH<3ÆÖ3\‘o …ð/$C9j @ô 0‰®QÖª#ÏDcçïÝ{`Âpðæ¾€VzJž,¡ Ï$w®Ô&ìÅß%¿ƒ°çˆ ÈN²#’œ™~B“}jQ¨’åÎÀ!2vTól`¹GšVo½YNÞÐB‹2IèxËŽ‰.€ümFKG°ÚG£tÙ‘æ¹µmã'5jäÖª^éÕë‘Ñ$Õ£º„ex͹š˜¦L™Ø$í#A‰ðîÌ ©}{Þ4 W¢rÖ_3^\wðÐE3¦½»»šâ¶-„U«-~ýÎ:ˆS°f…°ýŽOx ¬9Ÿk÷9·m÷…ô‰äyTy$㣺 ºü;­J±Ù÷Ó¦˜½y1ð~…deÕSC#  ˆ&ÑÃ5ÊZE ¢~™Lž &|¾Z¡ù0„» Ï$‡–ñ¯“Nä D…¡¼- ÞWÃÛóACþ’.š·ï@t™ í÷ $OÐ&-]‹Dò´iõb8ÉŠ‰[·îiøÐÃ#'ŠCÉä»wïÏÊÑâß ­†5zÌfùOçÙò˸­¡ ”ÎE8Õ˜ÑÏK¼ÇXáix\CèÂÂ÷¦&¾;{޹hÂÄ 2ñÞ”â…U(Ÿ]ÞÖ$y”c^ºô¼õÎ<ٯဨL¶V½(4ÿÄ*¿¡r™­¾…ýKU@[Cçð;ˆ>H¦BÛÚ"Ö G2TB0@=j1ß|íoî ‚e’Qí†2Ô&(m ¡ƒÎ¢íÒ¥›¦5އ$’ìuü$  x<q(ׯŽfHhÇD“(xK¬!o­±–’£Ãš¤ÛëÂúD©W¬Y‚„à“'¯;ý£—šœçuP‚E_p/ð– ”iÍšcÁZEÞEÕê¿’ã]=5÷îÝv®Ô‚Ÿ5€è°ƒþ’šµÇ™Üçö1±‡¡ph†Üª}9Ûß—šû‹HމsØ®”ÔŽP.TÃóENÆýVJî|#$OþÒ¯ÿJsŠ ¼òÚóa_˜ #Çk” *ù‹CJæïÐi–)ç‹òÎHrGH,k–ºJ6õT$3‚ûPæ¹{ù&'cé?`•ó\*ü¶#c“¦lwŒ}¢(€Attêø°Ü²;r³,óÁ*¼QÑ ¿“OH…J£ —B…¿1ë¸Df,¼†H€H€H l&a3Šñ3g‚ø~„¼ ´ÇšMîÅ~œU†ŠOXoá.µëŽ7 âís£&“M9VTŒBEªè¶¯‡×7‰ú-[ÏPñõÀK2Y'›•«Ž1oØQZÖÙ Îhª‘xމ<ÌNj_ÖI8šñkžàÍ9¼LQmíÚúnX‡žx|fÍÚm¼%è«åóEBí²zð|¿÷bSôålû¨Äs@I]&Í׮ݵw{PÙÀñqºè`¶ÃÌú7-\˜B“ÒQ †ü+„±#Îõ+ö½dÈ4Ȭ÷‚s°e‰Á ÀŠû®£ë©dÓ„ù>^†Saý, C9^¬õûQÓ3f,iÒ t” Æú9C×uT73'FàJ6ÛGÜñ¥Y’+ÏWR¸È·R´øŽV¬HP²kø Ÿ¦®®g’"U)]îgÙµ;P¢ðCt rÇÀ¸A$@$@q…ÉðÐ+TÈa¨1T;QÄ6<¨Äs-lv>ÆŸ6­ýd‚–åE.FÛö¿I‹–Óe®®ûЦÕ32~lÓÇh9ü—b…ñóÚ™|…ö~—Rš{ùP ã¹®RŽð2$E÷ÕÇjÈ …°N” NðoñçÌnþDàÌTx|¨ûuëþ•šuÆKÞ_KÓæ¿*d¹®ïÚô^*!:3圛µøUj© ˜š4á95²‰y«ß¤ÙTSÌõÚð|‡è\¼°½Y_² %ì”ȟؤk³ØE‘“2eRsóÌ1ÁGù^T¯Bx 93¨çlðbM›ú¼YWÆîÇ:*XCÄ®<ý¨·yã+º*üü [’á^+—wr”-¶íDäe'*7xÿ`G]–ÝZÖ"?“®óí×  —â8rz¦éb›ö|„ÔÁP­ "‹OÒH€H€H€¢W~>¶±¢e„³ìØqÆ„û`íúâɛà áÛ¶6ak™}“/ˆÍq’®á ÁäaGÖ®j¥)´uPß`“ëíñ¨þDˆÔÄ£—ŒˆÔµ?Âk¨J¶gÏ9xkuBî’áÝ1°ç†õ‰°1$Û#o•Ȳ-„ÒuŸC媓'¯šÅåê=°+¿'JÏ,r‰vv«·ù#~š'å*`œûAûÇ’É”ì o‚»s;!m#7 ëÌ@œ¢€r²ð°ž×kð›G%°}ûΛqÖµ`³nŒk;ü2®ü2î% ¡0ᯀH J„$L¢¤a6«P˜ÄªÇÉ›! (%ÀP®(ÅÉÆH€H€H€H€H€"C€Â$2Ôx @”x„¥Í²1 ¸F`Ü/MµZØ-×ñ~I€H€H€Ü 0qφGH€"@ €®PO#   È`(WdÉñ:     (#@ae(Ù @d x¤\pdÇëH€H€b'¬9D#  gÑê1ñ÷oàÜ·I ÎH—.øúúÆÙûç“€3† ùßgÜ& $­B&$P¦L 7JÚ´i‰…H€H€H€HÀ…@´zL\úâWˆó¾ç@$@$@$à†…‰0ÜM$@$@$@$@$à9&žcÍžH€H€H€H€H€Ü 0q†»I€H€H€H€H€^LÁ ÔªUKÒ¤Icv†$NìI&€5MâÅ ý^á/O2ŽH€H€H€"B ô™SDZâ¹$@XÓ¤zõêŽï®\»Ä•¿“ Äu&qýÀû6®IðÎ…vÌù9pà‚÷ó-ž–ܹÓ8ÚvÝm|Îç®^}T,<(ûöH‰Y¤UË"’#GjçSÌö¶m§Mø×öíg¤P¡ôR£zn©T)ç#ç…´cÀÀU²mÛI™2‘|û¿ü2v«Ì˜¹Kš>[Hºt.e.Y±âˆ|ÿÃÙúÏi“÷R¦tViѼ°4hPà‘&º }?[!7” nH¹rÙ¥ZÕ\R·N^[ÇùŸ|ºLï뼤OŸTô¯-¿NÛaú=sæš”-“MþÛ»ŠdΜÂq¾Ýùó&™õ¿=òŽå–z×üü|Íýöz·b°çyìØ%ù¨ÏRsYÓgŸ’Ë󦌽ªþ¾êÖÍ'I4¼ÐÙ"ÂÅù:n“ x'ŸjÑ54››ðànŸèê‚íF‚ÀÊ•G¤j±Ò¨a™=«uˆ-Ü»w_R¤ê/¾¾)äðÁ·ç`RÿB«é²^EIjT"ý䢆keUï Ú*©áa‹”†'™ü\˜(aŒ9&½T }©‚ ¹6¸Çë7îJ®œ©eŲޒS?­}5|­ôzo‘ ï¬Úï‰WÌ8ßx­´ ×±Æz}Ýúd¡òJŸ.© ‘’ÒàjÓô` Ÿëѽ‚|öù ˆÐ9{0CÖÏÐ!u0³6o>)•ªŒ‘*]-q¢ø2ÿ϶R­Zns¨B¥Q²öï%‹ŠˆÐù »$£ŠLœAƒmÓn¦/ÁN úR¾\6™õ[+ɤbá¬YÓ§ÌoâÊÕÛæ»ýSµJNYº¸ƒ£ÈB«ÖÓeê´öp°Ï†þ´ý–žá¬!~‰q> úš1Dãzbü9  È}Ö¹6yÕN“ëžï.Ô|“{æÍ½óí´n;C6éÛøñc›Ê…€^rþ\/Y4¿\ÕIgõšcåòå[R»v^¹uãCÉŸ/­`‚‹mˆ’ÈZç.³oäW¯è,_báÖÈèQMäêåäòÅ÷åU@ÿêøÇÿÇmwáßÏ£6ÉO?4’S'z˜v»k!xÆßêhwÞ¼ýÒ­Ç©P!»œ8Þ]Žíf˜´z¡ˆ|ûý#*'‡±¯Ô€ Q’,iõ>Ä3©? %iR'–·T½óV9#b SÐ÷Bõ@XëúÖŸF”@„`ìx.o¾QF b Øz¸Äžêø&+Ö:J ï×(X——KÊÁuǧ«÷ÐÆ J»¶EûkÕÊ+÷©&=TÌ Ä«³^•ö¬†v!Ü($ë߯–¼Ø¾˜9”H'ä={T~Ú(Û4¬êqìóÏj»x|†~µÖ„SÙv{öZh6‡ ©ç{J:‰Œ×Ìx FÙ"}>ªæðØëBú¼§.‘§ g0Þ"”k†·±Yó©Æû‚ó'OlnBذ°,ÿÆ“±iBŸ"Û¼ù”ùDxžB±ðl2fL.Ç5ì báR®e !ú,ßS*TŠø}'ç5 ÏzýúR¼xfùü‹¦müY¼ðE)¨!w°W_)-™ÕÓuMCñ†…0WO¼AË–tpxSîܹ'#µ¸lÏÞ ¸yKàØ±Eêè=‚¶øq#vË­ðû¢ÿÊq1ñ €× 0ñúG}DxÎùó7‚uχµ]ZVÕlÙR™]Ë5׆p+¼áw¶re³™¯‹ 6¡w>'²ÛíÛ=A®m ·ÅÙòæM+ âûÈÁƒâÊùXD¶‘ÛàlXמä±À®Ë­^VDEútåQI=ÌÙ'{uâýÔSs;ÌÅnþôû¼¦)’Éq9+0„“!ŸfÔèÀÉ<ļ"ð‚ kµjå1ßáíÈžs˜”ÕgR¥rN©_/Ÿ ár$ö:”…¶1S³f™>#Ð;²Wók’'Oh„ Î) ¬(Á÷)IUeõ§ztX²aãÉG„I™2Y¢× /Å5ض«ð vÐ勳(Áø‘ÏCÎH—Wÿp9;ð+ò6p.Æ×SsR~ùÊmfÍÚãæreòjòùØ1M¥² gCžòˆœ­RÅaOKJÖâj?&0 p5äæ8<[®VªTViÒ¸ üoö^s!fSÔû†ohH<3ÆÖ3ÉÿåâÚ¿“ x' ï|.1:*TàúXCð6ÞzI0 ûƹ¹r…\õ*SÆàB!¼7rTó7`6§!¼×ÅWïHtXX«¾[¨…ªaî¬@tî=²?†Û¶JÕ-M¬OXÀÉœb½1®ç{ëwËåy›?WØ!ÚìxQ ìÒ¦}(6ì±ð~¢ô0ò<àÁB%2gOÑ#UPˆñ @DÁ‹€R¼° å³ËÛš$RÍK—’·Þ™gò† rÖª¥‘æŸXC-šßa«oaÿR­vf-–]vEÃ3FiçdÉÚSd‘Š k…öfÏ·Ÿ(ÿ a…1ß|íoî ‚e’]cNCµ4“ˆp±íó“H€H€HÀû °*—÷?£!r`˜„Z«§kIÀfÿnc÷ã³ïgË¥A£IŽø{ e‡ÍN\1¶†R»X#&Ìu|á¼%çnÈš5Ç‚]†¼œªÕ$LJåy v¡Ë—ºAIíåBe²“'¯˜ªg_ô[)¹ó<ùGH?M‡]¹rK^ymŽù‡°¯K—nšÊXë¤dPÉ_œgËùbÛZ‡N³L9_TcC’;ÂÓ`(]NË#\â v[л÷˜/ç4b´ÿ€UÏ*tUÔ0°ÈØ$õò`üûÄIÛLÈÝéÔ±¸£9;öˆpq\Ì    ¯'@‰×?¢˜ B‰RhÒ3& xóŽÅíüu- ÿùÍ:]^™-mÛøO¥¿þf 7¡©®Ya­páŒFÄ ´+®Ãb˜¸~£¥g_Ö‰6*8Á°¸ÞŽÇ×>=i!/"ý=¼¾T¨4ZZ¶ž¡U­jiU­Œ¦Ê„ CÈÛãXï*›½ ãtÑA,<ˆÊZ0<Ëù)5ªç6Þ,Ę-Ç0MBO£bæªñDà|,rY¢Dfl: Èáõ©£ë©¸*­a1DØ­~†’¾È_ùQÓñOËf¡àÀP­àf=bæ¢üAáßµ¬0¶:¾4Kú|¼Ôxe°¤5+R"ÂÅ^ËO   ï'@‰÷?£!ÖžÀDѾ•Ç@¦Nn!/é[l”{­^kœY¨á6¯ë‚‚¿ÏlÌC€rÃH|î«‹Ž¸þVOÇZE‚8Á?¼©Ÿ3»Çï3¤ñEdZ æµ39í;ü.¥ÊŽ4‹&Nß,ÒÊìi?qâZš·½Y_ÄŠ+JÊj¥«Mº8*¦AHN™Ô\Ú´zƬ‚ò½Ûwœuˆ,PøÛŒ–ÁVgÇXàÑ™6õy³æŒÖQÁ"våyìGU²Í_ÑUá³ÚÓ¢á^+—wÒ¿5Ç Ø@Y㉞“œ9+À=vYvï 0IüÈ[úöëŽðÁˆp‰Àx* @ àÊï1üžÔîV„< $^c‹9‚W^²÷…ÄjxCPr^k(K¼mÛiÁºÈa°Éäö¸§>Ý/"ý£ì2î æ™}“¯Pd=îúEyb”É…÷ Uʰʼ;C(Ùç·$£Nêåú|ìÊï‰Ôƒ0a»µ<4<=~~™0Î}¡ý‡cÉ$9U|†7Áݹ¶‘Œ¿G „ë½{tÜ©ÌïÃzn\¯‰×kù=fpå÷˜áÎ^I€HàI @aò$<%Ž‘¢˜@HÂ$Š»`s$" “±p' €`($@$@$@$@$@1N€Â$Æ@$@$@$@$@$ð0èŸ,H€â q¿4•k×îhnHœ¹eÞ( €— 0ñòÄá‘@t( ‹eÒH€H€H€HÀ›0”Ë›žÇB$@$@$@$@q”…I}ð¼m    ð&&Þô48    ˆ£(Lâèƒçm“ €7 0ñ¦§Á± @%@aGxÞ6 x oz ÄQ&qôÁó¶I€H€H€H€HÀ›P˜xÓÓàXH€H€H€H€H Žðy ]÷îãã]M³]  '˜@4þ§ç ¦Â¡“ @Ü&­ÿ¸M—wO$@$ð† >²;H€H€H Z=&ÄK$@$@$@$@$@á!­“ð €ç … $@$@$@$@$@1N€Â$Æ@$@$@$@$@$@aÂß @Œ 0‰ñGÀ P˜ð7@$@$@$@$@$ã(Lbüp$@$@$@$@$@&ü Ä8 “ … $@$@$@$@$@1N€Â$Æ@$@$@$@$@$@aÂß @Œ 0‰ñGÀ P˜ð7@$@$@$@$@$ã(Lbüp$@$@$@$@$@&ü Ä8 “ … $@$@$@$@$@1N€Â$Æ@$@$@$@$@$ðc¡žHÂÂ2WIEND®B`‚pyramid-1.6/docs/_static/pyramid_router.svg0000644000076500000240000003314212520062551021717 0ustar michaelstaff00000000000000 2014-12-01 09:19ZPyramid RouterLayer 1Pyramid RouterObtain a root object from the root factoryTraverse the model graphfrom the root using the pathTraversal locatesthe context and view nameLook up a view callable in the registry using the context and view nameView callable found?Return the Not Found ViewNoCurrent user has authorization to invoke the view callable?YesYesReturn the Forbidden ViewNoInvoke the view callable,which returns a responseReturn the response pyramid-1.6/docs/api/0000755000076500000240000000000012642137501015254 5ustar michaelstaff00000000000000pyramid-1.6/docs/api/authentication.rst0000644000076500000240000000130112517346416021030 0ustar michaelstaff00000000000000.. _authentication_module: :mod:`pyramid.authentication` -------------------------------- Authentication Policies ~~~~~~~~~~~~~~~~~~~~~~~ .. automodule:: pyramid.authentication .. autoclass:: AuthTktAuthenticationPolicy :members: :inherited-members: .. autoclass:: RemoteUserAuthenticationPolicy :members: :inherited-members: .. autoclass:: SessionAuthenticationPolicy :members: :inherited-members: .. autoclass:: BasicAuthAuthenticationPolicy :members: :inherited-members: .. autoclass:: RepozeWho1AuthenticationPolicy :members: :inherited-members: Helper Classes ~~~~~~~~~~~~~~ .. autoclass:: AuthTktCookieHelper :members: pyramid-1.6/docs/api/authorization.rst0000644000076500000240000000025112234375161020707 0ustar michaelstaff00000000000000.. _authorization_module: :mod:`pyramid.authorization` ------------------------------- .. automodule:: pyramid.authorization .. autoclass:: ACLAuthorizationPolicy pyramid-1.6/docs/api/compat.rst0000644000076500000240000001037612234375161017303 0ustar michaelstaff00000000000000.. _compat_module: :mod:`pyramid.compat` ---------------------- The ``pyramid.compat`` module provides platform and version compatibility for Pyramid and its add-ons across Python platform and version differences. APIs will be removed from this module over time as Pyramid ceases to support systems which require compatibility imports. .. automodule:: pyramid.compat .. autofunction:: ascii_native_ .. attribute:: binary_type Binary type for this platform. For Python 3, it's ``bytes``. For Python 2, it's ``str``. .. autofunction:: bytes_ .. attribute:: class_types Sequence of class types for this platform. For Python 3, it's ``(type,)``. For Python 2, it's ``(type, types.ClassType)``. .. attribute:: configparser On Python 2, the ``ConfigParser`` module, on Python 3, the ``configparser`` module. .. function:: escape(v) On Python 2, the ``cgi.escape`` function, on Python 3, the ``html.escape`` function. .. function:: exec_(code, globs=None, locs=None) Exec code in a compatible way on both Python 2 and 3. .. attribute:: im_func On Python 2, the string value ``im_func``, on Python 3, the string value ``__func__``. .. function:: input_(v) On Python 2, the ``raw_input`` function, on Python 3, the ``input`` function. .. attribute:: integer_types Sequence of integer types for this platform. For Python 3, it's ``(int,)``. For Python 2, it's ``(int, long)``. .. function:: is_nonstr_iter(v) Return ``True`` if ``v`` is a non-``str`` iterable on both Python 2 and Python 3. .. function:: iteritems_(d) Return ``d.items()`` on Python 3, ``d.iteritems()`` on Python 2. .. function:: itervalues_(d) Return ``d.values()`` on Python 3, ``d.itervalues()`` on Python 2. .. function:: iterkeys_(d) Return ``d.keys()`` on Python 3, ``d.iterkeys()`` on Python 2. .. attribute:: long Long type for this platform. For Python 3, it's ``int``. For Python 2, it's ``long``. .. function:: map_(v) Return ``list(map(v))`` on Python 3, ``map(v)`` on Python 2. .. attribute:: pickle ``cPickle`` module if it exists, ``pickle`` module otherwise. .. attribute:: PY3 ``True`` if running on Python 3, ``False`` otherwise. .. attribute:: PYPY ``True`` if running on PyPy, ``False`` otherwise. .. function:: reraise(tp, value, tb=None) Reraise an exception in a compatible way on both Python 2 and Python 3, e.g. ``reraise(*sys.exc_info())``. .. attribute:: string_types Sequence of string types for this platform. For Python 3, it's ``(str,)``. For Python 2, it's ``(basestring,)``. .. attribute:: SimpleCookie On Python 2, the ``Cookie.SimpleCookie`` class, on Python 3, the ``http.cookies.SimpleCookie`` module. .. autofunction:: text_ .. attribute:: text_type Text type for this platform. For Python 3, it's ``str``. For Python 2, it's ``unicode``. .. autofunction:: native_ .. attribute:: urlparse ``urlparse`` module on Python 2, ``urllib.parse`` module on Python 3. .. attribute:: url_quote ``urllib.quote`` function on Python 2, ``urllib.parse.quote`` function on Python 3. .. attribute:: url_quote_plus ``urllib.quote_plus`` function on Python 2, ``urllib.parse.quote_plus`` function on Python 3. .. attribute:: url_unquote ``urllib.unquote`` function on Python 2, ``urllib.parse.unquote`` function on Python 3. .. attribute:: url_encode ``urllib.urlencode`` function on Python 2, ``urllib.parse.urlencode`` function on Python 3. .. attribute:: url_open ``urllib2.urlopen`` function on Python 2, ``urllib.request.urlopen`` function on Python 3. .. function:: url_unquote_text(v, encoding='utf-8', errors='replace') On Python 2, return ``url_unquote(v).decode(encoding(encoding, errors))``; on Python 3, return the result of ``urllib.parse.unquote``. .. function:: url_unquote_native(v, encoding='utf-8', errors='replace') On Python 2, return ``native_(url_unquote_text_v, encoding, errors))``; on Python 3, return the result of ``urllib.parse.unquote``. pyramid-1.6/docs/api/config.rst0000644000076500000240000000743212524266531017266 0ustar michaelstaff00000000000000.. _configuration_module: .. role:: methodcategory :class: methodcategory :mod:`pyramid.config` --------------------- .. automodule:: pyramid.config .. autoclass:: Configurator :methodcategory:`Controlling Configuration State` .. automethod:: commit .. automethod:: begin .. automethod:: end .. automethod:: include .. automethod:: make_wsgi_app() .. automethod:: scan :methodcategory:`Adding Routes and Views` .. automethod:: add_route .. automethod:: add_static_view(name, path, cache_max_age=3600, permission=NO_PERMISSION_REQUIRED) .. automethod:: add_view .. automethod:: add_notfound_view .. automethod:: add_forbidden_view :methodcategory:`Adding an Event Subscriber` .. automethod:: add_subscriber :methodcategory:`Using Security` .. automethod:: set_authentication_policy .. automethod:: set_authorization_policy .. automethod:: set_default_permission .. automethod:: add_permission :methodcategory:`Extending the Request Object` .. automethod:: add_request_method .. automethod:: set_request_property :methodcategory:`Using I18N` .. automethod:: add_translation_dirs .. automethod:: set_locale_negotiator :methodcategory:`Overriding Assets` .. automethod:: override_asset(to_override, override_with) :methodcategory:`Getting and Adding Settings` .. automethod:: add_settings .. automethod:: get_settings :methodcategory:`Hooking Pyramid Behavior` .. automethod:: add_renderer .. automethod:: add_resource_url_adapter .. automethod:: add_response_adapter .. automethod:: add_traverser .. automethod:: add_tween .. automethod:: add_route_predicate .. automethod:: add_view_predicate .. automethod:: set_request_factory .. automethod:: set_root_factory .. automethod:: set_session_factory .. automethod:: set_view_mapper :methodcategory:`Extension Author APIs` .. automethod:: action .. automethod:: add_directive .. automethod:: with_package .. automethod:: derive_view :methodcategory:`Utility Methods` .. automethod:: absolute_asset_spec .. automethod:: maybe_dotted :methodcategory:`ZCA-Related APIs` .. automethod:: hook_zca .. automethod:: unhook_zca .. automethod:: setup_registry :methodcategory:`Testing Helper APIs` .. automethod:: testing_add_renderer .. automethod:: testing_add_subscriber .. automethod:: testing_resources .. automethod:: testing_securitypolicy :methodcategory:`Attributes` .. attribute:: introspectable A shortcut attribute which points to the :class:`pyramid.registry.Introspectable` class (used during directives to provide introspection to actions). .. versionadded:: 1.3 .. attribute:: introspector The :term:`introspector` related to this configuration. It is an instance implementing the :class:`pyramid.interfaces.IIntrospector` interface. .. versionadded:: 1.3 .. attribute:: registry The :term:`application registry` which holds the configuration associated with this configurator. .. attribute:: global_registries The set of registries that have been created for :app:`Pyramid` applications, one for each call to :meth:`pyramid.config.Configurator.make_wsgi_app` in the current process. The object itself supports iteration and has a ``last`` property containing the last registry loaded. The registries contained in this object are stored as weakrefs, thus they will only exist for the lifetime of the actual applications for which they are being used. .. autoclass:: not_ .. attribute:: PHASE0_CONFIG .. attribute:: PHASE1_CONFIG .. attribute:: PHASE2_CONFIG .. attribute:: PHASE3_CONFIG pyramid-1.6/docs/api/decorator.rst0000644000076500000240000000021012517346416017771 0ustar michaelstaff00000000000000.. _decorator_module: :mod:`pyramid.decorator` -------------------------- .. automodule:: pyramid.decorator .. autofunction:: reify pyramid-1.6/docs/api/events.rst0000644000076500000240000000142612234375161017320 0ustar michaelstaff00000000000000.. _events_module: :mod:`pyramid.events` -------------------------- .. automodule:: pyramid.events Functions ~~~~~~~~~ .. autofunction:: subscriber .. _event_types: Event Types ~~~~~~~~~~~ .. autoclass:: ApplicationCreated .. autoclass:: NewRequest .. autoclass:: ContextFound .. autoclass:: NewResponse .. autoclass:: BeforeRender :members: :inherited-members: :exclude-members: update .. method:: update(E, **F) Update D from dict/iterable E and F. If E has a .keys() method, does: for k in E: D[k] = E[k] If E lacks .keys() method, does: for (k, v) in E: D[k] = v. In either case, this is followed by: for k in F: D[k] = F[k]. See :ref:`events_chapter` for more information about how to register code which subscribes to these events. pyramid-1.6/docs/api/exceptions.rst0000644000076500000240000000051312520062551020163 0ustar michaelstaff00000000000000.. _exceptions_module: :mod:`pyramid.exceptions` ---------------------------- .. automodule:: pyramid.exceptions .. autoexception:: BadCSRFToken .. autoexception:: PredicateMismatch .. autoexception:: Forbidden .. autoexception:: NotFound .. autoexception:: ConfigurationError .. autoexception:: URLDecodeError pyramid-1.6/docs/api/httpexceptions.rst0000644000076500000240000000443412520062551021071 0ustar michaelstaff00000000000000.. _httpexceptions_module: :mod:`pyramid.httpexceptions` -------------------------------- .. automodule:: pyramid.httpexceptions .. attribute:: status_map A mapping of integer status code to HTTP exception class (eg. the integer "401" maps to :class:`pyramid.httpexceptions.HTTPUnauthorized`). All mapped exception classes are children of :class:`pyramid.httpexceptions`, .. autofunction:: exception_response .. autoexception:: HTTPException .. autoexception:: HTTPOk .. autoexception:: HTTPRedirection .. autoexception:: HTTPError .. autoexception:: HTTPClientError .. autoexception:: HTTPServerError .. autoexception:: HTTPCreated .. autoexception:: HTTPAccepted .. autoexception:: HTTPNonAuthoritativeInformation .. autoexception:: HTTPNoContent .. autoexception:: HTTPResetContent .. autoexception:: HTTPPartialContent .. autoexception:: HTTPMultipleChoices .. autoexception:: HTTPMovedPermanently .. autoexception:: HTTPFound .. autoexception:: HTTPSeeOther .. autoexception:: HTTPNotModified .. autoexception:: HTTPUseProxy .. autoexception:: HTTPTemporaryRedirect .. autoexception:: HTTPBadRequest .. autoexception:: HTTPUnauthorized .. autoexception:: HTTPPaymentRequired .. autoexception:: HTTPForbidden .. autoexception:: HTTPNotFound .. autoexception:: HTTPMethodNotAllowed .. autoexception:: HTTPNotAcceptable .. autoexception:: HTTPProxyAuthenticationRequired .. autoexception:: HTTPRequestTimeout .. autoexception:: HTTPConflict .. autoexception:: HTTPGone .. autoexception:: HTTPLengthRequired .. autoexception:: HTTPPreconditionFailed .. autoexception:: HTTPRequestEntityTooLarge .. autoexception:: HTTPRequestURITooLong .. autoexception:: HTTPUnsupportedMediaType .. autoexception:: HTTPRequestRangeNotSatisfiable .. autoexception:: HTTPExpectationFailed .. autoexception:: HTTPUnprocessableEntity .. autoexception:: HTTPLocked .. autoexception:: HTTPFailedDependency .. autoexception:: HTTPInternalServerError .. autoexception:: HTTPNotImplemented .. autoexception:: HTTPBadGateway .. autoexception:: HTTPServiceUnavailable .. autoexception:: HTTPGatewayTimeout .. autoexception:: HTTPVersionNotSupported .. autoexception:: HTTPInsufficientStorage pyramid-1.6/docs/api/i18n.rst0000644000076500000240000000123112517346416016572 0ustar michaelstaff00000000000000.. _i18n_module: :mod:`pyramid.i18n` ---------------------- .. automodule:: pyramid.i18n .. autoclass:: TranslationString .. autofunction:: TranslationStringFactory .. autoclass:: Localizer :members: .. attribute:: locale_name The locale name for this localizer (e.g. ``en`` or ``en_US``). .. autofunction:: get_localizer .. autofunction:: negotiate_locale_name .. autofunction:: get_locale_name .. autofunction:: default_locale_negotiator .. autofunction:: make_localizer See :ref:`i18n_chapter` for more information about using :app:`Pyramid` internationalization and localization services within an application. pyramid-1.6/docs/api/index.rst0000644000076500000240000000030112520062551017104 0ustar michaelstaff00000000000000.. _html_api_documentation: API Documentation ================= Comprehensive reference material for every public API exposed by :app:`Pyramid`: .. toctree:: :maxdepth: 1 :glob: * pyramid-1.6/docs/api/interfaces.rst0000644000076500000240000000306312524266531020140 0ustar michaelstaff00000000000000.. _interfaces_module: :mod:`pyramid.interfaces` ---------------------------- .. automodule:: pyramid.interfaces Event-Related Interfaces ++++++++++++++++++++++++ .. autointerface:: IApplicationCreated :members: .. autointerface:: INewRequest :members: .. autointerface:: IContextFound :members: .. autointerface:: INewResponse :members: .. autointerface:: IBeforeRender :members: Other Interfaces ++++++++++++++++ .. autointerface:: IAuthenticationPolicy :members: .. autointerface:: IAuthorizationPolicy :members: .. autointerface:: IExceptionResponse :members: .. autointerface:: IRoute :members: .. autointerface:: IRoutePregenerator :members: .. autointerface:: ISession :members: .. autointerface:: ISessionFactory :members: .. autointerface:: IRendererInfo :members: .. autointerface:: IRendererFactory :members: .. autointerface:: IRenderer :members: .. autointerface:: IResponseFactory :members: .. autointerface:: IViewMapperFactory :members: .. autointerface:: IViewMapper :members: .. autointerface:: IDict :members: .. autointerface:: IMultiDict :members: .. autointerface:: IResponse :members: .. autointerface:: IIntrospectable :members: .. autointerface:: IIntrospector :members: .. autointerface:: IActionInfo :members: .. autointerface:: IAssetDescriptor :members: .. autointerface:: IResourceURL :members: .. autointerface:: ICacheBuster :members: pyramid-1.6/docs/api/location.rst0000644000076500000240000000024612234375161017623 0ustar michaelstaff00000000000000.. _location_module: :mod:`pyramid.location` -------------------------- .. automodule:: pyramid.location .. autofunction:: lineage .. autofunction:: inside pyramid-1.6/docs/api/paster.rst0000644000076500000240000000051012520062551017275 0ustar michaelstaff00000000000000.. _paster_module: :mod:`pyramid.paster` --------------------------- .. automodule:: pyramid.paster .. autofunction:: bootstrap .. autofunction:: get_app(config_uri, name=None, options=None) .. autofunction:: get_appsettings(config_uri, name=None, options=None) .. autofunction:: setup_logging(config_uri) pyramid-1.6/docs/api/path.rst0000644000076500000240000000057512520062551016746 0ustar michaelstaff00000000000000.. _path_module: :mod:`pyramid.path` --------------------------- .. automodule:: pyramid.path .. attribute:: CALLER_PACKAGE A constant used by the constructor of :class:`pyramid.path.DottedNameResolver` and :class:`pyramid.path.AssetResolver`. .. autoclass:: DottedNameResolver :members: .. autoclass:: AssetResolver :members: pyramid-1.6/docs/api/registry.rst0000644000076500000240000000425112524266531017665 0ustar michaelstaff00000000000000.. _registry_module: :mod:`pyramid.registry` --------------------------- .. module:: pyramid.registry .. autoclass:: Registry .. attribute:: settings The dictionary-like :term:`deployment settings` object. See :ref:`deployment_settings` for information. This object is often 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 When a registry is set up (or created) by a :term:`Configurator`, the registry will be decorated with an instance named ``introspector`` implementing the :class:`pyramid.interfaces.IIntrospector` interface. .. seealso:: See also :attr:`pyramid.config.Configurator.introspector`. When a registry is created "by hand", however, this attribute will not exist until set up by a configurator. This attribute is often accessed as ``request.registry.introspector`` in a typical Pyramid application. .. method:: notify(*events) Fire one or more events. All event subscribers to the event(s) will be notified. The subscribers will be called synchronously. This method is often accessed as ``request.registry.notify`` in Pyramid applications to fire custom events. See :ref:`custom_events` for more information. .. class:: Introspectable .. versionadded:: 1.3 The default implementation of the interface :class:`pyramid.interfaces.IIntrospectable` used by framework exenders. An instance of this class is created when :attr:`pyramid.config.Configurator.introspectable` is called. .. autoclass:: Deferred .. versionadded:: 1.4 .. autofunction:: undefer .. versionadded:: 1.4 .. autoclass:: predvalseq .. versionadded:: 1.4 pyramid-1.6/docs/api/renderers.rst0000644000076500000240000000135312517346416020011 0ustar michaelstaff00000000000000.. _renderers_module: :mod:`pyramid.renderers` --------------------------- .. module:: pyramid.renderers .. autofunction:: get_renderer .. autofunction:: render .. autofunction:: render_to_response .. autoclass:: JSON .. automethod:: add_adapter .. autoclass:: JSONP .. automethod:: add_adapter .. attribute:: null_renderer An object that can be used in advanced integration cases as input to the view configuration ``renderer=`` argument. When the null renderer is used as a view renderer argument, Pyramid avoids converting the view callable result into a Response object. This is useful if you want to reuse the view configuration and lookup machinery outside the context of its use by the Pyramid router. pyramid-1.6/docs/api/request.rst0000644000076500000240000003513212575217552017514 0ustar michaelstaff00000000000000.. _request_module: :mod:`pyramid.request` --------------------------- .. module:: pyramid.request .. autoclass:: Request :members: :inherited-members: :exclude-members: add_response_callback, add_finished_callback, route_url, route_path, current_route_url, current_route_path, static_url, static_path, model_url, resource_url, resource_path, set_property, effective_principals, authenticated_userid, unauthenticated_userid, has_permission .. attribute:: context The :term:`context` will be available as the ``context`` attribute of the :term:`request` object. It will be the context object implied by the current request. See :ref:`traversal_chapter` for information about context objects. .. attribute:: registry The :term:`application registry` will be available as the ``registry`` attribute of the :term:`request` object. See :ref:`zca_chapter` for more information about the application registry. .. attribute:: root The :term:`root` object will be available as the ``root`` attribute of the :term:`request` object. It will be the resource object at which traversal started (the root). See :ref:`traversal_chapter` for information about root objects. .. attribute:: subpath The traversal :term:`subpath` will be available as the ``subpath`` attribute of the :term:`request` object. It will be a sequence containing zero or more elements (which will be Unicode objects). See :ref:`traversal_chapter` for information about the subpath. .. attribute:: traversed The "traversal path" will be available as the ``traversed`` attribute of the :term:`request` object. It will be a sequence representing the ordered set of names that were used to traverse to the :term:`context`, not including the view name or subpath. If there is a virtual root associated with the request, the virtual root path is included within the traversal path. See :ref:`traversal_chapter` for more information. .. attribute:: view_name The :term:`view name` will be available as the ``view_name`` attribute of the :term:`request` object. It will be a single string (possibly the empty string if we're rendering a default view). See :ref:`traversal_chapter` for information about view names. .. attribute:: virtual_root The :term:`virtual root` will be available as the ``virtual_root`` attribute of the :term:`request` object. It will be the virtual root object implied by the current request. See :ref:`vhosting_chapter` for more information about virtual roots. .. attribute:: virtual_root_path The :term:`virtual root` *path* will be available as the ``virtual_root_path`` attribute of the :term:`request` object. It will be a sequence representing the ordered set of names that were used to traverse to the virtual root object. See :ref:`vhosting_chapter` for more information about virtual roots. .. attribute:: exception If an exception was raised by a :term:`root factory` or a :term:`view callable`, or at various other points where :app:`Pyramid` executes user-defined code during the processing of a request, the exception object which was caught will be available as the ``exception`` attribute of the request within a :term:`exception view`, a :term:`response callback` or a :term:`finished callback`. If no exception occurred, the value of ``request.exception`` will be ``None`` within response and finished callbacks. .. attribute:: exc_info If an exception was raised by a :term:`root factory` or a :term:`view callable`, or at various other points where :app:`Pyramid` executes user-defined code during the processing of a request, result of ``sys.exc_info()`` will be available as the ``exc_info`` attribute of the request within a :term:`exception view`, a :term:`response callback` or a :term:`finished callback`. If no exception occurred, the value of ``request.exc_info`` will be ``None`` within response and finished callbacks. .. attribute:: response This attribute is actually a "reified" property which returns an instance of the :class:`pyramid.response.Response` class. The response object returned does not exist until this attribute is accessed. Once it is accessed, subsequent accesses to this request object will return the same :class:`~pyramid.response.Response` object. The ``request.response`` API can is used by renderers. A render obtains the response object it will return from a view that uses that renderer by accessing ``request.response``. Therefore, it's possible to use the ``request.response`` API to set up a response object with "the right" attributes (e.g. by calling ``request.response.set_cookie(...)`` or ``request.response.content_type = 'text/plain'``, etc) within a view that uses a renderer. For example, within a view that uses a :term:`renderer`:: response = request.response response.set_cookie('mycookie', 'mine, all mine!') return {'text':'Value that will be used by the renderer'} Mutations to this response object will be preserved in the response sent to the client after rendering. For more information about using ``request.response`` in conjunction with a renderer, see :ref:`request_response_attr`. Non-renderer code can also make use of request.response instead of creating a response "by hand". For example, in view code:: response = request.response response.body = 'Hello!' response.content_type = 'text/plain' return response Note that the response in this circumstance is not "global"; it still must be returned from the view code if a renderer is not used. .. attribute:: session If a :term:`session factory` has been configured, this attribute will represent the current user's :term:`session` object. If a session factory *has not* been configured, requesting the ``request.session`` attribute will cause a :class:`pyramid.exceptions.ConfigurationError` to be raised. .. attribute:: matchdict If a :term:`route` has matched during this request, this attribute will be a dictionary containing the values matched by the URL pattern associated with the route. If a route has not matched during this request, the value of this attribute will be ``None``. See :ref:`matchdict`. .. attribute:: matched_route If a :term:`route` has matched during this request, this attribute will be an object representing the route matched by the URL pattern associated with the route. If a route has not matched during this request, the value of this attribute will be ``None``. See :ref:`matched_route`. .. attribute:: authenticated_userid .. versionadded:: 1.5 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) :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 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) .. versionadded:: 1.4a1 Obtain a response object from the Pyramid application based on information in the ``request`` object provided. The ``request`` object must be an object that implements the Pyramid request interface (such as a :class:`pyramid.request.Request` instance). If ``use_tweens`` is ``True``, the request will be sent to the :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main router handler, and no tweens will be invoked. This function also: - manages the threadlocal stack (so that :func:`~pyramid.threadlocal.get_current_request` and :func:`~pyramid.threadlocal.get_current_registry` work during a request) - Adds a ``registry`` attribute (the current Pyramid registry) and a ``invoke_subrequest`` attribute (a callable) to the request object it's handed. - sets request extensions (such as those added via :meth:`~pyramid.config.Configurator.add_request_method` or :meth:`~pyramid.config.Configurator.set_request_property`) on the request it's passed. - causes a :class:`~pyramid.events.NewRequest` event to be sent at the beginning of request processing. - causes a :class:`~pyramid.events.ContextFound` event to be sent when a context resource is found. - Ensures that the user implied by the request passed has the necessary authorization to invoke view callable before calling it. - Calls any :term:`response callback` functions defined within the request's lifetime if a response is obtained from the Pyramid application. - causes a :class:`~pyramid.events.NewResponse` event to be sent if a response is obtained. - Calls any :term:`finished callback` functions defined within the request's lifetime. ``invoke_subrequest`` isn't *actually* a method of the Request object; it's a callable added when the Pyramid router is invoked, or when a subrequest is invoked. This means that it's not available for use on a request provided by e.g. the ``pshell`` environment. .. seealso:: See also :ref:`subrequest_chapter`. .. automethod:: has_permission .. automethod:: add_response_callback .. automethod:: add_finished_callback .. automethod:: route_url .. automethod:: route_path .. automethod:: current_route_url .. automethod:: current_route_path .. automethod:: static_url .. automethod:: static_path .. automethod:: resource_url .. automethod:: resource_path .. attribute:: json_body This property will return the JSON-decoded variant of the request body. If the request body is not well-formed JSON, or there is no body associated with this request, this property will raise an exception. .. seealso:: See also :ref:`request_json_body`. .. method:: set_property(callable, name=None, reify=False) Add a callable or a property descriptor to the request instance. Properties, unlike attributes, are lazily evaluated by executing an underlying callable when accessed. They can be useful for adding features to an object without any cost if those features go unused. A property may also be reified via the :class:`pyramid.decorator.reify` decorator by setting ``reify=True``, allowing the result of the evaluation to be cached. Thus the value of the property is only computed once for the lifetime of the object. ``callable`` can either be a callable that accepts the request as its single positional parameter, or it can be a property descriptor. If the ``callable`` is a property descriptor a ``ValueError`` will be raised if ``name`` is ``None`` or ``reify`` is ``True``. If ``name`` is None, the name of the property will be computed from the name of the ``callable``. .. code-block:: python :linenos: def _connect(request): conn = request.registry.dbsession() 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 @subscriber(NewRequest) def new_request(event): request = event.request request.set_property(_connect, 'db', reify=True) The subscriber doesn't actually connect to the database, it just provides the API which, when accessed via ``request.db``, will create the connection. Thanks to reify, only one connection is made per-request even if ``request.db`` is accessed many times. This pattern provides a way to augment the ``request`` object without having to subclass it, which can be useful for extension authors. .. versionadded:: 1.3 .. attribute:: localizer A :term:`localizer` which will use the current locale name to translate values. .. versionadded:: 1.5 .. attribute:: locale_name The locale name of the current request as computed by the :term:`locale negotiator`. .. versionadded:: 1.5 .. note:: For information about the API of a :term:`multidict` structure (such as that used as ``request.GET``, ``request.POST``, and ``request.params``), see :class:`pyramid.interfaces.IMultiDict`. .. autofunction:: apply_request_extensions(request) pyramid-1.6/docs/api/response.rst0000644000076500000240000000044212234375161017647 0ustar michaelstaff00000000000000.. _response_module: :mod:`pyramid.response` --------------------------- .. module:: pyramid.response .. autoclass:: Response :members: :inherited-members: .. autoclass:: FileResponse :members: .. autoclass:: FileIter Functions ~~~~~~~~~ .. autofunction:: response_adapter pyramid-1.6/docs/api/scaffolds.rst0000644000076500000240000000035412234375161017757 0ustar michaelstaff00000000000000.. _scaffolds_module: :mod:`pyramid.scaffolds` ------------------------ .. automodule:: pyramid.scaffolds .. autoclass:: pyramid.scaffolds.Template :members: .. autoclass:: pyramid.scaffolds.PyramidTemplate :members: pyramid-1.6/docs/api/scripting.rst0000644000076500000240000000025312234375161020013 0ustar michaelstaff00000000000000.. _scripting_module: :mod:`pyramid.scripting` --------------------------- .. automodule:: pyramid.scripting .. autofunction:: get_root .. autofunction:: prepare pyramid-1.6/docs/api/security.rst0000644000076500000240000000503012524266531017660 0ustar michaelstaff00000000000000.. _security_module: :mod:`pyramid.security` ========================== .. automodule:: pyramid.security Authentication API Functions ---------------------------- .. autofunction:: authenticated_userid .. autofunction:: unauthenticated_userid .. autofunction:: effective_principals .. autofunction:: forget .. autofunction:: remember(request, userid, **kwargs) Authorization API Functions --------------------------- .. autofunction:: has_permission .. autofunction:: principals_allowed_by_permission .. autofunction:: view_execution_permitted Constants --------- .. attribute:: Everyone The special principal id named 'Everyone'. This principal id is granted to all requests. Its actual value is the string 'system.Everyone'. .. attribute:: Authenticated The special principal id named 'Authenticated'. This principal id is granted to all requests which contain any other non-Everyone principal id (according to the :term:`authentication policy`). Its actual value is the string 'system.Authenticated'. .. attribute:: ALL_PERMISSIONS An object that can be used as the ``permission`` member of an ACE which matches all permissions unconditionally. For example, an ACE that uses ``ALL_PERMISSIONS`` might be composed like so: ``('Deny', 'system.Everyone', ALL_PERMISSIONS)``. .. attribute:: DENY_ALL A convenience shorthand ACE that defines ``('Deny', 'system.Everyone', ALL_PERMISSIONS)``. This is often used as the last ACE in an ACL in systems that use an "inheriting" security policy, representing the concept "don't inherit any other ACEs". .. attribute:: NO_PERMISSION_REQUIRED A special permission which indicates that the view should always be executable by entirely anonymous users, regardless of the default permission, bypassing any :term:`authorization policy` that may be in effect. Its actual value is the string '__no_permission_required__'. Return Values ------------- .. attribute:: Allow The ACE "action" (the first element in an ACE e.g. ``(Allow, Everyone, 'read')`` that means allow access. A sequence of ACEs makes up an ACL. It is a string, and its actual value is "Allow". .. attribute:: Deny The ACE "action" (the first element in an ACE e.g. ``(Deny, 'george', 'read')`` that means deny access. A sequence of ACEs makes up an ACL. It is a string, and its actual value is "Deny". .. autoclass:: ACLDenied :members: .. autoclass:: ACLAllowed :members: .. autoclass:: Denied :members: .. autoclass:: Allowed :members: pyramid-1.6/docs/api/session.rst0000644000076500000240000000063212625453553017503 0ustar michaelstaff00000000000000.. _session_module: :mod:`pyramid.session` --------------------------- .. automodule:: pyramid.session .. autofunction:: signed_serialize .. autofunction:: signed_deserialize .. autofunction:: check_csrf_token .. autofunction:: SignedCookieSessionFactory .. autofunction:: UnencryptedCookieSessionFactoryConfig .. autofunction:: BaseCookieSessionFactory .. autoclass:: PickleSerializer pyramid-1.6/docs/api/settings.rst0000644000076500000240000000024512517346416017657 0ustar michaelstaff00000000000000.. _settings_module: :mod:`pyramid.settings` -------------------------- .. automodule:: pyramid.settings .. autofunction:: asbool .. autofunction:: aslist pyramid-1.6/docs/api/static.rst0000644000076500000240000000052312621251355017276 0ustar michaelstaff00000000000000.. _static_module: :mod:`pyramid.static` --------------------- .. automodule:: pyramid.static .. autoclass:: static_view :members: :inherited-members: .. autoclass:: ManifestCacheBuster :members: .. autoclass:: QueryStringCacheBuster :members: .. autoclass:: QueryStringConstantCacheBuster :members: pyramid-1.6/docs/api/testing.rst0000644000076500000240000000070112517346416017471 0ustar michaelstaff00000000000000.. _testing_module: :mod:`pyramid.testing` ========================== .. automodule:: pyramid.testing .. autofunction:: setUp .. autofunction:: tearDown .. autofunction:: testConfig(registry=None, request=None, hook_zca=True, autocommit=True, settings=None) .. autofunction:: cleanUp .. autoclass:: DummyResource :members: .. autoclass:: DummyRequest :members: .. autoclass:: DummyTemplateRenderer :members: pyramid-1.6/docs/api/threadlocal.rst0000644000076500000240000000032312234375161020271 0ustar michaelstaff00000000000000.. _threadlocal_module: :mod:`pyramid.threadlocal` ------------------------------- .. automodule:: pyramid.threadlocal .. autofunction:: get_current_request() .. autofunction:: get_current_registry() pyramid-1.6/docs/api/traversal.rst0000644000076500000240000000066412234375161020022 0ustar michaelstaff00000000000000.. _traversal_module: :mod:`pyramid.traversal` --------------------------- .. automodule:: pyramid.traversal .. autofunction:: find_interface .. autofunction:: find_resource .. autofunction:: find_root .. autofunction:: resource_path .. autofunction:: resource_path_tuple .. autofunction:: quote_path_segment .. autofunction:: virtual_root .. autofunction:: traverse .. autofunction:: traversal_path(path) pyramid-1.6/docs/api/tweens.rst0000644000076500000240000000127412234375161017322 0ustar michaelstaff00000000000000.. _tweens_module: :mod:`pyramid.tweens` --------------------- .. automodule:: pyramid.tweens .. autofunction:: excview_tween_factory .. attribute:: MAIN Constant representing the main Pyramid handling function, for use in ``under`` and ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`. .. attribute:: INGRESS Constant representing the request ingress, for use in ``under`` and ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`. .. attribute:: EXCVIEW Constant representing the exception view tween, for use in ``under`` and ``over`` arguments to :meth:`pyramid.config.Configurator.add_tween`. pyramid-1.6/docs/api/url.rst0000644000076500000240000000065212234375161016616 0ustar michaelstaff00000000000000.. _url_module: :mod:`pyramid.url` --------------------- .. automodule:: pyramid.url .. autofunction:: pyramid.url.resource_url(context, request, *elements, query=None, anchor=None) .. autofunction:: route_url .. autofunction:: current_route_url .. autofunction:: route_path .. autofunction:: current_route_path .. autofunction:: static_url .. autofunction:: static_path .. autofunction:: urlencode pyramid-1.6/docs/api/view.rst0000644000076500000240000000064112520062551016756 0ustar michaelstaff00000000000000.. _view_module: :mod:`pyramid.view` ---------------------- .. automodule:: pyramid.view .. autofunction:: render_view_to_response .. autofunction:: render_view_to_iterable .. autofunction:: render_view .. autoclass:: view_config :members: .. autoclass:: view_defaults :members: .. autoclass:: notfound_view_config :members: .. autoclass:: forbidden_view_config :members: pyramid-1.6/docs/api/wsgi.rst0000644000076500000240000000023212234375161016757 0ustar michaelstaff00000000000000.. _wsgi_module: :mod:`pyramid.wsgi` -------------------------- .. automodule:: pyramid.wsgi .. autofunction:: wsgiapp .. autofunction:: wsgiapp2 pyramid-1.6/docs/authorintro.rst0000644000076500000240000001617512520062551017622 0ustar michaelstaff00000000000000===================== Author Introduction ===================== Welcome to "The :app:`Pyramid` Web Framework". In this introduction, I'll describe the audience for this book, I'll describe the book content, I'll provide some context regarding the genesis of :app:`Pyramid`, and I'll thank some important people. I hope you enjoy both this book and the software it documents. I've had a blast writing both. .. index:: single: book audience Audience ======== This book is aimed primarily at a reader that has the following attributes: - At least a moderate amount of :term:`Python` experience. - A familiarity with web protocols such as HTTP and CGI. If you fit into both of these categories, you're in the direct target audience for this book. But don't worry, even if you have no experience with Python or the web, both are easy to pick up "on the fly". Python is an *excellent* language in which to write applications; becoming productive in Python is almost mind-blowingly easy. If you already have experience in another language such as Java, Visual Basic, Perl, Ruby, or even C/C++, learning Python will be a snap; it should take you no longer than a couple of days to become modestly productive. If you don't have previous programming experience, it will be slightly harder, and it will take a little longer, but you'd be hard-pressed to find a better "first language." Web technology familiarity is assumed in various places within the book. For example, the book doesn't try to define common web-related concepts like "URL" or "query string." Likewise, the book describes various interactions in terms of the HTTP protocol, but it does not describe how the HTTP protocol works in detail. Like any good web framework, though, :app:`Pyramid` shields you from needing to know most of the gory details of web protocols and low-level data structures. As a result, you can usually avoid becoming "blocked" while you read this book even if you don't yet deeply understand web technologies. .. index:: single: book content overview Book Content ============ This book is divided into three major parts: :ref:`narrative_documentation` This is documentation which describes :app:`Pyramid` concepts in narrative form, written in a largely conversational tone. Each narrative documentation chapter describes an isolated :app:`Pyramid` concept. You should be able to get useful information out of the narrative chapters if you read them out-of-order, or when you need only a reminder about a particular topic while you're developing an application. :ref:`tutorials` Each tutorial builds a sample application or implements a set of concepts with a sample; it then describes the application or concepts in terms of the sample. You should read the tutorials if you want a guided tour of :app:`Pyramid`. :ref:`api_documentation` Comprehensive reference material for every public API exposed by :app:`Pyramid`. The API documentation is organized alphabetically by module name. .. index:: single: repoze.zope2 single: Zope 3 single: Zope 2 single: repoze.bfg genesis single: pyramid genesis The Genesis of :mod:`repoze.bfg` ================================ Before the end of 2010, :app:`Pyramid` was known as :mod:`repoze.bfg`. I wrote :mod:`repoze.bfg` after many years of writing applications using :term:`Zope`. Zope provided me with a lot of mileage: it wasn't until almost a decade of successfully creating applications using it that I decided to write a different web framework. Although :mod:`repoze.bfg` takes inspiration from a variety of web frameworks, it owes more of its core design to Zope than any other. The Repoze "brand" existed before :mod:`repoze.bfg` was created. One of the first packages developed as part of the Repoze brand was a package named :mod:`repoze.zope2`. This was a package that allowed Zope 2 applications to run under a :term:`WSGI` server without modification. Zope 2 did not have reasonable WSGI support at the time. During the development of the :mod:`repoze.zope2` package, I found that replicating the Zope 2 "publisher" -- the machinery that maps URLs to code -- was time-consuming and fiddly. Zope 2 had evolved over many years, and emulating all of its edge cases was extremely difficult. I finished the :mod:`repoze.zope2` package, and it emulates the normal Zope 2 publisher pretty well. But during its development, it became clear that Zope 2 had simply begun to exceed my tolerance for complexity, and I began to look around for simpler options. I considered using the Zope 3 application server machinery, but it turned out that it had become more indirect than the Zope 2 machinery it aimed to replace, which didn't fulfill the goal of simplification. I also considered using Django and Pylons, but neither of those frameworks offer much along the axes of traversal, contextual declarative security, or application extensibility; these were features I had become accustomed to as a Zope developer. I decided that in the long term, creating a simpler framework that retained features I had become accustomed to when developing Zope applications was a more reasonable idea than continuing to use any Zope publisher or living with the limitations and unfamiliarities of a different framework. The result is what is now :app:`Pyramid`. The Genesis of :app:`Pyramid` ============================= What was :mod:`repoze.bfg` has become :app:`Pyramid` as the result of a coalition built between the :term:`Repoze` and :term:`Pylons` community throughout the year 2010. By merging technology, we're able to reduce duplication of effort, and take advantage of more of each others' technology. .. index:: single: Bicking, Ian single: Everitt, Paul single: Seaver, Tres single: Sawyers, Andrew single: Borch, Malthe single: de la Guardia, Carlos single: Brandl, Georg single: Oram, Simon single: Hardwick, Nat single: Fulton, Jim single: Moroz, Tom single: Koym, Todd single: van Rossum, Guido single: Peters, Tim single: Rossi, Chris single: Holth, Daniel single: Hathaway, Shane single: Akkerman, Wichert single: Laflamme, Blaise single: Laflamme, Hugues single: Bangert, Ben single: Duncan, Casey single: Orr, Mike single: Shipman, John single: Beelby, Chris single: Paez, Patricio single: Merickel, Michael Thanks ====== This book is dedicated to my grandmother, who gave me my first typewriter (a Royal), and my mother, who bought me my first computer (a VIC-20). Thanks to the following people for providing expertise, resources, and software. Without the help of these folks, neither this book nor the software which it details would exist: Paul Everitt, Tres Seaver, Andrew Sawyers, Malthe Borch, Carlos de la Guardia, Chris Rossi, Shane Hathaway, Daniel Holth, Wichert Akkerman, Georg Brandl, Blaise Laflamme, Ben Bangert, Casey Duncan, Hugues Laflamme, Mike Orr, John Shipman, Chris Beelby, Patricio Paez, Simon Oram, Nat Hardwick, Ian Bicking, Jim Fulton, Michael Merickel, Tom Moroz of the Open Society Institute, and Todd Koym of Environmental Health Sciences. Thanks to Guido van Rossum and Tim Peters for Python. Special thanks to Tricia for putting up with me. pyramid-1.6/docs/changes.rst0000644000076500000240000000044512517346416016660 0ustar michaelstaff00000000000000.. _changelog: :app:`Pyramid` Change History ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: ../CHANGES.txt .. include:: ../HISTORY.txt :mod:`repoze.bfg` Change History (previous name for Pyramid) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. include:: ../BFG_HISTORY.txt pyramid-1.6/docs/conf.py0000644000076500000240000003257012642137120016006 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- # # pyramid documentation build configuration file, created by # sphinx-quickstart on Wed Jul 16 13:18:14 2008. # # This file is execfile()d with the current directory set to its containing dir. # # The contents of this file are pickled, so don't put values in the namespace # that aren't pickleable (module imports are okay, they're removed automatically). # # All configuration values have a default value; values that are commented out # serve to show the default value. import sys import os import datetime import inspect import warnings warnings.simplefilter('ignore', DeprecationWarning) import pkg_resources import pylons_sphinx_themes # skip raw nodes from sphinx.writers.text import TextTranslator from sphinx.writers.latex import LaTeXTranslator from docutils import nodes from docutils import utils def raw(*arg): raise nodes.SkipNode TextTranslator.visit_raw = raw # make sure :app:`Pyramid` doesn't mess up LaTeX rendering def nothing(*arg): pass LaTeXTranslator.visit_inline = nothing LaTeXTranslator.depart_inline = nothing book = os.environ.get('BOOK') # General configuration # --------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'repoze.sphinx.autointerface', 'sphinx.ext.viewcode', 'sphinx.ext.intersphinx', 'sphinxcontrib.programoutput', ] # Looks for objects in external projects intersphinx_mapping = { 'colander': ('http://docs.pylonsproject.org/projects/colander/en/latest', None), 'cookbook': ('http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/', None), 'deform': ('http://docs.pylonsproject.org/projects/deform/en/latest', None), 'jinja2': ('http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/', None), 'pylonswebframework': ('http://docs.pylonsproject.org/projects/pylons-webframework/en/latest/', None), 'python': ('http://docs.python.org', None), 'python3': ('http://docs.python.org/3', None), 'sqla': ('http://docs.sqlalchemy.org/en/latest', None), 'tm': ('http://docs.pylonsproject.org/projects/pyramid_tm/en/latest/', None), 'toolbar': ('http://docs.pylonsproject.org/projects/pyramid-debugtoolbar/en/latest', None), 'tstring': ('http://docs.pylonsproject.org/projects/translationstring/en/latest', None), 'tutorials': ('http://docs.pylonsproject.org/projects/pyramid-tutorials/en/latest/', None), 'venusian': ('http://docs.pylonsproject.org/projects/venusian/en/latest', None), 'webob': ('http://docs.webob.org/en/latest', None), 'webtest': ('http://webtest.pythonpaste.org/en/latest', None), 'who': ('http://repozewho.readthedocs.org/en/latest', None), 'zcml': ('http://docs.pylonsproject.org/projects/pyramid-zcml/en/latest', None), 'zcomponent': ('http://docs.zope.org/zope.component', None), } # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General substitutions. project = 'The Pyramid Web Framework' thisyear = datetime.datetime.now().year copyright = '2008-%s, Agendaless Consulting' % thisyear # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. # # The short X.Y version. version = pkg_resources.get_distribution('pyramid').version # The full version, including alpha/beta/rc tags. release = version # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_themes/README.rst', ] # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # The name of the Pygments (syntax highlighting) style to use. #pygments_style = book and 'bw' or 'tango' if book: pygments_style = 'bw' # Options for HTML output # ----------------------- # Add and use Pylons theme html_theme = 'pyramid' html_theme_path = pylons_sphinx_themes.get_html_themes_path() html_theme_options = dict( github_url='https://github.com/Pylons/pyramid', in_progress='false', ) # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = 'The Pyramid Web Framework v%s' % release # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. html_use_smartypants = False # people use cutnpaste in some places # Output file base name for HTML help builder. htmlhelp_basename = 'pyramid' # Options for LaTeX output # ------------------------ # The paper size ('letter' or 'a4'). latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). latex_font_size = '10pt' latex_additional_files = ['_static/latex-note.png', '_static/latex-warning.png'] # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, document class [howto/manual]). latex_documents = [ ('latexindex', 'pyramid.tex', 'The Pyramid Web Framework', 'Chris McDonough', 'manual'), ] # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. latex_use_parts = True # If false, no module index is generated. latex_use_modindex = False ## Say, for a moment that you have a twoside document that needs a 3cm ## inner margin to allow for binding and at least two centimetres the ## rest of the way around. You've been using the a4wide package up until ## now, because you like the amount of text it places on the ## page. Perhaps try something like this in your preamble: ## \usepackage[bindingoffset=1cm,textheight=22cm,hdivide={2cm,*,2cm},vdivide={*,22cm,*}]{geometry} ## _PREAMBLE = r"""\usepackage[bindingoffset=0.45in,textheight=7.25in,hdivide={0.5in,*,0.75in},vdivide={1in,7.25in,1in},papersize={7.5in,9.25in}]{geometry}""" _PREAMBLE = r""" \usepackage[]{geometry} \geometry{bindingoffset=0.45in,textheight=7.25in,hdivide={0.5in,*,0.75in},vdivide={1in,7.25in,1in},papersize={7.5in,9.25in}} \hypersetup{ colorlinks=true, linkcolor=black, citecolor=black, filecolor=black, urlcolor=black } \fvset{frame=single,xleftmargin=9pt,numbersep=4pt} \pagestyle{fancy} % header and footer styles \renewcommand{\chaptermark}[1]% {\markboth{\MakeUppercase{\thechapter.\ #1}}{} } \renewcommand{\sectionmark}[1]% {\markright{\MakeUppercase{\thesection.\ #1}} } % defaults for fancy style \renewcommand{\headrulewidth}{0pt} \renewcommand{\footrulewidth}{0pt} \fancyhf{} \fancyfoot[C]{\thepage} % plain style \fancypagestyle{plain}{ \renewcommand{\headrulewidth}{0pt} % ho header line \renewcommand{\footrulewidth}{0pt}% no footer line \fancyhf{} % empty header and footer \fancyfoot[C]{\thepage} } % title page styles \makeatletter \def\@subtitle{\relax} \newcommand{\subtitle}[1]{\gdef\@subtitle{#1}} \renewcommand{\maketitle}{ \begin{titlepage} {\rm\Huge\@title\par} {\em\large\py@release\releaseinfo\par} \if\@subtitle\relax\else\large\@subtitle\par\fi {\large\@author\par} \end{titlepage} } \makeatother % Redefine link and title colors \definecolor{TitleColor}{rgb}{0,0,0} \definecolor{InnerLinkColor}{rgb}{0.208,0.374,0.486} \definecolor{OuterLinkColor}{rgb}{0.216,0.439,0.388} % Redefine these colors to something not white if you want to have colored % background and border for code examples. \definecolor{VerbatimColor}{rgb}{1,1,1} \definecolor{VerbatimBorderColor}{rgb}{1,1,1} \makeatletter \renewcommand{\py@noticestart@warning}{\py@heavybox} \renewcommand{\py@noticeend@warning}{\py@endheavybox} \renewcommand{\py@noticestart@note}{\py@heavybox} \renewcommand{\py@noticeend@note}{\py@endheavybox} \makeatother % icons in note and warning boxes \usepackage{ifthen} % Keep a copy of the original notice environment \let\origbeginnotice\notice \let\origendnotice\endnotice % Redefine the notice environment so we can add our own code to it \renewenvironment{notice}[2]{% \origbeginnotice{#1}{}% equivalent to original \begin{notice}{#1}{#2} % load graphics \ifthenelse{\equal{#1}{warning}}{\includegraphics{latex-warning.png}}{} \ifthenelse{\equal{#1}{note}}{\includegraphics{latex-note.png}}{} % etc. }{% \origendnotice% equivalent to original \end{notice} } % try to prevent code-block boxes from splitting across pages \sloppy \widowpenalty=300 \clubpenalty=300 \setlength{\parskip}{3ex plus 2ex minus 2ex} % suppress page numbers on pages showing part title \makeatletter \let\sv@endpart\@endpart \def\@endpart{\thispagestyle{empty}\sv@endpart} \makeatother % prevent page numbers in TOC (reset to fancy by frontmatter directive) \pagestyle{empty} """ latex_elements = { 'preamble': _PREAMBLE, 'wrapperclass': 'book', 'date': '', 'releasename': 'Version', 'title': r'The Pyramid Web Framework', # 'pointsize':'12pt', # uncomment for 12pt version } # secnumdepth counter reset to 2 causes numbering in related matter; # reset to -1 causes chapters to not be numbered, reset to -2 causes # parts to not be numbered. #part -1 #chapter 0 #section 1 #subsection 2 #subsubsection 3 #paragraph 4 #subparagraph 5 def frontmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return [nodes.raw( '', r""" \frontmatter % prevent part/chapter/section numbering \setcounter{secnumdepth}{-2} % suppress headers \pagestyle{plain} % reset page counter \setcounter{page}{1} % suppress first toc pagenum \addtocontents{toc}{\protect\thispagestyle{empty}} """, format='latex')] def mainmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return [nodes.raw( '', r""" \mainmatter % allow part/chapter/section numbering \setcounter{secnumdepth}{2} % get headers back \pagestyle{fancy} \fancyhf{} \renewcommand{\headrulewidth}{0.5pt} \renewcommand{\footrulewidth}{0pt} \fancyfoot[C]{\thepage} \fancyhead[RO]{\rightmark} \fancyhead[LE]{\leftmark} """, format='latex')] def backmatter(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return [nodes.raw('', '\\backmatter\n\\setcounter{secnumdepth}{-1}\n', format='latex')] def app_role(role, rawtext, text, lineno, inliner, options={}, content=[]): """custom role for :app: marker, does nothing in particular except allow :app:`Pyramid` to work (for later search and replace).""" if 'class' in options: assert 'classes' not in options options['classes'] = options['class'] del options['class'] return [nodes.inline(rawtext, utils.unescape(text), **options)], [] def setup(app): app.add_role('app', app_role) app.add_directive('frontmatter', frontmatter, 1, (0, 0, 0)) app.add_directive('mainmatter', mainmatter, 1, (0, 0, 0)) app.add_directive('backmatter', backmatter, 1, (0, 0, 0)) app.connect('autodoc-process-signature', resig) def resig(app, what, name, obj, options, signature, return_annotation): """ Allow for preservation of ``@action_method`` decorated methods in configurator """ docobj = getattr(obj, '__docobj__', None) if docobj is not None: argspec = inspect.getargspec(docobj) if argspec[0] and argspec[0][0] in ('cls', 'self'): del argspec[0][0] signature = inspect.formatargspec(*argspec) return signature, return_annotation # turn off all line numbers in latex formatting ## from pygments.formatters import LatexFormatter ## from sphinx.highlighting import PygmentsBridge ## class NoLinenosLatexFormatter(LatexFormatter): ## def __init__(self, **options): ## LatexFormatter.__init__(self, **options) ## self.linenos = False ## PygmentsBridge.latex_formatter = NoLinenosLatexFormatter # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = 'The Pyramid Web Framework, Version %s' \ % release epub_author = 'Chris McDonough' epub_publisher = 'Agendaless Consulting' epub_copyright = '2008-%d' % thisyear # The language of the text. It defaults to the language option # or en if the language is not set. epub_language = 'en' # The scheme of the identifier. Typical schemes are ISBN or URL. epub_scheme = 'ISBN' # The unique identifier of the text. This can be a ISBN number # or the project homepage. epub_identifier = '0615445675' # A unique identification for the text. epub_uid = 'The Pyramid Web Framework, Version %s' \ % release # A list of files that should not be packed into the epub file. epub_exclude_files = ['_static/opensearch.xml', '_static/doctools.js', '_static/jquery.js', '_static/searchtools.js', '_static/underscore.js', '_static/basic.css', 'search.html', '_static/websupport.js'] # The depth of the table of contents in toc.ncx. epub_tocdepth = 3 # For a list of all settings, visit http://sphinx-doc.org/config.html pyramid-1.6/docs/conventions.rst0000644000076500000240000000564212606630333017612 0ustar michaelstaff00000000000000Typographical Conventions ========================= Literals, filenames, and function arguments are presented using the following style: ``argument1`` Warnings which represent limitations and need-to-know information related to a topic or concept are presented in the following style: .. warning:: This is a warning. Notes which represent additional information related to a topic or concept are presented in the following style: .. note:: This is a note. We present Python method names using the following style: :meth:`pyramid.config.Configurator.add_view` We present Python class names, module names, attributes, and global variables using the following style: :class:`pyramid.config.Configurator.registry` References to glossary terms are presented using the following style: :term:`Pylons` URLs are presented using the following style: `Pylons `_ References to sections and chapters are presented using the following style: :ref:`traversal_chapter` Code and configuration file blocks are presented in the following style: .. code-block:: python :linenos: def foo(abc): pass Example blocks representing UNIX shell commands are prefixed with a ``$`` character, e.g.: .. code-block:: text $ $VENV/bin/nosetests (See :term:`virtualenv` for the meaning of ``$VENV``) Example blocks representing Windows ``cmd.exe`` commands are prefixed with a drive letter and/or a directory name, e.g.: .. code-block:: text c:\examples> %VENV%\Scripts\nosetests (See :term:`virtualenv` for the meaning of ``%VENV%``) Sometimes, when it's unknown which directory is current, Windows ``cmd.exe`` example block commands are prefixed only with a ``>`` character, e.g.: .. code-block:: text > %VENV%\Scripts\nosetests When a command that should be typed on one line is too long to fit on a page, the backslash ``\`` is used to indicate that the following printed line should actually be part of the command: .. code-block:: text c:\bigfntut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \ --cover-erase --with-coverage A sidebar, which presents a concept tangentially related to content discussed on a page, is rendered like so: .. sidebar:: This is a sidebar Sidebar information. When multiple objects are imported from the same package, the following convention is used: .. code-block:: python from foo import ( bar, baz, ) It may look unusual, but it has advantages: * It allows one to swap out the higher-level package ``foo`` for something else that provides the similar API. An example would be swapping out one database for another (e.g., graduating from SQLite to PostgreSQL). * Looks more neat in cases where a large number of objects get imported from that package. * Adding or removing imported objects from the package is quicker and results in simpler diffs. pyramid-1.6/docs/convert_images.sh0000755000076500000240000000051212234375161020050 0ustar michaelstaff00000000000000TEXDIR=_build/latex if test ! -z $BOOK; then for img in $TEXDIR/*.png; do cp $img ${img}.BAK convert $img -units PixelsPerInch -resample 300 -colorspace Gray ${img}.grey #convert -strip -density 300 ${img} -units PixelsPerInch -resample 300 -colorspace Gray ${img}.grey mv ${img}.grey $img done fi pyramid-1.6/docs/copyright.rst0000644000076500000240000000746712575217552017275 0ustar michaelstaff00000000000000Copyright, Trademarks, and Attributions ======================================= *The Pyramid Web Framework, Version 1.1* by Chris McDonough .. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN Copyright |copy| 2008-2011, Agendaless Consulting. ISBN-10: 0615445675 ISBN-13: 978-0615445670 First print publishing: February, 2011 All rights reserved. This material may be copied or distributed only subject to the terms and conditions set forth in the `Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License `_. You must give the original author credit. You may not use this work for commercial purposes. If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one. .. note:: While the :app:`Pyramid` documentation is offered under the Creative Commons Attribution-Nonconmmercial-Share Alike 3.0 United States License, the :app:`Pyramid` *software* is offered under a `less restrictive (BSD-like) license `_ . All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. However, use of a term in this book should not be regarded as affecting the validity of any trademark or service mark. Every effort has been made to make this book as complete and as accurate as possible, but no warranty or fitness is implied. The information provided is on an "as-is" basis. The author and the publisher shall have neither liability nor responsibility to any person or entity with respect to any loss or damages arising from the information contained in this book. No patent liability is assumed with respect to the use of the information contained herein. Attributions ------------ Editor: Casey Duncan Contributors: Ben Bangert, Blaise Laflamme, Rob Miller, Mike Orr, Carlos de la Guardia, Paul Everitt, Tres Seaver, John Shipman, Marius Gedminas, Chris Rossi, Joachim Krebs, Xavier Spriet, Reed O'Brien, William Chambers, Charlie Choiniere, Jamaludin Ahmad, Graham Higgins, Patricio Paez, Michael Merickel, Eric Ongerth, Niall O'Higgins, Christoph Zwerschke, John Anderson, Atsushi Odagiri, Kirk Strauser, JD Navarro, Joe Dallago, Savoir-Faire Linux, Åukasz Fidosz, Christopher Lambacher, Claus Conrad, Chris Beelby, Phil Jenvey and a number of people with only pseudonyms on GitHub. Cover Designer: Hugues Laflamme of `Kemeneur `_. Used with permission: The :ref:`webob_chapter` chapter is adapted, with permission, from documentation originally written by Ian Bicking. The :ref:`much_ado_about_traversal_chapter` chapter is adapted, with permission, from an article written by Rob Miller. The :ref:`logging_chapter` is adapted, with permission, from the Pylons documentation logging chapter, originally written by Phil Jenvey. Print Production ---------------- The print version of this book was produced using the `Sphinx `_ documentation generation system and the `LaTeX `_ typesetting system. Contacting The Publisher ------------------------ Please send documentation licensing inquiries, translation inquiries, and other business communications to `Agendaless Consulting `_. Please send software and other technical queries to the `Pylons-devel mailing list `_. HTML Version and Source Code ---------------------------- An HTML version of this book is freely available via http://docs.pylonsproject.org/projects/pyramid/en/latest/ The source code for the examples used in this book are available within the :app:`Pyramid` software distribution, always available via https://github.com/Pylons/pyramid pyramid-1.6/docs/coversizing.py0000644000076500000240000000135612234375161017427 0ustar michaelstaff00000000000000# see https://www.createspace.com/Products/Book/#content4 # https://www.createspace.com/Help/Index.jsp?orgId=00D300000001Sh9&id=50170000000I7be page_count = 600 bleed = .125 spine_width = .002252 * page_count trim_width = 7.5 trim_height = 9.25 min_cover_width = bleed + trim_width + spine_width + trim_width + bleed min_cover_height = bleed + trim_height + bleed print "spine width ", spine_width, "inches" print "min cover width ", min_cover_width, "inches" print "min cover height ", min_cover_height, "inches" print "barcode placeholder width: 2 inches" print "barcode placeholder height: 1.2 inches" print "bottom of barcode must be .25 inches from bottom trim line of cover" print "right side of barcode must be .25 inches to left of spine" pyramid-1.6/docs/designdefense.rst0000644000076500000240000024525612642137120020053 0ustar michaelstaff00000000000000.. _design_defense: Defending Pyramid's Design ========================== From time to time, challenges to various aspects of :app:`Pyramid` design are lodged. To give context to discussions that follow, we detail some of the design decisions and trade-offs here. In some cases, we acknowledge that the framework can be made better and we describe future steps which will be taken to improve it; in some cases we just file the challenge as noted, as obviously you can't please everyone all of the time. Pyramid Provides More Than One Way to Do It ------------------------------------------- A canon of Python popular culture is "TIOOWTDI" ("there is only one way to do it", a slighting, tongue-in-cheek reference to Perl's "TIMTOWTDI", which is an acronym for "there is more than one way to do it"). :app:`Pyramid` is, for better or worse, a "TIMTOWTDI" system. For example, it includes more than one way to resolve a URL to a :term:`view callable`: via :term:`url dispatch` or :term:`traversal`. Multiple methods of configuration exist: :term:`imperative configuration`, :term:`configuration decoration`, and :term:`ZCML` (optionally via :term:`pyramid_zcml`). It works with multiple different kinds of persistence and templating systems. And so on. However, the existence of most of these overlapping ways to do things are not without reason and purpose: we have a number of audiences to serve, and we believe that TIMTOWTI at the web framework level actually *prevents* a much more insidious and harmful set of duplication at higher levels in the Python web community. :app:`Pyramid` began its life as :mod:`repoze.bfg`, written by a team of people with many years of prior :term:`Zope` experience. The idea of :term:`traversal` and the way :term:`view lookup` works was stolen entirely from Zope. The authorization subsystem provided by :app:`Pyramid` is a derivative of Zope's. The idea that an application can be *extended* without forking is also a Zope derivative. Implementations of these features were *required* to allow the :app:`Pyramid` authors to build the bread-and-butter CMS-type systems for customers in the way in which they were accustomed. No other system, save for Zope itself, had such features, and Zope itself was beginning to show signs of its age. We were becoming hampered by consequences of its early design mistakes. Zope's lack of documentation was also difficult to work around: it was hard to hire smart people to work on Zope applications, because there was no comprehensive documentation set to point them at which explained "it all" in one consumable place, and it was too large and self-inconsistent to document properly. Before :mod:`repoze.bfg` went under development, its authors obviously looked around for other frameworks that fit the bill. But no non-Zope framework did. So we embarked on building :mod:`repoze.bfg`. As the result of our research, however, it became apparent that, despite the fact that no *one* framework had all the features we required, lots of existing frameworks had good, and sometimes very compelling ideas. In particular, :term:`URL dispatch` is a more direct mechanism to map URLs to code. So, although we couldn't find a framework, save for Zope, that fit our needs, and while we incorporated a lot of Zope ideas into BFG, we also emulated the features we found compelling in other frameworks (such as :term:`url dispatch`). After the initial public release of BFG, as time went on, features were added to support people allergic to various Zope-isms in the system, such as the ability to configure the application using :term:`imperative configuration` and :term:`configuration decoration` rather than solely using :term:`ZCML`, and the elimination of the required use of :term:`interface` objects. It soon became clear that we had a system that was very generic, and was beginning to appeal to non-Zope users as well as ex-Zope users. As the result of this generalization, it became obvious BFG shared 90% of its featureset with the featureset of Pylons 1, and thus had a very similar target market. Because they were so similar, choosing between the two systems was an exercise in frustration for an otherwise non-partisan developer. It was also strange for the Pylons and BFG development communities to be in competition for the same set of users, given how similar the two frameworks were. So the Pylons and BFG teams began to work together to form a plan to merge. The features missing from BFG (notably :term:`view handler` classes, flash messaging, and other minor missing bits), were added, to provide familiarity to ex-Pylons users. The result is :app:`Pyramid`. The Python web framework space is currently notoriously balkanized. We're truly hoping that the amalgamation of components in :app:`Pyramid` will appeal to at least two currently very distinct sets of users: Pylons and BFG users. By unifying the best concepts from Pylons and BFG into a single codebase and leaving the bad concepts from their ancestors behind, we'll be able to consolidate our efforts better, share more code, and promote our efforts as a unit rather than competing pointlessly. We hope to be able to shortcut the pack mentality which results in a *much larger* duplication of effort, represented by competing but incredibly similar applications and libraries, each built upon a specific low level stack that is incompatible with the other. We'll also shrink the choice of credible Python web frameworks down by at least one. We're also hoping to attract users from other communities (such as Zope's and TurboGears') by providing the features they require, while allowing enough flexibility to do things in a familiar fashion. Some overlap of functionality to achieve these goals is expected and unavoidable, at least if we aim to prevent pointless duplication at higher levels. If we've done our job well enough, the various audiences will be able to coexist and cooperate rather than firing at each other across some imaginary web framework DMZ. Pyramid Uses A Zope Component Architecture ("ZCA") Registry ----------------------------------------------------------- :app:`Pyramid` uses a :term:`Zope Component Architecture` (ZCA) "component registry" as its :term:`application registry` under the hood. This is a point of some contention. :app:`Pyramid` is of a :term:`Zope` pedigree, so it was natural for its developers to use a ZCA registry at its inception. However, we understand that using a ZCA registry has issues and consequences, which we've attempted to address as best we can. Here's an introspection about :app:`Pyramid` use of a ZCA registry, and the trade-offs its usage involves. Problems ++++++++ The global API that may be used to access data in a ZCA component registry is not particularly pretty or intuitive, and sometimes it's just plain obtuse. Likewise, the conceptual load on a casual source code reader of code that uses the ZCA global API is somewhat high. Consider a ZCA neophyte reading the code that performs a typical "unnamed utility" lookup using the :func:`zope.component.getUtility` global API: .. code-block:: python :linenos: from pyramid.interfaces import ISettings from zope.component import getUtility settings = getUtility(ISettings) After this code runs, ``settings`` will be a Python dictionary. But it's unlikely that any civilian would know that just by reading the code. There are a number of comprehension issues with the bit of code above that are obvious. First, what's a "utility"? Well, for the purposes of this discussion, and for the purpose of the code above, it's just not very important. If you really want to know, you can read `this `_. However, still, readers of such code need to understand the concept in order to parse it. This is problem number one. Second, what's this ``ISettings`` thing? It's an :term:`interface`. Is that important here? Not really, we're just using it as a key for some lookup based on its identity as a marker: it represents an object that has the dictionary API, but that's not very important in this context. That's problem number two. Third of all, what does the ``getUtility`` function do? It's performing a lookup for the ``ISettings`` "utility" that should return... well, a utility. Note how we've already built up a dependency on the understanding of an :term:`interface` and the concept of "utility" to answer this question: a bad sign so far. Note also that the answer is circular, a *really* bad sign. Fourth, where does ``getUtility`` look to get the data? Well, the "component registry" of course. What's a component registry? Problem number four. Fifth, assuming you buy that there's some magical registry hanging around, where *is* this registry? *Homina homina*... "around"? That's sort of the best answer in this context (a more specific answer would require knowledge of internals). Can there be more than one registry? Yes. So in *which* registry does it find the registration? Well, the "current" registry of course. In terms of :app:`Pyramid`, the current registry is a thread local variable. Using an API that consults a thread local makes understanding how it works non-local. You've now bought in to the fact that there's a registry that is just hanging around. But how does the registry get populated? Why, via code that calls directives like ``config.add_view``. In this particular case, however, the registration of ``ISettings`` is made by the framework itself under the hood: it's not present in any user configuration. This is extremely hard to comprehend. Problem number six. Clearly there's some amount of cognitive load here that needs to be borne by a reader of code that extends the :app:`Pyramid` framework due to its use of the ZCA, even if they are already an expert Python programmer and an expert in the domain of web applications. This is suboptimal. Ameliorations +++++++++++++ First, the primary amelioration: :app:`Pyramid` *does not expect application developers to understand ZCA concepts or any of its APIs*. If an *application* developer needs to understand a ZCA concept or API during the creation of a :app:`Pyramid` application, we've failed on some axis. Instead, the framework hides the presence of the ZCA registry behind special-purpose API functions that *do* use ZCA APIs. Take for example the ``pyramid.security.authenticated_userid`` function, which returns the userid present in the current request or ``None`` if no userid is present in the current request. The application developer calls it like so: .. code-block:: python :linenos: from pyramid.security import authenticated_userid userid = authenticated_userid(request) He now has the current user id. Under its hood however, the implementation of ``authenticated_userid`` is this: .. code-block:: python :linenos: def authenticated_userid(request): """ Return the userid of the currently authenticated user or ``None`` if there is no authentication policy in effect or there is no currently authenticated user. """ registry = request.registry # the ZCA component registry policy = registry.queryUtility(IAuthenticationPolicy) if policy is None: return None return policy.authenticated_userid(request) Using such wrappers, we strive to always hide the ZCA API from application developers. Application developers should just never know about the ZCA API: they should call a Python function with some object germane to the domain as an argument, and it should return a result. A corollary that follows is that any reader of an application that has been written using :app:`Pyramid` needn't understand the ZCA API either. Hiding the ZCA API from application developers and code readers is a form of enhancing domain specificity. No application developer wants to need to understand the small, detailed mechanics of how a web framework does its thing. People want to deal in concepts that are closer to the domain they're working in: for example, web developers want to know about *users*, not *utilities*. :app:`Pyramid` uses the ZCA as an implementation detail, not as a feature which is exposed to end users. However, unlike application developers, *framework developers*, including people who want to override :app:`Pyramid` functionality via preordained framework plugpoints like traversal or view lookup *must* understand the ZCA registry API. :app:`Pyramid` framework developers were so concerned about conceptual load issues of the ZCA registry API for framework developers that a `replacement registry implementation `_ named :mod:`repoze.component` was actually developed. Though this package has a registry implementation which is fully functional and well-tested, and its API is much nicer than the ZCA registry API, work on it was largely abandoned and it is not used in :app:`Pyramid`. We continued to use a ZCA registry within :app:`Pyramid` because it ultimately proved a better fit. .. note:: We continued using ZCA registry rather than disusing it in favor of using the registry implementation in :mod:`repoze.component` largely because the ZCA concept of interfaces provides for use of an interface hierarchy, which is useful in a lot of scenarios (such as context type inheritance). Coming up with a marker type that was something like an interface that allowed for this functionality seemed like it was just reinventing the wheel. Making framework developers and extenders understand the ZCA registry API is a trade-off. We (the :app:`Pyramid` developers) like the features that the ZCA registry gives us, and we have long-ago borne the weight of understanding what it does and how it works. The authors of :app:`Pyramid` understand the ZCA deeply and can read code that uses it as easily as any other code. But we recognize that developers who might want to extend the framework are not as comfortable with the ZCA registry API as the original developers are with it. So, for the purposes of being kind to third-party :app:`Pyramid` framework developers in, we've drawn some lines in the sand. In all core code, We've made use of ZCA global API functions such as ``zope.component.getUtility`` and ``zope.component.getAdapter`` the exception instead of the rule. So instead of: .. code-block:: python :linenos: from pyramid.interfaces import IAuthenticationPolicy from zope.component import getUtility policy = getUtility(IAuthenticationPolicy) :app:`Pyramid` code will usually do: .. code-block:: python :linenos: from pyramid.interfaces import IAuthenticationPolicy from pyramid.threadlocal import get_current_registry registry = get_current_registry() policy = registry.getUtility(IAuthenticationPolicy) While the latter is more verbose, it also arguably makes it more obvious what's going on. All of the :app:`Pyramid` core code uses this pattern rather than the ZCA global API. Rationale +++++++++ Here are the main rationales involved in the :app:`Pyramid` decision to use the ZCA registry: - History. A nontrivial part of the answer to this question is "history". Much of the design of :app:`Pyramid` is stolen directly from :term:`Zope`. Zope uses the ZCA registry to do a number of tricks. :app:`Pyramid` mimics these tricks, and, because the ZCA registry works well for that set of tricks, :app:`Pyramid` uses it for the same purposes. For example, the way that :app:`Pyramid` maps a :term:`request` to a :term:`view callable` using :term:`traversal` is lifted almost entirely from Zope. The ZCA registry plays an important role in the particulars of how this request to view mapping is done. - Features. The ZCA component registry essentially provides what can be considered something like a superdictionary, which allows for more complex lookups than retrieving a value based on a single key. Some of this lookup capability is very useful for end users, such as being able to register a view that is only found when the context is some class of object, or when the context implements some :term:`interface`. - Singularity. There's only one place where "application configuration" lives in a :app:`Pyramid` application: in a component registry. The component registry answers questions made to it by the framework at runtime based on the configuration of *an application*. Note: "an application" is not the same as "a process", multiple independently configured copies of the same :app:`Pyramid` application are capable of running in the same process space. - Composability. A ZCA component registry can be populated imperatively, or there's an existing mechanism to populate a registry via the use of a configuration file (ZCML, via the optional :term:`pyramid_zcml` package). We didn't need to write a frontend from scratch to make use of configuration-file-driven registry population. - Pluggability. Use of the ZCA registry allows for framework extensibility via a well-defined and widely understood plugin architecture. As long as framework developers and extenders understand the ZCA registry, it's possible to extend :app:`Pyramid` almost arbitrarily. For example, it's relatively easy to build a directive that registers several views all at once, allowing app developers to use that directive as a "macro" in code that they write. This is somewhat of a differentiating feature from other (non-Zope) frameworks. - Testability. Judicious use of the ZCA registry in framework code makes testing that code slightly easier. Instead of using monkeypatching or other facilities to register mock objects for testing, we inject dependencies via ZCA registrations and then use lookups in the code find our mock objects. - Speed. The ZCA registry is very fast for a specific set of complex lookup scenarios that :app:`Pyramid` uses, having been optimized through the years for just these purposes. The ZCA registry contains optional C code for this purpose which demonstrably has no (or very few) bugs. - Ecosystem. Many existing Zope packages can be used in :app:`Pyramid` with few (or no) changes due to our use of the ZCA registry. Conclusion ++++++++++ If you only *develop applications* using :app:`Pyramid`, there's not much to complain about here. You just should never need to understand the ZCA registry API: use documented :app:`Pyramid` APIs instead. However, you may be an application developer who doesn't read API documentation because it's unmanly. Instead you read the raw source code, and because you haven't read the documentation, you don't know what functions, classes, and methods even *form* the :app:`Pyramid` API. As a result, you've now written code that uses internals and you've painted yourself into a conceptual corner as a result of needing to wrestle with some ZCA-using implementation detail. If this is you, it's extremely hard to have a lot of sympathy for you. You'll either need to get familiar with how we're using the ZCA registry or you'll need to use only the documented APIs; that's why we document them as APIs. If you *extend* or *develop* :app:`Pyramid` (create new directives, use some of the more obscure hooks as described in :ref:`hooks_chapter`, or work on the :app:`Pyramid` core code), you will be faced with needing to understand at least some ZCA concepts. In some places it's used unabashedly, and will be forever. We know it's quirky, but it's also useful and fundamentally understandable if you take the time to do some reading about it. .. _zcml_encouragement: Pyramid "Encourages Use of ZCML" -------------------------------- :term:`ZCML` is a configuration language that can be used to configure the :term:`Zope Component Architecture` registry that :app:`Pyramid` uses for application configuration. Often people claim that Pyramid "needs ZCML". It doesn't. In :app:`Pyramid` 1.0, ZCML doesn't ship as part of the core; instead it ships in the :term:`pyramid_zcml` add-on package, which is completely optional. No ZCML is required at all to use :app:`Pyramid`, nor any other sort of frameworky declarative frontend to application configuration. Pyramid Does Traversal, And I Don't Like Traversal -------------------------------------------------- In :app:`Pyramid`, :term:`traversal` is the act of resolving a URL path to a :term:`resource` object in a resource tree. Some people are uncomfortable with this notion, and believe it is wrong. Thankfully, if you use :app:`Pyramid`, and you don't want to model your application in terms of a resource tree, you needn't use it at all. Instead, use :term:`URL dispatch` to map URL paths to views. The idea that some folks believe traversal is unilaterally wrong is understandable. The people who believe it is wrong almost invariably have all of their data in a relational database. Relational databases aren't naturally hierarchical, so traversing one like a tree is not possible. However, folks who deem traversal unilaterally wrong are neglecting to take into account that many persistence mechanisms *are* hierarchical. Examples include a filesystem, an LDAP database, a :term:`ZODB` (or another type of graph) database, an XML document, and the Python module namespace. It is often convenient to model the frontend to a hierarchical data store as a graph, using traversal to apply views to objects that either *are* the resources in the tree being traversed (such as in the case of ZODB) or at least ones which stand in for them (such as in the case of wrappers for files from the filesystem). Also, many website structures are naturally hierarchical, even if the data which drives them isn't. For example, newspaper websites are often extremely hierarchical: sections within sections within sections, ad infinitum. If you want your URLs to indicate this structure, and the structure is indefinite (the number of nested sections can be "N" instead of some fixed number), a resource tree is an excellent way to model this, even if the backend is a relational database. In this situation, the resource tree is just a site structure. Traversal also offers better composability of applications than URL dispatch, because it doesn't rely on a fixed ordering of URL matching. You can compose a set of disparate functionality (and add to it later) around a mapping of view to resource more predictably than trying to get the right ordering of URL pattern matching. But the point is ultimately moot. If you don't want to use traversal, you needn't. Use URL dispatch instead. Pyramid Does URL Dispatch, And I Don't Like URL Dispatch -------------------------------------------------------- In :app:`Pyramid`, :term:`url dispatch` is the act of resolving a URL path to a :term:`view` callable by performing pattern matching against some set of ordered route definitions. The route definitions are examined in order: the first pattern which matches is used to associate the URL with a view callable. Some people are uncomfortable with this notion, and believe it is wrong. These are usually people who are steeped deeply in :term:`Zope`. Zope does not provide any mechanism except :term:`traversal` to map code to URLs. This is mainly because Zope effectively requires use of :term:`ZODB`, which is a hierarchical object store. Zope also supports relational databases, but typically the code that calls into the database lives somewhere in the ZODB object graph (or at least is a :term:`view` related to a node in the object graph), and traversal is required to reach this code. I'll argue that URL dispatch is ultimately useful, even if you want to use traversal as well. You can actually *combine* URL dispatch and traversal in :app:`Pyramid` (see :ref:`hybrid_chapter`). One example of such a usage: if you want to emulate something like Zope 2's "Zope Management Interface" UI on top of your object graph (or any administrative interface), you can register a route like ``config.add_route('manage', '/manage/*traverse')`` and then associate "management" views in your code by using the ``route_name`` argument to a ``view`` configuration, e.g. ``config.add_view('.some.callable', context=".some.Resource", route_name='manage')``. If you wire things up this way someone then walks up to for example, ``/manage/ob1/ob2``, they might be presented with a management interface, but walking up to ``/ob1/ob2`` would present them with the default object view. There are other tricks you can pull in these hybrid configurations if you're clever (and maybe masochistic) too. Also, if you are a URL dispatch hater, if you should ever be asked to write an application that must use some legacy relational database structure, you might find that using URL dispatch comes in handy for one-off associations between views and URL paths. Sometimes it's just pointless to add a node to the object graph that effectively represents the entry point for some bit of code. You can just use a route and be done with it. If a route matches, a view associated with the route will be called; if no route matches, :app:`Pyramid` falls back to using traversal. But the point is ultimately moot. If you use :app:`Pyramid`, and you really don't want to use URL dispatch, you needn't use it at all. Instead, use :term:`traversal` exclusively to map URL paths to views, just like you do in :term:`Zope`. Pyramid Views Do Not Accept Arbitrary Keyword Arguments ------------------------------------------------------- Many web frameworks (Zope, TurboGears, Pylons 1.X, Django) allow for their variant of a :term:`view callable` to accept arbitrary keyword or positional arguments, which are filled in using values present in the ``request.POST`` or ``request.GET`` dictionaries or by values present in the route match dictionary. For example, a Django view will accept positional arguments which match information in an associated "urlconf" such as ``r'^polls/(?P\d+)/$``: .. code-block:: python :linenos: def aview(request, poll_id): return HttpResponse(poll_id) Zope, likewise allows you to add arbitrary keyword and positional arguments to any method of a resource object found via traversal: .. code-block:: python :linenos: from persistent import Persistent class MyZopeObject(Persistent): def aview(self, a, b, c=None): return '%s %s %c' % (a, b, c) When this method is called as the result of being the published callable, the Zope request object's GET and POST namespaces are searched for keys which match the names of the positional and keyword arguments in the request, and the method is called (if possible) with its argument list filled with values mentioned therein. TurboGears and Pylons 1.X operate similarly. Out of the box, :app:`Pyramid` is configured to have none of these features. By default, :app:`Pyramid` view callables always accept only ``request`` and no other arguments. The rationale: this argument specification matching done aggressively can be costly, and :app:`Pyramid` has performance as one of its main goals, so we've decided to make people, by default, obtain information by interrogating the request object within the view callable body instead of providing magic to do unpacking into the view argument list. However, as of :app:`Pyramid` 1.0a9, user code can influence the way view callables are expected to be called, making it possible to compose a system out of view callables which are called with arbitrary arguments. See :ref:`using_a_view_mapper`. Pyramid Provides Too Few "Rails" -------------------------------- By design, :app:`Pyramid` is not a particularly opinionated web framework. It has a relatively parsimonious feature set. It contains no built in ORM nor any particular database bindings. It contains no form generation framework. It has no administrative web user interface. It has no built in text indexing. It does not dictate how you arrange your code. Such opinionated functionality exists in applications and frameworks built *on top* of :app:`Pyramid`. It's intended that higher-level systems emerge built using :app:`Pyramid` as a base. .. seealso:: See also :ref:`apps_are_extensible`. Pyramid Provides Too Many "Rails" --------------------------------- :app:`Pyramid` provides some features that other web frameworks do not. These are features meant for use cases that might not make sense to you if you're building a simple bespoke web application: - An optional way to map URLs to code using :term:`traversal` which implies a walk of a :term:`resource tree`. - The ability to aggregate Pyramid application configuration from multiple sources using :meth:`pyramid.config.Configurator.include`. - View and subscriber registrations made using :term:`interface` objects instead of class objects (e.g. :ref:`using_resource_interfaces`). - A declarative :term:`authorization` system. - Multiple separate I18N :term:`translation string` factories, each of which can name its own domain. These features are important to the authors of :app:`Pyramid`. The :app:`Pyramid` authors are often commissioned to build CMS-style applications. Such applications are often frameworky because they have more than one deployment. Each deployment requires a slightly different composition of sub-applications, and the framework and sub-applications often need to be *extensible*. Because the application has more than one deployment, pluggability and extensibility is important, as maintaining multiple forks of the application, one per deployment, is extremely undesirable. Because it's easier to extend a system that uses :term:`traversal` from the outside than it is to do the same in a system that uses :term:`URL dispatch`, each deployment uses a :term:`resource tree` composed of a persistent tree of domain model objects, and uses :term:`traversal` to map :term:`view callable` code to resources in the tree. The resource tree contains very granular security declarations, as resources are owned and accessible by different sets of users. Interfaces are used to make unit testing and implementation substitutability easier. In a bespoke web application, usually there's a single canonical deployment, and therefore no possibility of multiple code forks. Extensibility is not required; the code is just changed in-place. Security requirements are often less granular. Using the features listed above will often be overkill for such an application. If you don't like these features, it doesn't mean you can't or shouldn't use :app:`Pyramid`. They are all optional, and a lot of time has been spent making sure you don't need to know about them up-front. You can build "Pylons-1.X-style" applications using :app:`Pyramid` that are purely bespoke by ignoring the features above. You may find these features handy later after building a bespoke web application that suddenly becomes popular and requires extensibility because it must be deployed in multiple locations. Pyramid Is Too Big ------------------ "The :app:`Pyramid` compressed tarball is larger than 2MB. It must be enormous!" No. We just ship it with docs, test code, and scaffolding. Here's a breakdown of what's included in subdirectories of the package tree: docs/ 4.9MB pyramid/tests/ 2.0MB pyramid/scaffolds/ 460KB pyramid/ (except for ``pyramd/tests`` and ``pyramid/scaffolds``) 844KB Of the approximately 34K lines of Python code in the package, the code that actually has a chance of executing during normal operation, excluding tests and scaffolding Python files, accounts for approximately 10K lines. Pyramid Has Too Many Dependencies --------------------------------- Over time, we've made lots of progress on reducing the number of packaging dependencies Pyramid has had. Pyramid 1.2 had 15 of them. Pyramid 1.3 and 1.4 had 12 of them. The current release as of this writing, Pyramid 1.5, has only 7. This number is unlikely to become any smaller. A port to Python 3 completed in Pyramid 1.3 helped us shed a good number of dependencies by forcing us to make better packaging decisions. Removing Chameleon and Mako templating system dependencies in the Pyramid core in 1.5 let us shed most of the remainder of them. Pyramid "Cheats" To Obtain Speed -------------------------------- Complaints have been lodged by other web framework authors at various times that :app:`Pyramid` "cheats" to gain performance. One claimed cheating mechanism is our use (transitively) of the C extensions provided by :mod:`zope.interface` to do fast lookups. Another claimed cheating mechanism is the religious avoidance of extraneous function calls. If there's such a thing as cheating to get better performance, we want to cheat as much as possible. We optimize :app:`Pyramid` aggressively. This comes at a cost: the core code has sections that could be expressed more readably. As an amelioration, we've commented these sections liberally. Pyramid Gets Its Terminology Wrong ("MVC") ------------------------------------------ "I'm a MVC web framework user, and I'm confused. :app:`Pyramid` calls the controller a view! And it doesn't have any controllers." If you are in this camp, you might have come to expect things about how your existing "MVC" framework uses its terminology. For example, you probably expect that models are ORM models, controllers are classes that have methods that map to URLs, and views are templates. :app:`Pyramid` indeed has each of these concepts, and each probably *works* almost exactly like your existing "MVC" web framework. We just don't use the MVC terminology, as we can't square its usage in the web framework space with historical reality. People very much want to give web applications the same properties as common desktop GUI platforms by using similar terminology, and to provide some frame of reference for how various components in the common web framework might hang together. But in the opinion of the author, "MVC" doesn't match the web very well in general. Quoting from the `Model-View-Controller Wikipedia entry `_: .. code-block:: text Though MVC comes in different flavors, control flow is generally as follows: The user interacts with the user interface in some way (for example, presses a mouse button). The controller handles the input event from the user interface, often via a registered handler or callback and converts the event into appropriate user action, understandable for the model. The controller notifies the model of the user action, possibly resulting in a change in the model's state. (For example, the controller updates the user's shopping cart.)[5] A view queries the model in order to generate an appropriate user interface (for example, the view lists the shopping cart's contents). Note that the view gets its own data from the model. The controller may (in some implementations) issue a general instruction to the view to render itself. In others, the view is automatically notified by the model of changes in state (Observer) which require a screen update. The user interface waits for further user interactions, which restarts the cycle. To the author, it seems as if someone edited this Wikipedia definition, tortuously couching concepts in the most generic terms possible in order to account for the use of the term "MVC" by current web frameworks. I doubt such a broad definition would ever be agreed to by the original authors of the MVC pattern. But *even so*, it seems most MVC web frameworks fail to meet even this falsely generic definition. For example, do your templates (views) always query models directly as is claimed in "note that the view gets its own data from the model"? Probably not. My "controllers" tend to do this, massaging the data for easier use by the "view" (template). What do you do when your "controller" returns JSON? Do your controllers use a template to generate JSON? If not, what's the "view" then? Most MVC-style GUI web frameworks have some sort of event system hooked up that lets the view detect when the model changes. The web just has no such facility in its current form: it's effectively pull-only. So, in the interest of not mistaking desire with reality, and instead of trying to jam the square peg that is the web into the round hole of "MVC", we just punt and say there are two things: resources and views. The resource tree represents a site structure, the view presents a resource. The templates are really just an implementation detail of any given view: a view doesn't need a template to return a response. There's no "controller": it just doesn't exist. The "model" is either represented by the resource tree or by a "domain model" (like a SQLAlchemy model) that is separate from the framework entirely. This seems to us like more reasonable terminology, given the current constraints of the web. .. _apps_are_extensible: Pyramid Applications are Extensible; I Don't Believe In Application Extensibility --------------------------------------------------------------------------------- Any :app:`Pyramid` application written obeying certain constraints is *extensible*. This feature is discussed in the :app:`Pyramid` documentation chapters named :ref:`extending_chapter` and :ref:`advconfig_narr`. It is made possible by the use of the :term:`Zope Component Architecture` and within :app:`Pyramid`. "Extensible", in this context, means: - The behavior of an application can be overridden or extended in a particular *deployment* of the application without requiring that the deployer modify the source of the original application. - The original developer is not required to anticipate any extensibility plugpoints at application creation time to allow fundamental application behavior to be overriden or extended. - The original developer may optionally choose to anticipate an application-specific set of plugpoints, which may be hooked by a deployer. If he chooses to use the facilities provided by the ZCA, the original developer does not need to think terribly hard about the mechanics of introducing such a plugpoint. Many developers seem to believe that creating extensible applications is not worth it. They instead suggest that modifying the source of a given application for each deployment to override behavior is more reasonable. Much discussion about version control branching and merging typically ensues. It's clear that making every application extensible isn't required. The majority of web applications only have a single deployment, and thus needn't be extensible at all. However, some web applications have multiple deployments, and some have *many* deployments. For example, a generic content management system (CMS) may have basic functionality that needs to be extended for a particular deployment. That CMS system may be deployed for many organizations at many places. Some number of deployments of this CMS may be deployed centrally by a third party and managed as a group. It's easier to be able to extend such a system for each deployment via preordained plugpoints than it is to continually keep each software branch of the system in sync with some upstream source: the upstream developers may change code in such a way that your changes to the same codebase conflict with theirs in fiddly, trivial ways. Merging such changes repeatedly over the lifetime of a deployment can be difficult and time consuming, and it's often useful to be able to modify an application for a particular deployment in a less invasive way. If you don't want to think about :app:`Pyramid` application extensibility at all, you needn't. You can ignore extensibility entirely. However, if you follow the set of rules defined in :ref:`extending_chapter`, you don't need to *make* your application extensible: any application you write in the framework just *is* automatically extensible at a basic level. The mechanisms that deployers use to extend it will be necessarily coarse: typically, views, routes, and resources will be capable of being overridden. But for most minor (and even some major) customizations, these are often the only override plugpoints necessary: if the application doesn't do exactly what the deployment requires, it's often possible for a deployer to override a view, route, or resource and quickly make it do what he or she wants it to do in ways *not necessarily anticipated by the original developer*. Here are some example scenarios demonstrating the benefits of such a feature. - If a deployment needs a different styling, the deployer may override the main template and the CSS in a separate Python package which defines overrides. - If a deployment needs an application page to do something differently, or to expose more or different information, the deployer may override the view that renders the page within a separate Python package. - If a deployment needs an additional feature, the deployer may add a view to the override package. As long as the fundamental design of the upstream package doesn't change, these types of modifications often survive across many releases of the upstream package without needing to be revisited. Extending an application externally is not a panacea, and carries a set of risks similar to branching and merging: sometimes major changes upstream will cause you to need to revisit and update some of your modifications. But you won't regularly need to deal wth meaningless textual merge conflicts that trivial changes to upstream packages often entail when it comes time to update the upstream package, because if you extend an application externally, there just is no textual merge done. Your modifications will also, for whatever it's worth, be contained in one, canonical, well-defined place. Branching an application and continually merging in order to get new features and bugfixes is clearly useful. You can do that with a :app:`Pyramid` application just as usefully as you can do it with any application. But deployment of an application written in :app:`Pyramid` makes it possible to avoid the need for this even if the application doesn't define any plugpoints ahead of time. It's possible that promoters of competing web frameworks dismiss this feature in favor of branching and merging because applications written in their framework of choice aren't extensible out of the box in a comparably fundamental way. While :app:`Pyramid` applications are fundamentally extensible even if you don't write them with specific extensibility in mind, if you're moderately adventurous, you can also take it a step further. If you learn more about the :term:`Zope Component Architecture`, you can optionally use it to expose other more domain-specific configuration plugpoints while developing an application. The plugpoints you expose needn't be as coarse as the ones provided automatically by :app:`Pyramid` itself. For example, you might compose your own directive that configures a set of views for a prebaked purpose (e.g. ``restview`` or somesuch) , allowing other people to refer to that directive when they make declarations in the ``includeme`` of their customization package. There is a cost for this: the developer of an application that defines custom plugpoints for its deployers will need to understand the ZCA or he will need to develop his own similar extensibility system. Ultimately, any argument about whether the extensibility features lent to applications by :app:`Pyramid` are good or bad is mostly pointless. You needn't take advantage of the extensibility features provided by a particular :app:`Pyramid` application in order to affect a modification for a particular set of its deployments. You can ignore the application's extensibility plugpoints entirely, and use version control branching and merging to manage application deployment modifications instead, as if you were deploying an application written using any other web framework. Zope 3 Enforces "TTW" Authorization Checks By Default; Pyramid Does Not ----------------------------------------------------------------------- Challenge +++++++++ :app:`Pyramid` performs automatic authorization checks only at :term:`view` execution time. Zope 3 wraps context objects with a `security proxy `_, which causes Zope 3 to do also security checks during attribute access. I like this, because it means: #) When I use the security proxy machinery, I can have a view that conditionally displays certain HTML elements (like form fields) or prevents certain attributes from being modified depending on the permissions that the accessing user possesses with respect to a context object. #) I want to also expose my resources via a REST API using Twisted Web. If Pyramid performed authorization based on attribute access via Zope3's security proxies, I could enforce my authorization policy in both :app:`Pyramid` and in the Twisted-based system the same way. Defense +++++++ :app:`Pyramid` was developed by folks familiar with Zope 2, which has a "through the web" security model. This TTW security model was the precursor to Zope 3's security proxies. Over time, as the :app:`Pyramid` developers (working in Zope 2) created such sites, we found authorization checks during code interpretation extremely useful in a minority of projects. But much of the time, TTW authorization checks usually slowed down the development velocity of projects that had no delegation requirements. In particular, if we weren't allowing untrusted users to write arbitrary Python code to be executed by our application, the burden of through the web security checks proved too costly to justify. We (collectively) haven't written an application on top of which untrusted developers are allowed to write code in many years, so it seemed to make sense to drop this model by default in a new web framework. And since we tend to use the same toolkit for all web applications, it's just never been a concern to be able to use the same set of restricted-execution code under two web different frameworks. Justifications for disabling security proxies by default notwithstanding, given that Zope 3 security proxies are viral by nature, the only requirement to use one is to make sure you wrap a single object in a security proxy and make sure to access that object normally when you want proxy security checks to happen. It is possible to override the :app:`Pyramid` traverser for a given application (see :ref:`changing_the_traverser`). To get Zope3-like behavior, it is possible to plug in a different traverser which returns Zope3-security-proxy-wrapped objects for each traversed object (including the :term:`context` and the :term:`root`). This would have the effect of creating a more Zope3-like environment without much effort. .. _http_exception_hierarchy: Pyramid uses its own HTTP exception class hierarchy rather than :mod:`webob.exc` -------------------------------------------------------------------------------- .. versionadded:: 1.1 The HTTP exception classes defined in :mod:`pyramid.httpexceptions` are very much like the ones defined in :mod:`webob.exc`, (e.g., :class:`~pyramid.httpexceptions.HTTPNotFound` or :class:`~pyramid.httpexceptions.HTTPForbidden`). They have the same names and largely the same behavior, and all have a very similar implementation, but not the same identity. Here's why they have a separate identity: - Making them separate allows the HTTP exception classes to subclass :class:`pyramid.response.Response`. This speeds up response generation slightly due to the way the Pyramid router works. The same speedup could be gained by monkeypatching :class:`webob.response.Response`, but it's usually the case that monkeypatching turns out to be evil and wrong. - Making them separate allows them to provide alternate ``__call__`` logic which also speeds up response generation. - Making them separate allows the exception classes to provide for the proper value of ``RequestClass`` (:class:`pyramid.request.Request`). - Making them separate allows us freedom from having to think about backwards compatibility code present in :mod:`webob.exc` having to do with Python 2.4, which we no longer support in Pyramid 1.1+. - We change the behavior of two classes (:class:`~pyramid.httpexceptions.HTTPNotFound` and :class:`~pyramid.httpexceptions.HTTPForbidden`) in the module so that they can be used by Pyramid internally for notfound and forbidden exceptions. - Making them separate allows us to influence the docstrings of the exception classes to provide Pyramid-specific documentation. - Making them separate allows us to silence a stupid deprecation warning under Python 2.6 when the response objects are used as exceptions (related to ``self.message``). .. _simpler_traversal_model: Pyramid has Simpler Traversal Machinery than Does Zope ------------------------------------------------------ Zope's default traverser: - Allows developers to mutate the traversal name stack while traversing (they can add and remove path elements). - Attempts to use an adaptation to obtain the next element in the path from the currently traversed object, falling back to ``__bobo_traverse__``, ``__getitem__`` and eventually ``__getattr__``. Zope's default traverser allows developers to mutate the traversal name stack during traversal by mutating ``REQUEST['TraversalNameStack']``. Pyramid's default traverser (``pyramid.traversal.ResourceTreeTraverser``) does not offer a way to do this; it does not maintain a stack as a request attribute and, even if it did, it does not pass the request to resource objects while it's traversing. While it was handy at times, this feature was abused in frameworks built atop Zope (like CMF and Plone), often making it difficult to tell exactly what was happening when a traversal didn't match a view. I felt it was better to make folks that wanted the feature replace the traverser rather than build that particular honey pot in to the default traverser. Zope uses multiple mechanisms to attempt to obtain the next element in the resource tree based on a name. It first tries an adaptation of the current resource to ``ITraversable``, and if that fails, it falls back to attempting number of magic methods on the resource (``__bobo_traverse__``, ``__getitem__``, and ``__getattr__``). My experience while both using Zope and attempting to reimplement its publisher in ``repoze.zope2`` led me to believe the following: - The *default* traverser should be as simple as possible. Zope's publisher is somewhat difficult to follow and replicate due to the fallbacks it tried when one traversal method failed. It is also slow. - The *entire traverser* should be replaceable, not just elements of the traversal machinery. Pyramid has a few big components rather than a plethora of small ones. If the entire traverser is replaceable, it's an antipattern to make portions of the default traverser replaceable. Doing so is a "knobs on knobs" pattern, which is unfortunately somewhat endemic in Zope. In a "knobs on knobs" pattern, a replaceable subcomponent of a larger component is made configurable using the same configuration mechanism that can be used to replace the larger component. For example, in Zope, you can replace the default traverser by registering an adapter. But you can also (or alternately) control how the default traverser traverses by registering one or more adapters. As a result of being able to either replace the larger component entirely or turn knobs on the default implementation of the larger component, no one understands when (or whether) they should ever override the larger component entrirely. This results, over time, in a rusting together of the larger "replaceable" component and the framework itself, because people come to depend on the availability of the default component in order just to turn its knobs. The default component effectively becomes part of the framework, which entirely subverts the goal of making it replaceable. In Pyramid, typically if a component is replaceable, it will itself have no knobs (it will be solid state). If you want to influence behavior controlled by that component, you will replace the component instead of turning knobs attached to the component. .. _microframeworks_smaller_hello_world: Microframeworks Have Smaller Hello World Programs ------------------------------------------------- Self-described "microframeworks" exist: `Bottle `_ and `Flask `_ are two that are becoming popular. `Bobo `_ doesn't describe itself as a microframework, but its intended userbase is much the same. Many others exist. We've actually even (only as a teaching tool, not as any sort of official project) `created one using Pyramid `_ (the videos use BFG, a precursor to Pyramid, but the resulting code is `available for Pyramid too `_). Microframeworks are small frameworks with one common feature: each allows its users to create a fully functional application that lives in a single Python file. Some developers and microframework authors point out that Pyramid's "hello world" single-file program is longer (by about five lines) than the equivalent program in their favorite microframework. Guilty as charged. This loss isn't for lack of trying. Pyramid is useful in the same circumstance in which microframeworks claim dominance: single-file applications. But Pyramid doesn't sacrifice its ability to credibly support larger applications in order to achieve hello-world LoC parity with the current crop of microframeworks. Pyramid's design instead tries to avoid some common pitfalls associated with naive declarative configuration schemes. The subsections which follow explain the rationale. .. _you_dont_own_modulescope: Application Programmers Don't Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil) +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Please imagine a directory structure with a set of Python files in it: .. code-block:: text . |-- app.py |-- app2.py `-- config.py The contents of ``app.py``: .. code-block:: python :linenos: from config import decorator from config import L import pprint @decorator def foo(): pass if __name__ == '__main__': import app2 pprint.pprint(L) The contents of ``app2.py``: .. code-block:: python :linenos: import app @app.decorator def bar(): pass The contents of ``config.py``: .. code-block:: python :linenos: L = [] def decorator(func): L.append(func) return func If we cd to the directory that holds these files and we run ``python app.py`` given the directory structure and code above, what happens? Presumably, our ``decorator`` decorator will be used twice, once by the decorated function ``foo`` in ``app.py`` and once by the decorated function ``bar`` in ``app2.py``. Since each time the decorator is used, the list ``L`` in ``config.py`` is appended to, we'd expect a list with two elements to be printed, right? Sadly, no: .. code-block:: text [chrism@thinko]$ python app.py [, , ] By visual inspection, that outcome (three different functions in the list) seems impossible. We only defined two functions and we decorated each of those functions only once, so we believe that the ``decorator`` decorator will only run twice. However, what we believe is wrong because the code at module scope in our ``app.py`` module was *executed twice*. The code is executed once when the script is run as ``__main__`` (via ``python app.py``), and then it is executed again when ``app2.py`` imports the same file as ``app``. What does this have to do with our comparison to microframeworks? Many microframeworks in the current crop (e.g. Bottle, Flask) encourage you to attach configuration decorators to objects defined at module scope. These decorators execute arbitrarily complex registration code which populates a singleton registry that is a global defined in external Python module. This is analogous to the above example: the "global registry" in the above example is the list ``L``. Let's see what happens when we use the same pattern with the `Groundhog `_ microframework. Replace the contents of ``app.py`` above with this: .. code-block:: python :linenos: from config import gh @gh.route('/foo/') def foo(): return 'foo' if __name__ == '__main__': import app2 pprint.pprint(L) Replace the contents of ``app2.py`` above with this: .. code-block:: python :linenos: import app @app.gh.route('/bar/') def bar(): 'return bar' Replace the contents of ``config.py`` above with this: .. code-block:: python :linenos: from groundhog import Groundhog gh = Groundhog('myapp', 'seekrit') How many routes will be registered within the routing table of the "gh" Groundhog application? If you answered three, you are correct. How many would a casual reader (and any sane developer) expect to be registered? If you answered two, you are correct. Will the double registration be a problem? With our Groundhog framework's ``route`` method backing this application, not really. It will slow the application down a little bit, because it will need to miss twice for a route when it does not match. Will it be a problem with another framework, another application, or another decorator? Who knows. You need to understand the application in its totality, the framework in its totality, and the chronology of execution to be able to predict what the impact of unintentional code double-execution will be. The encouragement to use decorators which perform population of an external registry has an unintended consequence: the application developer now must assert ownership of every codepath that executes Python module scope code. Module-scope code is presumed by the current crop of decorator-based microframeworks to execute once and only once; if it executes more than once, weird things will start to happen. It is up to the application developer to maintain this invariant. Unfortunately, however, in reality, this is an impossible task, because, Python programmers *do not own the module scope codepath, and never will*. Anyone who tries to sell you on the idea that they do is simply mistaken. Test runners that you may want to use to run your code's tests often perform imports of arbitrary code in strange orders that manifest bugs like the one demonstrated above. API documentation generation tools do the same. Some people even think it's safe to use the Python ``reload`` command or delete objects from ``sys.modules``, each of which has hilarious effects when used against code that has import-time side effects. Global-registry-mutating microframework programmers therefore will at some point need to start reading the tea leaves about what *might* happen if module scope code gets executed more than once like we do in the previous paragraph. When Python programmers assume they can use the module-scope codepath to run arbitrary code (especially code which populates an external registry), and this assumption is challenged by reality, the application developer is often required to undergo a painful, meticulous debugging process to find the root cause of an inevitably obscure symptom. The solution is often to rearrange application import ordering or move an import statement from module-scope into a function body. The rationale for doing so can never be expressed adequately in the checkin message which accompanies the fix and can't be documented succinctly enough for the benefit of the rest of the development team so that the problem never happens again. It will happen again, especially if you are working on a project with other people who haven't yet internalized the lessons you learned while you stepped through module-scope code using ``pdb``. This is a really pretty poor situation to find yourself in as an application developer: you probably didn't even know your or your team signed up for the job, because the documentation offered by decorator-based microframeworks don't warn you about it. Folks who have a large investment in eager decorator-based configuration that populates an external data structure (such as microframework authors) may argue that the set of circumstances I outlined above is anomalous and contrived. They will argue that it just will never happen. If you never intend your application to grow beyond one or two or three modules, that's probably true. However, as your codebase grows, and becomes spread across a greater number of modules, the circumstances in which module-scope code will be executed multiple times will become more and more likely to occur and less and less predictable. It's not responsible to claim that double-execution of module-scope code will never happen. It will; it's just a matter of luck, time, and application complexity. If microframework authors do admit that the circumstance isn't contrived, they might then argue that real damage will never happen as the result of the double-execution (or triple-execution, etc) of module scope code. You would be wise to disbelieve this assertion. The potential outcomes of multiple execution are too numerous to predict because they involve delicate relationships between application and framework code as well as chronology of code execution. It's literally impossible for a framework author to know what will happen in all circumstances. But even if given the gift of omniscience for some limited set of circumstances, the framework author almost certainly does not have the double-execution anomaly in mind when coding new features. He's thinking of adding a feature, not protecting against problems that might be caused by the 1% multiple execution case. However, any 1% case may cause 50% of your pain on a project, so it'd be nice if it never occured. Responsible microframeworks actually offer a back-door way around the problem. They allow you to disuse decorator based configuration entirely. Instead of requiring you to do the following: .. code-block:: python :linenos: gh = Groundhog('myapp', 'seekrit') @gh.route('/foo/') def foo(): return 'foo' if __name__ == '__main__': gh.run() They allow you to disuse the decorator syntax and go almost-all-imperative: .. code-block:: python :linenos: def foo(): return 'foo' gh = Groundhog('myapp', 'seekrit') if __name__ == '__main__': gh.add_route(foo, '/foo/') gh.run() This is a generic mode of operation that is encouraged in the Pyramid documentation. Some existing microframeworks (Flask, in particular) allow for it as well. None (other than Pyramid) *encourage* it. If you never expect your application to grow beyond two or three or four or ten modules, it probably doesn't matter very much which mode you use. If your application grows large, however, imperative configuration can provide better predictability. .. note:: Astute readers may notice that Pyramid has configuration decorators too. Aha! Don't these decorators have the same problems? No. These decorators do not populate an external Python module when they are executed. They only mutate the functions (and classes and methods) they're attached to. These mutations must later be found during a scan process that has a predictable and structured import phase. Module-localized mutation is actually the best-case circumstance for double-imports; if a module only mutates itself and its contents at import time, if it is imported twice, that's OK, because each decorator invocation will always be mutating an independent copy of the object it's attached to, not a shared resource like a registry in another module. This has the effect that double-registrations will never be performed. .. _routes_need_ordering: Routes Need Relative Ordering +++++++++++++++++++++++++++++ Consider the following simple `Groundhog `_ application: .. code-block:: python :linenos: from groundhog import Groundhog app = Groundhog('myapp', 'seekrit') app.route('/admin') def admin(): return 'admin page' app.route('/:action') def action(): if action == 'add': return 'add' if action == 'delete': return 'delete' return app.abort(404) if __name__ == '__main__': app.run() If you run this application and visit the URL ``/admin``, you will see the "admin" page. This is the intended result. However, what if you rearrange the order of the function definitions in the file? .. code-block:: python :linenos: from groundhog import Groundhog app = Groundhog('myapp', 'seekrit') app.route('/:action') def action(): if action == 'add': return 'add' if action == 'delete': return 'delete' return app.abort(404) app.route('/admin') def admin(): return 'admin page' if __name__ == '__main__': app.run() If you run this application and visit the URL ``/admin``, you will now be returned a 404 error. This is probably not what you intended. The reason you see a 404 error when you rearrange function definition ordering is that routing declarations expressed via our microframework's routing decorators have an *ordering*, and that ordering matters. In the first case, where we achieved the expected result, we first added a route with the pattern ``/admin``, then we added a route with the pattern ``/:action`` by virtue of adding routing patterns via decorators at module scope. When a request with a ``PATH_INFO`` of ``/admin`` enters our application, the web framework loops over each of our application's route patterns in the order in which they were defined in our module. As a result, the view associated with the ``/admin`` routing pattern will be invoked: it matches first. All is right with the world. In the second case, where we did not achieve the expected result, we first added a route with the pattern ``/:action``, then we added a route with the pattern ``/admin``. When a request with a ``PATH_INFO`` of ``/admin`` enters our application, the web framework loops over each of our application's route patterns in the order in which they were defined in our module. As a result, the view associated with the ``/:action`` routing pattern will be invoked: it matches first. A 404 error is raised. This is not what we wanted; it just happened due to the order in which we defined our view functions. This is because Groundhog routes are added to the routing map in import order, and matched in the same order when a request comes in. Bottle, like Groundhog, as of this writing, matches routes in the order in which they're defined at Python execution time. Flask, on the other hand, does not order route matching based on import order; it reorders the routes you add to your application based on their "complexity". Other microframeworks have varying strategies to do route ordering. Your application may be small enough where route ordering will never cause an issue. If your application becomes large enough, however, being able to specify or predict that ordering as your application grows larger will be difficult. At some point, you will likely need to more explicitly start controlling route ordering, especially in applications that require extensibility. If your microframework orders route matching based on complexity, you'll need to understand what is meant by "complexity", and you'll need to attempt to inject a "less complex" route to have it get matched before any "more complex" one to ensure that it's tried first. If your microframework orders its route matching based on relative import/execution of function decorator definitions, you will need to ensure you execute all of these statements in the "right" order, and you'll need to be cognizant of this import/execution ordering as you grow your application or try to extend it. This is a difficult invariant to maintain for all but the smallest applications. In either case, your application must import the non-``__main__`` modules which contain configuration decorations somehow for their configuration to be executed. Does that make you a little uncomfortable? It should, because :ref:`you_dont_own_modulescope`. Pyramid uses neither decorator import time ordering nor does it attempt to divine the relative complexity of one route to another in order to define a route match ordering. In Pyramid, you have to maintain relative route ordering imperatively via the chronology of multiple executions of the :meth:`pyramid.config.Configurator.add_route` method. The order in which you repeatedly call ``add_route`` becomes the order of route matching. If needing to maintain this imperative ordering truly bugs you, you can use :term:`traversal` instead of route matching, which is a completely declarative (and completely predictable) mechanism to map code to URLs. While URL dispatch is easier to understand for small non-extensible applications, traversal is a great fit for very large applications and applications that need to be arbitrarily extensible. "Stacked Object Proxies" Are Too Clever / Thread Locals Are A Nuisance ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Some microframeworks use the ``import`` statement to get a handle to an object which *is not logically global*: .. code-block:: python :linenos: from flask import request @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': if valid_login(request.form['username'], request.form['password']): return log_the_user_in(request.form['username']) else: error = 'Invalid username/password' # this is executed if the request method was GET or the # credentials were invalid The `Pylons 1.X `_ web framework uses a similar strategy. It calls these things "Stacked Object Proxies", so, for purposes of this discussion, I'll do so as well. Import statements in Python (``import foo``, ``from bar import baz``) are most frequently performed to obtain a reference to an object defined globally within an external Python module. However, in normal programs, they are never used to obtain a reference to an object that has a lifetime measured by the scope of the body of a function. It would be absurd to try to import, for example, a variable named ``i`` representing a loop counter defined in the body of a function. For example, we'd never try to import ``i`` from the code below: .. code-block:: python :linenos: def afunc(): for i in range(10): print(i) By its nature, the *request* object created as the result of a WSGI server's call into a long-lived web framework cannot be global, because the lifetime of a single request will be much shorter than the lifetime of the process running the framework. A request object created by a web framework actually has more similarity to the ``i`` loop counter in our example above than it has to any comparable importable object defined in the Python standard library or in normal library code. However, systems which use stacked object proxies promote locally scoped objects such as ``request`` out to module scope, for the purpose of being able to offer users a nice spelling involving ``import``. They, for what I consider dubious reasons, would rather present to their users the canonical way of getting at a ``request`` as ``from framework import request`` instead of a saner ``from myframework.threadlocals import get_request; request = get_request()`` even though the latter is more explicit. It would be *most* explicit if the microframeworks did not use thread local variables at all. Pyramid view functions are passed a request object; many of Pyramid's APIs require that an explicit request object be passed to them. It is *possible* to retrieve the current Pyramid request as a threadlocal variable but it is a "in case of emergency, break glass" type of activity. This explicitness makes Pyramid view functions more easily unit testable, as you don't need to rely on the framework to manufacture suitable "dummy" request (and other similarly-scoped) objects during test setup. It also makes them more likely to work on arbitrary systems, such as async servers that do no monkeypatching. Explicitly WSGI +++++++++++++++ Some microframeworks offer a ``run()`` method of an application object that executes a default server configuration for easy execution. Pyramid doesn't currently try to hide the fact that its router is a WSGI application behind a convenience ``run()`` API. It just tells people to import a WSGI server and use it to serve up their Pyramid application as per the documentation of that WSGI server. The extra lines saved by abstracting away the serving step behind ``run()`` seem to have driven dubious second-order decisions related to API in some microframeworks. For example, Bottle contains a ``ServerAdapter`` subclass for each type of WSGI server it supports via its ``app.run()`` mechanism. This means that there exists code in ``bottle.py`` that depends on the following modules: ``wsgiref``, ``flup``, ``paste``, ``cherrypy``, ``fapws``, ``tornado``, ``google.appengine``, ``twisted.web``, ``diesel``, ``gevent``, ``gunicorn``, ``eventlet``, and ``rocket``. You choose the kind of server you want to run by passing its name into the ``run`` method. In theory, this sounds great: I can try Bottle out on ``gunicorn`` just by passing in a name! However, to fully test Bottle, all of these third-party systems must be installed and functional; the Bottle developers must monitor changes to each of these packages and make sure their code still interfaces properly with them. This expands the packages required for testing greatly; this is a *lot* of requirements. It is likely difficult to fully automate these tests due to requirements conflicts and build issues. As a result, for single-file apps, we currently don't bother to offer a ``run()`` shortcut; we tell folks to import their WSGI server of choice and run it by hand. For the people who want a server abstraction layer, we suggest that they use PasteDeploy. In PasteDeploy-based systems, the onus for making sure that the server can interface with a WSGI application is placed on the server developer, not the web framework developer, making it more likely to be timely and correct. Wrapping Up +++++++++++ Here's a diagrammed version of the simplest pyramid application, where comments take into account what we've discussed in the :ref:`microframeworks_smaller_hello_world` section. .. code-block:: python :linenos: from pyramid.response import Response # explicit response, no thread local from wsgiref.simple_server import make_server # explicitly WSGI def hello_world(request): # accepts a request; no request thread local reqd # explicit response object means no response threadlocal return Response('Hello world!') if __name__ == '__main__': from pyramid.config import Configurator config = Configurator() # no global application object. config.add_view(hello_world) # explicit non-decorator registration app = config.make_wsgi_app() # explicitly WSGI server = make_server('0.0.0.0', 8080, app) server.serve_forever() # explicitly WSGI Pyramid Doesn't Offer Pluggable Apps ------------------------------------ It is "Pyramidic" to compose multiple external sources into the same configuration using :meth:`~pyramid.config.Configurator.include`. Any number of includes can be done to compose an application; includes can even be done from within other includes. Any directive can be used within an include that can be used outside of one (such as :meth:`~pyramid.config.Configurator.add_view`, etc). Pyramid has a conflict detection system that will throw an error if two included externals try to add the same configuration in a conflicting way (such as both externals trying to add a route using the same name, or both externals trying to add a view with the same set of predicates). It's awful tempting to call this set of features something that can be used to compose a system out of "pluggable applications". But in reality, there are a number of problems with claiming this: - The terminology is strained. Pyramid really has no notion of a plurality of "applications", just a way to compose configuration from multiple sources to create a single WSGI application. That WSGI application may gain behavior by including or disincluding configuration, but once it's all composed together, Pyramid doesn't really provide any machinery which can be used to demarcate the boundaries of one "application" (in the sense of configuration from an external that adds routes, views, etc) from another. - Pyramid doesn't provide enough "rails" to make it possible to integrate truly honest-to-god, download-an-app-from-a-random-place and-plug-it-in-to-create-a-system "pluggable" applications. Because Pyramid itself isn't opinionated (it doesn't mandate a particular kind of database, it offers multiple ways to map URLs to code, etc), it's unlikely that someone who creates something application-like will be able to casually redistribute it to J. Random Pyramid User and have it just work by asking him to config.include a function from the package. This is particularly true of very high level components such as blogs, wikis, twitter clones, commenting systems, etc. The integrator (the Pyramid developer who has downloaded a package advertised as a "pluggable app") will almost certainly have made different choices about e.g. what type of persistence system he's using, and for the integrator to appease the requirements of the "pluggable application", he may be required to set up a different database, make changes to his own code to prevent his application from shadowing the pluggable app (or vice versa), and any other number of arbitrary changes. For this reason, we claim that Pyramid has "extensible" applications, not pluggable applications. Any Pyramid application can be extended without forking it as long as its configuration statements have been composed into things that can be pulled in via ``config.include``. It's also perfectly reasonable for a single developer or team to create a set of interoperating components which can be enabled or disabled by using config.include. That developer or team will be able to provide the "rails" (by way of making high-level choices about the technology used to create the project, so there won't be any issues with plugging all of the components together. The problem only rears its head when the components need to be distributed to *arbitrary* users. Note that Django has a similar problem with "pluggable applications" that need to work for arbitrary third parties, even though they provide many, many more rails than does Pyramid. Even the rails they provide are not enough to make the "pluggable application" story really work without local modification. Truly pluggable applications need to be created at a much higher level than a web framework, as no web framework can offer enough constraints to really make them work out of the box. They really need to plug into an application, instead. It would be a noble goal to build an application with Pyramid that provides these constraints and which truly does offer a way to plug in applications (Joomla, Plone, Drupal come to mind). Pyramid Has Zope Things In It, So It's Too Complex -------------------------------------------------- On occasion, someone will feel compelled to post a mailing list message that reads something like this: .. code-block:: text had a quick look at pyramid ... too complex to me and not really understand for which benefits.. I feel should consider whether it's time for me to step back to django .. I always hated zope (useless ?) complexity and I love simple way of thinking (Paraphrased from a real email, actually.) Let's take this criticism point-by-point. Too Complex +++++++++++ If you can understand this hello world program, you can use Pyramid: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() Pyramid has ~ 700 pages of documentation (printed), covering topics from the very basic to the most advanced. *Nothing* is left undocumented, quite literally. It also has an *awesome*, very helpful community. Visit the #pyramid IRC channel on freenode.net (irc://freenode.net#pyramid) and see. Hate Zope +++++++++ I'm sorry you feel that way. The Zope brand has certainly taken its share of lumps over the years, and has a reputation for being insular and mysterious. But the word "Zope" is literally quite meaningless without qualification. What *part* of Zope do you hate? "Zope" is a brand, not a technology. If it's Zope2-the-web-framework, Pyramid is not that. The primary designers and developers of Pyramid, if anyone, should know. We wrote Pyramid's predecessor (:mod:`repoze.bfg`), in part, because *we* knew that Zope 2 had usability issues and limitations. :mod:`repoze.bfg` (and now :app:`Pyramid`) was written to address these issues. If it's Zope3-the-web-framework, Pyramid is *definitely* not that. Making use of lots of Zope 3 technologies is territory already staked out by the :term:`Grok` project. Save for the obvious fact that they're both web frameworks, :app:`Pyramid` is very, very different than Grok. Grok exposes lots of Zope technologies to end users. On the other hand, if you need to understand a Zope-only concept while using Pyramid, then we've failed on some very basic axis. If it's just the word Zope: this can only be guilt by association. Because a piece of software internally uses some package named ``zope.foo``, it doesn't turn the piece of software that uses it into "Zope". There is a lot of *great* software written that has the word Zope in its name. Zope is not some sort of monolithic thing, and a lot of its software is usable externally. And while it's not really the job of this document to defend it, Zope has been around for over 10 years and has an incredibly large, active community. If you don't believe this, http://pypi-ranking.info/author is an eye-opening reality check. Love Simplicity +++++++++++++++ Years of effort have gone into honing this package and its documentation to make it as simple as humanly possible for developers to use. Everything is a tradeoff, of course, and people have their own ideas about what "simple" is. You may have a style difference if you believe Pyramid is complex. Its developers obviously disagree. Other Challenges ---------------- Other challenges are encouraged to be sent to the `Pylons-devel `_ maillist. We'll try to address them by considering a design change, or at very least via exposition here. pyramid-1.6/docs/foreword.rst0000644000076500000240000001031012520062551017054 0ustar michaelstaff00000000000000:orphan: Foreword ======== .. sidebar:: A Foreword By Paul Everitt Paul Everitt is a principal at :term:`Agendaless Consulting`. Before his time at Agendaless, he was the co-founder of *Digital Creations*, which later became *Zope Corporation*. He has been a widely respected member of the Python community since 1994. Some times amazing things can actually happen. In the world of web frameworks, the rate of radioactive decay is very high. Projects are starting, splintering, folding, and clashing constantly. For Python, there are over 50 listed web frameworks. In some ways this shows health and experimentation. Yet others have started to ask: "Is this really good for Python developers?" This book is the result of an event which bucked this trend, an event which Armin Ronacher wrote was "one of the greatest moves in Python's web framework history." Two projects merged and are bringing in a third. Consolidation won a victory over splintering. As someone from the Zope world, I had a strong interest in repoze.bfg. I viewed it as the escape hatch for Zope, teleporting us into the modern world of Python development, permitting but not requiring Zope-style idioms. Chris McDonough established a great brand for repoze.bfg: small, documented, tested, fast, stable, friendly. As the project manager for a very large repoze.bfg application, I can strongly attest that it was a home run on those points. But in a crowded web frameworks landscape, repoze.bfg was a long-shot to get critical mass. It had a lot to offer, but was missing critical pieces such as momentum and name recognition. Pylons has long been viewed as holding the number two spot in Python web frameworks. It is one of (if not the) first "modern" web framework. With lots of users, and a "full-stack" framework atop it (TurboGears), Pylons had momentum and name recognition aplenty. But it needed more resources to accomplish its goals of an architectural transition, and Ben Bangert needed to share the load as architect during the transition. Ben and Chris started talking during 2011 about architectural patterns and discovered Pylons and repoze.bfg covered almost exactly the same surface area. After some experiments, it became clear that, technically at least, the next version of Pylons could be the same as the next version of repoze.bfg. But what about the non-technical parts? It was one thing to consolidate code. Consolidating projects was new territory. I was fortunate to meet with the principals in Las Vegas and watch as they hashed out the idea. The projects would merge and keep the Pylons identity. repoze.bfg would sacrifice its identity, but provide the technical foundation. All the resources from the two projects would be combined. I'll confess, I had high hopes for the outcome. Now that the merge has happened and 1.0 released, I can honestly say it has done better than I could have imagined. The story of "consolidation" is catching on, and interest in working together is growing. Pyramid 1.0 is very, very high quality and ready to go for PyCon 2011. People interested in "simple, fast, documented, tested" have a strong framework and healthy project. It took humility, patience, and pragmatism to reach this point of obvious success. Certainly by the project leaders, who each had to give up some of their sovereignty and sacred cows. But as well, each community had to discuss the challenges, the various alternatives for going forward, and the pros and cons of consolidation in general but also this particular consolidation. That such a conversation and change could happen in a responsible, adult fashion speaks volumes about the strength and maturity of each community. What might happen in 2011? TurboGears is considering a move into the umbrella Pylons Project. As Armin writes in his post, there is fertile ground for consolidation at other layers. In my own interests, I hope the worlds of Zope and Plone view Pyramid as the base for the next decade of their ideas. But also, the Pylons Project as a vibrant home for such ideas. Congratulations, Pylons Project. Not only have you accelerated your spot on the Python web frameworks chart, but you have injected the word "consolidation" into the lexicon of hot ideas for 2011. pyramid-1.6/docs/glossary.rst0000644000076500000240000013706612642135264017121 0ustar michaelstaff00000000000000.. _glossary: Glossary ======== .. glossary:: :sorted: request An object that represents an HTTP request, usually an instance of the :class:`pyramid.request.Request` class. See :ref:`webob_chapter` (narrative) and :ref:`request_module` (API documentation) for information about request objects. request factory 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. See :class:`pyramid.interfaces.IResponseFactory`. response An object returned by a :term:`view callable` that represents response data returned to the requesting user agent. It must implement the :class:`pyramid.interfaces.IResponse` interface. A response object is typically an instance of the :class:`pyramid.response.Response` class or a subclass such as :class:`pyramid.httpexceptions.HTTPFound`. See :ref:`webob_chapter` for information about response objects. response adapter A callable which accepts an arbitrary object and "converts" it to a :class:`pyramid.response.Response` object. See :ref:`using_iresponse` for more information. Repoze "Repoze" is essentially a "brand" of software developed by `Agendaless Consulting `_ and a set of contributors. The term has no special intrinsic meaning. The project's `website `_ has more information. The software developed "under the brand" is available in a `Subversion repository `_. Pyramid was originally known as :mod:`repoze.bfg`. setuptools `Setuptools `_ builds on Python's ``distutils`` to provide easier building, distribution, and installation of libraries and applications. As of this writing, setuptools runs under Python 2, but not under Python 3. You can use :term:`distribute` under Python 3 instead. distribute `Distribute `_ is a fork of :term:`setuptools` which runs on both Python 2 and Python 3. pkg_resources A module which ships with :term:`setuptools` and :term:`distribute` that provides an API for addressing "asset files" within a Python :term:`package`. Asset files are static files, template files, etc; basically anything non-Python-source that lives in a Python package can be considered a asset file. .. seealso:: See also `PkgResources `_. asset Any file contained within a Python :term:`package` which is *not* a Python source code file. asset specification A colon-delimited identifier for an :term:`asset`. The colon separates a Python :term:`package` name from a package subpath. For example, the asset specification ``my.package:static/baz.css`` identifies the file named ``baz.css`` in the ``static`` subdirectory of the ``my.package`` Python :term:`package`. See :ref:`asset_specifications` for more info. package A directory on disk which contains an ``__init__.py`` file, making it recognizable to Python as a location which can be ``import`` -ed. A package exists to contain :term:`module` files. module A Python source file; a file on the filesystem that typically ends with the extension ``.py`` or ``.pyc``. Modules often live in a :term:`package`. project (Setuptools/distutils terminology). A directory on disk which contains a ``setup.py`` file and one or more Python packages. The ``setup.py`` file contains code that allows the package(s) to be installed, distributed, and tested. distribution (Setuptools/distutils terminology). A file representing an installable library or application. Distributions are usually files that have the suffix of ``.egg``, ``.tar.gz``, or ``.zip``. Distributions are the target of Setuptools-related commands such as ``easy_install``. entry point A :term:`setuptools` indirection, defined within a setuptools :term:`distribution` setup.py. It is usually a name which refers to a function somewhere in a package which is held by the distribution. dotted Python name A reference to a Python object by name using a string, in the form ``path.to.modulename:attributename``. Often used in Pyramid and setuptools configurations. A variant is used in dotted names within configurator method arguments that name objects (such as the "add_view" method's "view" and "context" attributes): the colon (``:``) is not used; in its place is a dot. view Common vernacular for a :term:`view callable`. view callable A "view callable" is a callable Python object which is associated with a :term:`view configuration`; it returns a :term:`response` object . A view callable accepts a single argument: ``request``, which will be an instance of a :term:`request` object. An alternate calling convention allows a view to be defined as a callable which accepts a pair of arguments: ``context`` and ``request``: this calling convention is useful for traversal-based applications in which a :term:`context` is always very important. A view callable is the primary mechanism by which a developer writes user interface code within :app:`Pyramid`. See :ref:`views_chapter` for more information about :app:`Pyramid` view callables. view configuration View configuration is the act of associating a :term:`view callable` with configuration information. This configuration information helps map a given :term:`request` to a particular view callable and it can influence the response of a view callable. :app:`Pyramid` views can be configured via :term:`imperative configuration`, or by a special ``@view_config`` decorator coupled with a :term:`scan`. See :ref:`view_config_chapter` for more information about view configuration. view name The "URL name" of a view, e.g ``index.html``. If a view is configured without a name, its name is considered to be the empty string (which implies the :term:`default view`). Default view The default view of a :term:`resource` is the view invoked when the :term:`view name` is the empty string (``''``). This is the case when :term:`traversal` exhausts the path elements in the PATH_INFO of a request before it returns a :term:`context` resource. virtualenv A term referring both to an isolated Python environment, or `the leading tool `_ that allows one to create such environments. Note: whenever you encounter commands prefixed with ``$VENV`` (Unix) or ``%VENV`` (Windows), know that that is the environment variable whose value is the root of the virtual environment in question. resource An object representing a node in the :term:`resource tree` of an application. If :term:`traversal` is used, a resource is an element in the resource tree traversed by the system. When traversal is used, a resource becomes the :term:`context` of a :term:`view`. If :term:`url dispatch` is used, a single resource is generated for each request and is used as the context resource of a view. resource tree A nested set of dictionary-like objects, each of which is a :term:`resource`. The act of :term:`traversal` uses the resource tree to find a :term:`context` resource. domain model Persistent data related to your application. For example, data stored in a relational database. In some applications, the :term:`resource tree` acts as the domain model. traversal The act of descending "up" a tree of resource objects from a root resource in order to find a :term:`context` resource. The :app:`Pyramid` :term:`router` performs traversal of resource objects when a :term:`root factory` is specified. See the :ref:`traversal_chapter` chapter for more information. Traversal can be performed *instead* of :term:`URL dispatch` or can be combined *with* URL dispatch. See :ref:`hybrid_chapter` for more information about combining traversal and URL dispatch (advanced). router The :term:`WSGI` application created when you start a :app:`Pyramid` application. The router intercepts requests, invokes traversal and/or URL dispatch, calls view functions, and returns responses to the WSGI server on behalf of your :app:`Pyramid` application. URL dispatch An alternative to :term:`traversal` as a mechanism for locating a :term:`context` resource for a :term:`view`. When you use a :term:`route` in your :app:`Pyramid` application via a :term:`route configuration`, you are using URL dispatch. See the :ref:`urldispatch_chapter` for more information. context A resource in the resource tree that is found during :term:`traversal` or :term:`URL dispatch` based on URL data; if it's found via traversal, it's usually a :term:`resource` object that is part of a resource tree; if it's found via :term:`URL dispatch`, it's an object manufactured on behalf of the route's "factory". A context resource becomes the subject of a :term:`view`, and often has security information attached to it. See the :ref:`traversal_chapter` chapter and the :ref:`urldispatch_chapter` chapter for more information about how a URL is resolved to a context resource. application registry A registry of configuration information consulted by :app:`Pyramid` while servicing an application. An application registry maps resource types to views, as well as housing other application-specific component registrations. Every :app:`Pyramid` application has one (and only one) application registry. template A file with replaceable parts that is capable of representing some text, XML, or HTML when rendered. location The path to an object in a :term:`resource tree`. See :ref:`location_aware` for more information about how to make a resource object *location-aware*. permission A string or unicode object that represents an action being taken against a :term:`context` resource. A permission is associated with a view name and a resource type by the developer. Resources are decorated with security declarations (e.g. an :term:`ACL`), which reference these tokens also. Permissions are used by the active security policy to match the view permission against the resources's statements about which permissions are granted to which principal in a context in order to answer the question "is this user allowed to do this". Examples of permissions: ``read``, or ``view_blog_entries``. default permission A :term:`permission` which is registered as the default for an entire application. When a default permission is in effect, every :term:`view configuration` registered with the system will be effectively amended with a ``permission`` argument that will require that the executing user possess the default permission in order to successfully execute the associated :term:`view callable`. .. seealso:: See also :ref:`setting_a_default_permission`. ACE An *access control entry*. An access control entry is one element in an :term:`ACL`. An access control entry is a three-tuple that describes three things: an *action* (one of either ``Allow`` or ``Deny``), a :term:`principal` (a string describing a user or group), and a :term:`permission`. For example the ACE, ``(Allow, 'bob', 'read')`` is a member of an ACL that indicates that the principal ``bob`` is allowed the permission ``read`` against the resource the ACL is attached to. ACL An *access control list*. An ACL is a sequence of :term:`ACE` tuples. An ACL is attached to a resource instance. An example of an ACL is ``[ (Allow, 'bob', 'read'), (Deny, 'fred', 'write')]``. If an ACL is attached to a resource instance, and that resource is findable via the context resource, it will be consulted any active security policy to determine whether a particular request can be fulfilled given the :term:`authentication` information in the request. authentication The act of determining that the credentials a user presents during a particular request are "good". Authentication in :app:`Pyramid` is performed via an :term:`authentication policy`. authorization The act of determining whether a user can perform a specific action. In pyramid terms, this means determining whether, for a given resource, any :term:`principal` (or principals) associated with the request have the requisite :term:`permission` to allow the request to continue. Authorization in :app:`Pyramid` is performed via its :term:`authorization policy`. principal 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 `. 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 code which has an API which determines whether or not the principals associated with the request can perform an action associated with a permission, based on the information found on the :term:`context` resource. authentication policy An authentication policy in :app:`Pyramid` terms is a bit of code which has an API which determines the current :term:`principal` (or principals) associated with a request. WSGI `Web Server Gateway Interface `_. This is a Python standard for connecting web applications to web servers, similar to the concept of Java Servlets. :app:`Pyramid` requires that your application be served as a WSGI application. middleware *Middleware* is a :term:`WSGI` concept. It is a WSGI component that acts both as a server and an application. Interesting uses for middleware exist, such as caching, content-transport encoding, and other functions. See `WSGI.org `_ or `PyPI `_ to find middleware for your application. pipeline The :term:`PasteDeploy` term for a single configuration of a WSGI server, a WSGI application, with a set of :term:`middleware` in-between. Zope `The Z Object Publishing Framework `_, a full-featured Python web framework. Grok `A web framework based on Zope 3 `_. Django `A full-featured Python web framework `_. Pylons `A lightweight Python web framework `_ and a predecessor of Pyramid. ZODB `Zope Object Database `_, a persistent Python object store. WebOb `WebOb `_ is a WSGI request/response library created by Ian Bicking. PasteDeploy `PasteDeploy `_ is a library used by :app:`Pyramid` which makes it possible to configure :term:`WSGI` components together declaratively within an ``.ini`` file. It was developed by Ian Bicking. Chameleon `chameleon `_ is an attribute language template compiler which supports the :term:`ZPT` templating specification. It is written and maintained by Malthe Borch. It has several extensions, such as the ability to use bracketed (Mako-style) ``${name}`` syntax. It is also much faster than the reference implementation of ZPT. :app:`Pyramid` offers Chameleon templating out of the box in ZPT and text flavors. ZPT The `Zope Page Template `_ templating language. METAL `Macro Expansion for TAL `_, a part of :term:`ZPT` which makes it possible to share common look and feel between templates. Genshi An `XML templating language `_ by Christopher Lenz. Jinja2 A `text templating language `_ by Armin Ronacher. Routes A `system by Ben Bangert `_ which parses URLs and compares them against a number of user defined mappings. The URL pattern matching syntax in :app:`Pyramid` is inspired by the Routes syntax (which was inspired by Ruby On Rails pattern syntax). route A single pattern matched by the :term:`url dispatch` subsystem, which generally resolves to a :term:`root factory` (and then ultimately a :term:`view`). .. seealso:: See also :term:`url dispatch`. route configuration Route configuration is the act of associating request parameters with a particular :term:`route` using pattern matching and :term:`route predicate` statements. See :ref:`urldispatch_chapter` for more information about route configuration. Zope Component Architecture The `Zope Component Architecture `_ (aka ZCA) is a system which allows for application pluggability and complex dispatching based on objects which implement an :term:`interface`. :app:`Pyramid` uses the ZCA "under the hood" to perform view dispatching and other application configuration tasks. reStructuredText A `plain text markup format `_ that is the defacto standard for documenting Python projects. The Pyramid documentation is written in reStructuredText. root The object at which :term:`traversal` begins when :app:`Pyramid` searches for a :term:`context` resource (for :term:`URL Dispatch`, the root is *always* the context resource unless the ``traverse=`` argument is used in route configuration). subpath A list of element "left over" after the :term:`router` has performed a successful traversal to a view. The subpath is a sequence of strings, e.g. ``['left', 'over', 'names']``. Within Pyramid applications that use URL dispatch rather than traversal, you can use ``*subpath`` in the route pattern to influence the subpath. See :ref:`star_subpath` for more information. interface A `Zope interface `_ object. In :app:`Pyramid`, an interface may be attached to a :term:`resource` object or a :term:`request` object in order to identify that the object is "of a type". Interfaces are used internally by :app:`Pyramid` to perform view lookups and other policy lookups. The ability to make use of an interface is exposed to an application programmers during :term:`view configuration` via the ``context`` argument, the ``request_type`` argument and the ``containment`` argument. Interfaces are also exposed to application developers when they make use of the :term:`event` system. Fundamentally, :app:`Pyramid` programmers can think of an interface as something that they can attach to an object that stamps it with a "type" unrelated to its underlying Python type. Interfaces can also be used to describe the behavior of an object (its methods and attributes), but unless they choose to, :app:`Pyramid` programmers do not need to understand or use this feature of interfaces. event An object broadcast to zero or more :term:`subscriber` callables during normal :app:`Pyramid` system operations during the lifetime of an application. Application code can subscribe to these events by using the subscriber functionality described in :ref:`events_chapter`. subscriber A callable which receives an :term:`event`. A callable becomes a subscriber via :term:`imperative configuration` or via :term:`configuration decoration`. See :ref:`events_chapter` for more information. request type An attribute of a :term:`request` that allows for specialization of view invocation based on arbitrary categorization. The every :term:`request` object that :app:`Pyramid` generates and manipulates has one or more :term:`interface` objects attached to it. The default interface attached to a request object is :class:`pyramid.interfaces.IRequest`. repoze.lemonade Zope2 CMF-like `data structures and helper facilities `_ for CA-and-ZODB-based applications useful within :app:`Pyramid` applications. repoze.catalog An indexing and search facility (fielded and full-text) based on `zope.index `_. See `the documentation `_ for more information. repoze.who `Authentication middleware `_ for :term:`WSGI` applications. It can be used by :app:`Pyramid` to provide authentication information. repoze.workflow `Barebones workflow for Python apps `_ . It can be used by :app:`Pyramid` to form a workflow system. virtual root A resource object representing the "virtual" root of a request; this is typically the :term:`physical root` object unless :ref:`vhosting_chapter` is in use. physical root The object returned by the application :term:`root factory`. Unlike the :term:`virtual root` of a request, it is not impacted by :ref:`vhosting_chapter`: it will always be the actual object returned by the root factory, never a subobject. physical path The path required by a traversal which resolve a :term:`resource` starting from the :term:`physical root`. For example, the physical path of the ``abc`` subobject of the physical root object is ``/abc``. Physical paths can also be specified as tuples where the first element is the empty string (representing the root), and every other element is a Unicode object, e.g. ``('', 'abc')``. Physical paths are also sometimes called "traversal paths". lineage An ordered sequence of objects based on a ":term:`location` -aware" resource. The lineage of any given :term:`resource` is composed of itself, its parent, its parent's parent, and so on. The order of the sequence is resource-first, then the parent of the resource, then its parent's parent, and so on. The parent of a resource in a lineage is available as its ``__parent__`` attribute. root factory The "root factory" of a :app:`Pyramid` application is called on every request sent to the application. The root factory returns the traversal root of an application. It is conventionally named ``get_root``. An application may supply a root factory to :app:`Pyramid` during the construction of a :term:`Configurator`. If a root factory is not supplied, the application creates a default root object using the :term:`default root factory`. default root factory If an application does not register a :term:`root factory` at Pyramid configuration time, a *default* root factory is used to created the default root object. Use of the default root object is useful in application which use :term:`URL dispatch` for all URL-to-view code mappings, and does not (knowingly) use traversal otherwise. SQLAlchemy `SQLAlchemy `_ is an object relational mapper used in tutorials within this documentation. JSON `JavaScript Object Notation `_ is a data serialization format. jQuery A popular `Javascript library `_. renderer A serializer that can be referred to via :term:`view configuration` which converts a non-:term:`Response` return values from a :term:`view` into a string (and ultimately a response). Using a renderer can make writing views that require templating or other serialization less tedious. See :ref:`views_which_use_a_renderer` for more information. renderer factory A factory which creates a :term:`renderer`. See :ref:`adding_and_overriding_renderers` for more information. mod_wsgi `mod_wsgi `_ is an Apache module developed by Graham Dumpleton. It allows :term:`WSGI` applications (such as applications developed using :app:`Pyramid`) to be served using the Apache web server. view predicate An argument to a :term:`view configuration` which evaluates to ``True`` or ``False`` for a given :term:`request`. All predicates attached to a view configuration must evaluate to true for the associated view to be considered as a possible callable for a given request. route predicate An argument to a :term:`route configuration` which implies a value that evaluates to ``True`` or ``False`` for a given :term:`request`. All predicates attached to a :term:`route configuration` must evaluate to ``True`` for the associated route to "match" the current request. If a route does not match the current request, the next route (in definition order) is attempted. routes mapper An object which compares path information from a request to an ordered set of route patterns. See :ref:`urldispatch_chapter`. predicate A test which returns ``True`` or ``False``. Two different types of predicates exist in :app:`Pyramid`: a :term:`view predicate` and a :term:`route predicate`. View predicates are attached to :term:`view configuration` and route predicates are attached to :term:`route configuration`. decorator A wrapper around a Python function or class which accepts the function or class as its first argument and which returns an arbitrary object. :app:`Pyramid` provides several decorators, used for configuration and return value modification purposes. .. seealso:: See also `PEP 318 `_. configuration declaration An individual method call made to a :term:`configuration directive`, such as registering a :term:`view configuration` (via the :meth:`~pyramid.config.Configurator.add_view` method of the configurator) or :term:`route configuration` (via the :meth:`~pyramid.config.Configurator.add_route` method of the configurator). A set of configuration declarations is also implied by the :term:`configuration decoration` detected by a :term:`scan` of code in a package. configuration decoration Metadata implying one or more :term:`configuration declaration` invocations. Often set by configuration Python :term:`decorator` attributes, such as :class:`pyramid.view.view_config`, aka ``@view_config``. scan The term used by :app:`Pyramid` to define the process of importing and examining all code in a Python package or module for :term:`configuration decoration`. configurator An object used to do :term:`configuration declaration` within an application. The most common configurator is an instance of the :class:`pyramid.config.Configurator` class. imperative configuration The configuration mode in which you use Python to call methods on a :term:`Configurator` in order to add each :term:`configuration declaration` required by your application. declarative configuration The configuration mode in which you use the combination of :term:`configuration decoration` and a :term:`scan` to configure your Pyramid application. Not Found View An :term:`exception view` invoked by :app:`Pyramid` when the developer explicitly raises a :class:`pyramid.httpexceptions.HTTPNotFound` exception from within :term:`view` code or :term:`root factory` code, or when the current request doesn't match any :term:`view configuration`. :app:`Pyramid` provides a default implementation of a Not Found View; it can be overridden. See :ref:`changing_the_notfound_view`. Forbidden view An :term:`exception view` invoked by :app:`Pyramid` when the developer explicitly raises a :class:`pyramid.httpexceptions.HTTPForbidden` exception from within :term:`view` code or :term:`root factory` code, or when the :term:`view configuration` and :term:`authorization policy` found for a request disallows a particular view invocation. :app:`Pyramid` provides a default implementation of a forbidden view; it can be overridden. See :ref:`changing_the_forbidden_view`. Exception view An exception view is a :term:`view callable` which may be invoked by :app:`Pyramid` when an exception is raised during request processing. See :ref:`exception_views` for more information. HTTP Exception The set of exception classes defined in :mod:`pyramid.httpexceptions`. These can be used to generate responses with various status codes when raised or returned from a :term:`view callable`. .. seealso:: See also :ref:`http_exceptions`. thread local A thread-local variable is one which is essentially a global variable in terms of how it is accessed and treated, however, each `thread `_ used by the application may have a different value for this same "global" variable. :app:`Pyramid` uses a small number of thread local variables, as described in :ref:`threadlocals_chapter`. .. seealso:: See also the :class:`stdlib documentation ` for more information. multidict An ordered dictionary that can have multiple values for each key. Adds the methods ``getall``, ``getone``, ``mixed``, ``add`` and ``dict_of_lists`` to the normal dictionary interface. See :ref:`multidict_narr` and :class:`pyramid.interfaces.IMultiDict`. PyPI `The Python Package Index `_, a collection of software available for Python. Agendaless Consulting A consulting organization formed by Paul Everitt, Tres Seaver, and Chris McDonough. .. seealso:: See also `Agendaless Consulting `_. Jython A `Python implementation `_ written for the Java Virtual Machine. Python The `programming language `_ in which :app:`Pyramid` is written. CPython The C implementation of the Python language. This is the reference implementation that most people refer to as simply "Python"; :term:`Jython`, Google's App Engine, and `PyPy `_ are examples of non-C based Python implementations. View Lookup The act of finding and invoking the "best" :term:`view callable`, given a :term:`request` and a :term:`context` resource. Resource Location The act of locating a :term:`context` resource given a :term:`request`. :term:`Traversal` and :term:`URL dispatch` are the resource location subsystems used by :app:`Pyramid`. Google App Engine `Google App Engine `_ (aka "GAE") is a Python application hosting service offered by Google. :app:`Pyramid` runs on GAE. Venusian :ref:`Venusian` is a library which allows framework authors to defer decorator actions. Instead of taking actions when a function (or class) decorator is executed at import time, the action usually taken by the decorator is deferred until a separate "scan" phase. :app:`Pyramid` relies on Venusian to provide a basis for its :term:`scan` feature. Translation String An instance of :class:`pyramid.i18n.TranslationString`, which is a class that behaves like a Unicode string, but has several extra attributes such as ``domain``, ``msgid``, and ``mapping`` for use during translation. Translation strings are usually created by hand within software, but are sometimes created on the behalf of the system for automatic template translation. For more information, see :ref:`i18n_chapter`. Translation Domain A string representing the "context" in which a translation was 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 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 `_ for more information. Translator A callable which receives a :term:`translation string` and returns a translated Unicode object for the purposes of internationalization. A :term:`localizer` supplies a translator to a :app:`Pyramid` application accessible via its :class:`~pyramid.i18n.Localizer.translate` method. Translation Directory A translation directory is a :term:`gettext` translation directory. It contains language folders, which themselves contain ``LC_MESSAGES`` folders, which contain ``.mo`` files. Each ``.mo`` file represents a set of translations for a language in a :term:`translation domain`. The name of the ``.mo`` file (minus the .mo extension) is the translation domain name. Localizer An instance of the class :class:`pyramid.i18n.Localizer` which provides translation and pluralization services to an application. It is retrieved via the :func:`pyramid.i18n.get_localizer` function. Locale Name A string like ``en``, ``en_US``, ``de``, or ``de_AT`` which uniquely identifies a particular locale. Default Locale Name The :term:`locale name` used by an application when no explicit locale name is set. See :ref:`localization_deployment_settings`. Locale Negotiator An object supplying a policy determining which :term:`locale name` best represents a given :term:`request`. It is used by the :func:`pyramid.i18n.get_locale_name`, and :func:`pyramid.i18n.negotiate_locale_name` functions, and indirectly by :func:`pyramid.i18n.get_localizer`. The :func:`pyramid.i18n.default_locale_negotiator` function is an example of a locale negotiator. Gettext The GNU `gettext `_ library, used by the :app:`Pyramid` translation machinery. Babel A `collection of tools `_ for internationalizing Python applications. :app:`Pyramid` does not depend on Babel to operate, but if Babel is installed, additional locale functionality becomes available to your application. Lingua A package by Wichert Akkerman which provides the ``pot-create`` command to extract translateable messages from Python sources and Chameleon ZPT template files. Message Identifier A string used as a translation lookup key during localization. The ``msgid`` argument to a :term:`translation string` is a message identifier. Message identifiers are also present in a :term:`message catalog`. Message Catalog A :term:`gettext` ``.mo`` file containing translations. Internationalization The act of creating software with a user interface that can potentially be displayed in more than one language or cultural context. Often shortened to "i18n" (because the word "internationalization" is I, 18 letters, then N). .. seealso:: See also :term:`Localization`. Localization The process of displaying the user interface of an internationalized application in a particular language or cultural context. Often shortened to "l10" (because the word "localization" is L, 10 letters, then N). .. seealso:: See also :term:`Internationalization`. renderer globals Values injected as names into a renderer by a :class:`pyramid.event.BeforeRender` event. response callback A user-defined callback executed by the :term:`router` at a point after a :term:`response` object is successfully created. .. seealso:: See also :ref:`using_response_callbacks`. finished callback A user-defined callback executed by the :term:`router` unconditionally at the very end of request processing . See :ref:`using_finished_callbacks`. pregenerator A pregenerator is a function associated by a developer with a :term:`route`. It is called by :meth:`~pyramid.request.Request.route_url` in order to adjust the set of arguments passed to it by the user for special purposes. It will influence the URL returned by :meth:`~pyramid.request.Request.route_url`. See :class:`pyramid.interfaces.IRoutePregenerator` for more information. session A namespace that is valid for some period of continual activity that can be used to represent a user's interaction with a web application. session factory A callable, which, when called with a single argument named ``request`` (a :term:`request` object), returns a :term:`session` object. See :ref:`using_the_default_session_factory`, :ref:`using_alternate_session_factories` and :meth:`pyramid.config.Configurator.set_session_factory` for more information. Mako `Mako `_ is a template language which refines the familiar ideas of componentized layout and inheritance using Python with Python scoping and calling semantics. View handler A view handler ties together :meth:`pyramid.config.Configurator.add_route` and :meth:`pyramid.config.Configurator.add_view` to make it more convenient to register a collection of views as a single class when using :term:`url dispatch`. View handlers ship as part of the :term:`pyramid_handlers` add-on package. Deployment settings Deployment settings are settings passed to the :term:`Configurator` as a ``settings`` argument. These are later accessible via a ``request.registry.settings`` dictionary in views or as ``config.registry.settings`` in configuration code. Deployment settings can be used as global application values. WebTest `WebTest `_ is a package which can help you write functional tests for your WSGI application. view mapper A view mapper is a class which implements the :class:`pyramid.interfaces.IViewMapperFactory` interface, which performs view argument and return value mapping. This is a plug point for extension builders, not normally used by "civilians". matchdict The dictionary attached to the :term:`request` object as ``request.matchdict`` when a :term:`URL dispatch` route has been matched. Its keys are names as identified within the route pattern; its values are the values matched by each pattern name. pyramid_zcml An add-on package to :app:`Pyramid` which allows applications to be configured via :term:`ZCML`. It is available on :term:`PyPI`. If you use :mod:`pyramid_zcml`, you can use ZCML as an alternative to :term:`imperative configuration` or :term:`configuration decoration`. ZCML `Zope Configuration Markup Language `_, an XML dialect used by Zope and :term:`pyramid_zcml` for configuration tasks. pyramid_handlers An add-on package which allows :app:`Pyramid` users to create classes that are analogues of Pylons 1 "controllers". See http://docs.pylonsproject.org/projects/pyramid_handlers/dev/ . pyramid_jinja2 :term:`Jinja2` templating system bindings for Pyramid, documented at http://docs.pylonsproject.org/projects/pyramid_jinja2/dev/ . This package also includes a scaffold named ``pyramid_jinja2_starter``, which creates an application package based on the Jinja2 templating system. Akhet `Akhet `_ 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 preferring a more Pylons-like API. The scaffold has been retired but the demo plays a similar role. Pyramid Cookbook Additional documentation for Pyramid which presents topical, practical uses of Pyramid: http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest. distutils The standard system for packaging and distributing Python packages. See http://docs.python.org/distutils/index.html for more information. :term:`setuptools` is actually an *extension* of the Distutils. exception response A :term:`response` that is generated as the result of a raised exception being caught by an :term:`exception view`. PyPy PyPy is an "alternative implementation of the Python language": http://pypy.org/ tween A bit of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses :app:`Pyramid` as its 'app'. The word "tween" is a contraction of "between". A tween may be used by Pyramid framework extensions, to provide, for example, Pyramid-specific view timing support, bookkeeping code that examines exceptions before they are returned to the upstream WSGI application, or a variety of other features. Tweens behave a bit like :term:`WSGI` :term:`middleware` but they have the benefit of running in a context in which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. See :ref:`registering_tweens`. pyramid_debugtoolbar A Pyramid add-on which displays a helpful debug toolbar "on top of" HTML pages rendered by your application, displaying request, routing, and database information. :mod:`pyramid_debugtoolbar` is configured into the ``development.ini`` of all applications which use a Pyramid :term:`scaffold`. For more information, see http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest/. scaffold A project template that generates some of the major parts of a Pyramid application and helps users to quickly get started writing larger applications. Scaffolds are usually used via the ``pcreate`` command. pyramid_exclog A package which logs Pyramid application exception (error) information to a standard Python logger. This add-on is most useful when used in production applications, because the logger can be configured to log to a file, to UNIX syslog, to the Windows Event Log, or even to email. See its `documentation `_. console script A script written to the ``bin`` (on UNIX, or ``Scripts`` on Windows) directory of a Python installation or :term:`virtualenv` as the result of running ``setup.py install`` or ``setup.py develop``. introspector An object with the methods described by :class:`pyramid.interfaces.IIntrospector` that is available in both configuration code (for registration) and at runtime (for querying) that allows a developer to introspect configuration statements and relationships between those statements. conflict resolution Pyramid attempts to resolve ambiguous configuration statements made by application developers via automatic conflict resolution. Automatic conflict resolution is described in :ref:`automatic_conflict_resolution`. If Pyramid cannot resolve ambiguous configuration statements, it is possible to manually resolve them as described in :ref:`manually_resolving_conflicts`. configuration directive A method of the :term:`Configurator` which causes a configuration action to occur. The method :meth:`pyramid.config.Configurator.add_view` is a configuration directive, and application developers can add their own directives as necessary (see :ref:`add_directive`). action Represents a pending configuration statement generated by a call to a :term:`configuration directive`. The set of pending configuration actions are processed when :meth:`pyramid.config.Configurator.commit` is called. discriminator The unique identifier of an :term:`action`. introspectable An object which implements the attributes and methods described in :class:`pyramid.interfaces.IIntrospectable`. Introspectables are used by the :term:`introspector` to display configuration information about a running Pyramid application. An introspectable is associated with a :term:`action` by virtue of the :meth:`pyramid.config.Configurator.action` method. asset descriptor An instance representing an :term:`asset specification` provided by the :meth:`pyramid.path.AssetResolver.resolve` method. It supports the methods and attributes documented in :class:`pyramid.interfaces.IAssetDescriptor`. Waitress A :term:`WSGI` server that runs on UNIX and Windows under Python 2.6+ and Python 3.2+. Projects generated via Pyramid scaffolding use Waitress as a WGSI server. See http://docs.pylonsproject.org/projects/waitress/en/latest/ for detailed information. Green Unicorn Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under Python 2.6+ or Python 3.1+. See http://gunicorn.org/ for detailed information. predicate factory A callable which is used by a third party during the registration of a route, view, or subscriber predicates to extend the configuration system. See :ref:`registering_thirdparty_predicates` for more information. add-on A Python :term:`distribution` that uses Pyramid's extensibility to plug into a Pyramid application and provide extra, configurable services. pyramid_redis_sessions A package by Eric Rasmussen which allows you to store Pyramid session data in a Redis database. See https://pypi.python.org/pypi/pyramid_redis_sessions for more information. cache busting A technique used when serving a cacheable static asset in order to force a client to query the new version of the asset. See :ref:`cache_busting` for more information. pyramid-1.6/docs/index.rst0000644000076500000240000001146012642135264016352 0ustar michaelstaff00000000000000.. _index: ========================= The Pyramid Web Framework ========================= :app:`Pyramid` is a small, fast, down-to-earth Python web framework. It is developed as part of the `Pylons Project `_. It is licensed under a `BSD-like license `_. Here is one of the simplest :app:`Pyramid` applications you can make: .. literalinclude:: narr/helloworld.py After you install :app:`Pyramid` and run this application, when you visit ``_ in a browser, you will see the text ``Hello, world!`` See :ref:`firstapp_chapter` for a full explanation of how this application works. .. _html_getting_started: Getting Started =============== If you are new to Pyramid, we have a few resources that can help you get up to speed right away. .. toctree:: :hidden: quick_tour quick_tutorial/index * :doc:`quick_tour` gives an overview of the major features in Pyramid, covering a little about a lot. * :doc:`quick_tutorial/index` is similar to the Quick Tour, but in a tutorial format, with somewhat deeper treatment of each topic and with working code. * Like learning by example? Visit the official :ref:`html_tutorials` as well as the community-contributed :ref:`Pyramid tutorials `, which include a :ref:`Todo List Application in One File `. * For help getting Pyramid set up, try :ref:`installing_chapter`. * Need help? See :ref:`Support and Development `. .. _html_tutorials: Tutorials ========= Official tutorials explaining how to use :app:`Pyramid` to build various types of applications, and how to deploy :app:`Pyramid` applications to various platforms. .. toctree:: :maxdepth: 1 tutorials/wiki2/index.rst tutorials/wiki/index.rst tutorials/modwsgi/index.rst .. _support-and-development: Support and Development ======================= The `Pylons Project web site `_ is the main online source of :app:`Pyramid` support and development information. To report bugs, use the `issue tracker `_. If you've got questions that aren't answered by this documentation, contact the `Pylons-discuss maillist `_ or join the `#pyramid IRC channel `_. Browse and check out tagged and trunk versions of :app:`Pyramid` via the `Pyramid GitHub repository `_. To check out the trunk via ``git``, use either command: .. code-block:: text # If you have SSH keys configured on GitHub: git clone git@github.com:Pylons/pyramid.git # Otherwise, HTTPS will work, using your GitHub login: git clone https://github.com/Pylons/pyramid.git To find out how to become a contributor to :app:`Pyramid`, please see the `contributor's section of the documentation `_. .. _html_narrative_documentation: Narrative Documentation ======================= Narrative documentation in chapter form explaining how to use :app:`Pyramid`. .. toctree:: :maxdepth: 2 narr/introduction narr/install narr/firstapp narr/configuration narr/project narr/startup narr/router narr/urldispatch narr/views narr/renderers narr/templates narr/viewconfig narr/assets narr/webob narr/sessions narr/events narr/environment narr/logging narr/paste narr/commandline narr/i18n narr/vhosting narr/testing narr/resources narr/hellotraversal narr/muchadoabouttraversal narr/traversal narr/security narr/hybrid narr/subrequest narr/hooks narr/introspector narr/extending narr/advconfig narr/extconfig narr/scaffolding narr/upgrading narr/threadlocals narr/zca API Documentation ================= Comprehensive reference material for every public API exposed by :app:`Pyramid`: .. toctree:: :maxdepth: 1 :glob: api/index api/* ``p*`` Scripts Documentation ============================ ``p*`` scripts included with :app:`Pyramid`:. .. toctree:: :maxdepth: 1 :glob: pscripts/index pscripts/* Change History ============== .. toctree:: :maxdepth: 1 whatsnew-1.6 whatsnew-1.5 whatsnew-1.4 whatsnew-1.3 whatsnew-1.2 whatsnew-1.1 whatsnew-1.0 changes Design Documents ================ .. toctree:: :maxdepth: 1 designdefense Copyright, Trademarks, and Attributions ======================================= .. toctree:: :maxdepth: 1 copyright Typographical Conventions ========================= .. toctree:: :maxdepth: 1 conventions Index and Glossary ================== * :ref:`glossary` * :ref:`genindex` * :ref:`search` .. toctree:: :hidden: glossary pyramid-1.6/docs/latexindex.rst0000644000076500000240000000261012520062551017376 0ustar michaelstaff00000000000000:orphan: .. _latexindex: ========================= The Pyramid Web Framework ========================= .. frontmatter:: Front Matter @@@@@@@@@@@@ .. toctree:: :maxdepth: 1 copyright.rst conventions.rst authorintro.rst .. mainmatter:: .. _narrative_documentation: Narrative Documentation @@@@@@@@@@@@@@@@@@@@@@@ .. toctree:: :maxdepth: 1 narr/introduction narr/install narr/firstapp narr/configuration narr/project narr/startup narr/router narr/urldispatch narr/views narr/renderers narr/templates narr/viewconfig narr/assets narr/webob narr/sessions narr/events narr/environment narr/logging narr/paste narr/commandline narr/i18n narr/vhosting narr/testing narr/resources narr/hellotraversal narr/muchadoabouttraversal narr/traversal narr/security narr/hybrid narr/subrequest narr/hooks narr/introspector narr/extending narr/advconfig narr/extconfig narr/scaffolding narr/upgrading narr/threadlocals narr/zca .. _tutorials: Tutorials @@@@@@@@@ .. toctree:: :maxdepth: 1 tutorials/wiki2/index.rst tutorials/wiki/index.rst tutorials/modwsgi/index.rst .. _api_documentation: API Documentation @@@@@@@@@@@@@@@@@ .. toctree:: :maxdepth: 1 :glob: api/* .. backmatter:: Glossary and Index @@@@@@@@@@@@@@@@@@ .. toctree:: :maxdepth: 1 glossary pyramid-1.6/docs/make_book0000755000076500000240000000014712234375161016365 0ustar michaelstaff00000000000000#!/bin/sh make clean latex SPHINXBUILD=../env/bin/sphinx-build BOOK=1 cd _build/latex && make all-pdf pyramid-1.6/docs/make_epub0000755000076500000240000000007612234375161016367 0ustar michaelstaff00000000000000#!/bin/sh make clean epub SPHINXBUILD=../env/bin/sphinx-build pyramid-1.6/docs/make_pdf0000755000076500000240000000014012234375161016175 0ustar michaelstaff00000000000000#!/bin/sh make clean latex SPHINXBUILD=../env/bin/sphinx-build cd _build/latex && make all-pdf pyramid-1.6/docs/Makefile0000644000076500000240000000600312537427524016154 0ustar michaelstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -W SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html web pickle htmlhelp latex changes linkcheck help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " pickle to make pickle files (usable by e.g. sphinx-web)" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" clean: -rm -rf $(BUILDDIR)/* html: mkdir -p $(BUILDDIR)/html $(BUILDDIR)/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." text: mkdir -p $(BUILDDIR)/text $(BUILDDIR)/doctrees $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/text." pickle: mkdir -p $(BUILDDIR)/pickle $(BUILDDIR)/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files or run" @echo " sphinx-web $(BUILDDIR)/pickle" @echo "to start the sphinx-web server." web: pickle htmlhelp: mkdir -p $(BUILDDIR)/htmlhelp $(BUILDDIR)/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." latex: mkdir -p $(BUILDDIR)/latex $(BUILDDIR)/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex cp _static/*.png $(BUILDDIR)/latex ./convert_images.sh cp _static/latex-warning.png $(BUILDDIR)/latex cp _static/latex-note.png $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make latexpdf' to build a PDF file from them." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF file is in $(BUILDDIR)/latex." changes: mkdir -p $(BUILDDIR)/changes $(BUILDDIR)/doctrees $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: mkdir -p $(BUILDDIR)/linkcheck $(BUILDDIR)/doctrees $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." pyramid-1.6/docs/narr/0000755000076500000240000000000012642137501015445 5ustar michaelstaff00000000000000pyramid-1.6/docs/narr/advconfig.rst0000644000076500000240000003547312621241570020152 0ustar michaelstaff00000000000000.. index:: pair: advanced; configuration .. _advconfig_narr: Advanced Configuration ====================== To support application extensibility, the :app:`Pyramid` :term:`Configurator` by default detects configuration conflicts and allows you to include configuration imperatively from other packages or modules. It also by default performs configuration in two separate phases. This allows you to ignore relative configuration statement ordering in some circumstances. .. index:: pair: configuration; conflict detection .. _conflict_detection: Conflict Detection ------------------ Here's a familiar example of one of the simplest :app:`Pyramid` applications, configured imperatively: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() When you start this application, all will be OK. However, what happens if we try to add another view to the configuration with the same set of :term:`predicate` arguments as one we've already added? .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def goodbye_world(request): return Response('Goodbye world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world, name='hello') # conflicting view configuration config.add_view(goodbye_world, name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() The application now has two conflicting view configuration statements. When we try to start it again, it won't start. Instead we'll receive a traceback that ends something like this: .. code-block:: text :linenos: Traceback (most recent call last): File "app.py", line 12, in app = config.make_wsgi_app() File "pyramid/config.py", line 839, in make_wsgi_app self.commit() File "pyramid/pyramid/config.py", line 473, in commit self._ctx.execute_actions() ... more code ... pyramid.exceptions.ConfigurationConflictError: Conflicting configuration actions For: ('view', None, '', None, , None, None, None, None, None, False, None, None, None) Line 14 of file app.py in : 'config.add_view(hello_world)' Line 17 of file app.py in : 'config.add_view(goodbye_world)' This traceback is trying to tell us: - We've got conflicting information for a set of view configuration statements (The ``For:`` line). - There are two statements which conflict, shown beneath the ``For:`` line: ``config.add_view(hello_world. 'hello')`` on line 14 of ``app.py``, and ``config.add_view(goodbye_world, 'hello')`` on line 17 of ``app.py``. These two configuration statements are in conflict because we've tried to tell the system that the set of :term:`predicate` values for both view configurations are exactly the same. Both the ``hello_world`` and ``goodbye_world`` views are configured to respond under the same set of circumstances. This circumstance, the :term:`view name` represented by the ``name=`` predicate, is ``hello``. This presents an ambiguity that :app:`Pyramid` cannot resolve. Rather than allowing the circumstance to go unreported, by default Pyramid raises a :exc:`ConfigurationConflictError` error and prevents the application from running. Conflict detection happens for any kind of configuration: imperative configuration or configuration that results from the execution of a :term:`scan`. .. _manually_resolving_conflicts: Manually Resolving Conflicts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are a number of ways to manually resolve conflicts: by changing registrations to not conflict, by strategically using :meth:`pyramid.config.Configurator.commit`, or by using an "autocommitting" configurator. The Right Thing +++++++++++++++ The most correct way to resolve conflicts is to "do the needful": change your configuration code to not have conflicting configuration statements. The details of how this is done depends entirely on the configuration statements made by your application. Use the detail provided in the :exc:`ConfigurationConflictError` to track down the offending conflicts and modify your configuration code accordingly. If you're getting a conflict while trying to extend an existing application, and that application has a function which performs configuration like this one: .. code-block:: python :linenos: def add_routes(config): config.add_route(...) Don't call this function directly with ``config`` as an argument. Instead, use :meth:`pyramid.config.Configurator.include`: .. code-block:: python :linenos: config.include(add_routes) Using :meth:`~pyramid.config.Configurator.include` instead of calling the function directly provides a modicum of automated conflict resolution, with the configuration statements you define in the calling code overriding those of the included function. .. seealso:: See also :ref:`automatic_conflict_resolution` and :ref:`including_configuration`. Using ``config.commit()`` +++++++++++++++++++++++++ You can manually commit a configuration by using the :meth:`~pyramid.config.Configurator.commit` method between configuration calls. For example, we prevent conflicts from occurring in the application we examined previously as the result of adding a ``commit``. Here's the application that generates conflicts: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def goodbye_world(request): return Response('Goodbye world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world, name='hello') # conflicting view configuration config.add_view(goodbye_world, name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() We can prevent the two ``add_view`` calls from conflicting by issuing a call to :meth:`~pyramid.config.Configurator.commit` between them: .. code-block:: python :linenos: :emphasize-lines: 16 from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') def goodbye_world(request): return Response('Goodbye world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world, name='hello') config.commit() # commit any pending configuration actions # no-longer-conflicting view configuration config.add_view(goodbye_world, name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() In the above example we've issued a call to :meth:`~pyramid.config.Configurator.commit` between the two ``add_view`` calls. :meth:`~pyramid.config.Configurator.commit` will execute any pending configuration statements. Calling :meth:`~pyramid.config.Configurator.commit` is safe at any time. It executes all pending configuration actions and leaves the configuration action list "clean". Note that :meth:`~pyramid.config.Configurator.commit` has no effect when you're using an *autocommitting* configurator (see :ref:`autocommitting_configurator`). .. _autocommitting_configurator: Using an Autocommitting Configurator ++++++++++++++++++++++++++++++++++++ You can also use a heavy hammer to circumvent conflict detection by using a configurator constructor parameter: ``autocommit=True``. For example: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': config = Configurator(autocommit=True) When the ``autocommit`` parameter passed to the Configurator is ``True``, conflict detection (and :ref:`twophase_config`) is disabled. Configuration statements will be executed immediately, and succeeding statements will override preceding ones. :meth:`~pyramid.config.Configurator.commit` has no effect when ``autocommit`` is ``True``. If you use a Configurator in code that performs unit testing, it's usually a good idea to use an autocommitting Configurator, because you are usually unconcerned about conflict detection or two-phase configuration in test code. .. _automatic_conflict_resolution: Automatic Conflict Resolution ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If your code uses the :meth:`~pyramid.config.Configurator.include` method to include external configuration, some conflicts are automatically resolved. Configuration statements that are made as the result of an "include" will be overridden by configuration statements that happen within the caller of the "include" method. Automatic conflict resolution supports this goal. If a user wants to reuse a Pyramid application, and they want to customize the configuration of this application without hacking its code "from outside", they can "include" a configuration function from the package and override only some of its configuration statements within the code that does the include. No conflicts will be generated by configuration statements within the code that does the including, even if configuration statements in the included code would conflict if it was moved "up" to the calling code. Methods Which Provide Conflict Detection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ These are the methods of the configurator which provide conflict detection: :meth:`~pyramid.config.Configurator.add_view`, :meth:`~pyramid.config.Configurator.add_route`, :meth:`~pyramid.config.Configurator.add_renderer`, :meth:`~pyramid.config.Configurator.add_request_method`, :meth:`~pyramid.config.Configurator.set_request_factory`, :meth:`~pyramid.config.Configurator.set_session_factory`, :meth:`~pyramid.config.Configurator.set_request_property`, :meth:`~pyramid.config.Configurator.set_root_factory`, :meth:`~pyramid.config.Configurator.set_view_mapper`, :meth:`~pyramid.config.Configurator.set_authentication_policy`, :meth:`~pyramid.config.Configurator.set_authorization_policy`, :meth:`~pyramid.config.Configurator.set_locale_negotiator`, :meth:`~pyramid.config.Configurator.set_default_permission`, :meth:`~pyramid.config.Configurator.add_traverser`, :meth:`~pyramid.config.Configurator.add_resource_url_adapter`, and :meth:`~pyramid.config.Configurator.add_response_adapter`. :meth:`~pyramid.config.Configurator.add_static_view` also indirectly provides conflict detection, because it's implemented in terms of the conflict-aware ``add_route`` and ``add_view`` methods. .. index:: pair: configuration; including from external sources .. _including_configuration: Including Configuration from External Sources --------------------------------------------- Some application programmers will factor their configuration code in such a way that it is easy to reuse and override configuration statements. For example, such a developer might factor out a function used to add routes to their application: .. code-block:: python :linenos: def add_routes(config): config.add_route(...) Rather than calling this function directly with ``config`` as an argument, instead use :meth:`pyramid.config.Configurator.include`: .. code-block:: python :linenos: config.include(add_routes) Using ``include`` rather than calling the function directly will allow :ref:`automatic_conflict_resolution` to work. :meth:`~pyramid.config.Configurator.include` can also accept a :term:`module` as an argument: .. code-block:: python :linenos: import myapp config.include(myapp) For this to work properly, the ``myapp`` module must contain a callable with the special name ``includeme``, which should perform configuration (like the ``add_routes`` callable we showed above as an example). :meth:`~pyramid.config.Configurator.include` can also accept a :term:`dotted Python name` to a function or a module. .. note:: See :ref:`the_include_tag` for a declarative alternative to the :meth:`~pyramid.config.Configurator.include` method. .. _twophase_config: Two-Phase Configuration ----------------------- When a non-autocommitting :term:`Configurator` is used to do configuration (the default), configuration execution happens in two phases. In the first phase, "eager" configuration actions (actions that must happen before all others, such as registering a renderer) are executed, and *discriminators* are computed for each of the actions that depend on the result of the eager actions. In the second phase, the discriminators of all actions are compared to do conflict detection. Due to this, for configuration methods that have no internal ordering constraints, execution order of configuration method calls is not important. For example, the relative ordering of :meth:`~pyramid.config.Configurator.add_view` and :meth:`~pyramid.config.Configurator.add_renderer` is unimportant when a non-autocommitting configurator is used. This code snippet: .. code-block:: python :linenos: config.add_view('some.view', renderer='path_to_custom/renderer.rn') config.add_renderer('.rn', SomeCustomRendererFactory) Has the same result as: .. code-block:: python :linenos: config.add_renderer('.rn', SomeCustomRendererFactory) config.add_view('some.view', renderer='path_to_custom/renderer.rn') Even though the view statement depends on the registration of a custom renderer, due to two-phase configuration, the order in which the configuration statements are issued is not important. ``add_view`` will be able to find the ``.rn`` renderer even if ``add_renderer`` is called after ``add_view``. The same is untrue when you use an *autocommitting* configurator (see :ref:`autocommitting_configurator`). When an autocommitting configurator is used, two-phase configuration is disabled, and configuration statements must be ordered in dependency order. Some configuration methods, such as :meth:`~pyramid.config.Configurator.add_route` have internal ordering constraints: the routes they imply require relative ordering. Such ordering constraints are not absolved by two-phase configuration. Routes are still added in configuration execution order. More Information ---------------- For more information, see the article `"A Whirlwind Tour of Advanced Configuration Tactics" `_ in the Pyramid Cookbook. pyramid-1.6/docs/narr/assets.rst0000644000076500000240000011063512642137126017512 0ustar michaelstaff00000000000000.. index:: single: assets single: static asssets .. _assets_chapter: Static Assets ============= An :term:`asset` is any file contained within a Python :term:`package` which is *not* a Python source code file. For example, each of the following is an asset: - a GIF image file contained within a Python package or contained within any subdirectory of a Python package. - a CSS file contained within a Python package or contained within any subdirectory of a Python package. - a JavaScript source file contained within a Python package or contained within any subdirectory of a Python package. - A directory within a package that does not have an ``__init__.py`` in it (if it possessed an ``__init__.py`` it would *be* a package). - a :term:`Chameleon` or :term:`Mako` template file contained within a Python package. The use of assets is quite common in most web development projects. For example, when you create a :app:`Pyramid` application using one of the available scaffolds, as described in :ref:`creating_a_project`, the directory representing the application contains a Python :term:`package`. Within that Python package, there are directories full of files which are static assets. For example, there's a ``static`` directory which contains ``.css``, ``.js``, and ``.gif`` files. These asset files are delivered when a user visits an application URL. .. index:: single: asset specifications .. _asset_specifications: Understanding Asset Specifications ---------------------------------- Let's imagine you've created a :app:`Pyramid` application that uses a :term:`Chameleon` ZPT template via the :func:`pyramid.renderers.render_to_response` API. For example, the application might address the asset using the :term:`asset specification` ``myapp:templates/some_template.pt`` using that API within a ``views.py`` file inside a ``myapp`` package: .. code-block:: python :linenos: from pyramid.renderers import render_to_response render_to_response('myapp:templates/some_template.pt', {}, request) "Under the hood", when this API is called, :app:`Pyramid` attempts to make sense out of the string ``myapp:templates/some_template.pt`` provided by the developer. This string is an :term:`asset specification`. It is composed of two parts: - The *package name* (``myapp``) - The *asset name* (``templates/some_template.pt``), relative to the package directory. The two parts are separated by a colon ``:`` character. :app:`Pyramid` uses the Python :term:`pkg_resources` API to resolve the package name and asset name to an absolute (operating system-specific) file name. It eventually passes this resolved absolute filesystem path to the Chameleon templating engine, which then uses it to load, parse, and execute the template file. There is a second form of asset specification: a *relative* asset specification. Instead of using an "absolute" asset specification which includes the package name, in certain circumstances you can omit the package name from the specification. For example, you might be able to use ``templates/mytemplate.pt`` instead of ``myapp:templates/some_template.pt``. Such asset specifications are usually relative to a "current package". The "current package" is usually the package which contains the code that *uses* the asset specification. :app:`Pyramid` APIs which accept relative asset specifications typically describe to what the asset is relative in their individual documentation. .. index:: single: add_static_view pair: assets; serving .. _static_assets_section: Serving Static Assets --------------------- :app:`Pyramid` makes it possible to serve up static asset files from a directory on a filesystem to an application user's browser. Use the :meth:`pyramid.config.Configurator.add_static_view` to instruct :app:`Pyramid` to serve static assets, such as JavaScript and CSS files. This mechanism makes a directory of static files available at a name relative to the application root URL, e.g., ``/static``, or as an external URL. .. note:: :meth:`~pyramid.config.Configurator.add_static_view` cannot serve a single file, nor can it serve a directory of static files directly relative to the root URL of a :app:`Pyramid` application. For these features, see :ref:`advanced_static`. Here's an example of a use of :meth:`~pyramid.config.Configurator.add_static_view` that will serve files up from the ``/var/www/static`` directory of the computer which runs the :app:`Pyramid` application as URLs beneath the ``/static`` URL prefix. .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='/var/www/static') The ``name`` represents a URL *prefix*. In order for files that live in the ``path`` directory to be served, a URL that requests one of them must begin with that prefix. In the example above, ``name`` is ``static`` and ``path`` is ``/var/www/static``. In English this means that you wish to serve the files that live in ``/var/www/static`` as sub-URLs of the ``/static`` URL prefix. Therefore, the file ``/var/www/static/foo.css`` will be returned when the user visits your application's URL ``/static/foo.css``. A static directory named at ``path`` may contain subdirectories recursively, and any subdirectories may hold files; these will be resolved by the static view as you would expect. The ``Content-Type`` header returned by the static view for each particular type of file is dependent upon its file extension. By default, all files made available via :meth:`~pyramid.config.Configurator.add_static_view` are accessible by completely anonymous users. Simple authorization can be required, however. To protect a set of static files using a permission, in addition to passing the required ``name`` and ``path`` arguments, also pass the ``permission`` keyword argument to :meth:`~pyramid.config.Configurator.add_static_view`. The value of the ``permission`` argument represents the :term:`permission` that the user must have relative to the current :term:`context` when the static view is invoked. A user will be required to possess this permission to view any of the files represented by ``path`` of the static view. If your static assets must be protected by a more complex authorization scheme, see :ref:`advanced_static`. Here's another example that uses an :term:`asset specification` instead of an absolute path as the ``path`` argument. To convince :meth:`~pyramid.config.Configurator.add_static_view` to serve files up under the ``/static`` URL from the ``a/b/c/static`` directory of the Python package named ``some_package``, we can use a fully qualified :term:`asset specification` as the ``path``: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='some_package:a/b/c/static') The ``path`` provided to :meth:`~pyramid.config.Configurator.add_static_view` may be a fully qualified :term:`asset specification` or an *absolute path*. Instead of representing a URL prefix, the ``name`` argument of a call to :meth:`~pyramid.config.Configurator.add_static_view` can alternately be a *URL*. Each of the examples we've seen so far have shown usage of the ``name`` argument as a URL prefix. However, when ``name`` is a *URL*, static assets can be served from an external webserver. In this mode, the ``name`` is used as the URL prefix when generating a URL using :meth:`pyramid.request.Request.static_url`. For example, :meth:`~pyramid.config.Configurator.add_static_view` may be fed a ``name`` argument which is ``http://example.com/images``: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_static_view(name='http://example.com/images', path='mypackage:images') Because :meth:`~pyramid.config.Configurator.add_static_view` is provided with a ``name`` argument that is the URL ``http://example.com/images``, subsequent calls to :meth:`~pyramid.request.Request.static_url` with paths that start with the ``path`` argument passed to :meth:`~pyramid.config.Configurator.add_static_view` will generate a URL something like ``http://example.com/images/logo.png``. The external webserver listening on ``example.com`` must be itself configured to respond properly to such a request. The :meth:`~pyramid.request.Request.static_url` API is discussed in more detail later in this chapter. .. index:: single: generating static asset urls single: static asset urls pair: assets; generating urls .. _generating_static_asset_urls: Generating Static Asset URLs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When an :meth:`~pyramid.config.Configurator.add_static_view` method is used to register a static asset directory, a special helper API named :meth:`pyramid.request.Request.static_url` can be used to generate the appropriate URL for an asset that lives in one of the directories named by the static registration ``path`` attribute. For example, let's assume you create a set of static declarations like so: .. code-block:: python :linenos: config.add_static_view(name='static1', path='mypackage:assets/1') config.add_static_view(name='static2', path='mypackage:assets/2') These declarations create URL-accessible directories which have URLs that begin with ``/static1`` and ``/static2``, respectively. The assets in the ``assets/1`` directory of the ``mypackage`` package are consulted when a user visits a URL which begins with ``/static1``, and the assets in the ``assets/2`` directory of the ``mypackage`` package are consulted when a user visits a URL which begins with ``/static2``. You needn't generate the URLs to static assets "by hand" in such a configuration. Instead, use the :meth:`~pyramid.request.Request.static_url` API to generate them for you. For example: .. code-block:: python :linenos: from pyramid.renderers import render_to_response def my_view(request): css_url = request.static_url('mypackage:assets/1/foo.css') js_url = request.static_url('mypackage:assets/2/foo.js') return render_to_response('templates/my_template.pt', dict(css_url=css_url, js_url=js_url), request=request) If the request "application URL" of the running system is ``http://example.com``, the ``css_url`` generated above would be: ``http://example.com/static1/foo.css``. The ``js_url`` generated above would be ``http://example.com/static2/foo.js``. One benefit of using the :meth:`~pyramid.request.Request.static_url` function rather than constructing static URLs "by hand" is that if you need to change the ``name`` of a static URL declaration, the generated URLs will continue to resolve properly after the rename. URLs may also be generated by :meth:`~pyramid.request.Request.static_url` to static assets that live *outside* the :app:`Pyramid` application. This will happen when the :meth:`~pyramid.config.Configurator.add_static_view` API associated with the path fed to :meth:`~pyramid.request.Request.static_url` is a *URL* instead of a view name. For example, the ``name`` argument may be ``http://example.com`` while the ``path`` given may be ``mypackage:images``: .. code-block:: python :linenos: config.add_static_view(name='http://example.com/images', path='mypackage:images') Under such a configuration, the URL generated by ``static_url`` for assets which begin with ``mypackage:images`` will be prefixed with ``http://example.com/images``: .. code-block:: python :linenos: request.static_url('mypackage:images/logo.png') # -> http://example.com/images/logo.png Using :meth:`~pyramid.request.Request.static_url` in conjunction with a :meth:`~pyramid.config.Configurator.add_static_view` makes it possible 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). For example, we may define a :ref:`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 in 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 specification. .. versionadded:: 1.6 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 :term:`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 the asset, 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 using :meth:`~pyramid.config.Configurator.add_cache_buster`: .. code-block:: python :linenos: import time from pyramid.static import QueryStringConstantCacheBuster # config is an instance of pyramid.config.Configurator config.add_static_view(name='static', path='mypackage:folder/static/') config.add_cache_buster( 'mypackage:folder/static/', QueryStringConstantCacheBuster(str(int(time.time())))) Adding the cachebuster instructs :app:`Pyramid` to add the current time for a static asset to the query string 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/js/myapp.js?x=1445318121' When the web server restarts, the time constant will change and therefore so will its URL. .. note:: Cache busting is an inherently complex topic as it integrates the asset pipeline and the web application. It is expected and desired that application authors will write their own cache buster implementations conforming to the properties of their own asset pipelines. See :ref:`custom_cache_busters` for information on writing your own. 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_cache_buster`. To do this set the ``PYRAMID_PREVENT_CACHEBUST`` environment variable or the ``pyramid.prevent_cachebust`` configuration value to a true value. .. _custom_cache_busters: Customizing the Cache Buster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Calls to :meth:`~pyramid.config.Configurator.add_cache_buster` may use any object that implements the interface :class:`~pyramid.interfaces.ICacheBuster`. :app:`Pyramid` ships with a very simplistic :class:`~pyramid.static.QueryStringConstantCacheBuster`, which adds an arbitrary token you provide to the query string of the asset's URL. This is almost never what you want in production as it does not allow fine-grained busting of individual assets. 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 :class:`~pyramid.static.QueryStringCacheBuster` and define a ``tokenize(pathspec)`` method. Here is an example which uses Git to get the hash of the current commit: .. code-block:: python :linenos: import os import subprocess from pyramid.static import QueryStringCacheBuster class GitCacheBuster(QueryStringCacheBuster): """ Assuming your code is installed as a Git checkout, as opposed to 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, param='x', repo_path=None): super(GitCacheBuster, self).__init__(param=param) if repo_path is None: repo_path = os.path.dirname(os.path.abspath(__file__)) self.sha1 = subprocess.check_output( ['git', 'rev-parse', 'HEAD'], cwd=repo_path).strip() def tokenize(self, pathspec): return self.sha1 A simple cache buster that modifies the path segment can be constructed as well: .. code-block:: python :linenos: import posixpath class PathConstantCacheBuster(object): def __init__(self, token): self.token = token def __call__(self, request, subpath, kw): base_subpath, ext = posixpath.splitext(subpath) new_subpath = base_subpath + self.token + ext return new_subpath, kw The caveat with this approach is that modifying the path segment changes the file name, and thus must match what is actually on the filesystem in order for :meth:`~pyramid.config.Configurator.add_static_view` to find the file. It's better to use the :class:`~pyramid.static.ManifestCacheBuster` for these situations, as described in the next section. .. _path_segment_cache_busters: Path Segments and Choosing a Cache Buster ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Many caching HTTP proxies will fail to cache a resource if the URL contains a query string. Therefore, in general, you should prefer a cache busting strategy which modifies the path segment rather than methods which add a token to the query string. You will need to consider whether the :app:`Pyramid` application will be serving your static assets, whether you are using an external asset pipeline to handle rewriting urls internal to the css/javascript, and how fine-grained do you want the cache busting tokens to be. In many cases you will want to host the static assets on another web server or externally on a CDN. In these cases your :app:`Pyramid` application may not even have access to a copy of the static assets. In order to cache bust these assets you will need some information about them. If you are using an external asset pipeline to generate your static files you should consider using the :class:`~pyramid.static.ManifestCacheBuster`. This cache buster can load a standard JSON formatted file generated by your pipeline and use it to cache bust the assets. This has many performance advantages as :app:`Pyramid` does not need to look at the files to generate any cache busting tokens, but still supports fine-grained per-file tokens. Assuming an example ``manifest.json`` like: .. code-block:: json { "css/main.css": "css/main-678b7c80.css", "images/background.png": "images/background-a8169106.png" } The following code would set up a cachebuster: .. code-block:: python :linenos: from pyramid.static import ManifestCacheBuster config.add_static_view( name='http://mycdn.example.com/', path='mypackage:static') config.add_cache_buster( 'mypackage:static/', ManifestCacheBuster('myapp:static/manifest.json')) It's important to note that the cache buster only handles generating cache-busted URLs for static assets. It does **NOT** provide any solutions for serving those assets. For example, if you generated a URL for ``css/main-678b7c80.css`` then that URL needs to be valid either by configuring ``add_static_view`` properly to point to the location of the files or some other mechanism such as the files existing on your CDN or rewriting the incoming URL to remove the cache bust tokens. .. index:: single: static assets view CSS and JavaScript source and cache busting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Often one needs to refer to images and other static assets inside CSS and JavaScript files. If cache busting is active, the final static asset URL is not available until the static assets have been assembled. These URLs cannot be handwritten. Below is an example of how to integrate the cache buster into the entire stack. Remember, it is just an example and should be modified to fit your specific tools. * First, process the files by using a precompiler which rewrites URLs to their final cache-busted form. Then, you can use the :class:`~pyramid.static.ManifestCacheBuster` to synchronize your asset pipeline with :app:`Pyramid`, allowing the pipeline to have full control over the final URLs of your assets. Now that you are able to generate static URLs within :app:`Pyramid`, you'll need to handle URLs that are out of our control. To do this you may use some of the following options to get started: * Configure your asset pipeline to rewrite URL references inline in CSS and JavaScript. This is the best approach because then the files may be hosted by :app:`Pyramid` or an external CDN without having to change anything. They really are static. * Templatize JS and CSS, and call ``request.static_url()`` inside their template code. While this approach may work in certain scenarios, it is not recommended because your static assets will not really be static and are now dependent on :app:`Pyramid` to be served correctly. See :ref:`advanced_static` for more information on this approach. If your CSS and JavaScript assets use URLs to reference other assets it is recommended that you implement an external asset pipeline that can rewrite the generated static files with new URLs containing cache busting tokens. The machinery inside :app:`Pyramid` will not help with this step as it has very little knowledge of the asset types your application may use. The integration into :app:`Pyramid` is simply for linking those assets into your HTML and other dynamic content. .. _advanced_static: Advanced: Serving Static Assets Using a View Callable ----------------------------------------------------- For more flexibility, static assets can be served by a :term:`view callable` which you register manually. For example, if you're using :term:`URL dispatch`, you may want static assets to only be available as a fallback if no previous route matches. Alternatively, you might like to serve a particular static asset manually, because its download requires authentication. Note that you cannot use the :meth:`~pyramid.request.Request.static_url` API to generate URLs against assets made accessible by registering a custom static view. Root-Relative Custom Static View (URL Dispatch Only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :class:`pyramid.static.static_view` helper class generates a Pyramid view callable. This view callable can serve static assets from a directory. An instance of this class is actually used by the :meth:`~pyramid.config.Configurator.add_static_view` configuration method, so its behavior is almost exactly the same once it's configured. .. warning:: The following example *will not work* for applications that use :term:`traversal`; it will only work if you use :term:`URL dispatch` exclusively. The root-relative route we'll be registering will always be matched before traversal takes place, subverting any views registered via ``add_view`` (at least those without a ``route_name``). A :class:`~pyramid.static.static_view` static view cannot be made root-relative when you use traversal unless it's registered as a :term:`Not Found View`. To serve files within a directory located on your filesystem at ``/path/to/static/dir`` as the result of a "catchall" route hanging from the root that exists at the end of your routing table, create an instance of the :class:`~pyramid.static.static_view` class inside a ``static.py`` file in your application root as below. .. code-block:: python :linenos: from pyramid.static import static_view static_view = static_view('/path/to/static/dir', use_subpath=True) .. note:: For better cross-system flexibility, use an :term:`asset specification` as the argument to :class:`~pyramid.static.static_view` instead of a physical absolute filesystem path, e.g., ``mypackage:static``, instead of ``/path/to/mypackage/static``. Subsequently, you may wire the files that are served by this view up to be accessible as ``/`` using a configuration method in your application's startup code. .. code-block:: python :linenos: # .. every other add_route declaration should come # before this one, as it will, by default, catch all requests config.add_route('catchall_static', '/*subpath') config.add_view('myapp.static.static_view', route_name='catchall_static') The special name ``*subpath`` above is used by the :class:`~pyramid.static.static_view` view callable to signify the path of the file relative to the directory you're serving. Registering a View Callable to Serve a "Static" Asset ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can register a simple view callable to serve a single static asset. To do so, do things "by hand". First define the view callable. .. code-block:: python :linenos: import os from pyramid.response import FileResponse def favicon_view(request): here = os.path.dirname(__file__) icon = os.path.join(here, 'static', 'favicon.ico') return FileResponse(icon, request=request) The above bit of code within ``favicon_view`` computes "here", which is a path relative to the Python file in which the function is defined. It then creates a :class:`pyramid.response.FileResponse` using the file path as the response's ``path`` argument and the request as the response's ``request`` argument. :class:`pyramid.response.FileResponse` will serve the file as quickly as possible when it's used this way. It makes sure to set the right content length and content_type, too, based on the file extension of the file you pass. You might register such a view via configuration as a view callable that should be called as the result of a traversal: .. code-block:: python :linenos: config.add_view('myapp.views.favicon_view', name='favicon.ico') Or you might register it to be the view callable for a particular route: .. code-block:: python :linenos: config.add_route('favicon', '/favicon.ico') config.add_view('myapp.views.favicon_view', route_name='favicon') Because this is a simple view callable, it can be protected with a :term:`permission` or can be configured to respond under different circumstances using :term:`view predicate` arguments. .. index:: pair: overriding; assets .. _overriding_assets_section: Overriding Assets ----------------- It can often be useful to override specific assets from "outside" a given :app:`Pyramid` application. For example, you may wish to reuse an existing :app:`Pyramid` application more or less unchanged. However, some specific template file owned by the application might have inappropriate HTML, or some static asset (such as a logo file or some CSS file) might not be appropriate. You *could* just fork the application entirely, but it's often more convenient to just override the assets that are inappropriate and reuse the application "as is". This is particularly true when you reuse some "core" application over and over again for some set of customers (such as a CMS application, or some bug tracking application), and you want to make arbitrary visual modifications to a particular application deployment without forking the underlying code. To this end, :app:`Pyramid` contains a feature that makes it possible to "override" one asset with one or more other assets. In support of this feature, a :term:`Configurator` API exists named :meth:`pyramid.config.Configurator.override_asset`. This API allows you to *override* the following kinds of assets defined in any Python package: - Individual template files. - A directory containing multiple template files. - Individual static files served up by an instance of the ``pyramid.static.static_view`` helper class. - A directory of static files served up by an instance of the ``pyramid.static.static_view`` helper class. - Any other asset (or set of assets) addressed by code that uses the setuptools :term:`pkg_resources` API. .. index:: single: override_asset .. _override_asset: The ``override_asset`` API ~~~~~~~~~~~~~~~~~~~~~~~~~~ An individual call to :meth:`~pyramid.config.Configurator.override_asset` can override a single asset. For example: .. code-block:: python :linenos: config.override_asset( to_override='some.package:templates/mytemplate.pt', override_with='another.package:othertemplates/anothertemplate.pt') The string value passed to both ``to_override`` and ``override_with`` sent to the ``override_asset`` API is called an :term:`asset specification`. The colon separator in a specification separates the *package name* from the *asset name*. The colon and the following asset name are optional. If they are not specified, the override attempts to resolve every lookup into a package from the directory of another package. For example: .. code-block:: python :linenos: config.override_asset(to_override='some.package', override_with='another.package') Individual subdirectories within a package can also be overridden: .. code-block:: python :linenos: config.override_asset(to_override='some.package:templates/', override_with='another.package:othertemplates/') If you wish to override a directory with another directory, you *must* make sure to attach the slash to the end of both the ``to_override`` specification and the ``override_with`` specification. If you fail to attach a slash to the end of a specification that points to a directory, you will get unexpected results. You cannot override a directory specification with a file specification, and vice versa; a startup error will occur if you try. You cannot override an asset with itself; a startup error will occur if you try. Only individual *package* assets may be overridden. Overrides will not traverse through subpackages within an overridden package. This means that if you want to override assets for both ``some.package:templates``, and ``some.package.views:templates``, you will need to register two overrides. The package name in a specification may start with a dot, meaning that the package is relative to the package in which the configuration construction file resides (or the ``package`` argument to the :class:`~pyramid.config.Configurator` class construction). For example: .. code-block:: python :linenos: config.override_asset(to_override='.subpackage:templates/', override_with='another.package:templates/') Multiple calls to ``override_asset`` which name a shared ``to_override`` but a different ``override_with`` specification can be "stacked" to form a search path. The first asset that exists in the search path will be used; if no asset exists in the override path, the original asset is used. Asset overrides can actually override assets other than templates and static files. Any software which uses the :func:`pkg_resources.get_resource_filename`, :func:`pkg_resources.get_resource_stream`, or :func:`pkg_resources.get_resource_string` APIs will obtain an overridden file when an override is used. .. versionadded:: 1.6 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. Cache Busting and Asset Overrides ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Overriding static assets that are being hosted using :meth:`pyramid.config.Configurator.add_static_view` can affect your cache busting strategy when using any cache busters that are asset-aware such as :class:`pyramid.static.ManifestCacheBuster`. What sets asset-aware cache busters apart is that they have logic tied to specific assets. For example, a manifest is only generated for a specific set of pre-defined assets. Now, imagine you have overridden an asset defined in this manifest with a new, unknown version. By default, the cache buster will be invoked for an asset it has never seen before and will likely end up returning a cache busting token for the original asset rather than the asset that will actually end up being served! In order to get around this issue, it's possible to attach a different :class:`pyramid.interfaces.ICacheBuster` implementation to the new assets. This would cause the original assets to be served by their manifest, and the new assets served by their own cache buster. To do this, :meth:`pyramid.config.Configurator.add_cache_buster` supports an ``explicit`` option. For example: .. code-block:: python :linenos: from pyramid.static import ManifestCacheBuster # define a static view for myapp:static assets config.add_static_view('static', 'myapp:static') # setup a cache buster for your app based on the myapp:static assets my_cb = ManifestCacheBuster('myapp:static/manifest.json') config.add_cache_buster('myapp:static', my_cb) # override an asset config.override_asset( to_override='myapp:static/background.png', override_with='theme:static/background.png') # override the cache buster for theme:static assets theme_cb = ManifestCacheBuster('theme:static/manifest.json') config.add_cache_buster('theme:static', theme_cb, explicit=True) In the above example there is a default cache buster, ``my_cb``, for all assets served from the ``myapp:static`` folder. This would also affect ``theme:static/background.png`` when generating URLs via ``request.static_url('myapp:static/background.png')``. The ``theme_cb`` is defined explicitly for any assets loaded from the ``theme:static`` folder. Explicit cache busters have priority and thus ``theme_cb`` would be invoked for ``request.static_url('myapp:static/background.png')``, but ``my_cb`` would be used for any other assets like ``request.static_url('myapp:static/favicon.ico')``. pyramid-1.6/docs/narr/commandline.rst0000644000076500000240000010771612642135264020505 0ustar michaelstaff00000000000000.. _command_line_chapter: Command-Line Pyramid ==================== Your :app:`Pyramid` application can be controlled and inspected using a variety of command-line utilities. These utilities are documented in this chapter. .. index:: pair: matching views; printing single: pviews .. _displaying_matching_views: Displaying Matching Views for a Given URL ----------------------------------------- .. seealso:: See also the output of :ref:`pviews --help `. For a big application with several views, it can be hard to keep the view configuration details in your head, even if you defined all the views yourself. You can use the ``pviews`` command in a terminal window to print a summary of matching routes and views for a given URL in your application. The ``pviews`` command accepts two arguments. The first argument to ``pviews`` is the path to your application's ``.ini`` file and section name inside the ``.ini`` file which points to your application. This should be of the format ``config_file#section_name``. The second argument is the URL to test for matching views. The ``section_name`` may be omitted; if it is, it's considered to be ``main``. Here is an example for a simple view configuration using :term:`traversal`: .. code-block:: text :linenos: $ $VENV/bin/pviews development.ini#tutorial /FrontPage URL = /FrontPage context: view name: View: ----- tutorial.views.view_page required permission = view The output always has the requested URL at the top and below that all the views that matched with their view configuration details. In this example only one view matches, so there is just a single *View* section. For each matching view, the full code path to the associated view callable is shown, along with any permissions and predicates that are part of that view configuration. A more complex configuration might generate something like this: .. code-block:: text :linenos: $ $VENV/bin/pviews development.ini#shootout /about URL = /about context: view name: about Route: ------ route name: about route pattern: /about route path: /about subpath: route predicates (request method = GET) View: ----- shootout.views.about_view required permission = view view predicates (request_param testing, header X/header) Route: ------ route name: about_post route pattern: /about route path: /about subpath: route predicates (request method = POST) View: ----- shootout.views.about_view_post required permission = view view predicates (request_param test) View: ----- shootout.views.about_view_post2 required permission = view view predicates (request_param test2) In this case, we are dealing with a :term:`URL dispatch` application. This specific URL has two matching routes. The matching route information is displayed first, followed by any views that are associated with that route. As you can see from the second matching route output, a route can be associated with more than one view. For a URL that doesn't match any views, ``pviews`` will simply print out a *Not found* message. .. index:: single: interactive shell single: pshell .. _interactive_shell: The Interactive Shell --------------------- .. seealso:: See also the output of :ref:`pshell --help `. Once you've installed your program for development using ``setup.py develop``, you can use an interactive Python shell to execute expressions in a Python environment exactly like the one that will be used when your application runs "for real". To do so, use the ``pshell`` command line utility. The argument to ``pshell`` follows the format ``config_file#section_name`` where ``config_file`` is the path to your application's ``.ini`` file and ``section_name`` is the ``app`` section name inside the ``.ini`` file which points to your application. For example, your application ``.ini`` file might have an ``[app:main]`` section that looks like so: .. code-block:: ini :linenos: [app:main] use = egg:MyProject pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_templates = true pyramid.default_locale_name = en If so, you can use the following command to invoke a debug shell using the name ``main`` as a section name: .. code-block:: text $ $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. Environment: app The WSGI application. registry Active Pyramid registry. request Active request object. root Root of the default resource tree. root_factory Default root factory used to create `root`. >>> root >>> registry >>> registry.settings['pyramid.debug_notfound'] False >>> from myproject.views import my_view >>> from pyramid.request import Request >>> r = Request.blank('/') >>> my_view(r) {'project': 'myproject'} The WSGI application that is loaded will be available in the shell as the ``app`` global. Also, if the application that is loaded is the :app:`Pyramid` app with no surrounding :term:`middleware`, the ``root`` object returned by the default :term:`root factory`, ``registry``, and ``request`` will be available. You can also simply rely on the ``main`` default section name by omitting any hash after the filename: .. code-block:: text $ $VENV/bin/pshell starter/development.ini Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). .. index:: pair: pshell; extending .. _extending_pshell: Extending the Shell ~~~~~~~~~~~~~~~~~~~ It is convenient when using the interactive shell often to have some variables significant to your application already loaded as globals when you start the ``pshell``. To facilitate this, ``pshell`` will look for a special ``[pshell]`` section in your INI file and expose the subsequent key/value pairs to the shell. Each key is a variable name that will be global within the pshell session; each value is a :term:`dotted Python name`. If specified, the special key ``setup`` should be a :term:`dotted Python name` pointing to a callable that accepts the dictionary of globals that will be loaded into the shell. This allows for some custom initializing code to be executed each time the ``pshell`` is run. The ``setup`` callable can also be specified from the commandline using the ``--setup`` option which will override the key in the INI file. For example, you want to expose your model to the shell along with the database session so that you can mutate the model on an actual database. Here, we'll assume your model is stored in the ``myapp.models`` package. .. code-block:: ini :linenos: [pshell] setup = myapp.lib.pshell.setup m = myapp.models session = myapp.models.DBSession t = transaction By defining the ``setup`` callable, we will create the module ``myapp.lib.pshell`` containing a callable named ``setup`` that will receive the global environment before it is exposed to the shell. Here we mutate the environment's request as well as add a new value containing a WebTest version of the application to which we can easily submit requests. .. code-block:: python :linenos: # myapp/lib/pshell.py from webtest import TestApp def setup(env): env['request'].host = 'www.example.com' env['request'].scheme = 'https' env['testapp'] = TestApp(env['app']) When this INI file is loaded, the extra variables ``m``, ``session`` and ``t`` will be available for use immediately. Since a ``setup`` callable was also specified, it is executed and a new variable ``testapp`` is exposed, and the request is configured to generate urls from the host ``http://www.example.com``. For example: .. code-block:: text $ $VENV/bin/pshell starter/development.ini Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) [GCC 4.4.3] on linux2 Type "help" for more information. Environment: app The WSGI application. registry Active Pyramid registry. request Active request object. root Root of the default resource tree. root_factory Default root factory used to create `root`. testapp Custom Variables: m myapp.models session myapp.models.DBSession t transaction >>> testapp.get('/') <200 OK text/html body='\n'/3337> >>> request.route_url('home') 'https://www.example.com/' .. _ipython_or_bpython: Alternative Shells ~~~~~~~~~~~~~~~~~~ The ``pshell`` command can be easily extended with alternate REPLs if the default python REPL is not satisfactory. Assuming you have a binding installed such as ``pyramid_ipython`` it will normally be auto-selected and used. You may also specifically invoke your choice with the ``-p choice`` or ``--python-shell choice`` option. .. code-block:: text $ $VENV/bin/pshell -p ipython development.ini#MyProject You may use the ``--list-shells`` option to see the available shells. .. code-block:: text $ $VENV/bin/pshell --list-shells Available shells: bpython ipython python If you want to use a shell that isn't supported out of the box, you can introduce a new shell by registering an entry point in your setup.py: .. code-block:: python setup( entry_points={ 'pyramid.pshell_runner': [ 'myshell=my_app:ptpython_shell_factory', ], }, ) And then your shell factory should return a function that accepts two arguments, ``env`` and ``help``, which would look like this: .. code-block:: python from ptpython.repl import embed def ptpython_shell_runner(env, help): print(help) return embed(locals=env) .. versionchanged:: 1.6 User-defined shells may be registered using entry points. Prior to this the only supported shells were ``ipython``, ``bpython`` and ``python``. ``ipython`` and ``bpython`` have been moved into their respective packages ``pyramid_ipython`` and ``pyramid_bpython``. Setting a Default Shell ~~~~~~~~~~~~~~~~~~~~~~~ You may use the ``default_shell`` option in your ``[pshell]`` ini section to specify a list of preferred shells. .. code-block:: ini :linenos: [pshell] default_shell = ptpython ipython bpython .. versionadded:: 1.6 .. index:: pair: routes; printing single: proutes .. _displaying_application_routes: Displaying All Application Routes --------------------------------- .. seealso:: See also the output of :ref:`proutes --help `. You can use the ``proutes`` command in a terminal window to print a summary of routes related to your application. Much like the ``pshell`` command (see :ref:`interactive_shell`), the ``proutes`` command accepts one argument with the format ``config_file#section_name``. The ``config_file`` is the path to your application's ``.ini`` file, and ``section_name`` is the ``app`` section name inside the ``.ini`` file which points to your application. By default, the ``section_name`` is ``main`` and can be omitted. For example: .. code-block:: text :linenos: $ $VENV/bin/proutes development.ini Name Pattern View Method ---- ------- ---- ------ debugtoolbar /_debug_toolbar/*subpath * __static/ /static/*subpath dummy_starter:static/ * __static2/ /static2/*subpath /var/www/static/ * __pdt_images/ /pdt_images/*subpath pyramid_debugtoolbar:static/img/ * a / * no_view_attached / * route_and_view_attached / app1.standard_views.route_and_view_attached * method_conflicts /conflicts app1.standard_conflicts multiview /multiview app1.standard_views.multiview GET,PATCH not_post /not_post app1.standard_views.multview !POST,* ``proutes`` generates a table with four columns: *Name*, *Pattern*, *View*, and *Method*. The items listed in the Name column are route names, the items listed in the Pattern column are route patterns, the items listed in the View column are representations of the view callable that will be invoked when a request matches the associated route pattern, and the items listed in the Method column are the request methods that are associated with the route name. The View column may show ```` if no associated view callable could be found. The Method column, for the route name, may show either ```` if the view callable does not accept any of the route's request methods, or ``*`` if the view callable will accept any of the route's request methods. If no routes are configured within your application, nothing will be printed to the console when ``proutes`` is executed. It is convenient when using the ``proutes`` command often to configure which columns and the order you would like to view them. To facilitate this, ``proutes`` will look for a special ``[proutes]`` section in your ``.ini`` file and use those as defaults. For example you may remove the request method and place the view first: .. code-block:: text :linenos: [proutes] format = view name pattern You can also separate the formats with commas or spaces: .. code-block:: text :linenos: [proutes] format = view name pattern [proutes] format = view, name, pattern If you want to temporarily configure the columns and order, there is the argument ``--format``, which is a comma separated list of columns you want to include. The current available formats are ``name``, ``pattern``, ``view``, and ``method``. .. index:: pair: tweens; printing single: ptweens .. _displaying_tweens: Displaying "Tweens" ------------------- .. seealso:: See also the output of :ref:`ptweens --help `. A :term:`tween` is a bit of code that sits between the main Pyramid application request handler and the WSGI application which calls it. A user can get a representation of both the implicit tween ordering (the ordering specified by calls to :meth:`pyramid.config.Configurator.add_tween`) and the explicit tween ordering (specified by the ``pyramid.tweens`` configuration setting) using the ``ptweens`` command. Tween factories will show up represented by their standard Python dotted name in the ``ptweens`` output. For example, here's the ``ptweens`` command run against a system configured without any explicit tweens: .. code-block:: text :linenos: $ $VENV/bin/ptweens development.ini "pyramid.tweens" config value NOT set (implicitly ordered tweens used) Implicit Tween Chain Position Name Alias -------- ---- ----- - - INGRESS 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory pdbt 1 pyramid.tweens.excview_tween_factory excview - - MAIN Here's the ``ptweens`` command run against a system configured *with* explicit tweens defined in its ``development.ini`` file: .. code-block:: text :linenos: $ ptweens development.ini "pyramid.tweens" config value set (explicitly ordered tweens used) Explicit Tween Chain (used) Position Name -------- ---- - INGRESS 0 starter.tween_factory2 1 starter.tween_factory1 2 pyramid.tweens.excview_tween_factory - MAIN Implicit Tween Chain (not used) Position Name -------- ---- - INGRESS 0 pyramid_debugtoolbar.toolbar.toolbar_tween_factory 1 pyramid.tweens.excview_tween_factory - MAIN Here's the application configuration section of the ``development.ini`` used by the above ``ptweens`` command which reports that the explicit tween chain is used: .. code-block:: ini :linenos: [app:main] use = egg:starter reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false debug_templates = true default_locale_name = en pyramid.include = pyramid_debugtoolbar pyramid.tweens = starter.tween_factory2 starter.tween_factory1 pyramid.tweens.excview_tween_factory See :ref:`registering_tweens` for more information about tweens. .. index:: single: invoking a request single: prequest .. _invoking_a_request: Invoking a Request ------------------ .. seealso:: See also the output of :ref:`prequest --help `. You can use the ``prequest`` command-line utility to send a request to your application and see the response body without starting a server. There are two required arguments to ``prequest``: - The config file/section: follows the format ``config_file#section_name``, where ``config_file`` is the path to your application's ``.ini`` file and ``section_name`` is the ``app`` section name inside the ``.ini`` file. The ``section_name`` is optional; it defaults to ``main``. For example: ``development.ini``. - The path: this should be the non-URL-quoted path element of the URL to the resource you'd like to be rendered on the server. For example, ``/``. For example:: $ $VENV/bin/prequest development.ini / This will print the body of the response to the console on which it was invoked. Several options are supported by ``prequest``. These should precede any config file name or URL. ``prequest`` has a ``-d`` (i.e., ``--display-headers``) option which prints the status and headers returned by the server before the output:: $ $VENV/bin/prequest -d development.ini / This will print the status, headers, and the body of the response to the console. You can add request header values by using the ``--header`` option:: $ $VENV/bin/prequest --header=Host:example.com development.ini / Headers are added to the WSGI environment by converting them to their CGI/WSGI equivalents (e.g., ``Host=example.com`` will insert the ``HTTP_HOST`` header variable as the value ``example.com``). Multiple ``--header`` options can be supplied. The special header value ``content-type`` sets the ``CONTENT_TYPE`` in the WSGI environment. By default, ``prequest`` sends a ``GET`` request. You can change this by using the ``-m`` (aka ``--method``) option. ``GET``, ``HEAD``, ``POST``, and ``DELETE`` are currently supported. When you use ``POST``, the standard input of the ``prequest`` process is used as the ``POST`` body:: $ $VENV/bin/prequest -mPOST development.ini / < somefile Using Custom Arguments to Python when Running ``p*`` Scripts ------------------------------------------------------------ .. versionadded:: 1.5 Each of Pyramid's console scripts (``pserve``, ``pviews``, etc.) can be run directly using ``python -m``, allowing custom arguments to be sent to the Python interpreter at runtime. For example:: python -3 -m pyramid.scripts.pserve development.ini .. index:: single: pdistreport single: distributions, showing installed single: showing installed distributions .. _showing_distributions: Showing All Installed Distributions and Their Versions ------------------------------------------------------ .. versionadded:: 1.5 .. seealso:: See also the output of :ref:`pdistreport --help `. You can use the ``pdistreport`` command to show the :app:`Pyramid` version in use, the Python version in use, and all installed versions of Python distributions in your Python environment:: $ $VENV/bin/pdistreport Pyramid version: 1.5dev Platform Linux-3.2.0-51-generic-x86_64-with-debian-wheezy-sid Packages: authapp 0.0 /home/chrism/projects/foo/src/authapp beautifulsoup4 4.1.3 /home/chrism/projects/foo/lib/python2.7/site-packages/beautifulsoup4-4.1.3-py2.7.egg ... more output ... ``pdistreport`` takes no options. Its output is useful to paste into a pastebin when you are having problems and need someone with more familiarity with Python packaging and distribution than you have to look at your environment. .. _writing_a_script: Writing a Script ---------------- All web applications are, at their hearts, systems which accept a request and return a response. When a request is accepted by a :app:`Pyramid` application, the system receives state from the request which is later relied on by your application code. For example, one :term:`view callable` may assume it's working against a request that has a ``request.matchdict`` of a particular composition, while another assumes a different composition of the matchdict. In the meantime, it's convenient to be able to write a Python script that can work "in a Pyramid environment", for instance to update database tables used by your :app:`Pyramid` application. But a "real" Pyramid environment doesn't have a completely static state independent of a request; your application (and Pyramid itself) is almost always reliant on being able to obtain information from a request. When you run a Python script that simply imports code from your application and tries to run it, there just is no request data, because there isn't any real web request. Therefore some parts of your application and some Pyramid APIs will not work. For this reason, :app:`Pyramid` makes it possible to run a script in an environment much like the environment produced when a particular :term:`request` reaches your :app:`Pyramid` application. This is achieved by using the :func:`pyramid.paster.bootstrap` command in the body of your script. .. versionadded:: 1.1 :func:`pyramid.paster.bootstrap` In the simplest case, :func:`pyramid.paster.bootstrap` can be used with a single argument, which accepts the :term:`PasteDeploy` ``.ini`` file representing your Pyramid application's configuration as a single argument: .. code-block:: python from pyramid.paster import bootstrap env = bootstrap('/path/to/my/development.ini') print(env['request'].route_url('home')) :func:`pyramid.paster.bootstrap` returns a dictionary containing framework-related information. This dictionary will always contain a :term:`request` object as its ``request`` key. The following keys are available in the ``env`` dictionary returned by :func:`pyramid.paster.bootstrap`: request A :class:`pyramid.request.Request` object implying the current request state for your script. app The :term:`WSGI` application object generated by bootstrapping. root The :term:`resource` root of your :app:`Pyramid` application. This is an object generated by the :term:`root factory` configured in your application. registry The :term:`application registry` of your :app:`Pyramid` application. closer A parameterless callable that can be used to pop an internal :app:`Pyramid` threadlocal stack (used by :func:`pyramid.threadlocal.get_current_registry` and :func:`pyramid.threadlocal.get_current_request`) when your scripting job is finished. Let's assume that the ``/path/to/my/development.ini`` file used in the example above looks like so: .. code-block:: ini [pipeline:main] pipeline = translogger another [filter:translogger] filter_app_factory = egg:Paste#translogger setup_console_handler = False logger_name = wsgi [app:another] use = egg:MyProject The configuration loaded by the above bootstrap example will use the configuration implied by the ``[pipeline:main]`` section of your configuration file by default. Specifying ``/path/to/my/development.ini`` is logically equivalent to specifying ``/path/to/my/development.ini#main``. In this case, we'll be using a configuration that includes an ``app`` object which is wrapped in the Paste "translogger" :term:`middleware` (which logs requests to the console). You can also specify a particular *section* of the PasteDeploy ``.ini`` file to load instead of ``main``: .. code-block:: python from pyramid.paster import bootstrap env = bootstrap('/path/to/my/development.ini#another') print(env['request'].route_url('home')) The above example specifies the ``another`` ``app``, ``pipeline``, or ``composite`` section of your PasteDeploy configuration file. The ``app`` object present in the ``env`` dictionary returned by :func:`pyramid.paster.bootstrap` will be a :app:`Pyramid` :term:`router`. Changing the Request ~~~~~~~~~~~~~~~~~~~~ By default, Pyramid will generate a request object in the ``env`` dictionary for the URL ``http://localhost:80/``. This means that any URLs generated by Pyramid during the execution of your script will be anchored here. This is generally not what you want. So how do we make Pyramid generate the correct URLs? Assuming that you have a route configured in your application like so: .. code-block:: python config.add_route('verify', '/verify/{code}') You need to inform the Pyramid environment that the WSGI application is handling requests from a certain base. For example, we want to simulate mounting our application at `https://example.com/prefix`, to ensure that the generated URLs are correct for our deployment. This can be done by either mutating the resulting request object, or more simply by constructing the desired request and passing it into :func:`~pyramid.paster.bootstrap`: .. code-block:: python from pyramid.paster import bootstrap from pyramid.request import Request request = Request.blank('/', base_url='https://example.com/prefix') env = bootstrap('/path/to/my/development.ini#another', request=request) print(env['request'].application_url) # will print 'https://example.com/prefix' Now you can readily use Pyramid's APIs for generating URLs: .. code-block:: python env['request'].route_url('verify', code='1337') # will return 'https://example.com/prefix/verify/1337' Cleanup ~~~~~~~ When your scripting logic finishes, it's good manners to call the ``closer`` callback: .. code-block:: python from pyramid.paster import bootstrap env = bootstrap('/path/to/my/development.ini') # .. do stuff ... env['closer']() Setting Up Logging ~~~~~~~~~~~~~~~~~~ By default, :func:`pyramid.paster.bootstrap` does not configure logging parameters present in the configuration file. If you'd like to configure logging based on ``[logger]`` and related sections in the configuration file, use the following command: .. code-block:: python import pyramid.paster pyramid.paster.setup_logging('/path/to/my/development.ini') See :ref:`logging_chapter` for more information on logging within :app:`Pyramid`. .. index:: single: console script .. _making_a_console_script: Making Your Script into a Console Script ---------------------------------------- A "console script" is :term:`setuptools` terminology for a script that gets installed into the ``bin`` directory of a Python :term:`virtualenv` (or "base" Python environment) when a :term:`distribution` which houses that script is installed. Because it's installed into the ``bin`` directory of a virtualenv when the distribution is installed, it's a convenient way to package and distribute functionality that you can call from the command-line. It's often more convenient to create a console script than it is to create a ``.py`` script and instruct people to call it with the "right" Python interpreter. A console script generates a file that lives in ``bin``, and when it's invoked it will always use the "right" Python environment, which means it will always be invoked in an environment where all the libraries it needs (such as Pyramid) are available. In general, you can make your script into a console script by doing the following: - Use an existing distribution (such as one you've already created via ``pcreate``) or create a new distribution that possesses at least one package or module. It should, within any module within the distribution, house a callable (usually a function) that takes no arguments and which runs any of the code you wish to run. - Add a ``[console_scripts]`` section to the ``entry_points`` argument of the distribution which creates a mapping between a script name and a dotted name representing the callable you added to your distribution. - Run ``setup.py develop``, ``setup.py install``, or ``easy_install`` to get your distribution reinstalled. When you reinstall your distribution, a file representing the script that you named in the last step will be in the ``bin`` directory of the virtualenv in which you installed the distribution. It will be executable. Invoking it from a terminal will execute your callable. As an example, let's create some code that can be invoked by a console script that prints the deployment settings of a Pyramid application. To do so, we'll pretend you have a distribution with a package in it named ``myproject``. Within this package, we'll pretend you've added a ``scripts.py`` module which contains the following code: .. code-block:: python :linenos: # myproject.scripts module import optparse import sys import textwrap from pyramid.paster import bootstrap def settings_show(): description = """\ Print the deployment settings for a Pyramid application. Example: 'show_settings deployment.ini' """ usage = "usage: %prog config_uri" parser = optparse.OptionParser( usage=usage, description=textwrap.dedent(description) ) parser.add_option( '-o', '--omit', dest='omit', metavar='PREFIX', type='string', action='append', help=("Omit settings which start with PREFIX (you can use this " "option multiple times)") ) options, args = parser.parse_args(sys.argv[1:]) if not len(args) >= 1: print('You must provide at least one argument') return 2 config_uri = args[0] omit = options.omit if omit is None: omit = [] env = bootstrap(config_uri) settings, closer = env['registry'].settings, env['closer'] try: for k, v in settings.items(): if any([k.startswith(x) for x in omit]): continue print('%-40s %-20s' % (k, v)) finally: closer() This script uses the Python ``optparse`` module to allow us to make sense out of extra arguments passed to the script. It uses the :func:`pyramid.paster.bootstrap` function to get information about the application defined by a config file, and prints the deployment settings defined in that config file. After adding this script to the package, you'll need to tell your distribution's ``setup.py`` about its existence. Within your distribution's top-level directory, your ``setup.py`` file will look something like this: .. code-block:: python :linenos: 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 = ['pyramid', 'pyramid_debugtoolbar'] setup(name='MyProject', version='0.0', description='My project', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="myproject", entry_points = """\ [paste.app_factory] main = myproject:main """, ) We're going to change the setup.py file to add a ``[console_scripts]`` section within the ``entry_points`` string. Within this section, you should specify a ``scriptname = dotted.path.to:yourfunction`` line. For example:: [console_scripts] show_settings = myproject.scripts:settings_show The ``show_settings`` name will be the name of the script that is installed into ``bin``. The colon (``:``) between ``myproject.scripts`` and ``settings_show`` above indicates that ``myproject.scripts`` is a Python module, and ``settings_show`` is the function in that module which contains the code you'd like to run as the result of someone invoking the ``show_settings`` script from their command line. The result will be something like: .. code-block:: python :linenos: 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 = ['pyramid', 'pyramid_debugtoolbar'] setup(name='MyProject', version='0.0', description='My project', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="myproject", entry_points = """\ [paste.app_factory] main = myproject:main [console_scripts] show_settings = myproject.scripts:settings_show """, ) Once you've done this, invoking ``$$VENV/bin/python setup.py develop`` will install a file named ``show_settings`` into the ``$somevirtualenv/bin`` directory with a small bit of Python code that points to your entry point. It will be executable. Running it without any arguments will print an error and exit. Running it with a single argument that is the path of a config file will print the settings. Running it with an ``--omit=foo`` argument will omit the settings that have keys that start with ``foo``. Running it with two "omit" options (e.g., ``--omit=foo --omit=bar``) will omit all settings that have keys that start with either ``foo`` or ``bar``:: $ $VENV/bin/show_settings development.ini --omit=pyramid --omit=debugtoolbar debug_routematch False debug_templates True reload_templates True mako.directories [] debug_notfound False default_locale_name en reload_resources False debug_authorization False reload_assets False prevent_http_cache False Pyramid's ``pserve``, ``pcreate``, ``pshell``, ``prequest``, ``ptweens``, and other ``p*`` scripts are implemented as console scripts. When you invoke one of those, you are using a console script. pyramid-1.6/docs/narr/configuration.rst0000644000076500000240000001342512606630333021054 0ustar michaelstaff00000000000000.. index:: single: application configuration .. _configuration_narr: Application Configuration ========================= Most people already understand "configuration" as settings that influence the operation of an application. For instance, it's easy to think of the values in a ``.ini`` file parsed at application startup time as "configuration". However, if you're reasonably open-minded, it's easy to think of *code* as configuration too. Since Pyramid, like most other web application platforms, is a *framework*, it calls into code that you write (as opposed to a *library*, which is code that exists purely for you to call). The act of 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. .. index:: single: imperative configuration .. _imperative_configuration: Imperative Configuration ------------------------ "Imperative configuration" just means configuration done by Python statements, one after the next. Here's one of the simplest :app:`Pyramid` applications, configured imperatively: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello world!') if __name__ == '__main__': config = Configurator() config.add_view(hello_world) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() We won't talk much about what this application does yet. Just note that the "configuration' statements take place underneath the ``if __name__ == '__main__':`` stanza in the form of method calls on a :term:`Configurator` object (e.g., ``config.add_view(...)``). These statements take place one after the other, and are executed in order, so the full power of Python, including conditionals, can be employed in this mode of configuration. .. index:: single: view_config single: configuration decoration single: code scanning .. _decorations_and_code_scanning: Declarative Configuration ------------------------- It's sometimes painful to have all configuration done by imperative code, because often the code for a single application may live in many files. If the configuration is centralized in one place, you'll need to have at least two files open at once to see the "big picture": the file that represents the configuration, and the file that contains the implementation objects referenced by the configuration. To avoid this, :app:`Pyramid` allows you to insert :term:`configuration decoration` statements very close to code that is referred to by the declaration itself. For example: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config(name='hello', request_method='GET') def hello(request): return Response('Hello') The mere existence of configuration decoration doesn't cause any configuration registration to be performed. Before it has any effect on the configuration of a :app:`Pyramid` application, a configuration decoration within application code must be found through a process known as a :term:`scan`. For example, the :class:`pyramid.view.view_config` decorator in the code example above adds an attribute to the ``hello`` function, making it available for a :term:`scan` to find it later. A :term:`scan` of a :term:`module` or a :term:`package` and its subpackages for decorations happens when the :meth:`pyramid.config.Configurator.scan` method is invoked: scanning implies searching for configuration declarations in a package and its subpackages. For example: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response from pyramid.view import view_config @view_config() def hello(request): return Response('Hello') if __name__ == '__main__': config = Configurator() config.scan() app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() The scanning machinery imports each module and subpackage in a package or module recursively, looking for special attributes attached to objects defined within a module. These special attributes are typically attached to code via the use of a :term:`decorator`. For example, the :class:`~pyramid.view.view_config` decorator can be attached to a function or instance method. Once scanning is invoked, and :term:`configuration decoration` is found by the scanner, a set of calls are made to a :term:`Configurator` on your behalf. These calls replace the need to add imperative configuration statements that don't live near the code being configured. The combination of :term:`configuration decoration` and the invocation of a :term:`scan` is collectively known as :term:`declarative configuration`. In the example above, the scanner translates the arguments to :class:`~pyramid.view.view_config` into a call to the :meth:`pyramid.config.Configurator.add_view` method, effectively: .. code-block:: python config.add_view(hello) Summary ------- There are two ways to configure a :app:`Pyramid` application: declaratively and imperatively. You can choose the mode with which you're most comfortable; both are completely equivalent. Examples in this documentation will use both modes interchangeably. pyramid-1.6/docs/narr/environment.rst0000644000076500000240000005005312610765056020555 0ustar michaelstaff00000000000000.. index:: single: environment variables single: settings single: reload single: debug_authorization single: reload_assets single: debug_notfound single: debug_all single: reload_all single: debug settings single: debug_routematch single: prevent_http_cache single: reload settings single: default_locale_name single: environment variables single: ini file settings single: PasteDeploy settings .. _environment_chapter: Environment Variables and ``.ini`` File Settings ================================================ :app:`Pyramid` behavior can be configured through a combination of operating system environment variables and ``.ini`` configuration file application section settings. The meaning of the environment variables and the configuration file settings overlap. .. note:: Where a configuration file setting exists with the same meaning as an environment variable, and both are present at application startup time, the environment variable setting takes precedence. The term "configuration file setting name" refers to a key in the ``.ini`` configuration for your application. The configuration file setting names documented in this chapter are reserved for :app:`Pyramid` use. You should not use them to indicate application-specific configuration settings. Reloading Templates ------------------- When this value is true, templates are automatically reloaded whenever they are modified without restarting the application, so you can see changes to templates take effect immediately during development. This flag is meaningful to Chameleon and Mako templates, as well as most third-party template rendering extensions. +-------------------------------+--------------------------------+ | Environment Variable Name | Config File Setting Name | +===============================+================================+ | ``PYRAMID_RELOAD_TEMPLATES`` | ``pyramid.reload_templates`` | | | or ``reload_templates`` | +-------------------------------+--------------------------------+ Reloading Assets ---------------- Don't cache any asset file data when this value is true. .. seealso:: See also :ref:`overriding_assets_section`. +----------------------------+-----------------------------+ | Environment Variable Name | Config File Setting Name | +============================+=============================+ | ``PYRAMID_RELOAD_ASSETS`` | ``pyramid.reload_assets`` | | | or ``reload_assets`` | +----------------------------+-----------------------------+ .. note:: For backwards compatibility purposes, aliases can be used for configuring asset reloading: ``PYRAMID_RELOAD_RESOURCES`` (envvar) and ``pyramid.reload_resources`` (config file). Debugging Authorization ----------------------- Print view authorization failure and success information to stderr when this value is true. .. seealso:: See also :ref:`debug_authorization_section`. +---------------------------------+-----------------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+===================================+ | ``PYRAMID_DEBUG_AUTHORIZATION`` | ``pyramid.debug_authorization`` | | | or ``debug_authorization`` | +---------------------------------+-----------------------------------+ Debugging Not Found Errors -------------------------- Print view-related ``NotFound`` debug messages to stderr when this value is true. .. seealso:: See also :ref:`debug_notfound_section`. +----------------------------+------------------------------+ | Environment Variable Name | Config File Setting Name | +============================+==============================+ | ``PYRAMID_DEBUG_NOTFOUND`` | ``pyramid.debug_notfound`` | | | or ``debug_notfound`` | +----------------------------+------------------------------+ Debugging Route Matching ------------------------ Print debugging messages related to :term:`url dispatch` route matching when this value is true. .. seealso:: See also :ref:`debug_routematch_section`. +------------------------------+--------------------------------+ | Environment Variable Name | Config File Setting Name | +==============================+================================+ | ``PYRAMID_DEBUG_ROUTEMATCH`` | ``pyramid.debug_routematch`` | | | or ``debug_routematch`` | +------------------------------+--------------------------------+ .. _preventing_http_caching: Preventing HTTP Caching ----------------------- Prevent the ``http_cache`` view configuration argument from having any effect globally in this process when this value is true. No HTTP caching-related response headers will be set by the :app:`Pyramid` ``http_cache`` view configuration feature when this is true. .. seealso:: See also :ref:`influencing_http_caching`. +---------------------------------+----------------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+==================================+ | ``PYRAMID_PREVENT_HTTP_CACHE`` | ``pyramid.prevent_http_cache`` | | | or ``prevent_http_cache`` | +---------------------------------+----------------------------------+ 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 ------------- Turns on all ``debug*`` settings. +----------------------------+---------------------------+ | Environment Variable Name | Config File Setting Name | +============================+===========================+ | ``PYRAMID_DEBUG_ALL`` | ``pyramid.debug_all`` | | | or ``debug_all`` | +----------------------------+---------------------------+ Reloading All ------------- Turns on all ``reload*`` settings. +---------------------------+----------------------------+ | Environment Variable Name | Config File Setting Name | +===========================+============================+ | ``PYRAMID_RELOAD_ALL`` | ``pyramid.reload_all`` or | | | ``reload_all`` | +---------------------------+----------------------------+ .. _default_locale_name_setting: Default Locale Name ------------------- The value supplied here is used as the default locale name when a :term:`locale negotiator` is not registered. .. seealso:: See also :ref:`localization_deployment_settings`. +---------------------------------+-----------------------------------+ | Environment Variable Name | Config File Setting Name | +=================================+===================================+ | ``PYRAMID_DEFAULT_LOCALE_NAME`` | ``pyramid.default_locale_name`` | | | or ``default_locale_name`` | +---------------------------------+-----------------------------------+ .. _including_packages: Including Packages ------------------ ``pyramid.includes`` instructs your application to include other packages. Using the setting is equivalent to using the :meth:`pyramid.config.Configurator.include` method. +--------------------------+ | Config File Setting Name | +==========================+ | ``pyramid.includes`` | +--------------------------+ The value assigned to ``pyramid.includes`` should be a sequence. The sequence can take several different forms. 1) It can be a string. If it is a string, the package names can be separated by spaces:: package1 package2 package3 The package names can also be separated by carriage returns:: package1 package2 package3 2) It can be a Python list, where the values are strings:: ['package1', 'package2', 'package3'] Each value in the sequence should be a :term:`dotted Python name`. ``pyramid.includes`` vs. :meth:`pyramid.config.Configurator.include` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Two methods exist for including packages: ``pyramid.includes`` and :meth:`pyramid.config.Configurator.include`. This section explains their equivalence. Using PasteDeploy +++++++++++++++++ Using the following ``pyramid.includes`` setting in the PasteDeploy ``.ini`` file in your application: .. code-block:: ini [app:main] pyramid.includes = pyramid_debugtoolbar pyramid_tm Is equivalent to using the following statements in your configuration code: .. code-block:: python :linenos: from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) # ... config.include('pyramid_debugtoolbar') config.include('pyramid_tm') # ... It is fine to use both or either form. Plain Python ++++++++++++ Using the following ``pyramid.includes`` setting in your plain-Python Pyramid application: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': settings = {'pyramid.includes':'pyramid_debugtoolbar pyramid_tm'} config = Configurator(settings=settings) Is equivalent to using the following statements in your configuration code: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': settings = {} config = Configurator(settings=settings) config.include('pyramid_debugtoolbar') config.include('pyramid_tm') It is fine to use both or either form. .. _explicit_tween_config: Explicit Tween Configuration ---------------------------- This value allows you to perform explicit :term:`tween` ordering in your configuration. Tweens are bits of code used by add-on authors to extend Pyramid. They form a chain, and require ordering. Ideally you won't need to use the ``pyramid.tweens`` setting at all. Tweens are generally ordered and included "implicitly" when an add-on package which registers a tween is "included". Packages are included when you name a ``pyramid.includes`` setting in your configuration or when you call :meth:`pyramid.config.Configurator.include`. Authors of included add-ons provide "implicit" tween configuration ordering hints to Pyramid when their packages are included. However, the implicit tween ordering is only best-effort. Pyramid will attempt to provide an implicit order of tweens as best it can using hints provided by add-on authors, but because it's only best-effort, if very precise tween ordering is required, the only surefire way to get it is to use an explicit tween order. You may be required to inspect your tween ordering (see :ref:`displaying_tweens`) and add a ``pyramid.tweens`` configuration value at the behest of an add-on author. +---------------------------+ | Config File Setting Name | +===========================+ | ``pyramid.tweens`` | +---------------------------+ The value assigned to ``pyramid.tweens`` should be a sequence. The sequence can take several different forms. 1) It can be a string. If it is a string, the tween names can be separated by spaces:: pkg.tween_factory1 pkg.tween_factory2 pkg.tween_factory3 The tween names can also be separated by carriage returns:: pkg.tween_factory1 pkg.tween_factory2 pkg.tween_factory3 2) It can be a Python list, where the values are strings:: ['pkg.tween_factory1', 'pkg.tween_factory2', 'pkg.tween_factory3'] Each value in the sequence should be a :term:`dotted Python name`. PasteDeploy Configuration vs. Plain-Python Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Using the following ``pyramid.tweens`` setting in the PasteDeploy ``.ini`` file in your application: .. code-block:: ini [app:main] pyramid.tweens = pyramid_debugtoolbar.toolbar.tween_factory pyramid.tweens.excview_tween_factory pyramid_tm.tm_tween_factory Is equivalent to using the following statements in your configuration code: .. code-block:: python :linenos: from pyramid.config import Configurator def main(global_config, **settings): settings['pyramid.tweens'] = [ 'pyramid_debugtoolbar.toolbar.tween_factory', 'pyramid.tweebs.excview_tween_factory', 'pyramid_tm.tm_tween_factory', ] config = Configurator(settings=settings) It is fine to use both or either form. Examples -------- Let's presume your configuration file is named ``MyProject.ini``, and there is a section representing your application named ``[app:main]`` within the file that represents your :app:`Pyramid` application. The configuration file settings documented in the above "Config File Setting Name" column would go in the ``[app:main]`` section. Here's an example of such a section: .. code-block:: ini :linenos: [app:main] use = egg:MyProject pyramid.reload_templates = true pyramid.debug_authorization = true You can also use environment variables to accomplish the same purpose for settings documented as such. For example, you might start your :app:`Pyramid` application using the following command line: .. code-block:: text $ PYRAMID_DEBUG_AUTHORIZATION=1 PYRAMID_RELOAD_TEMPLATES=1 \ $VENV/bin/pserve MyProject.ini If you started your application this way, your :app:`Pyramid` application would behave in the same manner as if you had placed the respective settings in the ``[app:main]`` section of your application's ``.ini`` file. If you want to turn all ``debug`` settings (every setting that starts with ``pyramid.debug_``) on in one fell swoop, you can use ``PYRAMID_DEBUG_ALL=1`` as an environment variable setting or you may use ``pyramid.debug_all=true`` in the config file. Note that this does not affect settings that do not start with ``pyramid.debug_*`` such as ``pyramid.reload_templates``. If you want to turn all ``pyramid.reload`` settings (every setting that starts with ``pyramid.reload_``) on in one fell swoop, you can use ``PYRAMID_RELOAD_ALL=1`` as an environment variable setting or you may use ``pyramid.reload_all=true`` in the config file. Note that this does not affect settings that do not start with ``pyramid.reload_*`` such as ``pyramid.debug_notfound``. .. note:: Specifying configuration settings via environment variables is generally most useful during development, where you may wish to augment or override the more permanent settings in the configuration file. This is useful because many of the reload and debug settings may have performance or security (i.e., disclosure) implications that make them undesirable in a production environment. .. index:: single: reload_templates single: reload_assets Understanding the Distinction Between ``reload_templates`` and ``reload_assets`` -------------------------------------------------------------------------------- The difference between ``pyramid.reload_assets`` and ``pyramid.reload_templates`` is a bit subtle. Templates are themselves also treated by :app:`Pyramid` as asset files (along with other static files), so the distinction can be confusing. It's helpful to read :ref:`overriding_assets_section` for some context about assets in general. When ``pyramid.reload_templates`` is true, :app:`Pyramid` takes advantage of the underlying templating system's ability to check for file modifications to an individual template file. When ``pyramid.reload_templates`` is true, but ``pyramid.reload_assets`` is *not* true, the template filename returned by the ``pkg_resources`` package (used under the hood by asset resolution) is cached by :app:`Pyramid` on the first request. Subsequent requests for the same template file will return a cached template filename. The underlying templating system checks for modifications to this particular file for every request. Setting ``pyramid.reload_templates`` to ``True`` doesn't affect performance dramatically (although it should still not be used in production because it has some effect). However, when ``pyramid.reload_assets`` is true, :app:`Pyramid` will not cache the template filename, meaning you can see the effect of changing the content of an overridden asset directory for templates without restarting the server after every change. Subsequent requests for the same template file may return different filenames based on the current state of overridden asset directories. Setting ``pyramid.reload_assets`` to ``True`` affects performance *dramatically*, slowing things down by an order of magnitude for each template rendering. However, it's convenient to enable when moving files around in overridden asset directories. ``pyramid.reload_assets`` makes the system *very slow* when templates are in use. Never set ``pyramid.reload_assets`` to ``True`` on a production system. .. index:: par: settings; adding custom .. _adding_a_custom_setting: Adding a Custom Setting ----------------------- From time to time, you may need to add a custom setting to your application. Here's how: - If you're using an ``.ini`` file, change the ``.ini`` file, adding the setting to the ``[app:foo]`` section representing your Pyramid application. For example: .. code-block:: ini [app:main] # .. other settings debug_frobnosticator = True - In the ``main()`` function that represents the place that your Pyramid WSGI application is created, anticipate that you'll be getting this key/value pair as a setting and do any type conversion necessary. If you've done any type conversion of your custom value, reset the converted values into the ``settings`` dictionary *before* you pass the dictionary as ``settings`` to the :term:`Configurator`. For example: .. code-block:: python def main(global_config, **settings): # ... from pyramid.settings import asbool debug_frobnosticator = asbool(settings.get( 'debug_frobnosticator', 'false')) settings['debug_frobnosticator'] = debug_frobnosticator config = Configurator(settings=settings) .. note:: It's especially important that you mutate the ``settings`` dictionary with the converted version of the variable *before* passing it to the Configurator: the configurator makes a *copy* of ``settings``, it doesn't use the one you pass directly. - When creating an ``includeme`` function that will be later added to your application's configuration you may access the ``settings`` dictionary through the instance of the :term:`Configurator` that is passed into the function as its only argument. For Example: .. code-block:: python def includeme(config): settings = config.registry.settings debug_frobnosticator = settings['debug_frobnosticator'] - In the runtime code from where you need to access the new settings value, find the value in the ``registry.settings`` dictionary and use it. In :term:`view` code (or any other code that has access to the request), the easiest way to do this is via ``request.registry.settings``. For example: .. code-block:: python settings = request.registry.settings debug_frobnosticator = settings['debug_frobnosticator'] If you wish to use the value in code that does not have access to the request and you wish to use the value, you'll need to use the :func:`pyramid.threadlocal.get_current_registry` API to obtain the current registry, then ask for its ``settings`` attribute. For example: .. code-block:: python registry = pyramid.threadlocal.get_current_registry() settings = registry.settings debug_frobnosticator = settings['debug_frobnosticator'] pyramid-1.6/docs/narr/events.rst0000644000076500000240000001774612610765056017531 0ustar michaelstaff00000000000000.. index:: single: event single: subscriber single: INewRequest single: INewResponse single: NewRequest single: NewResponse .. _events_chapter: Using Events ============ An *event* is an object broadcast by the :app:`Pyramid` framework at interesting points during the lifetime of an application. You don't need to use events in order to create most :app:`Pyramid` applications, but they can be useful when you want to perform slightly advanced operations. For example, subscribing to an event can allow you to run some code as the result of every new request. Events in :app:`Pyramid` are always broadcast by the framework. However, they only become useful when you register a *subscriber*. A subscriber is a function that accepts a single argument named `event`: .. code-block:: python :linenos: def mysubscriber(event): print(event) The above is a subscriber that simply prints the event to the console when it's called. The mere existence of a subscriber function, however, is not sufficient to arrange for it to be called. To arrange for the subscriber to be called, you'll need to use the :meth:`pyramid.config.Configurator.add_subscriber` method or you'll need to use the :func:`pyramid.events.subscriber` decorator to decorate a function found via a :term:`scan`. Configuring an Event Listener Imperatively ------------------------------------------ You can imperatively configure a subscriber function to be called for some event type via the :meth:`~pyramid.config.Configurator.add_subscriber` method: .. code-block:: python :linenos: from pyramid.events import NewRequest from subscribers import mysubscriber # "config" below is assumed to be an instance of a # pyramid.config.Configurator object config.add_subscriber(mysubscriber, NewRequest) The first argument to :meth:`~pyramid.config.Configurator.add_subscriber` is the subscriber function (or a :term:`dotted Python name` which refers to a subscriber callable); the second argument is the event type. .. seealso:: See also :term:`Configurator`. Configuring an Event Listener Using a Decorator ----------------------------------------------- You can configure a subscriber function to be called for some event type via the :func:`pyramid.events.subscriber` function. .. code-block:: python :linenos: from pyramid.events import NewRequest from pyramid.events import subscriber @subscriber(NewRequest) def mysubscriber(event): event.request.foo = 1 When the :func:`~pyramid.events.subscriber` decorator is used, a :term:`scan` must be performed against the package containing the decorated function for the decorator to have any effect. Either of the above registration examples implies that every time the :app:`Pyramid` framework emits an event object that supplies an :class:`pyramid.events.NewRequest` interface, the ``mysubscriber`` function will be called with an *event* object. As you can see, a subscription is made in terms of a *class* (such as :class:`pyramid.events.NewResponse`). The event object sent to a subscriber will always be an object that possesses an :term:`interface`. For :class:`pyramid.events.NewResponse`, that interface is :class:`pyramid.interfaces.INewResponse`. The interface documentation provides information about available attributes and methods of the event objects. The return value of a subscriber function is ignored. Subscribers to the same event type are not guaranteed to be called in any particular order relative to each other. All the concrete :app:`Pyramid` event types are documented in the :ref:`events_module` API documentation. An Example ---------- If you create event listener functions in a ``subscribers.py`` file in your application like so: .. code-block:: python :linenos: def handle_new_request(event): print('request', event.request) def handle_new_response(event): print('response', event.response) You may configure these functions to be called at the appropriate times by adding the following code to your application's configuration startup: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_subscriber('myproject.subscribers.handle_new_request', 'pyramid.events.NewRequest') config.add_subscriber('myproject.subscribers.handle_new_response', 'pyramid.events.NewResponse') Either mechanism causes the functions in ``subscribers.py`` to be registered as event subscribers. Under this configuration, when the application is run, each time a new request or response is detected, a message will be printed to the console. Each of our subscriber functions accepts an ``event`` object and prints an attribute of the event object. This begs the question: how can we know which attributes a particular event has? We know that :class:`pyramid.events.NewRequest` event objects have a ``request`` attribute, which is a :term:`request` object, because the interface defined at :class:`pyramid.interfaces.INewRequest` says it must. Likewise, we know that :class:`pyramid.interfaces.NewResponse` events have a ``response`` attribute, which is a response object constructed by your application, because the interface defined at :class:`pyramid.interfaces.INewResponse` says it must (:class:`pyramid.events.NewResponse` objects also have a ``request``). .. _custom_events: Creating Your Own Events ------------------------ In addition to using the events that the Pyramid framework creates, you can create your own events for use in your application. This can be useful to decouple parts of your application. For example, suppose your application has to do many things when a new document is created. Rather than putting all this logic in the view that creates the document, you can create the document in your view and then fire a custom event. Subscribers to the custom event can take other actions, such as indexing the document, sending email, or sending a message to a remote system. An event is simply an object. There are no required attributes or method for your custom events. In general, your events should keep track of the information that subscribers will need. Here are some example custom event classes: .. code-block:: python :linenos: class DocCreated(object): def __init__(self, doc, request): self.doc = doc self.request = request class UserEvent(object): def __init__(self, user): self.user = user class UserLoggedIn(UserEvent): pass Some Pyramid applications choose to define custom events classes in an ``events`` module. You can subscribe to custom events in the same way that you subscribe to Pyramid events—either imperatively or with a decorator. You can also use custom events with :ref:`subscriber predicates `. Here's an example of subscribing to a custom event with a decorator: .. code-block:: python :linenos: from pyramid.events import subscriber from .events import DocCreated from .index import index_doc @subscriber(DocCreated) def index_doc(event): # index the document using our application's index_doc function index_doc(event.doc, event.request) The above example assumes that the application defines a ``DocCreated`` event class and an ``index_doc`` function. To fire your custom events use the :meth:`pyramid.registry.Registry.notify` method, which is most often accessed as ``request.registry.notify``. For example: .. code-block:: python :linenos: from .events import DocCreated def new_doc_view(request): doc = MyDoc() event = DocCreated(doc, request) request.registry.notify(event) return {'document': doc} This example view will notify all subscribers to the custom ``DocCreated`` event. Note that when you fire an event, all subscribers are run synchronously so it's generally not a good idea to create event handlers that may take a long time to run. Although event handlers could be used as a central place to spawn tasks on your own message queues. pyramid-1.6/docs/narr/extconfig.rst0000644000076500000240000004722712621241570020200 0ustar michaelstaff00000000000000.. index:: single: extending configuration .. _extconfig_narr: Extending Pyramid Configuration =============================== Pyramid allows you to extend its Configurator with custom directives. Custom directives can use other directives, they can add a custom :term:`action`, they can participate in :term:`conflict resolution`, and they can provide some number of :term:`introspectable` objects. .. index:: single: add_directive pair: configurator; adding directives .. _add_directive: Adding Methods to the Configurator via ``add_directive`` -------------------------------------------------------- Framework extension writers can add arbitrary methods to a :term:`Configurator` by using the :meth:`pyramid.config.Configurator.add_directive` method of the configurator. Using :meth:`~pyramid.config.Configurator.add_directive` makes it possible to extend a Pyramid configurator in arbitrary ways, and allows it to perform application-specific tasks more succinctly. The :meth:`~pyramid.config.Configurator.add_directive` method accepts two positional arguments: a method name and a callable object. The callable object is usually a function that takes the configurator instance as its first argument and accepts other arbitrary positional and keyword arguments. For example: .. code-block:: python :linenos: from pyramid.events import NewRequest from pyramid.config import Configurator def add_newrequest_subscriber(config, subscriber): config.add_subscriber(subscriber, NewRequest) if __name__ == '__main__': config = Configurator() config.add_directive('add_newrequest_subscriber', add_newrequest_subscriber) Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can then call the added directive by its given name as if it were a built-in method of the Configurator: .. code-block:: python :linenos: def mysubscriber(event): print(event.request) config.add_newrequest_subscriber(mysubscriber) A call to :meth:`~pyramid.config.Configurator.add_directive` is often "hidden" within an ``includeme`` function within a "frameworky" package meant to be included as per :ref:`including_configuration` via :meth:`~pyramid.config.Configurator.include`. For example, if you put this code in a package named ``pyramid_subscriberhelpers``: .. code-block:: python :linenos: def includeme(config): config.add_directive('add_newrequest_subscriber', add_newrequest_subscriber) The user of the add-on package ``pyramid_subscriberhelpers`` would then be able to install it and subsequently do: .. code-block:: python :linenos: def mysubscriber(event): print(event.request) from pyramid.config import Configurator config = Configurator() config.include('pyramid_subscriberhelpers') config.add_newrequest_subscriber(mysubscriber) Using ``config.action`` in a Directive -------------------------------------- If a custom directive can't do its work exclusively in terms of existing configurator methods (such as :meth:`pyramid.config.Configurator.add_subscriber` as above), the directive may need to make use of the :meth:`pyramid.config.Configurator.action` method. This method adds an entry to the list of "actions" that Pyramid will attempt to process when :meth:`pyramid.config.Configurator.commit` is called. An action is simply a dictionary that includes a :term:`discriminator`, possibly a callback function, and possibly other metadata used by Pyramid's action system. Here's an example directive which uses the "action" method: .. code-block:: python :linenos: def add_jammyjam(config, jammyjam): def register(): config.registry.jammyjam = jammyjam config.action('jammyjam', register) if __name__ == '__main__': config = Configurator() config.add_directive('add_jammyjam', add_jammyjam) Fancy, but what does it do? The action method accepts a number of arguments. In the above directive named ``add_jammyjam``, we call :meth:`~pyramid.config.Configurator.action` with two arguments: the string ``jammyjam`` is passed as the first argument named ``discriminator``, and the closure function named ``register`` is passed as the second argument named ``callable``. When the :meth:`~pyramid.config.Configurator.action` method is called, it appends an action to the list of pending configuration actions. All pending actions with the same discriminator value are potentially in conflict with one another (see :ref:`conflict_detection`). When the :meth:`~pyramid.config.Configurator.commit` method of the Configurator is called (either explicitly or as the result of calling :meth:`~pyramid.config.Configurator.make_wsgi_app`), conflicting actions are potentially automatically resolved as per :ref:`automatic_conflict_resolution`. If a conflict cannot be automatically resolved, a :exc:`pyramid.exceptions.ConfigurationConflictError` is raised and application startup is prevented. In our above example, therefore, if a consumer of our ``add_jammyjam`` directive did this: .. code-block:: python config.add_jammyjam('first') config.add_jammyjam('second') When the action list was committed resulting from the set of calls above, our user's application would not start, because the discriminators of the actions generated by the two calls are in direct conflict. Automatic conflict resolution cannot resolve the conflict (because no ``config.include`` is involved), and the user provided no intermediate :meth:`pyramid.config.Configurator.commit` call between the calls to ``add_jammyjam`` to ensure that the successive calls did not conflict with each other. This demonstrates the purpose of the discriminator argument to the action method: it's used to indicate a uniqueness constraint for an action. Two actions with the same discriminator will conflict unless the conflict is automatically or manually resolved. A discriminator can be any hashable object, but it is generally a string or a tuple. *You use a discriminator to declaratively ensure that the user doesn't provide ambiguous configuration statements.* But let's imagine that a consumer of ``add_jammyjam`` used it in such a way that no configuration conflicts are generated. .. code-block:: python config.add_jammyjam('first') What happens now? When the ``add_jammyjam`` method is called, an action is appended to the pending actions list. When the pending configuration actions are processed during :meth:`~pyramid.config.Configurator.commit`, and no conflicts occur, the *callable* provided as the second argument to the :meth:`~pyramid.config.Configurator.action` method within ``add_jammyjam`` is called with no arguments. The callable in ``add_jammyjam`` is the ``register`` closure function. It simply sets the value ``config.registry.jammyjam`` to whatever the user passed in as the ``jammyjam`` argument to the ``add_jammyjam`` function. Therefore, the result of the user's call to our directive will set the ``jammyjam`` attribute of the registry to the string ``first``. *A callable is used by a directive to defer the result of a user's call to the directive until conflict detection has had a chance to do its job*. Other arguments exist to the :meth:`~pyramid.config.Configurator.action` method, including ``args``, ``kw``, ``order``, and ``introspectables``. ``args`` and ``kw`` exist as values, which if passed will be used as arguments to the ``callable`` function when it is called back. For example, our directive might use them like so: .. code-block:: python :linenos: def add_jammyjam(config, jammyjam): def register(*arg, **kw): config.registry.jammyjam_args = arg config.registry.jammyjam_kw = kw config.registry.jammyjam = jammyjam config.action('jammyjam', register, args=('one',), kw={'two':'two'}) In the above example, when this directive is used to generate an action, and that action is committed, ``config.registry.jammyjam_args`` will be set to ``('one',)`` and ``config.registry.jammyjam_kw`` will be set to ``{'two':'two'}``. ``args`` and ``kw`` are honestly not very useful when your ``callable`` is a closure function, because you already usually have access to every local in the directive without needing them to be passed back. They can be useful, however, if you don't use a closure as a callable. ``order`` is a crude order control mechanism. ``order`` defaults to the integer ``0``; it can be set to any other integer. All actions that share an order will be called before other actions that share a higher order. This makes it possible to write a directive with callable logic that relies on the execution of the callable of another directive being done first. For example, Pyramid's :meth:`pyramid.config.Configurator.add_view` directive registers an action with a higher order than the :meth:`pyramid.config.Configurator.add_route` method. Due to this, the ``add_view`` method's callable can assume that, if a ``route_name`` was passed to it, that a route by this name was already registered by ``add_route``, and if such a route has not already been registered, it's a configuration error (a view that names a nonexistent route via its ``route_name`` parameter will never be called). .. versionchanged:: 1.6 As of Pyramid 1.6 it is possible for one action to invoke another. See :ref:`ordering_actions` for more information. Finally, ``introspectables`` is a sequence of :term:`introspectable` objects. You can pass a sequence of introspectables to the :meth:`~pyramid.config.Configurator.action` method, which allows you to augment Pyramid's configuration introspection system. .. _ordering_actions: Ordering Actions ---------------- In Pyramid every :term:`action` has an inherent ordering relative to other actions. The logic within actions is deferred until a call to :meth:`pyramid.config.Configurator.commit` (which is automatically invoked by :meth:`pyramid.config.Configurator.make_wsgi_app`). This means you may call ``config.add_view(route_name='foo')`` **before** ``config.add_route('foo', '/foo')`` because nothing actually happens until commit-time. During a commit cycle, conflicts are resolved, and actions are ordered and executed. By default, almost every action in Pyramid has an ``order`` of :const:`pyramid.config.PHASE3_CONFIG`. Every action within the same order-level will be executed in the order it was called. This means that if an action must be reliably executed before or after another action, the ``order`` must be defined explicitly to make this work. For example, views are dependent on routes being defined. Thus the action created by :meth:`pyramid.config.Configurator.add_route` has an ``order`` of :const:`pyramid.config.PHASE2_CONFIG`. Pre-defined Phases ~~~~~~~~~~~~~~~~~~ :const:`pyramid.config.PHASE0_CONFIG` - This phase is reserved for developers who want to execute actions prior to Pyramid's core directives. :const:`pyramid.config.PHASE1_CONFIG` - :meth:`pyramid.config.Configurator.add_renderer` - :meth:`pyramid.config.Configurator.add_route_predicate` - :meth:`pyramid.config.Configurator.add_subscriber_predicate` - :meth:`pyramid.config.Configurator.add_view_predicate` - :meth:`pyramid.config.Configurator.set_authorization_policy` - :meth:`pyramid.config.Configurator.set_default_permission` - :meth:`pyramid.config.Configurator.set_view_mapper` :const:`pyramid.config.PHASE2_CONFIG` - :meth:`pyramid.config.Configurator.add_route` - :meth:`pyramid.config.Configurator.set_authentication_policy` :const:`pyramid.config.PHASE3_CONFIG` - The default for all builtin or custom directives unless otherwise specified. Calling Actions from Actions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.6 Pyramid's configurator allows actions to be added during a commit-cycle as long as they are added to the current or a later ``order`` phase. This means that your custom action can defer decisions until commit-time and then do things like invoke :meth:`pyramid.config.Configurator.add_route`. It can also provide better conflict detection if your addon needs to call more than one other action. For example, let's make an addon that invokes ``add_route`` and ``add_view``, but we want it to conflict with any other call to our addon: .. code-block:: python :linenos: from pyramid.config import PHASE0_CONFIG def includeme(config): config.add_directive('add_auto_route', add_auto_route) def add_auto_route(config, name, view): def register(): config.add_view(route_name=name, view=view) config.add_route(name, '/' + name) config.action(('auto route', name), register, order=PHASE0_CONFIG) Now someone else can use your addon and be informed if there is a conflict between this route and another, or two calls to ``add_auto_route``. Notice how we had to invoke our action **before** ``add_view`` or ``add_route``. If we tried to invoke this afterward, the subsequent calls to ``add_view`` and ``add_route`` would cause conflicts because that phase had already been executed, and the configurator cannot go back in time to add more views during that commit-cycle. .. code-block:: python :linenos: from pyramid.config import Configurator def main(global_config, **settings): config = Configurator() config.include('auto_route_addon') config.add_auto_route('foo', my_view) def my_view(request): return request.response .. _introspection: Adding Configuration Introspection ---------------------------------- .. versionadded:: 1.3 Pyramid provides a configuration introspection system that can be used by debugging tools to provide visibility into the configuration of a running application. All built-in Pyramid directives (such as :meth:`pyramid.config.Configurator.add_view` and :meth:`pyramid.config.Configurator.add_route`) register a set of introspectables when called. For example, when you register a view via ``add_view``, the directive registers at least one introspectable: an introspectable about the view registration itself, providing human-consumable values for the arguments passed into it. You can later use the introspection query system to determine whether a particular view uses a renderer, or whether a particular view is limited to a particular request method, or against which routes a particular view is registered. The Pyramid "debug toolbar" makes use of the introspection system in various ways to display information to Pyramid developers. Introspection values are set when a sequence of :term:`introspectable` objects is passed to the :meth:`~pyramid.config.Configurator.action` method. Here's an example of a directive which uses introspectables: .. code-block:: python :linenos: def add_jammyjam(config, value): def register(): config.registry.jammyjam = value intr = config.introspectable(category_name='jammyjams', discriminator='jammyjam', title='a jammyjam', type_name=None) intr['value'] = value config.action('jammyjam', register, introspectables=(intr,)) if __name__ == '__main__': config = Configurator() config.add_directive('add_jammyjam', add_jammyjam) If you notice, the above directive uses the ``introspectable`` attribute of a Configurator (:attr:`pyramid.config.Configurator.introspectable`) to create an introspectable object. The introspectable object's constructor requires at least four arguments: the ``category_name``, the ``discriminator``, the ``title``, and the ``type_name``. The ``category_name`` is a string representing the logical category for this introspectable. Usually the category_name is a pluralization of the type of object being added via the action. The ``discriminator`` is a value unique **within the category** (unlike the action discriminator, which must be unique within the entire set of actions). It is typically a string or tuple representing the values unique to this introspectable within the category. It is used to generate links and as part of a relationship-forming target for other introspectables. The ``title`` is a human-consumable string that can be used by introspection system frontends to show a friendly summary of this introspectable. The ``type_name`` is a value that can be used to subtype this introspectable within its category for sorting and presentation purposes. It can be any value. An introspectable is also dictionary-like. It can contain any set of key/value pairs, typically related to the arguments passed to its related directive. While the ``category_name``, ``discriminator``, ``title``, and ``type_name`` are *metadata* about the introspectable, the values provided as key/value pairs are the actual data provided by the introspectable. In the above example, we set the ``value`` key to the value of the ``value`` argument passed to the directive. Our directive above mutates the introspectable, and passes it in to the ``action`` method as the first element of a tuple as the value of the ``introspectable`` keyword argument. This associates this introspectable with the action. Introspection tools will then display this introspectable in their index. Introspectable Relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Two introspectables may have relationships between each other. .. code-block:: python :linenos: def add_jammyjam(config, value, template): def register(): config.registry.jammyjam = (value, template) intr = config.introspectable(category_name='jammyjams', discriminator='jammyjam', title='a jammyjam', type_name=None) intr['value'] = value tmpl_intr = config.introspectable(category_name='jammyjam templates', discriminator=template, title=template, type_name=None) tmpl_intr['value'] = template intr.relate('jammyjam templates', template) config.action('jammyjam', register, introspectables=(intr, tmpl_intr)) if __name__ == '__main__': config = Configurator() config.add_directive('add_jammyjam', add_jammyjam) In the above example, the ``add_jammyjam`` directive registers two introspectables: the first is related to the ``value`` passed to the directive, and the second is related to the ``template`` passed to the directive. If you believe a concept within a directive is important enough to have its own introspectable, you can cause the same directive to register more than one introspectable, registering one introspectable for the "main idea" and another for a related concept. The call to ``intr.relate`` above (:meth:`pyramid.interfaces.IIntrospectable.relate`) is passed two arguments: a category name and a directive. The example above effectively indicates that the directive wishes to form a relationship between the ``intr`` introspectable and the ``tmpl_intr`` introspectable; the arguments passed to ``relate`` are the category name and discriminator of the ``tmpl_intr`` introspectable. Relationships need not be made between two introspectables created by the same directive. Instead a relationship can be formed between an introspectable created in one directive and another introspectable created in another by calling ``relate`` on either side with the other directive's category name and discriminator. An error will be raised at configuration commit time if you attempt to relate an introspectable with another nonexistent introspectable, however. Introspectable relationships will show up in frontend system renderings of introspection values. For example, if a view registration names a route name, the introspectable related to the view callable will show a reference to the route to which it relates and vice versa. pyramid-1.6/docs/narr/extending.rst0000644000076500000240000003030012621241570020157 0ustar michaelstaff00000000000000.. _extending_chapter: Extending an Existing :app:`Pyramid` Application ================================================ If a :app:`Pyramid` developer has obeyed certain constraints while building an application, a third party should be able to change the application's behavior without needing to modify its source code. The behavior of a :app:`Pyramid` application that obeys certain constraints can be *overridden* or *extended* without modification. We'll define some jargon here for the benefit of identifying the parties involved in such an effort. Developer The original application developer. Integrator Another developer who wishes to reuse the application written by the original application developer in an unanticipated context. They may also wish to modify the original application without changing the original application's source code. The Difference Between "Extensible" and "Pluggable" Applications ---------------------------------------------------------------- Other web frameworks, such as :term:`Django`, advertise that they allow developers to create "pluggable applications". They claim that if you create an application in a certain way, it will be integratable in a sensible, structured way into another arbitrarily-written application or project created by a third-party developer. :app:`Pyramid`, as a platform, does not claim to provide such a feature. The platform provides no guarantee that you can create an application and package it up such that an arbitrary integrator can use it as a subcomponent in a larger Pyramid application or project. Pyramid does not mandate the constraints necessary for such a pattern to work satisfactorily. Because Pyramid is not very "opinionated", developers are able to use wildly different patterns and technologies to build an application. A given Pyramid application may happen to be reusable by a particular third party integrator because the integrator and the original developer may share similar base technology choices (such as the use of a particular relational database or ORM). But the same application may not be reusable by a different developer, because they have made different technology choices which are incompatible with the original developer's. As a result, the concept of a "pluggable application" is left to layers built above Pyramid, such as a "CMS" layer or "application server" layer. Such layers are apt to provide the necessary "opinions" (such as mandating a storage layer, a templating system, and a structured, well-documented pattern of registering that certain URLs map to certain bits of code) which makes the concept of a "pluggable application" possible. "Pluggable applications", thus, should not plug into Pyramid itself but should instead plug into a system written atop Pyramid. Although it does not provide for "pluggable applications", Pyramid *does* provide a rich set of mechanisms which allows for the extension of a single existing application. Such features can be used by frameworks built using Pyramid as a base. All Pyramid applications may not be *pluggable*, but all Pyramid applications are *extensible*. .. index:: single: extensible application .. _building_an_extensible_app: Rules for Building an Extensible Application -------------------------------------------- There is only one rule you need to obey if you want to build a maximally extensible :app:`Pyramid` application: as a developer, you should factor any overridable :term:`imperative configuration` you've created into functions which can be used via :meth:`pyramid.config.Configurator.include`, rather than inlined as calls to methods of a :term:`Configurator` within the ``main`` function in your application's ``__init__.py``. For example, rather than: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_view('myapp.views.view1', name='view1') config.add_view('myapp.views.view2', name='view2') You should move the calls to ``add_view`` outside of the (non-reusable) ``if __name__ == '__main__'`` block, and into a reusable function: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.include(add_views) def add_views(config): config.add_view('myapp.views.view1', name='view1') config.add_view('myapp.views.view2', name='view2') Doing this allows an integrator to maximally reuse the configuration statements that relate to your application by allowing them to selectively include or exclude the configuration functions you've created from an "override package". Alternatively you can use :term:`ZCML` for the purpose of making configuration extensible and overridable. :term:`ZCML` declarations that belong to an application can be overridden and extended by integrators as necessary in a similar fashion. If you use only :term:`ZCML` to configure your application, it will automatically be maximally extensible without any manual effort. See :term:`pyramid_zcml` for information about using ZCML. Fundamental Plugpoints ~~~~~~~~~~~~~~~~~~~~~~ The fundamental "plug points" of an application developed using :app:`Pyramid` are *routes*, *views*, and *assets*. Routes are declarations made using the :meth:`pyramid.config.Configurator.add_route` method. Views are declarations made using the :meth:`pyramid.config.Configurator.add_view` method. Assets are files that are accessed by :app:`Pyramid` using the :term:`pkg_resources` API such as static files and templates via a :term:`asset specification`. Other directives and configurator methods also deal in routes, views, and assets. For example, the ``add_handler`` directive of the ``pyramid_handlers`` package adds a single route and some number of views. .. index:: single: extending an existing application Extending an Existing Application --------------------------------- The steps for extending an existing application depend largely on whether the application does or does not use configuration decorators or imperative code. If the Application Has Configuration Decorations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You've inherited a :app:`Pyramid` application which you'd like to extend or override that uses :class:`pyramid.view.view_config` decorators or other :term:`configuration decoration` decorators. If you just want to *extend* the application, you can run a :term:`scan` against the application's package, then add additional configuration that registers more views or routes. .. code-block:: python :linenos: if __name__ == '__main__': config.scan('someotherpackage') config.add_view('mypackage.views.myview', name='myview') If you want to *override* configuration in the application, you *may* need to run :meth:`pyramid.config.Configurator.commit` after performing the scan of the original package, then add additional configuration that registers more views or routes which perform overrides. .. code-block:: python :linenos: if __name__ == '__main__': config.scan('someotherpackage') config.commit() config.add_view('mypackage.views.myview', name='myview') Once this is done, you should be able to extend or override the application like any other (see :ref:`extending_the_application`). You can alternatively just prevent a :term:`scan` from happening by omitting any call to the :meth:`pyramid.config.Configurator.scan` method. This will cause the decorators attached to objects in the target application to do nothing. At this point, you will need to convert all the configuration done in decorators into equivalent imperative configuration or ZCML, and add that configuration or ZCML to a separate Python package as described in :ref:`extending_the_application`. .. _extending_the_application: Extending the Application ~~~~~~~~~~~~~~~~~~~~~~~~~ To extend or override the behavior of an existing application, you will need to create a new package which includes the configuration of the old package, and you'll perhaps need to create implementations of the types of things you'd like to override (such as views), to which they are referred within the original package. The general pattern for extending an existing application looks something like this: - Create a new Python package. The easiest way to do this is to create a new :app:`Pyramid` application using the scaffold mechanism. See :ref:`creating_a_project` for more information. - In the new package, create Python files containing views and other overridden elements, such as templates and static assets as necessary. - Install the new package into the same Python environment as the original application (e.g., ``$VENV/bin/python setup.py develop`` or ``$VENV/bin/python setup.py install``). - Change the ``main`` function in the new package's ``__init__.py`` to include the original :app:`Pyramid` application's configuration functions via :meth:`pyramid.config.Configurator.include` statements or a :term:`scan`. - Wire the new views and assets created in the new package up using imperative registrations within the ``main`` function of the ``__init__.py`` file of the new application. This wiring should happen *after* including the configuration functions of the old application. These registrations will extend or override any registrations performed by the original application. See :ref:`overriding_views`, :ref:`overriding_routes`, and :ref:`overriding_resources`. .. index:: pair: overriding; views .. _overriding_views: Overriding Views ~~~~~~~~~~~~~~~~ The :term:`view configuration` declarations that you make which *override* application behavior will usually have the same :term:`view predicate` attributes as the original that you wish to override. These ```` declarations will point at "new" view code in the override package that you've created. The new view code itself will usually be copy-and-paste copies of view callables from the original application with slight tweaks. For example, if the original application has the following ``configure_views`` configuration method: .. code-block:: python :linenos: def configure_views(config): config.add_view('theoriginalapp.views.theview', name='theview') You can override the first view configuration statement made by ``configure_views`` within the override package, after loading the original configuration function: .. code-block:: python :linenos: from pyramid.config import Configurator from originalapp import configure_views if __name == '__main__': config = Configurator() config.include(configure_views) config.add_view('theoverrideapp.views.theview', name='theview') In this case, the ``theoriginalapp.views.theview`` view will never be executed. Instead, a new view, ``theoverrideapp.views.theview`` will be executed when request circumstances dictate. A similar pattern can be used to *extend* the application with ``add_view`` declarations. Just register a new view against some other set of predicates to make sure the URLs it implies are available on some other page rendering. .. index:: pair: overriding; routes .. _overriding_routes: Overriding Routes ~~~~~~~~~~~~~~~~~ Route setup is currently typically performed in a sequence of ordered calls to :meth:`~pyramid.config.Configurator.add_route`. Because these calls are ordered relative to each other, and because this ordering is typically important, you should retain their relative ordering when performing an override. Typically this means *copying* all the ``add_route`` statements into the override package's file and changing them as necessary. Then exclude any ``add_route`` statements from the original application. .. index:: pair: overriding; assets .. _overriding_resources: Overriding Assets ~~~~~~~~~~~~~~~~~ Assets are files on the filesystem that are accessible within a Python *package*. An entire chapter is devoted to assets: :ref:`assets_chapter`. Within this chapter is a section named :ref:`overriding_assets_section`. This section of that chapter describes in detail how to override package assets with other assets by using the :meth:`pyramid.config.Configurator.override_asset` method. Add such ``override_asset`` calls to your override package's ``__init__.py`` to perform overrides. pyramid-1.6/docs/narr/firstapp.rst0000644000076500000240000002327012606630333020034 0ustar michaelstaff00000000000000.. index:: single: hello world program .. _firstapp_chapter: Creating Your First :app:`Pyramid` Application ============================================== In this chapter, we will walk through the creation of a tiny :app:`Pyramid` application. After we're finished creating the application, we'll explain in more detail how it works. It assumes you already have :app:`Pyramid` installed. If you do not, head over to the :ref:`installing_chapter` section. .. _helloworld_imperative: Hello World ----------- Here's one of the very simplest :app:`Pyramid` applications: .. literalinclude:: helloworld.py :linenos: When this code is inserted into a Python script named ``helloworld.py`` and executed by a Python interpreter which has the :app:`Pyramid` software installed, an HTTP server is started on TCP port 8080. On UNIX: .. code-block:: text $ $VENV/bin/python helloworld.py On Windows: .. code-block:: text C:\> %VENV%\Scripts\python.exe helloworld.py This command will not return and nothing will be printed to the console. When port 8080 is visited by a browser on the URL ``/hello/world``, the server will simply serve up the text "Hello world!". If your application is running on your local system, using ``_ in a browser will show this result. Each time you visit a URL served by the application in a browser, a logging line will be emitted to the console displaying the hostname, the date, the request method and path, and some additional information. This output is done by the wsgiref server we've used to serve this application. It logs an "access log" in Apache combined logging format to the console. Press ``Ctrl-C`` (or ``Ctrl-Break`` on Windows) to stop the application. Now that we have a rudimentary understanding of what the application does, let's examine it piece by piece. Imports ~~~~~~~ The above ``helloworld.py`` script uses the following set of import statements: .. literalinclude:: helloworld.py :linenos: :lines: 1-3 The script imports the :class:`~pyramid.config.Configurator` class from the :mod:`pyramid.config` module. An instance of the :class:`~pyramid.config.Configurator` class is later used to configure your :app:`Pyramid` application. Like many other Python web frameworks, :app:`Pyramid` uses the :term:`WSGI` protocol to connect an application and a web server together. The :mod:`wsgiref` server is used in this example as a WSGI server for convenience, as it is shipped within the Python standard library. The script also imports the :class:`pyramid.response.Response` class for later use. An instance of this class will be used to create a web response. View Callable Declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~ The above script, beneath its set of imports, defines a function named ``hello_world``. .. literalinclude:: helloworld.py :linenos: :pyobject: hello_world The function accepts a single argument (``request``) and it returns an instance of the :class:`pyramid.response.Response` class. The single argument to the class' constructor is a string computed from parameters matched from the URL. This value becomes the body of the response. This function is known as a :term:`view callable`. A view callable accepts a single argument, ``request``. It is expected to return a :term:`response` object. A view callable doesn't need to be a function; it can be represented via another type of object, like a class or an instance, but for our purposes here, a function serves us well. A view callable is always called with a :term:`request` object. A request object is a representation of an HTTP request sent to :app:`Pyramid` via the active :term:`WSGI` server. A view callable is required to return a :term:`response` object because a response object has all the information necessary to formulate an actual HTTP response; this object is then converted to text by the :term:`WSGI` server which called Pyramid and it is sent back to the requesting browser. To return a response, each view callable creates an instance of the :class:`~pyramid.response.Response` class. In the ``hello_world`` function, a string is passed as the body to the response. .. index:: single: imperative configuration single: Configurator single: helloworld (imperative) .. _helloworld_imperative_appconfig: Application Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~ In the above script, the following code represents the *configuration* of this simple application. The application is configured using the previously defined imports and function definitions, placed within the confines of an ``if`` statement: .. literalinclude:: helloworld.py :linenos: :lines: 9-15 Let's break this down piece by piece. Configurator Construction ~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: helloworld.py :linenos: :lines: 9-10 The ``if __name__ == '__main__':`` line in the code sample above represents a Python idiom: the code inside this if clause is not invoked unless the script containing this code is run directly from the operating system command line. For example, if the file named ``helloworld.py`` contains the entire script body, the code within the ``if`` statement will only be invoked when ``python helloworld.py`` is executed from the command line. Using the ``if`` clause is necessary—or at least best practice—because code in a Python ``.py`` file may be eventually imported via the Python ``import`` statement by another ``.py`` file. ``.py`` files that are imported by other ``.py`` files are referred to as *modules*. By using the ``if __name__ == '__main__':`` idiom, the script above is indicating that it does not want the code within the ``if`` statement to execute if this module is imported from another; the code within the ``if`` block should only be run during a direct script execution. The ``config = Configurator()`` line above creates an instance of the :class:`~pyramid.config.Configurator` class. The resulting ``config`` object represents an API which the script uses to configure this particular :app:`Pyramid` application. Methods called on the Configurator will cause registrations to be made in an :term:`application registry` associated with the application. .. _adding_configuration: Adding Configuration ~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: helloworld.py :linenos: :lines: 11-12 The first line above calls the :meth:`pyramid.config.Configurator.add_route` method, which registers a :term:`route` to match any URL path that begins with ``/hello/`` followed by a string. The second line registers the ``hello_world`` function as a :term:`view callable` and makes sure that it will be called when the ``hello`` route is matched. .. index:: single: make_wsgi_app single: WSGI application WSGI Application Creation ~~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: helloworld.py :linenos: :lines: 13 After configuring views and ending configuration, the script creates a WSGI *application* via the :meth:`pyramid.config.Configurator.make_wsgi_app` method. A call to ``make_wsgi_app`` implies that all configuration is finished (meaning all method calls to the configurator, which sets up views and various other configuration settings, have been performed). The ``make_wsgi_app`` method returns a :term:`WSGI` application object that can be used by any WSGI server to present an application to a requestor. :term:`WSGI` is a protocol that allows servers to talk to Python applications. We don't discuss :term:`WSGI` in any depth within this book, but you can learn more about it by visiting `wsgi.org `_. The :app:`Pyramid` application object, in particular, is an instance of a class representing a :app:`Pyramid` :term:`router`. It has a reference to the :term:`application registry` which resulted from method calls to the configurator used to configure it. The :term:`router` consults the registry to obey the policy choices made by a single application. These policy choices were informed by method calls to the :term:`Configurator` made earlier; in our case, the only policy choices made were implied by calls to its ``add_view`` and ``add_route`` methods. WSGI Application Serving ~~~~~~~~~~~~~~~~~~~~~~~~ .. literalinclude:: helloworld.py :linenos: :lines: 14-15 Finally, we actually serve the application to requestors by starting up a WSGI server. We happen to use the :mod:`wsgiref` ``make_server`` server maker for this purpose. We pass in as the first argument ``'0.0.0.0'``, which means "listen on all TCP interfaces". By default, the HTTP server listens only on the ``127.0.0.1`` interface, which is problematic if you're running the server on a remote system and you wish to access it with a web browser from a local system. We also specify a TCP port number to listen on, which is 8080, passing it as the second argument. The final argument is the ``app`` object (a :term:`router`), which is the application we wish to serve. Finally, we call the server's ``serve_forever`` method, which starts the main loop in which it will wait for requests from the outside world. When this line is invoked, it causes the server to start listening on TCP port 8080. The server will serve requests forever, or at least until we stop it by killing the process which runs it (usually by pressing ``Ctrl-C`` or ``Ctrl-Break`` in the terminal we used to start it). Conclusion ~~~~~~~~~~ Our hello world application is one of the simplest possible :app:`Pyramid` applications, configured "imperatively". We can see that it's configured imperatively because the full power of Python is available to us as we perform configuration tasks. References ---------- For more information about the API of a :term:`Configurator` object, see :class:`~pyramid.config.Configurator` . For more information about :term:`view configuration`, see :ref:`view_config_chapter`. pyramid-1.6/docs/narr/hellotraversal.py0000644000076500000240000000120412234375161021046 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response class Resource(dict): pass def get_root(request): return Resource({'a': Resource({'b': Resource({'c': Resource()})})}) def hello_world_of_resources(context, request): output = "Here's a resource and its children: %s" % context return Response(output) if __name__ == '__main__': config = Configurator(root_factory=get_root) config.add_view(hello_world_of_resources, context=Resource) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() pyramid-1.6/docs/narr/hellotraversal.rst0000644000076500000240000000453412621241570021233 0ustar michaelstaff00000000000000.. _hello_traversal_chapter: Hello Traversal World ===================== .. index:: single: traversal quick example Traversal is an alternative to URL dispatch which allows Pyramid applications to map URLs to code. If code speaks louder than words, maybe this will help. Here is a single-file Pyramid application that uses traversal: .. literalinclude:: hellotraversal.py :linenos: You may notice that this application is intentionally very similar to the "hello world" application from :doc:`firstapp`. On lines 5-6, we create a trivial :term:`resource` class that's just a dictionary subclass. On lines 8-9, we hard-code a :term:`resource tree` in our :term:`root factory` function. On lines 11-13, we define a single :term:`view callable` that can display a single instance of our ``Resource`` class, passed as the ``context`` argument. The rest of the file sets up and serves our :app:`Pyramid` WSGI app. Line 18 is where our view gets configured for use whenever the traversal ends with an instance of our ``Resource`` class. Interestingly, there are no URLs explicitly configured in this application. Instead, the URL space is defined entirely by the keys in the resource tree. Example requests ---------------- If this example is running on http://localhost:8080, and the user browses to http://localhost:8080/a/b, Pyramid will call ``get_root(request)`` to get the root resource, then traverse the tree from there by key; starting from the root, it will find the child with key ``"a"``, then its child with key ``"b"``; then use that as the ``context`` argument for calling ``hello_world_of_resources``. Or, if the user browses to http://localhost:8080/, Pyramid will stop at the root—the outermost ``Resource`` instance, in this case—and use that as the ``context`` argument to the same view. Or, if the user browses to a key that doesn't exist in this resource tree, like http://localhost:8080/xyz or http://localhost:8080/a/b/c/d, the traversal will end by raising a KeyError, and Pyramid will turn that into a 404 HTTP response. A more complicated application could have many types of resources, with different view callables defined for each type, and even multiple views for each type. .. seealso:: Full technical details may be found in :doc:`traversal`. For more about *why* you might use traversal, see :doc:`muchadoabouttraversal`. pyramid-1.6/docs/narr/helloworld.py0000644000076500000240000000073612234375161020203 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello %(name)s!' % request.matchdict) if __name__ == '__main__': config = Configurator() config.add_route('hello', '/hello/{name}') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() pyramid-1.6/docs/narr/hooks.rst0000644000076500000240000015716412621241570017337 0ustar michaelstaff00000000000000.. _hooks_chapter: Using Hooks =========== "Hooks" can be used to influence the behavior of the :app:`Pyramid` framework in various ways. .. index:: single: not found view .. _changing_the_notfound_view: Changing the Not Found View --------------------------- When :app:`Pyramid` can't map a URL to view code, it invokes a :term:`Not Found View`, which is a :term:`view callable`. The default Not Found View can be overridden through application configuration. If your application uses :term:`imperative configuration`, you can replace the Not Found View by using the :meth:`pyramid.config.Configurator.add_notfound_view` method: .. code-block:: python :linenos: def notfound(request): return Response('Not Found, dude', status='404 Not Found') def main(globals, **settings): config = Configurator() config.add_notfound_view(notfound) The :term:`Not Found View` callable is a view callable like any other. If your application instead uses :class:`pyramid.view.view_config` decorators and a :term:`scan`, you can replace the Not Found View by using the :class:`pyramid.view.notfound_view_config` decorator: .. code-block:: python :linenos: from pyramid.view import notfound_view_config @notfound_view_config() def notfound(request): return Response('Not Found, dude', status='404 Not Found') def main(globals, **settings): config = Configurator() config.scan() This does exactly what the imperative example above showed. Your application can define *multiple* Not Found Views if necessary. Both :meth:`pyramid.config.Configurator.add_notfound_view` and :class:`pyramid.view.notfound_view_config` take most of the same arguments as :class:`pyramid.config.Configurator.add_view` and :class:`pyramid.view.view_config`, respectively. This means that Not Found Views can carry predicates limiting their applicability. For example: .. code-block:: python :linenos: from pyramid.view import notfound_view_config @notfound_view_config(request_method='GET') def notfound_get(request): return Response('Not Found during GET, dude', status='404 Not Found') @notfound_view_config(request_method='POST') def notfound_post(request): return Response('Not Found during POST, dude', status='404 Not Found') def main(globals, **settings): config = Configurator() config.scan() The ``notfound_get`` view will be called when a view could not be found and the request method was ``GET``. The ``notfound_post`` view will be called when a view could not be found and the request method was ``POST``. Like any other view, the Not Found View must accept at least a ``request`` parameter, or both ``context`` and ``request``. The ``request`` is the current :term:`request` representing the denied action. The ``context`` (if used in the call signature) will be the instance of the :exc:`~pyramid.httpexceptions.HTTPNotFound` exception that caused the view to be called. Both :meth:`pyramid.config.Configurator.add_notfound_view` and :class:`pyramid.view.notfound_view_config` can be used to automatically redirect requests to slash-appended routes. See :ref:`redirecting_to_slash_appended_routes` for examples. Here's some sample code that implements a minimal :term:`Not Found View` callable: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPNotFound def notfound(request): return HTTPNotFound() .. note:: When a Not Found View callable is invoked, it is passed a :term:`request`. The ``exception`` attribute of the request will be an instance of the :exc:`~pyramid.httpexceptions.HTTPNotFound` exception that caused the Not Found View to be called. The value of ``request.exception.message`` will be a value explaining why the Not Found exception was raised. This message has different values depending on whether the ``pyramid.debug_notfound`` environment setting is true or false. .. note:: Both :meth:`pyramid.config.Configurator.add_notfound_view` and :class:`pyramid.view.notfound_view_config` are new as of Pyramid 1.3. Older Pyramid documentation instructed users to use ``add_view`` instead, with a ``context`` of ``HTTPNotFound``. This still works; the convenience method and decorator are just wrappers around this functionality. .. warning:: When a Not Found View callable accepts an argument list as described in :ref:`request_and_context_view_definitions`, the ``context`` passed as the first argument to the view callable will be the :exc:`~pyramid.httpexceptions.HTTPNotFound` exception instance. If available, the resource context will still be available as ``request.context``. .. index:: single: forbidden view .. _changing_the_forbidden_view: Changing the Forbidden View --------------------------- When :app:`Pyramid` can't authorize execution of a view based on the :term:`authorization policy` in use, it invokes a :term:`forbidden view`. The default forbidden response has a 403 status code and is very plain, but the view which generates it can be overridden as necessary. The :term:`forbidden view` callable is a view callable like any other. The :term:`view configuration` which causes it to be a "forbidden" view consists of using the :meth:`pyramid.config.Configurator.add_forbidden_view` API or the :class:`pyramid.view.forbidden_view_config` decorator. For example, you can add a forbidden view by using the :meth:`pyramid.config.Configurator.add_forbidden_view` method to register a forbidden view: .. code-block:: python :linenos: def forbidden(request): return Response('forbidden') def main(globals, **settings): config = Configurator() config.add_forbidden_view(forbidden_view) If instead you prefer to use decorators and a :term:`scan`, you can use the :class:`pyramid.view.forbidden_view_config` decorator to mark a view callable as a forbidden view: .. code-block:: python :linenos: from pyramid.view import forbidden_view_config @forbidden_view_config() def forbidden(request): return Response('forbidden') def main(globals, **settings): config = Configurator() config.scan() Like any other view, the forbidden view must accept at least a ``request`` parameter, or both ``context`` and ``request``. If a forbidden view callable accepts both ``context`` and ``request``, the HTTP Exception is passed as context. The ``context`` as found by the router when the view was denied (which you normally would expect) is available as ``request.context``. The ``request`` is the current :term:`request` representing the denied action. Here's some sample code that implements a minimal forbidden view: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response def forbidden_view(request): return Response('forbidden') .. note:: When a forbidden view callable is invoked, it is passed a :term:`request`. The ``exception`` attribute of the request will be an instance of the :exc:`~pyramid.httpexceptions.HTTPForbidden` exception that caused the forbidden view to be called. The value of ``request.exception.message`` will be a value explaining why the forbidden exception was raised, and ``request.exception.result`` will be extended information about the forbidden exception. These messages have different values depending on whether the ``pyramid.debug_authorization`` environment setting is true or false. .. index:: single: request factory .. _changing_the_request_factory: Changing the Request Factory ---------------------------- Whenever :app:`Pyramid` handles a request from a :term:`WSGI` server, it creates a :term:`request` object based on the WSGI environment it has been passed. By default, an instance of the :class:`pyramid.request.Request` class is created to represent the request object. The class (a.k.a., "factory") that :app:`Pyramid` uses to create a request object instance can be changed by passing a ``request_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.request import Request class MyRequest(Request): pass config = Configurator(request_factory=MyRequest) 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_request_factory` method: .. code-block:: python :linenos: from pyramid.config import Configurator from pyramid.request import Request class MyRequest(Request): pass config = Configurator() config.set_request_factory(MyRequest) .. index:: single: request method .. _adding_request_method: Adding Methods or Properties to a Request Object ------------------------------------------------ .. versionadded:: 1.4. Since each Pyramid application can only have one :term:`request` factory, :ref:`changing the request factory ` is not that extensible, especially if you want to build composable features (e.g., Pyramid add-ons and plugins). A lazy property can be registered to the request object via the :meth:`pyramid.config.Configurator.add_request_method` API. This allows you to specify a callable that will be available on the request object, but will not actually execute the function until accessed. .. warning:: This will silently override methods and properties from :term:`request factory` that have the same name. .. code-block:: python :linenos: from pyramid.config import Configurator def total(request, *args): return sum(args) def prop(request): print("getting the property") return "the property" config = Configurator() config.add_request_method(total) config.add_request_method(prop, reify=True) In the above example, ``total`` is added as a method. However, ``prop`` is added as a property and its result is cached per-request by setting ``reify=True``. This way, we eliminate the overhead of running the function multiple times. >>> request.total(1, 2, 3) 6 >>> request.prop getting the property the property >>> request.prop the property To not cache the result of ``request.prop``, set ``property=True`` instead of ``reify=True``. Here is an example of passing a class to ``Configurator.add_request_method``: .. code-block:: python :linenos: from pyramid.config import Configurator from pyramid.decorator import reify class ExtraStuff(object): def __init__(self, request): self.request = request def total(self, *args): return sum(args) # use @property if you don't want to cache the result @reify def prop(self): print("getting the property") return "the property" config = Configurator() config.add_request_method(ExtraStuff, 'extra', reify=True) We attach and cache an object named ``extra`` to the ``request`` object. >>> request.extra.total(1, 2, 3) 6 >>> request.extra.prop getting the property the property >>> request.extra.prop the property .. index:: single: response factory .. _changing_the_response_factory: Changing the Response Factory ----------------------------- .. versionadded:: 1.6 Whenever :app:`Pyramid` returns a response from a view, it creates a :term:`response` object. By default, an instance of the :class:`pyramid.response.Response` class is created to represent the response object. The factory that :app:`Pyramid` uses to create a response object instance can be changed by passing a :class:`pyramid.interfaces.IResponseFactory` argument to the constructor of the :term:`configurator`. This argument can be either a callable or a :term:`dotted Python name` representing a callable. The factory takes a single positional argument, which is a :term:`Request` object. The argument may be ``None``. .. code-block:: python :linenos: from pyramid.response import Response class MyResponse(Response): pass config = Configurator(response_factory=lambda r: MyResponse()) If you're doing imperative configuration and you'd rather do it after you've already constructed a :term:`configurator`, it can also be registered via the :meth:`pyramid.config.Configurator.set_response_factory` method: .. code-block:: python :linenos: from pyramid.config import Configurator from pyramid.response import Response class MyResponse(Response): pass config = Configurator() config.set_response_factory(lambda r: MyResponse()) .. index:: single: before render event single: adding renderer globals .. _beforerender_event: Using the Before Render Event ----------------------------- Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect and modify the set of :term:`renderer globals` before they are passed to a :term:`renderer`. This event object iself has a dictionary-like interface that can be used for this purpose. For example: .. code-block:: python :linenos: from pyramid.events import subscriber from pyramid.events import BeforeRender @subscriber(BeforeRender) def add_global(event): event['mykey'] = 'foo' An object of this type is sent as an event just before a :term:`renderer` is invoked. If a subscriber attempts to add a key that already exists in the renderer globals dictionary, a :exc:`KeyError` is raised. This limitation is enforced because event subscribers do not possess any relative ordering. The set of keys added to the renderer globals dictionary by all :class:`pyramid.events.BeforeRender` subscribers and renderer globals factories must be unique. The dictionary returned from the view is accessible through the :attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender` event. Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from your view callable, like so: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='some_renderer') def myview(request): return {'mykey': 'somevalue', 'mykey2': 'somevalue2'} :attr:`rendering_val` can be used to access these values from the :class:`~pyramid.events.BeforeRender` object: .. code-block:: python :linenos: from pyramid.events import subscriber from pyramid.events import BeforeRender @subscriber(BeforeRender) def read_return(event): # {'mykey': 'somevalue'} is returned from the view print(event.rendering_val['mykey']) See the API documentation for the :class:`~pyramid.events.BeforeRender` event interface at :class:`pyramid.interfaces.IBeforeRender`. .. index:: single: response callback .. _using_response_callbacks: Using Response Callbacks ------------------------ Unlike many other web frameworks, :app:`Pyramid` does not eagerly create a global response object. Adding a :term:`response callback` allows an application to register an action to be performed against whatever response object is returned by a view, usually in order to mutate the response. The :meth:`pyramid.request.Request.add_response_callback` method is used to register a response callback. A response callback is a callable which accepts two positional parameters: ``request`` and ``response``. For example: .. code-block:: python :linenos: def cache_callback(request, response): """Set the cache_control max_age for the response""" if request.exception is not None: response.cache_control.max_age = 360 request.add_response_callback(cache_callback) No response callback is called if an unhandled exception happens in application code, or if the response object returned by a :term:`view callable` is invalid. Response callbacks *are*, however, invoked when a :term:`exception view` is rendered successfully. In such a case, the :attr:`request.exception` attribute of the request when it enters a response callback will be an exception object instead of its default value of ``None``. Response callbacks are called in the order they're added (first-to-most-recently-added). All response callbacks are called *before* the :class:`~pyramid.events.NewResponse` event is sent. Errors raised by response callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router application. A response callback has a lifetime of a *single* request. If you want a response callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber of a :class:`~pyramid.events.NewRequest` event). .. index:: single: finished callback .. _using_finished_callbacks: Using Finished Callbacks ------------------------ A :term:`finished callback` is a function that will be called unconditionally by the :app:`Pyramid` :term:`router` at the very end of request processing. A finished callback can be used to perform an action at the end of a request unconditionally. The :meth:`pyramid.request.Request.add_finished_callback` method is used to register a finished callback. A finished callback is a callable which accepts a single positional parameter: ``request``. For example: .. code-block:: python :linenos: import logging log = logging.getLogger(__name__) def log_callback(request): """Log information at the end of request""" log.debug('Request is finished.') request.add_finished_callback(log_callback) Finished callbacks are called in the order they're added (first-to-most-recently-added). Finished callbacks (unlike a :term:`response callback`) are *always* called, even if an exception happens in application code that prevents a response from being generated. The set of finished callbacks associated with a request are called *very late* in the processing of that request; they are essentially the very last thing called by the :term:`router` before a request "ends". They are called after response processing has already occurred in a top-level ``finally:`` block within the router request processing code. As a result, mutations performed to the ``request`` provided to a finished callback will have no meaningful effect, because response processing will have already occurred, and the request's scope will expire almost immediately after all finished callbacks have been processed. Errors raised by finished callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router application. A finished callback has a lifetime of a *single* request. If you want a finished callback to happen as the result of *every* request, you must re-register the callback into every new request (perhaps within a subscriber of a :class:`~pyramid.events.NewRequest` event). .. index:: single: traverser .. _changing_the_traverser: Changing the Traverser ---------------------- The default :term:`traversal` algorithm that :app:`Pyramid` uses is explained in :ref:`traversal_algorithm`. Though it is rarely necessary, this default algorithm can be swapped out selectively for a different traversal pattern via configuration. .. code-block:: python :linenos: from pyramid.config import Configurator from myapp.traversal import Traverser config = Configurator() config.add_traverser(Traverser) In the example above, ``myapp.traversal.Traverser`` is assumed to be a class that implements the following interface: .. code-block:: python :linenos: class Traverser(object): def __init__(self, root): """ Accept the root object returned from the root factory """ def __call__(self, request): """ Return a dictionary with (at least) the keys ``root``, ``context``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. These values are typically the result of a resource tree traversal. ``root`` is the physical root object, ``context`` will be a resource object, ``view_name`` will be the view name used (a Unicode name), ``subpath`` will be a sequence of Unicode names that followed the view name but were not traversed, ``traversed`` will be a sequence of Unicode names that were traversed (including the virtual root path, if any) ``virtual_root`` will be a resource object representing the virtual root (or the physical root if traversal was not performed), and ``virtual_root_path`` will be a sequence representing the virtual root path (a sequence of Unicode names) or None if traversal was not performed. Extra keys for special purpose functionality can be added as necessary. All values returned in the dictionary will be made available as attributes of the ``request`` object. """ More than one traversal algorithm can be active at the same time. For instance, if your :term:`root factory` returns more than one type of object conditionally, you could claim that an alternative traverser adapter is "for" only one particular class or interface. When the root factory returned an object that implemented that class or interface, a custom traverser would be used. Otherwise the default traverser would be used. For example: .. code-block:: python :linenos: from myapp.traversal import Traverser from myapp.resources import MyRoot from pyramid.config import Configurator config = Configurator() config.add_traverser(Traverser, MyRoot) If the above stanza was added to a Pyramid ``__init__.py`` file's ``main`` function, :app:`Pyramid` would use the ``myapp.traversal.Traverser`` only when the application :term:`root factory` returned an instance of the ``myapp.resources.MyRoot`` object. Otherwise it would use the default :app:`Pyramid` traverser to do traversal. .. index:: single: URL generator .. _changing_resource_url: Changing How :meth:`pyramid.request.Request.resource_url` Generates a URL ------------------------------------------------------------------------- When you add a traverser as described in :ref:`changing_the_traverser`, it's often convenient to continue to use the :meth:`pyramid.request.Request.resource_url` API. However, since the way traversal is done will have been modified, the URLs it generates by default may be incorrect when used against resources derived from your custom traverser. If you've added a traverser, you can change how :meth:`~pyramid.request.Request.resource_url` generates a URL for a specific type of resource by adding a call to :meth:`pyramid.config.Configurator.add_resource_url_adapter`. For example: .. code-block:: python :linenos: from myapp.traversal import ResourceURLAdapter from myapp.resources import MyRoot config.add_resource_url_adapter(ResourceURLAdapter, MyRoot) In the above example, the ``myapp.traversal.ResourceURLAdapter`` class will be used to provide services to :meth:`~pyramid.request.Request.resource_url` any time the :term:`resource` passed to ``resource_url`` is of the class ``myapp.resources.MyRoot``. The ``resource_iface`` argument ``MyRoot`` represents the type of interface that must be possessed by the resource for this resource url factory to be found. If the ``resource_iface`` argument is omitted, this resource URL adapter will be used for *all* resources. The API that must be implemented by a class that provides :class:`~pyramid.interfaces.IResourceURL` is as follows: .. code-block:: python :linenos: class MyResourceURL(object): """ An adapter which provides the virtual and physical paths of a resource """ def __init__(self, resource, request): """ Accept the resource and request and set self.physical_path and self.virtual_path """ self.virtual_path = some_function_of(resource, request) self.physical_path = some_other_function_of(resource, request) The default context URL generator is available for perusal as the class :class:`pyramid.traversal.ResourceURL` in the `traversal module `_ of the :term:`Pylons` GitHub Pyramid repository. See :meth:`pyramid.config.Configurator.add_resource_url_adapter` for more information. .. index:: single: IResponse single: special view responses .. _using_iresponse: Changing How Pyramid Treats View Responses ------------------------------------------ .. versionadded:: 1.1 It is possible to control how Pyramid treats the result of calling a view callable on a per-type basis by using a hook involving :meth:`pyramid.config.Configurator.add_response_adapter` or the :class:`~pyramid.response.response_adapter` decorator. Pyramid, in various places, adapts the result of calling a view callable to the :class:`~pyramid.interfaces.IResponse` interface to ensure that the object returned by the view callable is a "true" response object. The vast majority of time, the result of this adaptation is the result object itself, as view callables written by "civilians" who read the narrative documentation contained in this manual will always return something that implements the :class:`~pyramid.interfaces.IResponse` interface. Most typically, this will be an instance of the :class:`pyramid.response.Response` class or a subclass. If a civilian returns a non-Response object from a view callable that isn't configured to use a :term:`renderer`, they will typically expect the router to raise an error. However, you can hook Pyramid in such a way that users can return arbitrary values from a view callable by providing an adapter which converts the arbitrary return value into something that implements :class:`~pyramid.interfaces.IResponse`. For example, if you'd like to allow view callables to return bare string objects (without requiring a :term:`renderer` to convert a string to a response object), you can register an adapter which converts the string to a Response: .. code-block:: python :linenos: from pyramid.response import Response def string_response_adapter(s): response = Response(s) return response # config is an instance of pyramid.config.Configurator config.add_response_adapter(string_response_adapter, str) Likewise, if you want to be able to return a simplified kind of response object from view callables, you can use the IResponse hook to register an adapter to the more complex IResponse interface: .. code-block:: python :linenos: from pyramid.response import Response class SimpleResponse(object): def __init__(self, body): self.body = body def simple_response_adapter(simple_response): response = Response(simple_response.body) return response # config is an instance of pyramid.config.Configurator config.add_response_adapter(simple_response_adapter, SimpleResponse) If you want to implement your own Response object instead of using the :class:`pyramid.response.Response` object in any capacity at all, you'll have to make sure that the object implements every attribute and method outlined in :class:`pyramid.interfaces.IResponse` and you'll have to ensure that it uses ``zope.interface.implementer(IResponse)`` as a class decorator. .. code-block:: python :linenos: from pyramid.interfaces import IResponse from zope.interface import implementer @implementer(IResponse) class MyResponse(object): # ... an implementation of every method and attribute # documented in IResponse should follow ... When an alternate response object implementation is returned by a view callable, if that object asserts that it implements :class:`~pyramid.interfaces.IResponse` (via ``zope.interface.implementer(IResponse)``) , an adapter needn't be registered for the object; Pyramid will use it directly. An IResponse adapter for ``webob.Response`` (as opposed to :class:`pyramid.response.Response`) is registered by Pyramid by default at startup time, as by their nature, instances of this class (and instances of subclasses of the class) will natively provide IResponse. The adapter registered for ``webob.Response`` simply returns the response object. Instead of using :meth:`pyramid.config.Configurator.add_response_adapter`, you can use the :class:`pyramid.response.response_adapter` decorator: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.response import response_adapter @response_adapter(str) def string_response_adapter(s): response = Response(s) return response The above example, when scanned, has the same effect as: .. code-block:: python config.add_response_adapter(string_response_adapter, str) The :class:`~pyramid.response.response_adapter` decorator will have no effect until activated by a :term:`scan`. .. index:: single: view mapper .. _using_a_view_mapper: Using a View Mapper ------------------- The default calling conventions for view callables are documented in the :ref:`views_chapter` chapter. You can change the way users define view callables by employing a :term:`view mapper`. A view mapper is an object that accepts a set of keyword arguments and which returns a callable. The returned callable is called with the :term:`view callable` object. The returned callable should itself return another callable which can be called with the "internal calling protocol" ``(context, request)``. You can use a view mapper in a number of ways: - by setting a ``__view_mapper__`` attribute (which is the view mapper object) on the view callable itself - by passing the mapper object to :meth:`pyramid.config.Configurator.add_view` (or its declarative and decorator equivalents) as the ``mapper`` argument - by registering a *default* view mapper Here's an example of a view mapper that emulates (somewhat) a Pylons "controller". The mapper is initialized with some keyword arguments. Its ``__call__`` method accepts the view object (which will be a class). It uses the ``attr`` keyword argument it is passed to determine which attribute should be used as an action method. The wrapper method it returns accepts ``(context, request)`` and returns the result of calling the action method with keyword arguments implied by the :term:`matchdict` after popping the ``action`` out of it. This somewhat emulates the Pylons style of calling action methods with routing parameters pulled out of the route matching dict as keyword arguments. .. code-block:: python :linenos: # framework class PylonsControllerViewMapper(object): def __init__(self, **kw): self.kw = kw def __call__(self, view): attr = self.kw['attr'] def wrapper(context, request): matchdict = request.matchdict.copy() matchdict.pop('action', None) inst = view(request) meth = getattr(inst, attr) return meth(**matchdict) return wrapper class BaseController(object): __view_mapper__ = PylonsControllerViewMapper A user might make use of these framework components like so: .. code-block:: python :linenos: # user application from pyramid.response import Response from pyramid.config import Configurator import pyramid_handlers from wsgiref.simple_server import make_server class MyController(BaseController): def index(self, id): return Response(id) if __name__ == '__main__': config = Configurator() config.include(pyramid_handlers) config.add_handler('one', '/{id}', MyController, action='index') config.add_handler('two', '/{action}/{id}', MyController) server.make_server('0.0.0.0', 8080, config.make_wsgi_app()) server.serve_forever() The :meth:`pyramid.config.Configurator.set_view_mapper` method can be used to set a *default* view mapper (overriding the superdefault view mapper used by Pyramid itself). A *single* view registration can use a view mapper by passing the mapper as the ``mapper`` argument to :meth:`~pyramid.config.Configurator.add_view`. .. index:: single: configuration decorator .. _registering_configuration_decorators: Registering Configuration Decorators ------------------------------------ Decorators such as :class:`~pyramid.view.view_config` don't change the behavior of the functions or classes they're decorating. Instead when a :term:`scan` is performed, a modified version of the function or class is registered with :app:`Pyramid`. You may wish to have your own decorators that offer such behaviour. This is possible by using the :term:`Venusian` package in the same way that it is used by :app:`Pyramid`. By way of example, let's suppose you want to write a decorator that registers the function it wraps with a :term:`Zope Component Architecture` "utility" within the :term:`application registry` provided by :app:`Pyramid`. The application registry and the utility inside the registry is likely only to be available once your application's configuration is at least partially completed. A normal decorator would fail as it would be executed before the configuration had even begun. However, using :term:`Venusian`, the decorator could be written as follows: .. code-block:: python :linenos: import venusian from mypackage.interfaces import IMyUtility class registerFunction(object): def __init__(self, path): self.path = path def register(self, scanner, name, wrapped): registry = scanner.config.registry registry.getUtility(IMyUtility).register( self.path, wrapped) def __call__(self, wrapped): venusian.attach(wrapped, self.register) return wrapped This decorator could then be used to register functions throughout your code: .. code-block:: python :linenos: @registerFunction('/some/path') def my_function(): do_stuff() However, the utility would only be looked up when a :term:`scan` was performed, enabling you to set up the utility in advance: .. code-block:: python :linenos: from zope.interface import implementer from wsgiref.simple_server import make_server from pyramid.config import Configurator from mypackage.interfaces import IMyUtility @implementer(IMyUtility) class UtilityImplementation: def __init__(self): self.registrations = {} def register(self, path, callable_): self.registrations[path] = callable_ if __name__ == '__main__': config = Configurator() config.registry.registerUtility(UtilityImplementation()) config.scan() app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() For full details, please read the `Venusian documentation `_. .. _registering_tweens: Registering Tweens ------------------ .. versionadded:: 1.2 Tweens A :term:`tween` (a contraction of the word "between") is a bit of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses :app:`Pyramid` as its "app". This is a feature that may be used by Pyramid framework extensions to provide, for example, Pyramid-specific view timing support bookkeeping code that examines exceptions before they are returned to the upstream WSGI application. Tweens behave a bit like :term:`WSGI` :term:`middleware`, but they have the benefit of running in a context in which they have access to the Pyramid :term:`request`, :term:`response`, and :term:`application registry`, as well as the Pyramid rendering machinery. 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 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. A tween is called with a single argument, ``request``, which is the :term:`request` created by Pyramid's router when it receives a WSGI request. A tween should return a :term:`response`, usually the one generated by the downstream Pyramid application. You can write the tween factory as a simple closure-returning function: .. code-block:: python :linenos: def simple_tween_factory(handler, registry): # one-time configuration code goes here def simple_tween(request): # code to be executed for each request before # the actual application code goes here response = handler(request) # code to be executed for each request after # the actual application code goes here return response return simple_tween Alternatively, the tween factory can be a class with the ``__call__`` magic method: .. code-block:: python :linenos: class simple_tween_factory(object): def __init__(self, handler, registry): self.handler = handler self.registry = registry # one-time configuration code goes here def __call__(self, request): # code to be executed for each request before # the actual application code goes here response = self.handler(request) # code to be executed for each request after # the actual application code goes here 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 state and allows subclassing. Here's a complete example of a tween that logs the time spent processing each request: .. code-block:: python :linenos: # in a module named myapp.tweens import time from pyramid.settings import asbool import logging log = logging.getLogger(__name__) def timing_tween_factory(handler, registry): if asbool(registry.settings.get('do_timing')): # if timing support is enabled, return a wrapper def timing_tween(request): start = time.time() try: response = handler(request) finally: end = time.time() log.debug('The request took %s seconds' % (end - start)) return response return timing_tween # if timing support is not enabled, return the original # handler return handler In the above example, the tween factory defines a ``timing_tween`` tween and returns it if ``asbool(registry.settings.get('do_timing'))`` is true. It otherwise simply returns the handler which it was given. The ``registry.settings`` attribute is a handle to the deployment settings provided by the user (usually in an ``.ini`` file). In this case, if the user has defined a ``do_timing`` setting and that setting is ``True``, the user has said they want to do timing, so the tween factory returns the timing tween; it otherwise just returns the handler it has been provided, preventing any timing. The example timing tween simply records the start time, calls the downstream handler, logs the number of seconds consumed by the downstream handler, and returns the response. Registering an Implicit Tween Factory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you've created a tween factory, you can register it into the implicit tween chain using the :meth:`pyramid.config.Configurator.add_tween` method using its :term:`dotted Python name`. Here's an example of registering a tween factory as an "implicit" tween in a Pyramid application: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator() config.add_tween('myapp.tweens.timing_tween_factory') Note that you must use a :term:`dotted Python name` as the first argument to :meth:`pyramid.config.Configurator.add_tween`; this must point at a tween factory. You cannot pass the tween factory object itself to the method: it must be :term:`dotted Python name` that points to a globally importable object. In the above example, we assume that a ``timing_tween_factory`` tween factory was defined in a module named ``myapp.tweens``, so the tween factory is importable as ``myapp.tweens.timing_tween_factory``. When you use :meth:`pyramid.config.Configurator.add_tween`, you're instructing the system to use your tween factory at startup time unless the user has provided an explicit tween list in their configuration. This is what's meant by an "implicit" tween. A user can always elect to supply an explicit tween list, reordering or disincluding implicitly added tweens. See :ref:`explicit_tween_ordering` for more information about explicit tween ordering. If more than one call to :meth:`pyramid.config.Configurator.add_tween` is made within a single application configuration, the tweens will be chained together at application startup time. The *first* tween factory added via ``add_tween`` will be called with the Pyramid exception view tween factory as its ``handler`` argument, then the tween factory added directly after that one will be called with the result of the first tween factory as its ``handler`` argument, and so on, ad infinitum until all tween factories have been called. The Pyramid router will use the outermost tween produced by this chain (the tween generated by the very last tween factory added) as its request handler function. For example: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator() config.add_tween('myapp.tween_factory1') config.add_tween('myapp.tween_factory2') The above example will generate an implicit tween chain that looks like this:: INGRESS (implicit) myapp.tween_factory2 myapp.tween_factory1 pyramid.tweens.excview_tween_factory (implicit) MAIN (implicit) Suggesting Implicit Tween Ordering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, as described above, the ordering of the chain is controlled entirely by the relative ordering of calls to :meth:`pyramid.config.Configurator.add_tween`. However, the caller of ``add_tween`` can provide an optional hint that can influence the implicit tween chain ordering by supplying ``under`` or ``over`` (or both) arguments to :meth:`~pyramid.config.Configurator.add_tween`. These hints are only used when an explicit tween ordering is not used. See :ref:`explicit_tween_ordering` for a description of how to set an explicit tween ordering. Allowable values for ``under`` or ``over`` (or both) are: - ``None`` (the default), - a :term:`dotted Python name` to a tween factory: a string representing the predicted dotted name of a tween factory added in a call to ``add_tween`` in the same configuration session, - one of the constants :attr:`pyramid.tweens.MAIN`, :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`, or - an iterable of any combination of the above. This allows the user to specify fallbacks if the desired tween is not included, as well as compatibility with multiple other tweens. Effectively, ``over`` means "closer to the request ingress than" and ``under`` means "closer to the main Pyramid application than". You can think of an onion with outer layers over the inner layers, the application being under all the layers at the center. For example, the following call to :meth:`~pyramid.config.Configurator.add_tween` will attempt to place the tween factory represented by ``myapp.tween_factory`` directly "above" (in ``ptweens`` order) the main Pyramid request handler. .. code-block:: python :linenos: import pyramid.tweens config.add_tween('myapp.tween_factory', over=pyramid.tweens.MAIN) The above example will generate an implicit tween chain that looks like this:: INGRESS (implicit) pyramid.tweens.excview_tween_factory (implicit) myapp.tween_factory MAIN (implicit) Likewise, calling the following call to :meth:`~pyramid.config.Configurator.add_tween` will attempt to place this tween factory "above" the main handler but "below" a separately added tween factory: .. code-block:: python :linenos: import pyramid.tweens config.add_tween('myapp.tween_factory1', over=pyramid.tweens.MAIN) config.add_tween('myapp.tween_factory2', over=pyramid.tweens.MAIN, under='myapp.tween_factory1') The above example will generate an implicit tween chain that looks like this:: INGRESS (implicit) pyramid.tweens.excview_tween_factory (implicit) myapp.tween_factory1 myapp.tween_factory2 MAIN (implicit) Specifying neither ``over`` nor ``under`` is equivalent to specifying ``under=INGRESS``. If all options for ``under`` (or ``over``) cannot be found in the current configuration, it is an error. If some options are specified purely for compatibilty with other tweens, just add a fallback of ``MAIN`` or ``INGRESS``. For example, ``under=('someothertween', 'someothertween2', INGRESS)``. This constraint will require the tween to be located under the ``someothertween`` tween, the ``someothertween2`` tween, and ``INGRESS``. If any of these is not in the current configuration, this constraint will only organize itself based on the tweens that are present. .. _explicit_tween_ordering: Explicit Tween Ordering ~~~~~~~~~~~~~~~~~~~~~~~ Implicit tween ordering is obviously only best-effort. Pyramid will attempt to provide an implicit order of tweens as best it can using hints provided by calls to :meth:`~pyramid.config.Configurator.add_tween`. But because it's only best-effort, if very precise tween ordering is required, the only surefire way to get it is to use an explicit tween order. The deploying user can override the implicit tween inclusion and ordering implied by calls to :meth:`~pyramid.config.Configurator.add_tween` entirely by using the ``pyramid.tweens`` settings value. When used, this settings value must be a list of Python dotted names which will override the ordering (and inclusion) of tween factories in the implicit tween chain. For example: .. code-block:: ini :linenos: [app:main] use = egg:MyApp pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.tweens = myapp.my_cool_tween_factory pyramid.tweens.excview_tween_factory In the above configuration, calls made during configuration to :meth:`pyramid.config.Configurator.add_tween` are ignored, and the user is telling the system to use the tween factories he has listed in the ``pyramid.tweens`` configuration setting (each is a :term:`dotted Python name` which points to a tween factory) instead of any tween factories added via :meth:`pyramid.config.Configurator.add_tween`. The *first* tween factory in the ``pyramid.tweens`` list will be used as the producer of the effective :app:`Pyramid` request handling function; it will wrap the tween factory declared directly "below" it, ad infinitum. The "main" Pyramid request handler is implicit, and always "at the bottom". .. note:: Pyramid's own :term:`exception view` handling logic is implemented as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`. If Pyramid exception view handling is desired, and tween factories are specified via the ``pyramid.tweens`` configuration setting, the :func:`pyramid.tweens.excview_tween_factory` function must be added to the ``pyramid.tweens`` configuration setting list explicitly. If it is not present, Pyramid will not perform exception view handling. Tween Conflicts and Ordering Cycles ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid will prevent the same tween factory from being added to the tween chain more than once using configuration conflict detection. If you wish to add the same tween factory more than once in a configuration, you should either: (a) use a tween factory that is a separate globally importable instance object from the factory that it conflicts with; (b) use a function or class as a tween factory with the same logic as the other tween factory it conflicts with, but with a different ``__name__`` attribute; or (c) call :meth:`pyramid.config.Configurator.commit` between calls to :meth:`pyramid.config.Configurator.add_tween`. If a cycle is detected in implicit tween ordering when ``over`` and ``under`` are used in any call to ``add_tween``, an exception will be raised at startup time. Displaying Tween Ordering ~~~~~~~~~~~~~~~~~~~~~~~~~ The ``ptweens`` command-line utility can be used to report the current implict and explicit tween chains used by an application. See :ref:`displaying_tweens`. .. _registering_thirdparty_predicates: Adding a Third Party View, Route, or Subscriber Predicate --------------------------------------------------------- .. versionadded:: 1.4 .. _view_and_route_predicates: View and Route Predicates ~~~~~~~~~~~~~~~~~~~~~~~~~ View and route predicates used during configuration allow you to narrow the set of circumstances under which a view or route will match. For example, the ``request_method`` view predicate can be used to ensure a view callable is only invoked when the request's method is ``POST``: .. code-block:: python @view_config(request_method='POST') def someview(request): ... Likewise, a similar predicate can be used as a *route* predicate: .. code-block:: python config.add_route('name', '/foo', request_method='POST') Many other built-in predicates exists (``request_param``, and others). You can add third-party predicates to the list of available predicates by using one of :meth:`pyramid.config.Configurator.add_view_predicate` or :meth:`pyramid.config.Configurator.add_route_predicate`. The former adds a view predicate, the latter a route predicate. When using one of those APIs, you pass a *name* and a *factory* to add a predicate during Pyramid's configuration stage. For example: .. code-block:: python config.add_view_predicate('content_type', ContentTypePredicate) The above example adds a new predicate named ``content_type`` to the list of available predicates for views. This will allow the following view configuration statement to work: .. code-block:: python :linenos: @view_config(content_type='File') def aview(request): ... The first argument to :meth:`pyramid.config.Configurator.add_view_predicate`, the name, is a string representing the name that is expected to be passed to ``view_config`` (or its imperative analogue ``add_view``). The second argument is a view or route predicate factory, or a :term:`dotted Python name` which refers to a view or route predicate factory. A view or route predicate factory is most often a class with a constructor (``__init__``), a ``text`` method, a ``phash`` method, and a ``__call__`` method. For example: .. code-block:: python :linenos: class ContentTypePredicate(object): def __init__(self, val, config): self.val = val def text(self): return 'content_type = %s' % (self.val,) phash = text def __call__(self, context, request): return getattr(context, 'content_type', None) == self.val The constructor of a predicate factory takes two arguments: ``val`` and ``config``. The ``val`` argument will be the argument passed to ``view_config`` (or ``add_view``). In the example above, it will be the string ``File``. The second argument, ``config``, will be the Configurator instance at the time of configuration. The ``text`` method must return a string. It should be useful to describe the behavior of the predicate in error messages. The ``phash`` method must return a string or a sequence of strings. It's most often the same as ``text``, as long as ``text`` uniquely describes the predicate's name and the value passed to the constructor. If ``text`` is more general, or doesn't describe things that way, ``phash`` should return a string with the name and the value serialized. The result of ``phash`` is not seen in output anywhere, it just informs the uniqueness constraints for view configuration. The ``__call__`` method of a predicate factory must accept a resource (``context``) and a request, and must return ``True`` or ``False``. It is the "meat" of the predicate. You can use the same predicate factory as both a view predicate and as a route predicate, but you'll need to call ``add_view_predicate`` and ``add_route_predicate`` separately with the same factory. .. _subscriber_predicates: Subscriber Predicates ~~~~~~~~~~~~~~~~~~~~~ Subscriber predicates work almost exactly like view and route predicates. They narrow the set of circumstances in which a subscriber will be called. There are several minor differences between a subscriber predicate and a view or route predicate: - There are no default subscriber predicates. You must register one to use one. - The ``__call__`` method of a subscriber predicate accepts a single ``event`` object instead of a ``context`` and a ``request``. - Not every subscriber predicate can be used with every event type. Some subscriber predicates will assume a certain event type. Here's an example of a subscriber predicate that can be used in conjunction with a subscriber that subscribes to the :class:`pyramid.events.NewRequest` event type. .. code-block:: python :linenos: class RequestPathStartsWith(object): def __init__(self, val, config): self.val = val def text(self): return 'path_startswith = %s' % (self.val,) phash = text def __call__(self, event): return event.request.path.startswith(self.val) Once you've created a subscriber predicate, it may registered via :meth:`pyramid.config.Configurator.add_subscriber_predicate`. For example: .. code-block:: python config.add_subscriber_predicate( 'request_path_startswith', RequestPathStartsWith) Once a subscriber predicate is registered, you can use it in a call to :meth:`pyramid.config.Configurator.add_subscriber` or to :class:`pyramid.events.subscriber`. Here's an example of using the previously registered ``request_path_startswith`` predicate in a call to :meth:`~pyramid.config.Configurator.add_subscriber`: .. code-block:: python :linenos: # define a subscriber in your code def yosubscriber(event): event.request.yo = 'YO!' # and at configuration time config.add_subscriber(yosubscriber, NewRequest, request_path_startswith='/add_yo') Here's the same subscriber/predicate/event-type combination used via :class:`~pyramid.events.subscriber`. .. code-block:: python :linenos: from pyramid.events import subscriber @subscriber(NewRequest, request_path_startswith='/add_yo') def yosubscriber(event): event.request.yo = 'YO!' In either of the above configurations, the ``yosubscriber`` callable will only be called if the request path starts with ``/add_yo``. Otherwise the event subscriber will not be called. Note that the ``request_path_startswith`` subscriber you defined can be used with events that have a ``request`` attribute, but not ones that do not. So, for example, the predicate can be used with subscribers registered for :class:`pyramid.events.NewRequest` and :class:`pyramid.events.ContextFound` events, but it cannot be used with subscribers registered for :class:`pyramid.events.ApplicationCreated` because the latter type of event has no ``request`` attribute. The point being, unlike route and view predicates, not every type of subscriber predicate will necessarily be applicable for use in every subscriber registration. It is not the responsibility of the predicate author to make every predicate make sense for every event type; it is the responsibility of the predicate consumer to use predicates that make sense for a particular event type registration. pyramid-1.6/docs/narr/hybrid.rst0000644000076500000240000005517612621241570017475 0ustar michaelstaff00000000000000.. _hybrid_chapter: Combining Traversal and URL Dispatch ==================================== When you write most :app:`Pyramid` applications, you'll be using one or the other of two available :term:`resource location` subsystems: traversal or URL dispatch. However, to solve a limited set of problems, it's useful to use *both* traversal and URL dispatch together within the same application. :app:`Pyramid` makes this possible via *hybrid* applications. .. warning:: Reasoning about the behavior of a "hybrid" URL dispatch + traversal application can be challenging. To successfully reason about using URL dispatch and traversal together, you need to understand URL pattern matching, root factories, and the :term:`traversal` algorithm, and the potential interactions between them. Therefore, we don't recommend creating an application that relies on hybrid behavior unless you must. A Review of Non-Hybrid Applications ----------------------------------- When used according to the tutorials in its documentation, :app:`Pyramid` is a "dual-mode" framework: the tutorials explain how to create an application in terms of using either :term:`URL dispatch` *or* :term:`traversal`. This chapter details how you might combine these two dispatch mechanisms, but we'll review how they work in isolation before trying to combine them. URL Dispatch Only ~~~~~~~~~~~~~~~~~ An application that uses :term:`URL dispatch` exclusively to map URLs to code will often have statements like this within its application startup configuration: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_route('foobar', '{foo}/{bar}') config.add_route('bazbuz', '{baz}/{buz}') config.add_view('myproject.views.foobar', route_name='foobar') config.add_view('myproject.views.bazbuz', route_name='bazbuz') Each :term:`route` corresponds to one or more view callables. Each view callable is associated with a route by passing a ``route_name`` parameter that matches its name during a call to :meth:`~pyramid.config.Configurator.add_view`. When a route is matched during a request, :term:`view lookup` is used to match the request to its associated view callable. The presence of calls to :meth:`~pyramid.config.Configurator.add_route` signify that an application is using URL dispatch. Traversal Only ~~~~~~~~~~~~~~ An application that uses only traversal will have view configuration declarations that look like this: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_view('mypackage.views.foobar', name='foobar') config.add_view('mypackage.views.bazbuz', name='bazbuz') When the above configuration is applied to an application, the ``mypackage.views.foobar`` view callable above will be called when the URL ``/foobar`` is visited. Likewise, the view ``mypackage.views.bazbuz`` will be called when the URL ``/bazbuz`` is visited. Typically, an application that uses traversal exclusively won't perform any calls to :meth:`pyramid.config.Configurator.add_route` in its startup code. .. index:: single: hybrid applications Hybrid Applications ------------------- Either traversal or URL dispatch alone can be used to create a :app:`Pyramid` application. However, it is also possible to combine the concepts of traversal and URL dispatch when building an application, the result of which is a hybrid application. In a hybrid application, traversal is performed *after* a particular route has matched. A hybrid application is a lot more like a "pure" traversal-based application than it is like a "pure" URL-dispatch based application. But unlike in a "pure" traversal-based application, in a hybrid application :term:`traversal` is performed during a request after a route has already matched. This means that the URL pattern that represents the ``pattern`` argument of a route must match the ``PATH_INFO`` of a request, and after the route pattern has matched, most of the "normal" rules of traversal with respect to :term:`resource location` and :term:`view lookup` apply. There are only four real differences between a purely traversal-based application and a hybrid application: - In a purely traversal-based application, no routes are defined. In a hybrid application, at least one route will be defined. - In a purely traversal-based application, the root object used is global, implied by the :term:`root factory` provided at startup time. In a hybrid application, the :term:`root` object at which traversal begins may be varied on a per-route basis. - In a purely traversal-based application, the ``PATH_INFO`` of the underlying :term:`WSGI` environment is used wholesale as a traversal path. In a hybrid application, the traversal path is not the entire ``PATH_INFO`` string, but a portion of the URL determined by a matching pattern in the matched route configuration's pattern. - In a purely traversal-based application, view configurations which do not mention a ``route_name`` argument are considered during :term:`view lookup`. In a hybrid application, when a route is matched, only view configurations which mention that route's name as a ``route_name`` are considered during :term:`view lookup`. More generally, a hybrid application *is* a traversal-based application except: - the traversal *root* is chosen based on the route configuration of the route that matched, instead of from the ``root_factory`` supplied during application startup configuration. - the traversal *path* is chosen based on the route configuration of the route that matched, rather than from the ``PATH_INFO`` of a request. - the set of views that may be chosen during :term:`view lookup` when a route matches are limited to those which specifically name a ``route_name`` in their configuration that is the same as the matched route's ``name``. To create a hybrid mode application, use a :term:`route configuration` that implies a particular :term:`root factory` and which also includes a ``pattern`` argument that contains a special dynamic part: either ``*traverse`` or ``*subpath``. The Root Object for a Route Match ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A hybrid application implies that traversal is performed during a request after a route has matched. Traversal, by definition, must always begin at a root object. Therefore it's important to know *which* root object will be traversed after a route has matched. Figuring out which :term:`root` object results from a particular route match is straightforward. When a route is matched: - If the route's configuration has a ``factory`` argument which points to a :term:`root factory` callable, that callable will be called to generate a :term:`root` object. - If the route's configuration does not have a ``factory`` argument, the *global* :term:`root factory` will be called to generate a :term:`root` object. The global root factory is the callable implied by the ``root_factory`` argument passed to the :class:`~pyramid.config.Configurator` at application startup time. - If a ``root_factory`` argument is not provided to the :class:`~pyramid.config.Configurator` at startup time, a *default* root factory is used. The default root factory is used to generate a root object. .. note:: Root factories related to a route were explained previously within :ref:`route_factories`. Both the global root factory and default root factory were explained previously within :ref:`the_resource_tree`. .. index:: pair: hybrid applications; *traverse route pattern .. _using_traverse_in_a_route_pattern: Using ``*traverse`` in a Route Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A hybrid application most often implies the inclusion of a route configuration that contains the special token ``*traverse`` at the end of a route's pattern: .. code-block:: python :linenos: config.add_route('home', '{foo}/{bar}/*traverse') A ``*traverse`` token at the end of the pattern in a route's configuration implies a "remainder" *capture* value. When it is used, it will match the remainder of the path segments of the URL. This remainder becomes the path used to perform traversal. .. note:: The ``*remainder`` route pattern syntax is explained in more detail within :ref:`route_pattern_syntax`. A hybrid mode application relies more heavily on :term:`traversal` to do :term:`resource location` and :term:`view lookup` than most examples indicate within :ref:`urldispatch_chapter`. Because the pattern of the above route ends with ``*traverse``, when this route configuration is matched during a request, :app:`Pyramid` will attempt to use :term:`traversal` against the :term:`root` object implied by the :term:`root factory` that is implied by the route's configuration. Since no ``root_factory`` argument is explicitly specified for this route, this will either be the *global* root factory for the application, or the *default* root factory. Once :term:`traversal` has found a :term:`context` resource, :term:`view lookup` will be invoked in almost exactly the same way it would have been invoked in a "pure" traversal-based application. Let's assume there is no *global* :term:`root factory` configured in this application. The *default* :term:`root factory` cannot be traversed; it has no useful ``__getitem__`` method. So we'll need to associate this route configuration with a custom root factory in order to create a useful hybrid application. To that end, let's imagine that we've created a root factory that looks like so in a module named ``routes.py``: .. code-block:: python :linenos: class Resource(object): def __init__(self, subobjects): self.subobjects = subobjects def __getitem__(self, name): return self.subobjects[name] root = Resource( {'a': Resource({'b': Resource({'c': Resource({})})})} ) def root_factory(request): return root Above we've defined a (bogus) resource tree that can be traversed, and a ``root_factory`` function that can be used as part of a particular route configuration statement: .. code-block:: python :linenos: config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') The ``factory`` above points at the function we've defined. It will return an instance of the ``Resource`` class as a root object whenever this route is matched. Instances of the ``Resource`` class can be used for tree traversal because they have a ``__getitem__`` method that does something nominally useful. Since traversal uses ``__getitem__`` to walk the resources of a resource tree, using traversal against the root resource implied by our route statement is a reasonable thing to do. .. note:: We could have also used our ``root_factory`` function as the ``root_factory`` argument of the :class:`~pyramid.config.Configurator` constructor, instead of associating it with a particular route inside the route's configuration. Every hybrid route configuration that is matched, but which does *not* name a ``factory`` attribute, will use the global ``root_factory`` function to generate a root object. When the route configuration named ``home`` above is matched during a request, the matchdict generated will be based on its pattern: ``{foo}/{bar}/*traverse``. The "capture value" implied by the ``*traverse`` element in the pattern will be used to traverse the resource tree in order to find a context resource, starting from the root object returned from the root factory. In the above example, the :term:`root` object found will be the instance named ``root`` in ``routes.py``. If the URL that matched a route with the pattern ``{foo}/{bar}/*traverse`` is ``http://example.com/one/two/a/b/c``, the traversal path used against the root object will be ``a/b/c``. As a result, :app:`Pyramid` will attempt to traverse through the edges ``'a'``, ``'b'``, and ``'c'``, beginning at the root object. In our above example, this particular set of traversal steps will mean that the :term:`context` resource of the view would be the ``Resource`` object we've named ``'c'`` in our bogus resource tree, and the :term:`view name` resulting from traversal will be the empty string. If you need a refresher about why this outcome is presumed, see :ref:`traversal_algorithm`. At this point, a suitable view callable will be found and invoked using :term:`view lookup` as described in :ref:`view_configuration`, but with a caveat: in order for view lookup to work, we need to define a view configuration that will match when :term:`view lookup` is invoked after a route matches: .. code-block:: python :linenos: config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') config.add_view('mypackage.views.myview', route_name='home') Note that the above call to :meth:`~pyramid.config.Configurator.add_view` includes a ``route_name`` argument. View configurations that include a ``route_name`` argument are meant to associate a particular view declaration with a route, using the route's name, in order to indicate that the view should *only be invoked when the route matches*. Calls to :meth:`~pyramid.config.Configurator.add_view` may pass a ``route_name`` attribute, which refers to the value of an existing route's ``name`` argument. In the above example, the route name is ``home``, referring to the name of the route defined above it. The above ``mypackage.views.myview`` view callable will be invoked when the following conditions are met: - The route named "home" is matched. - The :term:`view name` resulting from traversal is the empty string. - The :term:`context` resource is any object. It is also possible to declare alternative views that may be invoked when a hybrid route is matched: .. code-block:: python :linenos: config.add_route('home', '{foo}/{bar}/*traverse', factory='mypackage.routes.root_factory') config.add_view('mypackage.views.myview', route_name='home') config.add_view('mypackage.views.another_view', route_name='home', name='another') The ``add_view`` call for ``mypackage.views.another_view`` above names a different view and, more importantly, a different :term:`view name`. The above ``mypackage.views.another_view`` view will be invoked when the following conditions are met: - The route named "home" is matched. - The :term:`view name` resulting from traversal is ``another``. - The :term:`context` resource is any object. For instance, if the URL ``http://example.com/one/two/a/another`` is provided to an application that uses the previously mentioned resource tree, the ``mypackage.views.another_view`` view callable will be called instead of the ``mypackage.views.myview`` view callable because the :term:`view name` will be ``another`` instead of the empty string. More complicated matching can be composed. All arguments to *route* configuration statements and *view* configuration statements are supported in hybrid applications (such as :term:`predicate` arguments). Using the ``traverse`` Argument in a Route Definition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Rather than using the ``*traverse`` remainder marker in a pattern, you can use the ``traverse`` argument to the :meth:`~pyramid.config.Configurator.add_route` method. When you use the ``*traverse`` remainder marker, the traversal path is limited to being the remainder segments of a request URL when a route matches. However, when you use the ``traverse`` argument or attribute, you have more control over how to compose a traversal path. Here's a use of the ``traverse`` pattern in a call to :meth:`~pyramid.config.Configurator.add_route`: .. code-block:: python :linenos: config.add_route('abc', '/articles/{article}/edit', traverse='/{article}') The syntax of the ``traverse`` argument is the same as it is for ``pattern``. If, as above, the ``pattern`` provided is ``/articles/{article}/edit``, and the ``traverse`` argument provided is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``1`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``1`` during the traversal phase. If the ``1`` object exists, it will become the :term:`context` of the request. The :ref:`traversal_chapter` chapter has more information about traversal. If the traversal path contains segment marker names which are not present in the pattern argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``path``. Note that the ``traverse`` argument is ignored when attached to a route that has a ``*traverse`` remainder marker in its pattern. Traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). .. index:: pair: hybrid applications; global views Making Global Views Match +++++++++++++++++++++++++ By default, only view configurations that mention a ``route_name`` will be found during view lookup when a route that has a ``*traverse`` in its pattern matches. You can allow views without a ``route_name`` attribute to match a route by adding the ``use_global_views`` flag to the route definition. For example, the ``myproject.views.bazbuz`` view below will be found if the route named ``abc`` below is matched and the ``PATH_INFO`` is ``/abc/bazbuz``, even though the view configuration statement does not have the ``route_name="abc"`` attribute. .. code-block:: python :linenos: config.add_route('abc', '/abc/*traverse', use_global_views=True) config.add_view('myproject.views.bazbuz', name='bazbuz') .. index:: pair: hybrid applications; *subpath single: route subpath single: subpath (route) .. _star_subpath: Using ``*subpath`` in a Route Pattern ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are certain extremely rare cases when you'd like to influence the traversal :term:`subpath` when a route matches without actually performing traversal. For instance, the :func:`pyramid.wsgi.wsgiapp2` decorator and the :class:`pyramid.static.static_view` helper attempt to compute ``PATH_INFO`` from the request's subpath when its ``use_subpath`` argument is ``True``, so it's useful to be able to influence this value. When ``*subpath`` exists in a pattern, no path is actually traversed, but the traversal algorithm will return a :term:`subpath` list implied by the capture value of ``*subpath``. You'll see this pattern most commonly in route declarations that look like this: .. code-block:: python :linenos: from pyramid.static import static_view www = static_view('mypackage:static', use_subpath=True) config.add_route('static', '/static/*subpath') config.add_view(www, route_name='static') ``mypackage.views.www`` is an instance of :class:`pyramid.static.static_view`. This effectively tells the static helper to traverse everything in the subpath as a filename. .. index:: pair: hybrid URLs; generating .. _generating_hybrid_urls: Generating Hybrid URLs ---------------------- .. versionadded:: 1.5 The :meth:`pyramid.request.Request.resource_url` method and the :meth:`pyramid.request.Request.resource_path` method both accept optional keyword arguments that make it easier to generate route-prefixed URLs that contain paths to traversal resources: ``route_name``, ``route_kw``, and ``route_remainder_name``. Any route that has a pattern that contains a ``*remainder`` pattern (any stararg remainder pattern, such as ``*traverse``, ``*subpath``, or ``*fred``) can be used as the target name for ``request.resource_url(..., route_name=)`` and ``request.resource_path(..., route_name=)``. For example, let's imagine you have a route defined in your Pyramid application like so: .. code-block:: python config.add_route('mysection', '/mysection*traverse') If you'd like to generate the URL ``http://example.com/mysection/a/``, you can use the following incantation, assuming that the variable ``a`` below points to a resource that is a child of the root with a ``__name__`` of ``a``: .. code-block:: python request.resource_url(a, route_name='mysection') You can generate only the path portion ``/mysection/a/`` assuming the same: .. code-block:: python request.resource_path(a, route_name='mysection') The path is virtual host aware, so if the ``X-Vhm-Root`` environment variable is present in the request, and it's set to ``/a``, the above call to ``request.resource_url`` would generate ``http://example.com/mysection/``, and the above call to ``request.resource_path`` would generate ``/mysection/``. See :ref:`virtual_root_support` for more information. If the route you're trying to use needs simple dynamic part values to be filled in to succesfully generate the URL, you can pass these as the ``route_kw`` argument to ``resource_url`` and ``resource_path``. For example, assuming that the route definition is like so: .. code-block:: python config.add_route('mysection', '/{id}/mysection*traverse') You can pass ``route_kw`` in to fill in ``{id}`` above: .. code-block:: python request.resource_url(a, route_name='mysection', route_kw={'id':'1'}) If you pass ``route_kw`` but do not pass ``route_name``, ``route_kw`` will be ignored. By default this feature works by calling ``route_url`` under the hood, and passing the value of the resource path to that function as ``traverse``. If your route has a different ``*stararg`` remainder name (such as ``*subpath``), you can tell ``resource_url`` or ``resource_path`` to use that instead of ``traverse`` by passing ``route_remainder_name``. For example, if you have the following route: .. code-block:: python config.add_route('mysection', '/mysection*subpath') You can fill in the ``*subpath`` value using ``resource_url`` by doing: .. code-block:: python request.resource_path(a, route_name='mysection', route_remainder_name='subpath') If you pass ``route_remainder_name`` but do not pass ``route_name``, ``route_remainder_name`` will be ignored. If you try to use ``resource_path`` or ``resource_url`` when the ``route_name`` argument points at a route that does not have a remainder stararg, an error will not be raised, but the generated URL will not contain any remainder information either. All other values that are normally passable to ``resource_path`` and ``resource_url`` (such as ``query``, ``anchor``, ``host``, ``port``, and positional elements) work as you might expect in this configuration. Note that this feature is incompatible with the ``__resource_url__`` feature (see :ref:`overriding_resource_url_generation`) implemented on resource objects. Any ``__resource_url__`` supplied by your resource will be ignored when you pass ``route_name``. pyramid-1.6/docs/narr/i18n.rst0000644000076500000240000010264712621241570016767 0ustar michaelstaff00000000000000.. index:: single: i18n single: l10n single: internationalization single: localization .. _i18n_chapter: Internationalization and Localization ===================================== :term:`Internationalization` (i18n) is the act of creating software with a user interface that can potentially be displayed in more than one language or cultural context. :term:`Localization` (l10n) is the process of displaying the user interface of an internationalized application in a *particular* language or cultural context. :app:`Pyramid` offers internationalization and localization subsystems that can be used to translate the text of buttons, error messages, and other software- and template-defined values into the native language of a user of your application. .. index:: single: translation string pair: domain; translation pair: msgid; translation single: message identifier Creating a Translation String ----------------------------- While you write your software, you can insert specialized markup into your Python code that makes it possible for the system to translate text values into the languages used by your application's users. This markup creates a :term:`translation string`. A translation string is an object that behaves mostly like a normal Unicode object, except that it also carries around extra information related to its job as part of the :app:`Pyramid` translation machinery. Using the ``TranslationString`` Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The most primitive way to create a translation string is to use the :class:`pyramid.i18n.TranslationString` callable: .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('Add') This creates a Unicode-like object that is a TranslationString. .. note:: For people more familiar with :term:`Zope` i18n, a TranslationString is a lot like a ``zope.i18nmessageid.Message`` object. It is not a subclass, however. For people more familiar with :term:`Pylons` or :term:`Django` i18n, using a TranslationString is a lot like using "lazy" versions of related gettext APIs. The first argument to :class:`~pyramid.i18n.TranslationString` is the ``msgid``; it is required. It represents the key into the translation mappings provided by a particular localization. The ``msgid`` argument must be a Unicode object or an ASCII string. The msgid may optionally contain *replacement markers*. For instance: .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}') Within the string above, ``${number}`` is a replacement marker. It will be replaced by whatever is in the *mapping* for a translation string. The mapping may be supplied at the same time as the replacement marker itself: .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}', mapping={'number':1}) Any number of replacement markers can be present in the msgid value, any number of times. Only markers which can be replaced by the values in the *mapping* will be replaced at translation time. The others will not be interpolated and will be output literally. A translation string should also usually carry a *domain*. The domain represents a translation category to disambiguate it from other translations of the same msgid, in case they conflict. .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}', mapping={'number':1}, domain='form') The above translation string named a domain of ``form``. A :term:`translator` function will often use the domain to locate the right translator file on the filesystem which contains translations for a given domain. In this case, if it were trying to translate our msgid to German, it might try to find a translation from a :term:`gettext` file within a :term:`translation directory` like this one: .. code-block:: text locale/de/LC_MESSAGES/form.mo In other words, it would want to take translations from the ``form.mo`` translation file in the German language. Finally, the TranslationString constructor accepts a ``default`` argument. If a ``default`` argument is supplied, it replaces usages of the ``msgid`` as the *default value* for the translation string. When ``default`` is ``None``, the ``msgid`` value passed to a TranslationString is used as an implicit message identifier. Message identifiers are matched with translations in translation files, so it is often useful to create translation strings with "opaque" message identifiers unrelated to their default text: .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('add-number', default='Add ${number}', domain='form', mapping={'number':1}) When default text is used, Default text objects may contain replacement values. .. index:: single: translation string factory Using the ``TranslationStringFactory`` Class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Another way to generate a translation string is to use the :attr:`~pyramid.i18n.TranslationStringFactory` object. This object is a *translation string factory*. Basically a translation string factory presets the ``domain`` value of any :term:`translation string` generated by using it. For example: .. code-block:: python :linenos: from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('pyramid') ts = _('add-number', default='Add ${number}', mapping={'number':1}) .. note:: We assigned the translation string factory to the name ``_``. This is a convention which will be supported by translation file generation tools. After assigning ``_`` to the result of a :func:`~pyramid.i18n.TranslationStringFactory`, the subsequent result of calling ``_`` will be a :class:`~pyramid.i18n.TranslationString` instance. Even though a ``domain`` value was not passed to ``_`` (as would have been necessary if the :class:`~pyramid.i18n.TranslationString` constructor were used instead of a translation string factory), the ``domain`` attribute of the resulting translation string will be ``pyramid``. As a result, the previous code example is completely equivalent (except for spelling) to: .. code-block:: python :linenos: from pyramid.i18n import TranslationString as _ ts = _('add-number', default='Add ${number}', mapping={'number':1}, domain='pyramid') You can set up your own translation string factory much like the one provided above by using the :class:`~pyramid.i18n.TranslationStringFactory` class. For example, if you'd like to create a translation string factory which presets the ``domain`` value of generated translation strings to ``form``, you'd do something like this: .. code-block:: python :linenos: from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('form') ts = _('add-number', default='Add ${number}', mapping={'number':1}) Creating a unique domain for your application via a translation string factory is best practice. Using your own unique translation domain allows another person to reuse your application without needing to merge your translation files with their own. Instead they can just include your package's :term:`translation directory` via the :meth:`pyramid.config.Configurator.add_translation_dirs` method. .. note:: For people familiar with Zope internationalization, a TranslationStringFactory is a lot like a ``zope.i18nmessageid.MessageFactory`` object. It is not a subclass, however. .. index:: single: gettext single: translation directories Working with ``gettext`` Translation Files ------------------------------------------ The basis of :app:`Pyramid` translation services is GNU :term:`gettext`. Once your application source code files and templates are marked up with translation markers, you can work on translations by creating various kinds of gettext files. .. note:: The steps a developer must take to work with :term:`gettext` :term:`message catalog` files within a :app:`Pyramid` application are very similar to the steps a :term:`Pylons` developer must take to do the same. See the :ref:`Pylons Internationalization and Localization documentation ` for more information. GNU gettext uses three types of files in the translation framework, ``.pot`` files, ``.po`` files, and ``.mo`` files. ``.pot`` (Portable Object Template) files A ``.pot`` file is created by a program which searches through your project's source code and which picks out every :term:`message identifier` passed to one of the ``_()`` functions (e.g., :term:`translation string` constructions). The list of all message identifiers is placed into a ``.pot`` file, which serves as a template for creating ``.po`` files. ``.po`` (Portable Object) files The list of messages in a ``.pot`` file are translated by a human to a particular language; the result is saved as a ``.po`` file. ``.mo`` (Machine Object) files A ``.po`` file is turned into a machine-readable binary file, which is the ``.mo`` file. Compiling the translations to machine code makes the localized program start faster. The tools for working with :term:`gettext` translation files related to a :app:`Pyramid` application are :term:`Lingua` and :term:`Gettext`. Lingua can scrape i18n references out of Python and Chameleon files and create the ``.pot`` file. Gettext includes ``msgmerge`` tool to update a ``.po`` file from an updated ``.pot`` file and ``msgfmt`` to compile ``.po`` files to ``.mo`` files. .. index:: single: Gettext single: Lingua .. _installing_babel: Installing Lingua and Gettext ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order for the commands related to working with ``gettext`` translation files to work properly, you will need to have :term:`Lingua` and :term:`Gettext` installed into the same environment in which :app:`Pyramid` is installed. Installation on UNIX ++++++++++++++++++++ Gettext is often already installed on UNIX systems. You can check if it is installed by testing if the ``msgfmt`` command is available. If it is not available you can install it through the packaging system from your OS; the package name is almost always ``gettext``. For example on a Debian or Ubuntu system run this command: .. code-block:: text $ sudo apt-get install gettext Installing Lingua is done with the Python packaging tools. If the :term:`virtualenv` into which you've installed your :app:`Pyramid` application lives in ``/my/virtualenv``, you can install Lingua like so: .. code-block:: text $ cd /my/virtualenv $ $VENV/bin/easy_install lingua Installation on Windows +++++++++++++++++++++++ There are several ways to install Gettext on Windows: it is included in the `Cygwin `_ collection, or you can use the `installer from the GnuWin32 `_, or compile it yourself. Make sure the installation path is added to your ``$PATH``. Installing Lingua is done with the Python packaging tools. If the :term:`virtualenv` into which you've installed your :app:`Pyramid` application lives in ``C:\my\virtualenv``, you can install Lingua like so: .. code-block:: text C> %VENV%\Scripts\easy_install lingua .. index:: pair: extracting; messages .. _extracting_messages: Extracting Messages from Code and Templates ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once Lingua is installed, you may extract a message catalog template from the code and :term:`Chameleon` templates which reside in your :app:`Pyramid` application. You run a ``pot-create`` command to extract the messages: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives $ mkdir -p myapplication/locale $ $VENV/bin/pot-create -o myapplication/locale/myapplication.pot src The message catalog ``.pot`` template will end up in ``myapplication/locale/myapplication.pot``. .. index:: pair: initializing; message catalog Initializing a Message Catalog File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you've extracted messages into a ``.pot`` file (see :ref:`extracting_messages`), to begin localizing the messages present in the ``.pot`` file, you need to generate at least one ``.po`` file. A ``.po`` file represents translations of a particular set of messages to a particular locale. Initialize a ``.po`` file for a specific locale from a pre-generated ``.pot`` template by using the ``msginit`` command from Gettext: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives $ cd myapplication/locale $ mkdir -p es/LC_MESSAGES $ msginit -l es -o es/LC_MESSAGES/myapplication.po This will create a new message catalog ``.po`` file in ``myapplication/locale/es/LC_MESSAGES/myapplication.po``. Once the file is there, it can be worked on by a human translator. One tool which may help with this is `Poedit `_. Note that :app:`Pyramid` itself ignores the existence of all ``.po`` files. For a running application to have translations available, a ``.mo`` file must exist. See :ref:`compiling_message_catalog`. .. index:: pair: updating; message catalog Updating a Catalog File ~~~~~~~~~~~~~~~~~~~~~~~ If more translation strings are added to your application, or translation strings change, you will need to update existing ``.po`` files based on changes to the ``.pot`` file, so that the new and changed messages can also be translated or re-translated. First, regenerate the ``.pot`` file as per :ref:`extracting_messages`. Then use the ``msgmerge`` command from Gettext. .. code-block:: text $ cd /place/where/myapplication/setup.py/lives $ cd myapplication/locale $ msgmerge --update es/LC_MESSAGES/myapplication.po myapplication.pot .. index:: pair: compiling; message catalog .. _compiling_message_catalog: Compiling a Message Catalog File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Finally, to prepare an application for performing actual runtime translations, compile ``.po`` files to ``.mo`` files using the ``msgfmt`` command from Gettext: .. code-block:: text $ cd /place/where/myapplication/setup.py/lives $ 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 (see :ref:`adding_a_translation_directory`), these translations will be available to :app:`Pyramid`. .. index:: single: localizer single: translation single: pluralization Using a Localizer ----------------- A :term:`localizer` is an object that allows you to perform translation or pluralization "by hand" in an application. You may use the :attr:`pyramid.request.Request.localizer` attribute to obtain a :term:`localizer`. The localizer object will be configured to produce translations implied by the active :term:`locale negotiator`, or a default localizer object if no explicit locale negotiator is registered. .. code-block:: python :linenos: def aview(request): localizer = request.localizer .. note:: If you need to create a localizer for a locale, use the :func:`pyramid.i18n.make_localizer` function. .. index:: single: translating (i18n) .. _performing_a_translation: Performing a Translation ~~~~~~~~~~~~~~~~~~~~~~~~ A :term:`localizer` has a ``translate`` method which accepts either a :term:`translation string` or a Unicode string and which returns a Unicode object representing the translation. Generating a translation in a view component of an application might look like so: .. code-block:: python :linenos: from pyramid.i18n import TranslationString ts = TranslationString('Add ${number}', mapping={'number':1}, domain='pyramid') def aview(request): localizer = request.localizer translated = localizer.translate(ts) # translation string # ... use translated ... The ``request.localizer`` attribute will be a :class:`pyramid.i18n.Localizer` object bound to the locale name represented by the request. The translation returned from its :meth:`pyramid.i18n.Localizer.translate` method will depend on the ``domain`` attribute of the provided translation string as well as the locale of the localizer. .. note:: If you're using :term:`Chameleon` templates, you don't need to pre-translate translation strings this way. See :ref:`chameleon_translation_strings`. .. index:: single: pluralizing (i18n) .. _performing_a_pluralization: Performing a Pluralization ~~~~~~~~~~~~~~~~~~~~~~~~~~ A :term:`localizer` has a ``pluralize`` method with the following signature: .. code-block:: python :linenos: def pluralize(singular, plural, n, domain=None, mapping=None): ... The simplest case is the ``singular`` and ``plural`` arguments being passed as Unicode literals. This returns the appropriate literal according to the locale pluralization rules for the number ``n``, and interpolates ``mapping``. .. code-block:: python :linenos: def aview(request): localizer = request.localizer translated = localizer.pluralize('Item', 'Items', 1, 'mydomain') # ... use translated ... However, for support of other languages, the ``singular`` argument should be a Unicode value representing a :term:`message identifier`. In this case the ``plural`` value is ignored. ``domain`` should be a :term:`translation domain`, and ``mapping`` should be a dictionary that is used for *replacement value* interpolation of the translated string. The value of ``n`` will be used to find the appropriate plural form for the current language, and ``pluralize`` will return a Unicode translation for the message id ``singular``. The message file must have defined ``singular`` as a translation with plural forms. The argument provided as ``singular`` may be a :term:`translation string` object, but the domain and mapping information attached is ignored. .. code-block:: python :linenos: def aview(request): localizer = request.localizer num = 1 translated = localizer.pluralize('item_plural', '${number} items', num, 'mydomain', mapping={'number':num}) The corresponding message catalog must have language plural definitions and plural alternatives set. .. code-block:: text :linenos: "Plural-Forms: nplurals=3; plural=n==0 ? 0 : n==1 ? 1 : 2;" msgid "item_plural" msgid_plural "" msgstr[0] "No items" msgstr[1] "${number} item" msgstr[2] "${number} items" More information on complex plurals can be found in the `gettext documentation `_. .. index:: single: locale name single: negotiate_locale_name .. _obtaining_the_locale_name: Obtaining the Locale Name for a Request --------------------------------------- You can obtain the locale name related to a request by using the :func:`pyramid.request.Request.locale_name` attribute of the request. .. code-block:: python :linenos: def aview(request): locale_name = request.locale_name The locale name of a request is dynamically computed; it will be the locale name negotiated by the currently active :term:`locale negotiator`, or the :term:`default locale name` if the locale negotiator returns ``None``. You can change the default locale name by changing the ``pyramid.default_locale_name`` setting. See :ref:`default_locale_name_setting`. Once :func:`~pyramid.request.Request.locale_name` is first run, the locale name is stored on the request object. Subsequent calls to :func:`~pyramid.request.Request.locale_name` will return the stored locale name without invoking the :term:`locale negotiator`. To avoid this caching, you can use the :func:`pyramid.i18n.negotiate_locale_name` function: .. code-block:: python :linenos: from pyramid.i18n import negotiate_locale_name def aview(request): locale_name = negotiate_locale_name(request) You can also obtain the locale name related to a request using the ``locale_name`` attribute of a :term:`localizer`. .. code-block:: python :linenos: def aview(request): localizer = request.localizer locale_name = localizer.locale_name Obtaining the locale name as an attribute of a localizer is equivalent to obtaining a locale name by asking for the :func:`~pyramid.request.Request.locale_name` attribute. .. index:: single: date and currency formatting (i18n) single: Babel Performing Date Formatting and Currency Formatting -------------------------------------------------- :app:`Pyramid` does not itself perform date and currency formatting for different locales. However, :term:`Babel` can help you do this via the :class:`babel.core.Locale` class. The `Babel documentation for this class `_ provides minimal information about how to perform date and currency related locale operations. See :ref:`installing_babel` for information about how to install Babel. The :class:`babel.core.Locale` class requires a :term:`locale name` as an argument to its constructor. You can use :app:`Pyramid` APIs to obtain the locale name for a request to pass to the :class:`babel.core.Locale` constructor. See :ref:`obtaining_the_locale_name`. For example: .. code-block:: python :linenos: from babel.core import Locale def aview(request): locale_name = request.locale_name locale = Locale(locale_name) .. index:: pair: translation strings; Chameleon .. _chameleon_translation_strings: Chameleon Template Support for Translation Strings -------------------------------------------------- When a :term:`translation string` is used as the subject of textual rendering by a :term:`Chameleon` template renderer, it will automatically be translated to the requesting user's language if a suitable translation exists. This is true of both the ZPT and text variants of the Chameleon template renderers. For example, in a Chameleon ZPT template, the translation string represented by "some_translation_string" in each example below will go through translation before being rendered: .. code-block:: xml :linenos: .. code-block:: xml :linenos: .. code-block:: xml :linenos: ${some_translation_string} .. code-block:: xml :linenos: Click here .. XXX the last example above appears to not yet work as of Chameleon .. 1.2.3 The features represented by attributes of the ``i18n`` namespace of Chameleon will also consult the :app:`Pyramid` translations. See http://chameleon.readthedocs.org/en/latest/reference.html#id50. .. note:: Unlike when Chameleon is used outside of :app:`Pyramid`, when it is used *within* :app:`Pyramid`, it does not support use of the ``zope.i18n`` translation framework. Applications which use :app:`Pyramid` should use the features documented in this chapter rather than ``zope.i18n``. Third party :app:`Pyramid` template renderers might not provide this support out of the box and may need special code to do an equivalent. For those, you can always use the more manual translation facility described in :ref:`performing_a_translation`. .. index:: single: Mako i18n Mako Pyramid i18n Support ------------------------- There exists a recipe within the :term:`Pyramid Cookbook` named ":ref:`Mako Internationalization `" which explains how to add idiomatic i18n support to :term:`Mako` templates. .. index:: single: localization deployment settings single: default_locale_name .. _localization_deployment_settings: Localization-Related Deployment Settings ---------------------------------------- A :app:`Pyramid` application will have a ``pyramid.default_locale_name`` setting. This value represents the :term:`default locale name` used when the :term:`locale negotiator` returns ``None``. Pass it to the :mod:`~pyramid.config.Configurator` constructor at startup time: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator(settings={'pyramid.default_locale_name':'de'}) You may alternately supply a ``pyramid.default_locale_name`` via an application's ``.ini`` file: .. code-block:: ini :linenos: [app:main] use = egg:MyProject pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.default_locale_name = de If this value is not supplied via the Configurator constructor or via a config file, it will default to ``en``. If this setting is supplied within the :app:`Pyramid` application ``.ini`` file, it will be available as a settings key: .. code-block:: python :linenos: from pyramid.threadlocal import get_current_registry settings = get_current_registry().settings default_locale_name = settings['pyramid.default_locale_name'] .. index:: single: detecting languages "Detecting" Available Languages ------------------------------- Other systems provide an API that returns the set of "available languages" as indicated by the union of all languages in all translation directories on disk at the time of the call to the API. It is by design that :app:`Pyramid` doesn't supply such an API. Instead the application itself is responsible for knowing the "available languages". The rationale is this: any particular application deployment must always know which languages it should be translatable to anyway, regardless of which translation files are on disk. Here's why: it's not a given that because translations exist in a particular language within the registered set of translation directories that this particular deployment wants to allow translation to that language. For example, some translations may exist but they may be incomplete or incorrect. Or there may be translations to a language but not for all translation domains. Any nontrivial application deployment will always need to be able to selectively choose to allow only some languages even if that set of languages is smaller than all those detected within registered translation directories. The easiest way to allow for this is to make the application entirely responsible for knowing which languages are allowed to be translated to instead of relying on the framework to divine this information from translation directory file info. You can set up a system to allow a deployer to select available languages based on convention by using the :mod:`pyramid.settings` mechanism. Allow a deployer to modify your application's ``.ini`` file: .. code-block:: ini :linenos: [app:main] use = egg:MyProject # ... available_languages = fr de en ru Then as a part of the code of a custom :term:`locale negotiator`: .. code-block:: python :linenos: 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. .. index:: pair: translation; activating pair: locale; negotiator single: translation directory .. index:: pair: activating; translation .. _activating_translation: Activating Translation ---------------------- By default, a :app:`Pyramid` application performs no translation. To turn translation on you must: - add at least one :term:`translation directory` to your application. - ensure that your application sets the :term:`locale name` correctly. .. index:: pair: translation directory; adding .. _adding_a_translation_directory: Adding a Translation Directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :term:`gettext` is the underlying machinery behind the :app:`Pyramid` translation machinery. A translation directory is a directory organized to be useful to :term:`gettext`. A translation directory usually includes a listing of language directories, each of which itself includes an ``LC_MESSAGES`` directory. Each ``LC_MESSAGES`` directory should contain one or more ``.mo`` files. Each ``.mo`` file represents a :term:`message catalog`, which is used to provide translations to your application. Adding a :term:`translation directory` registers all of its constituent :term:`message catalog` files within your :app:`Pyramid` application to be available to use for translation services. This includes all of the ``.mo`` files found within all ``LC_MESSAGES`` directories within each locale directory in the translation directory. You can add a translation directory imperatively by using the :meth:`pyramid.config.Configurator.add_translation_dirs` during application startup. For example: .. code-block:: python :linenos: from pyramid.config import Configurator config.add_translation_dirs('my.application:locale/', 'another.application:locale/') A message catalog in a translation directory added via :meth:`~pyramid.config.Configurator.add_translation_dirs` will be merged into translations from a message catalog added earlier if both translation directories contain translations for the same locale and :term:`translation domain`. .. index:: pair: setting; locale Setting the Locale ~~~~~~~~~~~~~~~~~~ When the *default locale negotiator* (see :ref:`default_locale_negotiator`) is in use, you can inform :app:`Pyramid` of the current locale name by doing any of these things before any translations need to be performed: - Set the ``_LOCALE_`` attribute of the request to a valid locale name (usually directly within view code), e.g., ``request._LOCALE_ = 'de'``. - Ensure that a valid locale name value is in the ``request.params`` dictionary under the key named ``_LOCALE_``. This is usually the result of passing a ``_LOCALE_`` value in the query string or in the body of a form post associated with a request. For example, visiting ``http://my.application?_LOCALE_=de``. - Ensure that a valid locale name value is in the ``request.cookies`` dictionary under the key named ``_LOCALE_``. This is usually the result of setting a ``_LOCALE_`` cookie in a prior response, e.g., ``response.set_cookie('_LOCALE_', 'de')``. .. note:: If this locale negotiation scheme is inappropriate for a particular application, you can configure a custom :term:`locale negotiator` function into that application as required. See :ref:`custom_locale_negotiator`. .. index:: single: locale negotiator .. _locale_negotiators: Locale Negotiators ------------------ A :term:`locale negotiator` informs the operation of a :term:`localizer` by telling it what :term:`locale name` is related to a particular request. A locale negotiator is a bit of code which accepts a request and which returns a :term:`locale name`. It is consulted when :meth:`pyramid.i18n.Localizer.translate` or :meth:`pyramid.i18n.Localizer.pluralize` is invoked. It is also consulted when :func:`~pyramid.request.Request.locale_name` is accessed or when :func:`~pyramid.i18n.negotiate_locale_name` is invoked. .. _default_locale_negotiator: The Default Locale Negotiator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Most applications can make use of the default locale negotiator, which requires no additional coding or configuration. The default locale negotiator implementation named :class:`~pyramid.i18n.default_locale_negotiator` uses the following set of steps to determine the locale name. - First the negotiator looks for the ``_LOCALE_`` attribute of the request object (possibly set directly by view code or by a listener for an :term:`event`). - Then it looks for the ``request.params['_LOCALE_']`` value. - Then it looks for the ``request.cookies['_LOCALE_']`` value. - If no locale can be found via the request, it falls back to using the :term:`default locale name` (see :ref:`localization_deployment_settings`). - Finally if the default locale name is not explicitly set, it uses the locale name ``en``. .. _custom_locale_negotiator: Using a Custom Locale Negotiator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Locale negotiation is sometimes policy-laden and complex. If the (simple) default locale negotiation scheme described in :ref:`activating_translation` is inappropriate for your application, you may create a special :term:`locale negotiator`. Subsequently you may override the default locale negotiator by adding your newly created locale negotiator to your application's configuration. A locale negotiator is simply a callable which accepts a request and returns a single :term:`locale name` or ``None`` if no locale can be determined. Here's an implementation of a simple locale negotiator: .. code-block:: python :linenos: def my_locale_negotiator(request): locale_name = request.params.get('my_locale') return locale_name If a locale negotiator returns ``None``, it signifies to :app:`Pyramid` that the default application locale name should be used. You may add your newly created locale negotiator to your application's configuration by passing an object which can act as the negotiator (or a :term:`dotted Python name` referring to the object) as the ``locale_negotiator`` argument of the :class:`~pyramid.config.Configurator` instance during application startup. For example: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator(locale_negotiator=my_locale_negotiator) Alternatively, use the :meth:`pyramid.config.Configurator.set_locale_negotiator` method. For example: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator() config.set_locale_negotiator(my_locale_negotiator) pyramid-1.6/docs/narr/install.rst0000644000076500000240000003250112606630333017647 0ustar michaelstaff00000000000000.. _installing_chapter: Installing :app:`Pyramid` ========================= .. index:: single: install preparation Before You Install ------------------ You will need `Python `_ version 2.6 or better to run :app:`Pyramid`. .. sidebar:: Python Versions As of this writing, :app:`Pyramid` has been tested under Python 2.6, Python 2.7, Python 3.2, Python 3.3, Python 3.4, PyPy, and PyPy3. :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 on :term:`PyPy` (1.9+). :app:`Pyramid` installation does not require the compilation of any C code, so you need only a Python interpreter that meets the requirements mentioned. Some :app:`Pyramid` dependencies may attempt to build C extensions for performance speedups. If a compiler or Python headers are unavailable the dependency will fall back to using pure Python instead. For Mac OS X Users ~~~~~~~~~~~~~~~~~~ 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. You can install the latest verion of Python for Mac OS X from the binaries on `python.org `_. Alternatively, you can use the `homebrew `_ package manager. .. 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`. If You Don't Yet Have a Python Interpreter (UNIX) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If your system doesn't have a Python interpreter, and you're on UNIX, you can either install Python using your operating system's package manager *or* you can install Python from source fairly easily on any UNIX system that has development tools. .. index:: pair: install; Python (from package, UNIX) Package Manager Method ++++++++++++++++++++++ You can use your system's "package manager" to install Python. Each package manager is slightly different, but the "flavor" of them is usually the same. For example, on a Debian or Ubuntu system, use the following command: .. code-block:: text $ sudo apt-get install python2.7-dev This command will install both the Python interpreter and its development header files. Note that the headers are required by some (optional) C extensions in software depended upon by Pyramid, not by Pyramid itself. Once these steps are performed, the Python interpreter will usually be invokable via ``python2.7`` from a shell prompt. .. index:: pair: install; Python (from source, UNIX) Source Compile Method +++++++++++++++++++++ It's useful to use a Python interpreter that *isn't* the "system" Python interpreter to develop your software. The authors of :app:`Pyramid` tend not to use the system Python for development purposes; always a self-compiled one. Compiling Python is usually easy, and often the "system" Python is compiled with options that aren't optimal for web development. For an explanation, see https://github.com/Pylons/pyramid/issues/747. To compile software on your UNIX system, typically you need development tools. Often these can be installed via the package manager. For example, this works to do so on an Ubuntu Linux system: .. code-block:: text $ sudo apt-get install build-essential On Mac OS X, installing `XCode `_ has much the same effect. Once you've got development tools installed on your system, you can install a Python 2.7 interpreter from *source*, on the same system, using the following commands: .. code-block:: text $ cd ~ $ mkdir tmp $ mkdir opt $ cd tmp $ wget http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tgz $ tar xvzf Python-2.7.3.tgz $ cd Python-2.7.3 $ ./configure --prefix=$HOME/opt/Python-2.7.3 $ make && make install Once these steps are performed, the Python interpreter will be invokable via ``$HOME/opt/Python-2.7.3/bin/python`` from a shell prompt. .. index:: pair: install; Python (from package, Windows) If You Don't Yet Have a Python Interpreter (Windows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If your Windows system doesn't have a Python interpreter, you'll need to install it by downloading a Python 2.7-series interpreter executable from `python.org's download section `_ (the files labeled "Windows Installer"). Once you've downloaded it, double click on the executable and accept the defaults during the installation process. You may also need to download and install the Python for Windows extensions. .. warning:: After you install Python on Windows, you may need to add the ``C:\Python27`` directory to your environment's ``Path`` in order to make it possible to invoke Python from a command prompt by typing ``python``. To do so, right click ``My Computer``, select ``Properties`` --> ``Advanced Tab`` --> ``Environment Variables`` and add that directory to the end of the ``Path`` environment variable. .. index:: single: installing on UNIX .. _installing_unix: Installing :app:`Pyramid` on a UNIX System ------------------------------------------ It is best practice to install :app:`Pyramid` into a "virtual" Python environment in order to obtain isolation from any "system" packages you've got installed in your Python version. This can be done by using the :term:`virtualenv` package. Using a virtualenv will also prevent :app:`Pyramid` from globally installing versions of packages that are not compatible with your system Python. To set up a virtualenv in which to install :app:`Pyramid`, first ensure that :term:`setuptools` is installed. To do so, invoke ``import setuptools`` within the Python interpreter you'd like to run :app:`Pyramid` under. The following command will not display anything if setuptools is already installed: .. code-block:: text $ python2.7 -c 'import setuptools' Running the same command will yield the following output if setuptools is not yet installed: .. code-block:: text Traceback (most recent call last): File "", line 1, in ImportError: No module named setuptools If ``import setuptools`` raises an :exc:`ImportError` as it does above, you will need to install setuptools manually. If you are using a "system" Python (one installed by your OS distributor or a third-party packager such as Fink or MacPorts), you can usually install the setuptools package by using your system's package manager. If you cannot do this, or if you're using a self-installed version of Python, you will need to install setuptools "by hand". Installing setuptools "by hand" is always a reasonable thing to do, even if your package manager already has a pre-chewed version of setuptools for installation. Installing Setuptools ~~~~~~~~~~~~~~~~~~~~~ To install setuptools by hand under Python 2, first download `ez_setup.py `_ then invoke it using the Python interpreter into which you want to install setuptools. .. code-block:: text $ python ez_setup.py Once this command is invoked, setuptools should be installed on your system. If the command fails due to permission errors, you may need to be the administrative user on your system to successfully invoke the script. To remediate this, you may need to do: .. code-block:: text $ sudo python ez_setup.py .. index:: pair: install; virtualenv Installing the ``virtualenv`` Package ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once you've got setuptools installed, you should install the :term:`virtualenv` package. To install the :term:`virtualenv` package into your setuptools-enabled Python interpreter, use the ``easy_install`` command. .. warning:: Python 3.3 includes ``pyvenv`` out of the box, which provides similar functionality to ``virtualenv``. We however suggest using ``virtualenv`` instead, which works well with Python 3.3. This isn't a recommendation made for technical reasons; it's made because it's not feasible for the authors of this guide to explain setup using multiple virtual environment systems. We are aiming to not need to make the installation documentation Turing-complete. If you insist on using ``pyvenv``, you'll need to understand how to install software such as ``setuptools`` into the virtual environment manually, which this guide does not cover. .. code-block:: text $ easy_install virtualenv This command should succeed, and tell you that the virtualenv package is now installed. If it fails due to permission errors, you may need to install it as your system's administrative user. For example: .. code-block:: text $ sudo easy_install virtualenv .. index:: single: virtualenv pair: Python; virtual environment Creating the Virtual Python Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once the :term:`virtualenv` package is installed in your Python environment, you can then create a virtual environment. To do so, invoke the following: .. code-block:: text $ export VENV=~/env $ virtualenv $VENV New python executable in /home/foo/env/bin/python Installing setuptools.............done. You can either follow the use of the environment variable, ``$VENV``, or replace it with the root directory of the :term:`virtualenv`. In that case, the `export` command can be skipped. If you choose the former approach, ensure that it's an absolute path. .. warning:: Avoid using the ``--system-site-packages`` option when creating the virtualenv unless you know what you are doing. For versions of virtualenv prior to 1.7, make sure to use the ``--no-site-packages`` option, because this option was formerly not the default and may produce undesirable results. .. warning:: *do not* use ``sudo`` to run the ``virtualenv`` script. It's perfectly acceptable (and desirable) to create a virtualenv as a normal user. Installing :app:`Pyramid` into the Virtual Python Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After you've got your virtualenv installed, you may install :app:`Pyramid` itself using the following commands: .. parsed-literal:: $ $VENV/bin/easy_install "pyramid==\ |release|\ " The ``easy_install`` command will take longer than the previous ones to complete, as it downloads and installs a number of dependencies. .. note:: If you see any warnings and/or errors related to failing to compile the C extensions, in most cases you may safely ignore those errors. If you wish to use the C extensions, please verify that you have a functioning compiler and the Python header files installed. .. index:: single: installing on Windows .. _installing_windows: Installing :app:`Pyramid` on a Windows System --------------------------------------------- You can use Pyramid on Windows under Python 2 or 3. #. Download and install the most recent `Python 2.7.x or 3.3.x version `_ for your system. #. Download and install the `Python for Windows extensions `_. Carefully read the README.txt file at the end of the list of builds, and follow its directions. Make sure you get the proper 32- or 64-bit build and Python version. #. Install latest :term:`setuptools` distribution into the Python from step 1 above: download `ez_setup.py `_ and run it using the ``python`` interpreter of your Python 2.7 or 3.3 installation using a command prompt: .. code-block:: text # modify the command according to the python version, e.g.: # for Python 2.7: c:\> c:\Python27\python ez_setup.py # for Python 3.3: c:\> c:\Python33\python ez_setup.py #. Install `virtualenv`: .. code-block:: text # modify the command according to the python version, e.g.: # for Python 2.7: c:\> c:\Python27\Scripts\easy_install virtualenv # for Python 3.3: c:\> c:\Python33\Scripts\easy_install virtualenv #. Make a :term:`virtualenv` workspace: .. code-block:: text c:\> set VENV=c:\env # modify the command according to the python version, e.g.: # for Python 2.7: c:\> c:\Python27\Scripts\virtualenv %VENV% # for Python 3.3: c:\> c:\Python33\Scripts\virtualenv %VENV% You can either follow the use of the environment variable, ``%VENV%``, or replace it with the root directory of the :term:`virtualenv`. In that case, the `set` command can be skipped. If you choose the former approach, ensure that it's an absolute path. #. (Optional) Consider using ``%VENV%\Scripts\activate.bat`` to make your shell environment wired to use the virtualenv. #. Use ``easy_install`` to get :app:`Pyramid` and its direct dependencies installed: .. parsed-literal:: c:\\env> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ " What Gets Installed ------------------- When you ``easy_install`` :app:`Pyramid`, various other libraries such as WebOb, PasteDeploy, and others are installed. Additionally, as chronicled in :ref:`project_narr`, scaffolds will be registered, which make it easy to start a new :app:`Pyramid` project. pyramid-1.6/docs/narr/introduction.rst0000644000076500000240000012365512606630333020735 0ustar michaelstaff00000000000000.. index:: single: Agendaless Consulting single: Pylons single: Django single: Zope single: frameworks vs. libraries single: framework :app:`Pyramid` Introduction =========================== :app:`Pyramid` is a general, open source, Python web application development *framework*. Its primary goal is to make it easier for a Python developer to create web applications. .. sidebar:: Frameworks vs. Libraries A *framework* differs from a *library* in one very important way: library code is always *called* by code that you write, while a framework always *calls* code that you write. Using a set of libraries to create an application is usually easier than using a framework initially, because you can choose to cede control to library code you have not authored very selectively. But when you use a framework, you are required to cede a greater portion of control to code you have not authored: code that resides in the framework itself. You needn't use a framework at all to create a web application using Python. A rich set of libraries already exists for the platform. In practice, however, using a framework to create an application is often more practical than rolling your own via a set of libraries if the framework provides a set of facilities that fits your application requirements. Pyramid attempts to follow these design and engineering principles: Simplicity :app:`Pyramid` takes a *"pay only for what you eat"* approach. You can get results even if you have only a partial understanding of :app:`Pyramid`. It doesn't force you to use any particular technology to produce an application, and we try to keep the core set of concepts that you need to understand to a minimum. Minimalism :app:`Pyramid` tries to solve only the fundamental problems of creating a web application: the mapping of URLs to code, templating, security, and serving static assets. We consider these to be the core activities that are common to nearly all web applications. Documentation Pyramid's minimalism means that it is easier for us to maintain complete and up-to-date documentation. It is our goal that no aspect of Pyramid is undocumented. Speed :app:`Pyramid` is designed to provide noticeably fast execution for common tasks such as templating and simple response generation. Reliability :app:`Pyramid` is developed conservatively and tested exhaustively. Where Pyramid source code is concerned, our motto is: "If it ain't tested, it's broke". Openness As with Python, the Pyramid software is distributed under a `permissive open source license `_. .. _what_makes_pyramid_unique: What makes Pyramid unique ------------------------- Understandably, people don't usually want to hear about squishy engineering principles; they want to hear about concrete stuff that solves their problems. With that in mind, what would make someone want to use Pyramid instead of one of the many other web frameworks available today? What makes Pyramid unique? This is a hard question to answer because there are lots of excellent choices, and it's actually quite hard to make a wrong choice, particularly in the Python web framework market. But one reasonable answer is this: you can write very small applications in Pyramid without needing to know a lot. "What?" you say. "That can't possibly be a unique feature. Lots of other web frameworks let you do that!" Well, you're right. But unlike many other systems, you can also write very large applications in Pyramid if you learn a little more about it. Pyramid will allow you to become productive quickly, and will grow with you. It won't hold you back when your application is small, and it won't get in your way when your application becomes large. "Well that's fine," you say. "Lots of other frameworks let me write large apps, too." Absolutely. But other Python web frameworks don't seamlessly let you do both. They seem to fall into two non-overlapping categories: frameworks for "small apps" and frameworks for "big apps". The "small app" frameworks typically sacrifice "big app" features, and vice versa. We don't think it's a universally reasonable suggestion to write "small apps" in a "small framework" and "big apps" in a "big framework". You can't really know to what size every application will eventually grow. We don't really want to have to rewrite a previously small application in another framework when it gets "too big". We believe the current binary distinction between frameworks for small and large applications is just false. A well-designed framework should be able to be good at both. Pyramid strives to be that kind of framework. To this end, Pyramid provides a set of features that combined are unique amongst Python web frameworks. Lots of other frameworks contain some combination of these features. Pyramid of course actually stole many of them from those other frameworks. But Pyramid is the only one that has all of them in one place, documented appropriately, and useful *à la carte* without necessarily paying for the entire banquet. These are detailed below. Single-file applications ~~~~~~~~~~~~~~~~~~~~~~~~ You can write a Pyramid application that lives entirely in one Python file, not unlike existing Python microframeworks. This is beneficial for one-off prototyping, bug reproduction, and very small applications. These applications are easy to understand because all the information about the application lives in a single place, and you can deploy them without needing to understand much about Python distributions and packaging. Pyramid isn't really marketed as a microframework, but it allows you to do almost everything that frameworks that are marketed as "micro" offer in very similar ways. .. literalinclude:: helloworld.py .. seealso:: See also :ref:`firstapp_chapter`. Decorator-based configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you like the idea of framework configuration statements living next to the code it configures, so you don't have to constantly switch between files to refer to framework configuration when adding new code, you can use Pyramid decorators to localize the configuration. For example: .. code-block:: python from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='fred') def fred_view(request): return Response('fred') However, unlike some other systems, using decorators for Pyramid configuration does not make your application difficult to extend, test, or reuse. The :class:`~pyramid.view.view_config` decorator, for example, does not actually *change* the input or output of the function it decorates, so testing it is a "WYSIWYG" operation. You don't need to understand the framework to test your own code. You just behave as if the decorator is not there. You can also instruct Pyramid to ignore some decorators, or use completely imperative configuration instead of decorators to add views. Pyramid decorators are inert instead of eager. You detect and activate them with a :term:`scan`. Example: :ref:`mapping_views_using_a_decorator_section`. URL generation ~~~~~~~~~~~~~~ Pyramid is capable of generating URLs for resources, routes, and static assets. Its URL generation APIs are easy to use and flexible. If you use Pyramid's various APIs for generating URLs, you can change your configuration around arbitrarily without fear of breaking a link on one of your web pages. Example: :ref:`generating_route_urls`. Static file serving ~~~~~~~~~~~~~~~~~~~ Pyramid is perfectly willing to serve static files itself. It won't make you use some external web server to do that. You can even serve more than one set of static files in a single Pyramid web application (e.g., ``/static`` and ``/static2``). You can optionally place your files on an external web server and ask Pyramid to help you generate URLs to those files. This let's you use Pyramid's internal file serving while doing development, and a faster static file server in production, without changing any code. Example: :ref:`static_assets_section`. Fully interactive development ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When developing a Pyramid application, several interactive features are available. Pyramid can automatically utilize changed templates when rendering pages and automatically restart the application to incorporate changed Python code. Plain old ``print()`` calls used for debugging can display to a console. Pyramid's debug toolbar comes activated when you use a Pyramid scaffold to render a project. This toolbar overlays your application in the browser, and allows you access to framework data, such as the routes configured, the last renderings performed, the current set of packages installed, SQLAlchemy queries run, logging data, and various other facts. When an exception occurs, you can use its interactive debugger to poke around right in your browser to try to determine the cause of the exception. It's handy. Example: :ref:`debug_toolbar`. Debugging settings ~~~~~~~~~~~~~~~~~~ Pyramid has debugging settings that allow you to print Pyramid runtime information to the console when things aren't behaving as you're expecting. For example, you can turn on ``debug_notfound``, which prints an informative message to the console every time a URL does not match any view. You can turn on ``debug_authorization``, which lets you know why a view execution was allowed or denied by printing a message to the console. These features are useful for those WTF moments. There are also a number of commands that you can invoke within a Pyramid environment that allow you to introspect the configuration of your system. ``proutes`` shows all configured routes for an application in the order they'll be evaluated for matching. ``pviews`` shows all configured views for any given URL. These are also WTF-crushers in some circumstances. Examples: :ref:`debug_authorization_section` and :ref:`command_line_chapter`. Add-ons ~~~~~~~ Pyramid has an extensive set of add-ons held to the same quality standards as the Pyramid core itself. Add-ons are packages which provide functionality that the Pyramid core doesn't. Add-on packages already exist which let you easily send email, let you use the Jinja2 templating system, let you use XML-RPC or JSON-RPC, let you integrate with jQuery Mobile, etc. Examples: http://docs.pylonsproject.org/en/latest/docs/pyramid.html#pyramid-add-on-documentation Class-based and function-based views ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid has a structured, unified concept of a :term:`view callable`. View callables can be functions, methods of classes, or even instances. When you add a new view callable, you can choose to make it a function or a method of a class. In either case Pyramid treats it largely the same way. You can change your mind later and move code between methods of classes and functions. A collection of similar view callables can be attached to a single class as methods, if that floats your boat, and they can share initialization code as necessary. All kinds of views are easy to understand and use, and operate similarly. There is no phony distinction between them. They can be used for the same purposes. Here's a view callable defined as a function: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config(route_name='aview') def aview(request): return Response('one') Here's a few views defined as methods of a class instead: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config class AView(object): def __init__(self, request): self.request = request @view_config(route_name='view_one') def view_one(self): return Response('one') @view_config(route_name='view_two') def view_two(self): return Response('two') .. seealso:: See also :ref:`view_config_placement`. .. _intro_asset_specs: Asset specifications ~~~~~~~~~~~~~~~~~~~~ Asset specifications are strings that contain both a Python package name and a file or directory name, e.g., ``MyPackage:static/index.html``. Use of these specifications is omnipresent in Pyramid. An asset specification can refer to a template, a translation directory, or any other package-bound static resource. This makes a system built on Pyramid extensible because you don't have to rely on globals ("*the* static directory") or lookup schemes ("*the* ordered set of template directories") to address your files. You can move files around as necessary, and include other packages that may not share your system's templates or static files without encountering conflicts. Because asset specifications are used heavily in Pyramid, we've also provided a way to allow users to override assets. Say you love a system that someone else has created with Pyramid but you just need to change "that one template" to make it all better. No need to fork the application. Just override the asset specification for that template with your own inside a wrapper, and you're good to go. Examples: :ref:`asset_specifications` and :ref:`overriding_assets_section`. Extensible templating ~~~~~~~~~~~~~~~~~~~~~ Pyramid has a structured API that allows for pluggability of "renderers". Templating systems such as Mako, Genshi, Chameleon, and Jinja2 can be treated as renderers. Renderer bindings for all of these templating systems already exist for use in Pyramid. But if you'd rather use another, it's not a big deal. Just copy the code from an existing renderer package, and plug in your favorite templating system. You'll then be able to use that templating system from within Pyramid just as you'd use one of the "built-in" templating systems. Pyramid does not make you use a single templating system exclusively. You can use multiple templating systems, even in the same project. Example: :ref:`templates_used_directly`. Rendered views can return dictionaries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you use a :term:`renderer`, you don't have to return a special kind of "webby" ``Response`` object from a view. Instead you can return a dictionary, and Pyramid will take care of converting that dictionary to a Response using a template on your behalf. This makes the view easier to test, because you don't have to parse HTML in your tests. Instead just make an assertion that the view returns "the right stuff" in the dictionary. You can write "real" unit tests instead of functionally testing all of your views. .. index:: pair: renderer; explicitly calling pair: view renderer; explictly calling .. _example_render_to_response_call: For example, instead of returning a ``Response`` object from a ``render_to_response`` call: .. code-block:: python :linenos: from pyramid.renderers import render_to_response def myview(request): return render_to_response('myapp:templates/mytemplate.pt', {'a':1}, request=request) You can return a Python dictionary: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='myapp:templates/mytemplate.pt') def myview(request): return {'a':1} When this view callable is called by Pyramid, the ``{'a':1}`` dictionary will be rendered to a response on your behalf. The string passed as ``renderer=`` above is an :term:`asset specification`. It is in the form ``packagename:directoryname/filename.ext``. In this case, it refers to the ``mytemplate.pt`` file in the ``templates`` directory within the ``myapp`` Python package. Asset specifications are omnipresent in Pyramid. See :ref:`intro_asset_specs` for more information. Example: :ref:`renderers_chapter`. Event system ~~~~~~~~~~~~ Pyramid emits *events* during its request processing lifecycle. You can subscribe any number of listeners to these events. For example, to be notified of a new request, you can subscribe to the ``NewRequest`` event. To be notified that a template is about to be rendered, you can subscribe to the ``BeforeRender`` event, and so forth. Using an event publishing system as a framework notification feature instead of hardcoded hook points tends to make systems based on that framework less brittle. You can also use Pyramid's event system to send your *own* events. For example, if you'd like to create a system that is itself a framework, and may want to notify subscribers that a document has just been indexed, you can create your own event type (``DocumentIndexed`` perhaps) and send the event via Pyramid. Users of this framework can then subscribe to your event like they'd subscribe to the events that are normally sent by Pyramid itself. Example: :ref:`events_chapter` and :ref:`event_types`. Built-in internationalization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid ships with internationalization-related features in its core: localization, pluralization, and creating message catalogs from source files and templates. Pyramid allows for a plurality of message catalogs via the use of translation domains. You can create a system that has its own translations without conflict with other translations in other domains. Example: :ref:`i18n_chapter`. HTTP caching ~~~~~~~~~~~~ Pyramid provides an easy way to associate views with HTTP caching policies. You can just tell Pyramid to configure your view with an ``http_cache`` statement, and it will take care of the rest:: @view_config(http_cache=3600) # 60 minutes def myview(request): .... Pyramid will add appropriate ``Cache-Control`` and ``Expires`` headers to responses generated when this view is invoked. See the :meth:`~pyramid.config.Configurator.add_view` method's ``http_cache`` documentation for more information. Sessions ~~~~~~~~ Pyramid has built-in HTTP sessioning. This allows you to associate data with otherwise anonymous users between requests. Lots of systems do this. But Pyramid also allows you to plug in your own sessioning system by creating some code that adheres to a documented interface. Currently there is a binding package for the third-party Redis sessioning system that does exactly this. But if you have a specialized need (perhaps you want to store your session data in MongoDB), you can. You can even switch between implementations without changing your application code. Example: :ref:`sessions_chapter`. Speed ~~~~~ The Pyramid core is, as far as we can tell, at least marginally faster than any other existing Python web framework. It has been engineered from the ground up for speed. It only does as much work as absolutely necessary when you ask it to get a job done. Extraneous function calls and suboptimal algorithms in its core codepaths are avoided. It is feasible to get, for example, between 3500 and 4000 requests per second from a simple Pyramid view on commodity dual-core laptop hardware and an appropriate WSGI server (mod_wsgi or gunicorn). In any case, performance statistics are largely useless without requirements and goals, but if you need speed, Pyramid will almost certainly never be your application's bottleneck; at least no more than Python will be a bottleneck. Example: http://blog.curiasolutions.com/pages/the-great-web-framework-shootout.html Exception views ~~~~~~~~~~~~~~~ Exceptions happen. Rather than deal with exceptions that might present themselves to a user in production in an ad-hoc way, Pyramid allows you to register an :term:`exception view`. Exception views are like regular Pyramid views, but they're only invoked when an exception "bubbles up" to Pyramid itself. For example, you might register an exception view for the :exc:`Exception` exception, which will catch *all* exceptions, and present a pretty "well, this is embarrassing" page. Or you might choose to register an exception view for only specific kinds of application-specific exceptions, such as an exception that happens when a file is not found, or an exception that happens when an action cannot be performed because the user doesn't have permission to do something. In the former case, you can show a pretty "Not Found" page; in the latter case you might show a login form. Example: :ref:`exception_views`. No singletons ~~~~~~~~~~~~~ Pyramid is written in such a way that it requires your application to have exactly zero "singleton" data structures. Or put another way, Pyramid doesn't require you to construct any "mutable globals". Or put even another different way, an import of a Pyramid application needn't have any "import-time side effects". This is esoteric-sounding, but if you've ever tried to cope with parameterizing a Django ``settings.py`` file for multiple installations of the same application, or if you've ever needed to monkey-patch some framework fixture so that it behaves properly for your use case, or if you've ever wanted to deploy your system using an asynchronous server, you'll end up appreciating this feature. It just won't be a problem. You can even run multiple copies of a similar but not identically configured Pyramid application within the same Python process. This is good for shared hosting environments, where RAM is at a premium. View predicates and many views per route ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unlike many other systems, Pyramid allows you to associate more than one view per route. For example, you can create a route with the pattern ``/items`` and when the route is matched, you can shuffle off the request to one view if the request method is GET, another view if the request method is POST, etc. A system known as "view predicates" allows for this. Request method matching is the most basic thing you can do with a view predicate. You can also associate views with other request parameters, such as the elements in the query string, the Accept header, whether the request is an XHR request or not, and lots of other things. This feature allows you to keep your individual views clean. They won't need much conditional logic, so they'll be easier to test. Example: :ref:`view_configuration_parameters`. Transaction management ~~~~~~~~~~~~~~~~~~~~~~ Pyramid's :term:`scaffold` system renders projects that include a *transaction management* system, stolen from Zope. When you use this transaction management system, you cease being responsible for committing your data anymore. Instead Pyramid takes care of committing: it commits at the end of a request or aborts if there's an exception. Why is that a good thing? Having a centralized place for transaction management is a great thing. If, instead of managing your transactions in a centralized place, you sprinkle ``session.commit`` calls in your application logic itself, you can wind up in a bad place. Wherever you manually commit data to your database, it's likely that some of your other code is going to run *after* your commit. If that code goes on to do other important things after that commit, and an error happens in the later code, you can easily wind up with inconsistent data if you're not extremely careful. Some data will have been written to the database that probably should not have. Having a centralized commit point saves you from needing to think about this; it's great for lazy people who also care about data integrity. Either the request completes successfully, and all changes are committed, or it does not, and all changes are aborted. Pyramid's transaction management system allows you to synchronize commits between multiple databases. It also allows you to do things like conditionally send email if a transaction commits, but otherwise keep quiet. Example: :ref:`bfg_sql_wiki_tutorial` (note the lack of commit statements anywhere in application code). Configuration conflict detection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a system is small, it's reasonably easy to keep it all in your head. But when systems grow large, you may have hundreds or thousands of configuration statements which add a view, add a route, and so forth. Pyramid's configuration system keeps track of your configuration statements. If you accidentally add two that are identical, or Pyramid can't make sense out of what it would mean to have both statements active at the same time, it will complain loudly at startup time. It's not dumb though. It will automatically resolve conflicting configuration statements on its own if you use the configuration :meth:`~pyramid.config.Configurator.include` system. "More local" statements are preferred over "less local" ones. This allows you to intelligently factor large systems into smaller ones. Example: :ref:`conflict_detection`. Configuration extensibility ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unlike other systems, Pyramid provides a structured "include" mechanism (see :meth:`~pyramid.config.Configurator.include`) that allows you to combine applications from multiple Python packages. All the configuration statements that can be performed in your "main" Pyramid application can also be performed by included packages, including the addition of views, routes, subscribers, and even authentication and authorization policies. You can even extend or override an existing application by including another application's configuration in your own, overriding or adding new views and routes to it. This has the potential to allow you to create a big application out of many other smaller ones. For example, if you want to reuse an existing application that already has a bunch of routes, you can just use the ``include`` statement with a ``route_prefix``. The new application will live within your application at an URL prefix. It's not a big deal, and requires little up-front engineering effort. For example: .. code-block:: python :linenos: from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.include('pyramid_jinja2') config.include('pyramid_exclog') config.include('some.other.guys.package', route_prefix='/someotherguy') .. seealso:: See also :ref:`including_configuration` and :ref:`building_an_extensible_app`. Flexible authentication and authorization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid includes a flexible, pluggable authentication and authorization system. No matter where your user data is stored, or what scheme you'd like to use to permit your users to access your data, you can use a predefined Pyramid plugpoint to plug in your custom authentication and authorization code. If you want to change these schemes later, you can just change it in one place rather than everywhere in your code. It also ships with prebuilt well-tested authentication and authorization schemes out of the box. But what if you don't want to use Pyramid's built-in system? You don't have to. You can just write your own bespoke security code as you would in any other system. Example: :ref:`enabling_authorization_policy`. Traversal ~~~~~~~~~ :term:`Traversal` is a concept stolen from :term:`Zope`. It allows you to create a tree of resources, each of which can be addressed by one or more URLs. Each of those resources can have one or more *views* associated with it. If your data isn't naturally treelike, or you're unwilling to create a treelike representation of your data, you aren't going to find traversal very useful. However, traversal is absolutely fantastic for sites that need to be arbitrarily extensible. It's a lot easier to add a node to a tree than it is to shoehorn a route into an ordered list of other routes, or to create another entire instance of an application to service a department and glue code to allow disparate apps to share data. It's a great fit for sites that naturally lend themselves to changing departmental hierarchies, such as content management systems and document management systems. Traversal also lends itself well to systems that require very granular security ("Bob can edit *this* document" as opposed to "Bob can edit documents"). Examples: :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`. Tweens ~~~~~~ Pyramid has a sort of internal WSGI-middleware-ish pipeline that can be hooked by arbitrary add-ons named "tweens". The debug toolbar is a "tween", and the ``pyramid_tm`` transaction manager is also. Tweens are more useful than WSGI :term:`middleware` in some circumstances because they run in the context of Pyramid itself, meaning you have access to templates and other renderers, a "real" request object, and other niceties. Example: :ref:`registering_tweens`. View response adapters ~~~~~~~~~~~~~~~~~~~~~~ A lot is made of the aesthetics of what *kinds* of objects you're allowed to return from view callables in various frameworks. In a previous section in this document, we showed you that, if you use a :term:`renderer`, you can usually return a dictionary from a view callable instead of a full-on :term:`Response` object. But some frameworks allow you to return strings or tuples from view callables. When frameworks allow for this, code looks slightly prettier, because fewer imports need to be done, and there is less code. For example, compare this: .. code-block:: python :linenos: def aview(request): return "Hello world!" To this: .. code-block:: python :linenos: from pyramid.response import Response def aview(request): return Response("Hello world!") The former is "prettier", right? Out of the box, if you define the former view callable (the one that simply returns a string) in Pyramid, when it is executed, Pyramid will raise an exception. This is because "explicit is better than implicit", in most cases, and by default Pyramid wants you to return a :term:`Response` object from a view callable. This is because there's usually a heck of a lot more to a response object than just its body. But if you're the kind of person who values such aesthetics, we have an easy way to allow for this sort of thing: .. code-block:: python :linenos: from pyramid.config import Configurator from pyramid.response import Response def string_response_adapter(s): response = Response(s) response.content_type = 'text/html' return response if __name__ == '__main__': config = Configurator() config.add_response_adapter(string_response_adapter, basestring) Do that once in your Pyramid application at startup. Now you can return strings from any of your view callables, e.g.: .. code-block:: python :linenos: def helloview(request): return "Hello world!" def goodbyeview(request): return "Goodbye world!" Oh noes! What if you want to indicate a custom content type? And a custom status code? No fear: .. code-block:: python :linenos: from pyramid.config import Configurator def tuple_response_adapter(val): status_int, content_type, body = val response = Response(body) response.content_type = content_type response.status_int = status_int return response def string_response_adapter(body): response = Response(body) response.content_type = 'text/html' response.status_int = 200 return response if __name__ == '__main__': config = Configurator() config.add_response_adapter(string_response_adapter, basestring) config.add_response_adapter(tuple_response_adapter, tuple) Once this is done, both of these view callables will work: .. code-block:: python :linenos: def aview(request): return "Hello world!" def anotherview(request): return (403, 'text/plain', "Forbidden") Pyramid defaults to explicit behavior, because it's the most generally useful, but provides hooks that allow you to adapt the framework to localized aesthetic desires. .. seealso:: See also :ref:`using_iresponse`. "Global" response object ~~~~~~~~~~~~~~~~~~~~~~~~ "Constructing these response objects in my view callables is such a chore! And I'm way too lazy to register a response adapter, as per the prior section," you say. Fine. Be that way: .. code-block:: python :linenos: def aview(request): response = request.response response.body = 'Hello world!' response.content_type = 'text/plain' return response .. seealso:: See also :ref:`request_response_attr`. Automating repetitive configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Does Pyramid's configurator allow you to do something, but you're a little adventurous and just want it a little less verbose? Or you'd like to offer up some handy configuration feature to other Pyramid users without requiring that we change Pyramid? You can extend Pyramid's :term:`Configurator` with your own directives. For example, let's say you find yourself calling :meth:`pyramid.config.Configurator.add_view` repetitively. Usually you can take the boring away by using existing shortcuts, but let's say that this is a case where there is no such shortcut: .. code-block:: python :linenos: from pyramid.config import Configurator config = Configurator() config.add_route('xhr_route', '/xhr/{id}') config.add_view('my.package.GET_view', route_name='xhr_route', xhr=True, permission='view', request_method='GET') config.add_view('my.package.POST_view', route_name='xhr_route', xhr=True, permission='view', request_method='POST') config.add_view('my.package.HEAD_view', route_name='xhr_route', xhr=True, permission='view', request_method='HEAD') Pretty tedious right? You can add a directive to the Pyramid configurator to automate some of the tedium away: .. code-block:: python :linenos: from pyramid.config import Configurator def add_protected_xhr_views(config, module): module = config.maybe_dotted(module) for method in ('GET', 'POST', 'HEAD'): view = getattr(module, 'xhr_%s_view' % method, None) if view is not None: config.add_view(view, route_name='xhr_route', xhr=True, permission='view', request_method=method) config = Configurator() config.add_directive('add_protected_xhr_views', add_protected_xhr_views) Once that's done, you can call the directive you've just added as a method of the Configurator object: .. code-block:: python :linenos: config.add_route('xhr_route', '/xhr/{id}') config.add_protected_xhr_views('my.package') Your previously repetitive configuration lines have now morphed into one line. You can share your configuration code with others this way, too, by packaging it up and calling :meth:`~pyramid.config.Configurator.add_directive` from within a function called when another user uses the :meth:`~pyramid.config.Configurator.include` method against your code. .. seealso:: See also :ref:`add_directive`. Programmatic introspection ~~~~~~~~~~~~~~~~~~~~~~~~~~ If you're building a large system that other users may plug code into, it's useful to be able to get an enumeration of what code they plugged in *at application runtime*. For example, you might want to show them a set of tabs at the top of the screen based on an enumeration of views they registered. This is possible using Pyramid's :term:`introspector`. Here's an example of using Pyramid's introspector from within a view callable: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='bar') def show_current_route_pattern(request): introspector = request.registry.introspector route_name = request.matched_route.name route_intr = introspector.get('routes', route_name) return Response(str(route_intr['pattern'])) .. seealso:: See also :ref:`using_introspection`. Python 3 compatibility ~~~~~~~~~~~~~~~~~~~~~~ Pyramid and most of its add-ons are Python 3 compatible. If you develop a Pyramid application today, you won't need to worry that five years from now you'll be backwatered because there are language features you'd like to use but your framework doesn't support newer Python versions. Testing ~~~~~~~ Every release of Pyramid has 100% statement coverage via unit and integration tests, as measured by the ``coverage`` tool available on PyPI. It also has greater than 95% decision/condition coverage as measured by the ``instrumental`` tool available on PyPI. It is automatically tested by the Jenkins tool on Python 2.6, Python 2.7, Python 3.2, Python 3.3, Python 3.4, PyPy, and PyPy3 after each commit to its GitHub repository. Official Pyramid add-ons are held to a similar testing standard. We still find bugs in Pyramid and its official add-ons, but we've noticed we find a lot more of them while working on other projects that don't have a good testing regime. Example: http://jenkins.pylonsproject.org/ Support ~~~~~~~ It's our goal that no Pyramid question go unanswered. Whether you ask a question on IRC, on the Pylons-discuss mailing list, or on StackOverflow, you're likely to get a reasonably prompt response. We don't tolerate "support trolls" or other people who seem to get their rocks off by berating fellow users in our various official support channels. We try to keep it well-lit and new-user-friendly. Example: Visit irc\://freenode.net#pyramid (the ``#pyramid`` channel on irc.freenode.net in an IRC client) or the pylons-discuss maillist at http://groups.google.com/group/pylons-discuss/. Documentation ~~~~~~~~~~~~~ It's a constant struggle, but we try to maintain a balance between completeness and new-user-friendliness in the official narrative Pyramid documentation (concrete suggestions for improvement are always appreciated, by the way). We also maintain a "cookbook" of recipes, which are usually demonstrations of common integration scenarios too specific to add to the official narrative docs. In any case, the Pyramid documentation is comprehensive. Example: The Pyramid Cookbook at http://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/. .. index:: single: Pylons Project What Is The Pylons Project? --------------------------- :app:`Pyramid` is a member of the collection of software published under the Pylons Project. Pylons software is written by a loose-knit community of contributors. The `Pylons Project website `_ includes details about how :app:`Pyramid` relates to the Pylons Project. .. index:: single: pyramid and other frameworks single: Zope single: Pylons single: Django single: MVC :app:`Pyramid` and Other Web Frameworks ------------------------------------------ The first release of Pyramid's predecessor (named :mod:`repoze.bfg`) was made in July of 2008. At the end of 2010, we changed the name of :mod:`repoze.bfg` to :app:`Pyramid`. It was merged into the Pylons project as :app:`Pyramid` in November of that year. :app:`Pyramid` was inspired by :term:`Zope`, :term:`Pylons` (version 1.0), and :term:`Django`. As a result, :app:`Pyramid` borrows several concepts and features from each, combining them into a unique web framework. Many features of :app:`Pyramid` trace their origins back to :term:`Zope`. Like Zope applications, :app:`Pyramid` applications can be easily extended. If you obey certain constraints, the application you produce can be reused, modified, re-integrated, or extended by third-party developers without forking the original application. The concepts of :term:`traversal` and declarative security in :app:`Pyramid` were pioneered first in Zope. The :app:`Pyramid` concept of :term:`URL dispatch` is inspired by the :term:`Routes` system used by :term:`Pylons` version 1.0. Like Pylons version 1.0, :app:`Pyramid` is mostly policy-free. It makes no assertions about which database you should use. Pyramid no longer has built-in templating facilities as of version 1.5a2, but instead officially supports bindings for templating languages, including Chameleon, Jinja2, and Mako. In essence, it only supplies a mechanism to map URLs to :term:`view` code, along with a set of conventions for calling those views. You are free to use third-party components that fit your needs in your applications. The concept of :term:`view` is used by :app:`Pyramid` mostly as it would be by Django. :app:`Pyramid` has a documentation culture more like Django's than like Zope's. Like :term:`Pylons` version 1.0, but unlike :term:`Zope`, a :app:`Pyramid` application developer may use completely imperative code to perform common framework configuration tasks such as adding a view or a route. In Zope, :term:`ZCML` is typically required for similar purposes. In :term:`Grok`, a Zope-based web framework, :term:`decorator` objects and class-level declarations are used for this purpose. Out of the box, Pyramid supports imperative and decorator-based configuration. :term:`ZCML` may be used via an add-on package named ``pyramid_zcml``. Also unlike :term:`Zope` and other "full-stack" frameworks such as :term:`Django`, :app:`Pyramid` makes no assumptions about which persistence mechanisms you should use to build an application. Zope applications are typically reliant on :term:`ZODB`. :app:`Pyramid` allows you to build :term:`ZODB` applications, but it has no reliance on the ZODB software. Likewise, :term:`Django` tends to assume that you want to store your application's data in a relational database. :app:`Pyramid` makes no such assumption, allowing you to use a relational database, and neither encouraging nor discouraging the decision. Other Python web frameworks advertise themselves as members of a class of web frameworks named `model-view-controller `_ frameworks. Insofar as this term has been claimed to represent a class of web frameworks, :app:`Pyramid` also generally fits into this class. .. sidebar:: You Say :app:`Pyramid` is MVC, but Where's the Controller? The :app:`Pyramid` authors believe that the MVC pattern just doesn't really fit the web very well. In a :app:`Pyramid` application, there is a resource tree which represents the site structure, and views which tend to present the data stored in the resource tree and a user-defined "domain model". However, no facility provided *by the framework* actually necessarily maps to the concept of a "controller" or "model". So if you had to give it some acronym, I guess you'd say :app:`Pyramid` is actually an "RV" framework rather than an "MVC" framework. "MVC", however, is close enough as a general classification moniker for purposes of comparison with other web frameworks. pyramid-1.6/docs/narr/introspector.rst0000644000076500000240000004111012621241570020726 0ustar michaelstaff00000000000000.. index:: single: introspection single: introspector .. _using_introspection: Pyramid Configuration Introspection =================================== .. versionadded:: 1.3 When Pyramid starts up, each call to a :term:`configuration directive` causes one or more :term:`introspectable` objects to be registered with an :term:`introspector`. The introspector can be queried by application code to obtain information about the configuration of the running application. This feature is useful for debug toolbars, command-line scripts which show some aspect of configuration, and for runtime reporting of startup-time configuration settings. Using the Introspector ---------------------- Here's an example of using Pyramid's introspector from within a view callable: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='bar') def show_current_route_pattern(request): introspector = request.registry.introspector route_name = request.matched_route.name route_intr = introspector.get('routes', route_name) return Response(str(route_intr['pattern'])) This view will return a response that contains the "pattern" argument provided to the ``add_route`` method of the route which matched when the view was called. It uses the :meth:`pyramid.interfaces.IIntrospector.get` method to return an introspectable in the category ``routes`` with a :term:`discriminator` equal to the matched route name. It then uses the returned introspectable to obtain a "pattern" value. The introspectable returned by the query methods of the introspector has methods and attributes described by :class:`pyramid.interfaces.IIntrospectable`. In particular, the :meth:`~pyramid.interfaces.IIntrospector.get`, :meth:`~pyramid.interfaces.IIntrospector.get_category`, :meth:`~pyramid.interfaces.IIntrospector.categories`, :meth:`~pyramid.interfaces.IIntrospector.categorized`, and :meth:`~pyramid.interfaces.IIntrospector.related` methods of an introspector can be used to query for introspectables. Introspectable Objects ---------------------- Introspectable objects are returned from query methods of an introspector. Each introspectable object implements the attributes and methods documented at :class:`pyramid.interfaces.IIntrospectable`. The important attributes shared by all introspectables are the following: ``title`` A human-readable text title describing the introspectable ``category_name`` A text category name describing the introspection category to which this introspectable belongs. It is often a plural if there are expected to be more than one introspectable registered within the category. ``discriminator`` A hashable object representing the unique value of this introspectable within its category. ``discriminator_hash`` The integer hash of the discriminator (useful in HTML links). ``type_name`` The text name of a subtype within this introspectable's category. If there is only one type name in this introspectable's category, this value will often be a singular version of the category name but it can be an arbitrary value. ``action_info`` An object describing the directive call site which caused this introspectable to be registered. It contains attributes described in :class:`pyramid.interfaces.IActionInfo`. Besides having the attributes described above, an introspectable is a dictionary-like object. An introspectable can be queried for data values via its ``__getitem__``, ``get``, ``keys``, ``values``, or ``items`` methods. For example: .. code-block:: python :linenos: route_intr = introspector.get('routes', 'edit_user') pattern = route_intr['pattern'] Pyramid Introspection Categories -------------------------------- The list of concrete introspection categories provided by built-in Pyramid configuration directives follows. Add-on packages may supply other introspectables in categories not described here. ``subscribers`` Each introspectable in the ``subscribers`` category represents a call to :meth:`pyramid.config.Configurator.add_subscriber` (or the decorator equivalent). Each will have the following data. ``subscriber`` The subscriber callable object (the resolution of the ``subscriber`` argument passed to ``add_subscriber``). ``interfaces`` A sequence of interfaces (or classes) that are subscribed to (the resolution of the ``ifaces`` argument passed to ``add_subscriber``). ``derived_subscriber`` A wrapper around the subscriber used internally by the system so it can call it with more than one argument if your original subscriber accepts only one. ``predicates`` The predicate objects created as the result of passing predicate arguments to ``add_subscriber``. ``derived_predicates`` Wrappers around the predicate objects created as the result of passing predicate arguments to ``add_subscriber`` (to be used when predicates take only one value but must be passed more than one). ``response adapters`` Each introspectable in the ``response adapters`` category represents a call to :meth:`pyramid.config.Configurator.add_response_adapter` (or a decorator equivalent). Each will have the following data. ``adapter`` The adapter object (the resolved ``adapter`` argument to ``add_response_adapter``). ``type`` The resolved ``type_or_iface`` argument passed to ``add_response_adapter``. ``root factories`` Each introspectable in the ``root factories`` category represents a call to :meth:`pyramid.config.Configurator.set_root_factory` (or the Configurator constructor equivalent) *or* a ``factory`` argument passed to :meth:`pyramid.config.Configurator.add_route`. Each will have the following data. ``factory`` The factory object (the resolved ``factory`` argument to ``set_root_factory``). ``route_name`` The name of the route which will use this factory. If this is the *default* root factory (if it's registered during a call to ``set_root_factory``), this value will be ``None``. ``session factory`` Only one introspectable will exist in the ``session factory`` category. It represents a call to :meth:`pyramid.config.Configurator.set_session_factory` (or the Configurator constructor equivalent). It will have the following data. ``factory`` The factory object (the resolved ``factory`` argument to ``set_session_factory``). ``request factory`` Only one introspectable will exist in the ``request factory`` category. It represents a call to :meth:`pyramid.config.Configurator.set_request_factory` (or the Configurator constructor equivalent). It will have the following data. ``factory`` The factory object (the resolved ``factory`` argument to ``set_request_factory``). ``locale negotiator`` Only one introspectable will exist in the ``locale negotiator`` category. It represents a call to :meth:`pyramid.config.Configurator.set_locale_negotiator` (or the Configurator constructor equivalent). It will have the following data. ``negotiator`` The factory object (the resolved ``negotiator`` argument to ``set_locale_negotiator``). ``renderer factories`` Each introspectable in the ``renderer factories`` category represents a call to :meth:`pyramid.config.Configurator.add_renderer` (or the Configurator constructor equivalent). Each will have the following data. ``name`` The name of the renderer (the value of the ``name`` argument to ``add_renderer``). ``factory`` The factory object (the resolved ``factory`` argument to ``add_renderer``). ``routes`` Each introspectable in the ``routes`` category represents a call to :meth:`pyramid.config.Configurator.add_route`. Each will have the following data. ``name`` The ``name`` argument passed to ``add_route``. ``pattern`` The ``pattern`` argument passed to ``add_route``. ``factory`` The (resolved) ``factory`` argument passed to ``add_route``. ``xhr`` The ``xhr`` argument passed to ``add_route``. ``request_method`` The ``request_method`` argument passed to ``add_route``. ``request_methods`` A sequence of request method names implied by the ``request_method`` argument passed to ``add_route`` or the value ``None`` if a ``request_method`` argument was not supplied. ``path_info`` The ``path_info`` argument passed to ``add_route``. ``request_param`` The ``request_param`` argument passed to ``add_route``. ``header`` The ``header`` argument passed to ``add_route``. ``accept`` The ``accept`` argument passed to ``add_route``. ``traverse`` The ``traverse`` argument passed to ``add_route``. ``custom_predicates`` The ``custom_predicates`` argument passed to ``add_route``. ``pregenerator`` The ``pregenerator`` argument passed to ``add_route``. ``static`` The ``static`` argument passed to ``add_route``. ``use_global_views`` The ``use_global_views`` argument passed to ``add_route``. ``object`` The :class:`pyramid.interfaces.IRoute` object that is used to perform matching and generation for this route. ``authentication policy`` There will be one and only one introspectable in the ``authentication policy`` category. It represents a call to the :meth:`pyramid.config.Configurator.set_authentication_policy` method (or its Configurator constructor equivalent). It will have the following data. ``policy`` The policy object (the resolved ``policy`` argument to ``set_authentication_policy``). ``authorization policy`` There will be one and only one introspectable in the ``authorization policy`` category. It represents a call to the :meth:`pyramid.config.Configurator.set_authorization_policy` method (or its Configurator constructor equivalent). It will have the following data. ``policy`` The policy object (the resolved ``policy`` argument to ``set_authorization_policy``). ``default permission`` There will be one and only one introspectable in the ``default permission`` category. It represents a call to the :meth:`pyramid.config.Configurator.set_default_permission` method (or its Configurator constructor equivalent). It will have the following data. ``value`` The permission name passed to ``set_default_permission``. ``views`` Each introspectable in the ``views`` category represents a call to :meth:`pyramid.config.Configurator.add_view`. Each will have the following data. ``name`` The ``name`` argument passed to ``add_view``. ``context`` The (resolved) ``context`` argument passed to ``add_view``. ``containment`` The (resolved) ``containment`` argument passed to ``add_view``. ``request_param`` The ``request_param`` argument passed to ``add_view``. ``request_methods`` A sequence of request method names implied by the ``request_method`` argument passed to ``add_view`` or the value ``None`` if a ``request_method`` argument was not supplied. ``route_name`` The ``route_name`` argument passed to ``add_view``. ``attr`` The ``attr`` argument passed to ``add_view``. ``xhr`` The ``xhr`` argument passed to ``add_view``. ``accept`` The ``accept`` argument passed to ``add_view``. ``header`` The ``header`` argument passed to ``add_view``. ``path_info`` The ``path_info`` argument passed to ``add_view``. ``match_param`` The ``match_param`` argument passed to ``add_view``. ``csrf_token`` The ``csrf_token`` argument passed to ``add_view``. ``callable`` The (resolved) ``view`` argument passed to ``add_view``. Represents the "raw" view callable. ``derived_callable`` The view callable derived from the ``view`` argument passed to ``add_view``. Represents the view callable which Pyramid itself calls (wrapped in security and other wrappers). ``mapper`` The (resolved) ``mapper`` argument passed to ``add_view``. ``decorator`` The (resolved) ``decorator`` argument passed to ``add_view``. ``permissions`` Each introspectable in the ``permissions`` category represents a call to :meth:`pyramid.config.Configurator.add_view` that has an explicit ``permission`` argument *or* a call to :meth:`pyramid.config.Configurator.set_default_permission`. Each will have the following data. ``value`` The permission name passed to ``add_view`` or ``set_default_permission``. ``templates`` Each introspectable in the ``templates`` category represents a call to :meth:`pyramid.config.Configurator.add_view` that has a ``renderer`` argument which points to a template. Each will have the following data. ``name`` The renderer's name (a string). ``type`` The renderer's type (a string). ``renderer`` The :class:`pyramid.interfaces.IRendererInfo` object which represents this template's renderer. ``view mappers`` Each introspectable in the ``view mappers`` category represents a call to :meth:`pyramid.config.Configurator.add_view` that has an explicit ``mapper`` argument *or* a call to :meth:`pyramid.config.Configurator.set_view_mapper`. Each will have the following data. ``mapper`` The (resolved) ``mapper`` argument passed to ``add_view`` or ``set_view_mapper``. ``asset overrides`` Each introspectable in the ``asset overrides`` category represents a call to :meth:`pyramid.config.Configurator.override_asset`. Each will have the following data. ``to_override`` The ``to_override`` argument (an asset spec) passed to ``override_asset``. ``override_with`` The ``override_with`` argument (an asset spec) passed to ``override_asset``. ``translation directories`` Each introspectable in the ``translation directories`` category represents an individual element in a ``specs`` argument passed to :meth:`pyramid.config.Configurator.add_translation_dirs`. Each will have the following data. ``directory`` The absolute path of the translation directory. ``spec`` The asset specification passed to ``add_translation_dirs``. ``tweens`` Each introspectable in the ``tweens`` category represents a call to :meth:`pyramid.config.Configurator.add_tween`. Each will have the following data. ``name`` The dotted name to the tween factory as a string (passed as the ``tween_factory`` argument to ``add_tween``). ``factory`` The (resolved) tween factory object. ``type`` ``implicit`` or ``explicit`` as a string. ``under`` The ``under`` argument passed to ``add_tween`` (a string). ``over`` The ``over`` argument passed to ``add_tween`` (a string). ``static views`` Each introspectable in the ``static views`` category represents a call to :meth:`pyramid.config.Configurator.add_static_view`. Each will have the following data. ``name`` The ``name`` argument provided to ``add_static_view``. ``spec`` A normalized version of the ``spec`` argument provided to ``add_static_view``. ``traversers`` Each introspectable in the ``traversers`` category represents a call to :meth:`pyramid.config.Configurator.add_traverser`. Each will have the following data. ``iface`` The (resolved) interface or class object that represents the return value of a root factory for which this traverser will be used. ``adapter`` The (resolved) traverser class. ``resource url adapters`` Each introspectable in the ``resource url adapters`` category represents a call to :meth:`pyramid.config.Configurator.add_resource_url_adapter`. Each will have the following data. ``adapter`` The (resolved) resource URL adapter class. ``resource_iface`` The (resolved) interface or class object that represents the resource interface for which this URL adapter is registered. ``request_iface`` The (resolved) interface or class object that represents the request interface for which this URL adapter is registered. Introspection in the Toolbar ---------------------------- The Pyramid debug toolbar (part of the ``pyramid_debugtoolbar`` package) provides a canned view of all registered introspectables and their relationships. It is currently under the "Global" tab in the main navigation, and it looks something like this: .. image:: tb_introspector.png Disabling Introspection ----------------------- You can disable Pyramid introspection by passing the flag ``introspection=False`` to the :term:`Configurator` constructor in your application setup: .. code-block:: python from pyramid.config import Configurator config = Configurator(..., introspection=False) When ``introspection`` is ``False``, all introspectables generated by configuration directives are thrown away. pyramid-1.6/docs/narr/logging.rst0000644000076500000240000003221212610765056017634 0ustar michaelstaff00000000000000.. _logging_chapter: Logging ======= :app:`Pyramid` allows you to make use of the Python standard library :mod:`logging` module. This chapter describes how to configure logging and how to send log messages to loggers that you've configured. .. warning:: This chapter assumes you've used a :term:`scaffold` to create a project which contains ``development.ini`` and ``production.ini`` files which help configure logging. All of the scaffolds which ship with :app:`Pyramid` do this. If you're not using a scaffold, or if you've used 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 --------------------- A :app:`Pyramid` project created from a :term:`scaffold` is configured to allow you to send messages to :mod:`Python standard library logging package ` loggers from within your application. In particular, the :term:`PasteDeploy` ``development.ini`` and ``production.ini`` files created when you use a scaffold include a basic configuration for the Python :mod:`logging` package. PasteDeploy ``.ini`` files use the Python standard library :mod:`ConfigParser format `. This is the same format used as the Python :ref:`logging module's Configuration file format `. The application-related and logging-related sections in the configuration file can coexist peacefully, and the logging-related sections in the file are used from when you run ``pserve``. The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` function, a thin wrapper around the :func:`logging.config.fileConfig` using the specified ``.ini`` file, if it contains a ``[loggers]`` section (all of the scaffold-generated ``.ini`` files do). ``setup_logging`` reads the logging configuration from the ini file upon which ``pserve`` was invoked. Default logging configuration is provided in both the default ``development.ini`` and the ``production.ini`` file. The logging configuration in the ``development.ini`` file is as follows: .. code-block:: ini :linenos: # Begin logging configuration [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_{{package_logger}}] level = DEBUG handlers = qualname = {{package}} [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 The ``production.ini`` file uses the ``WARN`` level in its logger configuration, but it is otherwise identical. The name ``{{package_logger}}`` above will be replaced with the name of your project's :term:`package`, which is derived from the name you provide to your project. For instance, if you do: .. code-block:: text :linenos: pcreate -s starter MyApp The logging configuration will literally be: .. code-block:: ini :linenos: # Begin logging configuration [loggers] keys = root, myapp [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_myapp] level = DEBUG handlers = qualname = myapp [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 In this logging configuration: - a logger named ``root`` is created that logs messages at a level above or equal to the ``INFO`` level to stderr, with the following format: .. code-block:: text 2007-08-17 15:04:08,704 INFO [packagename] Loading resource, id: 86 - a logger named ``myapp`` is configured that logs messages sent at a level above or equal to ``DEBUG`` to stderr in the same format as the root logger. The ``root`` logger will be used by all applications in the Pyramid process that ask for a logger (via ``logging.getLogger``) that has a name which begins with anything except your project's package name (e.g., ``myapp``). The logger with the same name as your package name is reserved for your own usage in your :app:`Pyramid` application. Its existence means that you can log to a known logging location from any :app:`Pyramid` application generated via a scaffold. :app:`Pyramid` and many other libraries (such as Beaker, SQLAlchemy, Paste) log a number of messages to the root logger for debugging purposes. Switching the root logger level to ``DEBUG`` reveals them: .. code-block:: ini [logger_root] #level = INFO level = DEBUG handlers = console Some scaffolds configure additional loggers for additional subsystems they use (such as SQLALchemy). Take a look at the ``production.ini`` and ``development.ini`` files rendered when you create a project from a scaffold. Sending Logging Messages ------------------------ Python's special ``__name__`` variable refers to the current module's fully qualified name. From any module in a package named ``myapp``, the ``__name__`` builtin variable will always be something like ``myapp``, or ``myapp.subpackage`` or ``myapp.package.subpackage`` if your project is named ``myapp``. Sending a message to this logger will send it to the ``myapp`` logger. To log messages to the package-specific logger configured in your ``.ini`` file, simply create a logger object using the ``__name__`` builtin and call methods on it. .. code-block:: python :linenos: import logging log = logging.getLogger(__name__) def myview(request): content_type = 'text/plain' content = 'Hello World!' log.debug('Returning: %s (content-type: %s)', content, content_type) request.response.content_type = content_type return request.response This will result in the following printed to the console, on ``stderr``: .. code-block:: text 16:20:20,440 DEBUG [myapp.views] Returning: Hello World! (content-type: text/plain) Filtering log messages ---------------------- Often there's too much log output to sift through, such as when switching the root logger's level to ``DEBUG``. For example, you're diagnosing database connection issues in your application and only want to see SQLAlchemy's ``DEBUG`` messages in relation to database connection pooling. You can leave the root logger's level at the less verbose ``INFO`` level and set that particular SQLAlchemy logger to ``DEBUG`` on its own, apart from the root logger: .. code-block:: ini [logger_sqlalchemy.pool] level = DEBUG handlers = qualname = sqlalchemy.pool then add it to the list of loggers: .. code-block:: ini [loggers] keys = root, myapp, sqlalchemy.pool No handlers need to be configured for this logger as by default non-root loggers will propagate their log records up to their parent logger's handlers. The root logger is the top level parent of all loggers. This technique is used in the default ``development.ini``. The root logger's level is set to ``INFO``, whereas the application's log level is set to ``DEBUG``: .. code-block:: ini # Begin logging configuration [loggers] keys = root, myapp [logger_myapp] level = DEBUG handlers = 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``, ``myapp.models``, and all your app's modules' loggers by default have an effective level of ``DEBUG`` too. For more advanced filtering, the logging module provides a :class:`logging.Filter` object; however it cannot be used directly from the configuration file. Advanced Configuration ---------------------- To capture log output to a separate file, use :class:`logging.FileHandler` (or :class:`logging.handlers.RotatingFileHandler`): .. code-block:: ini [handler_filelog] class = FileHandler args = ('%(here)s/myapp.log','a') level = INFO formatter = generic Before it's recognized, it needs to be added to the list of handlers: .. code-block:: ini [handlers] keys = console, myapp, filelog and finally utilized by a logger. .. code-block:: ini [logger_root] level = INFO handlers = console, filelog These final three lines of configuration direct all of the root logger's output to the ``myapp.log`` as well as the console. Logging Exceptions ------------------ To log or email exceptions generated by your :app:`Pyramid` application, use the :term:`pyramid_exclog` package. Details about its configuration are in its `documentation `_. .. 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 ---------------------------------------- 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 `_ :term:`middleware`. TransLogger produces logs in the `Apache Combined Log Format `_. But TransLogger does not write to files; the Python logging system must be configured to do this. The Python :class:`logging.FileHandler` logging handler can be used alongside TransLogger to create an ``access.log`` file similar to Apache's. Like any standard :term:`middleware` with a Paste entry point, TransLogger can be configured to wrap your application using ``.ini`` file syntax. First rename your Pyramid ``.ini`` file's ``[app:main]`` section to ``[app:mypyramidapp]``, then add a ``[filter:translogger]`` section, then use a ``[pipeline:main]`` section file to form a WSGI pipeline with both the translogger and your application in it. For instance, change from this: .. code-block:: ini [app:main] use = egg:MyProject To this: .. code-block:: ini [app:mypyramidapp] use = egg:MyProject [filter:translogger] use = egg:Paste#translogger setup_console_handler = False [pipeline:main] pipeline = translogger mypyramidapp Using PasteDeploy this way to form and serve a pipeline is equivalent to wrapping your app in a TransLogger instance via the bottom of the ``main`` function of your project's ``__init__`` file: .. code-block:: python ... app = config.make_wsgi_app() from paste.translogger import TransLogger app = TransLogger(app, setup_console_handler=False) return app .. 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 its output to the console when we request a page: .. code-block:: text 00:50:53,694 INFO [myapp.views] Returning: Hello World! (content-type: text/plain) 00:50:53,695 INFO [wsgi] 192.168.1.111 - - [11/Aug/2011:20:09:33 -0700] "GET /hello HTTP/1.1" 404 - "-" "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 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 # Begin logging configuration [loggers] keys = root, myapp, wsgi [handlers] keys = console, accesslog [logger_wsgi] level = INFO handlers = accesslog qualname = wsgi propagate = 0 [handler_accesslog] class = FileHandler args = ('%(here)s/access.log','a') level = INFO formatter = generic As mentioned above, non-root loggers by default propagate their log records to the root logger's handlers (currently the console handler). Setting ``propagate`` to ``0`` (``False``) here disables this; so the ``wsgi`` logger 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 including the following in your configuration file: .. code-block:: ini [formatters] keys = generic, accesslog [formatter_accesslog] format = %(message)s Finally alter the existing configuration to wire this new ``accesslog`` formatter into the FileHandler: .. code-block:: ini [handler_accesslog] class = FileHandler args = ('%(here)s/access.log','a') level = INFO formatter = accesslog pyramid-1.6/docs/narr/muchadoabouttraversal.rst0000644000076500000240000003772612621241570022614 0ustar michaelstaff00000000000000.. _much_ado_about_traversal_chapter: ======================== Much Ado About Traversal ======================== (Or, why you should care about it.) .. note:: This chapter was adapted, with permission, from a blog post by `Rob Miller `_, originally published at http://blog.nonsequitarian.org/2010/much-ado-about-traversal/. Traversal is an alternative to :term:`URL dispatch` which allows :app:`Pyramid` applications to map URLs to code. .. note:: Ex-Zope users who are already familiar with traversal and view lookup conceptually may want to skip directly to the :ref:`traversal_chapter` chapter, which discusses technical details. This chapter is mostly aimed at people who have previous :term:`Pylons` experience or experience in another framework which does not provide traversal, and need an introduction to the "why" of traversal. Some folks who have been using Pylons and its Routes-based URL matching for a long time are being exposed for the first time, via :app:`Pyramid`, to new ideas such as ":term:`traversal`" and ":term:`view lookup`" as a way to route incoming HTTP requests to callable code. Some of the same folks believe that traversal is hard to understand. Others question its usefulness; URL matching has worked for them so far, so why should they even consider dealing with another approach, one which doesn't fit their brain and which doesn't provide any immediately obvious value? You can be assured that if you don't want to understand traversal, you don't have to. You can happily build :app:`Pyramid` applications with only :term:`URL dispatch`. However, there are some straightforward, real-world use cases that are much more easily served by a traversal-based approach than by a pattern-matching mechanism. Even if you haven't yet hit one of these use cases yourself, understanding these new ideas is worth the effort for any web developer so you know when you might want to use them. :term:`Traversal` is actually a straightforward metaphor easily comprehended by anyone who's ever used a run-of-the-mill file system with folders and files. .. index:: single: URL dispatch URL Dispatch ------------ Let's step back and consider the problem we're trying to solve. An HTTP request for a particular path has been routed to our web application. The requested path will possibly invoke a specific :term:`view callable` function defined somewhere in our app. We're trying to determine *which* callable function, if any, should be invoked for a given requested URL. Many systems, including Pyramid, offer a simple solution. They offer the concept of "URL matching". URL matching approaches this problem by parsing the URL path and comparing the results to a set of registered "patterns", defined by a set of regular expressions or some other URL path templating syntax. Each pattern is mapped to a callable function somewhere; if the request path matches a specific pattern, the associated function is called. If the request path matches more than one pattern, some conflict resolution scheme is used, usually a simple order precedence so that the first match will take priority over any subsequent matches. If a request path doesn't match any of the defined patterns, a "404 Not Found" response is returned. In Pyramid, we offer an implementation of URL matching which we call :term:`URL dispatch`. Using :app:`Pyramid` syntax, we might have a match pattern such as ``/{userid}/photos/{photoid}``, mapped to a ``photo_view()`` function defined somewhere in our code. Then a request for a path such as ``/joeschmoe/photos/photo1`` would be a match, and the ``photo_view()`` function would be invoked to handle the request. Similarly, ``/{userid}/blog/{year}/{month}/{postid}`` might map to a ``blog_post_view()`` function, so ``/joeschmoe/blog/2010/12/urlmatching`` would trigger the function, which presumably would know how to find and render the ``urlmatching`` blog post. Historical Refresher -------------------- Now that we've refreshed our understanding of :term:`URL dispatch`, we'll dig in to the idea of traversal. Before we do, though, let's take a trip down memory lane. If you've been doing web work for a while, you may remember a time when we didn't have fancy web frameworks like :term:`Pylons` and :app:`Pyramid`. Instead, we had general purpose HTTP servers that primarily served files off of a file system. The "root" of a given site mapped to a particular folder somewhere on the file system. Each segment of the request URL path represented a subdirectory. The final path segment would be either a directory or a file, and once the server found the right file it would package it up in an HTTP response and send it back to the client. So serving up a request for ``/joeschmoe/photos/photo1`` literally meant that there was a ``joeschmoe`` folder somewhere, which contained a ``photos`` folder, which in turn contained a ``photo1`` file. If at any point along the way we find that there is not a folder or file matching the requested path, we return a 404 response. As the web grew more dynamic, however, a little bit of extra complexity was added. Technologies such as CGI and HTTP server modules were developed. Files were still looked up on the file system, but if the file ended with (for example) ``.cgi`` or ``.php``, or if it lived in a special folder, instead of simply sending the file to the client the server would read the file, execute it using an interpreter of some sort, and then send the output from this process to the client as the final result. The server configuration specified which files would trigger some dynamic code, with the default case being to just serve the static file. .. index:: single: traversal Traversal (a.k.a., Resource Location) ------------------------------------- Believe it or not, if you understand how serving files from a file system works, you understand traversal. And if you understand that a server might do something different based on what type of file a given request specifies, then you understand view lookup. The major difference between file system lookup and traversal is that a file system lookup steps through nested directories and files in a file system tree, while traversal steps through nested dictionary-type objects in a :term:`resource tree`. Let's take a detailed look at one of our example paths, so we can see what I mean. The path ``/joeschmoe/photos/photo1``, has four segments: ``/``, ``joeschmoe``, ``photos`` and ``photo1``. With file system lookup we might have a root folder (``/``) containing a nested folder (``joeschmoe``), which contains another nested folder (``photos``), which finally contains a JPG file (``photo1``). With traversal, we instead have a dictionary-like root object. Asking for the ``joeschmoe`` key gives us another dictionary-like object. Asking in turn for the ``photos`` key gives us yet another mapping object, which finally (hopefully) contains the resource that we're looking for within its values, referenced by the ``photo1`` key. In pure Python terms, then, the traversal or "resource location" portion of satisfying the ``/joeschmoe/photos/photo1`` request will look something like this pseudocode:: get_root()['joeschmoe']['photos']['photo1'] ``get_root()`` is some function that returns a root traversal :term:`resource`. If all of the specified keys exist, then the returned object will be the resource that is being requested, analogous to the JPG file that was retrieved in the file system example. If a :exc:`KeyError` is generated anywhere along the way, :app:`Pyramid` will return 404. (This isn't precisely true, as you'll see when we learn about view lookup below, but the basic idea holds.) .. index:: single: resource What Is a "Resource"? --------------------- "Files on a file system I understand", you might say. "But what are these nested dictionary things? Where do these objects, these 'resources', live? What *are* they?" Since :app:`Pyramid` is not a highly opinionated framework, it makes no restriction on how a :term:`resource` is implemented; a developer can implement them as they wish. One common pattern used is to persist all of the resources, including the root, in a database as a graph. The root object is a dictionary-like object. Dictionary-like objects in Python supply a ``__getitem__`` method which is called when key lookup is done. Under the hood, when ``adict`` is a dictionary-like object, Python translates ``adict['a']`` to ``adict.__getitem__('a')``. Try doing this in a Python interpreter prompt if you don't believe us: >>> adict = {} >>> adict['a'] = 1 >>> adict['a'] 1 >>> adict.__getitem__('a') 1 The dictionary-like root object stores the ids of all of its subresources as keys, and provides a ``__getitem__`` implementation that fetches them. So ``get_root()`` fetches the unique root object, while ``get_root()['joeschmoe']`` returns a different object, also stored in the database, which in turn has its own subresources and ``__getitem__`` implementation, and so on. These resources might be persisted in a relational database, one of the many "NoSQL" solutions that are becoming popular these days, or anywhere else; it doesn't matter. As long as the returned objects provide the dictionary-like API (i.e., as long as they have an appropriately implemented ``__getitem__`` method), then traversal will work. In fact, you don't need a "database" at all. You could use plain dictionaries, with your site's URL structure hard-coded directly in the Python source. Or you could trivially implement a set of objects with ``__getitem__`` methods that search for files in specific directories, and thus precisely recreate the traditional mechanism of having the URL path mapped directly to a folder structure on the file system. Traversal is in fact a superset of file system lookup. .. note:: See the chapter entitled :ref:`resources_chapter` for a more technical overview of resources. .. index:: single: view lookup View Lookup ----------- At this point we're nearly there. We've covered traversal, which is the process by which a specific resource is retrieved according to a specific URL path. But what is "view lookup"? The need for view lookup is simple: there is more than one possible action that you might want to take after finding a :term:`resource`. With our photo example, for instance, you might want to view the photo in a page, but you might also want to provide a way for the user to edit the photo and any associated metadata. We'll call the former the ``view`` view, and the latter will be the ``edit`` view. (Original, I know.) :app:`Pyramid` has a centralized view :term:`application registry` where named views can be associated with specific resource types. So in our example, we'll assume that we've registered ``view`` and ``edit`` views for photo objects, and that we've specified the ``view`` view as the default, so that ``/joeschmoe/photos/photo1/view`` and ``/joeschmoe/photos/photo1`` are equivalent. The edit view would sensibly be provided by a request for ``/joeschmoe/photos/photo1/edit``. Hopefully it's clear that the first portion of the edit view's URL path is going to resolve to the same resource as the non-edit version, specifically the resource returned by ``get_root()['joeschmoe']['photos']['photo1']``. But traversal ends there; the ``photo1`` resource doesn't have an ``edit`` key. In fact, it might not even be a dictionary-like object, in which case ``photo1['edit']`` would be meaningless. When the :app:`Pyramid` resource location has been resolved to a *leaf* resource, but the entire request path has not yet been expended, the *very next* path segment is treated as a :term:`view name`. The registry is then checked to see if a view of the given name has been specified for a resource of the given type. If so, the view callable is invoked, with the resource passed in as the related ``context`` object (also available as ``request.context``). If a view callable could not be found, :app:`Pyramid` will return a "404 Not Found" response. You might conceptualize a request for ``/joeschmoe/photos/photo1/edit`` as ultimately converted into the following piece of Pythonic pseudocode:: context = get_root()['joeschmoe']['photos']['photo1'] view_callable = get_view(context, 'edit') request.context = context view_callable(request) The ``get_root`` and ``get_view`` functions don't really exist. Internally, :app:`Pyramid` does something more complicated. But the example above is a reasonable approximation of the view lookup algorithm in pseudocode. Use Cases --------- Why should we care about traversal? URL matching is easier to explain, and it's good enough, right? In some cases, yes, but certainly not in all cases. So far we've had very structured URLs, where our paths have had a specific, small number of pieces, like this:: /{userid}/{typename}/{objectid}[/{view_name}] In all of the examples thus far, we've hard coded the typename value, assuming that we'd know at development time what names were going to be used ("photos", "blog", etc.). But what if we don't know what these names will be? Or, worse yet, what if we don't know *anything* about the structure of the URLs inside a user's folder? We could be writing a CMS where we want the end user to be able to arbitrarily add content and other folders inside his folder. He might decide to nest folders dozens of layers deep. How will you construct matching patterns that could account for every possible combination of paths that might develop? It might be possible, but it certainly won't be easy. The matching patterns are going to become complex quickly as you try to handle all of the edge cases. With traversal, however, it's straightforward. Twenty layers of nesting would be no problem. :app:`Pyramid` will happily call ``__getitem__`` as many times as it needs to, until it runs out of path segments or until a resource raises a :exc:`KeyError`. Each resource only needs to know how to fetch its immediate children, and the traversal algorithm takes care of the rest. Also, since the structure of the resource tree can live in the database and not in the code, it's simple to let users modify the tree at runtime to set up their own personalized "directory" structures. Another use case in which traversal shines is when there is a need to support a context-dependent security policy. One example might be a document management infrastructure for a large corporation, where members of different departments have varying access levels to the various other departments' files. Reasonably, even specific files might need to be made available to specific individuals. Traversal does well here if your resources actually represent the data objects related to your documents, because the idea of a resource authorization is baked right into the code resolution and calling process. Resource objects can store ACLs, which can be inherited and/or overridden by the subresources. If each resource can thus generate a context-based ACL, then whenever view code is attempting to perform a sensitive action, it can check against that ACL to see whether the current user should be allowed to perform the action. In this way you achieve so called "instance based" or "row level" security which is considerably harder to model using a traditional tabular approach. :app:`Pyramid` actively supports such a scheme, and in fact if you register your views with guarded permissions and use an authorization policy, :app:`Pyramid` can check against a resource's ACL when deciding whether or not the view itself is available to the current user. In summary, there are entire classes of problems that are more easily served by traversal and view lookup than by :term:`URL dispatch`. If your problems don't require it, great, stick with :term:`URL dispatch`. But if you're using :app:`Pyramid` and you ever find that you *do* need to support one of these use cases, you'll be glad you have traversal in your toolkit. .. note:: It is even possible to mix and match :term:`traversal` with :term:`URL dispatch` in the same :app:`Pyramid` application. See the :ref:`hybrid_chapter` chapter for details. pyramid-1.6/docs/narr/MyProject/0000755000076500000240000000000012642137501017361 5ustar michaelstaff00000000000000pyramid-1.6/docs/narr/MyProject/CHANGES.txt0000644000076500000240000000003412234375161021172 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/narr/MyProject/development.ini0000644000076500000240000000211012642137120022373 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:MyProject pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, myproject [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_myproject] level = DEBUG handlers = qualname = myproject [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 pyramid-1.6/docs/narr/MyProject/MANIFEST.in0000644000076500000240000000020412234375161021116 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include myproject *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/narr/MyProject/myproject/0000755000076500000240000000000012642137501021375 5ustar michaelstaff00000000000000pyramid-1.6/docs/narr/MyProject/myproject/__init__.py0000644000076500000240000000057712520062551023514 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/narr/MyProject/myproject/static/0000755000076500000240000000000012642137501022664 5ustar michaelstaff00000000000000pyramid-1.6/docs/narr/MyProject/myproject/static/pyramid-16x16.png0000644000076500000240000000244712520062551025626 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/narr/MyProject/myproject/static/pyramid.png0000644000076500000240000003114512520062551025040 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/narr/MyProject/myproject/static/theme.css0000644000076500000240000000547212520062551024505 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a { color: #ffffff; } .starter-template .links ul li a:hover { text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/narr/MyProject/myproject/static/theme.min.css0000644000076500000240000000442512520062551025264 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/narr/MyProject/myproject/templates/0000755000076500000240000000000012642137501023373 5ustar michaelstaff00000000000000pyramid-1.6/docs/narr/MyProject/myproject/templates/mytemplate.pt0000644000076500000240000000565512642137120026131 0ustar michaelstaff00000000000000 Starter Template for The Pyramid Web Framework

Pyramid starter template

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/narr/MyProject/myproject/tests.py0000644000076500000240000000127612634676134023132 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'MyProject') 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) pyramid-1.6/docs/narr/MyProject/myproject/views.py0000644000076500000240000000024512517346416023115 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): return {'project': 'MyProject'} pyramid-1.6/docs/narr/MyProject/production.ini0000644000076500000240000000162712642137120022253 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:MyProject pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, myproject [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_myproject] level = WARN handlers = qualname = myproject [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 pyramid-1.6/docs/narr/MyProject/README.txt0000644000076500000240000000002112234375161021053 0ustar michaelstaff00000000000000MyProject README pyramid-1.6/docs/narr/MyProject/setup.py0000644000076500000240000000220112642137120021063 0ustar michaelstaff00000000000000"""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 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', ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=REQUIRES, tests_require=TESTS_REQUIRE, test_suite='myproject', entry_points="""\ [paste.app_factory] main = myproject:main """) pyramid-1.6/docs/narr/paste.rst0000644000076500000240000001117512610765056017327 0ustar michaelstaff00000000000000.. _paste_chapter: PasteDeploy Configuration Files =============================== Packages generated via a :term:`scaffold` make use of a system created by Ian Bicking named :term:`PasteDeploy`. PasteDeploy defines a way to declare :term:`WSGI` application configuration in an ``.ini`` file. Pyramid uses this configuration file format as input to its :term:`WSGI` server runner ``pserve``, as well as other commands such as ``pviews``, ``pshell``, ``proutes``, and ``ptweens``. PasteDeploy is not a particularly integral part of Pyramid. It's possible to create a Pyramid application which does not use PasteDeploy at all. We show a Pyramid application that doesn't use PasteDeploy in :ref:`firstapp_chapter`. However, all Pyramid scaffolds render PasteDeploy configuration files, to provide new developers with a standardized way of setting deployment values, and to provide new users with a standardized way of starting, stopping, and debugging an application. This chapter is not a replacement for documentation about PasteDeploy; it only contextualizes the use of PasteDeploy within Pyramid. For detailed documentation, see http://pythonpaste.org/deploy/. PasteDeploy ----------- :term:`PasteDeploy` is the system that Pyramid uses to allow :term:`deployment settings` to be specified using an ``.ini`` configuration file format. It also allows the ``pserve`` command to work. Its configuration format provides a convenient place to define application :term:`deployment settings` and WSGI server settings, and its server runner allows you to stop and start a Pyramid application easily. .. _pastedeploy_entry_points: Entry Points and PasteDeploy ``.ini`` Files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In the :ref:`project_narr` chapter, we breezed over the meaning of a configuration line in the ``deployment.ini`` file. This was the ``use = egg:MyProject`` line in the ``[app:main]`` section. We breezed over it because it's pretty confusing and "too much information" for an introduction to the system. We'll try to give it a bit of attention here. Let's see the config file again: .. literalinclude:: MyProject/development.ini :language: ini :linenos: The line in ``[app:main]`` above that says ``use = egg:MyProject`` is actually shorthand for a longer spelling: ``use = egg:MyProject#main``. The ``#main`` part is omitted for brevity, as ``#main`` is a default defined by PasteDeploy. ``egg:MyProject#main`` is a string which has meaning to PasteDeploy. It points at a :term:`setuptools` :term:`entry point` named ``main`` defined in the ``MyProject`` project. Take a look at the generated ``setup.py`` file for this project. .. literalinclude:: MyProject/setup.py :language: python :linenos: Note that ``entry_points`` is assigned a string which looks a lot like an ``.ini`` file. This string representation of an ``.ini`` file has a section named ``[paste.app_factory]``. Within this section, there is a key named ``main`` (the entry point name) which has a value ``myproject:main``. The *key* ``main`` is what our ``egg:MyProject#main`` value of the ``use`` section in our config file is pointing at, although it is actually shortened to ``egg:MyProject`` there. The value represents a :term:`dotted Python name` path, which refers to a callable in our ``myproject`` package's ``__init__.py`` module. The ``egg:`` prefix in ``egg:MyProject`` indicates that this is an entry point *URI* specifier, where the "scheme" is "egg". An "egg" is created when you run ``setup.py install`` or ``setup.py develop`` within your project. In English, this entry point can thus be referred to as a "PasteDeploy application factory in the ``MyProject`` project which has the entry point named ``main`` where the entry point refers to a ``main`` function in the ``mypackage`` module". Indeed, if you open up the ``__init__.py`` module generated within any scaffold-generated package, you'll see a ``main`` function. This is the function called by :term:`PasteDeploy` when the ``pserve`` command is invoked against our application. It accepts a global configuration object and *returns* an instance of our application. .. _defaults_section_of_pastedeploy_file: ``[DEFAULT]`` Section of a PasteDeploy ``.ini`` File ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can add a ``[DEFAULT]`` section to your PasteDeploy ``.ini`` file. Such a section should consist of global parameters that are shared by all the applications, servers, and :term:`middleware` defined within the configuration file. The values in a ``[DEFAULT]`` section will be passed to your application's ``main`` function as ``global_config`` (see the reference to the ``main`` function in :ref:`init_py`). pyramid-1.6/docs/narr/project-debug.png0000644000076500000240000061334012606630333020715 0ustar michaelstaff00000000000000‰PNG  IHDRk@œåàæ ÉiCCPICC ProfileH ­–wTSÙ‡÷½éˆ€”Ð;R¤K¯¤ 6BH(!„"ƒ#0¢¨ˆ`EGD cA,ØÁÞdPQŸƒ*ï̬·Þü÷ÎZûžïîó»ûž}ÊZ€ÞÊ•H2P€L±LìÇž“È&ýdPÁ F\^ŽÄ722þ±}¸ ˆbð†"Ö?Êþ÷€*_Ã@"±ád~/㣘mçI¤2\ æ7^$“(8cu)6AŒËœ:Î;œ<ÎØ·˜&&ÊÓ\ Ó¹\i*í&ægçòR±8´÷Û‰ù"1Ýc/žËÇ3°ÎÌÌRð:ŒÍ“ÿ'õoÌå&OÆärS'y<ìKìÇ¢IwñØËÿó‘™!ÇÖk¬éaOzNztÖ›bk–ÇãFO°PÀQìÙ˜_"ó‹š`‘Œ3ÁByHìËÓc}'8=+lR/Nž1áçåøck?3_?Á|A@àK³¢&õ9¹Ñ“þ|¡ÿ¬ M7T±ßcsãJ1ú 2‚'ÿ+‘ENÎSœ1k2—iФFóW¾2aLÈDv&8EÄ™`¡4dÒ/É;ÓcsÊ£&×A Ž\C>7`rm!„ 1ðARH†,Ȱ!Dì ØvËyØðÏ’,–ŠR…2¶/v+ÖlŽ˜gkÍv°³wÅShÞ±Æîºü—/»À­ÛOÅñf+T\#€ãO˜þò½?§'»yriè@eP-Ð#0pgð„PˆÀ2I€ÀÃòÉÄ2YKa%C)¬ƒMP ;`7샃pZàœ pºá<€^€—0`AÂ@˜ˆ¢˜ VˆâŠx!H8…$ IH*"FäÈRdRŠT ÕÈ.¤ù9ŽœA.!=È=¤DÞ"_PJGÕQ]Ô†º¢¾hƒÎGSÑl4-B×¢Uh-zmFÏ WÐ[h/úÆކcá p68Wœ?.—ˆKÁIqËq%¸J\-®׆ëÄÝÀõâ^á>ã‰x&ž·Á{àCð±x>¿_†¯ÆïÃ7ãÏáoàûðCøïA‡`Ep'ps©„E„bB%a/áá<áa€ðH$²ˆfDb1˜F\B,#n#6Û‰=Ä~â0‰DÒ"Y‘‘id}²9ˆœH“ É•äýäSäëägäŠ Å„âN‰ ð)‹)å”=”6Ê5Êe„ªJ5£zRc¨iÔ•Ô*j#õ<õ!õF3¤¹ÑfÓD´Zíí"­ö™®F·¤ûÓçÑåôµô:z;ýýƒÁ0eø02ÆZF=ã,ã1ã“SÉV‰£ÄWZ¡T£Ô¬t]éµ2EÙDÙWyr¾r¥òåkʯT(*¦*þ*\•å*5*ÇUî¨ «2UíU#T3UËT÷«^R}®FR3U Tã«©íV;«ÖÏÄ1˜þLss󤡦1]#N#O£Fã¤F/ Ç2eqX¬rÖaÖmÖ—)ºS|§¦¬™Ò8åú”šS5}4š%šMš·4¿h±µµÒµÖkµh=ÒÆk[jÏÖ^¤½]û¼ö«©êS=¦ò¦–L=<õ¾ªc©¥³Dg·ÎUa]=Ý`]‰îݳº¯ôXz>zizõNé ê3õ½ôEúõOë¿`k°}Ùì*ö9öŽAˆÜ`—A—Áˆ¡™a¬a¡a“á##ª‘«QŠÑF££!c}ã™ÆKŒï›PL\M„&›M:M>šš™Æ›®6m1}n¦iÆ1Ë7k0{hÎ0÷6Ï6¯5¿iA´pµH·ØfÑm‰Z:Y -k,¯Y¡VÎV"«mV=Ök7k±u­õº¯M®MƒMŸ-Ë6ܶжÅöõ4ãi‰ÓÖOëœöÝÎÉ.ÃnÝ{5ûPûBû6û·–<‡‡›Ž Ç ÇŽ­Žo¦[MLß>ý®Ói¦Ój§§oÎ.ÎRçFçAc—$—­.w\Õ]#]Ë\/ºÜüÜV¸pûìîì.s?ìþ§‡GºÇ~ç3Ìffì™ÑïièÉõÜåÙëÅöJòÚéÕëmàÍõ®õ~âcäÃ÷ÙëóÌ×Â7Í÷€ïk?;?©ß1¿þîþËüÛpÁ%]j±Õƒ ƒRƒ‚†‚‚—·‡BÂBÖ‡ÜáèrxœzÎP¨Kè²Ðsaô°è°ê°'á–áÒð¶™èÌЙf>œe2K<«%"8"EšEfGþ:›8;rvÍì§QöQK£:£™Ñ £÷Gˆñ‹)yk+íˆSŽ›W÷1> ¾"¾wδ9Ëæ\IÐN%´&’ã÷&Ï œ»iîÀ<§yÅónÏ7›Ÿ7ÿÒí N.T^È]x$‰Ÿ´?é+7‚[ËNæ$oMâùó6ó^ò}øùƒOA…àYŠgJEÊóTÏÔ ©ƒBoa¥ð•È_T-z“’¶#íczDz]úhF|FS&93)ó¸XMœ.>—¥—•—Õ#±’Kz³Ý³7eIä{sœù9­2u¬˜¹*7—ÿ ïËõÊ­Éý´(nÑ‘<Õv¸ãˆë‘Æ£&G·c+iFš7µ[z[Z{އïhóh;ö«í¯u' NÔœÔ8Y~ŠzªèÔèéüÓÃí’öWgRÏôw,ìxpvÎÙ›çfŸë:vþâ…  g;};O_ô¼xâ’û¥ã—]/·\q¾Ò|Õéê±ßœ~;ÖåÜÕ|ÍåZk·[w[ÏŒžS×½¯Ÿ¹pãÂMÎÍ+·fÝê¹{ûîywzïòï>¿—qïÍýÜû# ––|Òú´ï³ëçÎ/ñ_ž,úJúZõÍâ[Û÷°ïG3GG%\)w¬ÀaO4%àm#«º¨Jã5ð˜¯Û1VÔï S´ÿâñ:ylÄ Î ¶ ¼`;f&Ó±^QÎÅøêè8i˜GÑrRÆ¡K±ÒäÓèè;]RÀ7éèèȶÑÑo{°Zý@{öxí­PU*ÌXÚ–WýQ ðü½ýuEþªcÜ®‹ pHYs  šœ@IDATxì ^E™þßï–ÜÔ››žPC‡„@è-¡Ò{ÑEPXtUTP”E,kAWDQAzH¨¡$!½’BzrsëÿùÍwŸdø¸—æþwÕý&9wfÞyÛ¼óΜ3óÍ™Sˆˆa_|ñð£Ž:ê ÁƒïߥK—Më0 …hmm}G¹áíÅ ›Æåï`Ð:h¸–åv[¼Ë·Ø¸ŽÁuÚ±éÉ·´´¬«‡áÅÔ³¢¢b]}g¾¥±Ë;ŠÁ'Øž¶#0ó"ý^¡#\ÃÛ‹áiy./9.·¾Ð>,Ëí(¶<—;ÿn±qƒë´cÓ“/·ÿzûØ.ï—Ûÿíc­ýª4~/;‚O°=Ý€™é÷ áÞ^ OËsù{Éq¹õ…ŽðaùXnG±å¹Üùw‹ë\§›ž|¹ÿ¯·íò^q¹ýËýŸ¾ÃE(÷ÿâó·ÇÇïÕrû•Ò”æßWG¸†·ÿ-íVîÿåþOqý-~Ôž_Ú·<¦ÀßxIØ{ü1®ãœ>‡^¾ÿ0ûb7·ötž¸Ô¾©ð]þ¿£Ø¤.wþÝbã:×iǦ'ÿAÛ¿¾¾~¦Âã>øàŸ/½ôÒñ…¯ýëŸ:í´Ó¾T[[»3†!teeå:ï&ÜJ³À‡RºÒ2òî,àæÁ Uƒc\Ë6yÇyëÑ^ ®ñr:ËÖQ0q‰mCÓ¸Œ<øÎ›¶Ï8ylbèKi±»m‘Ó£ ¦q¹ù”æ[nÿ·ûc2bÉÛÔmJ\nÿõF²­‡¬OÙf¥64†ý²4¦Ü´öùœÆé\¶ÓíÅà[Ò9ùw ¦ƒ†@\nÿõ³-×CÖ§l³R£´Ý§Ü´åö/Z«<þí`_²8o?l/×x9} XGÁtÆ%.÷ÿõÖ²½×CÖ§l³R¸éó˜rÓ–ûÑZåþ_´ƒ}É>â|î?¥~®ñr:û°Ž‚éŒK\îÿë­e»¯‡¬OÙf¥64Fi;9O¹iËý¿h­rÿ/ÚÁ¾dqÞ~Ø^ ®ñr:û°Ž‚éŒKüßÕÿ‘É|åÊ•c¯¿þúW^yå•_8pࡵã“î(oåråQ0W–2ð #OÚ0Ç9œ4ò)ãrY[?`N›—iŒoyärÓ™ñ‰ 3¾càò†™·é]Nœw /Ç1±áæm˜i‰üs˜Ëˆ­«qœ§ÌxÐÇ4.ƒ?e¥yë–·ý‡‹à<§ 7Ìq'<ʸ\–ÇÖ˜Óæeã#;‡Ïó¦3ãf|ÇÀÍǰÐÓ»œ¸Üþë»lŸ<ÆFΓƾØÕ¶¥ŒàØíÌipsÆ/…7_§Á1ãç0ã;×´†%€þ˜ÞåÄåö/·¿ý  à'ûý&‡çe 9ûc|p<¶–ÇöUã8Žñ` `ÖÞ\íå­[¦R¢…žà<§ 7Ìq'<ʸ\–ÇÖ˜Óæeã#;‡Ïó¦3ãf|ÇÀÍǰÐÓ»œ¸ÜÿËýß¾„?ð‚ýÇ~“Ã󲄜ý1>8îÛ†å±}Õ8΃c<Ø/÷ÿõ6Á6n3·ƒcÛ¼anšÜ¦.+÷ÿrÿ·/å~BÚ>b¿!6ÕÕÕƒúôé³²0}úôY555#æÄVd`ÍÍÍëÊ€ã`—^ZV‰Ì+Ç5½é—ÒfÙæ‘ãåi‹˜`c§LÛðs‡l¾Ä¶‰q¬+±ÓÐX®c—961xG¶“qr>yšrרën¸éòØô¥ø¶yšrðÑØën¸éòØô¥øåñ¿ØF¶áÿvû¯]»vvá7ÞhuÃä 7"ÍåF¶cf:ç‰ ® ihs‡ÈÓ”cp‡< Ìü[±åßx.#æ"‡Ø a¸qò¼ñÍÃu‡–ti9ùR\ãsA N®·eÖžÀÆ5/h(³<ãQnx›—ñÍÃù7O›Ÿa¦sž˜PZëKœ§Á-·û6³M±‘ƒík–Û¿øëö ØfĹ¯RfÛ.ÏÛ®Æ3ò¤s~åþ¿þž€}¶1¡Üÿ×»Ø_³ßÙ—ð#`ÙÌ6…ÞÁ¸æUîÿåþŸû~A°Ÿx ´Ÿ96Žñ‘þ8oß#ÎÓæg˜éœ'&”û¹ÿçÏuø•Ç»<¯äxä öÃbnýý¸}ŒØ~füÜs_Íy–ÊÏyÏ|È“¶,bô…w.Óø¥¸Æ!æ‚Ö¼lò×…4¸æIÞÁ¸æCŽo<Û çç²ß<À#¸Œ8O›Ÿa¦sž˜PZëKœ§Á-·û6³M±‘ƒík–ÛÿŸÿþO›fÏž­6/ 8iB©“ä’Úþ:§]nâR‡2ÎûsÞyÚôíÁ\æúy1¼”Æù| æà:z€n±CËé)· ã[iã[Wò¶].\B^NÚyãZइÖibð #6~¶åIƒ›ãå¼\NØup>|€?–cúŽôh¥ëg›'ç™óu{¸NÆwÞ¶n±C³ \V*¸å‘6¾u%oÛårÁ%äå¤7®uNÚyh&ß0bã'`[ž4¸y0^ÎË唈]çSÁøc9¦ïHöXº~¶©qrž9_·‡ëd|çm[à†;ä0ÛÀe¥2[iã[Wò¶].\B^NÚyãZइÖibð #6~¶åIƒ›ãå¼\NØup>|€?–cúŽôh¥ëg›'ç™óu{¸NÆwÞ¶n±C³ \V*¸å‘6¾u%oÛårÁ%äå¤7®uNÚyh&ß0bã'`[ž4¸y0^ÎË唈]çSÁøc9¦ïHöXº~¶©qrž9_·‡ëd|çm[à†;ä0ÛÀe¥2[iã[Wò¶].\B^NÚyãZइÖibð #6~¶åIƒ›ãå¼\NØup>|€?–cúŽôh¥ëg›'ç™óu{¸NÆwÞ¶n±C³ \V*¸å‘6¾u%oÛårÁ%äå¤7®uNÚyh&ß0bã'`[ž4¸y0^ÎË唈]çSÁøc9¦ïHöXº~¶©qrž9_·‡ëd|çm[à†;ä0ÛÀe¥2[iã[Wò¶].\B^NÚyãZइÖibð #6~¶åIƒ›ãå¼\NØup>|€?–cúŽôh¥ëg›'ç™óu{¸NÆwÞ¶n±C³ \V*¸å‘6¾u%oÛårÁ%äå¤7®uNÚyh&ß0bã'`[ž4¸y0^ÎË唈]çSÁøc9¦ïHöXº~¶©qrž9_·‡ëd|çm[à†;ä0ÛÀe¥2[iã[Wò¶]Z¬©T€ó æÊ9mäMŸú“ dë«q] Û¼J7oÓç°Ò¦±\xßüÛ‹Á±Lã›WCk¹Äȱ bëèØvrlÙÎçÁ²¼%˜<—e˜Óº®æc8yë”—™§aÆ!vh§uËcÓZ¦ëm^æãrøCO§Üþåö·¯$§Ð|Æ}1÷û–ý'/û¦ýÎ0óî(†Î2Íß¼òzË%FŽe[GÇà”Ò›‡á¹N†•ûyüÏýÑ>f²Û×íGöOòöɼ ãr0±C{<í›ylZË´ß›—ù¸þ– Nyü/ÿö•Ü÷ìû¹ßØ·ì?y0øØ7íw†™wG1t–iþæ•ÇÐ[.1r,ƒØ::§”Þ< Ïu2¬<þ—ÇÿÜícö'û±}Ý~dÿ$oŸÌËð1.ã;´ÇÓ¾™Ç¦µLû½y™ËáoÙà”ÇÿòøpéÈ•X°`A‡ÉÛÏrŸlÏÿíS9ýÎ0óè(FVÞßÌÓòWUUE¿~ýb³Í6Kú!Ç2ˆíÿŽMçØò'΃ëüaÆÿÂŒ3Za`&ŽP*Ðey žó¤ ä ®œ+kÅOlÜRY‰AÉó3ŠM—¸ùšÁ´¦³Ö±ˆUÄ3Žaàš>çá wœ72ôÖÏrL>åÒyYfÌx¹æeȱ^àÁ×¶ mzË3ŽcÊ]Ö¾Ër>¤ ð XžëÜ㚟qƒ’?æ—ã˜Îöu™ùšÆ¬\n:ð¸¬cŽgÃÀ3}ÎßvÏån ãY?Ë1|.ÊMŸ—%`öÇ|Wnÿb±}mÛ°Üþë'¶En#ÒØŠËi——æKqlcÃí¦†[ž}Þ|OlÜRYæ•Çæg>”™®Üþë·æclåqÉö.µŸánÛÒö-Íçø.C–á¤ ä –Gœã8Ml\ó³ìÄ äùå8¦+·¹ýñû†ýÊ>cWr¹ýÆ>hÍñŒc¸¦Ïù»Ÿçrß§gÿ´ÃÁç¢ÜôyYfÌxåûùþp徟Ù/)ÃÇÜ죆›¸aøWiÞeyl?4Oç‰-Ï>oœœž4¡TV–ü1?óÉéÜ¿\f¾¦1+—[x\Ö1Ç3Žaà™>ço;[b·…ñ¬Ÿå?.ÊMŸ—%`öÇ|÷Òÿ§M›:w%† ]»v]W£¼®¤mßuY‚2ãlXG´.7¾ãžÓ®Y³&^~ùåèܹslºé¦o“—ãYÚóªýÓbM®8•±"À¬(1’;i€¹Ìencënä¸ÌõÇö0žé¬ƒcë™×šœ‡q´õ*·ÿÛ}ûDn+ÛË0bìçà´Ës|Û™¶r;º¼ÜþEŸ´¿c«Ü¶åþ_ì§ö›Ü>Ø©Üÿ×?¸ºOa+÷GÛËý4÷-Ò¶!å.3Œ89/ãºÜòrã{Œw;B¼ÜÿËýß¾aŸÂG<ÚOì7¹O_îÿåþxÜÁ?¹Ùg€4p|¯üüW~þÃwð |Ⱦãq&!éáÆ)…—âSnß3­i€çrɃC@¯ÿ«ó¿§Ÿ~:öÙgŸuv aÛ9¶sÛ‚÷?ÙÿÑ}ž|òÉØ{ï½S;[/tqÈutš2Òÿ¿Ú¿òœsι ¾l8ç1”ÓÄy°RÀ —`|ó"v9eN;††t^f>îxä Ö§4MÞür½ÌÓøÆN€?0ÉKË O“7¿\/Ë3¾q€àÌpâÒ2ë _—™/q.t®³Ë­+ù¼<)¡?À à•ÆÖÉe ¡íòÌÛið –mÞ–KÞ¼L ŒË²œ/-o›¢¼ÞЙ§iÍ‹Øå–®a0Ëqs>ÖÙr­ùäó4yóËõ²<ã8þÀ '.-³®ðu™ùçòHç:»Üº’ÏË“úœ^il\–Úþ ϼŸ`Ùæm¹äÍË´À¸,ËùÒò6±)Êë yšÖ¼ˆ]nà³7çc-×úÏÓäÍ/×ËòŒoàø3œ¸´ÌºÂ×eæKœË#ëìrëJ>/OJèpx¥±urYBhûƒ<óv|‚e›·å’7/Óã²,çKËÛĦ(¯7tæiZó"v¹e€kÌrÜœu¶\ëC>O“7¿\/Ë3¾q€àÌpâÒ2ë _—™/q.t®³Ë­+ù¼<)¡?À à•ÆÖÉe ¡íòÌÛið –mÞ–KÞ¼L ŒË²œ/-o›¢¼ÞЙ§iÍ‹Øå–®a0Ëqs>ÖÙr­ùäó4yóËõ²<ã8þÀ '.-³®ðu™ùçòHç:»Üº’ÏË“úœ^il\–Úþ ϼŸ`Ùæm¹äÍË´À¸,ËùÒò6±)Êë yšÖ¼ˆÙY³ñÆ'ã:†YŽ›ó±Î–k}Èçiòæ—ëå:ß8À ðf¸ã©S§&}'†¯éÌ—8—G:×ÙåÖ•|^ž”Ðà×=‘Myd 0ÓR€ƒc&›ä¹\!+¯$‚=eТt.DzÍÏx9å@ï+ç“ó€Ž`ãX_ë/yó"Mpž4zç2yçpð ÐX/bp€ÙF¦ËcÊÚã›Ó›¼ñÈ îç ºÐ&pÀÍ\àèêàúƒcà˜<ò¦slY.zøAkû'X6ió`:óî+ç“óH„úC9|ès^ÀÉ›i‚ó¤]ÿœ·yçpð ÐX/bp€åõfúœ8†¿›¾”9¯½¼åÀ]Êí¿¾-°WnÒlÅE(·qü³Obû¨}˜+÷qûšË Ã¾àØI;P–ó3^Ng^nŸR>9óE¸Ð[Ë"o~¤ Œ€À¹Lc^Ä9<´Ñ ZbpHCïz3}ÎÃIç´ð7uf<Ò„SîÿoßI‚m°ý »Ù>¶YnGlk¼œ¸íR>9óE\h­iyó"Mpžt¹ÿÿíýß6v›—¶vÇmBü¿ÕþèFpü·¶ÿ’%Kâ†nˆ†††´óϼ©ãªU«â¢‹.Zç›ÈNl{£CZ¬1Ð +FLëOSSÓ:ÅÁ5i3Ï3Mccc”+h:`– lxä²(CŽƒù˜GŸ˜2ÃÍôÈǺ›yófü\G`ðu™iÁ1Í[o½óæÍ Þ£¹,ëeš„ý—P*X·<~æeÝÈ[|ÌÓôæí|Î'‡%m · ó5/ã¼qHçðyœ—`\b.‚qˆÍ¸mBÚÁåŽÃZó3.±ñçey¹áÖÅùÇü;’•Ó8\´¥:–—·'?/'x¶g'pryáå89M©¥x9®ëñ÷Þþô…îݻǠAƒ¢OŸ>í¶ ã¾ïEÔ‘ºùžãzæí]r[8í˜{!Á¶žÀnø÷}/¶ý1Ìòà>y—f:ßËɃk<ë-—éˆMóéþO½Êí_|f,·ÿú‰b¹ÿ¯Ÿ,»¯xì!ïqÌcÜ?Úó¿ëÄPîÿåþÏ}’>OÀ7þ/÷žÜ×ýÜ€] ûgîÿÌ›¾øÅ/ƨQ£ÒY8:u¢ê©îK—.MiìCðs”ÓŽ «ì@=8†mXb¦.wp æ‡9ড܊ x>¸AgŽÁN×ôÈG˜iKe›Æ<ŒŸ³?æ(§A´\Ö\.—».“'Oc‡íuR—®‘ºªT­R5š+ QI§ ê";Á†KÏx…‚ê¡L!¨ÞU¤e£J V žm*IzV´´FSA|`ÑR-".tØ1à×"ûÃ\2š%F­’Ú ÛV €ö%£YâYnp3•+ji³?Ê"ŒŠ pÄC²Db- $ŠG 4Q(×ÿŸ¢ýW¯^Ó§MÅ‹ÇV[m•|¿§o¸O¸¯ÐGÙúÈab#FŒŒ®Ýº&ß—{¥¦$·Äqð!¹a+¾ÎÜS™ `ÂùRLZ©Vù|qz*¸_eQ>øøáûˆûEÓ7ÜO X{´ð¤qß$ä4¤¡#6< ¾u±L`–åXùþÿv[}û?íãÐ^ºÍˆËíÿÏ÷üWnÆìâ¸òñù¿Üþåö/ÿïÞÿý¬ñ0ÿçíç)?7ù9Ê÷rw.ï¨ýý<Æ™=cÆŒIÏ^¦¡Ìå¶O{r_ª,8'@hž‘  ‰W,Z¯~<¦ó§MKSA›oƒwÜ1†ì»_ôìßݪb)?Áƒ:ù¥‹çǤq£cÖäcáÌiÉP}7Ù,6ÙjçØzøÈ¨íÕ?Áà….6&´¤áEà—`‹/ˆç^~"^›:>fΛE…b“ 6m6Û>öÜqßèÝ«ïÛøøÁ,1iûãú´̲QF6þüô‹ë–[n ͱz­v´³¨¥:+e;-¤T2qÓ²Y´²š@QÕ”Rš4²ÑIò‚UjCN‹&­Ô«BõÓ²µ^3Ù4‘TµPRÉbKšçi ¬hк ¿¨²\">•r6óxŸ–]´Ó"Zôž´nmR¹2µT&²Íš”ªDõj¥ÍÅ¿E64ƒ.´òÅ«fi¬_`5)mªáU0Q–žâÁd•GV&»-Z*^®ÿ?OûW˶²]L™49ÞxãØ`ƒ ’?Ñ/ÜÜOØYÖ§OïØqøðhhZÍõMÑ …½‚ü¤+åCòÒê­ZÜkÑ¢ΟüZŽÔ¢E4mWº¢‰‰¬ǯM‹¢mÕâds•!EߪÅÊ¢ÊǵÐÙ¤¾T%y¢„™Ô?RïSÿC®°[ñÛä³ÂÃ]•©Lz°Ü¢þ›ú…Æõ'ú`!éA™ú`ZH¥oˆ~ݪ~' Ä$‘F#˜ªRÔ÷*Ä}胭Ô%­Îªo)®jª¦÷—mô`£ššN±ûî»Ç¸qãR_Øpà Óý$¯m÷ ¹•ÆBÚXmÎX­@÷0‚oФ݇Œ iîMÜ×JïQ.‡Ž2ò¾|¿¦Œ{“üÀ!ä²›Ëv^ægzäQN(íÿȃ_.7ço¾ÖÙ|¬t” ËuEp ãÊñ}ÿ‡Öuq .üà MΘñ %€k¹¤ßíþoZëd½‰ååÀM0ÒàŒ it+·?ãÑ>y»+·¹ÿ{¼(÷ÿâ=€>ⱇþáÀxœ=ÆÍÇ[ð2?Óco÷Çòø¿~ÌÆ&\¶q©]]†=KÛÀöÌÛ€ô?Ëø-¨õt½lla›åã¹a¦Û®ÿïµÿ/ÒzÉ=÷Ü“~Ø£.nêç:¾gûsÀ0H&r†9SŒD |ö«¯Æã7ü:^{à¾èÝÔÃ4iÛ´®6VÎtêó¢9s¢[ß¾iÁ¾V±ùXÉY“_ާïÿUÌxâîX½&vÞj@lÞ¿[¬]0+&½ðD,X4'ºôè½úL:AïÁÃ<¬?ñ¤iâŽQ¿ÇÇÿ9*zµÆà!ƒ¢÷F=âÍåóâÅñcbÞü9Q×½wôíÝ¡Lo¾Ôݼ­·óŽÑÃifæÌ™ú4ÙŽÑÜÐM-Zœá×xl‹ƒ21dC3ÂJ-’´²†I¡&•ši¦E’B‹U᳨¤³%-„W‹ ìla†—~{grˆlÍ Ù»Òšv0 ‹Nã0K*Z=\Ñ6k"Ø¢˜¹%<ø¥¿©E|åôÂJ GL2ÁQHx茾,Ð : -Lž¥«Š„£Xøš\Tv4hÈÒ“pé”L;Ëõÿ§jvÂЮ}ûôI“^O¯DÑOðñäç´{ò›Ö´«fäˆ}µP#?kįñEø¤¼w©Â¿X‹dáBøM…|µ ?X d…ò-@²(‚ ¾Æ" }©JN]h.HÍU¬‘¤þЪþXY]ôQfØ)F‚éì@kª¤~-òÕÀU'bѤ‚->t%-–›-k&<ºm¡¢Ø7Ñ‘ú§>+œ(•¤ÏHl«„§ÝnÒU5Q¿’mú´j`ñ¶l£±@ݤ±qƒÄK/[÷:”ûûDÞ?äÉO|Ï Œà8eô‡{ 8Æ3OÊ“ßÈ×\n9¦!ï‡Ó›¯ù˜Ö 9Üitâ2NÎÃ8ðA.!‡9 òÇN[?Ç¥v ï2bt1i`y¹ó–SªpÓƒK¾”Þ|sÙæ—㦠éù˜/pÃ,Ø<ˆÍÇ4ŽÍÓmi¼œÆ¼‰¦Ü4¤©ƒóæIl>¦¯ntâ2NBlãaø”ÚØ:œ' ƒy7n©È»Œ]ŒCX^î¼å”êÜôà–êÌ|sÙæ—Ër=ÌÇ|]h,ÏiÊH›i›§ÛÎx9å;M¹iHSçÍ3—mZðJá–…N\Æ1ÊŸR['b‚ó¤¡s0òÆ-µy—£‹qHËË·œRÝ€›ÜR€™o.ÛürY®‡ù˜¯ëå9Mió1cótÛ/§±¦qlžn;ãå4–Gì4å¦!Mœ7Ï\¶iÁ+…[:qÇ|(7|Jmlˆ Î37æSØæabä-[¶,ý0ÁÛ)äs>èŒ@š²¼ÜyxJunzpKuf¾3f̈M6Ù$ñ1¿\V*Ðó1_à†YñŸþô§à½ššš¨¯¯OŸ1Ï™o¾ùæf—b·åYÏô”Óˆ6 (Âêé¥:â¹?þ1–½46¾|Ø!±áGˆÎûì% #êŸ|:æ<òHüjÔ_âñê®÷ùë X§¼…'ô¾Ë—,ˆ±ý.Z'‰³>wDôßûÐèT·¯J ѰäñXðÌñûëï‹—4é©íÝ/êúllœ\wø.^²0}[L^<1N;ó„8h»C¢_aÅ›+ÆÅ¨ Ä-¿¹/ZŸhŽÞu}£_Ÿ©Ìº±JLÈ Mv± (ÆeC²r–N»æ÷|-Ô¯À9åÜê¸XA $Q¡IŸ&„r;íHnzmI|4ñ…ùÅO¿ü1‘dgŠ&yìp©hÒïüšTŠZr…©öhjЄ°í×}ìߪW§š…Óª‰e~Mh+Å—É ÕF-ªT)ŸƒàAE”g’ˆN-š|hNMš‰jš¬I«òt v)h›tÖ"SA»…šZ«!S6$}ØÉÃä¶¥ÀVu¹þÿtíßÒXUšäë›ÄÂ… càÀb¤àóΨÙvÛmÛ|?”kGŠïMý Eý€‹MJkËJThJKµxÐ×ä?M8hqU$-²s,õ7+´è•UÉ/+µ¨£%ùlsÚ)V¥>ÔH§SwSÕbŒü_èô#ÑHš ðyù)¯²H“4ÓMZU_[«’NªSc³_à¬…É ú—4Ç¿Ù9F?a·OÚ žU,©_+©zsÃŒ%þ©ó:"pv»± ŽEªJÁÊ6ú‡³‘Z>Ûl³M0þÓò~àþà¸åù=‹ûy¿ 9íûppÈ×qê 8x[§”?EÆ'†Æ1¸„\`ðžåæ÷¾84\®¯ÓÄx”ê Üz9MLŸ‹Ïð: Üzsr]È»~йÄ·´ækɇØ|¬7uÉmÓ›:×Ý<ˆ-—4å¹ÎèbýÊí…Êío$cèýÕ¾âØ¾i<ü°Ô¿(3>qî×àr`à“&Î}?Ç/÷ÝÏÛÆl‰ý°1Áq¹ÿ—Çñ¥ýÓ}ùgÿ=ŽPO‡¼î|Úûç?ÿyz;…›Í6Û,Î:ë¬9rd~ûÛ߯É'Ÿœž  ã"äcyoùfÛ»BK9—û(i8àQNí–9½i¡ë¨ÿsÎ!Ï3+W®L2:è xì±ÇÒbux¯ö¯<ûì³/³rÄ(Hå#œ`…^yèÁ˜ýàýqâÐmc³í·‚Èi|q\4{) 2r÷ÚÚ°zuŒ?>ª´X³á!ë ’;ªùMó`,îž8æ¡1p·=õÖÌšhZ,~KÆËr«£KïÞ±AÕ²xý¹—¢©{ßè¿ñ6ëâÐËÆ±aŸxþÏñÔ”ÇbäÃc!»ÆÊX3V¼sV½õ±*úÕöŸU1ö¥—£¶ºgl.~ëCì"m[î²DÔö¦OŸ[n±µø°K¦ØÈLyU¢ÐÊâ5¹kæ—vÆr™Uë#ñÖ·‹¿ùŸÑ¬s@ºì±»¼OpM`)K¯v°À£4.TÐ"tL[$S¿C§_û+´€ÂŽ­ÜKí‡ìÚa2JLhÒDT¿ô§4i*©­Õ¼Ö”ÔÂyY¼‘SŠ§Þ“<ê€éKZ¼šoÑ‚O!½§Eø%œ£ €tŽ&¶F‚ÝÖ ÞGý…Y¬ ÏÓ(S®ÿß}û³€ÒµG—˜:ejôêÕ+õ\Œ>ÈŪô.;ï"//È‘iZ¼ÿL»S´0A?Ò²†LªTÒ(ÿ«VûËÓÎ9~%^‚î…¿óÕs­dg—¼µ5m3[?ñ–Ü“C"O}‚ÅDÖéKÀÓ+‰Ê³ Ù¬W¬è¢ô¹Jù¹:Qòy^Ubçš$_põ“âX ˜C©G:G¢Ð«Eô¸²€âÉb§è¨;…Ð]1ë¢ôMê–Ä £l£@©í{Ôöˆ—tÿã°áÒ:‡Ì³xÉ¡ó&·|ùòué<ÏÈó B:ƒôœE9pøÓ—xð=Š˜àû-i`ÜËrphóàû¾cð æIì4pðÌ#Oû‰2—[v)àà@c\OáI¸^ÄnZËÇ<œ¦Œ4ü‰†žtilYÆ3=p—%¢ìq,#ç™ÃHƒëà¼õ0ÿ<6/èÀw¡á2nž†<ðMoZÀC›Ëp ‚õ³LÓ€gyÚ2(s¹õ)åáú@cÜrûû>Sll–· v²ÛKÓ>¶7ipËí_îÿ¹Oàîo¤ ö1ÇàðÇN“Ïû|ž.÷ÿõ6öiìäà¼ËÝ—ó\Êó6q9ðöÒÐäm ²r8ÐæÁ2ÿwµÿ¬Y³b£6J:  —‹ÿøÇã[ßüVüà?ˆ³¿pvÂûêy_ÓN?-¡±PsÆg$øÿÄýŸ@l¾ÈÛ[ÙÖÖÝ1öâÙ¬sçÎëêæ:£®®.^ýõt†çÁœža8ës=ÙYc\óÏcdPž~d¦A(„)ÁŽK‰C»u‹¢izíµ¨ÒâL…LøÚÞÓ$…è—í!:Xô­ñ/EB*CóÕ- –ÌC7îµÝ:GÓŒIQÙ]ü:ù57ꬋ•ËS8Ó…[Qqº“Öá‡~ð¤‘1õ‰ÑëÞÑ­¶&&¾91ºw®Nz%446ÄŠ5+¢km—´Mï„{páØT/;(1yìALp£¥LöÜÕZœêܹKTwªŠÆ鬉! P¦I¡&kÍÌ™nê×u&yÌK[W,‹Õ<ž¸­¼wTÔ}^ÎØIÝšòZ &,„°@î‚v餅qDpV‡G˜àjÉC@&ú„ňDÕÔ¨ÅýúY™^ÉÐŽaVj¦Èkì `G sQ^³Ò±ÒžÄOpTªÃž…‚v%ÈHÚ šh6Ú¢×@¨+Uã•(vG¤©óû¬¿´M2›´HëÊâ§zkÇD¹þßíߤ!]«k¢“¶ô1Yäa&‹ô?>EW«ÉkuçšhÔ™MiÿŒ|¨J~Ù(_®–‡4j—M¥üö®ûîK¾ßÞŸ#<\®¦RI´È“üœWŸØÍÒÈËX r9º§<WÕB½DþÊâˆøWÉQ[ªÑ@Ë3ZŒQ‰úÆ ý“—E«¶îT°“†bùµXK»b_HB8Œ/ì|A€ÔIzËéÕÔ$úŠf_´ä¤>[­¾U¥Ý:MBnQ'gáG¢£Y²šªÕ_ñuv¡¡•Ô”’)]:°‹ègzÁKJÒý¨—ê¢l«rË6blýß³Q“ü¦¦S—t3æ>ÀÉÿø 6åkho¸ItÖý-íÂRß)È_¤±®b?—¦4c²\‡‘V¾üO¡ÝÕüªg‘ ÿÑØ®ö_µº>ÞZ¼0-Ü Ð.Vd[>÷,ßoE)Åûit#ïI14ïçþï…9>÷_ʸa]:ºÿÃpÁ±Nþ•‰2àÖ|iÅüóû¿yŒC>·…ir¼¼nÐ!‹˜@:¯[êù“'mü< zË´à®/z6}©,ó°-Ì9À¸¬¯ëd™Ä„œ.۟๿¼×ø>>g´Áƒ/ÅòãS­ÖæÌ“æòÇsŒv±ë™Z 9çž{nòÇSO=5~÷»ß¥E+¯¼2fÏž{íµW\pÁéùëüóÏ<0X Á_ùúÒ3Ï<ßøÆ7tlä¸üòËcŽŽhÙsÏ=ã /L4ök÷×-÷}„žÉþ@g·ÜrK|âŸHÏ€Ôç>Íoø y#FŒˆ!Ú¸_ÊÐzè¡ëú™mD™íCš€œÊ3Ï<ó2!ã§­e0xý¦›b'-J ÔŽ‘ÎÊWi¤ ÷® š¤U._•2|aÍj=A´Äôå+b‹#J¼ác¾ð!Ï5éé›c‡ºFŸ¾Ý5ùÓ#(¯îè!µB¯×šVib•xéhRÑL]¼<6ßùÈuƒ:Á“ËüyáŽ4´wÔö馷$T¯´ Ð¤W ÖÆÚæÕQßR¯IÿVÄ¢™‹cï>ºN/øÙ6ViŒ`y]8l•÷Ûªª:i¦ƒ~YÍ`"¥UŒôõ%Æ>MVy‰7¯½õSgE§Áƒ£qÆÌhš57:¸oÔì·gÂãÜÍôŠ‹(¼>‚ŒlÒÌN-ŠÀ‡vÃhõFó”_=Å«~JkæÉ«"ìx'ÑëF(|&¥‘•õ—™ª¦Ò·™ ¦þ%qèÀ‚~šìÅ =-âÛÄâEŸÔ–,*‰1¯A½Wý™x7j¦* ¥“äK'&®Ø;•ë/{ÿ·³Ú½k—š˜«sªØ]ã~B_Ø^;îºj±?ÁËp(ðÙ’œ‹ó”ä?“&MŒ}ìc1tèз]Ï>û¬vçÌŒm¶Ù.ø‰œL6)ú§V~Ô¯‹¯Ø£ñ+÷„›â®¬ŽhIe™6£E–‚ƹ˜èñ_úNrtÝ€1&Éÿ)“/sÐ1‹˜Ô‚ÉsÚ½F}ô0Iÿ¡Š‹©ª™äU‰ž/K©‹Hï"|åáR¹èïа[Mµá‡± FúÓX”Ñšl’Ï ‚+5®4iÂÏn!vÛ1^–mô¿k£=º¥_IzöìIƒ¦þ@Ÿà×–:´]_¿V ùúÁ`­Ÿoˆ†µº?45ÄÚÝ›[³FÜ:ÿ­Q?<¬m¨–µ Q¯•*¿†Ý¦Â…ÜúµEÜ&U£Åýžêl¯åýg~Ùñ= ù¾/Z'îoyÇ÷<Ã}o#½ƒùÇ“ÇÆ‡`ÜR=Ès\ôÈéMc\p›ëKž+Ç5¼ ÏåPt6=0‚y¿4†0èŽ; .×Éå9,´ñ2±õ6`N»Ì¸ÀN ~ÎǸÈÉ8®³á–C^æGl·»cãƒC0®u3Œ<—ñ€ÛV9N^7àäs}ÉDZñàm¹¹ÊáQnÿbûäö´íˆ ØÏÁö$ÆÖ·»cãÛÞÆu;@c>ÀŒ¼ÜþëûŒíe;ÛŽØÉe¤ f81p.ÃŒ‹óN¹ý‹¾Œ]l ìB(m[·‹càò\ÐÙ¶”¹<‡%‚6äæ²À7/ð(Ëe9m¸e' œË|Ìã«ýYXa§Šõ°¾èȽìLþõ ¿N¯“SnvÙe—¤ÿþûï¿üå/ãöÛo—#ÍN?ýô¸â?®ˆçÆ<wÞugvØai.òä“OÆ!‡’luÕUWÅÞ{í>„râ‰'Æ:šçž‹»î¾kžm‰nnvÖ0§G?ëLlÛšÆ1p‚Æë¢~ýúŽ÷Þ›bx¼¦M-Æ K›:¼º½vÉe¹Üü)ÓÆ‰âjŠ0à–Ã)cE õpX«ÏðvéÞ#:uíU]´£DW'¼JW§îÝRYÏÚîÑE¿¶ÃÏ7Ebx8ŒÎ]ª¢¦W÷¨®ãªêÚnÚ­#¾ºH'X]„®uÉõ4OâáÔJ‡núlvwéÓ];^j•»ôì!X·®Ý¢V×5]Š;nSýž_írý€¡'1r‰iTðȳ-½WmO=h¯Q¯¢Ã²P£‡ MÌ8 —_J™Š‹^wZ‹ÎýF,½äЍa|ôºøüpço¢çE_*®‰h«¹ &†<WË™¤µhQ&m˜‘€Kélžåz˜—nú&x«LÜDW¡‰ »aª¤# ‚¤Ã€Ù­’vÔhRÈ’¸IW=£E·µm#“QiÊ.æØÒ_µK¼x­£…W¹´ðOÎרÔîf½él¢‰CKëŠxkÑ[iå°±ƒú3nLºêA™‡ev%Ð5éä6RhYãø¯øÆ7¿·<;Oç˜Ì‰?]ý£øú×O¿¹V„$WêªÊëëß´øÕ¸ã—t¾È;ëßºä•øÓ}/KwÙôõoŽÏÞßÿÆ¥ñ£?Žg³pçô52Ñ4±(HZx‘^,Ì"ME5 ‰¿|‚>ÎY7ÅÏpË·éÅm©/°Ã!:,:µp˜}D2å.ÒKu ™HO…;ÿåûâ®13Ò«†UÚúÁk‰ÅÅH½f(µT£E-€¾‡ªYØ‘8ø/wÜýì ¥UÏ*o·6j˜þîÞ˜¥µû}ÞnÜ]Œ'¦ëµ65Iwvñ:æ—Н°ÑþÎF­zOí~Ä"Ù»ø Ôø<¿˜Ð|¯ä>Áâɲ%KY_Tà,&éE›jãÜmÔEžÆSü¶‰¯‡1k•ŽIÆQâše³ô%4ѳÈÊ·üV³À£Ý5ƒmú¢ïIø3¿PÐ…4úÇå<89m§ÌuN0?Ò¾çôÀ|Y1tÎà ëe9e†“¶L`æi(ÏqÀ¥ÌÁd9Œ2òäþo¾ÄÖ‡Øõ„ytÉë‚Îø‰Ë£ƒñ€ u½QN 6変ót¹ý‹6Á6y[—Û¿Üÿé'¹OЧÊý¿8¦xaìq }(#äi=“<çò˜F óÆËe¸ ˜Óàåã iˤÌ<­å9¸”9”ûÿßWÿ§}nþÝÍiÌÔ©SC=Š|ä#ñýï?ù@ÍÛ ìZõçQ鵡O}òSÚÜÑ).ºè¢xüñÇÓUG~DŒ=:-Šðã?ú²póÐC¥¯0~ÚéQ£‘¿öµ¯Å_ÿú×tЯ}‚ù[îÿ<ëñª;z8L™î¸ÏË}ŸõýyÔ%8ø=ùéÙ¯­3ÎÜBœ²Ú-6å5z/K+_…~}£°ÑÆQ,ttèhA_’–±\î¾ùfÉÞjíØyÝûŽe-5Qѵ{ñêQ…Ú~éªPº¢»àZZ¢[ŸM!I¡)¡?èæÊÐ{“Ð|?:WvŠzªwç^ѯs¿èߥJw×–õ•µhÃ΀ºè|Qg.ëˆñ\fÃclK¬2<€ëb7ôi'{³ãEóL=p+0”]*úöŽØ·®¸à ×Eêïx0Zõ Øf]LæX0I”)/ÖÌŒ_½kì²÷ˆ¹×ž±óŽŸŠW— G >¹Ý¬ýf&€Ò¡QŸGn:?C: Ÿ /“&Gtª7µô¹`Õ=MØÄîILýr¯‰,“yˆl,|ÒàRUÑôIò‚nXZ5ktœ£3‚öÞ{ŸØ}Ýbø1ˆíÔ¿]XUÒAµìÚaÁ%M|y•Üù_'Ÿÿx ß{÷P±$úÁ¿Ä7_íûì:8 úe™¶æeΑúÒP±þ«ç?—|ý¹XÝNýëßx*.¹tL¬i¯þóžŠC>u~†ïƒ»jÛ݇ªÿš¸íœÝcÈvCu°îv±ívCb;]ÛhÛÛ6CN‹WV­§Ï½$žÕbÓ{Õ¿£ö饗âûW~/®ºêÇñ³k¯‹çŸyîmõÿ[Úÿõ©“t.M[lvˆÌœ;O¯Þ蕼ÚŸ_õyõßg†>¹lõkU»hšKNyÎ<¢­Ó9Lô¸ßªåÛ Ç{lÜyç”tÃg–&¦x¤Ü7bíøø·:8^[½êÔÇG|.n}q¡ð4á•›ê-¥4¹EÏôŠýJ:¥3hx]–ì\)~\ý@=¦U .M©¿©ÈÿY IN¡…ÍtÀ1ïJI–HÆ]5,6ÿîóª§d —þ6Š~=ïÑ âÜ¿.T¥±üºÐÀB²VÆMgnu½k£WŸºèݳ.úy^ÜüøÔ¤G©e¨ MsFŸ_y|‘ jé¡aãªLq{6 ‡†ÓWY4fÁ‰WÄÚ³Ñkן¢a[¶ì­K;Aêz¶]²ïU¯,ýp6ªŸÿò…ÓbžÖ0 Í«âñ3ΉÑô6±CŲwÚHuÖ¸Ò‚°.ÝÞÍrµVjQ™Fý6jÖ˜ùêkÒxdUIö´Y3ÚÆ;ùaG~¤6àPmî=IÆ¿ù…hÉ’·ÔŠc'K÷ªµüŠ> _“;1®òå¼&µc/O7j÷g:XOÓÕ¬èTj¬ãøJá±ÐÍ¢Ÿ‰_Ëâ°|¹V3,ё߻üp@Á8¤ÁåžF vYéýàrå8Χû^›\x'ø¾ïØ÷VÊ¡±n¦ODúžë@Ú÷_ø8XòÆ5_øQî2ëž/`àø‚†Ë:š'å–Oìû?å„Ünyû›—u †ü Äà88m½(Ÿéx¹ÿawhÊý¿Üÿí#ù8f_±_áOï(³?’ö _ÐpÙGÍ“rû_¹ÿðû?ö{·þ¿|Ùò8âˆ#âÖ?ޚμ¼ü[—ÇÕW_cÆŒY7Ö²s˜û¯êkÔÃwž^1rDjWž{úè¯eˆ-â9íœáÀâ=vß#-èÌ›7/^y啨qøŽé•¤‘ûŽL4Ì[:jE´ýùÚœ`·ú²ÃaÂx°þE F–ƒÓÖ‹rðÉWÁŒ÷Í($”¹çö;Äü'GÇÖú%±S/-¦ Ñ[ 4|k‘°«£¾ª&æ/]uÂ…ErEI× ßÒ©Èz=]1…šÑZSÜJΜ>Zõ™+•-XÓ=mŸ*l>Ä®Ž@Å6é»UL{ëù¨ÑBB×B×èY]«>u©.Uš%°|Móš¨_T[ –ô…³7Ð ¾ð#¦œ@š€,ÒÐðEœvÚIËÅE¾îÔ¢_HÓ1ü2¯ìýªÌ$5_£™dï_|O‹:wâ­å±ôGWEÍ®»Ä²k~UÛn+þëÆ-äôºä«ÂÑz=)-ŽhÀÂçîˆ>q\<øÂ¿ÇF•kcú”YѯF÷“}$îsulÞE»7¤# -é5,¥5ÇLƒ•ø¥Onó+­&í:ñî³ãÄ;Šg~uLÔ0ÝäL&’«ßþt‡ÚOxiBË«Wi÷‚죉z3“©µ’¦ú½tË×â™S¯Šg.80º¬™3¦·hÁ¬½úë—kÎûsúæ¶ÉU‹”äÅ‘• gÇÖg~%Ž9`¨tißjU|åöÓâ ­¥3Ù¹U—æ&^uÑ„…&QýZµ'55 ÒΨ’úWUwD»r$…v)¹þË—NÓ‚àÅñ¹£Wý¥ê(¬Xÿê8â;‰h2S¨Züä‘ÑøïwƧ‡÷ÔêmkÔtmˆ±[Vè\ù“ÚÿÝêßÂ+zÚ1¥Ú¾­ý×6Õ§‰ö9gžK–¾¥m|÷Ç[m½5™¦þ¦›NgQýßoûwÒ.‚‰Ó'Ç#?K-‰Ýw×ßÚ0fÌ3ñ cã𣎈MÔ¿å»íµ?g@ è¿A¼8ö¹´Š<þü¾ãp ®©TMìè[,¨é¼vØp&‘: ‘úžéþ%Ã+ }èÝÔ›SÌŸÛî¼;Ž=æxùŠÎnÑD¼)-N´êððÖ¸èî㬭›â¦¯g½m0óKÑ—6Æäãi1QiD¦WÅÿÖú¦\‰Ý ,¼´ œòtVo*XàТ$“â&\\­Ýk,Üð‰(±ÑYiõñÍ>vkÜÛ¤¯Ýi¥+íâa1He,ˆvî±i ÔBM•ø«ò’ ”of'ÜMÑë¢ÛâÅ/íkßœcïü~œ|ôN1ûîWâü‘¿ÍFœ´cÙÍÇ qº•Z̨³‹ oiQßæL«R±nÁNŸ‚úK‹#¨*_¿âu¯ÜF[}⺘q¬ìÛ¹"^øéañµ¸"îûòÎ:3L+×.IÕLR!ÿ6’¬¡qTzÊF5ÛJ™í²R°‘\ •Uìp*±çn±°Y)Ú÷ò£ÜFMúr}˜Åèb#Þܭذ@IDATžøÊ´¸ïÞ»cñÞ bß}öŸÖx|ôñÔ3OÅÇÿå_´-vSãíûQ“ƯÍ6Û<&N¼?ú÷ï¿îÞáw¹Wë<·š¶ög|á•T¾ÖÇ®¾0Æ"-0né•<-6ª<Õë½X(âÒµº?¥]›ÿÒ녂륩襶Ysg¥_ üà{–×Á’ðAîÿÐro4/xûa^”ååÀÀñezhÜ?Isä {y"œÚþð 1÷ß\ißÿgž”LCÞú€CžøÃÜÿáã`>À,“4| ¤-/·ƒq\–Çà¹>ð(¥³Ê #X>yðÉœ&¶NÀËí_nÿ÷ûüoÿ³/å>/á\.Ž/ÓÛWËý¿8îÙØ®ÜÿËã¿ý€þòÏ2þS'Æ×±Á~ÿðÃÇ 7Ü7ÞxcºÿsÜÆÑG7ÞtcZœÙyçAO¿üÈÁס<Îp. ¼¸Ž9ö˜àU(ÎÎ$ý 9èÀƒùðÑð›ôC}Ž4ºyþOš€ÎÈqšØóÊJñM·ß~û%ðwßÎÇAðò2ã—¶¿¾Íµ¨ ±‘`be¨°ówÝ5ocW¯}úGC]ïhÑ+Q\ ½úÄ ½¯5N¦ Ú4h·Ý’± ' |¨˜+7hsMXªÇËÓߌ•: ·_‘Û®á{yÚíŠØ46ØbÛ&é ?t†'ixn;x§èY³_[­šŸ´ ƒIi`”õ,l Ü×écláºÂËú#Ò¾xÿ šªjެ‡u~!N¯éùŒƒ€Ó¿šp¨°È£i…^9ý¬XzÁš`uÒDí íDê5{í½~rETlºA4Í}#ZæÌÕd[‰§fL”x¯_8OF\‹–Ë1+»Åàí¶Œî1#¾uô¥’õH³ËøÖíS¢bíü¸þ¢ãbˆÎ2dh|óÖ4Ÿ—+'Ç·OÿV<ôðM±ƒÎùù#·Æ1_{$Ç|-vÙḸsÊr½5/~Ùñi‡ÄNÇ~/ƽ%‡T{4M»'¾xáM1ê–ËbȰÓbüù6Ñl‡_ö–b-ÖÕëWüèÜ?620ýúÏbÌËwÿ,Ž“¼!C‡Å®_¾=VéÜ guQ ¶} “~g\úûX$+§ü)¿ä‘˜tí%q܉ÇűÇßj®+Ç]p§è"¼tWœ¦EÀí‡mÃOÿN<¿ ¸\Æ’žBФºB "óÆþ>ŽŠÌãâÜ+®ŒÊ!5šÌ¢«&šˆƒÞ¼fR|÷Øïk'Ö©þ·¦ú/ŒÛ¿óÅTÿ†lWÞ1V¿\óúŠõÍsâúFÅ7T—ãoŸÕ¿R¯v×$©N¯ÙõˆnjÝêž½¢SçžÑM šú´¦¢zÝgþ‹ñë/ïÛ‹ï9×>¥ ôƒŠ˜:i²Å:0?ò°xìáGÒ¡µïl™U;Å8¥¶oŸ´hÀ/ö·Ý~GzàÐ(Ÿ›1uf¬]±V[—ëக±T+Ö«W¯JóÒú7ê5»í¶Þ&öÝodLÐŽ‚žV[Ÿ×*ô«údÞ~1xãÁš jlHõ/ö]&—lÊ«IMš\uª‘ÿWU§Ý5]´šÜ¥›vÉÉc“ϲ¨B{°`ÀÁºô &ZVR‰Ú€¿ï gœñiü¾þÏC_ɤU‹?+Ä¥¶×€è1pËøØ)ghgÚÄøù)#ã{™SìóÒdÚ­çÆ1ß},VO¹5Nùü/âžÏ^ýމçW¬‰'®þLôÖ˜Õ»O¯8ú¼ßÄ\-*5Dz¸íÜOÅ/x0¾yäÑW¯z]ð‡çcÂCWê@2íàé{J<:MçriQvÑs7Æ/ÿ:'-®4µ®Š'už¾¤Ý2û_ýêSÑG›çZ´ØS¨`5QMÕƒW¨¨õö=ûèÕLÔ¾ÉVqØÙ?‹ß9 þóÂ{c©ê·æÍ§âK#6Ô.—^±÷y¿j½Ù¯3öZ;%n¹pDÚ3ðS߉—©-DóúoÏÏߨ/ðaÛÂò¸ýߎŽßLZ&©ˆú™£ãÜýD_íz<æÔOÆÈ}¿¦Ý^ëõ‚êš®QÛ»§^í¦ ½´`Ü+êjºEïÞÝ£›Ú¯¢e^üîâOêÕ·>Q×·W\|³~ñP=[Þx".;rCÙ²§våxrvZŒI;èÔØÙÄ"]u¯ÊX2}T\¼ïñè§^¡úJ6çV=þÓϤ³WzªÎGñ7ñ†v2UW¬ˆ;¿|jüøž»â˺×ì{¾8Øž±@Þf£*Ñ±ÈÆžZ|l[Š“ä}íú6jÕÂ×ö;lèAà¥qãâɧžˆÑO>c_›¶Øtp_;d8Ý,Ôá4¶3T°à§úuíÚ9=°×÷ î!<,,ÕŽ› -D°hu¤V-ø±Î"ŒtµƒüO 0­,ªÑïÔwØTÃ}…P%Züˆ5ðôš*¾%¹©\÷œ®Ýº¤{#ß÷2tàòÍßýŽøƒÞÿS{Óæºðuì´Ëà =ŒÌ<Їrëä!×5ç——CKÞrÌ×y}xž±LÓý±lóÿ ÷h ÐÂZbô1ß\xèÌ4ÆKŒôZ_àp‘'˜&[Çð·Œrû—Ûß>ˆàKö |‡²rÿ/öGìQîÿï=ÿó¸DLð¸Cì´Ëì{åñ¿8~—Çÿâ½Þ>Äx„Ïì¦u‚)“§¤Ã€éã_S¦L‰¡šâGÜÇ™osXïè'FǘçÆÄôÓã×ý">äàôñ ø²(Ã'±Ÿzê©t¨/tÐ<>úñxvŒÎœ9#~þ‹ŸÇ!‡’hàmÍû¿}—ÿÇiò¹¯‡–€þÆåyæ¶Ûn[·Ûùomí´..JX!ò0uE¸lØ®½zÇF~4–ÕõŒ'§LŽ Æ+WéZæ/ˆ'_ŸKõµ¨:8ºè¡cqÁ—_q\Ix’îÒ½Ol0ôðxkåÀxzÌëñÚä×cþ‚™é" lɪA±‘p:wÓÂP›~ÄtE7üzvï»ms`TÕ÷Š±Ï¼“'L7ôJ×ä Óâŧ'FÕšºØmÛ.zÀ Z7y.ýÁsp9; 6ßl MZy½E<´ÀÂo*µ]î¥_;4ÑÕ|EÏÞzÓ¬¨rËÍ¢Z;"–,ަgÇG—SOŒæµ«¢aÂÔXuÇÑûºŸF«¿kn½;VÞp‹&gpÑî}ôÌ8w§¿Ä© Ë;*Þ佊Ÿ¾îó’±u\uÏcñ•÷Ю€ªØñÄ+ãÙ—ÆÇc¿½(n»â†x]Z6êÐæ9ÏÝü|qÜ:ê‘8e¯Ããú³¶ŽŠ._ˆ»½1Þ´s<õýÇ:/½òrÜöÉÙqÚ7К¶ªê3êÞûøÙü½bÔ£WÅ–5êhš´ð+½~6½Ïº}n\wý¯´ÚúÃ8oëÎñ™ŸÝ¿>ÿÀè´øñØÿÔ‹cäõ÷ëÓðOÇ/œ§öØ#+h8Ô%»ËÖÍKŸˆƒ>uyõë5ÙúU·ëpÙG“&eà©Iôh­_ù7Ï]{æºúº!õ?!.{mx<ðì¸óÐÅ쯟ß{t¾Ú¿B®1ñƒó¯‰Ý~oüêÄ-³úÏpûó*·õFUqÍ9ŸêOü.¹õ»ñȵÿ©‰VA )+ã´ýdÜqÇÝqÇ=÷ÅO¯½V;gtZ¸ü ]yU"µ¿üsuÃùiKLžøº&áÍ1hƒ to—˜Ú-¶ƒÊôu7-`¼ÛͪìàÁ›¦ÓÖw¶ƒvÑhPSýùB’f¡Éìì`WK¥.ú»:XС•¶æåàUkBŽ-8Ë£ImQh¬}w'V¬\Kg©]ŽÿitûÄÑqØÁ½â{ÿz‡=´˜3ãêÏýVÛwŒ¦†qß/ŠïÎ< ÆOømlÛ½2úíú¹xeÞâX4eTÄ_{&étíY¹ðɸðWÄ–W</Þzaüü³ƒn¬‹ç&½?=þéøú}¯É"–¿ñ×xV‹%j¥X8ú?㨠Ư͎¹÷^誺*ðJ‹Þ7äuD¶e0T¥¡„ì:Uƾ'ŸͯßÓW̉o ;6º^òT,}kJ|~á×â¼Û§Ë~šÜ× ŠW¿ýo1wÏ«cêÄgâ¢e?C¿ö ì¡_ÈWL zïÐ6ZøêØX±Vömšÿ±Ó 1E»€f-Xל±GL˜øŠvŠ(ÙH‹H´ƒÚˆ®ÁkX¬·êÝ*ÙO»Œ´Ã…ÙQ_? Î{e÷xqÖ›1gü]1ó‹GÅ…j¡ªbv|gØqñü¿Ù:Kkì݇ÇŽÎ-¾†‰ Øûƒ:×VÄUŸ¹5ö¼ö¹˜òÌcùΈ lT·Ëgãµ¹Kcñô‡Ô_‰»^[ühÉ‚'ãÒÓ¿û<<&îû̶íú‘º‘vPi÷6Ò–©t´ ]Íâ›lÜÌûŸªn•?Úó#Þ›cwËZrÏ=öŒ}GŒÔÃÂËñ²ÆÏ‘ûŽˆ]k`Á!³QÝUZàYo#îEÍépm¶Ýr¯ã~Bè¦û!¯Ëby=CQÚmÆ+V| å;Š;ÆèúuQu’ƒÈ¾ºwjÑžvhPýÒ¸Æ}É#¾HF›7Ë·ÖÔ7èP»þéPcú ÷4ß·¸—Y`¤Kï}¾¯utÿw…/Ø0V,Ë|ÈïèþoZëƒN¹ŽÀ- yàC?Óæ÷ÃÀ¡¾¦'6_p\wx€!¸ëìÝîÿ9oÓ;mš?ø\Æ!m¹ ù¤ ƱNèçgÓ”Ò™Æz˜W¹ý‹}»”Û¿ÜÿÝ/Ü?Ëý¿8!¥xìÊÇ…Ø [9‡Pÿ‹÷Bì}lCl“Û‘´óÿ—Æú[éü;úôé£ã®J_h:üðÃÀù_wÚjë­Ò}òÀŒ“N:)óÙÏ~6Ž:ê¨8á„â'?ùI\sõ5‰üzë~v6ï¾ûî F[ðZ’iŽ?þøDsõO¯Nåüî¿£ÿs Ï~¼ªÅ‡WHs¿æ+Q|h ‹‹ômÿ* t6? 8oƤ2\©Õ®™ÚAóÖÄ 1kŠ& c_L<º½‡ ‹ÞÛ‰‚~eg%Œ€R\þ¼¯ùXùº¾›GÚSâ­7Æê}µWâÕ—Ç&ºnµG¯Õ§OwÒ/éÝSc[_óðƒ pdØgcýÊ{\LórÌ75^ÕBĽëÆV÷ˆÍ6ªskº¾í¡Zó†'{/M£ŒwÞ¶ß~˜&µ Z“Ñ/¸L"õ˨žýEÌn 9¶¤ó9ø%V_‹êqéW¢ññg£e¶øh×…,­ÚÐéÐý¢éѧ4Š×¼E±ê†?$¹UC¶‹ª]†‹/“ÝMâŒ_>»=òû8ù¼/ÅŸ~xRÜüØ¥±EïÞ2rœ´Oú¼µµw ÝlYÚùT»VÔ¥•ž{>µóu×ý<ª5á¤+õµžÝ»Êé7, ,ŸdÁã§×ü$íl8þ¸ã¢¦ªsì¼çñìSOꌜmbÆ”Q×­wS-é5´.Ý¢“v½ThÚÊ'¦e›ÒúëÑ5pÅ&ýšßUqS¯µZ\S¯…!µKñ ±Ôî\˜¾ò”ê_¡Nפsc  MúR ‡ýêšD2­b2)}ð@vÙðÕ±BkñuCºWG2ú%{u*ÔÒ‡ÚM¦Ð¦ˆo¶C|[ñéßüM<{Öa±á2íò;ïèøóœ/ÇGçþ>nê|N¼ºK靖 ö—ïÜqé±ÑOc±‹­wÝ4Æüåöøó¬¹Ñ“Š7¨…ǘÙçÞ{GœºcÿhYvP ­|"~ô_ŸM ”;ëõ¼µÕ²šp£WÒ½R¶yõ¡›cÈ7nŽõÐÂí°8ä„áñÇeäo½~Ùª¼ÂÅn‚¦±r ÙA¶I“tÅQÐñæ¿?ÓîZ(¼|LטxÿŠxfÇ%â£I{üréÃqÞqµ0pò—NË?ýF¬–‘Ù¸UÄ1;cçËêÙOŵ­[ÇŸ?=2ºË½ºíwPŒˆ¿h -W C‹ÝZ„`Ñ•þÀ!çã°sªR;?Ø‘WÙôFÜÝâ¸dÔi±Y7í~è±\|å^qàÏÄÛ×Ä[·Š‡O]U…î{j'ÌÐoÇmc¦ÅAGb#Õ_ý/_­Ù>ô‹8nÇî²ÑÆñÅ3ûÅe3¨Ûæ1t—Íã™GÿÍœ—áZhs)¬¶8ïŽ?Åq;õ×h€ï ÑÛý¨RþÝ,ûb#ÎçaÖ:а¯‡P¥ÙmÃôX®}›©³qÔ¯Êß8„ž–âË]ÍÍúÚ¡êð6‰‚Ý1"-ÚZL¨>À§(¹·ˆ¹ºëìµåË–(Ö"žú„\A‹0ª£úvoQÒmGí¸Ì+Xì¦*ˆ'÷£m±‘»H„ì)¡Íú|{A?°s‹×]YˆâK„={öÐ=tzÚE•ÈtðýÖ÷7ëÞû½ÿƒGÈyqÏõý8>ëû)e\íÝÿÑÅ´~Êxy™y˜gG÷ËFhÄäs=á`Æ1oÛ§£û?å¦Ïí`˜åÂ×åIH[Þp—åz#À#×<:Ù>ä=I"6~b ?æEìË:‚cérû¿ÿç¿rû¯÷kû.¾ˆoÙçHsì»ö[üÔ¾½iËý}ŸÅ&î϶q¹ÿ}Ê>侈¯a‚ýÐybü ÒöMû.4†“N(ÿÅ>l;a¿{ÿÇþõ¶ößÿx]?Bó‰mæÝpà ӡÁK—-Mm÷›ßü&ê×ÔëM¥qöÙgÇ™Ÿ?3fÍš¥óA·Mge²›˜6DÇÔ˜šÃÌ×ÀX$2 _¥ÚfÛmM¾xTÚþÔùƒ¶ÿYg•ä%çÉþP?Î2´_Räñ˜ík8±ëâ4y}˜¤èà(Þ©S§TÙܱa„0beüò]USî="¶øÈAëÊQÆ6ðz] ¾V Zd€lÊYÔ©ÒÄsãmŽ-v825*päBÃûg«ÅÏ4~@!ïŽ/;4•:Ãdçmö‹½v8x~Èæ0¢Uõú]86¼ è­[ŒC±…¿^ÝÒ3ŠvMˆxׄ¡JÞšCD“ŽV\õ‹è~ù…Q­sRZ´Û…Ù“òF½ÂÒ0î•èqîg£Óˆ=£Uõ®Øf3M94ÅãËO© :Çvûÿk¼ø×áqÉþgÄ}cψ³j¶Í¤H ^>ùO1â¤ËãÌ+®Ãöî[þä&=ȫ㠧¥b—è\Ã/˜•QÍ$zí Ñòk“&:ïdpF|d¿8b·Þ±ºñà8îì¾ÑCz× ·°±¾Ø¥ \.5¡xÇ ]úƧ_£µ“eä)Ä Ÿy¦¸»DS¤jvYˆZI¯ƒMˆKF~"VþÛñÅ#v‰EßÓ¯Æ*g ‘I«tÀE*´ß¿í Z8hì+èªÓË,1q!¬®çtâC7©®Ô¤¨°¡ì¦ v‡wÚAX*×Î<ç iG_ iN½ôú¡” ¡7õZÝ^ÚЄÞâÍkpÍâaZ)ö­Ó+L:7¨ë -õÙæÔ¤Ú ÖU‹'òé•ÜH:¤‰¾ôh­!]´QýDY]Ž^©3» ú´ºdÓ-HéX5¹§|<ù‚ê£q¥U6*p&»Ph$éËù[ò!v®¨Z ~a#½Å-Ëc…Üþ´€‚¿ÉVÕ½*be½ê' =õ}¡:ÙµVt¶Q]ÿ¾1_å…¥ãâÌÁÇŠ ¯‰ËNói ù7¾˜Ú¢—Þ'{?jÒ" ÀبZ~LÛ¨2ÕKc/‹çÔSLßîG ʺè]E>'ÿäS£ãåñ/Lj}öVûâ‰'õuí|ÜgäH½"šÙHmœüÈ6’Ý«ÔÎì Úfë­ÓýO8¸wò*Ô›oÎÓkbC£^¯%êå³46(‘ì“^űäKÍ,ÀÈÏÙ¡)­Ò½EÊ&Õ3ÇXrªÔXŸ~*Õ/XLe´Z o<ìp«««S{b‰¹¿|?Ǹç½ßû?t©OŠØ<·àï÷þ.tùÃ40ëIš‹€ì÷ºÿƒg^ÄÐZghýöaïÿæ _?¹ÞŽ­+18Ö‡¼ôè@™y×Û:ç1x~~B–åY×™9ñ Ïù“'”Û¿èËØì½žÿÊí_îÿô3÷9úû—c`ø¡Üÿ‹c7ýª½ùvÄn#mCS¶5¶Ä¦Œ‘Ààrž€úc^î«”ƒmyü/ÎçíŸØìÿ÷øO{½[û/[®9¹ž‹ØÙ53gÍLíEß–ùÓl’ÐSnôÕŽø‘ØmMÛØÕb˜ÛŸùºiàEŸ€/9Ø=s¿2Ü8æ ܴ࣠ôÀ ¶/8Ö'´ý(s o¿‡¾Ž+(0#Z¬$1̈ Ο•¬¥K—¦v9à‡{8l—‡Dx²Šf^¦§£s%cý'‚¼^Ķq Œ Ê ¡"Ö‰˜œ‹<ÙÄÔ…mIèOô£‘áG™ 9OêæººŒ8xÈ¢ž Ú(íŽhï Vk˜´‰¦UõXsÿ¨hš>; ;m=¯ûA4Ϙk{K4>3.ã'Få.âÛçNѯð•Ñý«gGí·¾­úÄ8¿Þ6k1gÉô—bÒ"‡ ƒVy-‡ÅƒZ-ÔÔ茉Öe±bÚFô §¼…N§ÆI‡ú|îdø5yJ³‹¶ú2uc¢Øµï¦Ñ:g¥~ÏŠ ã€OôŒ§ž}#ºo:4†éë&ûéË_œ¡Ð ÏlXuQX†ÂÚ˜ùꄘ¯s08CW x-«¶Kç~À±æökcÔÔÅzÊlŒ…‹ι/ÆýšéœtÜÁ±y×µ1‘Y`Ò§|Ûæt,y4W‡Lê¿ÉNûGó¤ïêÌNñ®ˆÙOÜÏW{ Ö : õ¸µFtyŒž*¼úéqÛU?ŒÊmÓïòéëQ©ÛêÏnÕHíZ¬ÿGë?¾ñ±X®ÉQÕªÉqû·ÇÆÁÇí ]ÂSWà äwÖŸ²l¯IV:C~Yû³s“°kª‰¯¼¤1EÓKÉÜmçâv=Ηé3¨Ÿv§h‚ÇN‡¶úkþ•ÚŸÏ K°&ݺ9µµ•ü ¥¶Ýf»˜8áõ¨Ñ™6…Nü2/<•¨·H/~‘—Õ/¤ÀÛêÏÖ¯‹ŽójvÛ}×´â¼Í¶[Ç.»î^y-fM›­:üõÝÎÐ`Aj¾ÎYÚ~û…§vÔ™:éó×ôGÙ ¤Ó{-ª=e'&™hC?z?¼ô•¬´ˆV­Åíj_άé,~é Ù¥’I{sMìû…ócÂωoŽÿvЖ²…úl½(æH>*êZ=åé¸U™OŸ~RlýÿØ;«ŠëÿŸW¶±”Ĥˆt¤*öB¢DÅ‚-‚Q“h4kb,C4ÑÄ(1ü Q“ˆc4V,h4 PDEºÈÒ¶··ïÿùÎÝYË6 &1Þ··M=sfîœï=çLó[€ð[…Œ£U–Ö‚L­¤R¡²äìZšudÆ•àZ Ÿú)Š–P·ƒúÚìŸ?l‹ Š­à“7íæ«g[kÀ9€ÕËG©€Òð ‰mÉßf¥ôɶ‚6ëÞ‹íÌ»>³Ûne-º²+£éöÚG%Ömà6`ðþ¶w Ú íÒp¾¾ì¡gléÀòÄf›õ÷¿[öØžÖ ~ÑHùäõb lö.³ëq¢.`*­]7;£êm»êæ¿ÚÂ%³íγFÙâHKǃ@@û0]s4\©‚8tûi‹uùh‰5ÛR9vËï_"hTô¡M½fŽ2n¸íÕi˜}'ù¶ÝóÜBG£âåÓí‡on·Óëêh¤þè!§ÍòYóüs3m  FrëB»ëçKí¼¾lûÇsìï€Wç;úg•ÙBúBJ°ºøDš/Tª>ØäúHã.Ƹ@Û~TŸeÀwšñêâ#½xa“ý¢ùómþ¼÷lÚjCp”=pÈ`·ã€œm/_¼”ùúxUóQ œRe@Ëýº÷tï0½‹ôîжŽzbâ(mÁ‚â~gÚý©Š6k·1 ¡® N”#@ZÍvšlÔ^»Ž©tä?Gú‚$Òª€©i¥à:LïOÍs©?Õ%KПgª×}ÿë«<”ŸòÑOçz?jq²»ï¥óõótóïuõÛ÷jÝtîè:WøWÞÿ>_†§©Úíé¡£ÊTð´ÑÑÓͧU[u®Ÿž‹Gê«›§‡ÏOitOש‹F¿8ôùú£êâëêË û_s]ÀÇž&¢‘ÎüQ4Ö¹ïÿÌÓSǰÿÃñ/>ðã3ÿzßê-Ì; ÉW>^8ÿ ‚è¡9G4L{<ù{_…ù_um¬ÿGŠÒêWðs­Žþ§8¢‡ü¤JŽO§ñ&ÞñïX?•F÷¤É¬4º®ï›J[å­¸ º¯s_'ÝÓµÊð¿/{þw†}¡ªˆéëž¿¯Š)øÊ辂 Å…€YçŽ("fõbÎÇsÁx¦ b©<]{B(½~ÊKÚ4"¬ž)/Ô¹îû´:WеòTð锦‡ÇÊK?åïÛæÛ§túùvé¨tº§ kßvå«üTNZfºS·Òv¸NJ`ÑŸêC€±NP+¼îWVtï_lûµ?³HiÈÓ÷ïoiCƒ±ä[tÐþ˜Ü¤[ÑÏceóQw³¢?>h[Ï¿ÂÊ}aC»„Dló»÷ÛYÇŠ 1Ć5Îò/¼Ý΀pßq(f:ëìûÇ ±«þ¾Â: 8Á,ÿ«3tˆ]ôð§6ªÇ<»àûOZ!‚Œ3+ÒBŸ/»Ú*6÷€‘vDÉÿÙÈÛÓËÊíð=h?n6ÉFh`G]ù¢Å‹t¤}ZîâC¡ÒLºØ¾9b° 2ÄŽ>÷»ð7¿°`({2ÞîßË®s´ :ÔN¸öU‹ô8Ò®<0Ë~øÁ6äâÇ­#~vî:ÿ*[ŠZ‹· gD@@ív;qŒ·?λãR»aÌQ6tÈ ;å'«ì×ìBÕºW G:2˜H“ÖíXûÍ…ÚOÏ<Ö†r²-lÛ—þ!X#ÉŽDN`¯n¿8|MWûˆÛ17ÜgnºÅŽBû=Ý–áÄøÇ'ì+,‰ÝÏ$4×Õþû?º ˜ðý/i1Þ âƒºI¸Ò¤¡@m-+#ÓþpÏ=vÉ¥?°SOm§Ÿq&‚[PW·?JÁngŠÀ§-ËÅô íéÕ©sg‰§ÖKö<×ÎdnË%ÄÓ×UQ4j·_Z"½g䔫/¦‹å8Ý-+)²þýûÙ·ÐèØü¤øUŸ2LRJûi˜Z5gÇ ù·I"LJFš.Ú\[0;_5ÐM¨*¡c0ù9B4òGí(ض0N)ƒ¬¹LZ4‹Û,”¡“„i9mnyÀÉöÓX†µúÑ÷m(Ú)iM¤“j+ôC5bÍûž`?;¼¹éÛÆö:mŠí;¦·Ý}J]Ô'®ŽÕ4"¾ÀKt‚yT “â°“Ôqw¼—u9ä"»òþ7쌮ÍÉ„¾€fš/D#²·- þj=ˆ×¦ûѶýŠìŠã:XöÀQ6¾8«æ˜c¦X—ÓûØÏgó qÜM_h7©†ø(&›'€6G#™Qñ‡LG+ÀhöŠFªÏ®|Þ´»\ïþ}íL&‡âð®`UšyC8?õ”S¬æ¨Ø5Âc<ѸjA&ñmm… §Æª„ýû¥ 檟V „Ìnž×ø 4ÍxçÀ為ÕÞŠÌùÔ—òBn¼'0óRÛðOqéE½{àAÔª[‚v6ÄKï9½Cü{TõÐûKï^5®t®£ž©¬Ýyÿ»y¨:mê¹òÒûS÷T–¿§£B]ïŸFé|뾂?êÜ?×Q?ßûçj“‚޾¾®:êžÿéZÁ—©ûþÚ¿ÿSïé¼vUŽÏ_môyë¨<üsŸ®SÛ ´ºV\õÜ×AeéžB]G_–¯“öØÿâñ‡Žáøg®dœéçÇŒŽºVÐQcH÷üu8þy‡2'Õ¦“§¡Žþ\qü$úyÞK¥i8ÿ¼õu™ÿk©ÿöþ÷<ûß4þ#³gÏfSŸ"¤šR‹öÏ4ÐübÃßóƒS RÝ÷ALè:ú*tMyëèUùú s*õ¨g*Ë—¯£¯OêËG÷}йÊÑ1õ¾¯üT¾êìóöíQÝ|Rãè\¿U«VYÇ}Ø­5ó ¶—-ºõ5>é}è+¬Ûü¶» Sάº[>0 ¸Û-¹h…Å<ÈbÅ%VøÇ‡,ý„cÝWàd¿Ñ×ùBšVO=€Z?ò‚G²¨qY–ƒ@ ¯ÈqâÈ‘fE ‚s¢Â{²³“>*23XÀ³;_ª³²èO÷4ú²PGè”PŒ”Z e343hôBó Ÿ¨€dãcA>˜Š¡UÐFIB ô%W$¾ýåE[1/C mÞÊZ²°o¿dô$W*ÍÙ œw¢øBý8ß”©OZA-$à €Dµ­9íŠRž„m¿ìÛÅœ! MKpŒ›Ån:ŠçÛ/§»ÈAœw–”²»U$Û22Ó͸öÔÝ~µ.¨“̆v´?ƒö§7¹ýõõ¿ÚïvÞ!oG ×~µ…“xP|ƒ0.Ó ]¶?ÊöĤÖÎ2êÿr|à¼õÖ\;æè#wj¿”{è:À]ÔÝ~ñJñíO‹g ðÉWÂr=ý/_Lk>]kýõ³ÜÜhI3O}‡ I»$VÈÏG• „Z™ &ЋÃ2ûë_¶üàb'X̾ë_ÍA÷Üs¯;nœãÍõ­BãE¦FF Sf-ÍAÚ&í³Vhƒ© /íæ„BœXú¥¾—pÚI›x,ÁY*ز»Êò ò-ÿKãG`" 5ÈXÚ?M£Q…mgˆf6c\Ò’Ùd&ˆÖM4Z‚³kêÚ è•¡ü¢ÌAÉò +dµj•±J‹ ðÁ’ãhT°èOÖíØ×íÍUS­_†êÎú¸~1ö¡…æ=œÅ¥l˜žÈ´¬–"B*(›¹Ìši7´zh„Ãc™§VUäÛV棜fìÐVC#սȚgµ„Fe8dcîÂÉNSø(*>ðb@#4™Àn×&ê »0¤ÐÈ9HQ79gX"µøˆ>TßEáYÍYM¥Q,3nŸ­]o ,°N:´#½_-Z„æÚ`:ðË Eñ™ø >Ñ™IûLqÚQP€_¬ MUÍÛ²§ƒô°‘ðD±ù3ˆŸ‰!™(oXû}Ul:tpåû÷˜aþ]¦÷œŸ»>¦Žáû¿þ÷¿_çøõƒ_3è¨gZkè\ÁÍGÐW×úé™_·è\ôý¼=¿ïÝóùè\ñÕOþ¹ï3_žòÉ ßuT¿ÆòitOç>èZ¿°ÿ™ßªûFGѨ¡õ_Øÿy¾ó<åéR›¿t->û_YÿûvŠWÔ.Ï3:ê™§‹Ú­k/=ÓOÏÂñÿÕ–ÿÂþÿbãÿÍ7ßtN¿*ò¿¶ÿ1SxƸæ8_ÿ'ûß9V%|𓎚h4Áø£î«òz¦Šë™ïÝ×sŸÖOVºVP<ÅÑ}O«~bÓ¹ŸÜ|<¥ó÷}}ý=-L|z}<å¡ |ý}]û S÷õÓBÉÇõmR|ߟVå(¾Ì³ödEåÒ°‘Ȇ{]>$ØeßtUüóu‹x>D[XÉU¬jSž¥oøÌ¢=÷³Š9ïY¤][köݱÈsÐ3…Ø~]-ñÉ*œ t ó ­Ôåw!«9æELü|~Mð× Žê «ʘHk¦œ]€pGâ´$ eˆæ4Õ¯ên™}ðEVTU¦á˜X@ ‹ºJ-Y8ª„‘¤›êC!JšÙ04l#G Î‘,íG<·HZ&5*WÏùš‰¤‘ÉNFÃQ432IÀ8¦m)§ÃÊø”ÃC #õµÆœÊj â–-0F`‘k3Y1†TF*Ðj èI~TLóޤ5³[Ö¦Q™½xõPûîc˜!V‡+˜c=›i×VÒc¢šÔW&\ªwFfsQ‚±º+š1·UAÌzi„Ï¥D%£ ½…µÆo–ÓPª¡@6à«æ¤*®lvŽj*¹ñB 4‚*З/˜ Òeðˆ´ÍvÐHxVB½)5ëž|ê8ÚÿÎLÞ“öøB&cT ?Xü´ËíØÞÞ;×:vDm w—ÞmþéßmþÚEâÞÕoþ®ûáû?XSˆzÿ§ÒQ÷´>ÐÚFtók ÑM´õ ‰žyš‹¶J£¸:*¤öòôùùþÐs+¸ù£ï;]ë§t>­â)Ÿ&5•éëâóP|_žŽJ¯ <ýQ÷•FÏüZéë²þ|»=Ýt/ìÿ@÷<åyGü‘ʃ_õõØÿÁûpüïx†ã¿iò¿—±Å;šôÎòï?5Wø{:jññü|ëŸë¾¿§w›O¯£~ЧŸ‚òõ÷u­>ós”žùºé™ê&ËÝSþŠçÓþ§Ç)hUåü"ÁB•õç¾ñjTê=Oß žý}ß`ÝWOHO0•™šÆç¯ç©å+?Wyø¼W÷Rƒoƒîû<ü¹Ò©î>¥ó÷T/_?Ÿ¿îéÜ_ËŽòì‰_‚¢"ìvpÊ))A j}¡À=¤«ñT°.pi­xòŸ,ñÆËÿ‹À¿Êò•éÖÑb‹&XtÀ›q¤ˆ#ǘÒDpÅ‚]ŸâY™×0¥ûÈ!A/¼€%Ä%žœêê‹­° !ZÕ«ÚZ‚AQj‡°` j@Z$Òò  i}Pe'¤IC@K%øHÝ>ηñhu‘ŸJà'A‰:!J“¡¦ý”x¢ªaûëï?ÿýc }ž·É²[4³!ƒcj0o$èǼ.G®QúM‚t P‚o ¾ÔÃΔ„ë¿Ly˜þmZ8ÿ¼sż.ç£Äyø…‚ãâN…LBvI¡ŲðåÂKAã~ÈAÅœ6ƒ`“¸ÀÚ SÀ˜Ól æ5rœ]EZµÑ¢e⥹Á„ð©zCZe:“ó^‰ÕI;<ß LÃÛ+uF€‚Qê«awi$‘æ6e´AíU th-í"èÆ¡„{7?×E#4ž¶lË·´í²ZæZ›Vhf1´Ü¼ D#¤Q|ô¯Ð(#-nsß{ÏùM“£a½3\ÏÁSÒÌT_uèÐ “©fNsP fbi·ÁNN("ß6Œ#vNÛ Þr&Sø“& €4‘“BÇ5?“wRZˆ´¦¬¸ÌmÑÞ gÇ Žwyîßcºçë¤s¥÷ïN?§(Ž?wüDÝéžÏSe(¤¾Ë•Fy(ÎWýý¯¶ùu„h¢Ÿuþžž«½žÖj·ÚìÛïŸë¨ t¢‹âëÜÓÞÓÓßóñ”ÆÓÓ—¯¼}¿(•§ sOs]»•65蹯ƒ«8þÜ—£4©÷|}U¦BØÿÁXíÂþÇ¿ø@cD!u ùqŽÿ¯†ü§þSŸù9Qs`8ÿ³ÖÕ¤ú½'Õ5ÿkkùÑëƒTÿŽý;.5òmõÜÓZeè§øþÝæÇ’î¥Ÿ¿ï+_ŽŽÊCyû|”ÎßS~º¯©´ý·ÖmºçËV\Ÿ§òøw÷õ$ê|õQeü W窌*ªçj¨¯¸¿¯g¾ò>ž¿§ôJ£NЗE¥õòé}~Š«t z¦4ª‡žëZÄ÷w‘êø£ç>_=öyëèóRÊ[qu®øz®k]+®/Ë—Zï%K–8¬1¾Ä—¢É!ó”T$žÑ`}Á%V ”q„+7 °±,Cø°!¾|0;Á°Ð£\gÂ×x9-¢9õw©´ `q>[$Drœƒ¯×Ô_f <È+®•¼ò¤þò‰êZãä8ÚB‰ *Gµ¡†îËs ASµc l1t¦Â ¡@8à s sdã¾êò™A Bõ¥7lÿ×±ÿ¥A±héR||g™ÍšÃ¯hLÀ匦el¼è†6ü)¼D@IÁRNQÕ9X|è £„xÚÞY«ÀëiÁH«®ÔxˆIÃf¬’ ÷â (á%Šp+ çP™ åŸH»êÀÕ˜tžòñï<½SuîŸù²Ã÷ýïÑÙÓ^´öAt==kÇMõ<µÿSé­gú¥æ©<Gy×Õÿ©eúzøu“òJÕÄѵê­4ÊSçaÿïþú¯v¿zº§ö…h];^ØÿÿëÿÚýöÿ×Kþ û?.ü{ο³üüWûݨûz'Iþ×:dáÂ…n»mùÓóï$?_ú£òöï=åçß[þÝåÇ\í£Ï¯ö}]+mjÝ×Ç÷÷OçZ7í·ß~TÊÇ_ïI/ÿû8J›ú®öí÷t©oOÍÿl˜|ÝQÊTö…úJ©Âº¯ gþ¨û>o´§ûŠë«NóçÊWÏRËR|ü¹ˆàƒ¯§Òè\y)O!›ê|ÝóAeè§8:*~¾l•¯´ºçƒ'ºÏOõðå*mI®Wq3ü®o/ÕižSGŠEž$o®ùQ2'탄'°EÖMUqhê|àƒ@¾@Ô CCêWJš$}…+_ûåøVZ?’<À¸:«!(ÖÈ4D½+ §†/¡4 )ë$eÞîü~`Ó¤ ÛÿõëÿŒSŒ3â ÐV9­­¸s@ *ÂKnkrñ lÁßFæqŽá9¸ Lƒ¿"ð¬›&ó’¶Zvj_:ø0fdv$>Õ« l,½>)‘ñ"Ó*¤QÌ€ªÐòr ¨´É¨^œ`ZA>i*äC}HÏ-Ê%4Ôž8™k©Bb4ÉJÚ Ay`2'ø‡±•МE4V*˜{œ'!ê/çÐLF<#eh×-‡Ó„4úÚÐHæM­[·q •‚‚çIïÿ^”³á¬¬,·Ð}½gô^R½—üûMÏü;ÌŸë˜úŽR<½ÃôþÒ»­Y³f5i|>­Êñï6ÿÌ—«gº§Ÿ×ú÷¤ž)è¨÷­O£çŠïãé¾£<Ô–¯òûß÷èëMŠü¹Ž¾oRiàé«£âè™§‹ïGLþˆ> ¢™§©Oçiª£§¿¯âúr|êݯ]/W|>>ê¥àËUº§Ÿâ¨ß||ù:†ý¿Ã/¢è£Ÿhæiåé«£ïÑØÓÐÇíÃþ×j2¢‘‚h惧¥hæyÒÏ~½.~ôÁó¯âø>ñý#úë·;ë•© :…ã?œÿýüêAqñ–ç/<ÏêøuÿÚ™yàÀvÐAÕŒëTšiŒÖ/_®Òê¼vÐzM;S«ŒÔ>T¼ÿäø¼öÚk5†}åT)?Ù‰ µ¯}£ý}?©é=!W“¨_@xB鍸¾“||ý=Ç×Aåè\étôét-ª,MÐJ«||œÔtº¯ç>¤–§ûÊCG_®Î•·Ô£úöíË^ðí¬´œ³‘ø`…… B[`û%šH^Ó®5ÈzÔ©„Ȣǎ@cÊ\/öN“€6Ix”? ¤2áC×$BkLZ€+r´«T$Ð2¸ôQL<$ºjëmÞPb%@ŽÞu2GiT¼m›Ày+)‚v%yË­‚„r!=aû¿~ý…?äÍfÉ’EvÈÁ#¬Ã>Û8"Ÿ »Ÿ9¬`ãUÂÜp(¼)¾†[‰W/iŒ¸íŠ564¤ñ ’ ¶àfl»ù»zl0v4N¥q#Fé¾(¡²Ã¯J¥µ£#7ˆ'pHs)Ðl©$4 ð©;r,cܺñ©±G½dêgàé¹vÑ ÃHåÈä.SrùÈ ©J>MŸjŸîU¹Âõ, †œ;k܉0!¾^4Šƒ*®[·ÖÞxãMëÝ»·{ù÷Qê;ÈŸûw—¿ù…ÜæÞGŠ—šÞ‡”÷¡Þ{© Ôw\ê;Íçã…ÿnTÞ*SAq|þµ¯•¯žùûî„?©éý{YqUöWùýïÛ§£ïµ_´òmöíUµY?W÷R饸ºVÿÌÓI}èÓ…ýÐPôòAçž>:¦Ò]ôòcHô}œÔt©´÷ô×Qi|?è˜Z®/ß—­<ÂþßÁ×¢h#š(ø~ñô ÇÿŽùR´IåAîy×óX8þÃñ/ÞPÐ8Ò¹ŸÏt®_ê¼¾ÿÿÇçÿ™3g&ýK.µãýD¡ÅŸîëZH´?÷L$æñçb*=WÐ=¥Ñsý|~î!<"æ‘þ娣ÏÓç¥´Š¯ çþ\Ì© ø *SÏ|>:ê¾òR:+]êµâè—ZŽžK%J÷´5ùš5kœ*×€ZQ~ßìi'‚XS}8› }‚@d"²WBh”]iº8WGÀ¤Ì‘ˆÃ2—†¨Þ LêG 4d¸GR™‡TÉI*ììÌ<Ê®íQ@íJå¶šeç Ë51©¯üÎÈ ÃíÔ$„ Ç9TuyH¨åe #®Q÷”…°ýø ÿ Šòmýêµ–»OG;戣¬¸¤\Üä@?ùÖh!c„Á·Pa'>ù‘ÖK¾ÒN<ò¿"À’@¤ G™ÅåøUiüÓ¨—#\6n'-üOZ1nLŒÙ9ÃÖ¥!7ÒéŸÊÀËx¦ÒAþDô*8€1qòÀm cUcL}I+)C¦F 0mÓ|Ç|Iëfn¼)„k¹2¶C}}h$&Héòk3^³õë×Û>ì (mÿžJ=º‰›?zßè=«àßozGê}äßYzæß©:W> zîJ£ûº§¸þ™ž‹ÇUŽO§s_†—ú犫´¾^þܧU|îËÖQ÷”FÏõóù陂¯Ãûû߯ |_¨Þj[íõ‚îéW›ÎþZñSƒ®}>:úàé¬k_¦ïå¥òU³c”Š&_W_á-ŠçTkTeêçÔ^(‡:JäRf‚¶ÅÉT§”Éä3XÚhZwaÝäêU‰¹£À'iÉ©e/Ò¨hí$¼©rH£¯)ÒÐÚŠ®Z¹Ê–.[j›>ß$}½ô½@IDATæ CH!B „)R ¤@HF))**Ä¿&RÂG`¦à>“{H%’‹$œ !qp@Ó×b}t– åþðL_ÂcQ=Ö}¥áœ¿AP¥ÕfÊä/Éù©l'À ÅTÞ”ë¢êO`æ áIutu­ÎÚ¤²y$aÑå­gÕ…’‹¤$ž)Žo‹*ÎÿêJº¯êN»%Ø”êí¶È•G< j!¤ÒèëÎGÚ^]þ‘ÊËÑ pZa—T€ÁXcd ?q; †zC‘›8oò&zÒÚA!ªÿ˜ø¹-—¹%ÚR*“†˜ÆbBcW&HD\¾ R4Âäƒ)Š“`ùr8ўƭ67KãóLP|uÐ#ç`Ž%gÆ$¿LL0Qê$å5@š„TÍWüwš1²Ã‚¯?¤×srÓü¤9E»A‰Gt-Mþ†4úšÓHï·X,¾‚OÄyzÉ„ï5ÆKøîgzÐÔÇðG¸>bîô4:!ܻȭKÃu6´Ð"C(‹¸É#+'¬A´ÓØ iÄ2ˆiD”FZC“P¦s|µdÚx ÛOK ÙŒÆw”…‚„"™+Äù$,ç·iºFÓÒBeèj:\_Ï\ôuZ_¬µÀà<Ø9‰Š/Û¸p‚–/ GNÈqò‹4÷•]ßÁ‰KÁî‹´+‚pXÜ'(+¦/öú:Ϲû‚Nª£”j±‹¼¦ƒæm—Oâ“Ä R•¥Ü´-/¹‡¹…¾Š+ÒØqŽ‘ò’nDEã8¤ÐV»è½Y< ¥úº®èúÒ*„4 ù$Æ»œfkÖ×ûÐ5i§H¿C4’Š‹Æ•üÁc-ˆ‡»QN4ß0Ö¹Tظ‰ÍøÆjR¾`´å5yÊ0I¦Dq¼f»ùˆ{šâÒâQÙ"3AíŒV%¿Pš/d.E1q" GI?1[€û¸‚Ü|$& n>a>ÊDYÔYÙk“coù}ŠÑPÝ &ò¡¾‰eÊ¥]°Ü|DÝÔt™‚‰FÁ|ÒèkM#ño¢Ä"åá{Íù† ßýÌ!áú(\C†ëìp­¥ˆ_…²H(¯Á¡LÊýÎ¥ƒ^j°‘ØU×^;A ‹´\s2O@(‘Ü¥/Ùx¥ °0¡H*“DâΑ„"¨r ¸‘€ƒˆâ -<Á @wÈÙ•ƒÙ€®ÉÏÆ”#3 ÅR¶”Gžòù ÿNÐs™¶¾V…ÄÅÄ,ÈeB¦|éŽP~L‚™»M~ªšŽÌÜv®`&9'HÒíÄT˜¶;!K‚›kBíuu‡”Ò(¤QÈGáX“sb!0á|ÎÙá{-|÷‡ë£p}®!Ãuv(‹„òZ(Ó†rÿ—‰D°¡O&øÂívb'#mÕ¬¯ úš­ÏÝUΞ€% —rR“i_›+Uâ|A—郶°%@‚ Š*ìܺÄÙN:¡’x¤/î ¾–KãFhP%ÀKšîâ‹¢Š3¢ÄK Í£¯Ü/¸mw+UgW4xô…œüãË·>›s­ìt0 1äóWf`ÎÖCu“ G#áQÔëEršIqè›Dc JjôÄ«’ù…£õTÝvyUk@ä·C4"wG#·Û”€«jÑô½hD^¢h$•è(_2ÿ ØUWPà;zC%ç¿CåË?ˆÓb ŽÚ$0N9£æ­AÓè ]ò€ì9Wß9n’¥¶ D˜œ÷5ˆFÎì&¤QÈG ›p¬šá|ÎÙL‰á{-|÷‡ë£p ù?±†”,âVÁ,å·'y_š,Êk¼;B™môPîÿ²åþ=1Ö"›6n4ዱóÙ€€]%Çhž8Þ™&’³µo­L“"€*ß½_ QiÝ lËŒHêlóQoÁÐ t’äY`Ä"˜“€!bC%@QŒ#à4w$ÔKÕ&JaQ÷…PsФT†À>€(“R+¥NÈ=ÊÐQ M\¦R³‘i‚ÒSi üq8ä£ÝdHˆ$xBÚ=ÕšÜÜ4 !}øá"ûtízK”—’3í‚æèô^TÓˆjȤCê‘NHwqˆFa4ÚÐ>€µÏ@Ÿô|ç®*KypM;=xJlùª_É( ‘h@ñŽF*ÀÓ˜´•ˆ'P'I>žF i4©Ö*“²Ýe‰u¾ ¶û¡>žTrPCUAý Àãn ÑHi?Ñ(2úÎ iÒ(ä£p¬E™+Âù(œ³Ã÷Zøî×Gáúèq §Y§NûÚ€ýû²FÆ ëô/C å5d ‰GJB™ù y-”ûÿ«ÇZ\B:3… t5·ƒ‹}i¶8¦–Gà·AFŠ #á£9ƒ`-Í!tˆù¯UFÊ_Ф‘£ODnr*͉¨üKÀ Q@Jiƒ$+8§l‹Hši,J™¨Tè31Î]9 yp_/Š'@üÅ>•Ê‹òÈZ$ßJ«)“´Za"ìΑ,Gs„2(pPÞiœ&y˧@²ØS4J¥ÙÒ¥K¬° È9ð@ËnѼ˜P=ÃR ¤@H!B „)R ¤À׉’‡ lÅŠöÉ'ŸZîûº¥_†,ÊkjB™6”ûb|¹rÿžk )̨ÛÉ|Eßd­XAË")í‡4viAFLYEИú‘\*`z=à!/57ªÐÎ@µCÊ!¨¨ލޱ4î+6[ÞJF JLš3UÐÞHÈ#'èPTZ-äáÀâ@Rvx©ä —Àá°¦rò–f$¡0× Ò9”‰vðÀù§Q]¤="°ˆ¸U5@2>Á[µ'f˜eq"…'{ˆFIÀ¡ÂâíÖ»_oëÒ¹«¥±­oB „)R ¤@H!B „øúR ¢¢÷q[öÉ2ëénVŽÐó%È"¡¼Ê´¡Üÿï‘û÷ÔXs^B¾ÈDGvUÒTŠ"GšÒ®q[XËTˆó`{l4X…­n……D«ÒVh4ÀÉÑšI“rà Ï™s•À&¨¼Ää F Lgâä ‚FŒ" øÁ\Iñd D­äã$!4 ¥ »ÍHPˆ‚“ä›Äw‹lÌœO*®z%ñC¥ÓÊ—‰ O¨õTªøq¾VØeÆm«ëlŠ”Ûã—Ô†v1r»ÙŸdj”ö"øèiÕ2'j mB „)R ¤@H!B |Ý) ¸-sZña\«¿\Y$”×B™6”ûÿ=rÿžkq9®•Z·ý­@´NjH;FZ(:©‰I$)ÀÀ¢øÑÛîVÔÀD+Zˆ+ÜEh¼ã:íjÕ}ð¤!3_(ƒ ôZØ"[ŠåÔ6Æsá%U€21iå(/™:ig&ê #+Jç?õI‰cþ¤ M¾k#h){°÷\ªƒj“|»¨<å+6ÚÉ¥² Êå&ÇÊäÁà x u¢mòñ"»hµwOÑ(‰I˜ ‰É60 !B „)R ¤@H!B „€’ª›ª’•|¤Þ Y$éd—X¼VQRb›KJ-'»2Pv¼†È$¡éZ^C2tò ‘ïó‚„9ÿ’dÚW­²ú÷$ÓŽsªµïÐÁI¡ÿŠLûÊÌW­¬´ÌŠéç²²2«¨¨´òò2+áú‚óÎ³ìœæ52mÂ*_e ×î)™¶!¹¿ª%ŠÏ7¿‰›•íÖ1ç[Ð0@òß%÷K©C~Yå‘÷?%÷7D£ÝÁFâ m;ú"“#X ®Â_ Œ9~Çñ°,ûª¬‚-œb˜1©`9}­B Fš.hÓ`ŠDjÒóW éªÐt©BS'±ª-¬b‘„™ãg¤Õã´_mHšŒ‰TB Š{D%tŸzE™¸Ú=Š2"€;‚W0ž²¤#ê$ÿ8I4pªð C£GysÛÅÜè"©öP´{ÆÈK0‘EQ®à ´JÕ‡ë¨l¢h£ ©¤ÚUM#iaÆýÆi$?=š$„!¤@H!B „)R ¤@Hÿ% HNyòɧlñâÅÖ¹sg;ö\vÇÕç߯CD² ƒÄ#'!¢T D×ÊqßNöòCRNº8›´ücæëöôÓOÙØsÇÙ‡f‰2Ä\[ 09ðB¶ìiy­¨°È^}y†ÍŸ÷¾mÊÛ€Pá, ÚµÛÛ†lGwœµl–¹GåµÚ4Z8`ˆ6q‰[ye‰“o›gçØúõílà€þM’×vW¦ýtÍ{éùíêk®±V­ZÕÛÉÛ¶m³;{§vêÛgïvÿ’L+q‰P ‰™º’c„£vY~þ/ò0â@œ}:äÚ7Žûí6+M”cÙ‚t+Å„/Qî‡Ûò?µÂ²UàYVRšoé™Í,íK–û·Tl··6Ì´·/±-¥›œlß:½­uoÙÛn„µÉÚ»^¹¿6ý7a#q·•µp,˜!9L…I¡‚ð®ÙÈ!øWAãE; Ûk˜HPÄA&° Œ´\À.]:mËM&òÔŒCž.sv…ÇÄ•§à]€1šP^’¢p$ …-ØõÀY,É{pЊI'ty˜R!äL&V0¨Ì±´yD÷ÈFå ivˆ&q¦]´3Ð"ÐEŒìráT[yËwŽÓ"îGË>¶W_yÉ>Zú‘X‹-¬wï>6rä±Ö½gï Ý ¤¾ÿö½ï×ii«óT¹-¶v€¸©¨;ñÕ4×^޵C%ZF<òHíÛ5×'Ÿ|²µlÙ²æ:< )R ¤@H!B „)R éذaƒ-Z´~½•––Zff¦uìØÑú÷ïïŽMϩвxÿý÷íŠ+.·Gý»-Y²Ä Pwä”»àü#Q 9LÒŠÛ9ÙG‚„ˆºä5‰Y’×øÜî>¦—WÛ¬Y³,//Ï^óu;ä°Ã-]òYHüq;ëîayíµWß°'žzy©¹¦öÚk¯š–U>½õöÛöÒËÿ´SO;ÍŽ>âèY^Ó7tѧ6¶æ¯±CF-ÑܾwðÏìê+¯°ù­~Ú)¶w»}ÜnÌ úVØY¥|Çh4E¦Ý¼½Ü¶nC{js>îdÂBZT‰Ò—+ª¶ø±£2rðºµk,oË6´pJì¼±ã Z••£ØŽ ¿;rSiA¬*-fùù+¬9¸Uáö+(_bí³‡ªhöÐV)/4Qîw 9Xç4F£÷òÞ¶V?MNw,ÈÜZ¾ÅÞÍ{ËÞßüŽ}³óÉ6¼ý¡‚jäþúø¨¾±VQ_;¸ä)…¹ƒñ؈ÜɸŽe€*4¾äCF._´SuS±©ÑÀH€"tn9DLƒxÚF;Ž3\¹Ûuà “„4e¤L¾KeHÞõÛ_ÛŒWgØÙgmãÆÅ @$J*Zî¶æ†i¤¯£­·Ô8-7S¸bi€ò 8 C•Q—*ùºÑs rÌÒ¬ *–‘´ Úg7Ø>:Y¿þ}áO¡Ä-">GH K*ê$ù‰pªW%y |R‡;Us„–ƒä­ìÔt÷ï~gsæÌ±æÍ›[VV–SKÌÏÏ·×^›aÏ?ÿœyÄávÙÇÛcOñ¯%~jf+Ÿ½Õnß)“L;êüŸØYÔÊV>kWüú=çϪëQG¤nå-ù‡Íœq°Ð$°¦Ô–¼ø²Íä…qÂÿ"XS²Íþ9s–•V ÀšÚ× Ðñ_yT»‹èÓknÇn9%ä;Ç~|á¶+äÀô±éûùMSÌFŽ· §õ­IÕ8/Á³àÙ5IÌrúÙ¥?¾Ô´ÑĩД8fekfÛ·NµìŸi#/ºÞNÜ.È¥©mâýÐon·Y©uÊewL8ɵ½ñ6¹âlù+÷ÚÄÇçú›3ÈÆ_÷=ëÛRíjZ›[ÚäßL¶ÅÛ|6mÜõ—ÛˆÎA/4¥Ÿš§imÊ·WîŸhÏÝA˜A£ÇÛÅÇïNÓÈG}³Í¬é¨vé/¯Øíþö1«{Ûñ|ƒýß%·ÛÐ_N´ƒ¶?e—Mª°_ÿö,«kFÜ‘&< )R ¤À'@oݺÕÕ)=-Ý®0°*mYK|QymÖ¬7mîܹÎáÔSO·!е YM‚{æYÊÚ¯{OëÙ³§}°à{â‰'lî»s­šL‡óñ{wå5çZÈP£ª%iSzzkÁ|ûtÕ£Ömß1hØìÜ'YVZ+;dÚúä5 À` Ê´ë×®·§Ÿ{Î~Œé“4jY4ÁÒs€¾móV•؈Vcé›r{eÁTËí“lM}3KìÏsN5’6bÀHûíoï°“Ocí;u¨ñ©#+Õ•žoP¦]þi²°}ëÙ¥e&mõÚ-¶…ƒRvt–?Öâ’|ËÊ(C{§ÊZçT×ìµ×߀·b6âðƒ­Uv @ÉÜBê—û›B#'‹¡eÅÛlSá"À ³’˜5Ïn‰r«(/·ô¬fÔ-î›*÷˧™^à‡÷;”J£m•[íÅ5Ï8¾nÊŸW×>c½szÚÞíÁêç£Ô±Ö6"å”—^ú§›Gšâ“VãñŸh˜>õà Æ^°‘¸s´ŠWAGË\G $@CÎ…+eº#¸ðDàF4 ±(äŽ;ï•þÐöÝ·«›|;ìÐ`@N,$äç´WHŒÊCРÀ1Ï,UL0ÎQ°& WÀEé*AТ2WbÖŠ¡ò¢ÉDPR„ÛwßNVPTh:´³ ë>§.K¬wŸ>nBK0b÷(ç_‡2z§*¨9j®“ïœ(ùÊJŽ–Dƒ(*[¿¿û÷0äÛ¾_rÛØÉ'lƒÀvãV“½;ÿ}{í–fÍšáÄ©ÜÚ¶Ý {Àò€fª›£3Ü/¤Z*c 9&LbÁœ<8VX£*zTNHz]á¼óÆ¡eó;í´S›Ød¶TÙ™ÎQr]ùíÑ{Ð)i+˜,µ¯ýý=v,µ9O²iyÚMãiPshÉ£·Ù}37š„æŽd•?¶W^œ·SM*ʹÌélƒömKÿrΟö™0Qaå‹ €¤"¦„´ö6lôqvÂð>fçÚ½“·™Sc2 í>™?Û,kí㣟5å*ž*–)•»¦º„ÐôøMËuwb•Ù³ÙÛ{Ùùg ß³Ú=0^uó‚ Õ¾ÞjîFÜÚ}˜Ù<—Eà(;bÔÁ–›Yh3¹Ï^žû7{áš|{ê÷SL"|®›¤R n/µì2ÌÆœ~´ îÚÊÖÍ~Ì&O›o“›c¸xDMFÇÙbM¨Éìg\u®õ¨\jS&Mµ—ï{ĆÝý#ë s5­Mìþo·¹¥fG]ð;¡Os[·èm{}MîŽù§ mJ¬yÅ59ƒFÛøs†[á¼é6ñ‘Yöà Ëì¶³p£)múÛ-5%¹vöµãm@å<ûÍÄÇmê­÷X‡»¯¶nMlS“ÚÝ„6-înÔô ùΈæöÂÝ·ÚÌi“ìÑ®)`p£ù$löä¨9ú¢›ìø½VÚD¶É7üÅnþÃ…ÀjhÛ4'¢ÉÏÈh:u²gŸ}ÖÅëÑ£‡í³Ï>»Ýù%Y¿~qÆ—67wo'7 êÛw@_WÆ’$hK^‘HL&ÒÎGbÑÇå´Ty(’d=0dè0ûàƒN†Ø¼yó.YK@ô@4† ‚ü…¬RκT›¸H›ã Èk…ù…öÜsÏ; æä“GÛ AQîÜJœ÷ï¢]›áü¦üå/SÖÍö°?Š#UöÌ´i¤{Ö† ˆ–iK÷a¾ÉòšûФŀ\Y›F9VV^â@¯m¶dù£Ö² Aú,vÈ´í\¼&Š7(ÓN{jš]sM`úô臷XZò´M ÃJ“Fô¼fì/lÖO^²ÒÂíM¯¦uF±ýiÎÍ®¯¼ò›8ñN»ä‡—"?!—Òݰ€“v¥y’ ë“iÛïÕܵ{·––™µ­[¶Zq"J¥¥kÛ¡# I©­\µÁ6n*¶vm+¬ý^Ù˜¢½lÅȺÇ?Òš¥§CBø‹2ë’ûw‹FÔ¿¸x³}¶í}+Kæ‘_%þ‰ºXËæ-?rÀc¾g½Ü_YhŸE„Ô'÷K¹#*^…«ë¢ÑìuoO¥õiÓßú´ L µÓó³ËŸpãàä^cSB#;À–m]l³×¿a£÷;‹qU?í4Öœ|_7¢‘tÖá§ÛÛXä¸1äJªÿÆøñÇOßi“#p&`#ñ(Ú%•8ŠR=¬)‡Às‰Ñw0ž4D8d6ñΉÎÎS³=ôP*y&CJïÀ=0¨l'å¨ncŽ(ÓÓ!@#-qFI"’3ÈWè–æ'gê:æ‚¶R’ÀNèq'UZ'´jÖ­ß`ùhÀ´oßÎ6~ž‡o™%ÖÀFSšœþ:´ŒÜ%ÿ Æý4P>®œUsí¢<Ú%4pùŠå6ÿƒùnbn_˜Ë/¿Âr0ƒ*Õ@m† ^øé'+ª_ëÖ­ŒP‹ ÐÝhëT¡ÖEܶ{ÚÜM€@ާ™XbnPˆ²ußÑš3SƒÀ¡ßažåè;JÓ¦1”ÞÇÿ«Ñ›¦|UØÝzÎÿÓm6Ñ~åÀšzÓ&ÖØt€Ë¹C»¡å`ÛsðÎI°+Èv†]|ZÏï׺Jlšm“¦­¨u7¸ìvü…ÖÍ?is¬}gävûË-ê’¶Ø{¯o³ÜÃ‡ìæ—j^!±YpÄ“;›˜ø¢¡ ž«ÃÒÂEqÓ–‹ÀA>ÿΰüµY6ßš™Öü[‚꘲rÐH܃a×>Œµn_ì‹hg£Oa//–¦ V+HX~YHBõ8 .Ìç¥l;ò }tksü9vè?æÛ¬¥kЗ@hwOš§h­`É9êxÞ¹ ©FبÙ¤¹ëm[iÒ:$6¥MEKÞr@Í ³o¢kƒEpË'YêÒ´ñ6á¯ue g|Ó:´d®<ò õâ,Ö¬·©lå,›E›÷C;²›Út¬ýø‚UvÔ¹6{Áë6¸M“ÚÔ”v7Þ¦MöÆ+k˜FÙEhÒhÄž5~¼-ùñ$›ùÏy6¦ï‘Ž'Ígûb{–¾¹G]jgÊÌÏ:Ø/Xj7Ѧ7–Ÿe§õØ8mˆäBCó˜S°v¡YëƒÝœöÚû­Ç¡ýöðøñ%…Ç!B |¹iÔ¤5øÃì’K.qÏÚ·ooƒvš"ŠßXóâ‹ÿpVZßú­Ê¨OŸ¾–ê³åpü>ó̳¸RÐZ@Úól˜ÂL²”ÖòP$ÈI’ #Ëmœ¢j«XÇȲ ¶¼&Ù-†Üq4*O?õ$þK6»¼TÉ2ÃúüóÏ]Y*C÷õ1úá#a°2@FsΔ×f¿õ–¢:uêl0À™âxyMÀ—ü} ˜"ïñ6@çý÷Þ³5kÖØo̶ãF~­iòZ°1 ò"µÆàÇ–-~—µ]Z0ÐHÚ0<¨ØºÂZï0´“ÊôMÁöí¶'·›ë’’ë$¯AÓV(! íÓkWy šA·úeZ¤TËÉɱ¿Ì¹Å²;Ê?ŒÜˆ +—WÙæµ%vÌÈoQ>M *-LXIa¥eÙý)¶É¯ÿÌFr9H$°q4¢ÏÑz@Û‰µyJR®K¦-ÞVJºÀ2„ž´ŠÒJ+.`å\»jMžÝò³“lË–B›4y£eÄ[ÛªÕ˜§æY‡ÜæöÏW_µ Ÿm´þðèSÈ kãZr£2m5*¡§Äˆ‚âÏmÝÖ×С¿1ß«Ek‘™kÅåŸ#s'­¨t³eÅ[hàø¸‚]«èùnI'q}QŸÜŸÄÍ)têÔE£O –8: ¨ùf¯ݹþx°æ„Þ§ÔÜÓ‰ÀšåKé€"®ÅG‚,kua#¢‘|ã~ûÛß¶qìÀåDÚä@ArŽâ¯¬lŠcô-&=— × *h 6Iõ‰ÊºžÈ¥—‘Ã4ØQ¡–4BÜþ›;jºÉE‚âC´ÓÏ<ÃÙáÉ>Ï¡TL(Î*â  cUeT–rÜVàB‘¨qŒ^c«bHíÃ$ô,"eòI“m3VD™±ãŠ] •˜:ƒv¯†IKËJl/&£M›6Úҥ˭O8—H“Q”¾(&]R-DWÇAÚáI¦I2Ýš9s¦t*â[£FY‹fYP’k9I–½Þ_þò€½þúëÖQh¼«ÀÏEMšaä)tXeFÐÑ‹°[ãê±êE¼‚ŸìýQQ…ŒžqÆ™;¥zá…Z½û` êb“~j'\~ŸË/6Óª?þÚ¾}PÇšüW½ñ°ÝxÙ…öЄ[B‹‹þjëîý¶µ¨Üdßv£Ý|Óm¡Z;Úþ0cŠ]|xך´»œÄö±¢uóì–«±›¦m·ØÀ‹ì¹'ï°ã»·¨ŽÚX}¤=óûþ¹?seZt ýõÃ7íÛ}ZØÒ‡/·ÌÀÆ!:ÙF3Ùì€kmÚo¿my˜PLú§Ùø Y7´YÊÖ/6A+ƒFµ¾ o±ÇÑB(µAvé¥ílòä¹»49õÆ6LëÌÒ™ v ÉÍ+œ@9zp—]ÖsgåkÙ$´ C-³Ç¡vx:‚-Ê5;ÂÌ-&Õ˜[t>tœ]>vÄÎíΨ´Åäsù(äôiã¯8Í:ÀžV¶Òþï§÷YÏñ7ÚñÝ-Móþn·>¸Ñ.ýÅeÖ³Z¨v=ŽÉÝh³·ö·tüÎe¹vüYùâÝö²3KyÃ&\÷†YÇoÖ¤Ù0ïY»÷¾éNËD)ú}ã»ðÔá)ùåÛìGÿhSgVe™mô…ß·ãx‚夞e¯¶ï½ß¦Íw[ªù‰Leþ4éa›¿1xf¹˜ÛŒÇÜÆ™•Ùk÷Þf3Ò¶Q]×Û”ÇçZΡãí¶±;à‡¦ôa%_‚¿;j–ØðšÝè—{èhë»zšÍ¯aGTwÖ/Qˬ\ŒÑ¯sà_qb™p(Š`k?äééÒ¸Œ†Ö«ñUW›Ö/Y@.96âàöAušð·Î6UÓkɵvÄ‘y“®·eÕÝSw–»¶©R/0o>´é3˜Ú͵EË@Çkj‡ºÚôEâìÒ&G«é—Ì¡ûîÐ$kÞņbA;}ñRûÖ­SO›þöc–ٳȚ·K·¢j0*›JJ`—X_C#Î¥T )³J6jÈ¡µeÚŠrÚ ÀS˜_Šœ©QVI±CßcÛÏZ5Ï´—­³RädYQÑvÞXЦN±µoیݙ¼¡aU)*Oé´“ºTËýNá ™V4RÿJéaCþR+H|heU®=ii­l¯œ>ø+ÚfyÛÖ†¤l·|¶ñNÂßRlPÛ’ìZ–Œ›µpò¹2KVÇ襆 ¸§¾¨›F[ËÓ>GÌ&þ)ÄD.!°(žîøÈYÆ4:ÖPë ‘ú©Œ-Óey¤¾”Ò‰sÆ,–âúVãHRØ£C·wg¬Åå´'Ê@²=øö+³#i×àX¾e8FÓ"ö»‰¿«jšeeQ˜¡Jõ’=üðÃn·¤r¥Ätçž{¨ñyh 0°Tœ¡ à®3âÔº‚ÆI†ƒcÚåË?²õk>sÀMr“ˆÓ¾¡ñQ@ÌÌ ËnÖÜZ·kE¾QÐã½lÓ盘Œ¢ÖÛÈ(¦W.´§ML|*—ZW ÙUéøÁÑ„Á¼ŠÑak×­söœÒ–ÙíBb©ˆ›x¢tÄS=a3f̰½÷Þ›Îéf–Ðý,ê!ÄP>y4I½ËíJE> uŒC’€tPZºI‚òê Ž6¢:¤:deeâèx‡D.§ÇŠ'$÷B©=se}×ûëÇí²CÒlÚÏεs~ÈòÞÞbãjm›^½Åö=ö&lLO±{¦]l]ËWÙÜõíƒþ•klÚ-Ëmì_Ÿ¶ÛÚ£¿ø¾]rÜ6bûƒ6‡Âu…dùCvD‡,ýè+Рʲ?]þ+;c‘¶iê·Á¯O媗ìàso²£¯›b¯œÐÞV³¡¾L+´Üï`´ÅýÚ)gfm»»zV–l¶Òm5Iþšu.~ZÁBûû/ڌ۰vd§ò5cxµÿ ?ÛÞx̪`½hCûbCêT—?wÍDxuí÷¬_rZ­§ÁeÎ?‹Ë¶Ûò·ž³©³¶YQçYÏ:L ò>šC‚~Ö¿‹oQÙÕÜÜ2ï!»]KçCí‚ö³e/–Öj€Z3µíæûh`ɸÓGXþÜgmÚKSìÆÏÍî¸x8õRÛ®Åy›š?ÆNê·ÙĦM¾É¶_‚)ÉÀº½h„X3ëq›fí¨‘ƒlÅ3m>æ'÷çþÒ.D`/ݸؖ–u´Ñã0+Üü¾M>ß&MyÓî¾Z´¨´Í6ÚÆÓ åë7ìPÛ·WÎN ª·ùÒ³ ›ñ¼•ïÚ£S^¦ÒGÙ±½Rë¸Åþv;è÷ñGÛÛ×}Q^*³-›p0\¸Ö^ûÛƒ67™kcN<`§:²”h8NfO;ud›ôò4»vÂr;ªKÍü ÓFe;A ¶©È–.Á”jË{ÏÙ/›nkééN´oŸ{¬uHañÆÆGv¯#ìÐÜ™6ë‘[íÖ¶¶«gØŠÌavý)}RÚÕp›ÜGbç´H)G‡ºÚ^„µ ¶©:Râ4ئ4Áµ„òçëbpVKn°<´—:Tƒ  å³nyðõHÛ³Ö„êwÀöM›{=«ç«†i£´ Îc ´ÅfΰÕ,>ÖÐ¥¹›í¥ûþˆšc·.´gg¬· fí¾ éfMÝÓ!B ü) Í:´yˆ‚4j|ðçÒ°‘6ˆ‚ïãÔ>J+@òÄ"|g<¨öãF¯—-[æ4^ÒÒxIT2IŠð“–†J@¨p²ˆ$ØTy­ǽïÌyÀhNv¢+¿qçŸo½zvWRkÞ»¿}‡ëŸÿ|‚Óè‘VÏÿýß$ëõžvðˆCìÈ£ŽÅ·‰à¤%>p8³“&Êk[YïJžÛ·K7çC‚“ÖÍäÄxÊST%èèC¶àÅïÚ¥³«ËÖ-h톼&àJb¾è¨((IX‡M|-¦¡eü>æ«Wï",">!ýœÝÙ’[gæL·üæû¢õRf‘ôb«بdíÉÛƒ ˜*¯Azd:dO” dUâd¼T鹬´Ó“*5ùŸ•Z›²vþI—¹vï³w»ÿg;Öw‡BËwê™Ö §Ãàc.=%…!ªm)4’|ê6ÉAvÊ÷Om™–J&ù-X°ÎŠÁbEرo³O`óÓÏ8É–/ûÌþñâB@“L¶1ÇÉokÚâ¦R[-£OÒV­\Å&9OâõdÀ…å~•Õ˜L«v XÙV¼Ê6|`Ͳ‘Kiµ¬lzµ?±Ø>Û*ó'ñ/=L\ˆo(ùÄunNÁ2ÒØ´ˆ.ËĽˆ|EѼª"—ÂGâa§ìPŽžP÷Ï@@LÊ;/HØìAð(¡ž±V6âi$ ­?ÿùÏöÒ?v8Þ©¨ZrírÎÙgÚ)§žÚdl$žDsE•ÕLˆ¨ÝI厊Cœ¤@ @ƒâ}WíB+ @DI•NÎw¥ÊW;¼ÑîF3g¾fcÇKûQE”h"µ.gnDUEçI­)¡"( žGÓcöùgy¶wÇö ÐT¸Gæ›LXLé .ÐÙ»ÅÓÓm¯¶mm3Gž”%¿5<«R»`!xjŒ wÉ¿úGiâ9´9 pJN±„IóHuÒ¤3”ZÙH3HíÝhN’ýãñø­ø \k»=*Îÿjº¨Ù5¢eL¶§:©'xÐKèbCÁÇk(Níg•?瀚׽bSrŒ{|ÐôÙ¶™íÔ~zÛ4»àéÓíÉ_L¤îíïK³1ûAÂñî/2‡ØÔòWý•u]ý‚ýñÍB+ÖøÈ¬¹½ËÉÑ×=fÓnc-÷[½z¯]¼®ÀôÊŒ7ZŸóÍ6êòkÙu¨sø³Ãkjc>Û~:ú»0ÿX»èâókªÐóÄëí;4á,ï³Ï\sÄ:…³el٦ܺÔ*o¾ÃFT"nÛ³‡õØZ`³fNw¿)GÙ/¯?©F`u¾4ž_cG^e'í |°ÜeKCªîPdÏÝrN|ý½;æÐ^þ"嘰o<ôç|‚¤<¨ç´ÈfM›‡ÙÍ×uþ)†a½þ|¥MY$Ilx33FýÄÆÛ›Cmû‚Ëlúl¾¸Öì€ :Û7]mÃ}¢í…cåiΰeEGXßÚä xîBð(ßÞxJÚDƒì¦ bˆA<Üö­®GíéÐ%LùÓnÀ7웹ÓmZÙ;å´“ª·"{þï´ àê'ÿïbçOÄô³¬¢kì‘ùOÙ‚üa6p{жÌAØõcƒ¯Súv´;¯œh3Ÿ|ÍN8°M)Êò½À¬Ç·ì—WŸôãq½ìºkï³¹Ðò¬Á¤é{–ýö6Ÿêûdú6‹¯eúÞ!zMÏ´ÑWýÂŽwf&>®Žõ÷aÑ'OÙM“Ô¦ :ꈴ'–r»µ6–FùiýþÊ+¯âtx¶xâ‰ÎOŽ/Gòør‰Ä>0»¯è,’”À™"¯mÇ_ÌÃ?Rã‹Æç!ó«öÈOÚ´¥ `&3+ÂõÞÖ*§•åmÊsÑ$·-Y²«„<6 f¼Ï ‘\$™«©òša…ôlvÛü¦ô›°TAÊ>+Ë”O'ÎôJýV™ð:ò·|¸€œ8å‘T>¢•hæðŒPÚfåÚ¦’ÎìI¦OCÚdgî?ÖÅן˞ÿŽ;¿ñˆÛlkIÀÿ9émQV ¬zõ©oÃc­.lÄÓHÞË/í¦ƒaÌÐFŸ:Ú—¦Œ5U/؉IÀ CHD‘vH”M=W¡»r†Ãq (Ë;ÀÊ$‘¦~Yxy–ó]ýâhÂ8ó,€øÂ/âRãƒà PÊÕ ˆ˜N¨$Lá°]˜¾{7! >ÕˆÞ×@Ð’€iµ4åËÙYÓ„°‘ä$äti Ô’EÜ:ÕòZ‹–Íí´ÓOcשýwò_™——gŸãT–:>B„E6¬ÆÝVÿÖ\Av2dˆƒ¯æÍøèHCÁ¬ˆvO^“a…·ËÔÎòš–çåhtèc·É?^^Ë/,qé‚ô»!¯!OÉ'‹VÔ¢†“±,µ0` ‚&Òùß5ðçÌ–üžÊ}Jƒyh”dS˜Òe«ÉÊpë–:å5hVŽÌ(] ɦ’kÓHÕüHSÉO²`³t‘Þ9@P `êOÖùìRG‹)F¿HÁioIþ•µG-I"Ž µ“Ð åv‘i‘;e&—pê˜Qeûã+¦}&2/ÀÛ=ζeË7X‹æéÖªE¦zÒ`;çÜìG¯½ñÓ_¯IÛ´9`$iÏàP»¶Üß™6B›7fE2)B;J* ÍÒÚZÛ]íÓ¼,¯`­eÄšq—ö©4CÀˆÌl2¨³´]"ì|Õ ‡È‰ „âU­$wæ#/÷×G£Þ-ú“&Gt9ÖÎx¾½¹j†¿e-Ò[Ø•]g-pdýþ†·Ýý®ÙÝvâ£ÆÆšZP/¨òélCß9*oì·–@§Œ>~júXÃÁp@Fi¸€ñÝ.FáO‚ö1ŠÚ¸±cí¯˜<-úp¦?l‰8 ųÎ<“Ó‰™õ4rJ¬Ý ”¾‚Zˆæ‰4é|IeK(bà•Ž£ü "ÝF2!’MÇ}÷±®Ñ. ò#_©(9 ‡øD¸U«×Yqȋƶۥ–Ó&ô8Õºr4]èt}×`f¸U*½#ê*ðÅÙd¡å¢§d»Ø­Û¾nÛ;™Íz ‡WGíìÏäÔXN˜]GA#ißøºF˜H#B4˜¹ã\õŒ€j€3ݺ¹¤ ºÀ” LNüúƒGš^5|¼†âìγªKpN¤¨Z™_gÒ­s&Y›ƒ/w&M½ñçvB'³±w_ êLP}3ÐNh(Æ®Ï\}è&Ë`÷n[nߘr—ýüòßÙ3²WîyϦ^):qG”ÐPœµï¼ pc]vŠõÄ¡oÏ wÙþÞiSfηw>)°n}=ßÖߦ ¸²mÛL4sî|tÊíÒ·;;kì³Tþo°MEöÖ³ô@æQö㪠°Ûîèc÷Þ8ÑæÏzÁ–Ÿ1¨Æ¤°¡6‰¨¼Øîè·Ü>\¨Ú¢³õi¿Án»iŠW—RBCm b4)Nƒm‚§;° wõ±…`×]™e=èjoÿ曾ÇHô@IDAT½¯uõ}íªC}ê™;rеÈb[ýy¹õí\=J°—?œœƒ{¤øvj ¿­ñyìÅøüb¾ôaÒM7WŸ>b7Ó5G]…YeOÿ8<†)Rà+Aü1kýyÖµkWç|W÷6:—3Þ÷ß_§q'ü‘YÕ·¿}ŽK'××^{­“qêJöàƒº²O8á„zýÜDäp‹ã|ˆ8dgYDòZ³ôLùÍ‘N^yûíwlò=¨1sš2õ;ö\@µ~Ýz{ð¡k\'ÈLêG?ú¡Ûý*†‘n“þ#çèÇÇæÝ×äyn$>Y¹Ò†0¹OVøECB éh²Ü‡†Í„›ou®'’Ît7ØÔE>7•~wä5I„ ìfdm¡ô-ÊøTWÆÚí!ÜXv_›°ägϲ–@Ýþ  @že5g+íÏñ©ÂÇ´–­,žÓÆò¡…ÌaôOX—פiƒAŽSˆÈá¯p €“hD:ÉÂø—ÅÌù£d7™F ”«äŒxÉÊy–ÝF&Wd }œf rj .œˆ5‡§‘ʬDžT¯D+¸¨%ÓJ‰Aj(È­Ý’Ev@|»åaV"Ò¥}ä@ëeK¾ ÐÝŽÿÖPÛ¶¹Àžêm+e‡¬H´¹}žWdmZ5Çwü“¦ˆ—û›"ÓŠF•ù€Z`Èù™ñVÖšµÖFèÌ$yº ¢Ê:EfPñ˜v¯’üÔˆî8[nÙR:Ä¥}ÒbÂêl'>R?ÿöÎ0ªêúÿßÉ6Ù!!aM€°$,‚ì›bA h ­ ¶û¯¥VÁºÐb±RqiQ¬¨Uô× Vp-‚J@eY%ìkس@V²Mæÿ=÷ÍL&{ÀAÎ…dfÞ»ï¾{?oÉœï;ç\ˆñ¬"£ž|ðùMÚjæËá±  χÉïà«#+Jñ³G²R˜‡õ?È.²lÖ«"{³_âµÄ„âÈQõV™6âfÄ„1ô°»Õè$r$å?;ÌQʵ$¯Ò“¥ˆ† "f.Ãk«ø™$K<(‡ð˜3O ON_z¡ˆ W^ñ&sÐ*â 8fÌX0ÿMl£`@îÓO?ÅIf“¾óÎñf ææ"…óe}b rÅØÁÞlJüˆ™ï<åü“x8'÷ãÏý•Ð N\ö"äp¿LpY(‰‚e¸ÒÛñãÁ%ŠÎcØ»w“iæ!"²•Ë<„G„¡y“&œ2N¢yÁðf'Î-âÆ&a\l’‚ ßËzž¢`:åŒ=p¿ý `B± Æ#hÑÛ Ñ¥ãUhÍ“†õ©œ[œ*ü$¦?9Ý„m=ùÄàMZ‰k¢(âæ€È¸Ä‘ÛH •OVÃWÖK¤3U9¹¥x笩¬ª»^eëªZÚ´M¾øæ&öp‰Î ìZÂäNé)Áœ#7&†á£U+°9u*®‹öîg>¾|ëïð ¾»¿ø'Zq';÷¸¬2¹œG©±?n%‚*í¨‰ÿĨ»‡?Ú¯ÆËo…ç)ÖÈè@ÈßÕ  8»”û2À ¹æ=e]óľª-œiLnµn ƒO«§=óŒûüh—nyýqæ2ÉÇ/ÿú8ú6vµkµ^ñw9Fû×}C «7ÚÈl7µ)œ¢Ûä½ð Ña¦|·“Fû"Å/ÔÕ‡zK èb-¬Åïcôª‚-–7Z«²˜g˜Qž>üIŦm\ïNŒÃ°‹Žb“§Â1z]˜Î)«ñú²3«Õ¢X’uí™êÐ’m¦dÓ•“ ÜbÍnaÄÁÅrªÁ`45ÃܱƒãõˆY8&6eÃh„óÚô¾-[Ü'—¸<ƒÚ5‚¡4Ë$¥Ý/1ÍLuíÀÉkª:)Ê6Šs>†ü)£‰¿Ï<3ÜÓšŸ_>øócXÛs<¿½'Bè=Xm)w.y×=›ËñÔ°yù:9™V¹ÍYÅŽ«;5¥À)ǽ†â“G­eëh΋˜lê’îƒXÁó‚¥wÜ/ï1™Àkzùö¸áGM°y9=¬ ûñj¢ü˜Ü«B¢Û£¤XüŽñf¹¥w÷êÊ_=cª|µYZ›:Þc’ì‘èÂ8¤8n¼Þš$võxù˜•ýrµÓ´SO®MÆ·›ahlœ©™»«ècèUÙ–fY6µ¸ùv¡wá­xó÷a–öç0¶ÉZÜýð Ü?ëQPVåÎt…PJ žá¢C‡œŒd§ ]’YŸ$¯{Æ¢½{÷â[ÎR$Îe(2û‘|7¯ê;¾÷ç—†bÎÆ^£‡Ç¹Øk’¬Y®•+Wq–-køÄXÛÖ y0/f ­7ck‰ýV‹UIIF¬ê@Ϥs±×d{18Eñ#«¯íãw4] 9c;†DE`ÿ¶ l¶]Eo~–å4à} ƒ°" ¿o…4FEˆ(#cnÎp,±YÅ”ð¶×$Ý…™œ†@D̪Àˆöª3þÅLòÀÉnä½4$çx.I*ï’Oã|÷Ñ-hÞ†IuÙž½$Ül/Vm1÷ãGÃXìQ7#'½ˆ8ÛÛ¥}\‰M+H•HFÂ\㟅†€%4`‚aÚÐ2f:.ÒÖÉÊ<‹…ow>Yƒ@_zÚЀñcBä< ;yô °Ó)¡¬Ý_›Vùù‡ò€óKЉ‹}‚øêƒ'·¯ÌŒÌÜ¡"„QhtØië3Žž3’Éõüì> à2òçðä ¡hÃãËÏÞçwC–}]#™ú}xÜOñÞ¾·ðõ‘•ÞÈÍ{hÞÝñ¦gy3N'Þ:L¾Z›?E3î¼Æk­¼6RʈÇIš‹ü(‚ñÚuðdñã8DZ²I4œKßSñ“„·¼‚©ÚqgÄõKî_NñÆ‘3¯Šâö˜q¿VQm К‹%fXõlÍ{bl—üùî;ð|«9¸¹c0¾ž}7žâk÷Ý7Œ¡J1ù÷œaé) ¹~Þ›óGÄã>|cnzì! cÑÌþÞþh®ÝŒž>ä¹Ó•"×aô¨nF(¯næg•ö»æþPŒÙ¹SÞÍÁ/~äíFùú0ÖØ2¿ÃÐûÇÝP0iÕ®ÃÍ]cЪyRV¿‰ÿ¬ñÇoîù…ÉsáÛŒ³ù4YˆO>| ‹›Ü·¾xý5c¸Ý6P.X)Yضþ¢ÚµB úÿÍY`È%ç8Kcb–½®áôFoaÎÓsvÿh4ÉùsÞÜÌ%¢OœÕNcâ•Ý Œ\Ž7—¶ÇÏy_ÉÝ¿¯Ëüæ× ©x¡ÔbL"àn[¹~m:¡E¤G×.ÄóŸðBèt]^)µSmêÔ<&ʪ;Vcgq3thÕÆqÿß ©ŒÅ­Ã;ðÕ*5µc‹îÉëû ,üäy,m÷04<‚ÿ{>‰wžʤԂMmîcÈ:ÄÀ@ÜÙ&§×n »TO´ôÎdíM+% .+4^/Û·o73>ɬOå‹[ÔXºt©I!ÓkצˆÇÎUWu1¢Heõ¥]™5iõê5hßÞzˆP¾žØÌèal&I´j Y.¡ãí þ*g¯ÑÚ0žk¾úŠÓ4ó$‹Û>W™xÅ]ÜËÓéżâ³:äFª(üNî¤xpžöZï^}ð÷}ú4Ûüâ º~Ç^“DÆ"Xˆ(Ó$Û»Kâ¾O󨤟èÝ£'Çu.ö½ŒUÈ‘ó{ÖMLŽ+Æ>m=aäK· ¬Uñű«áË01™åI"(| ñhôb ½¶?Ó‚ ½FsŒöšDBˆíf n{MòŠˆS­MËíÅ[fôUwcñÞWáɆoT>¦3‡ãõ}nF “ ‹=)EŽêfÚ®Yù§cã—Ëì0ü¤Ý8kb:#ˆGxʸIF úKò]q}¨Ì¦¡cëê›>¾éhÀH“ [cä‰gÇ%¦&5ìÜuŒ"!Ãθ,¿˜Þ'´•}ý‚éÐPÈåYm\©Ý_“M+ŒÂý£©'Ѿ *á´àyÈÏ8€B»"\‰“G@µ…Â|›ó3í7Î ÞW¾¡Â9I<ùfTuv¿1×É‚4ªdÔ!ô*ü4î6,9ôÓ•ɬ#`ý–Vnhq“åñâ:JèÒ#bM×ZeÚˆ0¢Š mütÙ2žû<)j("`þ‚¹®n9ÜˆŽµÑF(Ö°U”K¾t7aª_~¦Ë/\ åá9ģ̓.aE"ØP´{ûX#BlÙ²-š·0®Îß>¤.¸(K2}•™Â[”P6kg† ù±}3+›dËÜU9£qÿf‡å‹Ê|öG³«©¼n1ÍòWÍý‘ð›çþ:ž?Ö6~]îÃ/ÝáÊCG™Ÿü7ß·ŠãéŠûü©g&#ÿÄ^œÜÃ$q<¬ÖÐC0â¡ÉH}|>yõI|bš¢ûè]a;wKî!¼6Ç+Q+ •Þ£ïçŒA.#ÈÚ}™ß~Á´*yxÝ)²2ÿèJ,\ö!ijJC 7‘û)5©;!_Fwkî®T«×î¿þOÌ ]ý!^eö«oÆmk™„×½¹ƒx‚YÝ8u÷Š·0c…k9%ÿx„»_™¤4àø‡˜ñÅ6–ÀN7á‘;»›÷mƒ›®‰e~o0GvÒ.CÚïÀòõT8]¥Ù {1Ùÿ},XƘÔ4N%<þ~4ýî%¼±ÍÆ]³ò×–†¡IÒ[HzƒÉq þñòˆ|îÎ{/}²O~cÑ3SŠßãž <w<>þ³žçvüq5-Ópÿ¿AÍÊì(ªoþ®"½nØ© ¶¿õ¾v-ë=z2§9æp¤›:шOÆŸOfÅn¸iH>YΡþò.ûõSð ïOåJuǰ8ÿV|¼œ?¥Åöñ‰U᫸‡ZœK<¥Ž|ý1’øã)œMë—æè©Eè>¿Æ]GrñêòOð¿XWað¼ŒÁ®ë£6còm6OL<‹éÌÇòü_å g!Ó‰SF{úSóõÁsøžÉ8>óy&Ô~‰3wY͘vºw –ZŒIܬö,}Ë5M¼ÕF“Þ·ãþ;zSmêÔ<&æIß± oˆàä. ;áî‡îFG¯¨šÛ! ¤Ç§?‰Ÿÿ+g8“Òw=vwi’òZ±qwÂz­ì>–{t7Åê~hCfÿ¾#h2àgne·ÖOJ@ (ˇ€J"؈G<œ=~œyâ˜[DòlJŽ •o jä¡ìÿ˜€Urv¶lIûš"BÈ–-[pë­·šZòýË/¿¤ñ5bccð³ŸýÌìC¦ï^½ú9&öMõ„ay7+öÌ8[BûEì>?’N&¿”¨„òöZpÓÊ2¶ÎÖ­[="M4£Îdž1!Qî¶Å“§aƒ†œ„Åò²ùî»m:ìznËTܗ̪{>öšÓIŒy3Þ{o–~ú?Ú.ŸnðõÆæùç3ÏòA5í=*E “ñ)öŧ+–á3Î$,y=n1’F»„àœ›½f&ž¡gd–JåбÀé8ÂŒhÚ„ùkøEŽOé³E0 .›Öm¯ùR¼‘ôÞöZ‰x¦ð\©Î¦5z4žãwÒûÿxŸñ¤y×KˆnÇ¯ÜØø¿¥(¢çбeyLi"¸?ÚöçÃæÜ\3×w½Ï=˨ŸÿŒÇN¤'Ö#£"é;J*ìoÕ6íé“™ˆ pâúÙhÃi[ö¥"ãLNò‹· ä #2˜äÞiÒ˜Þ'}®–Ñ#/+ûfc÷ñl¦¡Í.ö)ϯÊì~7£ÊlÚ¢âzî2Ò, ™gOSs¡àQ”gÙôܘö… ßjÐÑþ­™Ó§ˆß©íLlÈôA §ÚýÆà­Úî}ÀI[[Σê%4쎘à8|“¾{3“‘žÏs]¼$¼Jÿ¦ƒÐ2¸µ±ûKÏ#2®ä<ª­6"ϲ—öBCBÍ9ãµ;˘ÕÌœyòJmŠ"æŠÏ?ÃÍÃGšÄÚh#¶CŽ8þ<)%Å2³4Sp‘`(9Mrð/Fé´ÉEãË$ˆ'ÿÂ÷ß÷6àšk`ð°!ô‚âhÙ)qbñç™æd{2U7?R ¡h"o<~tŒ;™XºBHêR"”“EDq5D„¹ðMFnI¬AeE2h?q 9g2ـ٭cx‚p?l‡?%<édÚ;? $Fe•¶84QÓŠyñ‰p""“nX’oFd Åꧦ녔f+cÄFefÜ2>Oâ@ª¼»¶î@ï~}(ü¸Vr‰qœ3g.&Mšè^Tåë,Þ ÆgnxUVªfE~v6/bòðsOU,ùÙÌšMéÎ/ˆ.¢Þò© Óm.,ˆÅÙ§qš9¢#¼ëTl®¦%Õ÷‡Óóe[ÉÈ‚8¥¢p-[¤OEðc’ë0oå¤l%~rp ãS •ócÌ(CgÊ‚‚\&ŽÎËåh?„S”cróó(äferÜo0áL8ÅÔ%eÊñ•3ñØ[øëË÷Z³)•Y[ó‡Ü,æv)¶s66*/ŽErûÚC˜ÏÄ«¼Vôì?s¹¼aKü§Ä‹–/¢€‚ceëÌÀx-xŠ33ï~{éóÜÃÃjiÔ0Y7ï;¼Ñ—é§IÎv<Êáå¼OÜû+à«bÖ€_8·u/­üÕÁ§:’ÛwVf.|C8ƒ]¹m,V¥cÍâÓ)ßeûU®ùšŽ¡£ ‹SD»N‚àHDž×ÉTó¹ä—q·å±ŽŽt…&z÷×5öjëH}ö9UŽ ßÚyÞ–ïr­ÇäjGšŒ¤×”×™Â%µ“lÈ’›ÁÄÑ<Ì<€ÇU›1™:¼x1úñ¸G—¿àÙtmÆTsÚI®Û<&Å/ö³£q.2âÚµ#õÜ÷±ðf¼Oɦޥ6l¼ëë{% ”€(CàðáÃF°‘Y“i\Ž?¾Ìúò$)±;'N4Ic.\ˆ””#ô¾ïoD!s$¯…B~øí 3TùväïôÚµkѹs䟥«3mãxb¹ZÐ ¦¬½æKMfÄÝ0ÅØ.]¯î†ýú›©¼_ü× Fp’Ü0øÃ½¸ºûÕX¿z5¶0•Ũ?E,(ÉÊ´!ßË^³1ñÆWœÙjeÒ 3“VXXNÎÙE;& „ïs)í ŸU+V"‹áî’ã'11®½†ý“p™s·×$¼FÂ…$ÙpyFï}µ ÿàôÙŽÓéŒÐá ¿Í›Á?ª1&[…Q×öª`ÓVf¯9èÉá'N 5Ø´é§Îàý…ïàþDÒÖ±hçsp†åP`ߨ†Lµ.¢_øÕ™6gN$†µúv¸ 3ÿ9?ÿùhæ[åÌD™¦ÏÁàè-SÄ<>a~ÍéßÁŒB®`~ç—4*Æù§–v?¯Úó<ÚçÁèñMö\bqañøEܯ¨OPkðá±¥Õ_ÝyTþZ«JmàýÞúuëM›žR“`·)𗬑÷|•Ÿ¡C‡âºA‰ÆÛ¨6Úˆíà¡CÔH(&ˆÖl<ñEfK’—P`‘÷”7ø#^)ôŒaU9óä&ÓgrÞú‚¼³Ü\ÄÙ†] ˜B†/3=Óda‚nlÑODÈ‘ƒï” œÌå#*ûRÌ:2ƒ“Ä´K†hÉÀCw+q3J˜/“C±ŽÄZŠwIàË@Zbþð•R½ƒøŸí ,žú¤xKàxØOf7±‡<ÖrI Ìq1ùÔñãG»º)¼aKL_hX(ÕñXô¤Ë^“&Ñ( ) ‘ªcd]lìŠáá@XP¶sª¼¾tƒ”xÆòEÄš—^z™íŠ…R}‘›ïï?á¼Åšê[×µŽ@>ò>,d(ÏËno³³\¼ýGÞì%¬©’˜8ÿ¼µc%k.þ¢\Æ~>øüvôN¼̓s±ö“$ZtÍÝOàŽ6Gñǽ=”¼ûX6y®÷ªËê}UÇð²„vV (% ”€¨‚@JJ >ãsñª_…ï#û œä)ž5A:â÷{?Æ­ƒzWjÓ–·×Î…Ñ©ô4,|ç<øàCøz'½h¿|>ô[–æ«Ø­Ü)s+¶À¨.÷ û!xæé§™VäV†A5 `À j8‡Œ‡ö67ãaªÙ¦}{ý4ÍLÃÀÿ-BÔ¡=ØÚå|ÖãGHñç$@|›Ï©´s˜`øô±S°ñAtL¾fq¨£'óq ýˆkºtj…ŸŸe¸œ‹M+‰éKƒ”Ì]È,HeTJr Ï "PfœâLÑŒ¬hhoÁ<9¡ôîáò<þ|X%v¿Û¯Ý/ Ï—Ñß7ÿÅ\c̓bq{»ñ°ÙyLÊÚýUGµ½Öd{Ú÷tæpŸGNsÌ)(šCJ E^>Ä7ÔVŠy}J‚áÚj#¶‡RxjPNqR !t¦âÀD”¯*¶lT’Z‰ö"§Ž¸Ü±2vŽj®&~lA\ÈdKñ†‘>Š8"OÇ%¿‹ä‘öÅõLU"" ëÊç.%«D²ôRÅ1û”1á°-É âÌÔ#SÝ‹c =pØ8×8(9)âØ$C6¼¸šI¼Ÿ¤ËÁFB‘L$Ë겄œ¢ZRiOÓCŽAgs¼XØ/qÇE•íÙéžçCQ„š~I$A”d…>FÁÌP¾kG2gÑê_ix¹±Ir0÷Í;©´¸×ËÔèrÔòà uüŽžo¦²ãáý [¶A³ò®e«]´OŽŒmX0oöKC&3á7ˆé‚FÞ‚í‡ëÈÅ݇é=Pnìx,µˆ³’_´Þꎔ€PJ@ (%pᤧ§ã7Þ PQDoš~¸öÚkQè½Çýû÷ã“OþgîÞïåøNûÛ£œIùVDrFªïcÓ.H>Ë\²Åˆ¥ vê$µj‹½ÍbmB1€KÅ26ÙH>žüœ|¤î=Œ´]ûµÿ0½ŠKè¡ŸŠæ-2йCžŸùÌ9Ûý©yÇ‘ž{Çò¡Eh+„ùG"^6q :3¿,…:V”·ûE+‘™Â.’ÝÿÔÖ)hÍô?kC&<®Îî/Uu­] mÄvðÀ ”ãÉLQƒ"U †(Qô P^uû÷s¶û÷œúÏ|‡˜°8ÄGvÅYg!£Vœô¨ 3öµÛî§gÇ%³û?Mù×5ý‰Ih|)ìþºÒFlû÷dJzÒ0VHâ±Ä3FÜuĻĸQ­•é¦e‘ä’q2|¨„¹,|èá"¡L>¬çà "n^–W ÏOÖ—“Ä4Àå8%’&#ž6–‹Œ•S†Õ$ÔÊõ‹XW–Lh”(¾FýeûÜVt+’P0©hêqܦ?‹7’¼7#âÑ–¶XÉ,‘µf{S‡ã—Å"ªQ¼2ýöD4“ãEVÒ¶„Çqñ¾2µd²H6eoÅcJÄ0³Oþ23€IöIÂåŒ×_e¦1Kd³¶t5ˆÜcçrs@ùÊÿÊÈÅYNLe¤ç‘^kz?’¿Y¼è=[ÿ®éß~ùv©ßô;äåûýH¾3gM~~ÏdF\@[Dí5FNÐVU›Víþ m÷×ɵ¶oï±ÄÍLL2[’äx‘Ù°%2HìdÉ,#Ób›ä½&dŠuDá:B|©dÈGñsÚ‡âŽÛáV#œb ëðO(si‹" íDü0I€ÅF²ÂP’u"`ˆ @g#\ø0ÌÉFoNÚd²ˆïéUp¹tFö/ùnä”SB¨x“sˆ;!­Ë×Y™j\Bªœ+D½6:÷-7CKN22%éûDÄá>Ä.–¼9ÊHŽ™2ÒóH¯5½é=[ÿ®éß~ý~¤ßă[¿Cê÷lµEÔ^S›Víþ ©øþñ¾IÓdÎsñBÑ‚¹yù€F¼-(zH”ñâàeñª0^â#‰‚åµäž¡ O±Çׄq=SDài¸EF1>E? 92¢½+$?=5|d¶&)² ×9(’˜úô134‰bÃý‹«ÉÌS6Éu#ê =IÄÃÄ|–6ÄÓ‡" ¨b}qN’ÝIg˜Ý†"’<²[ñý`³ôê0®6Òß‹¸$Ýr Ùd‡ô“Ë•‘2ÒóÈ%²êµ¦÷#½g›¿úwMÿöó ƒ~?ÒïúR¿gÛBmµ×Ô¦[]íþºÔF|ïøÇi&„F|P(¨ˆ;=PÌS# º"‰}eŽx™±‰’ E—»©„ÆPÔáE\U–ŠB%„ju_W˜‘ÉÃjÔuŒ÷‹È(±#*ˆxÀ˜ðÙ§ìÏ·˜š‹øÂð½5l—A9F|afþý†û“}Há{öYD ¥r° é¼DñH¨“xùHø•é§4ò¥‚Ëœ’˜XÚ¡·…*zô˜¥¸Läœb)$Fj±ˆÐ$cVFʈ'²žGz­ñž ÷#½gëß5ýÛ¯ßôû‘~‡ÔïÙj‹¨½¦6­ÚýT Lš—:ÖFü^ûÏÿ‰¡E (% ”€PJ@ (% ”€PJ °}×ÝøŽÔƒ¾h”€PJ@ (% ”€P?x»SvÿàÇx11v£ûP—„〴(% ”€PJ@ (% ”€PJ@ Ô*ÖÔ—#¡ýPJ@ (% ”€PJ@ (% ” ¨X£§PJ@ (% ”€PJ@ (% êkêÑÁЮ(% ”€PJ@ (% ”€PJ@Å=”€PJ@ (% ”€PJ@ (%P¨XS†vE (% ”€PJ@ (% ”€P*Öè9 ”€PJ@ (% ”€PJ@ (zD@Åšzt0´+J@ (% ”€PJ@ (% ”€P±FÏ% ”€PJ@ (% ”€PJ@ Ô#*ÖÔ£ƒ¡]QJ@ (% ”€PJ@ (% ”€Š5z(% ”€PJ@ (% ”€PJ ð»} lÑ ÍF@PË(ÎÌÁÉO–!óÛ-—¢+ºO% ”€PJ@ (% ”€PJ@ Ô+]¬ ëÜŸz!mãàˆ’‚D߈ýÏÍÆñE‹ëíŒPJ@ (% ”€PJ@ (% .6‹e³™qùG6D‹Û~†°«:¡FúØíO›·2ËME«¾y¯¿”€PJ@ (% ”€PJ@ (%p¸HbEÔˆ½á,.†Óá°~ä}aÂ:Æ#¬S‚©hóñ½‚U (% ”€PJ@ (% ”€P¥.œXãò¦_m¾nñEr©³î¾啬¿ |ýž‰¤áä½£°ëËê;9õUtŸ¬]Hx·ª¢¸ÚõãÔêÞEfï*)ÈFá¾m8>{ RVy¯=—÷ýÑcùß]«~ŸK˵¯ÎKžE£˜€ÊÏ2 ÙáSP³2õÏýƒu®F!oÙ3Ø0™ÓØw¼}æŽFàÞï¹÷T·PJ@ (% ”€PJ@ ( I ÎÅšöqð ¶’s ng±¾!Áô¢iUý8$ɰ;ÏMõ5Ïi­_tsø‡S•©¢Ø;E!ä•h4ï~¬$©ŠZõx±= áað¥\ãXs?}¢ÁÎúkÿêª{ÚeÝ % vr ë7­6}„Í£¦!§BšØÐ6Š}«U¿kjíüÖ' A'žµÝØÙ¨¶5Ï«žû\ ï7€ÛS¬‰iŽàh[î·Ö}<¯=ëFJ@ (% ”€PJ@ (%PŸÔ¹XSp*Ý$6Ss3±°{&(Gf–5nïY ¼IT"ÔȶNg P9l² }í!ÀÀ½ „"Ap§.‰¶„œ°±£ÅÖ¸¼Ê‘4Húf_Ö®ß^Ž2EÕlaóª—»v5òøÙÊF€€˜Ö£Ð"ÅÞ}$ú¬ ÃôÄ9×b¼ªÖÑε¹ó¨¿ é‹W#$œ§˜Ùšƒ´·F£~qæSQòjœIu›çMѦùU{"ÇÞ«ÚÄíæ9<´Õ«ªÚÑåJ@ (% ”€P?`>]0ðíË_ùh…&¼ðšžÀ¦[BJÇßbØsC7÷¯X5o[…ººÀ"ÐjÆ‹hÒ½5ÂÅf,d4Á.œœó-/%ÔlúL4‹ÉÆÁñÓQº¸Êw¡£Fü¨ÖÈ]þ vÍÙ]e½jWtƒ®$¢hÕ옽¦Úªwe":ÎCÛ°™1š%+ù´¡vOšyó«ï¹á~ì‚´]ýž/ÝÚ:krvìÆÑ7"æ—¿€Oåê‘öÙJûàk”nQÆåIÓ W74y#<°Gßùg1{u7+”#m6O˜Vt«ùËЮŸQh4!‡fŸçT¡åó[:°?(xeì,׎ñˆt¢âò3ñU›™Õì,¡¸ší‰÷‹%JTS½Âªd¾|v”¯ê8}Þ›ˆ0Š-¶˜ëÐã¡xl|º\Ÿ+´UÅ‚|×rŽ14ÈYužíTÑ|h‡xúU»±kÒ=e·ê8 ‰ÇQü*@êC÷޲«Ë2m7fÛµê3Ç7Þ9©kSC»eöÃλ›ìOJNùó£ÌçóçÏ“ çÝù4¥Û(% ”€PJ@ \x~­Ðü–!ÂÙ ãžÀ.Ga™}Æ&þ-:Øwã£HAÄ%³u&ÅšqeêUö!júÿðÓ?Çcï°îXñ¹ë¡{e0ËÆ ÏæÎh¯bElD“…b½¤)`‰ì—ˆˆ¶6 ™† /Çk³2oöD¦&pùyÚ91]ݯœ1éõG¬áÃúkçŽE™gïÑQj‡¾»`K÷qµ|ÐÝÝÖ=‹ðÔÅX5üI‹ÓA xo ŠNñD¿D"÷Z-™‰íµà^æ \¦êT¬O˜æ89ôïÿ"g×3Ápáé3H[ñòö4ˆD„±òžÀ¿a8ÚÜû[ž¼½Ív2e÷þY¯BêØ›6FäµýàÈÍEÚ_™õß‹±³ÌiäiêÐôÕˆûx$s èO#q2-ï‚B¤N„í‹<ÕÌ›Ž‹—¡yç0œ];«5ÇÀ§‡Éá»Evtš<Lçç¾OñÅ)ˆšú":Œî {¹¬‚äO‘<|ŠK…MD¯uO¡ý Ï>ˆèICäéf²—½‚õ‹Z£÷âÕNѾÕHruò|×¾2~Ìk²¯Ûí¼»ÊÐçñ‘c»§îBæ>ÏÜ‹k|õ©,¼jÇ\|ó»8 š;° ¿a<ðôO[ ó Y¿³ÎZX€ÜµŸâ»1C¦ì£_ĵ¯0üÜ ±¯GºûÜ9q\ãô9²96ï°«ÈésÑulW'/ÄWî œ µµ-nH€¿gÈÂ4 %Ýj?X–—kÇ6—§•|ô‰á¯JE•xtœÿw4é×Úk|ô~9Â>Oõ곫ÍP†‰]ÅóÃíÅe3ïOöÊ…X?áW­j^øw·ÙìèʱÈyj•œMNÂNϹäZ\Éq—5rìŒcßÌxäѽô-;ˆä‰Ÿ"öéßñðf9u.®¾³«×ÂÒ·öNCqÕ’4ª†VÛ~"ÂØÐrrBi%óÎŽ°&bð åó£Ûlc’†OƒMr¾° _{k„áJQ©ò|ed…<'~Ñ hDÏSÊ ï®…çöâ\õ(2R‡#:Úߘxú&R<º®zÑ1ìT™bGH¿‘èµÜŽ$ŠY¥ÅŽˆJ?ºÞù³¯­ç.ƒß¯o0I“=ãlÃ,Öñ>|ì’³‡…¯îÒ~ÑJ´ì^úÙZ.L‡ºª$”òroT«W&F^÷""8æòÅ?Fú¼õ÷}‘¾Šîw÷*_Õô5ŒWâòæåxT¬ {šV8ìâ¹Ô}S3l¢jm\0)h ¤pWY~9ö­ß›‹ÓôˆÊÒÁFüK@§¹åÏ»Jö¯‹”€PJ@ (% ê5{U©&ܽŽ;Œ3»¾CÁ‚…î%èòô\´OŒ7ߣgáèswaÝÇÙhýÀ?Ð5±•©qß ¸~C©~õ0ÒÄ>ðºßÖÁœDÆqæ08ÿëÜaUÇâúiCprÚïQ8aÛ¸ þ%'‘±õ+|ó«ÇÌö¦Ñ€á¸î5zd8cé/ÿäéÏ¥zúàƒˆ>¸ÜÝm”çáwΪùØÎŸâ%kÓ‰öáø{ikUþ µÕŒ™ˆf.L±Ë ÄñSp¼üC߀è8gÂbh£¤#{I¹¦Ž#БB‡YOò°õøŒieÚ±ÒR\*RÞûMDcïàdß»<ÌÒf?Šü¨xô½3ÄÆs‹5ý‘0{<0\ |h»r¶?-ë‘0k Ä7À•€Î³þ'5[Ç.fgÇsÙPdÌž†³û‚»qRæ“UçI3üfm&b»kÙîãî}ºê=2ŠÍ-æé³æ xÈH„f­vÕëö³ÆÒSŠýb)æÃ÷æÕÒ#ÈlrAÕ©Xß º]…’¢"d|½…§˜ðƒ7cœ…Ehн+bïcT̰§}ϽLfš ÇÙ³8¹dã=œˆ¼¦/ßøcSOôµÖœ<}ÕjdmÙnÚ««DÄ‘Œ3ŒŸì èuñæ=@›5hÚÖÿnÐ 3=^‘Óé9cz”ô—_F$šOž_TaOSMÌÞ´ ­'¹,ë‚c86ƒ*ãœ$ŒzñŒ¡‡Œ bÔ‡rCÜá-ÒNî²yønÂL„MçŒMcK |ñÆÙùФEC¿W&"„ñå ܌ۜ Ë•nðì¹+ça7o…ÑcÐî‘ß¡Q[ž¨Rl”³©…œü®ö¢f=åj 6±ßS§ ŸûŽç¾#¸oß¶CÑgêGXÿxÙ‹·ÐéÏ#%µ9:>ò š÷kÎ QhñÄLìò·SX‹ü-£f– 5œõê0Ûݳ(mgÿ ­op‹e;PËOÍfñfîjœ©[qhê+ØwH˜ü bű;šN‹ã‹D@xPÃp²Åó±sÒ <·*7 ž £ylx|M=È¥'ÎîOòXŽCüãä)7HªÖ'ºzÂ|´¥g–%ÔàôÂW°{Î\"v}Ð:îöæhÔ8]aèâåÃë‹3|eר ­ ”€PJ@ (%P¯0²!¯2ÏwªélëÑè|ë(8û:)ÈŒÃõÛÒоƒ·û|W4I<€°[Æ!êï@„k€QÃF!ʹ {=–í¦ÓÄkè ˆê7-‡MÆ»¿|‰û‚ö·ŒFûaÃ@«í´æ„W£ø­gñÙÇ™fÛFO<Œ·v…sÇB¯¶.ÝÛØ¡–0pvù"èàÝ›]3^¡ Ð%©½»ÞW|Ò6ƒ ò¡²‘!7ŒFˆ§…8„uê…ˆ¾³°züëRôq¹"°†Ðóý°g»úò& …YÌÑA˜èå¯"oÆæõ±ì™œÇoGrÚ8F Ìµ:+!MGW×Ã:uED÷xìL½1#Äîc O@Ó (Îü8_‘2j*ŽtTÀ¨Q´Ó)ªQ0ÛŽ|˜=ð<ˆ—ÚœC‡í†Ñ9a­xô )ïÀ@æ¯X6¶“öÛvÚ£ývüÍØÖÖÖü݉̓ÜévàYw ÞøÔÕ>ƒéñ?õtüû_щ `«ÿ÷KøsúnVD¨‘’äsuPÐHÞ…¼Ã)&Üéà¿þƒïíMéO¬à3›¯_…n¹Û|ίËbÞ¿±ÌO÷GFzŽ3õ[lÿ’mÑ6kß¶84cwi6¨µy+õÊçoq04çsª°'NÁž9¨RQ`É]òvÌI2¢L½t­Ah3ëÇOÁÎÇç›þ¹Wé«PJ@ (% ”Àe@ °+†Ú‰_§ì+óÓ®›—CáFâLØTççÐÒ5'±}X3¼| öl>õˆ›v=¾¾å6l]zÈ üès“±äÆ_ãàïßq 5ù8:{2Þév 6¾µÕÔ‰ºÞ݃iݸö!BMñ‰­Ø3{&öo8iÚmqßoL]ùÕ}˜ec¥¼ò€gÙ¥|ã¶.3—Ï÷t#rC &Œ³~:"k"ßño@IDATóVœY·Ë³Þý&–ƒÍƒ\~§>2ý~¬ûõ£8aì˜04¾ÀúŽî®Ìoÿ§>ƒumz`÷B‹]Р èÌ舶´Kí¬WÄ™v7qýç?yÙò]Ÿ6i(××ÇrøÝoM·üÛöB»WþE;{ ®]µÝf=€‚Ù|x½ÊêuçWÆ¡FäoúIlùǧƎµ÷‹æ{ŸÀ–‡æá,«ŠÝ›ü»?`ÇïGòË«ÍÆkb˸?x…ŸYöŽ',Œµ²Ïq1µŽO°ñèÜ Et|¦›¦䱿dB—È©´ÕzæVlñf˜€ØéýÍþ/õ¯ŠªÈyö(r@oæ˜éëfbÇߎ´ÏWâtú·!'°Yc¦ÁŽ)Á?"é+¿6{ò EIqŠNŸöìù̺ÈÞ–ŒàÖ-á,)Á©å+w(Ŭw‡Zy*×ÁwÓÔìùÈžÔË$Î > Ëp¯åÅÀw9‹ß5ÕJ1ítïÍnlìkÜ(ºÊu~e,‚Z4C §aöÎ_SVh)@úœ'K›ä»#øØ™Q;½Ìrχϻ2oœv÷ ¶ÉdÙ²‡WdØ‘%¨”]w~ŸÑÝÏ]üa®£èùÃP­«–1äÍZ+ã p‰HVÈÔ§îÍè·¸‚H°gÖj´xe¨qË49sxAÕ¶¸8ö}‹½å6òn·ÜªZ|,_.oæ.iƳÝÁñ‹³¼¹ÑS@áääÖ:ºT¦LNòÔ³ÞÌEÊZz;1¹µ­Ekã%U®‚×G9?^ðú,o×à åÕr,9^ù#³oÌ ØÇ×È!cèNøw„Ä´2ç]WÞ"ÙÒ»ä.c?¼è{% ”€PJ@ (ËŽ€Ó&Ïúê:ï+ÏNS2­ü'ÌIû÷7pÍÜã³ãýäpøø)/nùº²jÞÒ7‘ÂÃÿÞÛ4›õÁ$|tï<ó>ý—ŸÁÞá:wkˆæãø0v®YLÑf+–¶„”Z<ƒ¹¥w ¸ßhÄú¼€”ЩLzL‰u¶¾œåÚàR¾$2ìH ^Ýi6é4e2[ïâ<ò)Ž/Ÿâµ¨?š Š2Û¦N…]‹¬UÛ¿LCÈæ™¬˜ßñ)´ˆ\%¥hÓB>PoÞçL‡À¶VÚ†0Flä¤nCæÚe~ÏŒŽl·_3SÏôËõ®¾½d_?]'@CÚ„’JÄÎùi4b4N¿6]ƒF†ã1ŸýìFðA÷G8±vbû…!´sÒ^øížKþé仯 ÓV0 ¿§À˜¶i«¬e•ß™š„õ| .%gò+h1úY„´‘ô÷râ?Úa{èø!æLÃNF§\=Ú²… ó³e)#ÐfÎß4ÖOœ†öLQ¸9ÝZw‰×XÃP' }!Åào†TœƒW›æ¿¸­îúN~´”^4—r£ÄkÑyÆßpà_ÿ6?>~þLN¼Ûîý3¢oH„´sêãÏPÌ|5’ÇÇߟÎ:N+áp%î~e÷ú JÝžIð w‡ÑÀ (DîÝ.ÕϪž„ã+1·IsøR•‹åÂÜL~++y°—Wa¨ÓÉåÖ–îß‘S_ÅUwöªú†Yè®éõêusðZzÎo£†[q§Žã_Þ‹‚­•¤ºNÊÊúpÎ{cRæèPk+Š1E7 ÃW]% A1aîe_©að(z–îØåyïyÓÈ•Kˆyf†ó¢^äYSÃÆy2ŒMŠOxÅýÛŒVCU­îØ®ñùyÆé]y yŸç}Ê”`·HbcÂ,ïj®÷ÙûÒé{Ç<ÿ”US²ûZquP¸üq`±5Gþ!H+xýž·Bí¬5ýfèß’Å5UÒõJ@ (% ”€Põ™Ã”¾ê_a6¨ëêÔ¡C%Ïz;—GaÌáØízt}N~( ìüßÜf‰¾îÍÝ‚…xéäãäì²ß¬9lÄwuy-X·ÜjäÃçpøàX´oÝoEJ·!#‰˜sÉK² „öƒ/íDwIž>¾c8~Q·7Cô ÚŸîµîW×—~'S_”±UÖ°ÍB†ä¸ëY¯ÅGv—Y“* Òó½Í>¡óD#AÊmVf›úôAf1–È­ü1…"S«Q#Ñ„â“L^q烈]·Íõ0¿9ZÓû¦| ìÔ…‹Ž•_l>›sÎÕ•ÂdËCÉ]Ç#ÚšØÈì×q`Ž»Wò5mÎ68(Öȃîœgæ }(…¥¶aD’?ñí̤8”=V^M\Ô·•ÙçÖIfEÑäôÚoéö-"ô1¢ÍÑù‹<3@¦¦¡èL&ò™xØ×6òþìÁÃ8þÁ3{Tãa×£ÁUqôÝw€ «þý¦UŸ¿ý#¨ØRôi4hŠ2Nãø{K8KÔ—žõ5½•”EókªfÖ§ÌZ¸ÆRPˆB&‹=Û/Þ,/Ú¼º¢‚œ Þ…ñ†¥B v2ó€lÚ…œÍ‹Q8ð)tOˆòÛxoÿ=ß§­:÷!SOUbàÃ-`ÔE:NCC—âìØ¿ iË¿å-œ³q Ž}prS:=kÜ7x.,Ì·rôä3¡–³ty@›æ\Yö‚°í;nGÞ*ù£0€?•—ÒÙždýbäy˜^%â•䦼6s2y”ç×òZ½Ýq@t#ÂVl°’йÛÊ3†'‰k_ùb]ÉB Kâ,_Ãý™7q—.ã^"¯¹¼®äþÈZ.YìK…š‚}»Ås/wÇjœLe^Î(vÞãöÞ©¾WJ@ (% ”€¨w*› ªºN®Ñëü¯Gßgïà §]Ф=":ü×$½‡c‡WØ4ÀDT¯iÙo”¾A.Áµ…—Ù¾)³ýæ·¾Aû?÷¡÷ÍŸq}Óx®;ƒÓ^/SçR~(6síˆ÷0?iºâd^”­îò£^ÄàA•÷PŒ~ðÁlPùÕe±˜µågƒ*b¢]ÉÑY’ZÈ©«_äD0´²ŽÑ®¦— EŽñ0hù†ëÉç!O¡/£ ij勾÷[bjˆCtl8Äü©öÓS~µ«¿Ì#zbÙA _¥6 ìþ(aÞÒó-r&æg³Í*ŠŸ°rFÔ :dp¹Ãl³†iBx`‘0j(s+Åó¡”5)ÎâB$°Î…*š¿(‹¿¿Xãòn9Ë0¥mÿŒð®áÈ;‹ìí;Í«ŒâèÛàÔÒÏQ˜~Ú”—GLöö]Øõè ǵB<¡Fpþøb&>ðÂÿ• ¸Ó~êýhrÓ˜Ü5üÚ1¾ÁÁLJüii½ºz·c&Ò÷6‰†CGÿÁráP4¨VTq‡¡?áTÑ®Åéz6/*­Ó~|éÉâ®Sº¶nÞÙ$á¯)T3ÇŒc¦¹ÖG×ïfÝ›—ù|þуÓv»Ç‘³r>›jn)μ9•ð‚Ü1yf™æcg/B[^ ÅÉK°Ãë: ì7”õ’ÊÔm:¦§G\px{Ñm¥üÍP¦Ðö.EÖ'ß¶=ÑŠoy­LÕÓëÓ¹¾=æ_pßÑÜ8©L‘3Fyú&ÞBEm\«9›SË Trg{WO¤Û¤u,œitù㪆ޫ˼·£ÁèóÏx—f^ã>Ûq(gx² ¬|_—ãa•Щ¿ó°t/ÓW% ”€PJ@ (+”Àà¹øíÒÑplþ7^ë=΂8c³_AxƒÆˆò‘Ç¥eËɃ™&!qìŸÿ ÌsmC±§Ç-ñ¦¢ãôÉÒ ”¾ç»´'þƒÓ÷õADâОŸ—`ÕöÜ2u.å‡ÝŒ¾hF»Æ·óhôš¾IòêÎô{|€×gï·IÈc^™èæhÎR&¹¾wü ¢§6н’<÷ÃIf’<éb\ßå S}hìÍ4$wîòáƒØIÖ¾J¤ /ÛÉ»—ìýò]ÌoŽÂÉÌ%êª:}úŽM@öw¥K) —;ÌVóÔ¸ÿÐEL^†Îãû»vˆnËŸ2y‘dAÞÚÅ®åU¿x=¦–žOm9w­x|¥!Üs3ó‹éâiH¸w»3ÁóY¿iQJ@ (% ”€¸‚ œ8i< ìÝþîܶ?Yø>~¾íü6ÉïÜ™§VB3ÙxÒíçnÄmß,À™;Þ­;øuߦ|‹‘Ëÿ‡±ÇÞG“†´×˜æ›ûVV 4v­-sN}P=QõFosգطì€Ùaƒ±Ïbà*&Éý,z,Z„Äýz&¤©¬Gûç|k‡Žx×ò!yù 0ðãQÆ~t$¯À®/K·²E÷B¯u‹LÛ}Ö­±lI³1y½+Ä* íÙF×Ù¯bÀæ·¬éęڢÑï§y&-)míR¿cáµb÷±“ßâØ Çœ ³®žNæáÙÅ «^Zî_‰>sf¢#pD¨gˆÜUI| ãÍ|Ú„×®Z„®txpÛ~Aä:€LÚ4UjýË’Ôê_ôÓkÐoÉ2u1—V¨?ä»Òƒ„ñ˜÷[ô*ºÎ!÷§¯3û(q糩õ/LÅïïYS®_öæMÑfâo™Ø'Ç~„cï~hÄ“ˆ×rUÅY\Œ#o¼ËpH„´kcrÓˆ€#Å'(ùeLòâÖ³ùú˜Ïþ «öE¨j?µ^î•hX¶©, ®»-#æ¸>d,úE4ŽeYȈ¿a0l9‰ËzZ\¼€S‚Í.³ø|>¸OäÒm™àxV¹ÎxS4ý ¾—«Ëõ¡°¦Ð›Ò#îd–óÒeßуfOß{< ñ¡1sÑLnj¬rÃxf _Ïî³qrú \›è¤Á f¼l¹ºœfz>g@’ Ë©Ê2Ñ”dìÄvwŒá^!›SuK~™ô/×Ò{ÆR,eÇî"Iƒ¥˜äÄ~¾ˆúñÐâöŸálÊ1ì~ìiä=ŽÜ{‘»gì×öƒ¯+±xãÈlS5–rµÆúž ’h8‰†%t) iUÔŒq,³‹O`Ï«xÁ2L˴œ͔4œ^|á#zYË™óÄ;o‹«Rí_¬k8ÂDÎÜÊÚ—kó9`ƒ“èšfvïîs§ìËæ Ï19™¸Ú½¹wPy¥¢ÔÈY›„®ìÛ¥µæcíOÐkîxsqÉrP“uÇfMÁ.N%.źÙðB]¹ Áƒ¬„ÌžºŒáLŸ÷(6©½ß1ÑsG†ZžNn¡¦€\“ Q.¼kûþ(äÅÙ‚g)2_{ŒÙèÝÇFÚ­ªÐm¯Ò?|=uz¬úg³T`O ìKúkÒç5žEÇ'â]Áuã÷,µÞì[ä!Xbg s÷±òªÌ©Ó78Yïƒ<ò¿Ïð\ƒ S?ŵÌMãnÁªGÅz%Å™~×yž ¦ºÛ.„q«tÔW% ”€PJ@ (ˇ@á¼ZeWt‰Â ÏÚ ø?ÿ žO)݈×b3ü'ÐÉ)½rÖ§ï<ëññ¼îU߬˜7óøõ“áhÔ° NlÁAïm>Ç}Œ+mÃë]£¡-áY°jI…DÈ^Õ.éÛã“ ³ÈŒbȸF@úAÎÈêžµvš§o»†3™³ç¸{;+EDAòbd¸ì©–ÆðŸÏÙ®Uú³ý(øg¥yf>’åß…¤Ç9‹Ñ>ô§Í²È —Š"3̲.gD:ÞfšÕD=ú½k gÁâcñ¨Q;Ú dÿAælM*3~ÉMºcÔ ØÎrű3«Žs|ÞeÇY_¶lè[îö—Þܧ ©Í”²€3CuìáYÖ~ö\:ìÆçmhI„u‰H†>u§-\¸o›©·‡ýßÃþGiNGNbtde¹¾{š»$olŸÅu§NuŽEB‘(Ƹ‹L¯-!O"Ö´ø;´ºûN³J oýÃddnØì®j^%ŒÉéä6%ewmoÚ¦?Œ¨ÁiDæcÃèñÈN¶.…†}z'4¡9yHç {dþ»L6|¦LÛuù¡&K¢'Çþtý´slZNZæpɢΠl‘eÀ‡LäŒIað/`xϪÝçØæ¹W·.pªrLNuÚs³9÷vÎk‹Ž‰ˆe:ó³ÌeãO’ãË«¯Üƒ£P”J6.^•í×Ü@Y¯$•TÊ]è¦~Çè<™x²Vc뤹Lå\‘ÁsÆ6u~|g?¥ao›Ê䳩l_Õ.3ÓêÅN_(ÜOF×TsUmÉ0²ÐèFp¤#—ÇÂx UU¹Òåñ CëeíoyVqþD¡×R=§¨Äg»9ûÀ›aµl+Ý­.TJ@ (% ”€¨S»Sªÿ^\§;»„Å27I¿ÄV|XÚ•³@åcç°6XÁ©ÀëªÄÇÒ×¢*!à±ålÅII“Ñ®šsV/ ¿:ÁP«í‹*Ù¨ž-:?±¦šA·C›IwÁÞ8'/å´ÛË`oÒ”[8‘³{¯uÜ›Kš†½»£05¹û÷ÃYTÌÙž®A3&S=ËÙ ¾ú:…™ÒäSþ"˜¿¦=¨6&ï4õÝmÕåkäÀx؇<ŒNc»šfOOïsêrÚÖ…$`SWþ˜ìÅÏ3aÔ\³»ÐQÓÐíé‘–ç Å£uÝîò„o]ÈþhÛJ@ (% ”€PJÀ›À•"Ö´~ã[Üx›ÈœèhéLÌñ¨7†ïý^Åšïð‡ÛÀÀÐïùÒÙr­2ªcá“Ø0¹œ×N=¥p^boH0˜Æ&oŒ#/SrŸ4ùdÜcô ‚dCÂÉÐ¥¸{ ‡k†§ÌM[™¸5ëBB ºÿ÷%“”x×ô§øâÂY¤ò!9l¼‹ñÆa2â ]"©þv7 \{R£þB#¿íãtq=33‰zjåͱ{öuúåÛ°ÑbåY¬o”€PJ@ (% ”À'p¥ˆ5ðé‚Ö·óxù©:"¬bMü7#‘-m[S»(Dú+ó/«‡õ眳FD˜Ø_ÞŠÆÃ›œ4y›DÂ'>üŸ ]’ÙšD˜qg¢9)½xÏHNšÓ«¿Aá©4ôxs6òÇο<‰´ÏVI ™xÉYsfÜúßgÖŒR®G¶—âʸJ;J PœM¯׌S®jßûŧԞ§ü{oWï‹ï õ¢70«Ç…¡ÏÓ£ƤT&Ï‹û¸fÉÙO`{™éå.zu‡J@ (% ”€PJà‡O dγòƒüð«#¬rV1ÿϪúسšûtnb …˜¶÷ßÍÜ1£ÌÚ%ôŽiÐój„Ä·…ƒpbØRñ„9iR9wx·Î(:“É„À«àÑ~L„Ø´ в²°ëoÿ0½l|ã`´ýÓ$·Œ¡`3ˆâN N¼¿¤ÌºtBÓŸ Ehç†N¥âÔÒÏÙæ—F*Sñ{|Pëæ0ÇIt:sœ\±¤ßWýÝtÕ Xß÷Ó¿Hª©vŠ6’ 'Miý=fÚ3% ”€PJ@ (% ”€0ÎI¬ ïÚÙä”ïñtññ÷7áJ"¾4½ùF3MwÑiWÂ_WòàŒ/× sãŠ;>Ì?“ÉS³ñŽ ôÂ)@áÉT#úˆÀÐ(Ì*.ÅÇßbN´yïþù£þHøëƒ ¡j™¾[ö-^;^žƒC³ë6¡LÎŽ5ÌíÞ³¾^î2¨¦jQJ@ (% ”€PJ@ (%p¹ð9—޵Ša¬—¿å=ã?’ ø^ÉcSYqäæ¡FÖÉ”ÛY›·!gçnØ(¸ˆè"%uyN.ùÔ„>¥Sà9ñÑR³\~I¾š–wr¦ &/–)Á%dJ#_î³Å/n¡gO;O]}£”€PJ@ (% ”€PJ@ (Ë™À9yÖOŽÖäÏ'œ%Ì%Sbc™Bfœö°äÁ "Ð8ŠâÌw8›rÔä°1ÓuSh‘Pª¦#‡ñçðÜ8õÉgØ÷Ì‹Hyým>´҉›7Epë–¦m™1ÊæÇ©¿éÙ#"Ž_xöê†\™iJ‹PJ@ (% ”€PJ@ (% .sç$ÖdnÙŽ3ßlAã››a‹p"aMâY“¶,‰ydÒÌrÿ†áh;ù^DôéiÖËòÃsæãäâOÍúݺ ÍÄßRȱBÚL 7!Q§×}ËÜ6YŠä¤8$ÓY›“AÔŠœÅüL±¨$ŸÉŒµ(% ”€PJ@ (% ”€PJà@àœÄEöÏšâÜl4ìÛ L*\pâgtZ‰”ù h#LšÞ|šº™aNVó-š!fìÏ‘E±çìá#¢—Œ5F„)(4yhdÙéõMˆSIQ!½u$¾Ê*âe“µE¦üni¼id;zîHHTóÞd¬^ﮪ¯J@ (% ”€PJ@ (% ”€¸¬ œ“X##Íݳûžþgxº ‘f îÌo·šéº †858ÀxÀ8Å#†Þ/6_ˆ`#ÛˆX“·ÿò7ËÀü3¹»÷™vÅC§¼P#mŠGÍáÿ̧—N0ý¨/§ï¡˜S‚ìí;™\x®ŒÌ¾õ—PJ@ (% ”€PJ@ (% .sç,ÖÀÇÆ|2鯛¦ÌعÜxÃPp9›r®5«6昡‡$/)ÙßíÄþ¾ŒÆÃ3ÿŒ޾ý>²¶%›uÞ5Öë·3{Ÿž…ÌMƒÔ2ŽÌ,¤1ñzãhQJ@ (% ”€PJ@ (% ”À…À¹‹5žDïIîk ÅOÂa•ãï‚=»!´}[S·èL&Ò>ÿŠSxoµ6¡§Ìñ÷–pö§eœ†»¨LÈ“«‰Š/ƒÎLÁá¿Yv[$*»T?)% ”€PJ@ (% ”€PJà²$pîb Sšââ*œµå;ìœú=g®G`³&&§ŒÌöTfŠ,’{F„óÃ6«-î6ÜÎf³fw2ª*OœjÛÒ•J@ (% ”€PJ@ (% ”€¨§ÎO¬©Å`$™°üTYÜâKM"Mù¸œ.\‹PJ@ (% ”€PJ@ (% ~€,•àÀtHJ@ (% ”€PJ@ (% ”€¸ ¨Xs95í³PJ@ (% ”€PJ@ (%ðƒ% bÍöÐêÀ”€PJ@ (% ”€PJ@ (Ë‘€Š5—ãQÓ>+% ”€PJ@ (% ”€P?X*Öü`­L (% ”€PJ@ (% ”€¸ Øìv{ sf_ŽÃÒ>+% ”€PJ@ (% ”€PJàò$`ËÈÈP±æò>>î^L–¯(K*ÕõŽH€H`àP¬8öÌ™H€H€H€H€ÜŽ€êòÆ1ŠÜî°°@$@—Š5—ØguI€H€H€H€H€H€H€Ü›Å÷>>, À%F€bÍ%vÀY]       ÷&@±Æ½KG$@$@$@$@$@$@$p‰ Xs‰pV—H€H€H€H€H€H€HÀ½ P¬qïãÃÒ‘ \b(Ö\bœÕ%      po^î]<–ŽH€H€H€.<³ÙŒºº:TUUiK«Õzá3¹€)  44T[šL¦ ˜:“r7=Ÿ<Üíh±<$@$Ð?(ÖôW¦J$@$@$à†”@“••¥ 4nX¼n‹¤Ä¤êêjí§)á&55U[v‰;¾œŸ<ÝaeI€Hà¼ô«Xs¼ ¯f~‹#º+ÛÄMŸ†iÓ£àÛ]ú“ À ——õ N½ÔoÛ¶ ‰‰‰Úo(ÔéR¯ÃùžŸ<.õ3‡õ'ªúW¬)û™™zd·cÇ«@۬߼SÇõtÈìl(ØŽÅOÿÔg:Ön^‰(é+B$@$@nIà|_„ݲ2N…RõRÝbâãã|¹:Ø\ˆó“çÂ`;ê,/ ôL _ÅÕx°;ÿð)?Ú¾u8q¶íq+VmÇŽ×æ_6GÊ>•ºKåÛŽàŒ —$ÐÏ*?Û‡†‘&ü(.´KNõeûQq,‘W¢â³B´ ýÚíXÆtÏSµhí1Œ “âüðË´ ƒÐ‘ÆŽI;mõ­¬Ð} N‰çj}Ù”W—§LãÖuži ¦h§ÌÅ(±x">!>Zð˜ óÐè‡8“߀–¤¿3WçZÅ1+:ø?ld("äzý¡çyë©*Ì.D“\¾A³Ó>Ò£¿«ävé+Ëõ"ëÚEã¦ù‰ò÷†gk+ê*°/cäòwé’nº ÑA£á)wÁ¦úc(ÌÞ‰¢:=hôµéHŒ €·'`mªGuq.>*è.%—ÉŸ—gNNŽc,›óJà"D2•‹ïNÃ;åe„IW˜Ð­v§ðç¿iÁWŸ~…3—_“ÿù'Ó1{>?Û3¼ñ–;âÓ‚ÒO¶#·›Ój0œ ß}¹'›á➀Ëã&\„s¡éÅX;!÷øêJ+F^™ˆ0÷ß*áai@ÄÕC¯î=òm®Aa^ ª,õhhj…·¯?Lq‰ˆ ïúÌo©¯BQ~!Ìu hmõ”°ÒfO@L¨sX½Ý`1„#!¦k[·sYê+ŠQ\U¿PigtʳÞ\ˆb‹"{·ÔJ=Êà™S Wçl¸M?ˆÀÅ9£Äræ™7t± i8ü!/]M³ùöSTXç#ÖÅ;¢6èŸ?.vuª¼Úø€} Û)j_6%qiëj_°z ®Êì,V9‡7ø¶×dDûªsm½§4º¦ ôH Õ¿~§j—ãò¯çG›Šv<{-ªŸª‡!Ê=ÜcJ}Ýé•ÿ*$­³."|'~å·!iÞ{ûXÖ’LDûï¥óxöÔ“Ë$ŸÑý˜ êU]œƒ‚ÊVøEG#Χÿøö^É&å ªí(Â9¯Ë9Û{üÁBkÕO™]¸"pb>Yްó>k‘›0°ËÝ©"ÖüPùÇeQÝÚ3++«›ò…á–{RlGâåÿÐ(Ü|ðÒÖ=]â¤Ì¿1ŽÀð3†!Y^ ­¯mEsÊHi¼üŒˆœy3|}vâÝœnÞ¬»äpþªŽ ,8ÿú5æid­]Ý~¡ŠÀª?>‹„ ~¼ç4|‡ÿÚ¸³Wã/÷·§~­vÿøã{ 9yÖ|Dëåö1°'çÞçB3ª~ù€´G\× IÿüˆÛç‚ë¬ÏÛ·µröÞ»>XŽñ.îÕMÅ[P½,Ǟ݆°Û'ţíZð€|$[„±_¯rBUÇʶo5WìÃæ]E"y;¹º£¨,/EatîL59vT忌ÜjǶ¶¢…-AnXÞý…ÖŠbi7T¢‘"ÖtnëvL û2÷¢\ ÈÓ’9>PU‰Ð®·…&öÞj>Šœ‚„ÈÇ%S`Ϲv,·H wG¬‘r¸z>ûFÝ€%SÖc½­§T‡¯-Öo‘ñÊ&lܱßQ‹ÑSnÃÊeÿŠéã;Žpc=R€Wä¼c…#ì´ÛV m|2>.Æ\ƒ•ÍÇ8Ió…G7B… O_†‡nïoýöc¬^“ëè1˜¿r%®×®¢|ûE6mÚˆöäÃqÛ²¥ø×ôé,Ž|ŒWÿ¿MÈ:`7üçឥK‘>}<´<6J’¯<„Ä¢hÉËeÌž9X¹2 cD :ðñv¼¹iœ°8Ê>í6,]ÚµÞŽ\!>)ä%­ó×{Õà ÿcàeŒÇUŸgê‹Ñˆ¦/߯Áûž‡ÏëÛ0ùJ?XåKäC+šå«²Zí>Œ¯± J”Á}D²xœmV‘­8ñåǨxp—= sÚË0¹hØô©¬*í~t&ý˜‡;&í驎¬§~|´€ÞˆáMá}ê´ ?8sý\ó@Èî?!d$´ëï¬XØ7®Á©Œ-(ûË<„-Ž9¿|ÚêåªóÀ°•/ᆥÓÎ/AË>«Ž«jÌHt5UÅûpTÛ áòBì…´É{YäK^ ìBMM¡0¥&ܳ F/LNJ"õé‹í«Â”Ô$±‚ð@°)(õÄä0Xë*Ñè‰è0_´Zë½WÚ30+Rm7 lo®“EOf\k‚¯Alxºìs*—mUYg¨ººë,Qþc¥ §gcݺŸÂ[û²&¿¤u˜ÿîËxaw1Ö®Û‰Íëæößõ.Í:Íz§Cc³+Ç‹í£ŽÙñãÇ»Í6lF:ÒBm/£*XK·aí;Üý\Оñ~ fÇíðÖÚÊÊÆŠïÞ~5ë2Q¹2oÌéÝ¢Á^á^¶4W¡åë—§TÑBÓá2Ñòâ>Ô‹Xãü:_Ÿ¿O;¢^¿_u‰\®YéBMÈäYH›%÷ÑÔÎEæž"œ,ý…‰‹ïçFù°¯ 5žˆHJCjl¸¶¶¢íÊÅÉʼ•击m⎧z}Ë­ÚÌ–#ùºP£Â´–#¿ªÉ¡í¯ÅçÔ’v“ÖrÒÚO=dÊ]$pÚÏÊóˆÜç(†.f¯zÜã(«hOÅ!è4`¹ˆ-G»9q`V-Ù%ëÿŠŸM£E´~›‰¹KÖµ'b[Û¿c=2Opb¥ˆ5ò (;p@K×z²‘qv§EŒ‘¥<×4,“ºXS°}9Vlê\’ ìØ¸ ;¶/Å_¥ë–*É‘}/àîÕ;œÔÖ-û±qÕ|ºâU<yû%M¨±‡þ&1aGMi.3Åvç8¨n³2>ar­ØÅK¹Ç>û ²3¢e‹œ»"ÖhWJçz}åAÔXC5Q´ÌQßà/V"ý¢¶ ‡‹¿A°t1èšNä|6Éõ®;×ç³ï©B´†ÅcÌ©ƒøgv)p™ü®LFôD?9Ïí×OÆÊõ3~¬:j¶Ô*¿AÅ—¥h:ÕŒar}vÊË^æ —×£èíyiiƒWX4&J÷C{iT*§¥nß|yÍV#Œ¾zœ²ÍPÝ'kÊê´rÌLvÔG½¸vç¢CtfdìUÊL=LÌòõŠ–©§ÍE‹Ø¢¹f êý0?ÅK•Yºë£²ÁI‘ÍhimBÉG™(—,#­"àøè"¦ÎDr”Š?Ó–šZD!&:¢’;üb¢MØ'V:Õ“ÓpK²Éér¿™ ó¾wE@ê¾>ª®î*Öè•ôA ¿ëÔû§»FÅ¢}ØÓ[9V˜å%6ÿ+‘üGŒÅ¤äkpE­£Ôéïè âÆâPæn|qì4üåZŸžšŒËG´ß÷!á>ýô ˜cM“p n·0sï%ó$H­ïò³•oÅìEi’Zš±ï‹|giÓÓ¾æ G¾û*_ºZM…ix9²vŠÓ¦Ù˜›à埨 g†ÇØË'ɽ)Z‹ÓÓù©ŠééíÓáwú˜øg)˨S¨“ç}‹tŸàhØ24ÆÀ?½ µbq|l✬†*ÄOævC@šÉ8V¼UeÕh9%÷Ci…J^cmmš*Ùw6,!0£$3gÂ’qåÕƒ«­ñ}êÔ–zߘ,BVs/G%#µ² »J›PQÕ€øà“½åÚÞˆY 1'ÊÞ9Û á ¸ó.oüys6šJ?Áᤱ¶Ö‚öé¯ìêÞî‰Ø‘(Í-EI~©ˆ5q=ÄmAÕÁ"”škÐlô@ˆŒÕÁÇÓ³µ‡KQT&adOx\B—îU5Ò½ªä`•Öºô7Å#1Æ&ĪöPIü£#a-Ë—÷c‹ä㇘É3ÄZ§ ÅŸç£BÚSF¿PÄ&LF°O+*–À" cíih¥o”g}š<¥ÝÞC}¸k°pnZ÷_™Û£âÛ##1Úµ©r²žÀ¾?¯†C›˜žŽHí mŇ«Û…šÔÅkp÷ ‘°üs;–?­‹!¯.ÓvªnUÇñÊŠv¡ftê2¬]2 ¾ exõÁÕÈòhhÛ?¤D¸éò¬î¦æÖo3Ú…šÑ7`Õ“w!vÄ l߸;”æR± OoŸ‚ óÇcÿ6»PŽekWbZøh-ÉÂòµ¯j©ï_¿ Gv<†Më#ñÏÌØ˜©T* »fÂ¥[T$cµ F[xžYu§øÿÌ’<^Õe§u?Eê†úï«S7è=„ŒuÝЕ0¡jmÿ~wÖ©ÙÔ™F_ÂtŽc{2wñîàÑDzjæÇ×?ì(«J 8þ÷-ò%ÛˆæÊ±÷úå’¶ÈÖwKƒz|ùëù8žÑþ¤öëî*Û²ÿÜô0¾[—gß!yÕb5tíÉ06ƒÃ‹î•÷0y9û\c½*y+ÓaùÜE3s f¼õ§FcûþÁ±ÖŒ}ïlFÑÑÖöâ–—£(7×.\ˆ8ù&ŸÂð×Í{pÒ¢å¥Ú–gȵ"ÖHƒ¨×0&äef‹•A>p³=…ÈÉ©”Ÿ#QYQé–"å®»# æ,lÎT 0›“r•èÙ"ÄÓ„°„A{w^öv®Ï|åOɹ=±N¾òªšøÈ9'ª–r¯¯AùëàUò6 Dz°O® g1TÏϾ…ën—Ö±Ëó9õÒuÁR«¥æøÓâÈfûå£çuÏÁ•rýUe®GɃ[áÕŠ²!¨Ù’‰™2FÖñÌG¥Ìf9’]­ÓuRùþz\Ö1¹Æƒm׸¼ªáó§£A«³žŽv}¾˜¤´PTUUuLÜiË`Ô%Û«ýNÖ€¦Æ6ø»è(eåŒþˆrŒyˆà_XÞÜŽŒÍoj»’nÁñÁÚºú«:°¼Ã±Ý\_‹ziHúHL 5"þÔÔ(/"b¥7+Aaú ºˆsùY¥JžP%MñSÅd_ÒëÆ©ºªÙ¡ÜÖÉô öö˜£ŒV]¤qlã(^¾ÿaìn¿[^AòCÀoR/Çión¬]»³=¸mmË›ï`õëÏâ lN—~€ûׇ=à+¶•P»G_òyr'B#F¡ª\/Ì? |ñ Þ¸Û‘ŠZyå…üá/¿‚”»×®…Ĩ3Uú=0åG°¼ñ0Þí|†.Âëÿukç§J»<{+^Ê–•ˆ±xNdŸ…·?äßå%D„û}I·îùÚV⪫ç·~?™€ar¿ ©üPºd?/ëyÒmÑC!Õîqe‹V¾ÓÒ)âÑs>õÅo#7}­Õþ§îcÇ2Ä9=]dÝY¤` \ŽÜŸ;Ä;}ãý\Äš-¨ûÌ Ø»€Ë½¶îu¹yN\…MÀiFÑïÓQ£üœ\-®Bxþˈ)ÖG÷J2$âPmžÎê¾·qµ5.óÇH©_ƒ<8²>?ŒÔD=l'Cxê¸%¶þÁ~¢Ò£Z½8zG#Õ!Ô8ñ‰ARD.ö”7‰ˆÒ„(y|õɵT _ÆÂð$%Ì@}ÞATVÁÜ"ݘºœ”*ÅF|ò×Í(qº'Ië9E¸iñ­ò6§â/ß³½Ã³¬¼¼)"2ÅH]DžÉÿ-äjRiʵ-m”‚¢©b¡9FköæìE«üœ]y©³y§ ¥E%H»ç&gçHIÚõáÒ=×öÌj©{Ká–¢‰]Îiq}ppyJ^ðªxX°véÝÝ&Û†4¼ö¤.BXeì{·¨Äe›ðXz”oü¸‡°Cº(Ý&*ªûÐ_?=‚•‘ÚÅži+°ù±96!c{çy¿í×]¬sº-D—V|*]Ÿt7 Ïm^eOg<Ú°c߆WEo)Ø´Gæ/Ó[жÑŒqãÆÈïgxÕw´X윖±t|e _ŒŸ:†iÄ)±FÂN™>š±Œõ°££G`ô˜q7Æ€q?{#Fg â´Œ—cãÃ8/Ÿ¯Ágÿ§[{ÅžF‹Üð!þpg¾®C½2snVO\à¤t5øæ‰ˆÕMi¤$/bÍeß´y1høì/È_´A„š #×ß•rýÙÅK¦X,ƒŒkBÍ"Dç?¢•sâëñ?ŠFeÅ&b‡1D•°ŽëÏþòõjše¬¢æ<]¨ œ‡ðí+ÄZÍÇ>{­EͽbÒÿþ•"¤*¡ÆGÊu•”«åØäϽ>‚Òü·Ñ›å‚^õ¯\NY[¥ûR׿Q«@û·È@ÜB¼I,å+ûÌ”y©Ö_ÕàÂJú±§tÅ,Q§ì‘-ÈzKô)X¼ F´Èy¼U^6Â0ñÍÐǦT]MmNÒžœj„¥¾5GQ¸GÝ«»w}©k÷±/žªOñEiÆž9£YÑœ±˜ñÁo¢X²Nþùlí÷«—Ó„šä_¬ÆCiWàÌѯð_¿}û^XÔé/c’X¦èn*Vÿñ7šÅMéΧñøøBLâ4^Ö„šáX´ú¸õŠ œ>šu¯Õò±×²¯ù(¡&yÑ2Üš ùü.Y;Êz¹¶¾ìéù=Z¿~’ºÕÎèÚô{œêÊ­\ÏùlÁégu¡fÌn¹?ʳûD±ÜÇÒ…å÷y˜Q’ÕF(Y´ãþ¾Qcý\~}å)ÃX7JW(kT©ê¥=¤ZF†eÉÚvke–.ÔÈýú*¹_–}ßf¬Æ¡_¿“b‰Œ+ ¦îÃÒÖ8;sBžX€1_᫹â7¨Ú˜:9UE5¨,؃ÍòóôöEPP8¢ãb僎ÜWÅ5VWè|‚Â\2UaÂ#ƒ”êËÑ:1?T_zwõ¥…š:*FuãƒxiV–ZPXR+‚¸ž·s*µù™ºPãô[S*V-…nCNùQ|ôÑaÜ›Ò:âÚtÜ(–U–ÃÙÒþHu!‰‰“c© 5ž±HŸ›„€Ö£Øûþ.”Ö ³0ó&é‚O“$¥·aüQ¸s+r*ÅÇ3)·Ì•n¸­øü·PpÔ‚b38#•¹GQTx13ƒµB”Êq‰3T{€n(èpï¨ yȬH'Ž[1^Ɖ±žv´h·7ÿ'ä™.NÎß~á(âñ bœ£tmÝ-ù×k:ZœøÆâ_o ÇŠr‘œ—“±5EÙ?–‡Š^ !VlÉje·>Mn©¢Øˆ´vÉXÛæi7Ü€9i× 5}*Dwiwú;«¶íX!FÝ(_éîµôÎÀèp¤¦¦ã†k¦!}êøŽõÓbóÎ@Ë[´‰syž1DÐÈ}½k\ó2m½ý+W×ÝšOoem.“®äåÑk壈’‡mã)øÊ9»@ÄšmÛqìÔíð]ðyÙ¬„NIhÏb5~O3̯o‘¼& ü©y¶nFD-x 'ÞÎÄ)%¶HSáðÆçeyBîKÅeÍõò%ÑËÆgÛ–£ñËok”ó¸^„™'ô~ö­ÒmCsŸ½‡ÒÏ¢aºr®\—ªeµ0ÊÅÁéä«_þQ)ºI n‚IYш‹›s+Žny¥ f”5ŠÉ¹´™0, 7‹P£‘ ŒÂM³Ê°yþ2«¾böFKºË_PÒ,jÄA\È!Ù@IDATÔôxì-Ý‹:³Œà_'ßÉ¥Mv­&Ô¨ýQ×aV™YûꦶÝשó¡Õ×§ ºK!Ó0áöx4—eör®/Âe¶¸þ»ÿ»] ü¾N§Ê3Á¤½ 4—}¬¥ƒû^a‚Ã6ÓÞz{gËËG¶Y®=!çóY}ÔÜÄåHADW£t5ò´,X® 5jhÊ›a‹í븟Á°+’5¡¦Y®¦£úSo˜£é­¶eÇõ燨Es+_ OÊ—ÏJù¢­œïsºP£ÖÇ^};&½h1'T>z|ƒªuf¹„ATš Vu}ŒÁI¯äÁ÷奪AEéÖyÚö´èÅ’­øvÓ4²ß4Wa« L)}(bX¼'!}þLZe@çwßÅk²{jú]˜*]XüC}Ä–= ¥ètp·³·~Ÿ;®C¸¿Ä——Í@õÂ,3Vðævõæ?X]96>þ¸‹Â§àþTyéj+Çßþ÷¤ˆ!³±èšI8sú´ôG›„{߾ bÌ¡“˜¤_þHYõ+GרèëoE¨ˆ5_úÖÅØ'9Œš½RjTf#‚°rõ͸O,e4wùÄ-z¿¹Õ¤E3ð‚ö’7ûþE"‰§oÒìŸcê–ÈÚËýz8¹+â©g—!Z¾Œ´5kqqh7²rM¸æŠhÌ]õ:ªŽaøØáb…s©ºTÏ^à¢ò‹0%­/×¶|êõùí"y'/ÍŠ§×{ˆUºN!wK¾½OºÛÅÍÁŒ¿Ç˹`žŽFé*.×§ØŠŒëkלr±­'HÛ$ÕÛÚ»BUd¼!;ÅJ0E‰Ö"òMÜøG镤 5§å~ö½­K¹ã–©…Lä7ôÁŒ[+ÍšY[#<ùÜ}¹ybÑR}MM ¨KõË6„অé"hèU“éŸl+]ö=Mõrï—gIïN,\ò+%˜Ìz¯s»"Rz¼ :¿Íñ©QKP‰!*üŒyi"ÔxhYÄϹ õæÂ*÷xeᥕ#( sD¨Q.0j:"²¢\§FyÃËËSOw¥K›Ÿµ MRÖɱ(•A–š%ýIj~AÕ†Iq´a¢EhΩ,Ǩثã¥%\†‚ËUþÑdøæÊ‡°Ò4‹X#¶¬(RCb‰¨—S È¿AMÀ뢔^fƒZ¿ùIÄúª×ÝY*¹ñ×ÐzøˆÀ±bݧø@ºø)id·ëó\—ð@Æ×¨Lo;n´jìvt£Ç+•±¢£g_·¬øÔ)êŽõë]Ç”²—I#,ý¡ç°ìô*[÷& *ÖDû³vh?qÚ’õxògbUã:ñƒ_üuN¯X®ÝhÁNT kÇ&ùé‘–<ógülÚ¸nSàè•€˜ÚÎøßÛµ—6ç°ßþå~zâܾÚ9Çïq=]L‚L° 0,ïðòÒáƒñò…¼Gׇ²¶ÈR¹–uà³uSû'ʬÒÕb2Šþò>–¯^ZÃÌ,Òö Sï óq¹Ó7j|Û“D¬Ñ_PÕ5À?ðÝõIú Éö4dyVºFÕ/NÖ|Œ÷Å;òžÒüòÇÓðÝS™8¾hÖõ‰ùÔ*\eÏÛ)Á±jky† Ò&Ôèå6",À[ÄšVxZµ×zC¸²iw>¦Hx‹°ö ø¾aÚ£vX ’OÙvg3—±JCÎ*ã](mÒ–ö¿ðé_ÝìÛî¹TTà+c¡ÉB¶óT¾üŽ Áå2•«âxúXoçzƒŒa ÒY„ñ.¬¶ZõSÙQý‘é1Žuµb”qbTƒÀùq>ŸµÀÇäú«±Ú†üÙ¬å¼Bì>ò²­aÛ¶ŒÝpµ E/þ>z=§=Œ¬yH=Û¬N×ß°±&m×YõÅýT¬_…à+¥á䯧ýãÕö÷õ2ó<Ä>ïB_m15@,Œª««»ìS•µ ˆ’1T” 2U¶ "¢àokµÈ¹1#E>Öúhc&•Ê”±1rþI7¨$ +“e;L÷1Ü¡jŸØÝ%&•¢2§R¶¿F8M²ÐÇV׌´x·ÈØùY8P€«¥‹e¨Ì(‚M´äR*?W.  //+®b^$¿á)Xý‡ŸÂßöñK+ó·6b_U6ÞÈÿ9~Õ v'âNîÆÃ÷uìj¤¼³¾ø‹´¯ØÃ‘:JyéÎÖ¸²XÄbÇ* ЏÔùTíäFLJ)n—žþ龿LŸêHÅ~ŸÙ½^ºi9|m+g²`>½J_ÂÔÙšP£öx%cÅÍYX¿³o®o*Ïᡸùþ‡p¯Éê˜uw~ª çëÜþ\\„Èí‹ÚÛÅ"‚–?.VwŸoÁÏ~)ÝSt‹t{m—Ã_Ý×zy~÷ÊÏfÙÓ}>ß A¬jëÞÖ×Å!ùøtHõ‘:â‰Eò /·¡^3Ñ„ßw¯ˆ5¤+”ÜÒ›µ.Pׯ@„íþgƒ°ÈQúË8øws‡Tõnaâ%÷a,ø .·}ìœm47·Êý, ×Í‘ŸT©EÄüj±Xٜ̕ê¬ÕØ%£»ß“ +³­í¶†˜h6!'ÈÞuŸ Ÿ¶Úi«¨­ØûÖËØë¦© ùµ)˜ÙAè)5Ô­Ü3 ÑÚ=Hžs³[žCʘ¤½áìÔÉ¡âÊŸ–†<£s36#×9ŒÚ+ãùÕ{DkAC"ÛëѪ+¨ñÓÔ³Ewv?më²($„dcouJëS0ÙR õÄ ’1m:‡/æÚº*®B’ox¤ükö%˜²dþ5VæH²Ï °¹Á‘ðýú°#lƒsûÐæ´-ž=N‡¥SZº{ÚÚ¶a4"GKINÈ–XɬÚð8ÆHzz6Ò?Ð|"Ç«qH_ùÒ—ž@‰ Uòé~ddejÆ6*½ý¯®@Æ´0?ÊQywp†1S°òµÝXzü[””àÓýYÈÊÜïèaõꬳÕÓ!7Hàœ8øçëü{É×÷8ÛüsM¥×²ª†š8ïg_BhœAöú¶ö”:%_VeÀÏœëÒÐX+÷ž•ÏÈ׫hŒ•q4Ï—>æÇ”8%W´jøÔVË—ÛL!¶$šª—»1ú– #Ý0.c¼T—.Í©G¡¼@”6Yª—\ƒ¼¶;#~´ø?ñ£Ÿ?†ïŠâÈ—Y8õÔœzpöÊ´å)ö>ëíÅšÖà3ÅKoFÙ‹­µH´Æ†Z³6t´hh“i6›Äß~ìK{ÊíKOøû©Ö+§çßÔ ÇÄ©d–#Ëá*¦{øE dÞ ºáª@½žë¾h*–fZ É©ö]j± ,ºé´¿ƒÈ¢‹6Ïg§°V•¨âʵÛ½3]Jˆú£ nlÂ(cò¯möËH5-¥Ìv« ÎéèâÑ |¯Â;µ>O”I7Fc(ÆÚbˆ5]äƒÒJ1v8)—!lŽÕœéöe¸´@& –BmæâÅH”©»õ†‘E¬{+y‡Xù+aWÌä_2£1&ABŠÙü ¾=#Ô|ù%ZÆÎѺ1Çß,cÖ8vŠ-¾‘ÁT£¼z[õ B ¼Þ¿Š:½mÍ5¹É©k¯;ÚÝ.÷ð÷ñ‡)(¨]ð“Q^–­ºû~E¾v§Æ\ÖÜð©‹°ê§"¶Ø^V4Ox†‡šÄœ%_6½á?Bóíúg‚ŽÉÀÈv t¦Q„š6§¼E3é5ŸP˜ÄúÅîôâH÷ª«0É)iµ_J§ù}%ë£.o¹Rw½÷®ÁÖE§a>ôò¿Ø‡vîÃÎÇW§ŸÅÏä˜õ‡XãöçÂH“DÌöÃu¹2‰ –þ]q!Nly"º”m+”qÀžÑ¬`:¤ß͆_l²Üë6 YâUEJ çó`¢ãÖV÷ÙzéZºEî‰2Cìë«0V>l+U·Ïç;éÃb4‘HÏfðµ5fbsv%B’b^¼ŸV eA3áá>xisŽt•.P!q2ën *KÄf$Näq›«?Œ}Å^Hž&+•š§AfÍsvz‹ÀÙG_/Ï“òeÕ0*òLÑÉUKc êäƒSI^fŠ€ÔÁ©@]Ú=Í2À¯Y)4Ú£*ˆ··^—qíZFþ˜‘"î–BªÌr䎦Ú.Êé%Ò×õÿVGxg_}=:1{3JP\,£×XÌâé‹„Xûƒ±kxú >îS½ø†H¤M±å*ˆAVÇkßÊ4ÏqÁ±˜*c¼LŸ>]ûMO32!zÕH×£1aía·ï+éTüo±]Lf»s Ê:ì:þuç°Ò-)ÜÄã™Bv*¦ÚÊ1]Æ™}üS)G2‹k¤ÜGð?Ëcñâ;ðhæ {æ?´¯mÝWWÝæ”º-;9§Á^|åwü‹ï¸C,kÆ`ê sð¼\nݽk¤;—Ý9·[ì~\’€;èîEîB”yDd´ÖÅ£és™Á%n ¢®Ô—•lAù¢mhn«ƒU„ša+ßÀuKçH_è 2û„Ê9½´ž.{Mœ/+ ¸\f¿I\¼7”¼¤•÷ì±N÷=¶[ýkãÅt)‘|Ý—Æ…G5r‹¬Ý1#¯\ÕILÂ¥±¢5WêòQXo# 9N÷h5‹ŠÛS{Ú}\z„j!îK'Gœ|ÑÃÌ9Ž`n²r¶¹Wç"õz®; ã:o3iç`Ããï9>¨ý•ÿ«wæÜEÏé|vN£¯ëÇÌZÐË^ߎ™ódv¦ÈPé( ¿$Á¨žø½»2[ 𠎾_ê¸ ųâÐü÷pú2K'Ê®my0È‹ý0adµÜE]³tÅêÉÚ¤.J7:•ºC¨‹G¦\¼_t%@³|Ýõ¹Y³SÁu'3„f#»²Û3 aqÒŠÔþyØ·y—-¬òh_Õ×$‡ÖÎ~" PÛ¬ u|•¥ÍÞw¦öî¯Çºv î>þ¦ö—0_™ÁM fÎ|%³¢\!ãÏ迸f<¹v÷¤TÙjãsy”Í;×wêV46ÿ¯Þù‹>à¯Úîs>"ö:5¼L JA8ƒ¯,2­lW\1 æ¿=‰µ;¤:ìwq§Kÿ‚;îø9vV ‡éЏõÞßàeyWe´ˆ Ô÷cæâDé’[»GßÓm3Ðk2c¢—½½^Û}x~Û“|U÷›«Ê~»]üè5Ÿ6ä-¼ ýúCMüý‘Xõ%½±¦•&I­ÖɪæT×ËÛž¡}é!]¡î3¡íï¢$ýiñMBÈ•ö½¨ÿê ¬K·ìOžÁ•)ÓdÜ1yRjÖ…Ê*Ùõ=s0¶5¼Çèu®Î•1zµ×WÚ´îL²nôÁKî!JШCæûù¾ù™{PT‰WßAåU';@,o·Þzrþk/ƒ+ È´;oż[oÅ­ÚoÜ‘ªÅ±–ˆ0äì 2ÛœlK»'ïŸí7 ¶š<ìÊÎÆ®³Sàî®Q™*D?5“`$âââô_´'ò÷æhƒ{uÿøwJ¿ëªWh"$é“»-í0Ï„nIî—>ƒƒ€ãž8àÅ=ð)ŽXÓ1~ü¤¶µi39e>ý"}7Èx-2év÷Èxz©> “6öÎ¥08…­Ë•ucÖciª(–VvI‡Ù ´úÆáÑwöWÈÖÇ/`{º ò$S€/Ù‡UO(ž¶nz`L—*¦5Y²µw?:¯®LÃ8_™¦û‹ ,Y»C …²X,]‰ŠZ‡«ŠMëðaäÜ+Ý•¬25¹LÃfwVÛßnŸ#%AÆ%˜.v9§ÅŠÇ¢ÝôÔ&D>¿±bd=~jp*åן]÷$>[.BM=jd|«ºÖepÃCe"Ä>¸2®NâùÈÙø$ÆÖ㘘bÛß©Ôr¼ûdÆš<Ô^;¾Ø¸£laTÓÏ_ëŸlÖËãô™|%R Œã³%qKœfù§óéºÑå}WUè {¶¾‹1iozÈšTXº•å¼W€Ê½Û±½"—û5¢¬¨\fuÆQ„L1i †÷Ô¼[P‡œ·6£""­–jTŸÔSÒFR¯aÎ GàdL ÈEA]%Þú󻈿 s9lÙž[bs]LâëúRî‘Ódì¥|÷úüca5ïKFKÙ{°¬“kFûŸfkÒÒ¾¤äÓÝWûŒÔ߯|_>w;ŒÇ Q½ìy-^KF*æI^½¸±×/ p ¬O,ħÍÿ‰±q¾¨{qµö‚dxâù*Ò!ÿ>àóÇò1Vƪñ–AÄ«Ü )˸a9'M3Fžõǻ̭2'¯å "BðrÆ——W:Âeo} ÙŽ-Y‘„7åÛNDt´È(Ò¡´¼{e¶¾™ƒÀˆŒ¦¡©R7¶E¶ÏècO«r^{i}K–"ö¼ö’Ó6°ýÍ\éÂ!/b!"®²²¶ÃþΪŽ&“©³·ûo7Ê«“Œdõi>Nß{n¾7;eÖßÞÿ4î¹.Æ6æãåvJˆ™1GnJßõ\%ÿ¸7i86æìįVžÁC?ŸŽÓùà•åNGœW>#®¸R:ì{å·xºêÌ>ùï¼ éá„Ð[&ɹÚÕùh#ŸÁ¿ûŽþâ§Hð?ƒ/vêÂQ„˜©cÖÓùéH±2[Ιg£cWç•A{.@u?iÙ"Sd/ŽéõÚïñù­NÑ`ò…Fœå—kQ ÷Ï2TÁáz»‡Ñ0Vg<Š}ÆZ­qVîeÇÖ™Å3D?æšÅm*WnÀÉÙ©˜®†íp*~—t~bI„ò[tB¤cW†÷kdzﯵA†Çcé†ùÈZ!bˆŒû²iÕlêÌ:uÒ£T£Êw?v²Öfi!2Å|2s¶ÚÍŸ/~”>Øt@O{ÅÝNi·ß,í‘Ç¥.Á’²ðªÒHöoÂ’;»”«Ö¤K) ¸så4É{¿¬ÀúK°ÞžˆmÙ†T$Ûf¾ñõµu­};ÖüZ¤ plúàE¬˜¬WIX2±ânç‰-‘Ô»mÓ›Û¶¹ s% M é*’jbvú$쬻/:NAºýêã©·—œƒöm½e{b»|çüÔÊËgõ6[Ò׋ÐûÜ<ŒFФ×AÉ}Ï£AVE¤/ÂȱûpJÆÐ¨Ùò &Kã窌gp@fvh”0*ÌÄ$fæÀúùå¸LÒð›Œæ÷>ŠSæ” £uíx ‘RAé°Þù…Õ3lb_¯ÅÁû6HœØâHÃqå1íö Z ný×pÕ0§RzÊ}wÜ,œÕŠwö¡¶¼DM¤¹Àˆk1×f:¦¶ŠÉ5Óø¨Y·µ2>„æf.Bøs`¼zqpq>ë:ý‹…”:š­.®quÝ#ç!dåǨ–—ã‹ôgšAÆe¶}¾ÿüy™%ãvéã¢Ì¶W^íš’/Ñ3> û>€ïŸúGsö²ß˵7/T+P@Ê LÚ(ãH,Û"ù¼¯20 oü§5ú³ý†nÀ¶möD§zh›u"ÒØ•Wû;ú©)ã»sµrÎÛ¯‹îÂôÕ¿N,vúêTÝÞuê:¤•×#Ó#€âò/pHÆ|I¸æ7xêôp<ùJ¶Œñ"ʘròåü«Ó§åÖ<ä^ÔÉ ~ÄpÝóšßþ7,kðæ¾Ýظv·æ15N^hŠ16TIC2®Ãyå„ß¼¶Ã_‹ìo¢`§–”ÌÞr?ûy´lèšî«ÿ{¥á˪°fãNì|e£c@áˆÙ˰æV“¨÷óÓ9ÅÞ׏àêï ßëãÿøîÔD÷vm˳ÙÕóÛk¢šOoÓeÀÖðÇ“Pñ”|ȱÝ.[ùˆŒu÷¼c쬞ï!r3[—!Ÿ0äÙ/]Ÿí ¹Ç¿õŸú=32Y,ü¤k” Œn9–yÊ&ε3ª°Ò;Rn#e 0ç'ÓøÛŸDõî{бße¨ø2£ßïÁ÷O<–××âØï4Ï ζ†z^/vîDAåI™‰©ÄÉbÐ c…݈9 Áz=½Â±`q:²ÞýHfNª•q€ÛﮞÒõI V½÷oø<@t·EQ¤+{—&LƒN;6A]«â§F @&B¨.5#N^o€ÃSqKR³XÑ”£¢$×öò”r¦áºp9ÒòÌt>ŽRöóמh^Á3±0ÅŠw²¥®%òÁË($vnTVA.Ò0øùi¡\µ_œó ž okšdb‡Uº!EÀC¾4§áUï¾zÉÆýpšˆÏÀÕp-ía$Ô²WñLº˜¾ˆ;^’…§W?%Ö&zCKó”¿´×bق鶦œî{X¬d–Ú-]l§Ü¶B¾fbýfd€ãM;7Øòo£šÕX«ünn»M„“ª¬À²W?@º6Ú:.áŸî^’ OÃ3k–aº#œ%Y¯`õÓ;:˜–«ÂS—bͲùï«¶”;‚í>‚MûuKšöò©¼ÖI^z9ô°ò/cæ¤=ô,¼zÐ566bäÈ‘6l<<<››«-Õºú)g_¶‰•ÖÕW_³gÏÊHóMPÛ£G&Ä¡H@¾lÕ«nb*ì'³3ttÍ8}J+tÚ§fY€ÑOöÊï¬Ü]|F»}ðêaäåw¤ëé9ÛSk_k–²) /)—SvíéZ£˜+«¦ŒAº>Ù4­&…Ÿ ¤>iÉ1ò‰Tj.ÝKšˆ7¥víBÜ燾„97,(Ìúõ!‰HŽ ´g‹Ã¾!³AµâÚ»#Î6‹Ã¹¥ë†¡{<×Ï¥¼rÍÈuáúš9—tz«fak”kS .®_oz¾çr ©ôëHÊ+ßP¯ÛöÜmõ‘kØO®áÎ.//OÆ7Èëì=$¶“’’df“ø ^—¯¿þ'N¼àéö-A« Ö«›$áJåéC*2ذ–„Lã9B×-]D:¿|¬2’°VºÓî˜=Îp©Oçâ\¨óóBž {üÙõ|m«öç·ºö+·ý W6#²äe‡`«îCò>,ÎØÍýCíë9Ÿö4dŒ».í ÿÂ8u¯³Ê=ÓqÔîùrçsq_sÎqP¶5d®z™  ^~"NxûÈ xΕr^onD­¥jìvƒôM ô1¢þ`¶åœÄ ÒÁJÉ9Ú…[oAc£<ƒ”•Ž”µëS¦o99ÚN? çœZ*>Ák»J0jr:îLuÞÅõ!@ _Åš Á§¡á8ÔèÁ_1U6,Ý8kŽ‹„j0èa}e`‡ÔA¬Ñã[%]=¼|{ÓánÓµg§Ò—’:”eŒ¯ow1¬hP ûЬ"å6Ȳ}0b{bö¥tˆRÅí@<Ëàœ*m¿JÃ…K¹QR¬ái@îL ÛKÈ­“nQ!±2£‡IL>Š‘[.ÂŽ×.¼q2pj_œ[kðÎKïj&Î!±IbñäºÂ\ä*k Ï,\2§£™ô¹%ÎÐC„À…z!v'òå¼s½Üçe½sɆæö=?/ô¹0X¿}†Ëk¢lÖuCóŒa­.eÍU…È)«“î¸2 T«7fÝsÏ÷!xBt+^ºK]ua¤¥ÑÄœ>„³1ˆà¢ÆŸé³Sé÷)‚A´tÇô&±8Ï*åToTcå:ë8N¡¸J$@nI !ý&ÔlËDyu öÈOwÞˆu«&Ô¨í¾„±Eìã"7¥ÏÀ;»rQ]’#?[4Cf-uú˜ ƒ m‰‰‰0™Løøã»Ãf°PíÕÝe0$;X_ìržïùÉsÁÕ‘ ìv–9W¡éGƒ@k}JKôU!ÒuŒÂä`;‚}+¯Û[Öô­®C}›±\ºa©îNáxîƒ×Û›vâ:úºZÖ¸ÙaqH -b²lQ]¥Z=èÒ´¹/aºI¾[ïæÆzÔ×7¢ÕÓÁÒœŽ\0›Í2pªªª´¥Õ6X¥‹ nᥬp•0£¦dVK%:õ·¬–ýÍåb¤ßÓùy±Îÿ‹q¤™ œ/é–Uß$_ö½áãÜý|“c<·$àö–5?„šaL$¦Lë•1×`4…š‚’qI€Hàœ xÉÔ›òëÉõ%LOñ]í3úH_rùÑ‘@O”Ø¡~ÊšŽÜÏOw;", ¸/øÈ¸7tC›ÀkÆ%?„ ÉCû²v$@$@$@$@$@$@$@C‹@çÙf‡VíX       d(Ö ²Æâ’ mk†öñeíH€H€H€H€H€H€H€Š5ƒì€±¸$@$@$@$@$@$@$@C›Åš¡}|Y;       AF€bÍ ;`,. ÀÐ&@±fh_ÖŽH€H€H€H€H€H€H` X3È‹K$@$@$@$@$@$@$0´ P¬ÚÇ—µ#      d(Ö ²Æâ’ m^ûÛ߆v Y»!G`îܹC®N¬ ¸___w+Ës Œ;ö"æÆ¬H€H€: eMg"Ü&   @PP)\Âüüü.áÚ³ê$@$0ð(Ö ü1` H€H€H€H€H€H€H€HÀA€bWH€H€H€H€H€H€H€H`à P¬øcÀ €ƒÅ ® ÀÀ X3ðÇ€%        Š5\!       '@±fàK@$@$@$@$@$@$@$@k(¸B$@$@$@$@$@$@$@O€bÍÀ–€H€H€H€H€H€H€H€¼k\’ªÎTãd«GÏ“åIœi;sAê9Üc8FyŽBÐð±²ôGèð ’.!      KÅš!zXD Ù:Ohú£ŠJô©m©Õ~*}%ÜL‘nèH€H€H€H€H€H€H€Ο»A?;·Yò} >:µ§ß„WWV;*O•7 Àù XsþìÜ2fqS1Š›N0Qyj>ì–lX(       .J7¨ˆ©)Hˆ †ŸAcbm´ ¦¬ÙåíŒ'cÖtŒhm÷s¬y¢ÙÚ ŒÝìW=æ£ÈÝ“‹:G¼KkEu}*ùþŸÝVºmÂbl¼ñÇå ´4 æh^{kök1fã©{ïBlÀhZ¬²o?^|ë?mû€G~úßH ‡ÁËŠ†“_cÇ{¿ÃÖ£®³:Ðxc½Ù%Ê5ú’ @ú]¬™5ÿD;ÂÇÇþ¡0Eě۳õ}#‚Ú!\‡æzÀè×Á«ëF(šËr±ÇIêfèú¨1jºuA¿ÄÿÜqÆØxïøÙxúÁÑXúâüzÙ¿!ö2}¯—áãSñÔ/<ð/¯<ƒGî}éÁ"ðh΀1S°äÞ—a}ö~¼cóí¼PeùñÈ;{s{ ÈØBÅEe°ŠÔéÚ‰êŽÉ1¡è÷‚‹´Ô›‘WXƒàèD˜û¯õæB[|Ù- Å£ \tý÷f$U™‘~—M¨iFUqröˆžš€Ø¨`cpǬjlÝS ´¶¡Eâx5Va_®žÞb)ãäZ›¬òBiPö3¶ ~¦DÄ…ú¡±ª¹æzèÁ[Qw‰ 5Ugªz£æ§7¦Ø„šìûl3BoCÚø`X„—[G8„š‚/_Ä?GÍÅÃá‚Õ©Ç1Å&Ôÿö¼VŒ__= pÜvÛ­xg‡k¹Fa£Ê:¼Îéørµ ˆÅÙÞÜ‚^2(€!ü~ÄùØ»^B_ÈÝV¹† Š*â'bMo‚ìùç\Uœ‹‚ÊVøEOzžÉ“H€H€H€H€HàR#ÐbM@bC}„g ÌûÞD¦è4š5%GL_*¬óqsœt• FJ]—Z¬(*µ¶Åq± H@œø[+PZt‰*4N\,-§­®«Q£FkžÖšl<™­–p¤þû\¨ŽiãÆ†ëöáw™j_ ’ÿ}µ„&FNƒnSSƒ¿¾õh*ËBfA5‚¦¦áÆHo4j==á?BFÆSf=mËþ¢¥§ï© ¾ý• Ó%     @ ßÄšˆÉázWƒÆÊv¡Æ©À•{óPlˆ„¡µÁÉW­º³¦SÙl·»é®kG×8CÙç˜tuéÉeíß k/ÊJž—`±ú³5¡Fä.´ ³%Ô,ÖKš«Áñ†6„ûzÀËk„îÕfÅiÛÞçD¬ƒ3h³ù¸^ôV&×±è{á x!0X¬¨l®Ù¢oïQAð ôÝ–¥¥¾…­ˆ Cua.Še=þÆëjlAÕÁ”UW¡QzLùø…"2>¡~N·±ˆËÏ/EM}³ì÷Gˆ)QšXëÈæÂ<¬’îŒð)>1b×ÁYâp~Êjµ.Q±‰ˆ v¾¾/D9:ä(õ*F]“XÛ„Åök¬Ž¹r‹H€H€H€H€H€z&àô¶ÕsÀsÞkSS,Uæö¨aјàíØ¶•j­õ«µÃË?wÝe³ô°…ôôlEEN&ö”Ö9âr¥#Õí¨'—³ÿu䨓Á+Ëç"ÜväË ÿ„²ÐG4+¿Ùæ¾FMóÀW½Ô«Îiâ<ô|µZyRÉ6ŽÑo”—K×[™\F¢g¿°Ë¡­ö5=KkU¡tU¬D^ž'¬"`(Ú:æmo¢¨Ã¥WŽ’¢<$Í¿ ñ2ÆL£ùlÎtžLí/@Áä›° Y®å–*d¼™*§ìÊËKPd߯gJ¹Æ+mëjQ.vÅ3nÁ¼%45cß-‡-m{1~¸ÙåJ,@ZìdÛ^.H€H€H€H€H€H`à ô›Xc ´H+Ÿâm.BÕék:,°––KG¨v§ îìÊ5±8¶Ûz±rQ“nþÖˆE„îðÅgÏàñì<<ú‹G4/5á–î’9Ê&ÎxØN™!Ên•Ún¥aájÙ—2¹ŠG¿"`놤„šÀˆ©¸*Þ„±'rñ%ÔŒŠÅü×!Ы‡³¶‰pzG›D¬iD¦M¨‰Hº 7ƇC‰>Û2rPWô'Þkæ.M¨ ˆMAzR ZëŠñþ»{e& ã#ÒQ]&ÏJÇŒ¨@X‚w÷” :7£ïB¤%WŒ~@9ôl<áchEñη°·²IÉÌ_4Ž2p…H€H€H€H€H€ž@¿‰5æÚÄøûKw%‡¹Ê÷ìÃA¯8éÑŠæB£Be]}3wr–Ãxië'®ö…€¿§?j{ê 5óÙv¡¦¡ëÿûß´ñgTÚûÖH·&™–[fyºC¶·NJqXÞœ´Tbä¨12e· (<ÈùÜIÈi±wŒr]BU&ºÁGÀ7" ó瘴‚7×X+³ƒ%ÎЄšÆz šì¬XÏ5›K öqð JÂj”ó Ç¿¤Ôã‹2+|ZË‘[-¶,ž!HJ4‰ÅŽtƒòDòäBì*: ³¹2lŽæFÅÞˆdj” ŒºiÒíjWÉI”U4ÁäôÃÊ4j'›ðÉÖÍhj."üÜsçuNw(-kþ‘ À€è7±¦ÕªwŸñ3MF˜ØÍTjU-Gv¦}0àɸKĺ C`¬W`bÍD<{u|{F¾qXñïÿ+4+²>ú ±á2ð,Yþî–©»5»š¶ üù^ÄmËþSeZï©©ïáýkeŸvÖX±ïS5þM÷N•‰nð qÚƒhË>ìÝö2m]£ì;UOÇÖ]¹ ›Ün£öIÆÍ1²ÒxPFE×ZŒÍoªµ®Ú,RÍØË' £¸çï¯ ½–ê£0ÆüÀrH®‡m9kBZo°ˆ]Ð7;1[d.H€H€H€H€H€Hà"Ö_y¨„kµ¹¸qã-×vÉfÖ‰šUMç-ú«]gon÷BÀß«ã‹nÇàÁðõòèèå¼uòOxæ‹:y»PƒãÈÚy¿Œsó5V¾÷ŽhÚ[»P£*~æs"]×{.S×ðôq„µOT“ÿÞÍ.’Ax½=5 iéó¥+“.æhc¿Ø€ij’A\sm¬@³m ê6ÿ¤ÌJÁµ×^ÛþKJ¬DÝGEí<”§A7¹ññ÷Æ.‡SÙb¯ER„¤-Ò®ÅN{¸J$@$@$@$@$@îA ß,kdxPlÏ5cq² Æà8eîiT£Îtvý¢RoDÉÖ]¨n¨DnŽn邺òj4Ö¡Úš„ëîHGã¶Lh$ŒLý­œ§t{JK¿V³’ žy fY3°§ä(ææØ2ôFlÊMˆk{*)J$αkå‚Y nÒfj ¼åðÒn72FÌÁµaoȬPGñчfÜ=Çd+$@$@$@$@$@$0°<þô§?ÙF•Ø‚0÷ G ¸©xÀ%ÔL2F]¸Ê¸Hiîܹ9r$† äææjKµ®~ÊÙ—mmm¸úê«qöìY455Am=ÚEªôê‰@sc#¬­­0xûÁ¨$ÞæFÔ‹…ŠŸŸšÏMw-âg-¤UD?WÝŠšQ¯"©ýNñìñõe3%L«§Áe¦sä ¸õÚE7ÄÄyÇ!ÔŠý§÷w3†Í…¯°£Fu}¢EÍ…gë)}|:Nqmô_'=ÆKüz¾¡E¤é©KåŒðé!Ì…)G—LéA$@$@$@$@$@nE çw+·** s.”hòã‘?FÕ™*XZ,8ÖR K«-mFt>—DÂzyxiÂŒšž[ÍúÄÁ„àp•H€H€H€H€H€H€~Š5?Þ`ˆªD )ƒáH±Œ$@$@$@$@$@$@$ F$@$@$@$@$@$@$@$@îC€bû –„H€H€H€H€H€H€H€@±†' ¸Š5nt0X       ¸HbÍdÌ¿ëÜ‘6•ÄI€H€H€H€H€H€H€H€z pQÄšˆYqô1¢¥ÑÒCQ¸‹H€H€H€H€H€H€H€Hà"ˆ5ˆ÷Òí-'q        ô¿XˆP#Ð\eFiá.        ôÿlPI“C„s *òsÉ›H€H€H€H€H€H€H€H ýlY3‘bVÓX=•½”„»I€H€H€H€H€H€H€H€úײ&,%>¹Ö\HÔ$@$@$@$@$@$@$@$@} Ж5H0JêQº—f5}8 B$@$@$@$@$@$@$@ðê7 úÀÂ5fõ[&L˜†>¯¿þzèW’5$     ~$0qâÄ~LýÂ'ÝobMRB˜”V ,œsáKÍIà"0Øn*—СaUI€H€H€H€H€H _ôS7¨É0«…bOy¿”›‰’ À$Ð/bMصÑð\XxHž3¬ @?è—nP•%yøÜêº\šÕôã±cÒ$@$@$@$@$@$@$@C@¿ˆ5¨+GüèH€H€H€H€H€H€H€H€Î@¿tƒ:·"04 €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ P¬qƒƒÀ" €Å; .I€H€H€H€H€H€H€HÀ xýô§?uƒb°$Ðw}Ì$@$@$@$@$@$@$0Èвf0—H€H€H€H€H€H€H`h X3´/kG$@$@$@$@$@$@$0ÈP¬dŒÅ%      Ú(Ö íãËÚ‘ 2kÙcqI€H€H€H€H€H€H€†6Š5Cûø²v$@$@$@$@$@$@$@ƒŒ€× +/‹K—$ªÂó&ó$‘˜ˆÀÎqkk‘W^ÞÁ×XþÆÞŒ††&4;]0Ë=c“’šœ ½I£XßgÚoéyl‚¶ð²êwµô»]ú3>¹ãƒó”"ؾۅæ&{Ëq¸ÚÛá³kR’“aOò™ö0p^¢(@ P€ ¢D€Áš(yÐæÀèy5+Rì2^=Xnä-uXX°¡“¦Œ’6,-}yiIÆ ùvcgÞTÿ"Ç+^y æŠ,ÛÕõ&^dâ¹O#+©s. •+çIðwÍ_Q ºe˜Ý³í÷/_ß[ÝZ”Ôø[·Ž÷k"×~,ê¶Ê}×ìòß·ËŽ-¯l.ÂøØpS]Jó(@ P€ (0€¸Àð~¸ÚÀè>³FÆüm3vÖÆn²¥#1(«¦¥jft¨QõX[0++›È^ðâ¿«î¾5l FU0¡ g¬B³ÛTß…Š‚º Ô¨‚e«–¡,|Gk§?ú½æŽ!Õp‚¾q$Û¯Û¼¨ç@ꈣ ónXÖÉM]àF P€ (@ D›ƒ5ÑöÄ9Þ!Ðmf»•Ï— .(9ÖeóOkòþwfKH}Ôf“,…Ùÿ »Õ®óŸ 8ëP×u¹˜›:%ƒcKõŠÀÂÂ2=jë)¢ òŸ.õjŒŠ[.ÖnÅz&ˆwWš—^/m%ôo[æýTìØ4¤ÉÌ'#«Ç…à[ZÕŒ(ý¾Þœâ @Ñnò‹ŸÆ«SòŠŒ+jZW¿õ[-|y³jbýÍG¼ýÀ´=GÉblIX‰œÌdYTY`-i¨xï=¸Ô"ÏÚff F—à(@ P€ ¢U€Ášøä+ê+ðfí[çtd*(´¤b)nK¿yóÎé½y³nLN,Z»;Væjü»ÛšýU£¬ìU$»ìðÇÔIY¿¥¹,Þ¢ÖŸirzäMEêb`KËKCP¸Ãwah2í¬étÕrß…yY†‚öL i˜/ Z§Kõg¿aÍGÞU¡=tûjÇô,/êjôh•äДüònh9HäÌɹ×gg"-Ùî Þ™p— (@ P€ˆNkØs³öMTÔo=o£RA¢a2EjZê?·> ü§áé·Š‘£ÞÑØ<íN®zË6¨ô߿ܵ nN“LÉsñ´§}ßueXˆË„^ ::îRÝ®’TÒ,¯ô–ÃNÁš ¾Ý!]Îè'ºÉ(éÇ~Ûò³»™"ݾ„Ên^³u·Þ./Ï’ çž² ÚG!¤å£xq.üYIÝQñ<(@ P€ (0 ÎI°¦ãø1|Xû!>ûâœ2#G'cÂÕ×`LÂð Ühü¨ 'î/¹SfM”W·vs]5s v9®ñ¯CÔt”쪩O=j¼—ÿ ž½q2FÇË_ùßµãë¿Ôáå²uð½8h VÏûg\aów¹V‹Êׯ o{ÙI£aìAû‰cضƒ=+@IDATã”ÿ%<ìï«+¯à”¨ð<ýr61ÑŠX‹)4ð ‘’¤yk"‹Õܺ*°©nlõkÂÜÙjU+Ût¿9NX¼} ÔtßFHD©‡b§séLû`–ß~¶~o?6EÞ®õÁþª=’Õ´§ët/éW]Ù2Lm6aÿs9¡Ï¶}f P€ (@ P`àDl7Úëô­úòDÞãÎòð\Øsòa“`‘à’`у-Eÿþæþ?£é®‡Á9<Ý•éæ|¬ñÛñ]wžRm…NEÒ¯ 6\¦O¤ûéöý,ØÒ²1O}¯ÛÕ‚æ¦&ÔìxÞ6ž–®)•×wOÁx Æq£(@ P€ ¢S ¢ÁšãÕ/êšdÜ»ü^\;:^”;ðUã‡x±d Ž”?­?z³TtEj†ªgy/–ÏNƩɔ Ú† —º·\­òg¤ìpœlÚ†Õ/âË1;y|Ň >J5µŽÚ¾íÆëõ@M;ªþ;Ú“f"ç²Kx ¼Ür‰?Pó‡^ÀÆß„9cFÃd½+²Ûp•¨iûüm¼Ür)e¤Ãl™3oÁömáÃ5j Õ§t[zÐSäî9°Ø17hí—º=Mòv¡XFÚC~õí ÖHÁNÛñê͘»n'.WsqçãiYó&\h¥Sµn-ÖdÊÃ5zì¡lÓ»(,Í번®«nv…i%ÒýŽtûnG%þAÞ°¥½Ûü¸Å;ßA®¼Ë›„y#VŠor²VbÆ2cônnÇøàEøð(@ P€ (0pEnh_aË«2ÁÆt)>¹XÔ¨» Ǩ”ë±dùLíÖ•»> é°¸8 ¸ÄËÚ !Ÿøáˆ1ÎIDf”u˜V/.ΊáñFÙø°“¨Bn0@>ûëg=ŽìïãGh×=_¿‹UïnǺ²Z¨•HÔ6ê£};íÕxdÏv¼üæËøÂwc~œ_^Ä×x£ì9ì}w9ªeÁYµ]j½B/þ«·>…¯Å³g/ÐyíýIËB·³r­;Ë °¹®%pBöÜ-ÕX¶pÚí5àu‡ÿrýŒ,‰¸57¨fÝZ<´%4…FÝ÷¡ù‚ íFºß‘nÿÛvß`œò%‹>o*«œo×øï¢ïȆ”¤Ðl¤.x‚ (@ P€Ðˬé8V#B—1©*¡¦Ó6dô$,/ºC:_étþÐÈ»1¾Ã—Šž³·~Òã`«jËáùa >ýä9)7+î¢/ëwošŒÇmüÉø5ÚÚ½cÂàÁ—øÚõz`üÙþ_kG¶u¤d9©÷1w¿õÖ§îkòJ¿ 47Ã% Zä?9 ‹±l¯dyèÛ†‚(ËœŽ»rÒà‘Ì– »jü¯ØVEŠVL9«¬ßm,È((öî[SR€ôŠLÎ͆¬ŠÜå¾¾zÆ¿‘îwdÛ·ØÓB2‹$Kß“†ü»raÇg¨ªØ‚G`Ê“ã‘86øM P€ (@ D@Ä‚5¾ùJ@z¦¾&2=Õã':üºq£F!N662ø²r^üæZ)Ü<9¹÷ÞµkÑ>=}«iG=m5µ¯ÈK˜e»/-¾ £õ'ÿéáR|š´*·Æl1Z8†¯Ý⣞Èw¾“&_@G|yB…mü«ßø®‡ù··>…©ÂSÝœîŠ1!åïA]K‘6õÆ””‹ë0cQ™ÿNΚ](‘Oç-séë˜ôšë6;îåØbËÅ[Å5¸Õ?ÕG*8j°a­ö«ì¥¶Ä"Üo±cÍæ"Üp·ö²nßXu(+1²‹B3kÞZФ^EX€ (@ P€È›õÕ‘#š[ #«ÕÛ†eË–ù?<òyx§¬bãÛ|yß¡V ®­­õŽ9€¾0J äÇqæcóö’å¢ZΜö *gšv|xp~¾{¯ÿ¥Ìÿ_ã™øq¼œ1éQyC”>™YI²ÖM¶¾ô©ͰˆÄ½µÉëbÝÑX6='pQ¦Þìª ó’²cÿ[ë1=Í(¼gÍDñ+;ðœ¬s¼™ÍA™TþÀ^p‰Ð}«5´=w%Þy¥™ÝÜvúŠR”®êwhuDºß‘l?vüܞͅ.-§¯¼óríÊÊ# P€ (@ P LmmmAõ߈;·â%•˜Tô î𿞩ÇšäOƒ1lÈIxé%Ô"Ï<5 ÃO5âW…%øö§ExjNJ¯é8&í?Q‰ŸJûsüí÷ZmÀXµó1|ÒÓT¨ëžDeöU¾ñ·Œ§Ÿ/Â^]cÊŒñÐ*·¦ ¿{b.ÊÇ.ÁÎYÙZHàSÇŸ1Úv•¶ÿUSñÈû™øíâø±Äp<_¿›7«iUá·+äõÝ+nþuø‹gq¶££q²¶Ñ Aƒ`2™ðÁhßj_}Ôf|{½^dddàûï¿ÇÉ“'¡ŽGŒð­ßs]0UÝ®ãhu¶ÃívÃb±ÀlMDRl䃮ã-p¶»ä¾2=Ë kb’¼Š¼ï¬‘îw$Ûw»]P¯D÷HtÔí–Éiò¶,«5á´Æßw)–¤(@ P€ .FˆMƒÒÞÞ$"Êk0ûW7ê ÿǘÔkt§¨QÉ2Qúö¦þþ±\)‘îƒ5cðdÆøÀ-c®ÄC¾ƒ‡´3Týñm´K°&F¦6ݳxî”Wwky5Þ/ðÚ^ÀÌžÇÕòZ﫳w`ç?Ê5íWãAõ{ÝjTÓªOÜ.lKlìò9×[l‚gÎâ¶‘îw$ÛWÁ©¤¤ØsMÎûQ€ (@ P€‘@ĦA ‰™j¶Ì—娸®ñn¡€LãÖ—P£/‡8 ɸaô&Ø£¯û—ÿàòŠ^Š˜Á¡ëb„>QеþÙ÷v(#P#Y6U»ï“unŽááÛñ•ö¬µPñÚ£!­t9è¹O]Šó(@ P€ (@ P€"±Ì•23uñB¼÷Ès8²e5®„™7^¸SNÔ¾SŽš/õHͰÀsPÙ_V®Çæ“’}´À°Ú»f*f]«¿b:P…{º@º-6ëep8?cRƒŸ¯›æ|Щ£àæ?AæXa󠿍±ø©L)ú¬w­+EúØ)aiǧ *„Óó¦ú¢úÄ (@ P€ (@Óˆ`°F:ŸŠ{æQ¼±q#þÔt¯ÊÇØÒç,Ĥ¡BÉ6ãŒñý jÊgч©×Îé2kêã'Z½K*–úOç˜iºÃÔ5V¹é½eÕn (@ P€ (@ œ¾@ÄîÒYö¸| «×Ä'ÄëkØt)Åg)ðf훨¨ßz–­œ]õŸeý ÓRÿéìé¡6(@ P€ (@ P࢈lfM0ÏðáH·È Ü–~›vƒó°‰t &²zl (@ P€ (pþÎ]°æü5jz 6×^~-6Vmìf ›þ§PkÔ¨©Ov«½ÿg‹ (@ P€ (@(`°f€>l4y"ï Ô:jñÙ_?ÃÇ­Ÿ ÙÙŒ“ž“ý2âaæaZ`F½ž[½õ‰‹ ÷ +¡(@ P€ (@ DòmPÔ½T…” áI° (@ P€ (@¾ ê[1–¢(@ P€ (@ P€8 Öœ eÞƒ (@ P€ (@ ôQ€Áš>B±(@ P€ (@ P€8 Öœ eÞƒ (@ P€ (@ ôQàkNà/®ÇÖ†ã}ì‹Q€ (@ P€ (@è8'ÁšŽÆ?¢¼ö.‰Ê5(@ P€ (@ P€è£À9 Ö|TY þ)~2šÁš>>£(@ P€ (@ P J"¬éhÀ¶#À¸;nC5Qú+ã°)@ P€ (@ P€è³@ă5_¬Ä7¦K‘{mBŸ;Å‚ (@ P€ (@ P Z"¬9ŽwÞhÒf!eH´sÜ (@ P€ (@ P ï Öœ:V‡éËœ©ãúÞ#–¤(@ P€ (@ P€Q,Ñ`ÍG»¶Ê“6ši5QüãÐ)@ P€ (@ P€8 Èk:Q. 'Ͼñ§Ñ!¥(@ P€ (@ P€Ñ,±`ÍWµ»´……§_;*š}9v P€ (@ P€ (pZ ÖœÀµ……oF ß×}Z„…)@ P€ (@ P€ˆnˆkN}Qƒß³¸°ptÿº8z P€ (@ P€ N[`ði×èC…!£'¡¸8ñ L«é‹P€ (@ P€ (@¿@D‚5Àp$0PãGæ")àj>ŒÃN,anb¶&"ÙžöZ˜âgwÊÕŒºÃ-p[¬Ÿ–‚سk­k»p¸î0<ªÅ;ÒR’‚Úö]s¹-°Ÿˆ¤XSÐ5îR€ (@ P€8? ÖœŸÁð®ˆF–ê•XTâè~èÖ|¼µ£vKdî–j,*ÑúQôú~ÌM¹@Â5îf¬,XCèé";I·ºf+zsSºwä P€ (@ P€çH "kÖœ£¾ó6 €X†2EFÚl°é?޳ ·®­ñFlÇ.·'bwë{Ã^3’¼â­Ú —qt-Á8Ço P€ (@ P€çY€™5çùðöè?4<·å¤4߯ÂîFJ"9Ü,Š,}j’ŽºÔ6Ê”%™ •”’†Ì4»ª”«¹MÎvX“ãiBMu£Ôƒ=m"²‚§¹£®ªNìir_wØ¡¸ezTMõa´H¹¡Ö$LÌÊ„=ÖØQךœ0[mŸTW½‹fØ‘—›¦õÇÕ\‡êºf8ÝnX“R•™½*z«¶3êdÝZlªÎÄâ¬@«sYWK£Lé’û¶;åR,¬öd¤¥ÊÔ.=ÕÒx­í@b²§šªñ^£–Ø$¤e_/ckG]e•¸x¤ÏvŒ—>'éõ´ûˆÛáº:4·:Å5)i™H³_ YH!xL P€ (@ œkÎ ;oJþðÂì¸h­ÇÉú,V ¦M õÝÐÝ‚usg Ì˜ä; ¤áÒ¹:pcçü;P¢Õ1.¾sŠw`M®9\u(È.@>›(P"t¯¥jfÔíA³{–Ö/£v ®q&üwæâ5˜ïÚ€»e gYvf¯‡UÆá߆ÚQZZªM»Š‘©]‰Vܱ‰þl³Å_RÛ±ãÿ6M•wh™FÊd¥Ê’ɳ£)ën_ö‘Ôs7Ö@AþŠùÈoþùصÅøÚRׂ¼””Ððˆ (@ P€ˆJk¢ò±sÐR@‚"9ss|™28©.Û¥O·)ÃÎ?ÏÇÜdø³Hœe‹pƒ900 k¬Y×o[†êù'Ç}‘ #en^ªQ‰iêÞ»üS€,&½”‘ºuª¿œ±S!Á‰,}v–ÌÃB~v ;Åœ ¥ª¤,ŽÜ¥£u²Op '´®Ñ~¸oË‚ñyK1}í é)°JÞºYc>ŽÍ›·bWMçk¡%Õ‘Ýã?i˜$$êçdñbãœVH6ÆV¶èVàF½ÔÖ¼¹nyUP1ßþK P€ (@ Dƒ5Q÷È9à*à5%cþâŰë,º+S§.Ò‚'õͬ1ÖgÉ)ÂÓ9VȺ½2H>ê[rUe_‹‘ÈQp B] ÞŒu^š[\RÐ^ðÈb¼ÎàB^£”¬QSü´r|7³È·Ú‹µKfɽ‚-WáÕ£rJëŽ|«õ^Ö<}½ï„zÛ”Öa¹¥5( Ò©®Þbø/­¾‹K ±«`C—2nG%¦Þ½L;ïµå`™dM`SÁªÐ±5ŽÇ}üÎYZŒ‰éh"ú°L±ö>Öf1 P€ (@ P`  pÍšþ„9¾¨0I(Æ%ëÀ›)a< ôdÇΣpI°Ã˜ò“fMAvv.rssa÷4`Ó¦MØÚ ³œŒÊê»»@„9Éz\eË¢ ’£îéFÕæNÁ£µ4ØÇ_¯Ý+7+u[·bSÉVxbcƒï’…k$娒1^ïgV¢»¤Ÿ›¤£± ¾‘Ñ@PèÆ8ÕãwlZ>Ч„)âm×OÚ°is1fåf!9Æ>P¦z§L,œdûxd‹}®´ï®Û¥ùöÄ2«¦G@^¤(@ P€ @ô0³&zž5G¡A 3¼*^¢ReÔZ3&»,*œƒ=Ëö NÖkIßb…M²Fz:LÖb»ìf Ê’) ùÒN™´c’ÉD·fÕÀ*í8CÒj$û%g>¦/«”uX4ãZŒ´ÙÐæ0òvò‘,ï³äÞßׂë%óDÀ‘¾oÀŒô °Ù¬p¿IR¼9¸J/û]ïcAî/K±io?“(´  æ-B~V,ÊÊö„^:Ã#‹-[ -Û l(˜ j±c§Ã’)l “ϰeV£(@ P€ š3kÚåx¢O`ˆ/CE½º;t³ 93M;å­«Ò2`’r×àõ5ú››Lz Fù+^Aa– mAÉ+æ= Aµ%å®@iQޝ°´£5™Óõr|3«`²aåþ×ýo€25VÉj)}§P ¸øõvcKR.ö¿^ ý…Jþ@MZþJ¼³2Ë(æûîT7ô¢™E!©+±iXW<=P\Æk±OÇúÂLß9‡,,[Zš¾𮬳¼™cƒô ‰I¾ç\ÎWJDk÷£8_oßÔXÓ°bÓëÈN L ®Ë} P€ (@ P úLmmmyÑ7~Žø"èèè@\\ “É„>ø@ûVûê£6ãÛëõ"##ßÿ=Nž< uœìã¸úÐRø"‘n?ü]y– (@ P€¸H8 ê"yPì&(@ P€ (@ P€Ñ!À`Mt P€ (@ P€ .*k.ªÇÅÎR€ (@ P€ (0иf͵Ž:8œÍ8Òò1š7£ÃÓÑ/#n{‚ã’®„ÍjGº-­_Úe# (@ P€ (@h`°f€þš%@³±ê Ô8"2BôùX@ê£6›Õ†Ùÿ »n¸Q€ (@ P€ (pæœuævlÍŠú ,©X±@M¸« º§º77 P€ (@ P€ Î\€Áš3·» k¾Yû&Þ¬}ë¼õMÝ{wÃÿ>o÷ç)@ P€ (@ P€»À9™Õqü>¬ýŸ}ñ N ‚‘£“1áêk0&axß 4~Ô„“tÎØ=…aÖDÀÙÚÍuUî0ìr\“’`TŠºo5õ©¢~k·ãö^þ/xöÆÉ|׎¯ÿR‡—ËÖ¡V«1«çý3®°Ž€ù;\«Å eë×€…·=ì¤Ñ0ö ýÄ1lÛñÊÿþV¿¯þ=®L¼‚S¢Âóð,(@ P€ (@ P GS[[›·Çgy±ñu(ÙÖ¶•q³Å¢Çø®u4âá_”à›°%å䵳˻»ê?_ôL)R‚c@þ+çÑŠ%ÝO}úaÊ‘NÔbÁ Û°èÕ¸b¨)äª×ù.¦¾´ ç½…é—J€'xó~OÞ‡íÁç‚öÕ6Oä=t¦ÿv;::‡AƒÁd2áƒ>оվú¨Íøöz½ÈÈÈÀ÷ß“'OB1¢ÿ:sZòx<çà.¼(@ P€ (@+`6›/ªÁE4³æxõ‹z &÷.¿׎Žœ|Õø!^,Ù‚#åO`ëžÁ,]‘„š¡Š.ó^,ŸŒS’)´ .uo¹ZåÏHÙá8Ù´ «_: Å—cvò0øŠA|”jjµÝj„춯×5í¨>øïhOš‰œË.â¯Â=·\âÔüÇ¡ðŸñ7aΘÑ0Y¯ÇŠì6\¥jÚ>/·\ŠEé0›FcæÌ[°}[øpZÃFõ)Ý–ô¹K P€ (@ P€ @o\³æ+lyU&ؘ.ÅÂ'ëÕá•r=–,Ÿ©õ­r×G!}&ñ˜IHHùÄ—@ŒqN"2£¬Ã´zqqV 7ÊÆ‡DrƒzðÙ_?ëqdïË&ñ|ý.V½»ëÊjaäkŒúÁh_Ýöj<²g;^~óe|¡·6æÇéðåÔ|7ÊžÃÞw—£Úé«y©õŠïÙ[Ÿz¬Ì‹ (@ P€ (@(ˆXfMDZjÔ¸ŒÙHU 5¶!£'ayÑåèÒy™pkÖtª,‡FÞñݵDtù¸õ“\U[Ïcðé'ÏI¹1Xqïø’À<ðÒ÷ÜFøæk´µ{1:Æ„Áƒ/ñµëõàoúþëoíÈ¶Ž”gÐó ºÞúÔc‡yñ"p¡þàXÇN×¹[Îû\ oã)Iþz`ùM°ž÷ÞD°.rbìuq°Gp lš (@ P€Ñ!¹Ì=Š’ž©¯I£4Ã"½ xðö¡CðM,ìµpd xͰJ•µÏ‹wE°ï_½'?WY5×Á‡ìÇÑ»=­xÿó÷/Œ „Å÷ß×@Hµ·Fнӭ=­oK¶ÕíÎò (@ P€èI bÁšQãÆ•_Ê«¶Ûð”™xtá8yãÓ` r^zI^ :í)î§ExjNJ ÷ú$`·ÚñIOS¡®{2¨iÿO?_„½z˵ùZ¦5Ék¹­WAÞ¹…ò±×c´þË8ñ¿D\üHye·,(|Pó~P 绞3lTŸ¸EXÀíDიŽ6Bƒ+¶TbÕmåZýû‡dáiÌ16L̘ˆDíàjm‚ÓˆÔQí8°ï}8=C`µ¥bÒD¾mmÀÿ©ÿ/ ž^‚ÄÔ‰˜hÓ'¹ZQÔ‰D["\ŽzuHø.ÆŠÔ‰×u™òä ¶«Õ# Mp¶{¤±Ò¿ HVé©ïi®GÒ’9c'føêèíº¥/G6 UÞ”e6' yâ5]úа÷·œ†I©±g0^šê%;(qlÈ}ánEÑV˜†ÆÂyXÏ^Ži€G9$'úz×ÏîêYu…-qõ p´· »<Ï^¦<‘{}z¤¾Ý>üœò[:xàs˜Ð€C ‘ÒoíÙwiŒ'(@ P€ (@`ˆk´·7É”×`ö¯nÔC2Ã1&õýþ'P£¦6EéÛ›‚Bì_™xEÁš1x2c|à61Wâ¡GßÁCÚªþø6Ú¯-SEFâžÅ;pç`³o=y=÷kx3xWËk½¯ÎÞÿ(×´_Õï©õoºßTŸ¸EV ©ü.Ìߨ¦Ýdwqv{G`sÕÛˆm;ˆûgáPÈÛØ/Ãã¯bR¢­~ùϪO§m¤¼Ü½Íמqeòª2<6Å&qŠ(,|Ö8ò½`ÃNÜ9±ëª0­~ƒ¼%å!eÕ*Ý‘nún‘):·çac§îÍ~¼N’@‘¼ .wÞS]Ú|xs%f&KHÛ(ÿ­y*„ÒtºãÍp¢¨°m—=Œý˜éÏÌi=°óWìÃ$É: låS\8Þ‘ j×Xd:R»«gUîYy'`îç†]žï¸÷ò›ñ¹þíõ÷0®wå͇þëDÑüÝùÀfìšc<£@[Ü£(@ P€ B…ößÑÑ™˜©fË|YŽïï ´ß¸õ%ÔèË¡ÎB2n½ öèëþå?¸¼‡¢—"fpÈ_í¡eO”bí‡öMC35ò'VÕîûPƒcxxÇv|¥=«@ F-T¼¶—eIzîShxtfɳ+PY±—IõÙ%eØ_µÉ^»Wj.êÍ;Q]]›–I™Ïñè}Û¡V5±X|Y #§­ÂÎýÕØ¿s®SëEK föc›±_êì¯x\kwßïêµ:ЧÚ@‚—UjíVþa•Vfcáj4ù§ÑciÅïT Æ;e•ûýå%„·¶"|߆×~¡jüýع Ó¤Rù’_ ÞåÆÁOj7XU¦·¹y•vüÔ³)I®úmØ'ÁÇ;'Û´k§=ÞØ‰xl¶ÜÔñdÝ^}sáýßíFÎÆUûQ±aŒm„À*QU!QŠ„»Ñwï„ÛtÇý({\ràL‡Pøë·ÃLÃ:÷Þûn(ø¿{û= û+±aü:õß@ÅœduîP€ (@ P€Ý D,³F¥ÌL]¼ï=òŽlY‡k'aæW#æK=RX_XËæø²r=6Ÿ”ì›S§½–ý¸k¦bÖµú+¦W¸§ ¤ÛÒeÈe2å¥S:‚v½?_7µg«£àæ?AæXaó æh¿¼é³Rܵ®éc§`„¥Ÿ6¨NÏ›ê‹ê· X,ˆ£­“8r”a,p7ÀÆÿ&,x“l1p¹\ˆIŒŸO+Æ’ÝÛpÔ5GÞŒ$oþRA—‡§øÞd‡É÷ëgcÎäd_&Iâu¸s‚dŽH°"x-”i-—v}Ù±—MÁ3«ÞGÞŠÝ8$+í&«¨‘3cÚªU˜–œ[¬ôËíBûqßǬªÅ0}—舖ÛÏ1ç'6x¤ïˆ‹{™†ÝKvãÈQƪiYï·a÷¶Hž#m'O‘€Uª„ÍzŒ~'A¢ŒU˜àë¦ôèôÇ;î–$B´ot ã& ú8hkà\·j&•³U^joŠÅ¨‘±j(â~0¢îÿ¶üâèõMzOÛ‡%oï–Žo‚/$eÀŸ¾{_~3ý–Æ}|ß=þ$ÓI1y­vŒ’ß@$Ö í (@ P€ (00"¬ øTüÛ3âñ§¦xU>Æ–>g!& ýJ¶gŒïoPs PÎ8‹¦8L½Ö·±ÿœìüqü ²`IÅRÿñéï“ M÷a˜Ú£Æ*7½·¬úÂíÜ 8µÄ)‘?‡M¾¥ m,Ä ;÷ásYÆ%ÓxœòôíÛù¯g›MòQzÞl‰ò×wУþ—íˆã+hi6þkVŒ“5pv¿&ÓxÊß÷ŸU;cƒ¢?!}ÿÖ §š[ôùFäuí<Þ8â@ÅœÇ0y_!öI %_b2j›0í<ðÀßó}RS­2Á3¯Åž#€ÀuÓh¯Cˤ¹3#44⻩ü1wõ\/“ ÿNÚÎÈKÔ=eM¢¯Ü‚5gàÞ‡¾O”µÂm=þ’»± ×ÏQ€ (@ P€~ÈkÔm†ÁœÅOaN‡¼¶[>êýPñ ñú6©(ÍÒû2$¿*-Õzÿ>fJKgõ^0ŠJØå½Î›8 õ[Ï먖õ3¨¾p;Oút¤iË6`¶DEd ^ߦ$DZJz†§—)l=õÜÜéMOjñbµÔ‚6As¡œõÈ»YÖ}‘©B +Áuò‡{¢¹÷åúÔÍFN[†’;ÇJfÑy)(‰M´Á"‹?¶«ËÂÅG6àÀ¶×P¾ûYÜõöü¡ê1¸÷ùžÜ]*H7÷ìz:“†K6âPëd´ýNN’­3.|ÌBRk|-DÄÝÛu= ØË•»dU”ˆ[ð2CgâÞ‡¾wõñéù÷Ð]-ž§(@ P€ zˆØš5]n:|8äcjº”à‰~¸-ý6-`ÓMQ*P3-õŸÎ¨.+¹€z]¶”±ØÆj .»µ#99©©¾Oì•ã¾ÂíðeµœÉ_Ûš%sd·dœÈ6Vϰ1Út;Z aÚ3ÀSämNò©XÉàÐò~Ì”žà¾ch¢L±’G»!Ö–ìï{jL+æßÿ+4Iìæíû³õë½0Kàf⤛𠴿IÖE1INŽ ­ø÷ß8`{`¶¶°°Ñ—3ýNügc‚Ø.¹ï>m Ô‚{‚²uT£ò6(#œ9wy`’A³ïýÖ a8±í%I’QZ;=Ï3qï[ßÝÚ”:wP¶Òîjõê(“ƒ>Ú‡}&”uwzÛú4ޡɸgŽ,Ä# /«×€Ož ä–¾›þ«‹ƒí{ëá¶DÖ½|I~³m¯¸Äo~qÊ%›æ²w¢3»1÷ë´Üûð›q7mGnn.î*o ¡íí÷à î<‹âß¼&Ï38àÒ (@ P€ (@ ÈOƒ ºwÏ€š†ôDÞ¨uÔ⳿~†[?A³³'='û¥ÃÌô©NêõÜê­O\L¸_XϬ‹ ·Ì™€Cåû°ñ)YfÚddÿøh³ƒLm1b•ía±MCÉ‚(Ú¸+ å-J²M^ðÌo?‹Ý‡6b›ã<˜Úµï‰¢L^ðtÿŠr©çïKZƒKÕ—ufbÕ+‹Îbs;¶á†ü§ð°¼2{¦¤s¨·Lõ©]½f³ï­Ijq—¬EcŽ•ãûã+§ÞE+oê²I»Æäõ,µ¹]N™fe…µ»ueº4Òû‰Ö½«µ·]=VQɉ½—×Jô£»cûýÈ(«y^Öíü–=ôçLÝûØ÷3þ=ôÐe^¢(@ P€ @¤:¯µ©ûôW»Ì¬é/I¶C Q@‚g¤é<,šþ#a–X ¶ôiëÒU7Lð¥Kc½”“v;·b‘9RÏui¶'Ü­Mx¿á~+¯%Ç„eøI_5ªý.cîãM{(æR‘)ÕnŸìÂõ¡uO³ï§ý{èaŒ¼D P€ (@ ø¬á/8-kl§mO«öÅSر¯K6ÊÂ0Þ (Y®O;OÝ÷šd!áþŠBõó¢å÷ÐÏllŽ (@ P€= pT<¼x! pÔ…øTbŸú:]k Žc¢(@ P€ ÀÀà4¨õ<9 P jú8m(j}8p P€ (@ P RçèÕÝ'ðÇ×ckÃñHƒíR€ (@ P€ (@!pN‚5Dyí\?|@ q (@ P€ (@ P Rç$XóQe%0ø§øÉhk"õ Ù.(@ P€ (@ P€C òÁšŽl;Œ»ãF0T30~4(@ P€ (@ P€‘ˆx°æ«ƒ•øÆt)r¯MˆÜ(Ø2(@ P€ (@ P€ ÖÇ;o4i³2d€ˆq (@ P€ (@ P ‚ Öœ:V‡éüœ©ã"86M P€ (@ P€ Ž@Dƒ5íÚ* OBÚh¦Õ œŸ GB P€ (@ P€ @$G¬ñŽF”ËÂÂÉsoD|Än†)0ð¾üòË?HŽ (@ P€ @ÆŒÁÖû¿éˆk¾ªÝ¥-,|ïµ£ú¿×l‘Q$p±ý•(z4*(@ P€ (@ˆDhÔ üQ[Xøf¤ð}Ýypl” (@ P€ (@)‘`Í©/jpà;`˜¿ŽŠ (@ P€ (@ˆ DdÔÑ“P\œŽø¦ÕDìɱa P€ (@ P€ ¤@D‚5Àp$0P3 0(@ P€ (@ P€‘ˆÈ4¨Èv™­S€ (@ P€ (@+À`ÍÀ}¶(@ P€ (@ P€¡ƒ5áCc—)@ P€ (@ P€¸ Ö ÜgË‘Q€ (@ P€ (p 0Xs>4v™ (@ P€ (@+À`ÍÀ}¶(@ P€ (@ P€¡ƒ5áCc—)@ P€ (@ P€¸ƒîÐ82 D‡€«ù0;=°„®Ùšˆd{RØkaŠŸÝ)W3ê·Àm±b|Z bÏ¢5mL-.X,IHM³G¬ÿ~;7`?öXSP¯]8\w¹c”¤ÞFäFs]ZÜnXíiR>Ü jž» (@ P€ º`°¦ž¦ÀÅ"ÐR½‹JÝwך·vÁn Dt_üL¯¸[ªQ°¨D«^ôú~ÌMé-¸ÑýücòZQZýÒ"ÕwWlÐ:b+zsSür®ÀÝÝ\ó Þù¶‹  ž„µðTÎ|•û (@ P€ ú,ÀiP}¦bA \˜–¡IþŽ´Ù`Ó?þ“Î2ܺ¶Æ±Kÿe’X¬7#3'™¹w!1RˆM½9:Hó«5pá4í)óÍÏNöï÷´“äõ]µ›Í=ã5 P€ (@ P€= 0³¦G^¤ÀÅ$†ç¶¼€#¸ñÍa,¼ánÔ¨„šÃ͈ÈÒ§&¹á¨«Am£LY’ FI)iÈ šjäjnD“³ÖÄdÄxšPSÝ(uã`O›ˆ¬”@`î㨫ªA£Ó…5íÇ"ó…Âln™US}-Rn¨5 ³2eº‘/°£®549a¶Ú0>¨®zͰ#/;…öLÀ «´(—{ŒÕ5õpº¾Õî{}VJÈ4©ãÍuþ>§de 5¦ 'bd<]¦2™lÈË·bO™¦ãe¨;~²X vÊ9µy­…ÈJRçWK£Lõj†³]]“¾Ù“‘–*S¾º‰Sµ4Fk;˜œŠ$}Š•ïœ'´?by¸®Í­N±N@JZ&Òìgž™¤u–ÿP€ (@ P€­ƒ5í£cÇ)*à…9$h8;Ò$ÒQÓ¦Ê õv·`ÝÜ(s„ÖEZÞ)+a7vο%ZNeä0§xÖäJÀF¦d ÎÃèZP?ÓRµ3 d¨‹JßÁÜ´4ï\Œ‚NS¸¼#‹p…s îÓÎÛðÊ{oÁ¦œÑ–7§Õkrµ±®Xˆ»ƒ³ˆ|³²´¢ÝMMJι (“‚&'*êÈεÃÝ\ƒ]ú ÒçgKXh®\‰[—g»K0Grs¶ì/FÊÀ9mïÛF,¼ãnmZTáËû1ï*ÕŠU ïÖ|þ¸[ª0wÆCZ¹àÒŠJQ:7-ø÷)@ P€ (@(à4¨(yÐæÀ0y›$3¥u’¡QWWŠu¿Ä=è’9o¢p¨Z{·¨±¡°x=ž^™¯e® ®«v6kHq—¬lÓ‹P¼4ßbϦƒ’ùáFåš@ &§°OëíøKJã¿«ükN!Ö—®C¾ŠÉVR° n/‚§pi¬V$¤ÇâYXØ·%HJl‚¦zymÓ±¢ø—HÓ§™ölE“´å=^ÔXsP¼þiägúî§ÚênjRlr2õ¶j6ÕË耦ª-ªŠ¶ÍÊ´x)05™E²Ð¼"cV› {Pó™¤Ï„ÙŒiQæ ÌÃ×ןl05ZŸK±"ß ©+)@E£+L«þzí^¹Y‰¨Ûº›J¶Âkäíøêt“{Ô ìê™=¡'}G‰sôÓ|[uk>°7P'}~¦X  Í[„ü¬X”•í œ·' ì² Ù¹kþA>7‹-ÅS€e{ 3°A^anu:|A ™Ö¶09±sS€ (@ P€Q ÀÌš(xÈâ« P½º;t³ 93Íw­®JË€IÊ]ƒ××èon2é ä¯x…YI«ûÍ1zÐ@‚>jKÊ]Ò"=EÚQšLIM‘‰MÚ¦WL6¬Üÿºÿ PF Æš–Òw r7½]½ºÿË+-úF§Ÿ .göe¥ÆmAÎsûQZ\„üéÓ‘3=Ų(p‘Àßfw;±©9þ7LALfe&û‹ZìÓ±¾0ÓwìE%PcKKó÷]Y'x3Ç* ¦¬+†žÜ仜™Â›ô3’¹k÷£8_oÛÔXÓ°bÓëÚ"ÉÁírŸ (@ P€ˆS[[›ï¯¯è/G9:::‡AƒÁd2áƒ>оվú¨Íøöz½ÈÈÈÀ÷ß“'OB1b(œíÜp¹TΊ,,Ó‘´¸Á™4) «¸<ÒŽ9Z|¢›6ÜnT1µ4qlO»©ß—ÓÞYWf†Ê^±â®Ò×17MÒŠ\uX˜] ­E“¹â-–ú¨ÍfµaAö¿Â.n (@ P€ (@ œ¹ƒ5gnwÁÖ¬¨¯À›µoÓþ© Ð’Š¥¸-ýVäMÌ;§÷è7;vìØ@"ÇG P€ (@ P ¢cÆŒ‰hûýÝ8׬éoÑóÜÞ›µožó@MðUhwÃÿ>Å} P€ (@ P€ NCàœdÖt?†k?Äg_|ƒSC†`äèdL¸úŒIÔÕhü¨ '1$蜱{ 쉀³µ›ëªÜ)`Øå¸&%Á¨ußjêSEýÖnÇí½ü_ðì“1:>ø®_ÿ¥/—­C­Vc VÏûg\aów¹V‹Êׯ o{ÙI£aìAû‰cضã”ÿ%ü­~_ý{\™x§D…ç9í³?úÑN»+P€ (@ P€ ÀÅ+`jkkóF²ûï¬Cɶ¦°·7ûQ,ºQOEêhÄÿ(Á7aKÊÉkg–wwÕ¾è™R¤Ç€üWþΣKº_£æ‡(»{&Fvf8Q‹/lâV㊡¦«^绘úÒZ,œ÷¦_*žàÍû6>y¶Ÿ ÚWkØ<‘÷xЙþÛíèè@\\ “É„>ø@ûVûê£6ãÛëõ"##ßÿ=Nž< uñQ¨©uÔv¨²Ûn¼^Ô´£úà¿£=i&r.»ˆ¿ ÷Ür‰?Pó‡^ÀÆß„9cFÃd½+²Ûp•¨iûüm¼Ür)e¤Ãl™3oÁömáÃ5j Õ§t[zÐSä.(@ P€ (@ P€½ DpÍš¯°åU™`cº Ÿ\¬jTw†cTÊõX²|¦Ö·Ê]…ôq˜dLÄK`&!!!ä?\1Æ9‰ÈŒ²ÓêÅÅY1<Þ(vUÈ èÁgý¬Ç‘ý}¼/›Äóõ»Xõîv¬+«…‘¯1ê£}uÛ«ñÈžíxùÍ—ñ…ÞÚ˜§Ã—Só5Þ({{ß]Žj§¯æ¥Ö+z¼go}ê±2/R€ (@ P€ ¢T b™5ǪqDPã2f#U%ÔtÚ†Œž„åE—£cHç5f­YÓ©²y7Æw×ÑuæãÖOzpUm9MZ•[c¶-Ã×nñQOä;ßI“/ £¾<¡Â6]V¿ñ• ú··>å.(@ P€ (@ P€º@Ä‚5_Qy5ÚR4ú­dµšcÛ°¬ä€ÿXÛüS<óü™}ZÎw¨­Ñ !å.ýb&R¢uåà‰ðÞ^²\T­ÌiÏ`åxcêR;><¸Ëß­ÃÒ{jzÜFÛ™øq¼œ1é?yCT»~9+IÖºéÃÖ—>õ¡¡(@ P€ (@ D•@Ä‚5£Æ*¿”Wm¶á)3ñèÂqòƧÁ6ä$¼ô’¼:Y£JÆý´OÍI Tâ^ŸìV;>éi*ÔuO5íãéç‹°Wo¹ö/_Ë´&y-·õ*È;·P>özæÍ‰ÿû%ââGÊ+»eAá뀚÷ƒ9ߣÂwQõ‰(@ P€ (@ P€§'±`öö&éËòÌþÕzHf8Ƥ^£÷ðjÔŒ¨(}{Óé=¦ÞK_™xEÁš1x2c| ‘˜+ñУïà!íŒU|íWŒ–…„GâžÅ;pç`³o=y=÷kx3xWËk½¯ÎÞÿ(×´_Õï©õoºßTŸ¸Q€ (@ P€ (pz{ÔÑ™˜©fË|YŽïï t®qëK¨Ñ—C œ…dÜ0zìÑ×ýËpyE/EÌ`S÷×O”bí‡öMC35hCÕîûd›cxxÇv|¥=«@ F-T¼öh÷Mª+=÷©çº¼J P€ (@ P€ˆVˆeÖ¨”™©‹â½GžÃ‘-«ñpí$̼ñjÄr¢örÔ|©Gj|oàÖü} ¯Çæ“’}´À°Ú»f*f]«¿b:ZŸVãN·¥Ãf½ ççaJÕàç릆9têè#¸ùOc9VEØ<¨9Zç¿hú¬w­+EúØ)aiǧ *„Óó¦ú¢úÄ (@ P€ (@Óˆ`°F:ŸŠ{æQ¼±q#þÔt¯ÊÇØÒç,Ĥ¡BÉ6ãŒñý jÊgч©×ú"öŸ“¿c"ŽŸcAö,©Xê?>ýc¤é> S{ÔXå¦÷–U_¸Q€ (@ P€ (pú¦¶¶6ïéW;ƒòÚnù¨÷CÅ'ÄwZVø Úc•°oÖ¾‰Šú­a¯«“?Ëú¦¥þSÄn×!¿£¸¸8 4&“ |ðö­öÕGmÆ·×ëEFF¾ÿþ{œZˆ¢¢"ä?&{OîìvìÃüÂ"b÷QWÆ£·)å ;}æååâößCšë\ä<Øšcbк¯KîZGàtî)@ P€ (@ œ»`MÁ¦CTÀæñ¼µÚ6¡W"w¤Ö¨Q÷ŒdFMäzñ·ìj•@‰w6Ê~»z&å2à²s=¶ØP²ìa¬šØožÖ·%['ôÕðg2žÎub’õ.JRC˜XÌ‘m¿ó¡¯t1v©rÙ”UVbçÎØY¹e›VaòHàóò%ØÞtsnkÁÄ™¢äù‡%=íêßú=¸C P€ (@ œ;È®YsîÆÁ;u°[íx"ï Ô:jñÙ_?ÃÇ­Ÿ ÙÙŒ“ž“JžÙá0ó0¨{¨×s«·>q1á3sì¯ZÉèóÊóè>±Ï…¦ú£°$Ú`v9PÄ9¶Ô‰˜h³êÝPeHœ˜ 8êqà ìOš†‰*øãv¢þýChuJ„#ÆŠ±©‚¦ø˜eZÑ8$Zc‚†ãFsýû8âpJªO ÆNÌ*ï+Ö*Ó´Ž~.ý¸Vk2&L´AMLrJàéàÏaB5ü=FZmR·ëÈ\Ž&•æÇNLÆiOÖ‘@Äöƒ­H`ú¶ ¯½Ñ&AŠÀ0\­rÖ®÷hu4ˆ…L•’>û¶ŒŠÕú¯Ž­©S°¬¤M²žÅCÌIN¼¨lõAÉRã§dw¨qÈo P€ (@ Pà 0XsŽ Ï×mT…”ó¥ïk2.{ ìªù=øµLÓù<øœ¾?aÁ<çD­LQá|´M˜:¤]=n2Æá}Ü•·¤KÝÉË6ã±›$UÅ}÷Ï›,Ø„]wJ ­xíö϶À¦ª·‘nùi#Ä@µ©–ÿ™0 Ó¼»±ûwðÀ”9þ@ó£Ý2ÒXðÈl|²\•†c߯Q(y`ó~ ¸7q`ãí÷a¯õAìÙek´â¡ÿ¨š±‰ÿÅcktšß (@ P€ Àùà4¨ó%ÏûR \’-£¶ÀRÄÜô‡jüaŽSÔæ5ê/%>û‰?`u5öW–a¶L•:´±ÛÔâ$Rf¬*#škf/Çæ² Ü3(¿×¨YPR¦Õ«¬Ø„i#}Åó°W¯g“z6}ÞPÃk¿Ð5³Ûì»ÏN_ùò%¿@½$æ¸ê7jš“—¡bÿ~TWïdž‡¯Úʱzw¬œ«Ä†Ò15­«r?*´1ttÓvû4\7åv°E 3tëZG»nž€[nŸ-s”žÅû­Æ%Y{æµrÉNú&MAë[ê-B†Š×vû‚Wê´«é öÉBÖs›¸w»mn7\.—öqJfÒÆ'ŸÒZ™4NT™­±ÐŽ98आÿP€ (@ P€çB€Ášs¡Ì{P ‚îÖzüî·m0Ù†ô0 JïÀäUxð'—iÓu,±6<øÌ*íÂ>™ò¤6-ØsÝ*<óà4$Ûaþê6JÌÈÙpg†ošRlb*Þà«÷;½žª+3’ds ü·Ò–¬á2ç'6x$xቋ{™&×>Ç™·tðµ7dÿ2üÛÃ7!Ñ¢²UÔ:)áñ‡À-63,–X¨UjZרX‹j‘ Ú,ȸs9žyìNØŒ„— «=îÊ Ç^7 $Àô’±³óŠ%“ñÀ$Œ JˆAì<ðÿ³÷.`Qi¾ïŸ¦úŒØsÄ™4egSöÞ}’¢MªÒO8ï·V]© *úÿ¥Öå[ßå÷]ÖZïzß÷Ké9ÒŸ³Ý ÕûeU±tÌ› Í$‚¦ÌGÁÌ™3µÿsròP©ä;"ÊHдF[i³ER½µ¿"_c     ¸Áhuƒ3;ZN~:g£€uëÓûløs7ÞêßQ[1fí¥¼U|ظæêç2²BÒY˜î×6Q—-ñ†<õ _ºDh#…¹\.‹ò^'.ã)·œ—‰AŽfLH›—X8DlbŒGötàùòð‹é–û—"@ê-) Œ]šXÅ“µ = œ-Ç¡ N¬JêÂñkcΞ,$ŠIGÑÆ ½Ë¬‹–QŒøJW`1lÍ©Ø,òµ¼êBäD[pl•2oc     ¸Q(¬¹Q¤™ r<Ÿ…Ò •8p¨óV¥öŸK ÖˆŠÙ#N†E袄6Fùuˆ%Í¢ <Á¥ .¼2ïaô¸L®|G}1(Y8^4k’ˆÉ2­Ç%Zw·v½_)Æ{» ‡E´inDHÌZ‰¨Ó²ÜyË\t¿yH*ÐH.Ýaޝ¦¤td •Õ°0úŠ&Ûœ=Ñw^ÛˆNAZjjˆ+0ÒÈbkGuµ”¾G4‹æéB¼Àºp›H€H€H€H€H`x Ü 3¨Ïð‹7_LJmW‡·6Lî@–´UØ,>/‹IÒ@&+—÷WiÃ(§º­" ?>À¤Ç# Q(ÕêQâ=Fü¯T¥mûe¥v%¦—`å^ ÒäÝÞ!>^LÖx$$$èÿ£íXºüïÐî–U“REÔUŽÓíËB;Î"sñb-õ0A¡`âakDbö*-§í"œ9pª-MDZzöíØªt½â«WB)Šwí—4BŒ­äœ ÿc‘ò³m×!¯ÑE+ÌÍ!ŒR‚}ÐxŽzâÓjæb]¢u3CüÚ ‰øâ–e+µöÈà¬Ñ÷xð—H€H€H€H€Hà¸!fP¿®©îþküð kn`Û2«;‰@oó¦¾ê“‚é= 0ï=†ìoݽIJú Š.Þ£/OZU­æMØP.×µê×õ$þ»7?«ûmñ(ÈxusÌ©«PQ,/¬”|”~†ɧ¨d3´Õ¯-é8ZæÂ¦åÅ(-ÌÓÏG§Å;‘îÑX±¦ÌÀ­8]Yçø4ˆ{™pÊð²8A93Àøhß5 ²twLy+¢³B¥kN„’ë·$â'Úòã½òðV¾×áÀÝ‘ÄÖ[n7¾’Í{½»ü%    ¸A¢ºººz†5¯kmX·ú <°¨+¦ŽÖ¬˜øAàÚµk5jîºë.DÉÊü±ö«¶Õ¼¿===H_"_ý5¾øâ ÑéÁèÑ£G(wˆÃ˜ÐâÛÍÇü·æ¡îX¶W)"8Ò—íXþÈb`}v‹—Z¥ID¦Aù‡QKS‹NŠ8Ï5i«8y³°aõ”ØWíó/®òÄï'—R¡Qõ3™Â—Û›ÅMÿµcËÔLTglEÃÆ´a(Í-ÆöòqL_Œ•ûj?$zDÃÀŒI’ @ä ò3’°kÖ\iªÁçQßÁ̇(¨Iƒea4/¥8ܔйž%¶ÃÕÀáÖm†Œ×%1†w\Ž6Þ_©9ÝÍñm¿w™ŒJè$øéãfï»ÐÞt­§ªùpY·0e˜ Êêf±uÚmhúè´¶:Öx 5ÃÔàL–H€H€H€H€ú%0Ìš«8q ˜œ‹ t}ÐoCð$ |Ö¯GÖû9(ÍÏ{o &„ÉNˆ üºóv\@i¥¼ØËBÖDËu'sË^躄üü P D%æ–ažµ—Cåá,øMaëBõÓÒÄárʳe½–WÎÊ2m     @ÃjõÕ'5È{õCdo,Ã_?@iM xn_?šA]?;^9xjÕ)Ídì–Ö|½x ÜIhÐÚ¿>ö¡8NC25T¸I$0’·º?‘D“e%    ˆ„Àð-Ý}í"*/ñY?Âý‘”„qH€H€H€H€H€H€H€H€0lš+çŽiŽ…g?4–˜I€H€H€H€H€H€H€H€"$0LšÏð åX8y&ÜaIH€H€H€H€H€H€H€H`x4k¾ú]#êÿ<þèD"&       aYºûžÒP\<÷¡ZÍ Ú‚QI€H€H€H€H€H€H€HÃ"¬îà jؽH€H€H€H€H€H€H€H`ІÉgÍ ËÁ H€H€H€H€H€H€H€H€„…5ì$@$@$@$@$@$@$@$p &3¨[¨†, ŒpŸ~ú鯋O$@$@$@$@$@7—Àƒ>xs 0ÈÜ)¬$0F'M`¤M*7šó#     ÛÍ n·e}H€H€H€H€H€H€H€F4 kFtó±ð$@$@$@$@$@$@$@· kn·e}H€H€H€H€H€H€H€F4 kFtó±ð$@$@$@$@$@$@$@· kn·e}H€H€H€H€H€H€H€F4 kFtó±ð$@$@$@$@$@$@$@· kn·e}H€H€H€H€H€H€H€F4 kFtó±ð$@$@$@$@$@$@$@· kn·e}H€H€H€H€H€H€H€F4 kFtó±ð$@$@$@$@$@$@$@· kn·e}H€H€H€H€H€H€H€F4»GtéYx lÍø—‹h·;`‰‹G\Ü$$'ÇÁx[0r¢¹á<´Êâ<)6¨VÎŽó8ïp˱hLJžSÐÙЗ³mç;%½X$Ü6ŒBëÉ#$@$@$@$@$@#“…5#³ÝXjðpuà¥ÇžÀ1‡ÿoËœƒƒGògŒòºî ÉgÇÚWÐ! ÄÍ~kfÆ]wR×sáù—V ¬K]™ŒwÏü |urâèÒ%(ÑÎYñö™ƒ˜ä;>§Ž£k°¬Ä&'#‹>%     á!@3¨ááÊTIàp¡æ%¿ ÆR1aÆšžsR†¶n<çy¼£ jäÐŒ jÔ…Æ{½š9c`ð§Ä-     ¸%P³æ–h‚†€€5GÞX Y -^œƒ—&?†c*Ùcíp,E‡˜GÁ`FÂ$¿iTçÅó°»Å|È`Aò„X8;/¢ù¼Äw8“qñbJ%ÇåΟÿ\õ(è\m³£‹@üOZ®«8ßܬåáÄLHž‚ä81’º¶Ý·äöºF\”ôMæ ˜63&1Iª9Ù‡/ÅŠùÖ1c gºeIž 3NJ< âèy,NžªAëlЩœÌ$í˜úÓy±ç;:ÑP—©K¸´µúImÁ$U_¼åÓªøÓ*eBÕ(&Y’î½æX$M‚8S@ªr]ss:T¾" 2K’®×Òæ     臅5ýÀá) þØî)¥­EûÌX4-YüÕÄâ…ÚÈs¹áv›àl\‹e¯4k_;ñ+L#R×y<÷ä(c sÞÛØÓ^†'ŠN†TÙœó>˜Ý%+Ê|çlÇŠ°D$A{Îü Ž°à±µZ:¾²‘œ¿{$ÃÕY‡%ËJOù¶‹^ (%~ñ‡žÅhxyfˆP%jÌ$,²Êzéê±cèxA„$F7Z>”‚HèÁÌNˆ–-'j^|'ƒÓÕâL)DÝsÔfP¸xô9,‘„{bòQwrˆO§·Ü~S©ÎºxlmEеj'Ï ,HWÇQüà‰¢óJs´ÐÔy„H€H€H€H€Hà6#@3¨Û¬AY;€IKgú*}²¬O>ñ(~0y2V¼òÚDY$6Ö„¸KE+EUJ<#¶“º€¥ÇŒÂÙfìójÌ3 qD={^ÔÍŽkÑhÌôí«kÍ3òðÚë{0Î`G™WPcžb9V˜“¬¢ ¹dª.Šv‰1@ëD|Ää¼ÞÂh‚šdä¾€)žcQ'?D»«GK#ø SOÑE‰†MÃoÝ"QiÃÛÍz¬¨Oh~l\ú5SÖ¼Ž#G¢ØS¦¨Æ}è“¶×,*ê»F¿Y”¯Üº©TÏÿ¬ó jTý_ß³9{³’e¸(é6Wé­ÌÀž#'Pµç½pb޶·QƒH€H€H€H€H€" @aM…neq3‹qdO¡ßO§°Í'+°vÉcÈÜÑ—)kt9÷ɾĹPU¡Çœœ‡d“v|ÄÑ| 'ëÚ´{ö¼ƒÅa¯9Éó–aŠ'N\ò4L›šŒQí𤂜¥˜:)Ór–"Ùï½æÎ t9{öaM澞ï;ž·ç5,˜“)Âÿ±¾üÈX“2}×Uµ´£³­Î§Ñã52ÆM“rïÁë"8záñd˜M˜,ÞðQãµóòåâßhoòÔT„[/,IñIÈYºÈ¡Í]èîеy¢zšqôd#>7'keQ —Æ+}     ˜Í fÄ$pKp¹\ˆMžƒ=5sà¼Ú‰ö¶fÔ‰†GE£.8°½W„¶¥'0EüØ Q¶*,L?1zµr%‹ò‹˜M½<OˆI‘£eEòßSëbÎT¸ øòK?ÉS Fÿ¡ŠOø7ˆÒwìk†kª?RÜ·=BŸÖŠxu sÌŸjðVÔÿ‘Œ¼h«BÙÞ)Ã+ÑZ¿ ”ìÍ0\=‰}Eã ]‹(8•~öÆx¤La¢£Lº]RBfž¬{]䋳t~²¾}¯ f= ^‘4êæLÎ-ý`ò‘8Ü+KuËn³\a«X†T$ãÝ3¯£x:PpJL“–‰Âl…ÙaÓ´S”è¹x‹ZœiHCTl²&©ðÈ_‚L rŠr”añs˜j:Š“6ß%Ê  ©ý¸‰R_ˆpK´ žøÄ)$('ͳ jd9ôf¬xì!ÄX­è²yÓÍA|¬íR]_y™u¢íƒóð.J5%9.(_=&ÿ’ @(jÖ„2áQ’¿‡·‹s|Z0…Ÿ‘÷ê–ÁVÎw•Ï=ä`ÊñŠ-bQxb¾º‘,eiìF›HfDà’÷ÚẢSñb‘óZ^P>nY;iæ+µ²Ú’vHð jınáÞw1-VÒ0Eû®1 z<ï_IŒh¤†ÿzL‡¶Ç`ö"O~rtÌìLŸ ”Š7ûäy––²5Ó5ÉÉ^ý£fÑœéö¥Õ#%3Éžq©ƒ7ŽråäåhZK¾òDYñRí»¾ ¼‚sröœÈ:FL{鄬¬¥ ÀTÞ'uaΔå¯áå9q¾|¹A$@$@$@$@$@ýˆêêêêÛ£fWò Ü$×®]èQ£p×]w!** ü±ö«¶Õ¼¿===HMMÅ×_/¾øjôèÑ7©äß­Óé(Qe1™ä_  ÒU4Ô´Á¼QP¦ùQIÎ{L-”˜Q9Ý¢obU”E”8.9çõhç¼×‰°Åd :téÚq¹œÐ«!",N¤×xã ¤>ê* Jgǃ°ˆ0û…†ù Üâhu‹7‹Gƒ! 4êHpÙ±£`­o™k¥9²tN|H4í€H6úÒx¯è-¨QÇ#¹Î{ý ø5#Òx‹é5dž·€ÀÊ[/þ’ Œ,ÖŒ¬öbiIà: ˜‘9c†¾.“!³g"ÙãÇæ:äe$@$@$@$@$@$@ÃD€Âša{«${ÎÖ ›£:ƒŽ«¸æ¾6$E»ÏpâÆÄabì_ÀjŽÃd«î§cHg"COÀ‹/¿<ôé2E      !'@aÍ#½5ìMyÝÏDP£;8êR)¡ÏoD¤þ«`•U€r§ý-âDpÃ@$@$@$@$@$@$@$pý¸Ôõ³»e¯¬j©Â†ª†MP®âJ(¤òTy3 \? k®ŸÝ-yåç>ÀçÞ´²©¼«ÛþÇMËŸ“ ÀH'pCÌ ®]ý¿:÷+üöwŸã«{îAÌñHü«ïãÁ1÷ðû ÝŽ/pOÀ1ïæWø–Ù8ì}œWñ¾¾õ]|ÂïEwܯ2}ªjù°Ïz÷|÷¿£ôGéxàþhàOÝøýÿjÆ?TìÀ9íŠéزø¿á{æÑ0üÉ-çÎág[=ç€ç~²Ób€án7º?û‡Ž¬Gåÿ ŸÕ?6ü#þÂò=šD…Çã$@$@$@$@$@$@$Ð/¨®®®ž~c|ÓOì@É¡ö°©LÌz+~ô ~îÚE¬[]‚ÏÃÆ”ƒe¿ªìë¬ïxþÎ=˜(ò¹ý7ž¯ÚзéÓZ†Š%óÓÃgçû³CX±r ¾woTÐÙÇGxôç¯à¹Å1û;"à =¿Cù¶gp8ðXÀ¶òaójæÖ€#C·yíÚ5Œ5 wÝu¢¢¢ðñÇk¿j[ýWÁûÛÓÓƒÔÔT|ýõ×øâ‹/ öG=t…aJ$@$@$@$@$@$@$0ĆU³æjÛAM<žÞø4zà~)þ5\¹ø+¼Yò.T¾Šÿl'WÒQ¨¹WUnÊÓØ˜¯®‰¦L@¸ç>¹vî_)ý‰{¾h?„-?¯—è‘ÿ-èÑïÁýw¨ æœí\ß‚Aö“=ìÔt£¡éŸÐ;3Æ}¸ÿ/ñÔÜoû5ÿOëÏðÿÞ? Ù>€(óÃ(œÖ…¿ôjº.Ç?t~+R'ÃõæÍ›‹Ã‡Â‹k”U¦ÉÖÉ­ÈM        £Ïš+xï1°‰úžÛ¶Æ#¨QŹc'<Œ çie«9öë 2~K4&îÁ̘1c‚þߟb¼ÇD"3Öü-íºQ£Ì¸ï~oÜûÃQep›îüöÿ¶ßšýùýº6‰û÷¡è£ÃØQqnÏcÿãúVwÖŸ<Œøàð;Ϲÿódè:5¿ÇŠ7pê£hpèW~Çü½~ó¨Lý^Ì“$@$@$@$@$@$@$p‡6ÍškŸ4à‚@•š…¥PÓ+Üó@6æ×îéíc&œÏš^Ë®WïÆûãÎ:òû¿ö[áºs•pÿ§hüû¿¾!ñDáÓÓaЮp£ç.Ï–Ë+¾ù=ºº{ð@tî¾ûÛzº=nüѓÿý±ÓÌ1Òý[Ð T&Orü!       0l¯4eòO•éW×põ³k¾ìG‹QâpØ”ÈàÓšxóó‡$®_ óÅ÷aæÓOÞ±¾h¼|úûUfGý…Æso£QEÿ~¾fð´ü¿Ÿßƒ}J·Æ`ô¦ð ~ïþѪEþ¤ŒÒ:jçÓÏ”Ø&Äû/àï@e ˆÊM       aÖ\¹ ôj4W4ž¬Ä[Í'‡PPRïÛ×6îþkìÜ-ÆQð˜åü ç5±BP¼ïün&Ü©žƒƒH„ßé@ËE]5%c'^šä5]êÆ¯š^ÁÆšñÂÓÏi‰º]Þ´§à?ßïÎDyºˆ¬Õí9=5V|ÝD")SÉÜñQ>ùä“;ž À7!ðàƒŠ$ß$¡tí° kÆNœÔ|*KmûÃ}æáùç&ÊŠOwã[÷|úŸÿ\–†ökÖ¨˜£þ:Û³'ø/âVDâÌqø×þL¡R¶ù5Ý¿Ák»óqÊ“ò¹ÿõ{1k’e¹Í Ys •ãöiÞ|öÿ}ŠQ÷ÇÈ’ÝâP8h< Èù“×0*|U™H€H€H€H€H€H€H€G`Ø„5ÚêMR–úÊFdýÝ<"™ûð`Â÷=%ü Ê"ê]½ipÍ4pì¿°|¯a̓ؖ:ÉŸHô_`íó'°V;âFÝ/Ž£û{ˆ#á<µæÞmÐýÙÈòÜûßÿæ­Ü¿’e½ÿjÚý¯rNë5n4œQþoúªL ßœÀŸýÙŸ}óD˜ Àˆ!0l«AÝóÀÌSÖ2ŸV¢ü#ïÚB~.?ü9=îPüG!7”Þòˆtû»ÿñ»ýDý¢ïŽêûüg{ðʯþE7Có jÐ…ºêgÄÏÍ'Xwä0®hmåÔ(Gů\ê;Iu¦ÿ2õ-Ï’ ÀJ`Ø4k”ÊÌ£kžÃ™õoàÂ{[°î\æýè¯0ê+ΨDã§I¾·Æ_w0ü:ö}!Ú7†Õö¨ï?ŠÇò,1}§¶V?õžl «ylŽËab5âÙ†9pèÒzÌùç1e¼’°¹Ñx©Ùw2ê·{°hÇL?£Ýø÷6%Âé?¨²¨21 ŽÀ0 k¤ ÷'àïw>ååøçöz¼#ÿ½arösH»÷ŸQrÈ{Äûû9ëýñ¼GÑ> >¤;"ö“ÿ@EŽÜi¹ØPõ‚o🈦o1̹K^/7§¬ÊÂ@$@$@$@$@$@$@$0xQ]]]=ƒ¿ì:®¸&Ëv˵>ÔýcîïåVø:Òã%a |pîTµ|öÜ:ø7Sÿ ?¶ì®I?5jîºë.DEEáã?Ö~Õ¶ú¯‚÷·§§©©©øúë¯ñÅ_@í=zØÊ6 »ÝîáH–i’ ÀCÀ`ð¬xÒ nr¼ÈÆÙM.ä-Ÿ½ËaLJ.£ –±–ë_ª’N{;º`ÕfÒs9`»â€Ëe„É2–^ƒØét†r2˜ô²¸œpJ  0õJKÅsÙm¸ä6 Áj Ù@Gv´Ù„øÐë]N®Ø¥.F#Ìc­0‡CN»]ªN&3ÆZÌ}Ì‚çI€H ˜À Ö|†_¼ù>›²'Œ .÷H€F{V¯nĪwVÃæAåº*1i^WAx |C.òòó€Ü½hX˜ÐGb.4=ŸÒ?¬DMƒ ÂIJ7íÇ–ÓcQ²qúmö°/uÿ»|ìŠZ‰“DzÃÔ]±É6a ôŒFÖßïĪôø0'ò×ïBæ†Jýdb~(šo:mÙ›¶!—{ëŽ#A i+é[òòq9|‘€”•8º3áEU}]ÔÿñÁ–!ko V%„ëyýçröfÍݳÒösÀ…–]P=q6Nÿæ½~2º§ìØ¿| Æ®/Áô>oÒ.ÔïZ„ •½TÆWÁ;ïcVü`û…Û_„ÓϾ2çÙNíBN¡güyjž^°›gyư³93óDÐÓ+üDæÏÕ h¯ÌÁâò³Z䞘0sˆ« ù/E«Ì/5 áæ—^ù„Ù=µ=…§såú…AóSÛáMXºítЛ÷a£w>r¶cKÎbT·§'»OnFÒ`‘å ÀY êÚÅ_ òÜ<þè}dN$0B ¸Üvœ½|®!,ÿp¤9„ÅcR$9£; Vâsù¢Ú_0ÄÉY“!Œî…~•Û~\4tæ÷—Ĉ=D9ú®»:S.*Þœ‹h·ú¬î†C´YÊ—£rÓb\rîÃîy‘ lÜ]—äúq(«yÈ^˜ŒFÕ¶f_Û…´U扒]‚7Ÿš·VÑrÛP½mÊÏ–bËñTìœ5tBÈÊ eòCôм=Þ´¹;Âqæ­o$¿—›Î¢)T™"’Ko±8nomż~Jåh*×5=‰?Á;ÿFÛi,Ï—~¹h;7#’žé°Ù`³·áð®bœŽŠB¢Aúx`p4a¹Ôô$¢èH2ÚP:5N/FZR-ÒEµËÕeÓ5+·–abŒ M´E/%}3ЬÝ>«!:Žê<WÓW¦ SD§§6<#‚š(Ha|ã3°H}m+ £KRŸÖûP®ä1‰ÑA×;Û肚qY({U­Î6ìz¦Õ2¥NTuNmZ$‚š(¤¯ÜŠÜôñp´À†M•ÈÛôCÔî¼Ý„î}‘äq á"pC4k~]S#b¡¿Æ °f¸’éÞéœhoi•‡ b6`ŽW¦ÁOŸê¥§Et|»å¡Èl/ªüñ¾¥ÊlÓŒ„±Ý8UÝ„nÁi²ŒGJj‚G=Мmª—‡/'.]hƒ[ò𪠻6œÕGwç=]—¨_¸d‡Ù2QT¤½ß´U9/¡Û`ÁD)^_iÞé­y§×_õ7·YTó¥)3‹ôee‚rÉ&=Sõ½¤X5=t½?Aúj| ¾KÔÙ/ØaãØõ¾-ï-õõ¸ì²àÇÓ“ä%À ›ôÛv»C3[1Yâ5óýUV¥k“¦ñøêR+ÎÛ®ÀmˆABjš˜8‰™IS.‰½N´”1)%É£oÀØñ‰4îô¸6{·ŒK+&¥Gï÷šÀ¶vÈ PSýeD¡ ­mŽe:ÝÛWHJ{K=š.¹:wºfÊbooA›MÆ´†EƯ2q‘ ¨1yÁæÂø€1®òQñíB$É£æßרõÆu[Ĥ 6Ô×·ÂeIÁô¤ØÛ.hyº¥"f³ÎÌ;º½×…+“:YˆÆX“É÷’f6ÏÂÎ:+VO[гÛJÑž±ñž ç4‹˜Ÿ¥jõ’vm¿„¦Z%;av© 6yÔÍ4jóK0×zËÚ›™÷xض­Ÿ5[cÄL#P(’€…×£|θ»]Þ$TC¡ål+ìbŽ¢úõø„Äà~,1UµJ?µËuÑÒ‡Rýå\üÙê[ý±Ðc¸ä¾p¶íßàpß#m-åó˜P t?諼b4£+K’hžÉx®o²Áð@,ÌcÆùú¤'gؤŸ9ä>áí«þÒ³î]ÚubRÓ&B ›p6¨~Ÿä1ÅsI_9‹lKúI[›Sî Úû´È½Îîø£Øå}[æ Y´yɉIcqéÔi\›jt´”55U¿Ö[X™‡ZÎ^|¥í„_¼Ìþ{¡ŠäBGËY«É#ZÆkjHû{“òþ:ÅÔçB[»ÜoÅØNoVoŸ‘¾ÔÖÚ¤iu]jkE›SL“zkï‰Qµ.DÙ½u54EK6vo¼€œ-§ež)Àؘ®~ïÛI 1øå³9ØNÎSH{Kµ&ˆÉ}s+¦ë™ àý8SŒCMW>ÏŠ+mò<¤¥IöV.à×$sÏô JkM"¤¡ —”ýÔv6Å Åúœ”½C_ó¤Šwåôj,- Ð2êu±ý‚*'°u÷*$irx ¶¾Ó…G—¢¾Mê"æ]Ýnõda]všö¼dI é"¬©”y_É ŒÒ6ò†3Òç è‡Àð k®µáÐ`⢢š~Z‚§Hàz ˆZø®ÌT¨àª¤b²¶¢jUš¼ô„Q{–óJM÷MQÓUñözQó ÷À"æ5»ÓQ='¥*Qy&)Î[ ¥†\'¦ ]b²‘™_®ÎøCLªŽ­BL÷äåm’ã)ØW»S{Áj;°AòiŸ•»1oéò°i¾øùåÖ@À)Š귶 ´ú2Êj0Ñ^/}Hëy!ÕÏ-;Š…ãmÈÏUúqëPûþ<ß ¾½¾K Ocå¾ 5Kwõ Qä[µgò\üH>‡–KŸî=f¤câèûòõôK;6I?÷/µoz÷k7ŠPã–/^*fPûpla<ðe;6="&êkoïØû€ì‹ÿ¢Ì¥ž´«‘¿´1+÷¡*±yKK‘˜2­gõ•ÍMÃ¥]™ò…¼× —/Ùe'wÃܲ y…g‘U&&/^=|)Ï–EyhM-’¯½–~Ç®EÍ«Å$)J„O]­:‡¬]è~+Û• $0hsÄ,yIQóLßeò#ðÒˆ· È]•ˆ³2w´Úœˆ7 e>òʃ “U‚ª\ ž_œçi»ËÚ|¥8ËîÆ®Ùý´¹Ì£ª/e‹™ÐBŸ™‡ƒf¶–í/®;|[û¿¼QBg1W·d‚Kúö¢Ì !},ÐT$È”+àÚé׳¬¶°ý¥¿2ø“°÷ÏB"†3cQ¯­UožÙÇý ÐôÌŸ´òª—v±ôñD"d‡¯4Õàó¨ï`æCôU3dP™ h)^{éL_W&¶Ö h¨­Âºy¡¬;|› Î]íß_‰ŠšZ9_ƒ½3uK7’W,ùòcô|ÂJ/@•¼$74Ô (]^Kå ™Mœέ­EUY®<€–‡õÔUeÃ(6ç«• FÔƒ÷©tjµt%c¬Þߣ%U[³$õ³Èß^»í¸&¨AJÞÉN Ÿf@½¸yçP_Yw-Ÿ*þKQ*öe˜¨Þwúc°zðßZQ#}¬5ïÉ+#Pž·í†$lÎ’~jû9ZåAYNœ}KôÙEh˜./ˆFãxyA-QÒáÇѹèþe¹6f2Š*P«ÆŒêï£Ë‡`WB‚E=kK/ØwTëÛe+ePIè èï%?'ïyÕhÄÆ*×X=EnúY¾&¨É(Ø«ç!ã²@)|eÕwµ¿"¨’±Y–+µ“¯´j¬VeÇû(AMVA *ªŽbb×/uAMF‘>æ¥ERO¥]ç„5u¡Æ¨²Ú/Ìpþ欘 ÈËèS©Ž]Užè8ù#‚›˜K”ì«@Õ,‡.¨‘/Û>fé§µõ2ϨlMxÔG™$æ7 ÖT•™œmbb ‚šqYE8ªæ+a[”1N¦ž|T^ˆá™â¨KÄŠ¤ßŽJ€§d[}¶¹§ýzk>iÆàiV½†>ÚJΪx­§šÐ.¦íííò_´¥NÀ"Ñ*Pý);MÚT4L*ŸÖ5¹%z¬©Ú‹ éÊTä”â)óë3šÏ”h}_úÃÞ¨¤LVl  ۞Ů]»üÿ·lÁñ½ã ÈBòÖÌXb2P&ó½wUeë$çËØ°º}Üú-¯WÂx¼W"¨ù~ÖF쫨ÂóEÙZ¿­ö`´×ÖX­ÏP¬zO;Yh ýß»dj8[© jÒe¬«zÔÖìñªÜ•¶—ãJ|–ìWA 9ˆ ¯Fê•-j[gËEP#m¥úŽº¦ÁsÍåã—¤åT¦zæJP»÷¨º÷yÆŸÜëZUÛÉ/ÏA¤±®¬JÏ·ª )R÷Êç·È=hÛ¿ZÔdmÞ§§Ú£zûWnX–`¹žÊQ‚o©þà™Tžj.Týá¸h°ÆgU¡FòЫ"}ªîCŸšv¹ú#Bèz›L©Ÿ`[;¯•­½Ë5aß+F@IDATÀ}[4¢T0ŠÃ`‘™b¬H”:Og.üæšwÆ÷EJ’L@íJÛD´º.uiÏ ò6`{i)¶"gÎ#8àé£Z>ž?ΖJŸó§¢õ½êäÀþ¿É—g•(H³ŠðH„>!¢9IÕEÝ'bDsL.ªŒÓ7GÃîyšÆŒž´‡·ÉX–0K4«fm,ÒúpÞœ©X¾z9f?ºT›û7ïÌÐ8žBYAšJ ‘9ó,ßrX‡md-]þ! @Ã,¬¹ŠDŒŸü8&ܘ-·I€†„€|©<|@=}åbݼ$ýB/óä° w¥¼8ºÑ´ÿ€<àÉw±·¶ªÏlF2Ö‰0FJÐtbU!A>“ɃeÙºYž•BLbj!äåÏvEàäá,Æ,_“¢L¢&­lä!X®Uÿv½¤+ù8å3Óø-Ô„/hÕj-i«°UÞ@ºª7 S^V”6Ͼ­³ô˜0iª’0Ü)Ä$¢é8VO™ŠÇ—nÀ(¡€¼ØÛ)ÁÁÎ`36oDšg‰%ӏ騩ž£‰sWJ¿üˆI…°M:fÊÊyAŽ\ Þ/‘tãa1›`;Eâ+a¥¨Ó+Í3§S½@˜d ø#‘ÄÆ­’²Ä›eˈ‰aAƳÙb> @Ž%>¾PË2ðö¬/ãòÀ‘ ŒËÅÊY Z—· Ì*ت½ÜÆÜ6ÊØTìÇ'¦@Fý:O„ô‚ ¬š•*fRÃ8íDźéÚ˜wÉ $ޝ<Õ{¤i"žRã»úöb¨Î´V—C½\¦‰ÖH$cW]£Æëû%«Å¼È*_ƒ¿­iÜz1sºL˜^PƒªŠ ¤ÆEƒµÿ2éWƒ¿ž·si©¦·~*…KÄz1;ˆv«UcböÔ³ZÚÇ/H?P¥9UëXµ!È"hsG”5¢?ýµ~]ŠÅ9â uñbù¿ù…¥ºMÖ«PJ.[=Êÿ ä‰eXèq|l²$È‹}‘–÷[Ò—m¿<¬ieä–mFªÖ÷°$ÌÂn™¸• ] û*ƒRzPc¢²RL1¼ÿ««Q­>ýKˆ…êJ«$wóJ$yÌ -IóP±µ@„}ÂÌÝ‘”Wå­•-¥;WeÈêXbªóýyšÆEå‰*ØQ-TH]¤ý¡ ø£×DèÿÞmRâ ÕmëÅÌÇ.c#ëD@SQ±^2Æä¼ê!ã,ò«nlRб[ñê“^9ã’ùAÙjÉàí…*Í첕Hðh‚XÓæjc»éÒ¸:Z4Áà¸ì¿Ç¼$ip F1ëÙüΫX¹j. =6TþTú©ÌÙ?´Â-ã×=O­WóÚe1C ÷"oAcÊÞJ»»Ä´«ûªFS÷¢$e7ŽÖ|&YbÆj÷l-ãÀ?Q²’‘ÌÖ SM‰ÐãÒÛÅ·¿ûv`rêºÐ`ÄX%ÓSk|h ƒ˜‡5µÒôužCJ(ó¸„]yåÒƒ‡ÿNæ.™‹þ{°]ÚmBy—<·¼)/ñlõœärˆ¿šùs ºg¢hY)ß;*´·Š$É:ìŸ6Õ¡Ób>ª7!iÖ*“Z%0߆¥™31eùN4µ×Ö›I€HÀKànïÆpü~õI3%áìG'GòL“H@–òm?ñ)ÝVÚDHg)S yT8¥öR{V/šñ)¢)p*p‘ÙéAKÄ_† Ý.õ@æQèúãòÏò2ñ3-fÀÇû"Z(Ž´µ%H<¾XûªŸ´1·×—±€k¸yµöå3Q,½˜¾N„º ±V‹H0B´’hH¸ ¾d²§§"W”bÊËë᜵Ý-J89Z{ Ö/Q/sYâ£Â߇ÍñâGéJ56ÍϧÙz,ío”þb¥¶Õ‹û¼D} è1ô£‰âÄôcþ}Ï–çågܼíEÐw^´|2’DpâÓôh#MûŠ«Ç2Н˜$k=öo˜Ê  È‹¸Å(ÂÖ\QÓ(3d§˜BuáÐq™ž«ù‡° 8v³ð•”1j–´‹ÛØ”­LG^éiæåè‰IÄÊõë‘­a¨LÕ°ÿó]6å0Xš$çe>â=äû½,7Î0«qEÒæ¾D¾Á†êãÄug¶htyÂWöV¼*«ô´~°-O¿ïÑBqFŒâ7Ii_ØÕAñ¦„M)ö*rxl¼Òðͱ‚fB¤4³Â„Y¨þ&okÚ,ŸÚ×ôA”7#Û#ÀTe‡ÁYÏZ¥«¾… ÎéHênÓLQ²ŠX˜²‡?Ô÷½Ëœ˜•)§Qz¶y9åÚå1ßÏÀúçåcFØÄäÞ˜8WïÇü¼J]ÈæxÌ1ºÍsR4~TP¾…% Ùó¼-­íŠœè‡¢¹#Ûb’¨îÛ¸\ŽÌGô2é1ô¿Dà¸Pùõ fñ‘cEõþMÈ«ô T”ñzÖZl]à¨zaz2/9%ßÖzÝÉ#*t»þû¶ véÀ=¶³¸â’þâËÄ­Í)J¸æ6ÆcgcƒV^ïKR6öæS.ñ$]ßë‚Ì+ØLZÿß‚ÚË%šº‹Å&sÜ*qPlp)™šøñôRÃ.Ù±ˆðjàyÒ›û@¿.´ÓÚbÑl’ ´¡|+Ó‰ùê¦-b?'Zhû*6ê~€DX¯™¦—çã—é šb=£h=Š®ÆYš¶Òœ<ä/þ‰+ÅqzcU¿ŽI€îdÃ*¬ùõ±űp’ ZÍÜÉX÷á'`³©·¿€'IùêÖ.jÆ–ñçwòe0äñí+ýM;Þã¯ùÓüJܹ]òR¡™‰ì~U{X |ÜscÆ{ÒÚñOš F¥Ö²¥í²Êƒ×Ahÿ9ðìíLÀü碰¯œÈˆÏ‚–Vù ›¦;³WgC/Ûå¬WMÛKÄ!éÏg |C9ZíéèzK^dÄ/‹ï×Ó·E,-â[%OlbbÒsQ²^œ7ćŸAÞO{±ÊAjÃÜ} /}1üÝv¥ŸÜè¾Azq'Ú+ÞàŸ4säER*€Í%ë1^¾b쇑)_¥½50%¥‰ÆÂOQY £/k¦ ›=ÂHÆ®’aH͈¤ìÍh˜»NL.‰g•V¢tCšÄÄd£ùЀeò– ßPêNÔÿTê+/ÈÔ)—ê娬$ ï22š˜JÞÖLâ˜ZN÷&y›¼ù Í®IL%8ˆ`¶Š&’Åâ¿B¶7nnÑ|k(3µ‰¢‰¨‚¯èÞ˜!ŸÚu©“QÍ!|ð{··…½ þw Zÿ“·ÓÖŽ.ƒhÃxߤ²Ž´¼J˜&Z,!A²¢\Vع`G´ôc¥E1/)BèÝ»Œdï<†¹âø’ôÛ õ‡PZY 9Õ(¨¨ß?%QÛyÑž£›ça}J<Æ[ ¨~Füô¨~ç ¢eÓ× Ó#ɲ9¤­üÒ (§û6{”h¶è‰Äd dáxѬ hSiw“gE$oVÚ¯CúÏñÏ%c ws R”¶›ÁŽg2óBûQÐ…;÷Zñ°ä}VßוKÚ=:+ ]¯ÿ¾-Zˆé‰ˆs¦ ý /íbÞ'¹ p“œQÜMÅ™³Z“EòÌA´yÅ+IÓ^|ý~­¨óR•”ËlbÖ¨ÂåÒ< ºÕJSú¾®ÏCOÜkÓ vöùŸpµË~õd” “S}ö†(3¢äe%(È‹s`pÈK§ˆRÄŸÈf¤Ëê@Ú˳G dðúùáí\ÑX@å6lÚ´M„¹Hñ¼E2vóSÛ.Û!L2 §®ˆož„$ÌÊ^%¾atv)ode N5¤îÚé`S¥Ør`“ÎMLcRÄìäÏç‰s‰.YqF4õyGÍ?FT>³•íÞ*0¯ÈÚ\-»]áŠÿB{“f®$Ódø®­$¦ÃÝ«ýäXŒhsiAdAFyW¾EÊ·U½ÐÚ~Y©½4[D0gœ2‰y«ºM¿Nû+SßzKÛŠñ¶se¸¨×æÀ,ÔJ€ZÞ§Û®µ£4g1æ?Sí1íSswÄåU)jÏ€¤-©X'MZ½úiñoÖ*`YŸ"~Óvx5¦LÝ‚+"dJf¶˜6ÖìÍÕÒµFõ4»½’³/š£Øq¹{±y¡húˆPD-ìeWãÖ(Èë»,FYùI‰£*ÅwV`Oh-ÍÄâEO‰+r‹˜uJ?ŸR&k@޶céò¿C»;4µ*˜@fì| E›P™™DƒG›µ þYKõaoUBK(æÌ²š“òñ¸jÒ¥&]Sk¼Gk0¢ûvhâ¾#&³ôÑ|;}Öî;æü7q.Ï*))â¾¼½‹Åû–êÀþå¹½¨ZŠ`zâXo}¨? àe®N´ø’Ò6,é[QVVæÿ¿· ?–º‰w«O¶ë™“‚s‘vW+M‰ F9þnØ,¨Qq£ôÉÁ~!x޾ðëzORþ¶T+—ÚµSgf"Ë)I³Dó¹¥;œî3÷I€H@'p×p¸rî˜æXxöCÊ À𯆚ó™³X´h Nµ´ é¸|ußP­}TV“ÊÊL’yé3ó±ëÐ)Yø86ÍV++ˆÂF±÷> T@õ€-™-Å»pøT Ó%×TfbËS²,x lYY„‰‰ñ²\¥ Û•SMõR)¾5â“jþkpºÛOyâz¥ø`;Pqx~ä0Š©Ý,yyRbKÄcå6ñm4s*žÝu*è%V9Ë^¾ëZ¤ßµZ„—µÉÒ¼÷Þx<•­„’]"€Ì@ºo5ŸpŒ ¸G=_GUb»ê·-MØ¿i66T«W¡úX½•… ƒêœfdüŒeª“³ÇÅAC‹r2»x»ž°ä/£I3‡X4s&fdöÕW—Û”¢x×~ÏžqÒ«8¢á/á2¶ИœúÇMÈԜъ°áÐY_ZÓŸÖâ)¦ô•~SISÂc·W~j×(K «ñ_8?Än‹hDí/Þ¦}U¶D‹ÉAeÒêìMûËvô®»vêr1¶KÝuç¸[0ê#bz¥„ub†µY_ &!}¡6§mÊœï)Ë)l™Ÿ£9õLŒ~æÐóŒ ÍïÕýy´n꥽Dcùã…ziå=,¨ìž:DÒVž¨bŸ&oüê[¥MMIX¯c_.Å ñ[qª¾ ÕÒ†9²ªÒ*Y%oò&1 QókkéR­ï×7 ïMj~•~*>_¼}PeÐJ00 KªîG¦uûb}n—1²kõ3Ú=c†ô#­©{ÍÝ17Ф¼ZBþ˜¶P—?h‰§{™‡…DÄ÷Eø)%Ÿ/÷ÇÓjÊýo»hxª­ ¼ô–í*ß‚]û£å÷ºó§Ë²¿_õó¦Sr¿ô¬ÔzgE3jÀ`NX JãmÇ¢-2FåÞ¨æ­üj96}ħRúª,­\Ï×ïÛ-õ0_VBT¼øYÒgã9T½e»~Ÿ?µ³×W¬ê8]­Xs¦ŸJœý‡Nûœ¥&”˜%~¶„ó†E«µtêlÑÿ‹@$Uͧ‘Ü·”m]\á?hIIׄç•Ï?- óxFV´SšqÙÊGS|šæÛNõ¯Õ»Ž£Iúö.YíI­4“-*Þê{g˜>1DëWùxJJ’%ؽÿE€œ0É*ó‚82Ö—·dNò—3­À#Nqë§M†o÷%?t‡<ÿû‘òF?VLAåÖs¹2ÏwRm¥:aš®*ãl÷8Ð_,÷`]‰8•n<®ù »øU`¸M$pÇ&3¨Ïð ͱðÓ˜pßϘH`X X¦ ì+òÄnº0O°è—Ý;×iþ)`NgŠ[±iùTn/ô-M™%«ÔäzuÀÕ×Ùhù$ô/BÞïBFk*²ÆíGåéJlsŒÇ§'aUÍ>`ƒ¬À"«è9Ë{„¬Ð²Y–æl;°\;–µu§¨4ë +;øôã‹P½i æ¦íFBHšAàÎB@LRÅcì§d%˜ÓÈßp¶Ü鲞ˆFËÃ?l!¾#êè»·fi…Mœ—-Ÿ²·ÃºR÷ËâCÒ·HßX‚ÓËjMÒoµoÊbN´r¥¥¥Õ²ZËGxª6EsÔéKC6¼_PåU7ð°¶­qÞ©~½ßÍ¢²¯X\(+™ä+aƒˆ-Ó3xª­¢äWv—eiï¾5E9*•¯Ò•åpŠöZšzÇÇÁúãÈý蔟.Ežª€¼e­ÏE“¬psöÀ«¸”›¦ù‹2ZR´•nÊ/Ã\yIòK¿cWi³„ËtT•tÉ p¥(-”·*OHÉ-ÁV™GŒ®Êô!¢ ÁsLïº{Ó¬–º{Ãèq‰Èš7Ùs§ûM&Ôœ&p7åJYòõ¨šyH9æyxr´G|é/C´yÃB¬Ü½ §e©øÒ yZš‰Ù¹Èh­Äãé{T+ùÅ6½Ûjú_è>ƒ¼}@/˜þ×3^óGsVVÊqŠФUØjÞ„ å °õ-R¬¼µ{ó³ºj£ ˜7=ré×­ž¾Ÿ(¦3›7ú—/L<%¸ÿ ‹‚c»aXž'Î~ýs{†¬8¸Îãä5üý`àò*ÍpÁ,~dRzdå&ëJŸX¸xè5Î2¾ÕUúhR-⫬Ë-?ñQ¥IKä ê+¯þT,r”T@…?•È8­,߆ñiµØ¸3ÏÈJ‡åž¾“ž…•1Mb>u>´¡öq•Fð˜”#ã_VÚ\·a Š«eŒznŽã2Öc§|¸PAÍ2?¨U·¼÷mĤ ¨$¼™°Ñš’Üzä—+ŸQÚ¬…tYDÀp¼Õ­â›Ê6«D›NVZl­<òíN¤e(‰Qp0Åg‹£h;rD¸[˜§ÏKÚóÂî\MèÒvxÛÀ÷m¯0Å£Ù{É;yy³’•í¶ñ™]( õ±¤>Ú¬Û½©Ú³€˜ÊêWћ䙤²²`—RÄ×ÓæU©ÞT´_•´öñ'èhß;QÑþù9ÒyROMiÉV "›ˆ/x¦ºêÒbß3Ž7÷•‰éšîì7+àöŒSß=*1 %[WiL]⳪Ò&?ûžBZ¼Å{9I€H "Q]]]}Ü:#º>l¤¯~wB^áñ;1óûÂÆáA¸^×®]èQ£p×]w‰YM>þøcíWm«ÿ*x{zzššŠ¯¿þ_|ñ…|µìÁèÑ£¯7ë›r»oæ^åÑì©èjYÍpA™¨W ƒZª2\„ë<¦V¦Pùäë±þ5ë:âe$@@™Þ<’³ëÄ·„z w*ûñ_£¯ÚQ6í§¶h>A6W:t ŽÓ{Oï·’žg¼hûòÊ.ýÞ×F¾¯V›Òǧ+"Q«Póƒo<˾ÊÊ[ŸHÊx=cWå«ì½ç‘¡(S$åöÆìœ6p›ëó¨J_­‚7ü!‚þ¡µ«´óϯ³Ðû˜. ÄÜ~=嵟ÂÔÌBdl­ÂÆ´áx¡l»J|Lî€ùFõ5¥!5¨n!,ÔhQ÷ÝðóЧýÕ¼ÓÇ};¨†°Õ¯÷ÍA‘ûÛ‰ ßõwy„çôñÙwÝ|s‰Ì$ƒâaþ*Ú ›“BÚf…dT F ·Ä–ñuf4,š5÷<†ââɸ 5×Ù.¼Œ®ƒ€þ°Óßqˆ…4ÞBª%dûË׿$p=¼N}MÊyD¯à?gÛZñS1!Ab~8ˆ÷¼ÞýVÛï•þ7ß•%u#y »ŽŒ”0!H  ûƒÍª7ƒHŠ”g¯ †¢L½’ìww°sZïú†¶ùð½4†¯HýCkס ÌÂ+°dÞƒ(¯ËÞ‚ú6.¼U(xR°P|š Ol»J|L5W}mÐAQ¹(0à4"hÿÀ BØòz_Z×{/ˆ6ŸýÍ%eA¤6'…´M…c €À°k€û0†‚šÐó4 @$̦Þzöþ«l§óÅ”D9 NDÉÆYý¼ù¯á À-BÀy ……âÏDÂß–­Ü‡Ú-R|ƒH€H€†“À°˜A g™6 Ð Š}€HÀOàzÍü)p‹Hàæ¸.ó¢›W\æL$@$0‚ Ð j7‹N$@$0ÒÜuþ‘F…å%‘BàºÌ‹FJåXN  ø†méîoP&^J$@$@$@$@$@$@$@w, kîØ¦gÅI€H€H€H€H€H€H€nEÖÜŠ­Â2‘ ܱ(¬¹c›ž'      ¸ PXs+¶ ËD$@$@$@$@$@$@$pÇ °æŽmzVœH€H€H€H€H€H€HàV$@aÍ­Ø*, ÀK€Âš;¶éYq       [‘ÀÝ·b¡X& ?O?ýÔ¿Ã-      AxðÁ}Íͼ€Âš›IŸy“@FÚ¤A•…H€H€H€H€H€H 4ƒêO‘ À&@aÍ&ÎüH€H€H€H€H€H€H€H Öô‡§H€H€H€H€H€H€H€HàF °æFg~$@$@$@$@$@$@$@$Ð kúÃS$@$@$@$@$@$@$@$p£ PXs£‰3?       臅5ýÀá)       ¸Ñ(¬¹ÑÄ™ ôC€Âš~àð ÜhÖÜhâÌH€H€H€H€H€H€H€ú!@aM?pxŠH€H€H€H€H€H€H€n4»ot†ÌH`dè8߀Nw,¦&ÇÝBq¢¹¡æIIˆ3EE\®Î‹ hnwÀhž„So¥úD\F¼eDÖo©ñãì@Cs'b“§ jÜÜxäNœo>7Œ!YG›­˜7&äøÐ24Hb'!9Î4ôÉ÷“bGsÑqHžËÙÑŒó3’e>¥ƒšÂðqvœ‡tG$O„> «>{Þ‰Iýž"IÊW;;àpº`4™`‰¨ÝÂeÜyñ"Ø0÷¼~óp9át‡¦h’ò¨àr:e¬õ &˜¼MÒé°wÂå2Âk‘rxO "€l:¥ý݆8ÄÅ´þeõ^Þo}½‘øK$@#…5#°ÑXd¸y\hX³%]ù¨=×÷õ. «ËV,òÞÆ¹Å“"ʽ³n[[¡ÇM.ÄÃ"¬ñ?jF”#‘€Ÿ@D}ðÖ?®Î¬X[‚¼¨Åâ¿ xAò×êÖØ¶/-[[_¥™’o,À°ŠlTV¬@·Ì15Î1}wpǥϼàs„̹ /cÅŽn¼Ýp“Œ ªpeýf±;^ÂÚêâê¬ÃŠeCØg]hÞ±GÖख़q×]Ά}(:iÁë/Íìû>"}hÇ‚'PؑͳñöÁBL #pé¯0=5xìÉ‚PäÑ\ö–U8B’ßsæWH6v£â‘GPrV?`Î{Wúÿ¨ú>&í’s^Çëk¦Jý¥x­ób•Ôç˜{¯þË*ý=‚úæÃm i(¬i-Æò’ÀM&`ü®Àt/ 7¹AÙãðza>œ±æ Ãýí¸írÚŠ=µ‘<ÈåþÒå¹;”@„}ð–?F]§ÊäêÄù6; r<}ŒéÒÿ.v:Dåß jü4S“V'ïøˆ—ñ!f&WD=?FL/s¡nmL´ÛÝR”X$OI†VI#°jÉD4~ô˜Þøj\Æ&O‚u ²=m6’eÀ <^`q¶ãd˜żBÆÃÔIÁcÆÙy^Ìž:d<´ó“Œ¡/¼çn\~0»;qAÌŠþ§¼, fLHNö´i àí1qf™§Ä Ãc®L¢ÂPöèZ¸Å¬Ä\WåŹ§6o%OMò›Šh}Ê„d ÚkNâ¼Lަh‹˜ÞLñÇQ I¼††Ø»{ô¹Cš$„V?ùøæ ¸ÁÏËÁí?ÝW-·l©^?`è§\Á×êãÇh±Âà´¡¹­Cú¹Ü&%‹©×˜ ¨××/‚’Ðvš_{„r·ÃŽ–óçñÛîncbþåc’„vI>@bÀúªzªùÃ!—›1f‚ÌëÚ½Â%÷‚FÔvHºíçq^îƒæ¸„à~ •Zýé{¾¸ªÆUMâˆ×ùx˜¥Ì¾{ ÷úª”6‹5¯­™©Ýëbâ寓x¡ñ(쮇aèh“þf–¾é×òTý¨]î¿æxw†v<öÄoŠ¡¿æ1]î©v­¾S–N“ûrðüàM0Vîág\¶,““3ŠbfœßûP‹úBi!¦Ê¾Øba;=hxdÞÛW‡¼ä92‡õŸ†7/™¬QóÒ8‰d¹7ãªÿ„œ ¬Ô×âæcÆD6nóæ6 Ü"(¬¹E‚Å ë%ÐY·KvˆœÍêyy˜>3mûV`YYsP²fQS>²FÔ”¿ìċ˖ˆ($4¼ðn-2'˜€//âÅÿú¤&cV+ë1¬Dôû‰Öj¾Àý9rÛ´f&k‚ý„ ³g J6íñaóÎ2¬‘º–Øçç±vÎéËV¼- 0NBm­ÌiÒæÎæ2™*üé«­ópÃjèD»Ô7^4Kj%_‘uOHÆ´QP¢ò°öq “_@áÌ8í”ɬÔD ÷]ƒ?ª) ¤»¼Òù•ÿ˜w+LÞSWÊPp xùH1zŠf"p¶ïi¿e ¾ÎŠG±¤ÂŠü—_œ‡åcMï¦õ„¿$@$p‹àjP·hðX$)ã½ñò°©5É(Üó.ŽœÈ”|š Æ*êÖ'Μù3GP<Û ‡¨)W4;µ¤c=ò–·àܹs¨=X ³œyO¾¼«Ð°{…&¨™]ø6ÎÈy•Fá CÂUõt+Ùì{NÔä¿«Å9sâmÌ–(kŸC³S2ð˜Y(AMNáë8xäÜ-xN©P[sðníÿ-yŸÁÛ…³å ¸Ïí;¯¥«©ý+AMr^÷ j–Jû b¢'ÙÅyž÷ê/ =3 qDÕÿ\-ŠUÙ›¡C|«ÎÔbO^²–bñÁZÔLÐ~5AMr>Öž‘úÖjåŠ K^<%¯ ÃE ó|v,›ŒG䥧¤1VúñA$ÈK³·ÿ¨ƒ×¤¼ýÔ*ycY.’ðrŽ´kÇ4_õthùÞ´WÞxDø7C„ŽáƇó£2íåm¶|)Öú¶êªãÚªä ·žŽ6>zÌ(|÷„ÖG÷äOѪïè·¯çXå=ï”ÆO¯>Éø á)iÄ«ìEP“œóÞ=xKǶG6^ä²Þ±Zû®ˆ€Æ÷.hý¶çj–)AòqDú¶Œç#ojãÝWgsdùô—ƒŸŽàó=¯hÙÔËRûn±¶ÿJICŸãL íæ“ ¸ØÑ‹âXõâEÑf¨y ž(öfäLSs„ï,V‚+еv;‡RO«ˆ#Ö,®’³ã…=âgM¿½ CÙQ±§ŒãLíäI}!2jÏ„ÔHá¯ö„/ ÷u?ÑÑÊ}Dî'½ƒKÌ—”qjœ)è”y‚FÂUg~ùTm_Z†ºÎNœ,P‚ ðà{¾šf™00ÇÆ©Ë‚ÂÀy¸Ek©C,5–¬ÅŠ‚WPVò –=,{/X«Å“róû/¢QÊð²Ü£5ärÜ)šOªMêÚdÞó„ž«ÍØÛ,]ÊV‹Ïê=. íœô­e"p²ÊÇ’¢ûGy®ðôvíô@e¤¾ñK¢0'%/.Á#?˜Œ—Þ«C§z6a  B€ÂšÒP,& D ðàë˜#&;±c hÜ+ÂyÁ}1gLnµòƒÓ–>§%qô|‡ö«^KÌ9{0Ç£ mŠ›†eò¼i«úÿÙ{°8«3ïÿ‹—ÎhÊÄ× » ÛdFwohkˆ—ßM°â*ä2c!ëJÞÚP[+¤*Á–ÐW@mÀšàFRwëšÑ4¨¿„æ'Än0×%D…Ø·$íBÞU°.ènȾ:4q&þä÷=ç™?Ï ÃÌ@ 1ñ>¹Âóï<çÜçsÎyæ9÷sîû¥Oˆ~¸vðíÔQŠòü ã%Íš‚üªMÁI5£Œ£¦Ô0NaŽ“f\A–Š5j˜8€ž¾à‹\.¿–çgÓÏ@†:^âUN‚ÙPHåÊ)¸iŸš[¬—ý®nc@ÅëjFË®Íe4Ýr")ÎÏaÇxŸ£,[Û(‹2¹¢²ÕŒ¾`öñšÕ»~g΄3Y½zйÝÅ-¿àrpàTù1NF~j”Bg3úÂ^@yVÂipã_;ö`mæ,[½.S6?OÅaë¶cg`` ²È«©BŽocs.ÅÓà:}E™VVº¨TaôXj¹›UV€$}Æøì6šG fSÊ—ª|l¤5‘…a¿gN~®uâÌn¶EÖýÚÚÀ³ÑšDEø3lg¾0‘|‚í.þç²¹þ;N—‹~ABÿ·Í^K6¹üeQÛÑÜ”/6úœÕæDÙCyÖÖÑIµ ³Ð†dz¾ÖÐÿ˜~³9sʱI)R•¢=ì³¼TL4¿0ÀŸö ÿŒþŽÔ>ªRæAÕfÇLÊèH¦‰O¤E6ý‰õ¼°úOìN$³ìF3%À]e~¥‚3ÅØêýG÷xßa ÊþŸM°' c=gmèT Ø]ÈwFJ1˜‚/ž<†û˜(ËšŒ4ê#LY–âÛ€†ÖARÆ–Ê”ZþÆ«ö±Ø÷ÜPRsnÓ×׬ÀŽšaSÙ^yÓ:ýŒIà“Ô\"¥ì‹”†R¬6¯ç¬.*Õ›"(·T±d§¼ª=ç—oÑ›6o,D •TË®¿%õ-4uërÈ! „À™€˜A}‘kg dëàì¾°ü=úé#ä„÷Ĥ ̰̠'æ¥|¾ ,pdNIº’È$(Ꙧ:Ã7 ¿Z-»nL‚4r_órsÔ×À`°¨q©1ÚÔ/\Ž‚,ª9LÁšŠü|¯Sq¼=­œŽ}]£)’±ëêé§ÇØÏ¡ˆ@Ñ ±„æS“ÆNÂ1•"…ð2JB^œÖ„°8LȅüßÂvª‚Û£xóòëXŠtÓ=*^}Y`Ÿòj"aêxÐRr=6v3Å¥ØE¥š18‹œƒ39¤–dBÑÒl”ÎâL›Æv¸ó‹96péÁHq¶¯mGèIiépµ`CÁtÛÈ(ðüyQ0ßé?äÖ‚dø Æãœ)’±;ê‰ÝÆÜ<‘W˜l¡ñö—Üo„ôU5îT'5±i'¢™ÈJO fÂ=gvР*!Þ|ÌýÒó餞™…µÈm+A›‹¦Y¬.2óÊPV®æDŠ´ƒ¦+[ SN Bíšèvñ?¿ §ŽèkÊéèØGÈï ]Ì9XÍr˜XpF‹ ƒÚïQ§E9éúœÿ55Y£ œwÃG>9Ú]¼Ïåú÷ @S¿ý4õ í @s VÔù"Å#—?=ÓÖ™ê4±ÝØ“ô¬«®ÃýðfÓצäyëùª™9®Ï0ëÝZÑ"i¬òü—nŸ èŸ%„•™ù\ÑÈßvüM<Ï‹ñï6®xO ëîÞ!8è^Ohþ ‘ƒ¦ŠLÖ©ê—™X“Œ¸iœxòpæSq‘oJÀš‚¢ÚZ4__‚^úùÂÒ”ÀÅþöíZºáïr‚Ï#^MHYŠÝ›‡´ifúU†éßí|ßvЯ—éw{¼4ú÷ÔA±ì™»`#¾Mà>¬F¼#üXÄ_^+ŸO1dõ¦ÅÇTˆåÌÎ+GW^)º÷¡ d#V¹Æ3{ ! „ÀY' Êš³^Ó#@?4[ÛŸ¢¢f`Z2PJŸßS¤þ«à°;pOÎ÷áô ˆ§%SIt|™æª vÓÕõ~M“‡¦Í…°V{Pflɰºµ%Óâ¯ØLwÝ2^†‚W¼øÄxwœ²çUas1±#ã<ó´Ñi/½³ò˜¾gìÆ`H]Ô¯¦ÊÄ`Û&¤ò´14RWÔ¾+R¨y9lS©æéËøc0‰^ºÐ䔸£#Ÿhy(B0øø™å ^”½É˜•JeŠr²ÔÝN«:åÌQΟ#‹ö˼fItê{R"·\ydC×7¢{0Ç·uÒÖ Ö¤t3Ú‚¹5u×/ÓK×Ús×bó:+NMÁPs1MÃj9A‡´$ÆžÓÂDøOÿ ¿M)‰›F«q÷—䯄'¥UÏÓãA΄£¿Ýâ¨Ùpþw>úåDŸÖ”LÔ¶v¡b°}tìÚÞ¼®–µ¦‰bMÈ`Ï/ŸÚ&9HII žâ~Um–ÑD¦W)b|U•WÕ„B>\Â}ö+Ù^Ô#-!Ñ7³.˜T`Ï7xV3~(Hà4¼ÃdÝ$©3g|EÉÇ{tl»‹û¹lªÿ œa>ˆ´—\n Ox4Q›•‚J›Q} ž×ñ<_ Úšpˆ6Ö;=‹#Iýލúó‡Xåup6ãö÷«›œþ»Xt8Lg-)©NΦ žo/îçÅx ð¼õŠt={«—¾iÌaøðëšo üëði/¶iE:¢iÑž~Ôæ;õ¥XâÉC;¾vsÖhš©]D¶¬èî¾A6mÿì67Ú·t²þó5Ƈ] gs¶JõCÅÆ~i¡þ:lWOeH0xðzcCàpRùÐ7ÔÄŸjVÕdnh……~72sòQ¾¥ÏÐçGGâFï3LÓ± ?AéíiFXqVg*ýÓмªÛ4ž÷?ûl}.—4s¥£@qÇݱ¦¤é4\õm>YŒ¨½mÆ,u4¹|&ð\6Õÿ¸‚F¸09¹¨¶Å˜1äKóØ¡í+%•йIµ‹1²Åù|MPÔ¶Î!S ÇÐLg(jÖ›ÒÕ˜CÌò&RùOÛ1×vôšL¨ŽuÖcÕêhî>ëGš=sj?Þç•LaŠ˜”.6dàR݃ ntì`ÙhŽK3>¥lû±áÔ¿ê™çQÆÆÜV½­ý¡ žÀíá;1óðbOñ*¯ª áá>zD×wæâÔàó+˹ŽS9YÈ•ÂòñôîÀ>¯·óq¢”§)ô73:ØŽ-ÝüÈR0/Ž4¬¸mû.45щµïÿ3M›µ¿;{^š¶m¦ž4Y­ñ05„ìíøE«¦ž¨j³òÑÔ¬Ž‡O…€_(¢¬ùBUÇé ó«®_áW]»N?¡I¦ òÞ{øÿäÝrÛTHϽKÏ´©ä*;Z;8s¡Wh‡Á™iÉ1²ù”³K’Gûnõâ\²bött£[9óäKžü §fÁäÒlA9XÍ/؈ÖnÆißtÎ9šJ_0aoÖ¾\m9&s@U¹ŒÎþZ¹Ôi7vl,Ò+B,àÀyJ˾¼âÙøUWó‹Ë5³¾Ä×7·¢£}6ÜTÀÒq¢FÕšq¿öÇ“‡Ä‰LÀJSJ³?Wߟ@kÈ@Y9-©of;îÆžúµ(Q~(f•Ñ/‰¯µ\œ†5Ê+êð°þœûhª5 ´¹=“Ô©ö×ÝíK±¾e˜_¸éãâà@daã/7ÇÓ“_Ñ‹®¿9ùÍ!å5 pºýEµo[f®îs«±¶^•Ùès9¸ò‡Éå3ÉgÁ m•(Ú¸ƒK‰ÓQiëvlT¾1¨ªU5çémÆõäRDåmÌ`1f$¼ÞÍ!°•+½)‡Ó-ëQàK»õ¥XÁÑYBf&’Mæã¦kË4ÒhÀŠµÛµ|m;h2Q×¼e’ùœÞs9˜ý¸{“K?¥éÜ}YI½~†w´P‘¡Wr ˜f²“kc%œÈóÕµ~™~«¾Y¿v•vîë,-û ŽYÞÖpµ%ί)*ª”ï¦õ-lk¹Ú¹nçpãFÔo‰³óÂ;z|Ï CØ€êúí̇mqLHAžöåÂj¶+õ»×¼±XüH*,€“ñ[ë´ŸG©ò%G¿S>ßAhçÖž^¸˜úz¬<¬È¡/,ÅcÕ²uhiï@;ë{ÅêZ=»ÇpÒmdäá’á|"Òt295ÓåxaôÊÿ‰'n¸³/å/âg#øè?ºñ®zté–àáâ¿Å×ì—Áò™—׺ð”ëß5`ííOò ÄlX.äj¿‹—wßÿ9§êø'|=ùkbÏ4œå½DõU.’²±‹+¬lXW‰†ÊuƾH•Ö6Ñ^žÜOùÅl4?d‡Õ ¼-»ÏónÕª×Gn2[[ÐeO Äá"R\©ÃÅ%”ZƒÁž…Ú͵z \ã}’ò…„”sµ¬çËSC¥V†¨ËY\¹ª¶Àɽˆo¡*Êø!Áªåñù e¼±LüF,} Œ"vßN3Ûµ‰KÙÒÙm]¥v2ª"r…Ò|gàÙ™œ¥eøXƒÞö6¬[ÿúKsáퟕ•ËeÊêhºdämÏ*ÕŽ ÕÀÞÒW/ÔÁIÇÂ)þ“zÞ¬X•ÉZ¹¼sÛ_›ŠC3²2 øÕÛUÞŽ5odé~g–VŸsºM…µÁ¤8úJC­:5p¥Ñ~ÕñØ~9ùþlߦ4è'¦Sst /7-mÝtà­z¼)Îú¥*çÄžV,®¢SS¬åí Xç{l¨çF}S­{”à ÅÂTÁJÂ`ÿÖ—õ«=¾d”ðn¸é¨4»|øäC¥+˜¶j/›kÕl"?“Œ§ƒ?}#õL£¾G²¸ÄrgK’}³¹båétZÏå± Ã€ g£(•œbÊ¥M ƒñõ]dŸË'^e°ƒaë"Sûïš\»ðËã߯õ|U¯üÊãÐægp}Õgú“ ÙÆ*oÊÒ*4qJUIuKà7jÔ‘‡m[*|Ï æ¯)+›Î²ké<÷™CÏ‹.*’®» ™õœÄ6ãNÍÁÒÐDt‚it„½ihWÅjDI§‘‡#—3IÊù“To`gH*Ä_Y•o˜]U¯SÙØBÅr¨#ÞñžGQó`–)K+ðŒ×†ÕÕ.l\ï‚õ_ÃúVNºÁ×3ÒìS™Ø´Ãx¿h¬,ñæ l4gÌæòê-@¤àßâ©p Ç#k¬òv¿„÷––áùÒ|¤q! „À¹F áøñãã Ù¦¤,½¯Ö£á很iÍ[ùÖÝp•qíD/~|_>‰“'¯Y ¼½s¼«óe7!ͬ \9ÿwh~p|Ó§?§ùËê嘎áã.ÜóÔËXwïÃøÚŦYÆ~7=]‡µÅ»w9<æ0úGl}ì{xÅ|δ¯|س±iÊ™‰ðô7ã:~U®àÏJɨV÷¯¶nÔ¾Kjv¿M™¡Ï²ñr6Ú_°ÍêcÕ†£5Æñ÷|<ýgÜ›C.ÄÛ_Bn ;¤åyˆ£_†&=‰g§#øU³Ö)eNɘ¶Ûx¸L¾>}iD}>M2ŸiÆÄ#×§½(¹n°&)T”2ûChý*¤ñ=¯Ãï ?Ž«ìã°í߃k¸\{ÙóûQ”f|Ç,/Û›jù2Ën”?ÈGOæy1NÙÌyö~Ì< *ŸÓé{qÕåéF×]TYc–÷´…„€g…À´Î¬9ÖñKŸ¢&w?t7®™}) yö¾_6ìÀ‘⥯>ŽÛ”v…_T.V²îÆC+Sqê„Ö± \4ƒ÷ÞúM5†qgàdßËxøéŒþV¦^#úE¸ôKª¨éè_QCd·ß°Ø§¨AÇÁÆHÊräι¸ôøî­_ (jþ÷;Oá_/½w\5 öŨÊ9Žoø5Çßÿ5þqðr¬[¸–„ÙX¾üV¼òrduòa£dZàX¨CÙ9;¬QeñIdãå6ž8csSKžÆ3FÚW‡µcðñæÇ0Á §Ïl‚Jô¨üN}mlËááSN×?H“‚-t2‹L.ñ§¢F¥Þþôqx§}<¹¾)Ûpy#ʼnu.ž4â‰36ŸI”“?ŠŽvl9ôO[éOñĉ ÷´?c& —ßP,™&×.Æ>V>úŽò{»ÑX¿]_NáìÎn\@IDAT!~Èu¶4F‰ë÷&ô¾àQxùõqðrü{1e?©qcÆÌÃà1îýq^ˆ«.ãLkühqȳ¼ã§.W„€_dÓ¨¬ù;ž¥MÂåXûh9Ò•žF‡¸"m1|è$J~­-¿¥²&Û—pÆÄ¥J1Ié2#)ï”ý½?s¦3¨¡‰=ùK°óÞ¾µ”u©1›ÄûÑë¨~])Xf#ç[´éÈ6Û¸w¤÷·©k!û*Æ®úË\ƒA…ð¢k öqoÁÜÝÈ¡§¿Ëí_ãQdeºCÉ$ÊEBÂépÞÆåFo;ÝTäþs•€a¦Yú¶uXß8Ì‹™Ø\e˜ºDŽ)g…€ˆD ÃgÚéÚé\{-\ȤÉl¦ÙÜæ‹$¤È"„€B` L›²æÄ»8BAg.\iRÔ%¿hö"i”°§NàØÇ'bϼâ ̤ÃaP*ƒZëñËO®aÜ æäÉXz÷ª/­/?Ÿh[ev-tv=Cÿÿ s×âéò[0ÛWóÿÖÓ„KY«gÑXóÑßÅGòOT5ò™‘¬vbhì~ð±RÛŒñ~c\4ý%“)ªì ! &I`æ7“ÌInB@! „€gŠÀ´)k><¢æÕhW4²œx÷eT6ë ¿ÇŸ¼C›1îC?CW§V+„Ä»üË‘öeõB"òÁhŒY.ꮬ›ÇÆ eº¤ÂÞ>X‡‡^ïFÅÝkõ¯ßÓ#²ð——³màk"\!jDÇâ2Æ)ôuGˆG¦8’‘(B@! „€B@! ¾T¦MYsży@ë\j;f¤-Çkçqŧ qÉE'qàé§¹4tpfŠ9óÛeøùiÁ›d/.N»ˆf uícAEÍÈï±éÉ2íF%ÞõѬ‰ËrÛ¿®¹…sfÞ|ü?ÀÌKgqÉn:¾–Ë£¾iRä|æ7ŒŠ,¢’I‚B@! „€B@!01Ó¦¬Ñ«7Q–;;±ò'7øT23pUú·|~ŒNeõe÷ <±ú7öד¿EYs[˜¼7ñëXÿÀ«X¯ÏxÑþ›_cäk³éHx¾[¾w^h1üÙpyîç^x Ëï}ßä²ÞßÌÙ=ÿƒ×t«ñ¢ã åÿfü d’ „€B@! „€B@LŒÀ‹ì‹fga¹²–ù`'¶¾þÇ17ö¾ô4:}îPÌ/á²Ü&NàÊ?»2ÊM—#ñ„ñ¯Ü„º·GWà ~E Ž£}ï÷èçæ]üx÷+øP×UPQ£×?Iu%ºLÑï•«B@! „€B@! ¾¬¦mfš2sSùZ¼qÿÙñ0~ܵËoø&fžF׫;ÑùOSc¬À­ù†7cûIξ19Vû3¿un»Æ·Äô—µ¶¢”[-‘í°ÏÁÀðûbuâõ7E8o:uô~äÿËUÈš«4l^tí\Lx¯ wÕ7qÉî%¸Ì:‚;¬T8у’E–íŽÎH® ! „€B@! „€ˆD`•5ÌîÒtü¯ÇÀ‹[·â_úàYþ÷‡w¬Å¢‹ÿ /ûÏø·Ÿ ó@0žÿ,úfâ¦k GÄsÜùo2'€ãžœ{ð`sEàxâ;ïRI3¾¦ë辸“T²HB@! „€B@! &N áøñ㣿mwœà²Ýü¯Ö‡º4éÒ0·Â“HOn‰HàW]¿Bó¡—"^;S'ÿ.ûïpsúßL[v'ØŽfΜ‰ .¸ xë­·ôVí«ÿ*ø·£££X¸p!>ÿüsœ3§¬9ûeýÒH 6×\y ¶¶oLJÍÔ£P>j”é“,×=õlß}w|Ó´©ÏMRB@! „€B@œ®ºêªsªP¢¬9§ª+~a•ÒäÑ‚GÑ5Ð…÷þó=ü~èèîÇIïÉø‰óË%Z1£–çV«>‰3á(°NóÒW¿úÕÓLAnB@! „€B@s‰€(kÎ¥Úš„¬J‰"Š”I€“[„€B@! „€B@œ%œ¥|%[! „€B@! „€B@DYŠœB@! „€B@! „ÀÙ" Êš³E^òB@! „€B@! „@¢¬‰EN ! „€B@! „€Bàl8CÊšñ›_nÆK‡­rJ¾B@! „€B@! „€8'œe͉Þß`g×|åÒçR! „€B@! „€g‹ÀQÖü¶µ¸ðÛøëÙ¢¬9[-ù ! „€B@! „€çéWÖœ8Œ—óVÝQÕœB¤B@! „€B@! ÎiWÖ|x°Ÿ$\Ž¥×$½RJÎB@! „€B@! „€8GL³²æ^}±ȼ i#DDL! „€B@! „€B@œEÓª¬9õn7:Y¸;nšw‹(Y !0•ÄÁCS™¤¤õ¥$àÁÀ¡Cò|1 ?pè  1„ûä0~ñÓ_àÐDœéƒâÆ¡ƒ10ümaw¡¾¡°³r(„€B@©!0­Êšß¶¼DÇ‹9[¦ÕLMuI*Bàlðààe(+=÷ÙEò?· |:€––âµ£þ–äÁ¡_܇‡÷}lç?)ÃøZœí|zeïûÍVì|möÄé¨òé•}B{ðX¹ï=çmgPö¡ƒ¸ï;cÀ¤G:øS”ÇÛFâ,’DB@! „€Àô)kNôb' §®¼— n! Î'‹2ÇËyS")ÈÙ"à,¦†ôþÁ7qðøÙ’&4ßD';Ÿ>Ù‡°óçï`ν߅Ã*ãTMŸì”pÔ;Û„ÃÜ(b$q¦d÷x‡ðæûo¤«Õ’ˆÑY6yƨ#¹,„€B@LŽÀ…“»-ö]vµhÇÂw_sEìÈCIpõaÀ›ŒtpèÀ¼ïIÆß,™5®VÓôaÄ $Ï›…©É¾|Üè£)“}^:N} ¯q#11Žù ‘žl3ÉâÆae–04K¢‹æ† ®ˆôzG”É/s™Fª? ÷MINa~ú :€ƒG=Xxë$S8ÏðÞæ;ì~ÆnU[>rCÃb;ÿ ’)ß|ÝŸ&+;³ð°]½óÍ~ÜìWTT¤Ž•Á}ø5ìeÔG¥J†£^Ì›? GýýÒîÀÂEóaó á ûÊwöät\Ëv­š¨êKC`› ô}U4Ê|˜}Ïb‡},2÷ýz‚rPø¸¹óI “âEö`ʾ½Øry†©¨9xuã(Û€7ÑŽôžc•B@! „€8mÓ¤¬ù¿ÑŽ…ïFš¬×}Ú•$ h†”aÍ/Fqµã¿ðÎû*æ=¸aÉ<y® ¥[ß ¹uÖÊ4ÿh!¬Ÿá§¥k £‡Ä~¼½ËS©6ù´?½þ.¼–Qc^íßÂsß)ÀÖ°„V>ÒŒ-J†gèJ×<«¯ƒwÞ4"5RYƒƒÏ¡ l«?c;k%š[~Äa¥î+Å WcÎñw oß]YOy8dýE^v†Ïܘs/ö¼pìQÒ˜E Í·âf¼°ÿ!=Óa(jYB‹(G¡Ô, <};QXÌö šÚÞ¬¡vâÞíûq›á¡I¶ãqëŒÊ‰ŸÒü*¬Éj9®¾§OÞ9_ï›ÿ¨v\Pð`HP×ç0þ³×™œìôCsßÒ5x3¬{]ûãíx|yª/{šcýÃß3£‡p-u•ž>ö§Ò'Ì¢û£—‘Ý…ž¿±û¶ÐèKǯÆöö'‘êW0²<…kª0»èvüqǯ"p?»_OHŽ%°ÒÄéÌp.{($Å”k^É/€¦ÎºªásstÖ½ho¹ƒÜìœ}åkÄc–B@! „€8=œÞí‘ï>õÇNø ¸M G$g…À°ZçêÚ;W£²q;š÷Ü Þ©5sVVcÏþtìoFõÍsp|gv2|„$ûƕۚÑÑÑÖª¡¸/¾3 ¥;øT™VÔÜ\¹ ûy]¥Qy£ŠÁàûô}ø¹û´¢få϶ë8û÷lÃÍŒ²óÁû ³±6.JQ³²²®æ=˜ç=„û”¢fÎJloÝϼ÷cÛC7S²÷=wX'Ÿè䆊š«oGÃvöÜÍ2ƱÊ3t€~?¨q¹¹Úe”§£•L8È}ÿ倓[¾•Û÷h¹ï½VçzÜ$kÃís¨SØ‹ƒäèŽ]–by/_œŠ';;pƒÖÔ•ØßÚŒ{抭û©¨¡fÁo;žHùLmø•¾`´ÛVV2ïw¶–â峡‹Q=on¥¢†yT»Zu_éhÝNu›Î¯Â;IÙÞÜ©57²=ªþ·ß—æ›?ßJu’/¸à jsVÞO«:åëOJY¸MõöÉ{¯æy*j‚}Ý( ö½‚£nü_+yýì}Ç×i½ïÀ+œ%r¸ë¹Çۯ㗃™ž!î1e§(!!¦\À­lÍ÷hf4·¢½ù]Ž[ŸDÇ Ëº IT„€B@!pú¦EYsÑìE¨©©Á·gË´šÓ¯"IAÄG ò…Ü2?Év‹ñ5~ôjÜÇ"$zÝp{gaÑw ú5Í>T ef­lÄ-é†y‘mÎ"ÜÍëû/…‡_›_|‘š9÷àÞ[Ò}ÅdÜRù®|HÀοgZŒsÇ_;àu»i0ß½_cq$à8¸±Ò…ݲŽd;><øŠžÕðýûï o /Ün/æÞp§1ø}ñ€C×ÑÑñBÃ}4ÝrÀnóO ТGüµ<¼ÃrÅ­¨~¤÷.Qæ!æ;Bœº1j§A”Ts~ô8nIUæ0VÌ[x£¾póî@ª–ÁŠ«o»ÓˆÌ¿q–%pƒìŒCÀ «m–6G›“Ì­UÕ·oVIíx"u€3O~ô×stÛ¶ÚøÑãÕúÒkª_T®¸ù<úäÓXâ`{ñ°íŒŒ°ó¨Ëþ›œì‰,¯ ‡ö ©à`KÅ©°r¹î7’æµÿÀÙ<×bù¼Pó¬mÿ1ÒU{´&cá͆Bñw.1Ló¬,¿Ó8§Úµ}ÞrÜ8:Š/"Q†°÷ θ[ø#Ìcc¹Ç߯'"‡ÎZý™VîñËÇ¿E.+Ûã,åÝ9Á†+è£F7Oÿ}²B@! „À4˜&3¨HJEÍ4Õ™$+ÂPÅ0º’þ6‚ o‚þš^Zp}X\*chå^i æn\ä¹~‘êð­Ì™³üZŸ?_4Îâ¹y>g ¨8´sŽ DÁõ[CÒQ/R)´r‘qzÑ|“ïª?é ðTiž ¿køÎX‰SŒ’pËÍv¨:~y8hMLJ{ñÓï”ÒQ¨)ãCY¥Î(*˯63Qê`žÃXëMÆØC̲܉ùÁjñÝ$›x ÄÓŽ'\g¾ÌçÌ5×3Oβk%É;l·zK +R¯ž‡_yß)Ýj>e޼AïŒݲÿÜ{íkœ9³¥…Fÿ™õ­›qÿ÷úrÂË5ï`M­L]›iϡɣ©QRýÿj\a^)Jó*oVþÀ×¶þޏ—`þÈaì䵕wúfëøãù·ŸÆÛ¯'(‡/ý¨Üo5÷8d¿s>}ÿDQå‚ß,-ÂrJ! „€ÓD`š”5Ó$­$+„ÀX£Ãœ²$ð5^EHPúY7£±a%,^ÿÀM}gçŒ[2lt”©¹‹èT8Z2{jQ1½1t-ÛfÝ\‰†;çrf?sVèÀ^.ÁËäœYÁÁ¥g”וÙÊ“b.E2ÝÅý™˜Ë¨J—2g®YAÈnÜXå9ô‹”ÒjÖ÷ áþkᘛŒ¡W¾‡Ò¿7K ’WÇAyõÏØsê|<üBí0øêT“IRD¹½ØqóO Ǥ¥…÷á×¹ø¾qWñÏ;8žUþ^nüÑJBíÅmßyû1Îñ¢**cRá¸"² Ä–¾L{« ðð‹û¸´ø!¼øð]¨z ¸úêÔI+N"EŸTå±@›x%ìÄÏUž‡⹟æáÁ½jæP?^~˯ K%t<v‘“”ÎxYƈp0†ÁÇ·>Œ_<÷  y0©v£Î0­b¡CëÛ~ø¸n·ý ÜU¦ŒƒæàNŸi`ø ü}ÊõœêK÷á§jU1•Ð;¯áMåhÚ7„Ÿˆì–¯p%!öïÜõ0^SýëÀ¯ñó‡ s¨ÄÏ~'ØVþhQÌþ gw)Y¢Ý©úóé­î¾Ñì°;œ»eRý:9âá^Œ‰qCv®rw×Ò¥È-x%à+.¹ÔL-ò{¸æxeŸßÿO¸´r,„€B@©# fPSÇRRg‡@¤åcí áÚ^Ÿ–Uቪ2C.šÝó³­X®ü]|ÊI!&&ÑépX ^lt@Ê$P\µ5eoêsn¼Wïã :ÛôÇq1Χªt¯‘ȬkQÝð3½L°7ÓohHƸò äÊTOÐ4ÃwñZ®\õ³åœª3Iƒƒèå±âƇðÚm\áŠyrL[‰qï½<ñÄ^ì|àu|wÿµc˜Xg²SÕã“2¸Ñ<.4½,Áø²‹€ W— ¶¥[ÃÜEpFÈDÛqì:óÉÁvºdôWl·\ºZ?òäÏà·J´ø:ˆ5=~¾Ç̶úúÒ¬WâÞYi¶ô&|i÷¥O\vÇÑxÜ‹R¶¿Ÿ–ùzê£þ=Òß{Œ«O]MÿIá}‡r†õ'›=™'Ís„TaTë U7ÙéçÚQ®@帗ˀ›©c¹Ïgߟh¿ŽWÅy\î«–\OXâja“áKvõìKfÕ¾ïPΡM!š\Œfu,äŠaÏaçk;ñØð\üÍÓ½²+„€B@i püøññ†lÓ$)NŸÀ‰'0sæL\pÁHHHÀ[o½¥·j_ýWÁ¿å*( .Ä矎“'OÒWÃ(.»ì²Óâ ¦àõ›'L2O5å_ Û,´Ý0ÑâON­œÄ,ß*=‘îôÅáðÇÇÊMþ<ʦ“Ž¾ÂŠ‡~=>äÀv¼0ë š¦ÄY8#Ï œúXÉ}šK¼Ä[–ñÊ ç£8ývìKŸ3+~x}1p¿ OR9h¤lãKáa\ÎÒ2õu/;VÌÕ¢ËÎt}³ÔªC:°_ ÑB/ÙÝ¯Ìø²F¸2´ÙU¸ù‘f<´H)xâ “ë×S>ãÜã”}ÒrE,¥œB@! ¾À,|;—‚̬9—jKd“ `´’ÆŸ™5L+>EË9Å}UUtVÌðýÆûÖ?ó¥û¢rÿ¢ÊuækHrB@! ¾(Ä ê‹R"GÜÄ *nTQ! M½â1×TB@! „€8Ÿ ˆÔù\»R6! „€çx͵αb‰¸B@! „€8¯ ÈÒÝçuõJá„€B@! „€B@s€(kεy…€B@! „€B@óš€(kÎëê• ! „€B@! „€çQÖœk5&ò ! „€B@! „€ç5QÖœ×Õ+…B@! „€B@! Î5¢¬9×jLäB@! „€B@! Îk¢¬9¯«W '„€B@! „€B@œkDYs®Õ˜È+„€B@! „€B@œ×.<¯K'…ç>øà<(…A! „€B@!pö\uÕUg/óIä,ÊšI@“[„À™$p®=TÎ$ÉK! „€B@!p>3¨ó±V¥LB@! „€B@! „À9K@”5çlÕ‰àB@! „€B@! „ÀùH@”5çc­J™„€B@! „€B@s–€(kÎÙªÁ…€B@! „€B@ó‘€(kÎÇZ•2 ! „€B@! „€ç,QÖœ³U'‚ ! „€B@! „€ç#QÖœµ*eB@! „€B@! ÎY¢¬9g«NB@! „€B@! ÎG¢¬9kUÊ$„€B@! „€B@œ³DYsÎV.„€B@! „€B@œDYs>Öª”I! „€B@! „€8g \xÎJ.‚ÇE k Ãý82ø{ôëÇ ï‰¸î‹i†eœINÌKù:v'82cÝ"×…€$àFOw¼°Ž¹/Ñî@š3iÌy9FÀs ýCÃðx¬°¥$#Å6–eØ‘݃è2ÒRÆ^‘‡Çíf† lZÜî±Wul‹ q‰ëîGG÷ R2³à´%„gvìÆ`ÿ0ÜóOAJŠ-äztYýQ£§á¾õð7¸½³‡û†ô¥+Ó31_Éœdª–¥»gš<àì©p÷EîF~Œ”èDf„zêïîÀ°=™ÎÐ2‡Ë9íÇŸô þ‘6䔕!3É__n´Ö×c$§NÏ ì©™¡\¦S(Ý~†‘š• U_Nþ2{ÜìÃãôáÈýÇb³ElBªõ÷ôÁâLç³ÀÏ^eäa¿b¿lv;R’Æi#ëÎ/¨l…€B`ª ˆ²fª‰~AÒë§‚fkûSTÔ L‹DJéó{*€Ô8Ý“ó}8©¸‘ „€˜ž~l,Y‡qŸbYexuKÎ%•Íè`Ö­íDùŽ28­æÁÒDˆyÐ]¿{Ò˱q©sÜû[ë±¢Òr=·êyÔæ§…œ‹ç µv*÷•bW1ÌøØy¸Ñxýõ•Båh¤eéqáúÕ㈉çßx i18yÓõ (ýÇý(þ†YºÐd=­(*¨ iOöÜ*ìªÍ÷•)º¬*åØi„æé?êÙ³««Ûü‡¾­A%¯fW UYJÖ5„Å n{.ö‰Xá™7ÞFF7:x_Cé3è*ÎçöHíjÛKª‘\±KÖqî›ØéÞ}ØÑfAAUð>Oo*]-¨ÊÏG-塜­ãʼo*öíçy¶Ÿ4Kœ¦"×ø¸vïÀ²’Ðö[ñ j Œ:tw7âú’±½«ð™ý(ÏÛz›×aU]wH_=ÖuKKÐi~9 ±+Â3*RÝM IC! "eMd.çôÙæCÍøU×®3Z¥z°¹·/X‚ùg4oÉLó”À¨I£,[Ñfl_“Á/Ëü쫾{ûÑR· ¨Þ“-ùÎs€×;„Î~Ç.;-™::Ñ™<~£Ç8è׊šLÔ<_…Lk?V¬C[õ*äd¾Ü”ØoϱAô p¦Gs=÷1¯L,¦,ãÊãS¦Á:Ì-߄ ;¼^ß,šÄdCAâÌÁæ'¼~q8›Æ2ÜŒuJ±‘™ {ˆÂÁ”¹y×jÜl±˜¥3GPûÇÐèSÔÖ<»2­dQ‚†¶jÔ-ÎDíÒ –¬ñ¤ž-Ûëµ¢f¹Ø²«Ùzf g2ô¼Žºâ h©\ ç~¤qpí+Káæ]X“‘È6š Õ–„5¯¾ÊöÃÀ¸#=ÍX±®¡ñ9‹*·‹ü°•0¦]žÂžînLݯú \T8Ëž QVvºê0:« 9ŽD´°½$Å3”ÊiùÛ/™x8fŽ@\Gÿ½ÝPÔØóÐÔ´ ¡±¤„Ͼ(ÈÝÅáþ>¨vUßTö®À ¶dgâ•¢Q)jT°øûÜx™i*EMaÍ3ìvniÄúFÊ]¹hQ˜E®»1É ! „€˜2¢¬™2”_Œ„~Õõ+4zé¬ £”D—ÐDêæô¿9k2|Ù2vö¢ß›Â—} »½žܲ4S iq¬¿›Sê1ÂñQ2§Üg‡MwöÐ| #‚%;Õôx Sôí©óæHÙ¨JyÏ&¥$ð&¥fp$9í4Y¡I ÿ!wUU ñ¦õðªyûþ@sœîÎCèvÃ’HSìðvL³ªŽš°ÅSY‘IÅ@ûŒÝ0¡©OwŸ©™i¼ÁÁLow;P*Òü&31òÑýª§ŸŠ ,ö$ddfBéFT¹:;Ú9PAßáÃð&Ú#›±<îÁ~ôôôb˜×’hƒ3#Ó—?Íz;±¿Ÿ‘hÓÓã†}ŒI0ÔÝ‚aF)ýÇMXª”HAÕ®*´­¨FsççZ´9Ž=9΀âÆ(ëˆ%™)j[‹Õ ¼7rˆ•GnžûÐÍ`MÎbd¤p'<ØœÈ^ê4õ uã@óмù¶à¬)U7==áH3)êyö<߆0ÐÝ‹ßõ3Žbk21ò vëÙ=ÎØ(_š¦ó+Ú¼ ‡¯)@ÛKݨZJeM YãI#0”hÛÖ»x”‰mûkôÛ¸dEJF.65{pë¤îÙC(¨]¸+y•Y´ 4ùÀîX“íÓæk“ÎhñCîµÂãîG{GFˆ)™æFÙ¬ke3¦]Ñln¸÷ ¨Ð×Ó÷_°RãÃßœ¾a+ɼO3Å÷˜P"gÕfú̈tv4Ýé=ÖoxÁÝÓ†n7å¤ëhú»2Sl£n}êXð*÷(F=ýCð²î©ꞦS݇Ù÷=”ÅŽ4eFh×L"žöcΓ œÈœ‚‘Ì¿¯vg*2Cž”šy!ç§‚Òb¡L|h“7>?zºÇrµ0¾2‘Köýþrmá³ÂŽÍì·J1£úpESZVl7LÙ­ûÚ»‘UKlFP°ˆ{ƒ¨£²’BòE¡;ƒ³ŸW\¸ýÂH#§¸y¬§==pSYÃlt©;–á{§Ù‚ÏM6B@!0…Έ²æÄ±wñv×ÛxïŸàÔEaÖìT\ýÍo᪤¦¢|ŒÞßöá$.2óïžÂ%v~B纊w ¸äJ|+-ÉÓ—n«LŸ¢)jF¯üŸxâ†1ûÒDà³|ôÝøGW=º4©%x¸øoñ5ûe°|æåµ.<åzÄw X{û“ÈI™ Ë…^Œ|ü.^Þ}?vþGdÄÿÔñOøzò×Ä$*2ž)?;ؾ«ëùæF·zé¢yÁ’¥é8¼}JM/e¼b/ÜŒÝåÙZ‘ÓßZI‰ÖˆòÄ2'ˆx“œ§I@)9ölÛˆ†–4ÑŒƒÃ _;öŽƒ: e^ôýüuZ1à?ÇyØ´{Ÿ]¼ÿÓ^lø«Ð¼ØS_ôÛÛŠ`lCII#BLH8˜YÇ/Ïð™eÄʧ·y-¿^wÒV;£ÈÂŽ76¡û¦eÐ wU—¬Ö3 :˜oxéÔLŒez€’ J›^Eab V¬jÔØ­¥«9È-ûç7PôßCSq3B&²Òø¼÷«ãü´R¡áÉËZé“m³65êÙ±žÊ™n88ë¡™xgÑìÏWMš]·lŒ)SÌ<¨¬ä€a¸¯­ýT Ù8hÍâó*òouÿžõ¨lƆ®̼ð r†Á²õ¼748J›øå?ØJJV|MѳâÌ$*õŒ» ¦¯&\ÅKÈc_7†ó55cdáí£pÓn”ç¤ ÞöãO=§-iŒæA{}Ö‡ ¥f¸l§B.ƒŠwo3®_šwVÐÔ*½sV7Ò†pݳVs5~-èd¡³k‘a¦rg@û“IáL´®®|Ÿ¨ô#ÕÉݬcèèhE{Wr²r ß;¾HzÓQ¿{¨}iS1e+‚—¬N¬¡ï ¤œys>‡U²Hû‹€¢F•Ù\wžžgqÓjòÊj°&?gò¾°¹ÊŽB@D"0íÊšÞWëÑð²ñ ³m;ŸÅ¼•`Ý W§O|ˆ§·>O‘Âv®Y ¼½3ìäØÃ²Ç›fÖrÞžQ>jÆ ^‚V.Ç,„ g!qÎÔ|ÿ2ÜóÔËXwo¾v±zûg¸Ð‚ÙsrððÝ ¸éé:¬-Þ…¼Ëý/ü̲ß-þ%¼}¯wŒù«dy´à‘1çåÄÔ°^ÌÁGB'5™¨j¢ÃFG @?JQã(¬ASéR~ÿDkÝZúXWÎ~Üå衉5œ^ýÌ3üâmå”ü=X½ºZ¿ðZ¬S/§¤(Æ#0ØÓWãz¸ºÞÅv\teÆñ)'QðTg[zÕ˜\Û„x9õŸJ+©öàà±0G ¾Ýx¶X)j4ùiâL’$c{.a{./nÖŠ˜ž'iĘyUÏ "?V÷T¯FuÛ0lë|`µ1†ÃØçž£8i–AÿÄȧ5ÏjE ý=¼¡|ÒxÑÛZ‡U•-4·Âæ7Þ@Îa–­qaÓž]ȲGrꟉÁ4ö—Òa®îþV¬XQ‰=½C(.*Äûsá*^†ÆìÍØ_šI“—ðëÁ‘f³0Ô†Ê N&B[ïŸrvo:¬•Bëê^Ç3Ån­¨ͪŽ¢ ]t5ø7œ[¨`á©c¾ß}5vJ}ঙ† ë×é­ÿOfYšŠT¢¦ÀåÕˆ× Ϥ|:ȶ1̺®iÞ…¥NÖ‘»¯_Å/ÿ½üòÏÙQ$2±i×&ä0ŽbVLf[èo%ëÍœMðºn/)3ƒ±UùÒ²³°ŽU™†%†¬ƒq¤Å¿ã1v –¨vê œurÌ4#Ìžâ„ÝT «®d=ús¾ÔÆ 7ûFyñRÓÚty»¹ìUªø9î84GhWLó»«£xY ²i–Uš™LË«ô_Ìß5ß&“mt“j£@{#VPÁX¶þFS>½Ô¦ —J¹TuÑ܇QO-@aS–I­t ®JöLÎìо‰ØçÍ¡»qµVÔd•nFma6) ¢¿eÕTTnXœ-KѸ†Š¶ŠmÏ  S)gº±>¿®õÕ( Ï£¸Û9gšîEà䡲ÆK?1ZQ“YÆòÀiñ¢§M™ºµ`õ†¼±e1:ëtb5»Þ`›eîm¥ò¦u xcónìÏËu0%Y™}pª6:ú!~GÄØ€ë¯ •ËÁ.;ÔGϰ61DgÖ‘k0Pi¶ŸJ3=‡-§{;Ö¹†QÎßzGâ‡4wcذ´¨HßêîoǶmÍpµ‰m*]L2¼î2Ö€“|°¡º-|gæU tM.ß%Lõ¼[ö„€B`’¦UYs¬ã—>EM*î~èn\3ûRŠyö¾_6ìÀ‘⥯>ŽÛ”v…jô‡¤¬»ñÐÊTœ:Á™2¦pÑ Þ{ë7ÕüÆ“}/ãá§ ë2õÑ/Â¥_REM×@WTg·߰ا¨AÇÁÆHÊräι¸”Š—[¿PÔüïwžÂ¿^z î¸j6ì‹Q•sßð)jŽ¿ÿküãàåX·p, ³±|ù­xååÈêåÃFÉ´À±ÀT‹²;ªvmF>_ Õ°¶j~½ä—Ê …œVîåK>‡9kÖòK<’öô#gÐ¥•2¥µåZQ£äJÉÈÇæ²¬â—u B`ú ¸ñ¯íxrmµáØ’¿ÍÏŽ1ÕÓrt7`Õ ŽÂÂhæüBÆãlñ¥Œ:W²ÑÄdm^5Ö·4Ód#®À³åjªÒ±¦ ¿jZZW#žŒ·ÄÌg$‡=a´ íí9ÈÍNGÚÒ ìÎ(¦&Àήv*hˆd{"Uäð`Á²šä§ekE‡{÷°WGJÒz+uJvÌäG2ÍÃ"&bÅê–TC}w襲HÉ)Ǧ¼62Ze-›3vlÊ3 ¤Cï =Š'ú:9wÓ¾M(fr çHw6” -'ÔwNwS­R ®4Ç$ƒWäÕ£¾`.«?vn÷ÓTÒø”l>Áòjªtý«C›s)¶Ôtb•ÝïyÙN¨,á Õ©ïóÝ 6>:xcÊš3 Sº¾Ý¡#ízÏjòÁâéoÆMjv”)fwùÏ wÂå2ÍþSÀ 9 VŠÌI†Q{©¡¬T÷ÛÒPX–‰= û9³h9ÛÛØve›•¨ýF)³,Õ~Í¡¦Š IŸBÀhK-èç,¥´”Ô6嘣S¡³ Ç8ˬ =X jæ‘‹±j ‚3;7y8[˜JNõBmq6Õ *¨~ۀΚ¯m;„á´¯@G)ªÕŠÚÂY:;6Ñ׎‡=ÆwûQ÷úÃøœnAßv%1"› }å·"#¿5-tÂÍçg UCëFKs;Ò¨dr¦-ÅþÝüݵhŽÖ\S¸Ö–ŸŸ~Š!¥$fUÏÊ­À¶ úor÷¡~5}Ö¨.Kö£p&ýIñz.ŸT|²w¡{ÇzÎÎéDIÝtÕ.féÁú5°5¡(ƒõÇtµ0*¡ÃÃ0MÔ\ÊäÓÚiÆ–“’¦ÆÖ ™ùåhÍ/EGêù_ÝRG,|Ž—ó÷',[! „€ˆ›À4*k>ÄŽgù’–p9Ö>ZŽt¥§Ña®H[Œ:‰Ò‡_FkËo©¬Éö_Ä%3gRáÂÈ‘”.3’ñNÙ/Ñû3gÚ1ƒšHÑ‘¿;ïýç{QKùW—^¦¯{?zÕ¯+ËläÚ׳o;Ñ´&zNNó:>¢óõþi˜ªª›á̯…¶°R3®VpæÚ† (X¼‹ÊÁ¡ˆu§ob Îì|æ—ÏÙlݨ_Q‚u«\È,{ž3çü`Ä”¿B@!0qÓ¦¬9ñn8±3®4)j‚^4{*»'.J žÔ{‘|Ö„Eáá)ß)ÿvlŒ/×™ßý!jÛ»vÂûç‰ø·?la¼«Pu÷ýBÁÏŠ½ÀøÂâõ¨—1>âïù(f'&à ¿bœâËÙŸŒ=üŸ?PY3‹u >ûŒbÉ4þrebøÆjçW7ÓM <¥Lœš6ÂâûrÌaOòÅÖ–Œ ;:9’£ÿ7OYƒ7z9#A‚8f¥:8Rã༻þ.9$'èÈ2<ÿ$§)))ÁÓܯªíÒ³&zߡɔq)¯ª …©ü–î”ùš¼Ýaáp™­h8˜†ÞóâÕW¢…SÆàFÿRùLZÆÍçJ:¾µ¡µ«ƒýýèëíFó¶zš ¬GËaúS_ºcµŒî²›J´YGiífúœaÙ-4¢)Š×—Œ$xÙŠôÜL$t‡Â?¥9d?/ç;ƒßÞ}ÏjEJ³‹3òzi‚¤ÍQÔ‰¨!ž<¸âQo¼¶Ôg¯»QaýÃô'c Ã݇Ûõ «¼ÛÌ&2J€c¨_v“ž=‘[Zƒ YiHM±`ÍÀ´!(døjP–D§¾hOâL&úúÐJ“3޾ÞÍó\IÇê)kaz¬4˜VX¸8QŸhüe' ·äú·à*g³úŠmJß`DÓg¦ó#Ù÷›–‰ñv2êaø»ZØOUA’ø1' (<÷ù<êñ£g÷´v¡bÏ:ooÞí…ŠZ£ù_Ýî»s±f’|<^™‰dÓÕ„äÿŽ,¾z ªßs–±»Ï‹t*wƒ%·`¦êRÝü$Û;Ÿ”„ÙR5–ÐŒ’þíZw%cÍŠ—P¾û)d§,ÕŒ«5e¹\¥¬ }ïy‘š¡îL2é]ÊÒ£ú°ï|ª˜C…’c! „À¤\0©»â¹É§EYuU0ö©8vŒäõÿ˜yŸâ c†ŒŠ¤~Þ?j­Ç/·oÇ/ùËÀÿÍ›ŸGï‰`2²7–€2;Š:»žAýÞ-xåµxºüï‘í{™ú·ž&üáÿ3î´~íßÅG¿ì3ãbBðåëƒýj›h9òõ †LÑï–«“'`Å\~‰Äp Žq”‘‘aüO¿.N¡võz‘’–ÅÏ©Ã|Q>lÊÆƒ×LDz+¦‹€ÙåÍxãÕ]¨Ê¥ÙËúÕ¸~ÁÔÓd`ÐÍ‘HX8fL9kOË0Žùܲ:Séi†V~Ýn¤ñ¼¿ÍÛú\(.iÖ«.©Ï°‹Š SúžÞ6Œõ“:€Ãœà†óS_V±òù¼k3`CëW+JCÎÒ"la™J•p½ÿTœp†N0NÆÖ;ÜaîæmÙ…â¥Ùœ¡‚D:]ÕÃÕ03”‘€V*4 ud³§òo7Ú:‡ÝG¹:ÇcYY)úœvË%²•˜ç›J9â E<ö„7޳3OV¯ZaiöutèÏ CûöïáØN?'Ás:Ò§ôËA…šZÅ©¶x)W€¢¢‰ƒÑ!õ“—úqçÙ_û‡Š†À‡÷ìÐ;©œòaáêP*<ûën½Õè‡cO÷²2`CÖ˜iSìY9ØÈÇ1:7 NkÊ—ôNÏKuZz–mÚj ?uÆŽÃÛ•2› Qê÷ж¥”jÍÛºù 'Æë…2Wsn¦(yûvþ¶8h¦èWðÜ`»š)•‰»rÂêÜ_RŸÂb¸q[h¿lGªÆ”D®ü4K¨pm m·Ýõ˰ªh5ú>‰¿ýø³¾¥«¹þ|6bÏïÜÁ¨£ØN¡F)½»¥d27´Â’âDfN>Ê·4ã>¸Øvà90†+Ÿsš™NÕ†dÕnèo¦ûXð™è>´_+5SlVôîYM'á+ÐÒo걟ôé•â×Ó÷ }úЇWS“éÿæ Í+·bžy>‰ô{3 ž=ÁçU]t\LÀìο°D¬;ÁÕj”Íõ%Xpý2¬«j£ŸŸÍxõ.íØÙG¶B@!0yÓ6³æÃ#j^vEîÄ»/£²á@àXï\øm<þäÚŒÉxyý ]¡/\*Þå\Ηð/»±S(:óÑhŒY.*nÖÍcc†2]Rao¬ÃC¯w£âîµúLðe, y©ïE1Á×D¸B”ÿÛVv }ÝÄâ‘)Žd$Ê$¤çÞ;Í™*—­ÀpM9Ò’ÜØS[©¬V¤qIXg.¿%7Ò©ëj¬®AqN_üjÅj¬å–ɰ&9‘_¾ù¥ƒèØçºt2\§Lj°©<†#U‹1áõîAš;d ¼ÐN'šëQ€2ú¥Iƒ{`*kÛY…dkò6²Å¯£ÔëPUUŒNõ¯UNŠUÐN¸õ¥Y[^‡Ä ù°ö·c}]›ŠakŒ|øIÛNeHKå . ®üÎ(GÇÍhTŠ…\cemŠ0ŒÕôó8·,Í4}g<«‘UKu²hþ`;ÖƒêÊF­ÀA[;ÎÞE“1ã×r¸q#ê± 9¹·ÐŒïFãv¤då"sÔWùjØk¹¢Œ•å]ÏòrV˜MíÍŽêVT3¶CûÃH£IǦ<ý×Ð êâ l\j(t|Éù̧üGÆ6fœ­B 7¦YekûPQ˜w§ Õ.õ_ˆœ€a7ÔÊ7£ y\Ú˜#„–u{2}lÙÜ4É0že£ÝmèìÏ¡# ³þKP59N ¶oGõ L“+}å8­$g¢ŒÅn ¿µv>óè¿cOíz­¼*,žOåˆ-¦¬1Óˆ ·ªÐ¼MMØ“C_#l=¯—bmg‘qéãvW\ÃÆ]þ6èK£¶¼î|;ô¢^¦t³ Æ12ř̮Ѣ"´+»qn Û#Š#7oi y×úe\Þ¨Fû j§9“‹Eq–k3'^ ©J¶çÑ\0‚†}¬ñÍ‹üÍ›ipFQ}›:e&W°ß®¹~*k 0ËÛ-œ¦BUùbµŠ9Ê–€¾bêP´Ñƒ t<ØÆºoaS_ZÆ•”tT:ŽÝ~Œ˜Ñÿ*ó‹ËaaV¯ÀІ2΀¡r¦®Zÿ¾fW­Ñ|¨Lh«D‘…Ž}ù<ò²oáƒ@)³l* mþ–%ÈÕÝ\DŸq(¥ \qš 9Å˵î¦ï£jóú¬iǺJovð7Û§»Œ šýµ`ú7V ‹}ÃE9ÔdšÂò…šu =ƒ§¨üüa4 Nê~Òéü9ƒýžlzªj`Ûd]êg–=Í[ÐHÇëÊ$1‡°aLÝ11>?ëׯ2œÂ³oUð£j^vš©~ýÊV! N‡À´)k®˜7ëa~À¥¶ƒaFÚr<°vW|º—\tž~]´86‡™ß.ÃÏïH3Ÿ’ý88íNü!š)Ôµ5#¿Ç¦'Ë´ÿ•t×|D³&.ËÍUž¸ævÎ]ŒÙ¾–ññÿý3/Å%»éPøZ~|Ó¤Èù,ú %“„3A€o«4Y3‡„¤l.K“u•|—[g\â ­´¶ ê¯oåûŸç(оhSÏñƒ¹¹´µ ñå¯8S¬)ÈÎ+GWÞô¶·aÖ®‚Ÿ@IDATÝú—Ð_JGªÌ_µ’T“ V;—¨å £“«¹é!»|Øâ¹âWFá@M{V)6×s­Ùåx¾XµÊßÒ)ŽÜ\8ÚÚøeÙV:Em*Ë¢ƒN: -1É+-ÅЖƀ%j> ¨Ø½ÞÕ\昃(ŸZަªE:«3…Žg¹âŠ µÃ©ZYãËÞw=›K9(kl£ Ì2ä––Á²‡*®BÓÜ_€ò æ¯)ä•ʘÆZ¤æPY£cšþP±´©™D¸¤±òŸcWÌiB63=;ê´|…›¶VQþkr[è«¢’«èäó|™äU4¡œK{«[ÖØiø’ ÝÐ×HSû.ì¨+çª`\IȨV§°¦ yV.s½%ì>ІF6»ðÐA“×ìbU¡Áv3qFihc·ªü#´+«·Ñym·« [êÜÈÉË dœÉ}W]%|¸‘U¸‰³Ÿ|-Ñ7 L¹ RÎi‡ÙB 2©ÁðO»v ^¥fšC‚5¤Ï'±ßîÚdGÉúF*Wýµ¦V>¤2Tÿ–ÑŒªn7¼Öj*hàë¶ppu¢-Ku¿·ý˜Åˆ´ïç”`åï+WÛP¢”ËÁòrõ¨Ò|§¾uqÕnª×¢2ùŸGj¥»z:Ñ6|öŒåjõ­²e´WŠžVÀ²»u٫ו"1¦¡Wz²-Å«ÏxQYLEY5eQ1|¿óÅÙ!*ã^õ×cØZýgX¿U»›`YKe¢é™¥Vwªª(À0ýî„×Nf°®þã5­Èá}/-CÝÑÄB æß†ÛÜrn*Nœ  Q_pÁHààè­·ÞÒ[µ¯þ«àßŽŽŽbáÂ…øüóÏqòäI¨ãË.3œ-O…,çBNE÷RP m/h&ÁÕj3ÊšB]·ôïÁ5üÚ>Î-MYÊ®˜>Ê”@7hË8+%)SÕàÕu~S(ÉFå±J¼Ñ|[°0 ð€+/Eê4Júùè4º”QÒˆFÁ—¾ÅÂ~«e0ä¯GMJ?X^Uài Æs&Jñ0*ë„õæÕõf”CåÉWXž¬ù™‘H²ÆL#“˜ùd“¾YnsybïÇzÄN!vŒ˜yÄÃ4j6„²Þuĸ*ϱal¼ð8æg^ø5}‡¬1Óˆ˜0Oòyûÿ³÷.PQ]iÞ÷Ÿ¬H%à„∅8#Ì4Ø­¢K°W“ॽdEICÐÉH¾$:´xCML°ƒØ’xÃÐØé NZ‰tP¿h˜ &æ´tFèpÆ€É f^pÞ¤LaVóý÷>§nP\¼`Ô<[©:uÎ>{ïó;ûœªý?Ïóì þÝ Éfç³>ÕO×S7GÅãí䥧@ ¨ëÊn 0Vû{=ßn»˜çͽ¸¶víÞûk¾,õ¿W­èñ ßoT-n¿œÇîõÜ9·Ê‚B@ô3~kÔÜÛS—/‰·£fÏ:¬¬ˆÅ¬Ç„€«-¨ø¨å_˜J+¾°¶æø¢dò¯ŒâtOW]‡Îå€QS1{œ§àãÊ KjŠìë0õ½èF9~¾iª—õn«Î¿ˆ™Ÿ G̦å DéH>ŸåaÞ¦Ò’lB@! ®Ÿ@¿Å¬éÒ$Æi柚jPÐ /1lºì!+®ƒ@ϱk®£ÀëØåï'ü=¦Eþô:öìÛ.³¦oœ$—B@! „€B@Ü™úײÆÉÀ⟤þ%àèÛ}°áþ­¿¿…šþm½”.„€B@! „€Bà»'pëÄšïþX¿7-P‚͸‡Ç!÷xn71ln> £F¹>ÉtÝ7Ÿ­”(„€B@! „€ß/"ÖÜ¥ç[‰&oÄ¿Š† |ö>Úþˆú–z\i¿rSŽø~ßûµ0óƒà¿Õ³>I0ᛂU B@! „€B@!kîòN DRîò“,‡'„€B@! „€w{‘ƒB@! „€B@! „ÀN@Äš;üJó…€B@! „€B@»‹€ˆ5w×ù”£B@! „€B@! îp"ÖÜá'Pš/„€B@! „€B@Ü]nQ€á¯ðñ¯w᫘9˜ÔÿÛ>Çéš6DŒ ÇÀk¬í«Úðv 0oñTÜ‚–^cëºËþªO†A£0´‡þ¼ö4.]Œq‘C»+HÖ߆.\¸p¶Jš$„€B@! „€¸s >üÎi,[zKÄš¶ÚQXQƒÙS{P4¶«8›¿¥C“±øñ¾ ͧwc[ùCXµø-δ}^Š·ß.ElÀfÌ ï­N÷óÕ†ßÞºû0È}õí¾Üv ÛßÎbÓ77¼›Ö¶¡lÛÛøäÛÇ‘—xÍ"V7…Êê[@ࡇºµHB@! „€B@!p»¸%bÍÙšªÜû~Ò“Ù‡IäBͨ½ÿê5ñ¹òùYüWíý`î5ph f=v?²:Öô±¸–³(ùxìùgY}Üó»Í6p0žŸõÚ²öØŽjó•kdÒc‰²Q! „€B@! „€¸Ùú_¬i«Æþ bÞã¦5ÇU\ª­AÍg—puÀX|‘ù­ Ÿ×žE Å|yµµW1hh8kØ6\¨®Ág_´à*e”€ÁcÔ(µÐ|©eÜ©_àlm- Ex@5CƒÜ„‰¶fœ=û¯¸pé*¬ƒ­áAžV7µíg‰xœÖ8m,÷ÂW0Ø:_ÔüZþ/þ|p~8.F±m¨­¦{Êàáw/‡õÔ^¸ ÇP|ŽÏév>tªËJQ÷ÍC˜ùX$0OuM ¾hi¨ <ôP"ÃM§+ZÉT_¸Šá‘V|~ú4>û¼¾¬7jB$qÛéÓgñùVËu,ƒ…û‡º;n±}æþ¬Ãñ£qc ÂÁ}% ! „€B@! „€·/~k.,Á×>bÊ8%$´aß«ËPòe' ÎÂës[°.»ÔØP³ÙxKÛŒÄð¯ñë…¨øÖsŸ]!ëµQÈÈȆ±©os§{KÃÆQg‘ý ½‚ 7¨¶Ï?ÅÊu{Ì|®rÆÎËÄ ›+>ǡүñà”:VMí§ocû'J9rO%ØûO"eÝZŒxÛ¶oÇ·S°yãl§[Ñ¥“»½·N·g·ÍÀƒßâK]T,ÿÁ×x%cW—¶Ä.ÂÆ¹‘Pmݾ;y$Ö»K*O»Âæ gù#>ÇúìlíµC¹AµÕbÓ²lÔ¹•Q¸Çüð ÛJYB@! „€B@! n;ý<T3>¢p¨Ù§UÇÕK' ¡†"Ãæ¼<äåm@B™|¹§ÎÂæÍ™Œ3ÃÏcŸçr…šh.Û­…šˆ¹/!DZO…‹¯+Ðr57oFÊOÔN1XÍå-‰+h±£’¡D5£p½jîÅ”E™¬3›³AE©ø§·@#¾:û1Å{1óq#èЀªL¦cð҆—æÅ_"wÛGh‰ƪv|Œšf#«£Êþ™Ç{oŒ¶Î0@ AjlbVJ²²f¡f·jîÅœ—6è¶ämN£-‹9[ý™Ì¶+ Ÿ4UoNÓË·;Y9ä–³1>ÀùqAíÄà ä›Õ8`œÞ½M 5³Ò Λ³0+ÌÜ(–5в$! „€B@! „€·-~k®^¨D9=qª’#”qŸ~Ç…R”Ÿ½€¯ÚáñÅ›‘¹z5¢èn4p`îgŽº1 Ô>;ü`}sæ-Âó±Ã©I\EÛWWøJ÷%•Ì7êã½°rÙiô&ýrõóE9 RbR8•aE30(‹Óæá±G!É´¡|[úÐlŒêYxÎÏ“1|*u†OHÆ<%ýW>§H1u6׋*è¥RsŽyóÐìÇ=f’šýâ/0uT8‚è?e‡äE¿À#ÃYÑÕ6|õ5Õ"­£x¶ü±´ç®ê„ ?VÒ0kV¬á‚5`(ûK½ÎýEë0W/°=ê€c1oª9ÖÀ LMYŒ¿pËÜ|éÔ,CοK_¹m•E! „€B@! „€Bà»"`š[ôOõgí£‹(ÆlÑÉ:)•#÷“:æ¾BµòÞñØÜd$v3ùSPx—Ħ•Ûñ…‡W’iùb”ÜíëÕ«†C±Ç= Ÿ@Ës  ï§kV좱.±§C™Ü`p§j¬þ\MëšÿÝr•±hÆ"öÞB”,GÛÔáøúß>âFZçŒ5F—1QÃBÌ„ CÙ¾ýX¹½‡chOfƒ0Ê-óÕV¶…®dV7!I¯3s{¾]E;WŒë9£ÕÀáø1-tökE§ %o ÔcÇZá$;ݹ<6É! „€B@! „€Bà–è?±†qS w&lÎãn¢ÁŒJ\Ž¼Ù &|¡5gOããO*ðÉ®7Pwe5~ñx×ٌξ³¹'é8‹ççÅÃCãÒÁuØþÿ)1¥ÉÌöu›gþ«_]b°a`xø`\8jŽtSCTѦõŽ{-ô”J PÌ L™Ò]¥8Û<—þù à/ç ÂQŒÏZüXÝfä/M×–>a± ˜76 C)d}”‘Oú➤Á¡û¸7Ê}Ù<̶¯=¤ æ¸ %™i ¦dÒ•ŠyâÔ­™›å]! „€B@! „€øÎô›Ô¥ŠC:°ðŒq†ë‘:¶ û°`ÁB|ri††ÂÔÄsf‘vEúºYGlÑ ÚèÅc$Îõõ!—ÏŸHº „¯/yá]:îGÇ·¦JáØÕ|4X{•~lÄ„1ןݕÁ Ä¿dÌ—K8Ä@ÂM«¥ÌäCƒ4¥gi¸²¥ŸÐ‡-V®W*š¢Ý‹vQpQÓ~Ç&Žê^OikÁj¥4,Ÿû8g€ŠA,Gë*÷wuá2j¸Æ×Vü %¸o+¢Ú…”1‡?‚{¼ä ÁC1|èPŠEê3W vÌ$EW³¶6J;žI­ë¤wé|×yî%Ÿ„€B@! „€B@k%ÐObÍWøXž Æv¦Jø{³~‰½ŸžFõÙrìÎݥݔja¦oKßFþî}¨nftíDש½Ÿ¢ºú4voZˆ]5T<:ZPzÚˆsU‹;Ÿ 77>®vWX ]¯Œ Æ%ÈØ¶—Ó{W£$ÿ—x›V?KÀÐ *°0ð¸Ã'Êl„ùV±+¿ÞgÔÿË #þMì,8=›†#~,}¥¾UPÓ~;Ìj<ËÑŸL«˜¯y|»ÕñŸþ›ÔLWjã—§qú‚÷¸1…/%»­ ”ä±ZhÚ¾ò—øèt5Î~ºÙ%Fw+·ÝÌŶÚ}X¶l^Ù[ëܨD6µne¡kšq*ƒë–½²ÏCsî$ B@! „€B@! „Àuè±æêçå(¥v1Û ,ìlYP,2Ÿ ´Xùd§ÆÎÍGiÝ×x(öy,Ÿªâ¼ Ĩ©(ß”—–à=ÿüy=sSÝ'{8¥õÛ(½0=¦‹ò]ŒÃ¥¡ãÑÖ-uå¥(c`›\Ârôyd9gCŠx_×PÐÉÞŽ}å_ b 2S& ¼ð_h(3 £†%,Ï==†Š£îò/¾ÅC1ÉÈäÛî)âñý±‹u޲øñhJ8>KKŸ¯QªŽÿíB\ƒÇ”؃/°«”~Y*ur¿²{iœ²üq¦ðåòý檠q/ -¿ýûßÞŽÜ=œ <,Â0<ÈÚ½å*Ï4Wr”嬂 Vw¡‡uép>îëÜ3˲B@! „€B@!p]|._¾Üq]{ö¸SšéÖ4((¨[aàªéj3ÀË NÞŠVn8*©ÙŸT2öàš5J¯íå…³/n;®ýÚ¾jFg\ rUXÌ…¢WñÆà¥_­ÅpJBºzŠ@ŽIªÜkºôé6dì©Áܬ<ÄzÑUÜóËÊÕHÙ˸µCUÐMù]÷ïëš®õôuÏÛ9Ÿê jư{î¹>>>øýï¯ßÕ²úSÉñÞÑÑèèhüéO•+W >?ðÀ·óáui[{» -I! „€B@! ®—€¯¯2q¸sR?Èiª;©˜ôU¤qìæiŸ¯u½Ÿ1dà  ëRßQ ‘úß%µ5×âì¿r&)IùÁYÛ'¡Fãi…v>>Çú{ïZÏ•'{ ! n%ª«j8Ã]×/V?kÂBº¥¿•­»#ê²· áR ìv üƒ#Øßr}Ͷ5¡º ˆ îº/uØm6=K¡ç޾ð×m±ÃfëFöõGŸškkÀÉ3M5ccɆ¦†Ë°Ùí¬?ÁÁž;ôÜVGÁ=—áÈÕùÝÞÒ€Ò“'Qsž ™B#Æ`ô˜ñ±ºKUM ¼ty(ˆÖˆ°÷~Mõ1“_Æx9O U'ÑbÀ˜Þ!Eõ×ë×ÕØòæQÄ.YŠ1ÎK؆#[¶¢5ö„´7Á:b´'—þj‹*W÷ŸŒ?êTÜ6œÇl·ñnêæö~ýøúûíW9Jâ» ÕçáÉ{Ûj®oj¸Äëð·"XO}ê¾Ý\özî¼ä“UB@!pSô“XsSÚöÝÒ¤w×ÏK¶Ñu‰þ^œVûùÅS{}º+CÖ ï7&¼»p¿˜I!ÞZ~¿ù|ÏÞÞ€u©i¸Ø†ñKpps"œã½îòÝNë›N2X9–îZ†ëïòvTmY…∥X=)¤Û£k8²I…ÛãÒó±vz˜Çº¾|8²1GSPRö ÜÇx½×aCî”)ðl…ªÑ(Ë·ºSæçzoBÇhäß°^n v2M[µ);KðL¤{ë<‹µ×Á¼9ýé¸t¼·vºyL=·U•Ü{žu:>Uø*ægu|4ß *Ó2 œçQKjÚÖNù\vlA!¯‰ÞÒÎceˆôàfÇIî·5e'Êžñtåv•å­_Ýü{tÝǹ(<ê‹Yén5×EFa1Ò§Odžyihe;uÛN×~7cÉÙòÙÂ|ûÀéfÔÚ7®MU{ŸêÙâVîÄÚYÆ9´UåbJj׫+×ÂR/×BÝþ4$o<ãy­´TaÙŒTœ2 “ƒ–€‚]K»Ü£¼»›ACÊB@ïD¬ñÎÃã×"/¾›æêð¹[°yÖUz/ݤ™œz®N¶ »@;>žmÁÑu ±õhÞüÉdN"ÌÞÚŠ>”á¥M¥[´PÓчÍï­D´¶l±ãRõ¿à^EqF"BJ0+Œry, Ùx.¼< ôõ³â¹ƒ K%šr·Ö@RZ.<óûÂÏÁÓmwË0`X/æß]ûÕ;G7¡bÁ°%;=„€S…Ѹ±Ãþ Åì/Ö^ÚévX7¾èè?fI}átã•öë¥RC¨ œ†œÏ!MÈ]˜ŠâëðdÜ{ÃîrùâyºwÇá àÕå´` öb=¥„F%Ô¨äÂkÖ©„š„Ìx†¦N5ŹX•[ˆ— ãðž‡`æýÜÝ8 )A! º# bMwdú´^ÜŒú„I2õ+[SÚƒÉñjUi).ÚƒñÓIc´ tKCªè»ÐÊüÁ4¹îdokªÆÉª´rL³èÑ,ãUuê ZlìŠt‰VýØ%ݪè†ÒÐÄï7‘Ñ£à{ù<ìV£?³¯V·±‡¹õCã:@ðW¿î¥}]U7p í ‹ÕŠˆÑlÏê¸N,¥€bÃùšj´sàíÕ­ˆM¶55 ¦º-¼x}ýüymŒ6ë§CÝ)|ÚÀLç«9K¢|:»4MUŸÌ,)¿^IJà5›þÞjMÊÂþ“—ð“8_íŽc Ž@ˆS¸1޵՗×wd0.]†ù[»µk굎¸Y!°yg8\ Ù5ù‡ ÚÃ:ÈŽ#ë~LÃ{Ù³\VSêÜYSËÿð>ógú~ÕÅÕ§õ.VÕá\C+Å ²us1²7ÑÖ=Ãh±±4ΰ,Jܼ51OãÈ*¼2i:geì¹­})£«F҄߬2D³‡×ê¶Á‚Á‘qXÿžòœlø-Eëµ±N>ÁƒM71ç*ׂÅeKæì§×‡>Èü÷¹òt·äkn?¥¼'·S0ïïÑúÄxéWÁÁh9ßõ ~çœo¹ýf]†ªÑÀ‚”[âÓHWM׺šóª×uà ¶ê£(æûúX7ë[ÞâÊéëcáÏkC¥ýêxaÛªøÑÀû¿cBFDº}ÿ˜yìì§jxíÛÙ+”•³_3O_ú£:õÞ-'W&÷ïWkȺ´¹ß7x½_ãwŸ¯ÊÏï_uNÔ}«ê½·x¯xÙ[õ},+w¤£8é]ž;Þ÷¸®îÓ*øD¯¥ëÆÓÕD·¥&l¤X‰Ñ£> q&Z/¾§.ñ„,d”ûL ¦Q-þ°6Š5ºjfñ8w¼¶ðêt÷às–) B@!pÓˆXsÓPJABà»!ÐTš†ù[:(´ü7ÎèqU ŸšwÓšëö£ŒÍ LÈFÑÒh-ä4y•.ÍòcÐî#–šŠD×;J—¥bë/¡;D"ÄÑ”ûéxävÏ%¬/ÂÒØ`Øêös*ù]À¬¤¹yÄ©y˜Ÿ«†“@qVŸ¦>@—‡û£¢K‰²ân  DŽâ߬ÃÖâ‹È¡ÇçAuÛ[A3 ÝGÎNÓ€s†a}Ñ.ĪAÛ7uxõÑy8j!wåáxˆOôJ„oÓQ¤¦æÂÃ…„ƒuÐoÀpË襞º˼á”{ñ\ücëqff<´CÅ‹¬ÔùºÞ㬷óÑ)KŒx5Àï”Rr"Á¯IÉtb¼p0ŸƒÜ%ùÇØ)¸‹ír‹¶ª?Â1Ô¢ÑE8Y?z¦íÑà±rð¦Û¶Y»Uï]EqưzxøÄ](™¦DÖVä>ßÅ•©·:ìk.7qÀÎÔrþŽœ¬§€dºcº‰7Ôðá*dÿ7^Ì_á´¼°7Ñ ~•ŸtaæË°”>ùwõ’­©I_·LŠ™²h·‘Ó¬ñ#ܶÆO&ùàÈÑ3¸dŸÎóßs[ûRFg×6{ÃI-N K|ÎM¨q5Á‡‚üØÜµ•ZÁ5¥v\eþžÅšÔx.nL£nÉãWæc}Ä©.ýjÎÏÀžßý·n‡û=¥¯"Õ›ˆG+®œC;t{C1’é¾è¸nt)tÅú ˆa«1ÞM¸k:y¹ïZµÒkœOfË<ö½Nß1 ûZ‰é¾¦,Fâ)Dß&Î=áøêkÿqì٧ͳ”ÐÇïÁ-ó°ªS£”…˯)È)ï£ëùîCñ2ÍÕpçóÅÅ“<èèLDøòÁ†z¨¢ºÐ­¬ŒÂ¢Nw”IÌøË8yòÎóê<ѱFì3“~;¹å|¨ÐõÏ`Ëä$×&Kž[²ÖØç:{S NòSî¸îžçÎ^ý.fÎ/Ä´%™xnZ,Å¥kì°ÎÚdA! z"pOOe›·?‹…ƒ 5 £‘ž“¢ƒOŒ¡„ša ™8ÈAoÙ±"dN†Ë…i(¬â W»/P¨¡yõ΢cüñW†¢é4£6’6‘¦ JH †‡„_(‡ùjwˆêw—i¡F¹cÇîÄ4R¸jª©ðdî½_fQGI~¦þ¼që)„$¡¤(‡Ãi>Ô£Éÿ±ãûzM¡w–—»ŽÀ¥êRlY8Sâçc+}{Òs aþöWÝïÌ‘“¨£;N]]ÿh vd/æÑA=uNŒUƒ'Þ}^ 5ÙP÷çƒìÏÃh·õÒ ¸8ù«4-ÔLKß©ûªº&ÒãŒïãgôgXü˜s˜îÛ\0’y„è ¡·zðÛ78Ìé`¼uÝ•C~æ4–sŠî6—ðä±c(ÊIÑí^_T‚ãE]…ÚĘ–,£Ä¼nÞËÔ׿‡|ên KÀ±’"¤JXfbXç’د„ÚÄÏÁ?†ƒ»:eR1 EëtÛÒ6–2¸è‡Z¨ÁøtìJt<¥gPb¨Ô*t2wè½åÁck0­¼'dlÜŠ­³š4 ÷Vqk§D ‹—²(tÅebZ˜Kx8•K¡†ç:³ DŸÛ²’|;)<|x^Ÿ[g)ð¯7ó”™B”›ºu4Bh:Sª¹{*-?>MºœÞÚÚ—2œmq,°n•fʉD´:ii¡e‚þkE`pBw_#ÿÆ…«°eË·¿uX÷îÏã5²^ókÜjó09žÚËI¼ô«ÔŸïóz¶X(ª0uŒþ™ÙG¡@õ%Ÿ3H}õCÃÅÁ›ãFG¢»;Ž­[).$¼h<00Þ‚½ê¡Ŭ.ݘªr_ÐBÍø”löu~Ǩë–ßeôçëG˜ƒ®iUÿX™S¤ûÇ1~¯ŒçwWáKëh5Ç«¯¯ýÇhóÕ'u:Uœ-ÔŒZbÿ±ì\=³PÅüWðø¯ï»Ï?x:ÆŠclu×»„j%PÊÀ”Ggb>ã´´T$ÅOÁÓ[NŒiÝ¢\ Awд´ lÍÝŠ¬ŒTÌœ¸ß¿\o&[Õ»H+¼Œ¥;WêÜM¼¸’?&%&b m[ïï.ãñ¯jáë”XW¶NçÎùrÒprkâ§<Š…ë0¹[¥®=eI! n€€ˆ57Ov·ô÷²1fØÁVHTO/9py11~í6ØÚûÜÏus?¬i@ÃɽúÇXÊÚ%tM0zÁ‘Ó‘½DÈ\©ÅãGk½±Ô€Â·øcyX ˜jÆ¿xîE=Œ¢9w;üͧçÅûKézbƒØ$þø/BÁêÑr ø€ŸŽI¢LþÕgIß't“8ù!–ÅLÀìù«°?Cv>[‡6³‡xZ›œÝŠä¤$$''óo>Ò2¶AbÞ ÕŒ úz¹4ò"bCü8» ~t1ù9»¢ÏåýtÙhÀÞ½|îξºdz¤Q¶%ÓÓ×c´ìô!)»Œ^ë±ù"P[îAi)Ý9v ›´EE ¨ûx •‚?*Ä[ž˜–™‰œ÷£‚O«íØ·6K¬ZF²ÀâO·0î:,˜ï^ Q.6Ì@× O ÀUŸÇ.Åzª«—‹W!žâ—² È_?Ý“½k—NK}©ÃŽó')Æ!‡â”…é3[Sq´I }]©ê7ôy]ËA¢ ƒ§­gLŽ·„œGÍYœlÊG˜¯‡°6míjžE†"Ã&a³)”áˆÝŸî8@(E½ÙõrÕ`ëËáoomí½ W±Ž¥K HÄäë;À± ö†˜9s¦óo 0ON2DEg¦Ë§PXXèöWŒâ·hñÁ™çzS°ršy ø‡!AÝó/–Ò²ÈK¿êåýËÕ ’­-*,1ûè~§Kp,ÖîØŒgbCœ­l(ý ¿wÆcV„Õ¹NYòSæ,—e‡ÛFØË+oج}&Z J(š¾ú m!vê7Uh¹Xê–øKÌÛ“%x ÖîzK–>ÉþÑ÷þã¬W-tˉBÌ»{õ÷kΉÆñ[ü9m%2㸭„Γåõ|÷s&¬ÍoÑ¥I™g}ÃÙ™Ì{S`ÜJŠB%Z8SC.ª‡.t´_b<)~GÇ-É¡¥«!ç,¯E³…`u²WcÕÂ\ZÕæ QEœf¹:˜Ç•cd½\s”s qU­)­©36ðµë¹óǘéKqˆ‚tAv:|?Ü€ù’bnÆÉº&ç~² „€7F@Ü nŒŸì-nlðIþ7ûûÖvþ‚SO9ãíÒ¾‹´¸ùŸ•jŸÑa l™B¢9º¥DŸM-Êãb.âí:›Ë^ŠBE‰kw”ƒ²Â $©_äL£§-Á’%‰Æ¾‚0¹†gβp—°ãÃ…S¥ºÅŒ‚•ÓÌA_×ÃU=cXÂzlNtY&\eü‘78‹Ø™ß­CÕóï!‡ƒw¦3¹©èÚ/¢æâëÀ›Ãfw3ëç´J›6†ûq<اÔ[= ~HÌY‚£tÿÈån}Uð‰ÿ´¥/aI¢kÐÚs]ŒqC±ªø]ºšzºSP KŸz/u4œÒî=®[eô±HÕ»"£?LÖƒ¾1«S¼Z7x¯²/uX0}sNª=Þ‘ÂlrÙFž]S,¦‚w÷Ò”`ÌJüıNWLë—Ѹtà]<ÍYo˜Ã•:! 1ã·82ø)aŒ©†SGÿOƒ¾'V_²#ÄDi?\ßÂþÑ{[G\î­ ]ÇËà(ë„Staq¹))먂œÞ?•ˆÓÎ@Ç|sðì~z—ÐU4QÇò(î†>èsáq h­¯£½G‹®÷h}URpôlNàŸñ3¿wÈ8̱‘« ûyÁÒuÍ}Ó™w³´0í¾ÒQl‡ñ3lÖh6†…ØQôí0®ýÄNbØOÈÏ((¸ýÇQmŸ8Ñ}ÍóëÓ°Òº ² Ñ7é»OÅnÚ±vƒ 3ùÁê_g¢8>ê¡Ë3‰Ó)€z\]ÃzSþi ré6ÅÇ4(]5_[æ0@»]uöÿnÕß¹öÖË”±‚=¾uC¦­EЇ ë’’Qüê«xò'ïqV1ïçN5I}o‡DOÇæòé´Ì©ÂÖ$Z%ÿ£—äc‡ãåU! ®ƒ€ˆ5×Mv·ŽtX'y 6}©ƒ‚NÝy´ûðöêKë?•N6\f<£%¶šR=#Í´'Ý]dT®l‰Ÿ©­'âR2ñâø0ŒöEñ ŒÿÓéÜuž J™VIY3 f¬-š˜çPoPÖ4¥T )v[-½·5!¢·2ŒR=^W¥ÜßœDÂæIæí–Ü1cÌûŸ GÕm×Ȧóöç˰N‚–£.÷»¼c]ï.ëG>ÿ‡oZKº}©˜íu¥Ú‚fmÕQG¢ÕÇ–bêµ4IqôGÇ&÷÷Öö®ßKŽo›v;05´p›à£‚û6Ðß'$lrûØÜë쉓~Bë.Õ#5­´ôzeÝs#ß}÷YAc<œ MiÊ-‡i¯&% © É´`U⮫¾t]d~Z5ÙFã]­÷^D*]•ÜÓ¯Rãñ+¤àà{ƒ±ðéXZ´ÑŽ[,-®ž[ÇYÊŽòþú )½œ;÷ÂÔ² ŽLwVuŸUi„gdwc¥¼ ! „À5¸çš÷„€¸Í Xð×|Iß\æ®ÈÈHóς¢°®³M0º(ãÜl)®v;M¤Ï»{RæÒ¥5—\«\U¹›põˆœæÚJŸ¡É·[=~M˜¿ð`5´ž˜€ ôá÷¥p3&v:–n~;lÇ0›YªªÃ9¸vÕ$Kw5 ¢—½ÇGŒläÓß)&`ËÆNqt ·ãoi÷aë-a¦ÛÇ,ºûPúAñ™V„…9ú;ÅŸÿ(Ä ©ô¬Kjàsyï»±ìuGÑ5N*-qhàH:ø©ù¡×z:ê´[׫G.1hb'%rªæ_Ƈ®ÎCcU5 ó–ìtÙR’Ò4^+ÏLâÌj! òI‹=\íu¶µ‡ ÇßJK$Z9=Õ䬯ög´03ÞŒîjgœšd5Ev\&òKGÅÜHÎR17ú–z­Ã~/Ðm-iÝQ2Ïk±˜V=®aèùÒ)š<€8Ó•ÅÙ‚o—ƒ¢ŒšÅií3“8 …&F›”‰)ú8ò¾[ìi‰TS¼WoA±Æ nZÀ»¼_9ãpWñStûÐÖ^ËpìZ°„Æ"]y—2öÈF­”¹¶©¥êµå¹–#ætÒ×ߊÏûU×{4/ºN}K‰jûßVlƒÍÓBw5º#jË%®-ý-¿[†)«)×4•ÒRŠžscÝVº63^²!X\~ë7ž×mÓ¿@Ï@MáÉϨTþƳÙäyÏá<K÷µÿ¸WÝý2…¶¼Û\ÎE1]‘\‰–aÎp3+…'e9xß}„¥™éB)0FqáÌ»–6ŠÛ*¦p0çf¯+~A“P¬üÎéëóÆLqÃbi­‰Íù9ÈÉqûË^©yÅ­\ùqðûæ2­ÕxŸà¬‘®dGõ©J}=†ßçõÜ9òªÙ(÷oYˆ Sâ‘¶îã>gë8yj‚IB@!pãIJæÆJ Bà¶#÷ éÎôjüÓhÉ\ŠVŠ7dè«+Ã8%,M¸è¤Q˜;ËZ2WÀŠóÅ<= î³ðg'gŽÙøöZ_äÓµüæ¥ü‘ÎÃåà‰¶3ˆ[š€­i…˜ý4§æe¬këylXÅå>ã2Ø—Ôøèhæù2¸á4Zß´Ôà-5u”9Ë„1d}kËe|¸iqN¯ˆÛª4覰pºßéK£&¥ 'îEZÖ*nF%d⥓z|Ú_ŽØ™JÏ4ÑÝ!KDsžÆÝ×l‹‘±á(|F§c°ÅŠi¿`g_MMZ†ôÕÏ ØÆ¾ÊØ7:ãxŽ27°Ÿû±?ß×PŠ—6RÌp$K/õPE°òú(ÎHâ”à ’;Š–ê:wGœÑçívöy ¥ë²¶àéŸÄ⧓Ƹ=gEæ#òb^ã×> ^3ë2rµ€Óq”±Ÿxm‡ø×ÍåÜu`)ˆû©êh¦zÇx<ŒyòÒó°®ý%6›×&‘Ähª¬t;Ú¨‚4SæÊY9 Êãfý´½XULQá'‘X=És°¥]CÜ+èK´VQ!¬VqÖªøeçC+6ÆËÊRî]´f‰uF”µé™o:|èç8êºÈc}wðRºžØp`ïejû™£8ÕË((FºÌó¿+ñUä¦ÒwYÏE=ã–Ž‚ÑXÂ1öVÆûXfå=¬‹7¬ÒâUg”²0¸tïmí¥ ³žot¯ZŸƒâÉ©º_TÿK ~þdc‰µ tïVž2­½:÷†—²Ð:=Aˆ=K‹ž•¯.CžÙ®ù“Ñ£¼ô«@cû=ÚQxáªx`e¦ŽTúî:#vL û'û°½îgÜJ‘-ï=ÙÊÛ*¼{,"ZmáÊÄì,¹Ü®Û)Ëðbæ,¶7à­,Ãí6})Ë#7 ãèFÌ[׎Ÿ¦£<÷´ØÁ¤¥ˆ`´¶/ýÇq\=½+cžYŠ@ö£­/<¦—"ÚÚûdôjÆšâñ7¨óyß}¶óLõ8….pÏð‚Œý»•,c#Òf,Dúæç`µ•2^W! ¦c…´.e‡~Ÿž†Õ/!š×Æ^¶C‰9 æ¬ÁaœÜ〬zâ€Nk©OT4ãÿlaßä¹Ô÷, j¼…Ü£|*C—Ĩ«]Î ¤«Ô–UÉ(Tú·•›ô;:Ìqûò¨Q>! „Àõ±æúÙÉžBàö àc…ÍSÜ“5šÓÁfâU5CDFš±…ƒ´”µ¹˜¥Mѱ´$Ÿ£(ÆJ`<™Sê÷ÆÅ ÃѣꑵJ!X²c©Ž¿±uUª^3:1ÓNò‰b¤êÔ?z) 2…ü™‘ª~3ŽGf¶r¥ z‡ÎË83g«07«íëwÐJJCðdâhœ)<ŠÜ6ÄR¬‘ô=$@+­h«,›þêJ"m©¦LB$Q(÷ŒN%p„v8ÅYlŒ½´ìñÈ(tõµÀñ)Èæô¾ª«YØWÕddÉì«Yi†ư¸8 ;zÔÅü¨©[)ô˜ýyZJ šÞâàP5„©Çz8HZY”ö8ýÖ =e³ÚGµcGºr¡QV@ÑHFwÀ£…ØÐ2‚b^í|±„LCv e¹GÙŽ’˜âR–0€'Ý©ÎäbÓXʹG?—À*Åθ6"ö§Îý –Öï%‘Ä ?ÇX? +wÐÝÂLõÞ º} ë7;§”Vñkâ>œÇXëðdìƪp”fA :î†xjS/u(å)võA¤û½JᤫŒÁc-3Gñ„«¦Kö±ŽöpçÔ›ï ÃêÍ)x!-—ÇaÜËã°$ð$…—SXµ¯Çf'g|BÚ÷nd¬£`Í}}¢)úù#ñ×hZEëÂ\Þó̺§­ÌÁRNí­Rïmí½ ³XÏ7ÆÙq¸{)|o=Ê™„ŒÓªóü,3Ó-œæú-Ï]|.òžhhNZã8]¹:žÉï,q:(ž[õ¹öÒ¯8µsw÷èѼ_nÌpNé>ž±¥Ö:¦R7Û£Âéà´ÐÏmpV-·SUVšéÊ Ô=ùX<®y«úŽYoÅÂU¹WͳƲÒù2ÝøÁ¤µEh÷]Gf+_C潈Í!ÕÅß×þãÞ oËNN~¿¬Ç«œ±Ëýø8Ó\ ] UнŽï>c–­‹N8KØ,{«>ö,Î¥“ú~å±kïAÿ8ÜiÇ«/d¡P Ý*ƒù=ÿŒÓ§ÉØÍùÊÃ*™·3uCBú¾ð]F1Ñíž5šüV¯|—.¤K¦ç¹SûÛ7¬°aÖç?GÑÕu%«m’„€Bàæð¹|ùr§QÞÍ+\JýA ­­ ¸çž{h¶ïƒßÿþ÷ú]-«?•ïˆŽŽÆŸþô'\¹r…³tàèfõ[™í=¸:ô¥R»š¥‰}Õ¼^vP³Í¨*ôö‹bÂÓYð nɉHßwz6/Ep•2ßÖµÐEÁK-,À,¢‡2¼—,k…À5P®ºC3vƒ×™’̾ʩ¸UW=ºðQ¼Ú²ÇÞ›åº>œý•3äxéκ=½Ô£ƒyêŒ=”ÑÓ™åûÒÚÇhƒÑîî®ã‹Ò÷Ë¢»ƒéiï¾m3î3=ÔѦ=VÅ{ï1íú¼Ç¡êTÖPžç¨k¾ÎÅzÜó:oTŸûÐÖ^ËðV®.›Óv«Ù¬8$÷³z¿'w·ëí²¾áÀB$½”ï ¤oöË.çÁ­µüŽiâ!óx]‰Áƒ¹Ò/Øêºî\½.)!%3tÛÍ󦾉º^û]û…÷þãµênWööýêêKÔE<;j·evÝ`Þ³z8vçý†u\ïUîìÓîçÒë¹ëÚBY#„€¸StŽow»·[,kn÷3$í7HÀÒHã(Öbq v윤kêË`“S•ö4¼]Û#k„@Ø×ºÔÜó»÷U‚ÌMªç;9}鯽Ôsý3³A]Êwo·ûñô¾ÜÛ= ÷zÏÑk}aÚc5¼ñãÿW @àÐ0ŒþÑ( èÆï+Ôž­Ã p[çX¼Šû­Á@KS7ÛU¾«ÀýcTxc§ïÝ»r}*ªÚ×íqw<üÿ`ëãq:Èø¶_þW%þ±`*ô“°.ùïð·Öàûm;·UàWëÍmÀ¢ŸíÀÄ!Cá{o;Z¿º€ý¼ˆÂÿò^Õ?•ý~ü·âå¬B@! „€B@!Ð#ŸË—/wô˜ã7Ö~´ Ùûë¼–‘ð?>ÜØÖV‹•˲ñµ×œ\9.8]ØÝVçú´Íyw×€œ[îþ…—ŠVuïúô Pðì,vÆðUR~µ‹—¬ÃßÞçã±µ£åSL}ûu,J~3¤Àãž:>Gî†pÀ}Û²ŠaóFüz·57o±­­ ¸çž{àããƒßÿþ÷ú]-«?•ïˆŽŽÆŸþô'\¹rê󃾧BMECE÷B ‘ýìñGL¡¦e'‹Ö!³0y؃À â¹'ÿÌ)Ôüë™_áßMGâð¡ð±>‚Œ‰—ñCS¨¹|ñCücãƒX=¾>C1kÖ“8°ß»\£bب6 ëveQ! „€B@! „€è@?Ƭ¹„=»è`ãó mXn 5ª918ü{ln‹@IDAT¬Z=K·­äÐY6ÞO‹‰Af‚‚‚<þ ¤ãXGEf°õ~½_@€9òòêDåQÁ]úá³ÿóYGö׃ k’ö/?Eæ§°© íæƒÿ×Pc©µ />€üÝ?âssÛð¿ ææKì-ØŽ#Ÿ®FY‹±çƒÖ¿í±ÎÞÚÔãβñ¦¨¯,ùzÛM+O ·–€ ÿ^V‚ƒ¢ì;ìǶúJ”k¼µ‡þ×fCeY%êmýj€ÛOGiCc}=jkkÑØx#÷?jÏÕÃ{ =×a·Ù`ëògït¼v³õhlö^‹ÝÖŒzG-§¹óîº4Gµ¨ïæXí¶Fg¶NeØêÏáxÙ¹nŽÑ³¹öfG9è\ŽgNù$„€B@Ü(~³¬i»P†¶. :‘Ê ¦S04«ÓFÛ€Î1f¼Å¬é´3?:ìnï]s|¿Öü¡é=ðñŠB´ÿ…þóÛ™o82žŸ_½G;:î1—ìùæK\níÀP?Ü{ïŸåv´ãÌþãZ1ÑÈsÐóøÞÚd'oýJÀ†ã #©8V‘ ÿ~­K 7Ÿ@妧°  E•1BoE/¶£rÓ Œ\Ž5SBuÝÇ`qðΉÓiñt½ùG}›”h¯Ç‚Å €ÔwP‘<ò6hT×óâ­Qö†̉OGƒÛFëä ¼ÿÚÌk¾6–¼Ž¹¯Tâ²<Î{ïuØóè£(pkƒ±èºw4Wbñ”(wïN!IxOBÍ>vnß+xöµÃ¥Ìxm7ÖL×ëìeXQîŸ}¨ã›FÔñyÆäåo"i¤íííF~Áf?¶aÿC¨IÊz‡í´¢úPVä`yÁdQ³Ûc5pò6%Áj;‡MÉé8ôÊ\Lˆ<ÉCZðújØ+Ó¶åarP¹k9Ò rðlÎH”,‚›”P89 ;S'çé$Ò¬AÎâEyì}DùûÀbáC3Ÿ NÇèÆƒ‹Íel›j¢’°;c,õ‡)äecÍÜ×1²â5„zf—OB@! „ÀM ÐobÍ¥šÝ<—Ãh5ö#=»Ô³Ù÷>†Í;éÓ-ç[T”{>R;<øù,„_#{óú©£+µSÌ´ÍX3ÒáºÔŠÓ'_ÇêO+ñòó‹t™íNÓèüÕ Sœñ1»gˆj5kž0„±núúÒ¦>#Yz"ÀAŹêXÃÆ`t;Ò7õ¨i²#,*~CÂ0Æ7N-ŽOH*Ëñoõûú1Ï„1žŒ*sø:»QáŽõ•ÕháõŽn•u@p˜s?G½ò.nåjÑd…­¶uúÆÓ„ºsµåh´± 6Ͼgk¬E]t÷§»Ge];"£‚QWrçZÛá¯úï„ktú¨­dù¼|ý¬ CT¨ê÷ìóµå8VÏźs8wÎkh$¬~CåkZ+Gio®G%¯¦V;üüB1r‚ÛuØçveéWç>VÔ••¡¾©¾ÖPL˜{#ÊŽW¡©½Ö!#¥®én®Eæ=WÝß^®QÕþêã>aÜŒ¶4’{Ô5oÅ0 š‹ÙNÞs*Ë«Pßb#·Pr5Ù˽ȸÙx«ˆt²÷8Wn‡î\T ÎCc O<ïOCBGš÷ïçÅý¾§Ê°7Vjk–Z-Ÿ®‹³m'ªÇÅãð¾JdLñç=­íì#÷9v«Ús<ª„oÓ<õì&g“::ɽ×Á~t©N[»dM|#‡¸îÍÎBiµ´»Ÿ’v²#õê‰É©˜AqèÐAº$Q¬i<÷©^ÿf^¢‚TCðæžüxn6ŽŸkÂ#¶2âÚ˜W¶a΄PwÊò|4•=Šíù¨ODÕÎCüuƒíI†µÎÈÏkÆ£ r¼Q3ýÔ1¶rÆÍ*²ÿ¬µ~ìwQ1QÒ_´h:XÀò£÷fÂÕwÍ9È˨ÆS™‡QYŸÐ`šA—t_7E^„€B@$ÐobÍàˆ ä NµíJÃgá¥Eœñé^Ü?à Jß~›SC»Ë9t›z, X®=e©7¡üaõÇž\¡Æop 5­À›;ÒpÄ,´â¿¾¤[§å¶þœs …#qZÞ|õ¿@À @NÙÍ€ÂãòSnBηʦû¤Ú$© Ø›Êñì‚LX“òôST£6;Š=…¬>­È@Ùü¹Ø„4?¬,‘ÿrÔ Á-%½ù–O´¢|Ó³x¥Ìмãé'®ÇñÔ‚tþØŸƒk8Tà`¨ö æ.ÈÆ î³ÆyÜŠ’E!pÝlçppçdj@N~R“³Í² °àÙ¤íþì|Èîä–Óxü,0Ý8Âcûg礬{NlC¸r/á@yÓO¡ Å3—5éMünF=žš›CKn;”‰g9Nûí DU-À\Žßî0Ç7á‰jë™2vÓn!öÆ>´ÃsW Þ÷ÝMàãÙØŽÉY({9‹iÑò NÍv ²ÇiY‘~˜¼Ž¡§oÓ¦ÊMX^ޤ¼cXåo´æ›ZdÎY€Ê ¯áÄ›ÁX0÷YºAíFIr8:Ëð]n‚7?؃Ghßñ,ïVž› *Êy ËøWL4„°#‹æòÞdÜOÌzùªŽÿ‰'VÀóHiX•š‡=1缞—9ã©´ÛŒ½ã£)ì9’Ï`<2‰?IŽP„²G¡€Ì”È‘ñþ Ì ¥äE –'žeŸÑ\#a M‰c3(ÑR%çQ,.hw”¤ß{¯c|ëtÞ–ºr””×ó˜üCA0È(ËŠùiišÈßJf²³ÿ—«åð¿dnŠSuõÔG^6…3“Å fSMò7,ž&R¸s%Äć '»%ðXOÐd§[•ÊçÁvtä Ýt†–³>¬yÁO¸ŠÑKg>úëoG‹:œ)ˆt{(õWR¬©m [¦ò‘–…ù3'Rœóñ,²Ë‡ #ï}ŒŽŒç€ù¤²+±ðýc'0'Ü‚ûø9ÄWß¡œ•n¡Æ}Ëb Õ>ï|tìçïgÍЃÑÊÏŒAwåöåZ¨™ürc9U âÄx9h)X#–ÙÎz‘´ ÇN°^ ®ácÖa£0A¡F @ÛÞ?Æ:Nàƒw2èŒíRoçµÒ‡v0»gr߇ǫڕÅ,jB’²ðÑ ÕÖ÷‘D>‡÷¡Îw ^Kb­õy¨lf:ÙprçaPÁÅäpc`onèò:!™R ¯ÿƒ•Îm¶šrm’öB ,¾e±¡· »’Ul”díþHsýˆÇ‚,O.â ƒ¨îE•´÷Q©l†JåÇëõ;ìu(ábhZ’~•®×“9j(Lei¦<Öc»y†ƒµhWŠÑÜÏ‹koc©QÝŸXÆ÷>bA8-«|ÐĶ#ãÿ}SŸ«Ì§rPß\‹×)Ô¨s¹{OE#Yüýy ý1äavŒN©÷:h·U¯Ô {Åb¤¿žì×3±à©©X°ÇÁÚSæÌAËØêÙÿ_Y„?‘NQx35–¯LYS‚мxg›HE,G¥™´ƒ)L>nܧõæ9´«‹•¨n²kÁ•‡Pí$º¹ê>Ç•ŸÖÛy[g¼ÕïØŸß3I—± ýì´Ôú´ž]jÂ`§ ¨vñ±†#†ý£¶ÅËÈù´´IByv:žxôÇX°¦çž òJB@! ®@ÿ‰5Cc0KyË|QˆÜOs ¹X»ïm”›áP\kA‹›ïéÜÛî®cùáÿõp{=¿{}ºßþU^?ýoÐØ{}.ãxñ |Öv+?8€Kú\q›)Ô¨@ůŸï¾Hµ¥ç6õ¼¯lí+Ƥ™Ï8|N\^o×;5Väð ˜?1̳Žz(“šT$M åSUÚéN2ÿe=‚hü#fð‰+oÇFýaŽ¶Ì§úÇ•Ÿ ‡:•ùlÄÄ#LY(H×M@Íòt‹¢ÆÒªa]W’°m÷¨(ÙΨrñá?Zp@ø„üÁ×ø¤~NÞrŒ4}2B'Æë~]~ž}Ø^¢÷Ø­Sñr<Ý‹Tû-CÏ ­© ìêkÔÀÕ!ÁVø›ŠûaÖ/ÒƒêEy¯™-2r&ò²¦ðZ<Œ²úVgönÛáÌÑuaùo_ÆHu¼lׄ†X°hÞdÃÅÄŠødc/w|*M‹9åêªçåÚ|¯q1&-žN)½$ÿḨÅIÇ¡"h‰Ù+æhqlâ5]@3_í eȹÌñ|ê+˜ê¯g9ò9‹xûð¹\„:›bDÁ§¹€Ët‚m®# ð©%µ*×b²½®R‹óM—]¸óÅŽÁ36aÓÎw0…åS!`äÈý%6÷~^TNº}Â'¡z?µÆLfÌÅÌç/'â-Fà©©su;×¼ÿ¦auåÈïxïÐߌŽOú½÷:ÚQW^Á¶OFÞJÌ£0þAž6*³àp£q¯vÚrî0 ”k©™ŽW»–ëì<¯›â§"›·ß(ZM¦Èc Ÿ¨Å¬ÊœX³¯ õtcÝCË¥‚c/»Ý1³Õý½’BQ&Êjëqîxæ..024_5¾÷ÍJ²Ÿ(T_g„N\Ž7•’O¡§Þn•·ûÐÐ!fNó­Ãî¶¿?]ª–£D =Œ“æ{ðu<ûÄ£»`ëmôÜO> ! „€}"ÐonPÊŠfêòE8ñâvÔìY‡•±˜õøpµ¢ü S©1fàÖU?¢¾(Ù†ü+£< «`ã¦bö8sŠé>Ú÷+ÓØ±±CCËE/^ŽŸošêe½Ûªó/bæ'Ã3B)lí(?Ï_„fòù,ó6åaìˆIxÀÒŠÿ¬VNÏIµEµIRÿ2!žO·cgQ5â—3FAc¦aBç8 ív>µf{rðÄsº4¬à\=’£Â0{2 ò8lËCP~¨“_{éÇë±&Æ»8`›éñ„µKa²BôHÀŽC Åu›™ò2Þ§`h {Ü©ï)0Æ„ø¹òÓBD¥FåöÁk@]cçO4„G.ÿp̤«‘<ÓŽ,®w£éVׇGóµÄ•­§v¸ruZ AÔÃF{õ-2DQ¬rËf j%İhÙžÃkvf2l•ˆ³Þä !n;t·hÁ„§Rás$EÕ­X>æ2ŠrP¾(¾«åK…&% t½}4àÜg­HŠ™M%$Ç)ü†Q h¦”¿-ÏÎMG½-ÕhE¹‘¡/ ¢õKTšŠv!~Aœ™”yOSûåzæ¬Ä¹¦vŠ ®z¨ý05;Å…!)ÖEàuÕC² Ü¡úšz¯Ã3·Wb¦[–! n¼3UÇŠidÛ܃…Î| *³­kžšË¯ þ‘÷ÍÙ§ì8GíÙLžW&eéˆÅÃBðòÛÐD×´C¯ñOçá÷A ùE1¦cþ„¿ŒmM- 2|‹çš9¢˜£²!3Gèk E R!èGÚªû\%ê/µBæTRÄ·Ç+!ÕL><Ž.‰BÏ„™Ø^9“C•ØôgQ›[€¨´ÝÈ›Þ%·¬B@! º'Ðb +‰_n~ {ssñI])vñÏ‘Æ&.Bì}Ÿ {¿cãýk”—ºò9Ö¢.SLjë¸ðçbˆãÄ‘21«Š^v~¾ö… iº—a*Î;¢Üô^²j‹¤[DÀ|:®–Æù¾Œ-Ãñïk¢nM±ÎÈÀ¶ä0ZÖ¸ýÐæàÏ?8”¹,|:š)CYe¤~âœ62 gJ‡îr”›‚6Áw+S…Àµ £˜À#G€ ÒëÄ‘žâÉ5Øbkfî ×>~0š®U—êë[¸*ÔµÚÎ`½u- ¨Jk×joKv-–4ƒ±‹Õ%ã–Ü®)µ¶ípÛù:ý1ù•ÈY‘ƒÊÆÉ¸¼³`¼÷Ø"=ì?æQLF­éÎcž_½žFúµÉa]wÑb…ÚŒ<$…ùrv#3‹Ò•¨ãXö£%ÔHJ1Œbr¼ŒRÁaøÄк)–sd³ÀCL±;³ñ üò$¨ø63ã× „ÓW߃§žâlO>|l0i©3c‚évÀ³’}|‚Gò黑&ÏŽr>œEË‚¸– PÌ\¾]Òݶ& ¯¯`pÒqœÙ©Ä Rë*´Ý\ä Úìè™x}O -r˜õ8%³¹Í‘ǵ—·¥!t%QqžÊ1gN¦¾ÊmÂT 5ìåFP^£¤–œ5Ø”¿+®«H•èÏ@¬I|¯Ì~–m-Âñ²ãÈeÒ·ðú{1]ú,•¨âzLž5w“õ>Zd¨¨Ã--Z€˜ìo¦›=Âü[Q¾ï:$øÚêp°¨Eæßž=EFŸ´ð:í ð õܹ2Ƶ1˜%-Õ–jCFƳ„ÌONg_?Õ×õ,f^P¬x›Š3–cƾ MMÖ¢OTÒ<ÂmÁй‹õ5s|Ï>§tá2‚„Ï¥P¼¼Aì+K°œJ:JŠ$! „€Bàô¿eÍ54F²ÞJ°÷ð8äÏí&†Íͩǽ£F¹>‰E;•[¸¬ŸŽÓñ §’ϧêþ¾®vMXΙqÀéz•åŒ1€5¯m{Íia WQñsø µSá4¶jwK°žúöð>…÷fµãªB–„Àµ ÕׄËQ1c>jÆâûPŸ:™1;̢­0º±“è†sp §^æ¬3ºS8I^†í;]\¼¹t¨ýƒÆ!S2×î‹™‡œ×AGÈ ìÜþ²iqæ1ó)Çð:)Èy a§kë‡X ,H–ÓRÁúÊrä¼&F¥(ºf­™¬¯cdlp{uo‡Ûj·EÏ}ü­ÁÜÖä¶‹ÚÂSšŠ|Š×ì{¯s¦%/ñf<÷îòI¹Ò¨Y·rÔõÍGò±hîn–¿:(K6;o©ØF±Ãqª,¡QPšÎž–tc2ÖÿM,K,Ah’Ë¢ÏQ…ó‚SÆöT<»89éÊ„OÅçMBšµ ÙåX^TÊå#»œ—(gŽÌÉM+h ŸrsõŒ—ó8=yž¦;½!œé+9Ê_o¸I ‚œy(EI8똱^mô·†ñµNçs½ô\‡Ê7qÍGÈðOGfAVð^ª’•®Lyte¢>XB‘Á Ã¾‹Üú2WGÍ`[^æ P´;§´¦CÙ™f<ã³zM‹šLW3ÕŽÝ<ÖÅúX››“²ÞÇò F?ò9»_kÂâWÔ=ßÌÂYŸò9m¸£8e¸D÷ÖshõzºÈÔ¦˜¤7iådöŸÃ¢šðgAK_`PÕ×L^ª.ÇÞX‰‚úxs÷|L ×Gè(]Þ…€B@ë$àsùòåŽëÜWv»T4Tà³ÿóþÐôGÔ·ÔãJû•›Òêû}ï×ÂŒšž[Íút+ƒ ·Ñ. ÷Üs|||ðûßÿ^¿«eõ§’ã½££ÑÑÑøÓŸþ„+W®@}~àn ƒ;»5ÓJ;Áþ½踳TZ—°c9½²!\ßarjcª*ÊÛ麯`c¾¾jªçëkÅÍØ«±d žà4ËYœÆ3¸¸qCšZÔuî=Y§êº.ó˜UPo³ey¯­¯kyNxojw+Û®c­\;_;ã©s뫦áîkõט¯×:ÈÊîhMÓM#œeÜ@r”Áƒíæœ:îùª¯»Ä¨.‡Ûk6Ëq;?]ÊB@! „ÀM! bÍMÁ(…ÜJ"ÖÜJÚR—·3o#ä$M0¶§g£¡SŒ”ú} 4ûZy·ÍùýˆíFAèv/Ù „€B@!p+ˆÔ­ ,u! „€è ‡cEN KŽÂ¶ —;’ª*t6ãIþ~ .E ! „€B ÿ ˆXÓÿŒ¥! „€ýB üÿoïþ£â¼ôŽ?HŒlÆöyp ‰f-± Ó6¦5œ Ú¹u7>'(t*µ+µ‹«¸ÒÆhå‡lªHJ,»‚ä%:æ$"­”Ь!'önD³ÐˆÝî§ãœn½ r@Dl GôÞwf`B²°ô}`Þ÷½÷¾Ÿá=çÞûîø¡þçgÍ4¬$NõIJÇ©@@`^šyy¸ˆ €Àr¸íú×ÛYÎEß@@[\`ñ^Ý}‹Cóø € € € €ÀB)¬UÛ³µjî^HŸÞ}™Ð€ºº‚ ]GM£ÁÓ:R{Z³õ44У®žë¨•[@@@@… ,JX ¶©©»Ww¤»¯Ò«°Ç¨¶íÚ‘á®z¢öÌT8èPCCµZ‚×ׄÔÖТ¾a—ÒM\sâ‰'Ô60]ÇÀÙz5ÔuLµs•‡á2 € € € pÍ‹²fM µUJ-Ñ'|W k¤þÞAÓÂ×ô ã½L“+z—ÛW¤ò’4­ñÆÎ,°º‘€ZǤ’Š"S×çÏ« <]‡ËåVJjÚ+£Øb ÜvÛmºtéÒ¬ÍÙkl € € €¼—’Ö„zÔÒ+ån/U$ª k(Ø«ÞsC »\òf®Sn^¶¹Ò@0 ^–èü« ÃJ÷ù•åÜRO¯Î Ž(lbOÖ:åçÛ{¤á¡ Îš›&5¨@0(OºO~Gëò å˘ZV ð²ú‡ÂòfyåËÉUvFbx<ÝbjÌU©/¤`W—œ®ô ›òþlç{t›{¦®³?SÿÈoô>_®>^è>›SÄ\6Ó°^~EC£“òúÖ©0îúP°G¡,ó\á~utô*äN—?·@¹¾tãÒ¥@¯U”ž¥œûògô/¬ž.õö In¯ÖÊ?£ÿ‘Ö§‡†ƒ ¼üš†Â©Êòf*'7OÓ·Ìõ=Dï*0æ#cr¹Ò”¹.WyÙÓ•/£½Õ«Wë¼ ÖfÛì56@@@ÞK).\˜Lf‡‡Ú¨ªiT•u_“ßRóªuæÿ«3Ëõm#úruGBWJ*k´Å?¦g©R÷DÂ%ÉS¢C_ÏWÕ¬Vü¥Ô’J=УÕí*6÷oó›hàŒ;x2¡œ­­`û~=´>+Zñ€Ž<|P£e{µmüˆª;jUeÍ3r> Ã­ƒ3:bMÿk¾¶1™)Y_mH|¥iÿ3;”e©æG*Õ:q%yª‰Í&â›4ÕnÙT%Y6p2S²ûª:lz·%ö?î‚Ù8ó¬žìN<)¶ïÿºÖg…çüœçuàÑj%nkÊ*õø&âÉ%8 …Bò˜@nÅŠJII‘=~ýõ×õÖ[o9ǶKöüwÞ©µk×êŽ;îÐåË—5>>®ÉÉIà,Á—F“ € € € Hòš5Ã:}ªOº“ jÌ€”¡#AMÎVÕ;¦cÇžÒæÓ×ó-êr›À£f¿Š=渠Âì5A[ÃgO8AMî¶½:»'×$cÝ ûõtMv~ÂÞT¤}fÿ›[L˜`FìØÍ”2Û°šž´AMªÊví7mSÍ¡]Zc®t÷ÏÕq5hSŸ)ó`i¶üåO›2;M´a»²O5G¿)Ó•©-5g“ÕÄúoZy㬜¥mÌ(¢Ã6¨ñ¨ò©ÓV*ËsM Ó©#'‚Îýno$¨ÉÝRé<Ï¡]%Îù‰ 6Wrú·ûÇs?:Óï|Ov‚š‚­‘{jUÊt7QÏô’:NYç×h@OÚ &5W»Ù~Ó¡]eæÒ˜´)4Ï÷ð7fÏ‹/8AMÎÖ}νGŸ2í™»[›tm« Mw)™{wÝu—ÊdffÊN{²?YYYZ·ny¥í]Élšº@@@¸á‘<ã†W©0Üÿ’:Íî–ö¿ú6C¹ÝùT‡:>äd«twr‡F”êuËÎ0²+ÂØQnwt “·T[·—©p}¶™VhtÜü6S"kǸÍM{˜ê‘×ìÛ»âW¼ ¼¬N3bÅS´S›ò"£hÜyÚ]¹]§ãN #3⥳ÙôtÍfå§Û.š¾˜©?¶·×ìGû2ikNÕC;Ë¢Ó‰ÒUTV¨¦¾N½6’ûµVgêTñ¶re›[FM’]\®\3¬÷l·F·ùlå¦ÒU”ø¾fäšà¨]ƒ¹›UêL3Ê*Ü ÷7¾¨·ÂzÁ©ñ«¼0[a3ŠDéÙÚ¼-WU½z¹TyyN§Òö×ÀKmÎ(¢âÛ•÷”‘·I{·»Õõöó=D‡èÌú=˜Ž٘ʬÔÑ¡€¯T9Ù~í¬9¤¡‘ y+Ëï— eb£mlïìȻّ4l € € €¼—’Ö^h6ÙF±î÷EÃo¡v–tª¾½OMõ‡Õd¥R3U²m‡¶DsŒ™xþ\e}^G«Ó`Â4 H 0³üÌã°Y{ÅnEÅÙ —ÒýëÍÈè)³°p‹™šU¼«À Pb#aIì(öiÂMmîH?~crœð›‘¶:êÍ”¥©Ñ ;ú¦\¡q“»äO¯qË.›¹&>‰‹›L8ó[ÅX‡¾úèµêìËCÚ6#¬qú‘’©bˆ“½~£" áù¿‡üU²& öÁÕŽ´™š™«m;¶kޝ)ú| € € € €À»H^XcÖ=i2 çl-5¯ÁŽm.åoÙ£c›ÌbÂý}ê t©­½[í‡Õ7¾O—Æ‘{ßù‚ê_4“˜rŠU±½@ë|Yzþ êþ6.ЈU?Ûg´ØX(±|xtÈ,6lF¾ø³Ôÿ×-æN³°ðŒÐãŠêRÆ<ñYM|™H ©*ß¹[ë"¦.Û…‘³ÍfRج۸ mæÛRs7i÷§×Î6djͺ2>qjš׸݉æd¶îÑ F\YÊÎJ¿Ê÷àÓ–ÇŸÑ&³(s_Ÿ]mj7¯^o<üEývßQ•ÅÂ7[) € € €ÜP7´¶¸Ê†º_ИÝñéÂÈÔ#{)Ô߬‡~DíC.óv¥|mÜòYsf—3ilxzñ•ÐÔB»æ-P¯Øƒ"=½g› óüÊHOר…HÙØì$M¦irbö°Ã‘å¬]ÓÙdÖj‰ë_ ±JÕÕÔÒ ícffR™Y8q³Ó ¦º’xiÖ#_žî5¡¾Qó†'¿?ú“­ÁªU÷¹øÜdÖûg=éö)Ï Þ™è šÙO±:ÍgÚ/U]W¯sf¤ŽÝì"»¡h åýàŒ©ùtd§€Y»§ñ`µ8­‘«|ýÍÌ÷T«W†üùfÒC«¦²Ø©æ73B¯HÝüF@@@%¤‘5£js®HX˜×í±k²LèÔ¡úõ¿~P¹éz¹­ÙYçeMúôx•‰Ž7MA™yÝ·¡2f¦NZ§²\·^n=®Ž>¡Œ¨£«_Ùf-›°“¨´«þø„ò ÊT4]•d¦^mÎiÔɾVUÕ†µ­,Wç;[ÔlFý(g³|ývaa©¢46'ÊØ-šýt4Ô›¬¨@enˆœŸç·Û_ª³þL÷É*ÕmVI¾W½­'df})³lÝìaM´qâZq«t³©µ¡[UÕšµjJä õêDc»)³FkÌZ?2£˜ªÌÛ›ÆRKTóÌenTŽ)ß×Z­ÚðV•·î–ãŠ<òy=ö‰çþÜiv,T¯¥V[6ëîðÚZ"Ó¡Þo×Kv@@@@àÆ$%¬ tʾùzStaá©®fkůt¤¡]í'L¬ÙÖWhÏÆÈtžüjmêVgG«>P´Q¥Ÿ¯PWUƒúÚOšS>5G%%.µ·÷ª³±S›MXã³ ò¶7©¯³C¡¬"›pbzsiÞC¯­UK¯ tÌÝ<¹eÚS±^Oî2åʬí;}›ÑòÉ‚LìîSGkHE6¬±#xâ«vJGNDfe衚J¹ž¬U§éO´)­)ڪݛìj1ñc{¢M¹ÓuÙŽ›®d¯¬2?)Ñs…ioÈ¥#';ÍZ?6r1[êmݽ;†™ÀÇ®œ3æUâÓnóÆ«úÃu¦'§ú‘[¶Sìø¡¬y¿·*´s¨Þ¬-Ô«S ÑöÌø¤â_2S ®°½aC@@@$ráÂ…$¼.'¤a3­)=#cöÑ$¦óö­FvP‰+ú§«=æc7ûö'»EîwM¿5Ê9{•_a3UÈÉ2}_htX!3Ý'úÒ¤«T°ð˱ç3hÞ&µðûæ/iÞ††s˜¿|ÌÉ”š£±~Îþ=ÄÚ›öºZ{‹qÝþ-ÄÞüdßú´råJçíOvÅŠÈ̾ø·A]¾|Yög||Üy;ÔêÕ«£›´ € € €Àu $)¬¹®¾p  ¬Y…@@@Þ£I[`ø=êA·@@@@% ¬YR~G@@@k=8B@@@–T€°fIùi@@@H ¬Iôà@@@XRš%å§q@@@@ Q€°&у#@@@@`Ik–”ŸÆ@@@@DšDŽ@@@@% ¬YR~G@@@k=8B@@@–T€°fIùi@@@H ¬Iôà@@@XRš%å§q@@@@ Q€°&у#@@@@`Ik–”ŸÆ@@@@DšDŽ@@@@% ¬YR~G@@@k=8B@@@–T€°fIùi@@@H ¬Iôà@@@XRš%å§q@@@@ Q€°&у#@@@@`Ik–”ŸÆ@@@@DšDŽ@@@@% ¬YR~G@@@k=8B@@@–T€°fIùi@@@H ¬Iôà@@@XRš%å§q@@@@ Q 5ñ£›M ûõ—ôúÈkêý‡WôÚðk ] ÝGt¯rkmÆZå~ð£º×»V÷ÞCê¥@@@nuš›ô/à5ÐÔÿä[&¨y=)OhCŸWLdìv¯÷^í|à?h­ nØ@@@@àúR.\¸0yý·sçrxî§Ïéûݱ$]û\Á¿Ògÿég“Úv(’ÇãÑŠ+”’’¢•+W:Ÿvßž³›Ý·Ûää¤._¾ìüŒ;Ç«W¯v®ñ @@@å(Àš5Ëñ[y}ú~÷÷—,¨±Ý¶!Ñ_õüè]<·"€ € € pk ,Ê4¨Ðp¿ºº»tn`La—KwûrôOîËWv†;NTÁ@ŸÆåŠ;Û +ÍûiäWs\·åÂRÚ:åû3b7ÝrŸvêÓs?mžó¹'×ý{ý—ÒOÊ—~§4qQçßxIßþÞu;w|Jwü}Ä»Z«&.™kÝúÖ÷žŒ^“v}î=ðAŸV¥^ÒÅÑ~µüð‹jzcö¦¾{ö»úè>”¨Ùy8‹ € € €À¼IŸ<}DÕ-}³v"wó^í.ÍŽ\ õØ£Õ›µ¤9Y¸YêjšëêÔùÊšcòÇg@SWnþ½Ï}iî5jÞÿ°¾÷ïÊu÷L†ÑníüV‹vÿéA}äöÈÔ¡X‘É‘3ÚØð íÚñút¦ xâ·ÉÕ?õ~.nß®asø³Oƹq»Lƒºq–Ô„ € € °ü’:²føì³Ñ &Gû*TèK7! »ôlõIõ6Vóšm²éŠPs»õ)ªÐ¾Í9 ‡ÌH™¸Íå6÷~æ>;~Æ”uk¼¯E:Lñ}Úœ“¦Hq—ÒoÑ ¦ûõCö¹Ò Ñ æ¢Î¾øßtñƒåú¿“)¥Lò™;¦‚š—ÿ÷·ôÓÿH[²}JñnPÕô±hPsáïÿRßþ‡LíþxV¥øT^þý eö¸Æ.llûTpoAÜ·È. € € €\M ‰kÖ éd£™`“’©]Oí‰5¶;neù7èKûʾµ¾HècšY86Ý3 ?énÄÄΙD&Ë›æÜçñxåN•MŸuUB7éÁ¹_Ÿ›÷ÉþQzdQÝKçÏhÿ™èÈ÷ºu)zGÖ=¾ÈÞųúâÿø¾ýýok z-ûw Ss^§¾W§ŸÙ§³#‘;3½™·Í«õiÞ›¹ˆ € € €À-*´‘5¡þ³ê5¨žoVžP3csùе¯rB®™kÌ̶fÍŒ›ÍalÜMìóÊ·Ö™W~õóyø'ÝMºôþ;õ‹Ÿ×™rÙªªø”V9w\ÒäŠèÞ;±øæ¼.\œ”ïÎ¥¦Þ©wò’ÞŠ¶ðÿÞº¨¼w›ï`þ‰]­OÑêø@@@@8¤…5±4¥ (º&m4ÒðhhªyOV–¸KvlͪÛb5ôëü;ÆÿNûLDN¦D{08jc›+V¿‰”‹û}µ>Åe@@@¢I k†zí¸g)šhSfµšþ}µºcêØÙI-QÍ3[Ìä(E§åL¨»Ó‰Êe”Ë«®œ 1ûÁäUF¹Ø»Šþ°Fÿé÷bS—.ªëÅohß™—ôåŠ]N¥—Þ‰Õ]¤ßM†3)Ñ?󆨋ÑËë?hÖºYÀ¶>- Š € € € pK $-¬ÉÊÍ•ZÍ«¶§7·¿\{wåš7>¥*Í5®Ž†ójèé‘5¶¤§¤ROoñOßÄÞ‚Öz×êçóM…úý§¦ƒš‹¯è??S©Gkî~ã¼™Öd^Ëíý˜Ì;·Ôôá S#oF3(OúÝæ•ÝfAáß—:ÿW\3›5{mŸØ@@@@àÚ’Ö8oo2}éhêÔæÇK£‘Œ[ÙyùÑŽªÓΈºEßÞtm_ÓÕKô™'¬ÉÖSÿ½éJîü¨þlïiý™sæ’~Òö—ºøŸYHønýÉžêߦ®Š¬gc^Ïý_ÿû·Tþ§Ïè>óZïûø¡žÿgæšóWsIgÿή3÷fûĆ € € €×&´·A¹|E*·³e›T&ön¡éΛÔ]eú¬ÌˆÒ›x…gÝÍ/W@@@¸U’6²Æ™Ù¸g—þî‹uê=yPu«¼ô>yÂ#ê>ݤÎÁhRy·ãY`¸VÇÇÍ蛸†í¾'£6F_1}«~[ó6F"ž:gvÞÇ@œ)ŽìÔ—žûòÔñµïô›fî¦ûÕØ*7W¯Ùö… @@@®] åÂ… “×~ÛuÜ2¯í6?öýPéé3–¾Žú¸eVïw_Ïý´yÖk‹uò×ÿ±þ0ï_&­¹ù;òx¿²œ›BêïéÕ¹Á…MŒâÉZ§ü|{4<ÔYsÓ¤åI÷Éïñh]~¡|qÁDhXÀËê Ë›å•/'WÙ‰áQðt‹©1W¥~ÓSoÿ¨KY^·{_QÿÈoô¾¬}¬0O‘jC öôKYÙòÇ×cÚ öIÙòi@á,ù}.õœíPßÛkô`Iž\¦LOo¯GF$“ ¬Y“«<Fä»5£dzúÃÊÎój «KçF´Ê´{ÿú<¥›k]vºÓ¸iÖœ‹˜J”µ._i¾hNM¦Ñû]ÞlÝW¸NnËaîeC@@@å+ô°fèÅV¥dª¬Ð !5?ñ¨ZÏÏÉ,×7¶è`uGäBo³ªMÀSRY£-þ1=ûH•º'ïiô”èÐ×óUUU­È¥^5˜›RK*õt~@ÕÕí*6÷o³ÁËÀ=vðd´Üt=Û÷ë¡õYÑz¡cL™eŸ–íiðLƒêÚmr¿µêÔw3µóà×”ïîWm]&§ï ÔÊtC™ž wª*VéGÇô•ªÆ+úâ)Þ¥§·å9}­«37%l¦ÝFûU%"4ælÕÑ=Lø3 '««eXÏlóæ Ž{ jr·íÕÑØ=¹&¸ëÖHد§kj´óö¦"í3ûßÜb 3bÇn‘$jXMOÚ &Ue»ö›6©æÐ.­1×»¿ûç2ƒXœm4ÐfÂT=Xší»\¶N³eiïSGÍ}Gµw{‘4y^õfâ;OØ~´©wj5b³.ÌÌó¦9£s\.™ æ·™*ßY©C‡ÊÕ{Â5©Úº÷)§/Çj*ÍXSM 7²–L´ïv„O¥m÷è!•8Ë„<Ûtè¨q;ºOEžéÕ6õÛeyÌãÞm>¼‘V׉Z'¨É-¯Œ8×RyNô"#kŒ € € €ËW ©aM¸ÿ%ušgß²ÑÆ6C¹ÝùT‡:ý ¥«twöïÛ§ûÍt#·Û£4SÂc¦1¹9;æÀ[ª­Ûw©¢8Ûda…FÇÍo3})Å–”)ç–Ǧzä5û‘˜Æ¹äü ¼¬N3 ÅS´S›ò"£hÜyÚ]¹]%ÿ|ƒ"‘LHͦ§k6)ÆÊÂ[?¿CÙé¶V—²×ïÐv½qÖyóRîÆMæü„Nw›éPvîuÖ¼Y³©Ô9)múâãÚ˜ïW†™?å-Þ®»׆lÓP8¤Ñ1“99JbÏK*+ä·íº2´þl´$•—G¦`¹|*ýä‡bÕO}:9L¸ßôÇ>p±¶oôGFü¸3´qçn½ª$; € € € °\¢Ã-’Ó½À Í&ˆ(ÖýfÍgójgI§êÛûÔTXMödj¦J¶íÐ_¤ÈÌßþ\e}^G«Ó`¬¤èÈ—™7Ì8‡G3E&ì‰ßÒýëÍÈè³°p‹™šU¼«`:ì™´Cn<ʚь÷.sÚ«qåÞ -IDATŒ®ùåHجES âÔ&u<ß©ÐÆlýì´¹hFçDÆ©£@÷gÇ‚—üù9:ÛÜ¢Ç꺕ð8‘ì)Ú!òãc_4}1SɼqA’s.Z:ñ#¬Kæ„§  ñVîlý¡ÓÂÈšD.Ž@@@XfÉ k̺)MfÝ™œ­¥q¡Kù[öèØ&³˜pŸz]jkïV{ãaõïÓã¥W¾Í(ð/¨þE3q(§XÛ ´Î—¥¡çªîom˜²€-Zl,”X><:d–²ýYêÿëèÂÂyqiˆ­::z'¾—Çy¥’îöØ&]eÛrÕÑØ¡Àp™†~4(}h«rcÕ¤Œ›?Þ©õlÌÐÿÂW‘>9Å›µ½ G>d®ªRûBBû±Ü'¾SñûÑÇ %DA¦DXfP € € €,s¤Mƒê~ÁYXøÓ…‘©GÖ!Ô߬‡~DíC.ùüùÚ¸å!³æÌ.g*ÒØ°]|%²…Ì,žèžú_±EzzÏ6晩Déé»);µ¼Ëdš&'ØX îŒ,g–QgS[dM˜è…@c•Y„ø€YóeH/˜…„=%ešî©)”bR3‚¦#0µ 99¬Žv3ÇôØN½²[F~™3½¨Ñ.öµßÅ[òçÎSB#ú…3C©R{¶•š7@ù”nêqr•´+§pEZ¸Æßn¯þ±‰à&^z^=Ó¤fÍáÓJ\/ÙL) …4aEŽg*&–‰ôe¶sרKŠ#€ € € €ÀI kFÕæ,,ü ÌÁS›Ûcß³4¡S‡èÔ™.õ:u¢¾Ñ™䱩Et›èhÐñÍê6«Ó8ÓƒÌÔ©SgÔÓÓ¥GQc¯I<&GÔÑY+&ì„;íª?~Bm=ñኩÐL½Š,bܪªÚSæõÞ=j=~@ fÔr6Ë×o–6—ÆæDE;ýènüªžmŽ´}ü@Udý›ârMÍlrûõÙ3WjÂv¾ö;6¬&±ç(:*fÌ<ß ûü]m:bßte/žïRWdÊÖÌ;g(3¯'g¨lG4Õ=v@§»z8sJUÕ­‘bÑéV¡`³}ôQU5ó±ã¯œŠÛ“6\³e‹–‰T•9÷èWšÂ/ç¿@@@@à] $%¬ tªÃd›¢ Oõ2£Xû+Jä1#VÚOšWc×WGߘÖWhÏF»Î‹[ùMÐ`â›ÎŽVýrÌ¥ÒÏW8onêk?©ººuôg«¤Ä.X<¡ÎF³VŒÙónpF·ôuvè¬YØÆåš~켡 {ÌÛr35Ökê:5wÊ“[¦ý;׫³éoÌ@™råÛi–-37GÝ­‘¶;'´¦h‡ö›WlÇo¹¥EÎá£s숟„®øõHE±é3¦ûü MêÏ.R‰ {4¨Æ3/Ën3¦_y?0KçìÈŸ©Í¥Uf?-z*£ð!Un6Žƒji¨SýIó*ðœÜÈÃéÞÈÈŸè°¤´ØZ9±ãøj£õ{ceì±¹î,ã.ZŽ@@@x÷).\˜|÷Õ̬!¤a3­)=#cÎ)Aa3ÇŽqÍò§™µÙc;õÆnöíOv‹Üïš~k”sö*¿ÌÛ—"Ó~¦ï +dÞ¸”ª˜júŸ{B‡,íýÖ×”m"!§yÅ^RßÒЙZUìÕ¶CÇT7`rm!„ 1ðARH†,Ȱ!Dì ØvËyØðÏ’,–ŠR…2¶/v+ÖlŽ˜gkÍv°³wÅShÞ±Æîºü—/»À­ÛOÅñf+T\#€ãO˜þò½?§'»yriè@eP-Ð#0pgð„PˆÀ2I€ÀÃòÉÄ2YKa%C)¬ƒMP ;`7샃pZàœ pºá<€^€—0`AÂ@˜ˆ¢˜ VˆâŠx!H8…$ IH*"FäÈRdRŠT ÕÈ.¤ù9ŽœA.!=È=¤DÞ"_PJGÕQ]Ô†º¢¾hƒÎGSÑl4-B×¢Uh-zmFÏ WÐ[h/úÆކcá p68Wœ?.—ˆKÁIqËq%¸J\-®׆ëÄÝÀõâ^á>ã‰x&ž·Á{àCð±x>¿_†¯ÆïÃ7ãÏáoàûðCøïA‡`Ep'ps©„E„bB%a/áá<áa€ðH$²ˆfDb1˜F\B,#n#6Û‰=Ä~â0‰DÒ"Y‘‘id}²9ˆœH“ É•äýäSäëägäŠ Å„âN‰ ð)‹)å”=”6Ê5Êe„ªJ5£zRc¨iÔ•Ô*j#õ<õ!õF3¤¹ÑfÓD´Zíí"­ö™®F·¤ûÓçÑåôµô:z;ýýƒÁ0eø02ÆZF=ã,ã1ã“SÉV‰£ÄWZ¡T£Ô¬t]éµ2EÙDÙWyr¾r¥òåkʯT(*¦*þ*\•å*5*ÇUî¨ «2UíU#T3UËT÷«^R}®FR3U Tã«©íV;«ÖÏÄ1˜þLss󤡦1]#N#O£Fã¤F/ Ç2eqX¬rÖaÖmÖ—)ºS|§¦¬™Ò8åú”šS5}4š%šMš·4¿h±µµÒµÖkµh=ÒÆk[jÏÖ^¤½]û¼ö«©êS=¦ò¦–L=<õ¾ªc©¥³Dg·ÎUa]=Ý`]‰îݳº¯ôXz>zizõNé ê3õ½ôEúõOë¿`k°}Ùì*ö9öŽAˆÜ`—A—Áˆ¡™a¬a¡a“á##ª‘«QŠÑF££!c}ã™ÆKŒï›PL\M„&›M:M>šš™Æ›®6m1}n¦iÆ1Ë7k0{hÎ0÷6Ï6¯5¿iA´pµH·ØfÑm‰Z:Y -k,¯Y¡VÎV"«mV=Ök7k±u­õº¯M®MƒMŸ-Ë6ܶжÅöõ4ãi‰ÓÖOëœöÝÎÉ.ÃnÝ{5ûPûBû6û·–<‡‡›Ž Ç ÇŽ­Žo¦[MLß>ý®Ói¦Ój§§oÎ.ÎRçFçAc—$—­.w\Õ]#]Ë\/ºÜüÜV¸pûìîì.s?ìþ§‡GºÇ~ç3Ìffì™ÑïièÉõÜåÙëÅöJòÚéÕëmàÍõ®õ~âcäÃ÷ÙëóÌ×Â7Í÷€ïk?;?©ß1¿þîþËüÛpÁ%]j±Õƒ ƒRƒ‚†‚‚—·‡BÂBÖ‡ÜáèrxœzÎP¨Kè²Ðsaô°è°ê°'á–áÒð¶™èÌЙf>œe2K<«%"8"EšEfGþ:›8;rvÍì§QöQK£:£™Ñ £÷Gˆñ‹)yk+íˆSŽ›W÷1> ¾"¾wδ9Ëæ\IÐN%´&’ã÷&Ï œ»iîÀ<§yÅónÏ7›Ÿ7ÿÒí N.T^È]x$‰Ÿ´?é+7‚[ËNæ$oMâùó6ó^ò}øùƒOA…àYŠgJEÊóTÏÔ ©ƒBoa¥ð•È_T-z“’¶#íczDz]úhF|FS&93)ó¸XMœ.>—¥—•—Õ#±’Kz³Ý³7eIä{sœù9­2u¬˜¹*7—ÿ ïËõÊ­Éý´(nÑ‘<Õv¸ãˆë‘Æ£&G·c+iFš7µ[z[Z{އïhóh;ö«í¯u' NÔœÔ8Y~ŠzªèÔèéüÓÃí’öWgRÏôw,ìxpvÎÙ›çfŸë:vþâ…  g;};O_ô¼xâ’û¥ã—]/·\q¾Ò|Õéê±ßœ~;ÖåÜÕ|ÍåZk·[w[ÏŒžS×½¯Ÿ¹pãÂMÎÍ+·fÝê¹{ûîywzïòï>¿—qïÍýÜû# ––|Òú´ï³ëçÎ/ñ_ž,úJúZõÍâ[Û÷°ïG3GG%\)w¬ÀaO4%àm#«º¨Jã5ð˜¯Û1VÔï S´ÿâñ:ylÄ Î ¶ ¼`;f&Ó±^QÎÅøêè8i˜GÑrRÆ¡K±ÒäÓèè;]RÀ7éèèȶÑÑo{°Zý@{öxí­PU*ÌXÚ–WýQ ðü½ýuEþªcÜ®‹ pHYs  šœ IDATxí] x•Õ™~oöB°dº"TÅ…‘ÑZ§RZk©Õ.ÚZ§3mé:ÏÔÎÓ±cŽC;Vk­µ.ÅqQ@EEDd7,a Ù—›dÞ÷Üû]~®IHA¼ÿ=Oþÿœóå¿ÿwÞ|ç|ßYþÀ¢q-ˆ»Ó‚¯L¿£FBbb"œ¯pô•””Écáè2V^~[W ˆ¤)l—ò[Ø|1Ht9ÑZó½4—¡[¨–6ãä8ºƒqÐuWãu¶Ë8èÚeO<±;8]wp5^g»ˆƒ®]öÄ»ƒqÐuWãu¶Ë8èÚeO<±;8]wp5^g»ˆƒ®]öÄ»ƒqÐuWãu¶Ë8èÚeO<±;8Ô•žuN.^u:üŒÿ†EƒÏípÞ{Ƙæå¡çlÚÇO]ŽŸÞõcÀXÿ¡qÐÅz Ÿ†ïÓÝëÉâwKKKh ™Öœ%êÿTëÉZÐÒÔL¾¥ÓgÆ“õؘ­'º¶š6   ˆ¥–Æ4®EK0ˆ@r2’2{ À•f8óÛª7NGtÑ 0ÐHb1,@µ´4Sªµ 9'=Ϥœl4*GÕ¦-VUxÉN˜¿EÒ0ºÎxüÄAçe‡Ž4“pZËßÒDJïÞ0ízôÿÌ5H)胺]{±ë‰§±wî‹h,?Œ„ä”POÞªãᣈ+GyIZ‘æ$¥VKSÓR‘?yо5=†!!%Y#ÎDÑm_Eï‰rœ—÷¬q×>ü º(€`à´9…WsC’{ç¡×¤ ¾tŽçš8›‘–ß9çEr¯^”†NÉP÷wísÀßÝk Ö•:°‰_ÍÃd±‡T…››B…C ÒŽ('pr…é+^»¨ó·¤‹f Á┇ I$!9‡ãÐÒÖÖqÑÅK¦“úe(_ù)*wÇ倿%]X*9©ÅpKS=СߵW#kä0yoö>ÿª·nÃþ—_CJÏ\\7éT$ª÷ìEÉsP¶d)Z(4®ãøÏö„šô@ÆO rK1½¹ÂÙê2¤Q8 5;w"shμãHïW€ÌáCP·s*7lAÞ% çœQ¨Þ¾—,CSMMVÑŒ$ø0àOÐÙXN¾¨ñÇfC‡ ™àÛ3w¾šÆvM”XY#†âÌú:R{ç;¥Bã¦ÚZv»4¡Pªiš,gühæù&rÆŒFUñ6$ggc×ßæ¸.W§ñ)îY^ÉçC¼¹Wö'èÂ’N6¹f.+F¤0ô»þïhNÁÞ9/à0írê&Ë+À•$9ã9ó•…¬³F`×cÿ‡­¿ž…žçÅÁ·ÞAåÖbN‘}¹cG;¦f )BïË.Áî'Ÿfœ],Aç ÎŒYwî2úôækÐIú¸‚$5¿ß~3r ,Ê>Úáò±~÷^T¾¿‰ Ð$Rü߸i¯‹ß „«ÅÁ7–¡|Õ{VT¡™ñšm;Q¹¹EÑ@ VÐÆ'Ð:MVö;JRëÊCš‡OÇ×ö'è¬½Ý¸Ž˜ÊÌDj^žœ’’ózºõr©}{cô½¿p’ê½;~Œú½{©(ðÀBN úÆWX·ÿáQ4©Àáåïâƒ{f!oâŨں{Ÿ]à$¨ÖÛÉIÉÅ9 '#O ‘üt÷7èÔÒwµ%»±ï¹1àÆDbjгËÕlÛÁõsÙh¬¨$-5,¥LÙ¸©1sÆŽBÞ¥ÚúM(}q¾N‚‹¤ çOž€>W_Éžµ ûæÌÃáﺅp«ý³cßÕ  çÄu¥Í”\²ÃmûÃ_pã¸fίVi¦`Ó¦=ÙâÊʤ!Xã³Ý³Ÿu5QâNû Jú«+¬­a¾$äMº#þC¤ôé­G!kø™Øü«ßR^N« ëñü—ÁG7û×õÇ+[C‡µÖȬãÁÊJ”½¶uœÄÏ6Ø!25?¿ô9äM¸Ài¨rËËX@ÙPv/AñoïGÙË™F-µ± IÙ™\a|.RiRQw¬ÇfslØ“ŠF‚–Gq¬ÇÌþày+oé/I›ãÃó…PÁ±WÏq£1ø;·¢ÇÀþØõøÓÔLW8e¡©²ŠEhËc6Wo‰4§$¤5áð²U\²^‚«/Cæ°!\I<- AJÌZ{LHo ÛH)*{ Ÿ'¦ø tÎ\¡·&œƒàq³´³iDÎØ³Ñ‡¦¹>ÑW—b»ÝäÜjšg•½ehŸkä<¬V'¤&£`ê8óÛ·¢œ«4Úê-\•²øuäŒ;Ù£GºúÊ^ ‡8Þk®¯wãF‡D—â¿›¯@ç:4j©I9[@œ¹q[BZ …O*Ön ñ RûæcÿÂ%ì*ZØÍœüo¡rÐÈyX¹ôý‘VØu{ö¹©±ZNŸ¢T¬â¨&­¹ãƒ*×ovc¸Ü Æsh3ÓW¢úƒmÎtâ$¦‰MÿaÎ?’ÎI4I6*I4‘d=™4ê÷—¡v×n§$”¯Zƒâû  %5œÊÊŸ<Ñ™@Ê^{û_xÉ\ž®.9cÈ ~áì|ðQع›ãº7¸öî}j´å!c2=¯bÍzÒ×;åD’29+Ú¥ˆøÙùCÒKs°‘R¬}¯Ÿ‚‚«.uõ•›·bÏ“Ïà°–,q¼U±n§À¸ÿ¡?ŽÓ.Goj¡GŽÐ$²ƒïüº›YØóÔs¨â\«¦Áò&^àfª?ØŽý/½â¤œ•@SKJïžnñg»Õº=¥®{ÕžY ýªJøt’pDBBj ?;E· I\"'­²ÇÀB¬ŸùK®&)v@LLOsÒI]¬Ì)e‹^gw\‹äŒ šC4—<þ ²¹ºxä]3ÑçòKÐXYM)–H„ó­çiܨٌþŸ½Ù#†¹E û潌²WÞpšr‚öÊú´‹õè€fJ¥ìQ#Ћ32öº±—f¸ SþÜóÎáÒ¦v¹vNR±±;ÿô8Jy’]/XR*ÖÜñ#®áD?Çmi½òÑLP*Þpà@hÖe\S—1x—>}yŽwàÖ-—sµ[¨|HRꟀò¥´óè$Q¸C?Àp±¥4WÍ… MÎg·*û™À&šŒÂi}¹­0È v­ŽÎY…†‘F%"ÿбNr•/_…müµ{K¹¼©eÛAuQRæ^0ÖN¶@-*¨=}®ºŒ«ßC寭n…²Çwþ„ »¼Úí%¨åØ çŽq; ‰¡QUc9ÇllhIT" ¦L¦q ¥P‚§í_ôš*kÐãÌAþ“ï¡çE硉ÇIlá\ëÞ9/bóÚ{ݲu7·ªb” )YÙ’e!‰&±À¥pjM«Uül¯óè4h§ñ·áÀ!n®™MI”Êù8ÆKAeÉ..Uz GV­uÝdn0Ç|2ËeÐ଩Céó ¸Cl8§·.r’/±WOä_6‰‹5ßFÕ–-d””Z&¥.ºŠs·R4¤Lˆ.IbVmÞ†jËšãõ£”Oý:i“\R¹f×Âýž[ —¸îMK’ެyß)aÏ‹>‰tŽ]°\3 9;—v»×ÜŠàZÚæzÐF'W­9Ú‰ÙÊp¬ êº›¸Èóð²wœa¹pú \ü™ç$ëEK°kö3h ™&@0FŒÕ®6ÿÜü:I‚N¤%q´u°–ûV}›i%N¤„RZç^T ƒ®™³M×åê­Û±é—¿A|6Rjî¥ô ²kÖD•‘6ê´dJÕÆòrìxøqTðd§ô3ÐÀ\Cœ23±ëmãÚklÿ§9e@LÂm:$íêr«>ôúçÞçPÒ @þU—ºÙþº%KÒ`›9Cq€3G¸DI¦wLX@»ÂjuáªÃ€D“ˆºóRšIdª‘‚ÑÄÝf Rdø¼ÀV1?9H:µ(%PØÌ„f¤(¸¸´K¥ ÚÍ_<ëne°ŠY½ŽÝêvšVhW“c¾ú}ìyÜ„ŽÈàHwú‚'àØQã6®£r‰=>ºÈêóßÍ?  K I<J¶y„Æ<2—TmþÀ)Ä$ÒÃÙP„JµGõy¦$í¹pàe8"ùø<·¸@¢ò‹äGçÐ lt&•B±P“C#0´©Z¦—_RPãÁpyÙÛØW:p)=BE°ðÂà9âXw$ ‘4B*Z ¿°uOu)®Úº/7ð¨Vç?DS—¨= ¼>dÖPùVêˆTçM‹Î)ä¯@̃n[C-^-ÛK!ˆ9-ÕØTWí¯> ß6¦»×É›–!cÙ2LðA*­‰øÍ¬ÿÁš5k0yæLšáN¯ÿ·øgšNÃÿŽÎþ$5b3»ÖýM X—Á9Íü îùŸ?!7!ƒSÓQÝÌ.”.ƒ›iâîÔp ¦9-¨er4‹ƒÿ;v`àÀxnýZ¼[Pà†j£y*zeŸ^xkÓFüºß™ijJ2ewó޽NM;øê)1 ºÚj™t-ûJñæo`ú¿ÈÅ&-¸ÿá‡qÑE!‡ß…ؼüu²¡C‡¢€ó§ÙTNgõLˈW¿õ&ºýeO¯ÍI|ÝEÔT§íÙˆv¬E~Sï¼µÌÕ>qâD4pn4›ÜÈ 0C† qáw6¬GáÙ£°¡>®l8†tÓ-&A¤4»¯l'Ê9ßùöÊ•x¾îžœ=Û±pÉ’%¸öÚk]Xæ”t® n¢¡x'+,,D—(çÙÃÆ]÷p &A×È ü³œ={ö`üøñxkÅÛØ½¿‡j•‹5ܦñž\~^/¤°kC®UVbL‚N£±Q«pÛŒ¨¨¨À9眃#\)Ò‹‡VËÍš5Ëù÷Üsóoºé&çë¶oï>dQ“è",9é˜]2—„LÊÌE൸ë§?uL³1œ"Ë—/w´U«V9ßnëm¤&Û›“ûqÐWN¾“ Kd÷˜Ÿ”‚©™yxû…ùXF±×ÉPÜš{æé§Ñ§6ˆtŽãf“Ö8trh1 :±&…Ò®KÎ>\ƒŸýÓ÷°iÓ¦ÇÆŽëÂãÆ‹Ð6lØ€ÿú·_`r ’”q×},,³cfÙäpFbAÅAüåH)6n܈áÇG¸)íµš®‰ïÝþm$s ûÔl~dNcºÀN÷Êôk0jÔ(7e§i:IäÖ®$®L]y,lqËoåå·ué-Ma»D³°ùbšèr¢µæ{i.C·˜6«›í˜‚ÉY½PÁ-„#FŒpÿjßRµi†®ž»ï›ý7ü±pdH‰3µ žÅÉ'Șx“Dh|—Õs+Ëpë_FÏ2ÙÎi± ûôC"÷¶ø’µ@“ù?JÑ/é{ª.ñÇž¥°9ÑLšídú1:1Kc´>ÜxóžýQ1ox®zðº&­’’ÓÇÍѦ3_w3\¿§-§gwÄPNÔ׳¬ŽèçFÿ–“ B_€NM™Î‰ü+ØÍ6°a¹DÓñ8‹‹4:ù'ú%{0.‚ötu°ý£Ûê„âjh` ,¿µKc+KG|wÂ@+¿Ì[7Ù}æ <~]¿Ážc¿Í›¿³á˜Wn¨Û̦‚`€sÌ [ã’m€Ì¸[ÞY.ž¤üÖ°Çó½À´°Á@êÞï,ç—Õ­r¶¼¦(xéJSÜœ…íyF ;ʦ;$½ÊšŒó¦™TóÒT":Þ¦v5¯fMð ÓD {}…u©á¥½zóE‡W>ùvyã []–ÞZ™þýûGÀ§2Ÿü®¸˜]4` X:ŠpÚúßw…O§¤Ì´0€Úk̶ÒÚ¢Ûo?]œ‘ -SÈ›ËVž½BТ}å9+h[€]à‹»ÎsÀÛ prÑ~gj9Ðy_Þ$œAí£¯y×Ç-¬¥_™÷Ò;ð:û>1:—cˆä ¢ |R ¬é,“üž_’ήhà9^‡¥_GùS ³—È3„𸅵æT••£¬¬u¡ý9–mÆ_fot¶<«ïãàëw?òĺÐïÄó,Æ.$O‚“¤k«‹íŠ´‹Ð…`v”Æ/I8¥©kÕ\lÄÕíÆÝÓ¯EnÑuè[4™yßÅj~£¤v×*Ü|Ûên]-þ4ýÓHÊšô¡+-ãv¬ÐwRºàjKVâæ[V„~w])>ýÕ`oø© ÕSD{†MÒyÁçÍÔðÅœö*ˆ [‚— Óࢻ×=ËçãÇ ¦`ËÛP”T‹Mëö¡€G”RxPM v“KÇøåÄÏII5øý_Að?þˆ;Çg¡ŠÇò O]ržÁÂÃTBR þTâ8ð8¾“âLÒ pÒj0 ëŠ{µÝã=4f$]k/ª1\|“0ì•tµû÷‰å8x„f‚¤t [„Üp#7àõ¹8I”œûcÌÛúäЀå}0"¡®þÑ”° ÛóÚ#8÷–Å\~;}~>wŸ‹÷½¦>MêQ—–™…ÜÜ,dfr÷§ÞÒrzÂhú Á}kð“« å$§}.-Þóö¿ÁêråoàѬ_ÆænÀ°Þ}0lÄ|"M+›°ò…W1ö_Ç¥yªSZn½{ª^Œ¹gág »±pílZ¸c¾öy ÿÏM)Çü;×ãן¡Š;ä‚%qwËÜuëyÈä':^| fÎÂSKK°sÕSö]‚p¡“ŽKn¾“gcÙöK»ŠÀ ¼>çŒé›‹áW^…ï'¤¢¸œ KÊÃÃSðê/à‰×w»ßÄÃFIç~È©…‹¾ƒ1óé_p2ùÞq]Gæ}ù˜ÆpæÐxãh#$åúp§ŽqŸž†`ÉÿbZðy<¶’].]âÐ^È Wú˜" ¨,åǃS½%ŠXƒFäâïÿí,üê±ùøíöã?~0 ߘ° ÿùYü8ñ:\yVèC)á*ÛõštRT Ç3ãIíy‰¨jhB]Ω¯¨ªj¼’2œà)îÑù2Uï?‹”‚Ï` 1uÊyÅC…".a Âç6FH dvyÁg™Œfñãù1:{YŸI8£{Aw`ó¼¿ï¨M¡Š ì™Ãç)4š‹­„×OÇÄÏOÂê™Ïá}7x«Å ü†Dúç¦`8¥ÂYS¦ úÁßá!\ˆOöÍÃÅÿ0÷þóƒ¸ü߯Â@o5Ç §À×°÷ÍÝêrVmZ‚[^-Çg/ˆÁMDŸ˜÷~¥KÛ8.–$^‰O ã7dŽþÃ)QÝëüÅ<ˆ›áòõ+qws=FŸ‘]kWSYú:ÑIžÄÁ£ñí„ì¾ýBÊ= mÂe¼/Á×®äò¶uãçTPsLb!~ýÞ]¸þœ‘|cèßäGOþŸ+¢Tŧ°úoßÄØ±“1]eÎÅÿ½ûŸÌV¬Ná¤>¥ ×­xh&R¯ÛâH?}ö\Ñ7_Í.ùÛ(L|c¾øLµ_ú7Â=Ùü-­9“d^ßòux1±G€& 'ÅÁùäJ7]ó}§µòˆC”4ÔáÎ-¿¬«EUE¿–“›©Fí˜S¹º`"µÏŽ—éHÍÁc&ü›øÛê9vì4hp¦‰`ªhÍNËä"ÔpÅÑ&‹`bá!ÞuU•&e!+Ý(Wž‡ é÷Ƀ` ÇpÇîóÖ÷ÒËe6lRh–IæbX­l±K+Rl•ŠÊ(~ÖéNÊ2=o’$°ñê¬S¹.šÓ:ñ(Zë›[q\~<À pr2Å|ȹòa*ÍE™ÇAAkîCuv‚p|Xv¢²xÖ8:Â_€NK×v„‡¾È£®ÓÛåžÈKûtbPxžq͉pÍgem%±tÞpgÙqœÞ¼³Õ}ôù«£Ã7} Dúktôÿsè 4ÒÀ©ãÄäËÂt mQ-²éIùBÒz™ÌØÑï.÷Á¸(à)¡ÞFVØâ¶¸Þñ^š…ÍW>»D³KeM!0_Š‚mË4?---RÞê1(ÞYs Èbނ͘ҟ æz ‚Œ@£Á38/<7ËŒnºÌ•AMÚð©rùùîFE[¾ÀbiÞ°`tóf¾Ñ­œÍ|ù<•÷^â‰â]q1:ͤ’“n¼é+6n5ÁÓ¹×%7ðA»ÿ¥•ië!Z´y…ù$ùD'' lÌîâ]an[eôÛ¼Îpm’Ç€íPÌW£+쫌âJ³ò6ßò[YNažÑ­¼×WvÙ;DÇÞ–S ÓK’'“¤]è¢ÕAÑân§«§ÕµòDL㇯)ïØPŸ›£%M`°KuŸ,çy¼«RÏScK¨‘õ›·ïHØòFûª+ºNÕ'§ç)ìžh@ùVÖÀwuâ S#ZÃéÝC¢ò¤‡Â2ÞeÒJeVÜyÅš%k@³²¸KùO‘ó6¾ÅÀf¾\a]LošÒ£/«ËÀâͯ°ÝÂò t–×|Õ!gô²F´Ž¸˜][/*öDêPÕ¥ Y’‚•a_ô"SRNÝ­S 4%ë evn¿©¡Mò ÔÈÖà^ß¶<Þ2¢y/¥)n¾Ê+¬g*l—ÅMÚÝ[—Â'âbtN‰0iF If…¤yxNxñ¦Ž×i¬2f M£…$݉0¶+eÕà¸{X®¸·ñÛ [9+cqù¶è°òê¹¢[½<óne­.ùvuæ}ctaHEÞüpRÍ«àIŒ9aŸ“l2“#5º€j`/¼aÜ o {}‹Õ¥¸=ÓÒ,n¾å®Gù½.:îM‹Ç è¼/’o™…N4‰ç:S2M@SG¡.WáÐÊâ37ç ­;|“|ocÝh4Å-le,ù&Å[»TNy¼Ï°:­+§|véý½áÎð#¦@g s ä(µšVš: xêXCôÐÒ§f%ÒI³ÕhÎûB¤SzWëÁ @^?m¥ybyŽ7ݽ3ŸéÍgÏQ {ËÐä›ó†ÖžS ‹~Qµ0ð$­ÔÕ TR.ÃàÀBì5ä¤éz¥›7lyºÃ7@¼q/¼a“JVÆÒ 4^ÀX}JóÒUæx4¥{/½¿â]q1:±Á /ðB â°ËÖP#|sú­“rRW8ze ÑŠŽGKCZkåE‹”ÎèV¿ÊâÞ²z-K·°×W¸£.æ@§oxJŒ 2Ôå†H" Q<ÙBÑSr70´æ{àM7Àµ•îÍÛ^ØÇèb€Â­ùŽØ‰[L‚Î1†·£PÃhRwKç€Π4ËÎåòœê›5ºÑ½q˜—öæiˆ&{f´¯z½yÏ<ݩҺêbtŽIa® B4³Âôæ ùH” o#·ŽÚñâ­Õ]¦µ<^šñÅhïŠÓ 3†̼àSZDúYFÿÈžZ:TƒFƒ¡µ¸5|GýÖê­­òÑiz ËÛ¹7j=·/@g¯nà³x4þQùjØŽ8@wùú Ñuwäwu4¯@Í”Ž5qt©îG7öGïη<±™Ûîüeñºc–qÐÅlÓž¾/öÿLŠÅAµ±öIEND®B`‚pyramid-1.6/docs/narr/project.png0000644000076500000240000040417212606630333017632 0ustar michaelstaff00000000000000‰PNG  IHDRk@œåàæ ÉiCCPICC ProfileH ­–wTSÙ‡÷½éˆ€”Ð;R¤K¯¤ 6BH(!„"ƒ#0¢¨ˆ`EGD cA,ØÁÞdPQŸƒ*ï̬·Þü÷ÎZûžïîó»ûž}ÊZ€ÞÊ•H2P€L±LìÇž“È&ýdPÁ F\^ŽÄ722þ±}¸ ˆbð†"Ö?Êþ÷€*_Ã@"±ád~/㣘mçI¤2\ æ7^$“(8cu)6AŒËœ:Î;œ<ÎØ·˜&&ÊÓ\ Ó¹\i*í&ægçòR±8´÷Û‰ù"1Ýc/žËÇ3°ÎÌÌRð:ŒÍ“ÿ'õoÌå&OÆärS'y<ìKìÇ¢IwñØËÿó‘™!ÇÖk¬éaOzNztÖ›bk–ÇãFO°PÀQìÙ˜_"ó‹š`‘Œ3ÁByHìËÓc}'8=+lR/Nž1áçåøck?3_?Á|A@àK³¢&õ9¹Ñ“þ|¡ÿ¬ M7T±ßcsãJ1ú 2‚'ÿ+‘ENÎSœ1k2—iФFóW¾2aLÈDv&8EÄ™`¡4dÒ/É;ÓcsÊ£&×A Ž\C>7`rm!„ 1ðARH†,Ȱ!Dì ØvËyØðÏ’,–ŠR…2¶/v+ÖlŽ˜gkÍv°³wÅShÞ±Æîºü—/»À­ÛOÅñf+T\#€ãO˜þò½?§'»yriè@eP-Ð#0pgð„PˆÀ2I€ÀÃòÉÄ2YKa%C)¬ƒMP ;`7샃pZàœ pºá<€^€—0`AÂ@˜ˆ¢˜ VˆâŠx!H8…$ IH*"FäÈRdRŠT ÕÈ.¤ù9ŽœA.!=È=¤DÞ"_PJGÕQ]Ô†º¢¾hƒÎGSÑl4-B×¢Uh-zmFÏ WÐ[h/úÆކcá p68Wœ?.—ˆKÁIqËq%¸J\-®׆ëÄÝÀõâ^á>ã‰x&ž·Á{àCð±x>¿_†¯ÆïÃ7ãÏáoàûðCøïA‡`Ep'ps©„E„bB%a/áá<áa€ðH$²ˆfDb1˜F\B,#n#6Û‰=Ä~â0‰DÒ"Y‘‘id}²9ˆœH“ É•äýäSäëägäŠ Å„âN‰ ð)‹)å”=”6Ê5Êe„ªJ5£zRc¨iÔ•Ô*j#õ<õ!õF3¤¹ÑfÓD´Zíí"­ö™®F·¤ûÓçÑåôµô:z;ýýƒÁ0eø02ÆZF=ã,ã1ã“SÉV‰£ÄWZ¡T£Ô¬t]éµ2EÙDÙWyr¾r¥òåkʯT(*¦*þ*\•å*5*ÇUî¨ «2UíU#T3UËT÷«^R}®FR3U Tã«©íV;«ÖÏÄ1˜þLss󤡦1]#N#O£Fã¤F/ Ç2eqX¬rÖaÖmÖ—)ºS|§¦¬™Ò8åú”šS5}4š%šMš·4¿h±µµÒµÖkµh=ÒÆk[jÏÖ^¤½]û¼ö«©êS=¦ò¦–L=<õ¾ªc©¥³Dg·ÎUa]=Ý`]‰îݳº¯ôXz>zizõNé ê3õ½ôEúõOë¿`k°}Ùì*ö9öŽAˆÜ`—A—Áˆ¡™a¬a¡a“á##ª‘«QŠÑF££!c}ã™ÆKŒï›PL\M„&›M:M>šš™Æ›®6m1}n¦iÆ1Ë7k0{hÎ0÷6Ï6¯5¿iA´pµH·ØfÑm‰Z:Y -k,¯Y¡VÎV"«mV=Ök7k±u­õº¯M®MƒMŸ-Ë6ܶжÅöõ4ãi‰ÓÖOëœöÝÎÉ.ÃnÝ{5ûPûBû6û·–<‡‡›Ž Ç ÇŽ­Žo¦[MLß>ý®Ói¦Ój§§oÎ.ÎRçFçAc—$—­.w\Õ]#]Ë\/ºÜüÜV¸pûìîì.s?ìþ§‡GºÇ~ç3Ìffì™ÑïièÉõÜåÙëÅöJòÚéÕëmàÍõ®õ~âcäÃ÷ÙëóÌ×Â7Í÷€ïk?;?©ß1¿þîþËüÛpÁ%]j±Õƒ ƒRƒ‚†‚‚—·‡BÂBÖ‡ÜáèrxœzÎP¨Kè²Ðsaô°è°ê°'á–áÒð¶™èÌЙf>œe2K<«%"8"EšEfGþ:›8;rvÍì§QöQK£:£™Ñ £÷Gˆñ‹)yk+íˆSŽ›W÷1> ¾"¾wδ9Ëæ\IÐN%´&’ã÷&Ï œ»iîÀ<§yÅónÏ7›Ÿ7ÿÒí N.T^È]x$‰Ÿ´?é+7‚[ËNæ$oMâùó6ó^ò}øùƒOA…àYŠgJEÊóTÏÔ ©ƒBoa¥ð•È_T-z“’¶#íczDz]úhF|FS&93)ó¸XMœ.>—¥—•—Õ#±’Kz³Ý³7eIä{sœù9­2u¬˜¹*7—ÿ ïËõÊ­Éý´(nÑ‘<Õv¸ãˆë‘Æ£&G·c+iFš7µ[z[Z{އïhóh;ö«í¯u' NÔœÔ8Y~ŠzªèÔèéüÓÃí’öWgRÏôw,ìxpvÎÙ›çfŸë:vþâ…  g;};O_ô¼xâ’û¥ã—]/·\q¾Ò|Õéê±ßœ~;ÖåÜÕ|ÍåZk·[w[ÏŒžS×½¯Ÿ¹pãÂMÎÍ+·fÝê¹{ûîywzïòï>¿—qïÍýÜû# ––|Òú´ï³ëçÎ/ñ_ž,úJúZõÍâ[Û÷°ïG3GG%\)w¬ÀaO4%àm#«º¨Jã5ð˜¯Û1VÔï S´ÿâñ:ylÄ Î ¶ ¼`;f&Ó±^QÎÅøêè8i˜GÑrRÆ¡K±ÒäÓèè;]RÀ7éèèȶÑÑo{°Zý@{öxí­PU*ÌXÚ–WýQ ðü½ýuEþªcÜ®‹ pHYs  šœ@IDATxì eUu°×}eÞô>ÃІ¤·é  "¨ ŠKK,A#jT4Ôøcb,ùó»Á‚-jl B¢H/*H‘>ô¡M/¯þëÛ÷}3››7MŒål8oí½ú^»Ü{Öìsn+"vy×»Þµð9ÏyΡ ,8x„ ó·ÁÒjµbddä¿ÐÅaVFúQ°rÈpQ~S=ÚÝÔ¼tÛåÂk]¨<íáááuý¿!H?»ººÖõW>õvBé‚ðSŒ§q§.êU6Ä+~,ˆNíI,;Òõ9ÊoªG»‚Ú“nûÑ ¼Bx­ •§ÝŒÿúø—Ç‚Íø?r¯u^uÂÇŠ#üãé:§.êU6Ä+~,ˆNíI,;Òõ9ÊoªG»‚Ú“nûÑ ¼Bx­ •§Ý¬ÿõñ1.›ñoÖ?k‡‹Ò¬ÿö÷o÷ác­£:~2íGÓµ!^ñcÁßfÜšõ߬æ×o3Æš—Î-÷ôËWŒ=Æy…µ|ß|þ?±ø7LjxÚvÆ·åü‚ŠJ·ýhP^!¼Ö…ÊÓ~¢ã¿fÍšÛ³œwÖYgó·û·WµÞýîwÿùqÇ÷WS§NÝÀ B½»»{G3®@ ué”ë¤Ñv±À[ªÂ#¯¶•S—<¶õc,¯|µœ6Àm¨('/Ð*#6ü¶•íä“§†ò‘ï”%îÆ¢–Ç8R”‘®žÎ¶¼Íø?r>– vü1¦Ž)°ÿõArŽ­Ç¬¯³ÎÊá¼ì„ЕuÎ×2ÖkÛÖÇ‚ðëõš‡ö£å¡›ñ_1c¹³¾fÌ:c(Gç¸Û†®l3þíh5û;Î%çˆmçáX^ùj9ç¸ åä6ë}´Œ÷zÌúš1댡à•¯!te›õߎV³þÛqp.9Gl×ó§s^Á+_-ç·¡¢œ¼Àfý¯–q_Y_3f1”£sœlCW¶Yÿíh5ë¿ç’sĶóp,¯|µœs ܆Šròÿ»Ö?6¹_±bÅŸÿüç?Ö}ê©§¾eÞ¼y‡cÔ‰O}Cm«ÇÁÚYhð‰£M]œ°ÆSÇ>4.i5Ô?pÖÕ¥ŒüÚ£m©y”Sü@qò ÁSh‹S·òÒõªùjå€âÕ-NY üð ¿ÆIê«<¶¡É‡,xx”‘†~hm}+†GÿÀÃEÂg]¼8a§Ž=h\Òj¨?ଫKù±]ãÀ×måÔ#?PœüBðêWùGyéÀfü×'»ŒO ‰‘mêÄ—¸[h¡ãÎ:¼µù;qàÕkõÈ_ã䫬¸‚È?ÊK6ãߌ¿ó‹ù@ažPœ?Λ_Ó sõG~xÜÛÅÕй*mxäC-xxÀé+º¹Æjë[åR‘Ež"„ϺxqÂO{и¤ÕPÀYW—2òc»Æ¯ÛÊ©G~ 8ù…àÕ#® òòÒÍúoÖ¿s‰ù@ažPœ?Λ_Ó sõG~x\Ûâjè\•Ç6<ò¡|³þ×Ç„Ø8fŽƒÐÒçÐÔ1•Ö¬ÿfý;—êyBÝ9â¼НiYý‘×¶¸:Wå± |¨߬ÿõ1!6Ž™ã 4†´Å94uL¥ý¶ëßñéííÝxÖ¬Y+Z·Þz뢾¾¾Í1€r à nhhhÝÄÐx,ÊHßÙ²Dêªy•WNØ)œ¶ÕQóÕuƒ¤ìÒý=aA«hLäÑW ud´+”&”H¿¡‚ã$O­§®C·@ <ú.^¹*ßÉo¬ÐQûŸ2µ-lH_ã 5ãÿÈñ1N¸Õ1‡Nü:ÇQ9ðŒ—tù›ñoŸ6#çtiŒþ1vð?ç1Ð6ë¿Ccc¬£sSšñ…G°YÿÍúw_s^ÔóÇy#ìœ?àÁ9÷ÔQóÕuÖ®ëÙfý¯Ol×q²î^GÛXs̈asÚpêhcÚk<ÍúoÖ¿sÉyTÏŸzîPïœ?âÀSÔQóÕõfý¯¿Ÿ3^ÆGÞz³þ9¯˜_ÆÄÏpÆËzÃNZ'¼ð*èpÈSë©ëÐáÇ? }¯\ •ïäoöÿöÃÿíñ_»ví­»ï¾{Ä©ºD›ËAvbˆSÎ6b©#[Oˆº PÀ[ê:8õƒ×P;òË' ÈE‘è@ˆ—§n˯ûŽ,õN:íN^y€\ÈÂSû­mqcùŽ"¯º±@Óž|ÐÅ×P]ò«ÃvÍ[×Õ'N9Û@Jg_ôX×ámÆì˜Sbd1¾Æ°ÿö¿Š1Ösš<Æ®nWùÔC›z­¯Yÿë?ˆÅx)Íú_¿ïæšóιÄ<·¡˜Sä-òª«YÿÍú¯çó‚âä¬KWØ9¡äy¼°Ö]ו 'Íþ¹Éˆï”±]oÄà,öÑ ¼8 ¥ÆÕòе!/P{Ôå×WÚÆ®¶ /¥¦S·-¯¾€§nYë@øÅå/ÈÑ6uxë"_­K:4 Ð>Ø.„'ðG;ÊoȱTÚ?c*O­³ÖëxØ'ùm[ð‖g ¤uÚ¯=êòë+mcWÛ…—RÓ©Û–W_ÀS·¬u üâ€òäh›:¼u‘¯Ö%hlÂø£å7äÇX*íŸ1•§ÖYëu<ì“ü¶-xq@K3Ò:m‚×uùõ•¶±«íÂK©éÔmË«/à©ÛFÖ:~q@ù r´MÞºÈWë’´¶ á üÑŽòòc,•öϘÊSë¬õ:öI~ÛÆ¼8 ¥Æi6ÁkºüúJÛØÕvá¥Ôtê¶åÕðÔm#k¿8 ü9Ú¦o]ä«uI‡FÚÛ…ðþhGù ù1–JûgLå©uÖzû$¿mc ^ÐR㌴N›àµG]~}¥mìj»ðRj:uÛòê xê¶‘µ„_Pþ‚mS‡·.òÕº¤C£íƒíBx´£ü†üK¥ý3¦òÔ:k½Ž‡}’ß¶±/h©qÆ@Z§MðÚ£.¿¾Ò6vµ]x)5ºmyõÉoÛØ‚´Ô8c ­Ó&xíQ—__i»Ú.¼”šNݶ¼úžºmd­á”¿ GÛÔá­‹|µ.éÐ(@û`»žÀí(¿!?ÆRiÿŒ©<µÎZ¯ãaŸä·mlÁ‹Zjœ1Öi¼ö¨Ë¯¯´]m^JM§n[^}OÝ6²Öð‹Ê_£mêðÖE¾Z—th }°]Oàv”ßc©´ÆTžZg­×ñ°OòÛ6¶àÅ-5ÎHë´ ^{Ôå×WÚÆ®$k`ê4`ÆÚ9ë* ­|©äŸÚ G_啎 Ǽ:W·òutZí¢[~õáѦüêª!²ÚbG@}'¡¶më¢-ÓæÒ†rÈ(k_Õ#ž¶>Õ4uŠ“hK§¾ÕPYmÚou©G:ú‘§ÀÓŒ3þΕ2)òsƵXÏç–ó§¦CsÓy'NÝ‚ÈiSýêª!òÚbG@}ÂÓ)¯ñµOâšõßìÿõ|tŽ9ŸœC@çºóÈùIÛ9YÓ˜c\y€–±t:7k¨¬6÷êRtôkžfÿoöçJ=÷œûõ¼qn9j8ô87wâÔ½!ˆœ6Õ¯®"¯] v´ÔG!<òê_û$®Ùÿ›ý¿žÎ1ç“sè\w9?i;'ksŒË"Ð2–Nçf •Õ¦ó^]ꑎ~mÃÓìÿÍþÏ<àÊW®ÄâÅ‹Ë>LÛyVÏɱæ¿sª–qÞ‰Sdž ¶êõ¦Ní {zzbΜ9±å–[ÿ°£  ó_¨œPû¶u±Ï¿Éþߺí¶ÛFP !: J«!|¶©ShSìœÕqùòvÚ* :þ¨O=•«ƒ^½Ê€£(«œ>èc›«Í'8x•¯õxñÂz‘×?í(?t õšVÕõ‚‚¯ž€5 ;úzuåµ'º´±ø¥Õz¨SÐAÑž}K¼ê“§(èø£¾šG9ã+M½Ê¨JºrðqécÍ'8ø”¯õgø¤;òéŸvÄÃÏ]ùšVÕõ‚‚¯ÿöz1¾Æß6ã¿þfÂXÔ1¢N¬¸¬KïlwòcñNSñÚsΫW~ ¼¶ÔUCõ©šrÍø¯?šO\ˆ•û’ñxÇÁXßÎvÍ/ [â©ShS´¬y¬åUŸ¶‹‚Ž?ê«y”kÆ¿æ‚sÃyåœq*IwÞ8£5Ÿ<âàU¾Öï:ƒOºŸÓò9?µ#~.èÊ×´‚¬þ¨|ÍçóùÏ<àªçóÌy 9æZpŽŠW¼8æWg[Z ‡ê´ Ôžs^žZž:¥ÓVAvüQŸzj9×—4õ*£*éÚƒKk>yÄÁ§|­ß8ëбOÿ´#}\Е¯iYýQ/(øþPÖÿ-·ÜùÞ•Øe—]bâĉëzT÷•ºñ]ÇPU É/Z܆d¥Ë/¬ñµìêÕ«ãꫯŽñãÇÇ[lñ{5Ÿ~0ž¿«ñ/ÉšÚq:£#à-: d‚Ô“FpÒÔÙ¹X§Œ|@ä\Òµý/uý"3†ôQ´©¬v q”Ž.ìÀcÙ?tmË£ øÚ.muâ—+_ ùjݵOcÙªeÔ©òwöº´š—:¥ÖÙÆ¬ç‡æë»2Ø‘f¿á1ò)§Bý¬ûL­C^ðòQׯfü¹é;'êX/q@âg±.½æ7ÎŒ•ã(½ÿöœt¾«:¶Íúo¯SçMâÔ¬ÿõ_\]SÄÊõh¼\§õÜ¢n ¡K¬K­K^éÚ«eäºÇ;ŽÈoÖ³þÎ)æˆû¡óÄySÏ)ø›õ߬æûóƒRÏ!ç xù¨ƒgî5ßÿšïÌŠûsƒ9äÜqŸ)LùG¼<øN~èÎ=e•_Û¥ ¿þTïÿ.¾øâØo¿ýÖÅŠ±ç:¶ðý.×?>àÏ…^ûî»ogýÂKí£uhÔÿ§Æ¿û oxÃ)ð2p¶ ”u`]t rðRäWP:4ëBd¨×4õ¸ðhSô§³N[}µ_ê”_ðôƒì¤a|=hêÖö¨×>K×oÚ5½8‘ÀSŒa õI\aýƒ=u[‡Ÿ¢muk—¶º”Ç¥-ÛôQ³ÔýFNʪ (]ðŠCYÍ[ëÑgíêíºN[}µ_Ú“_ðôƒì¤é+z¥©XÛ£^û,]_i×ôâDþO¯ê“´Â0ú{ê¶?EÛêÖ.mu) ŽK[¶;é£f ¨ûœ:•UPº6à‡²š·Ö£ÏÚÕÚu¶új¿´'¿<à)è'ØIÓWôJS/°¶G½öYº¾Ò®éʼnüž_'Ô'i…aôöÔm~жխ]ÚêR—¶lwÒGÍP÷9u*«. tmÀ+e5o­GŸµ«?´ë:mõÕ~iO~yÀSÐN<°“¦¯è•¦^`mzí³t}¥]Ó‹ù<¾N¨OÒ Ãèì©Û:üm«[»´Õ¥,8.mÙ- î7rêTV]@éÚ€WÊjÞZ>kWh×uÚê«ýÒžüò€§ œx`'M_Ñ+M½ÀÚõÚgéúJ»¦'òx |PŸ¤†Ñ?ØS·uø)ÚV·vi«KYp\Ú²ÝI5[@ÝoäÔ©¬º€Òµ¯8”Õ¼µ}Ö®þЮë´ÕWû¥=ùåOA?8ñÀNš¾¢Wšzµ=êµÏÒõ•vM/Näðø:¡>I+ £°§nëðS´­níÒV—²à¸´e»“>j¶€ºßÈ©SYu¥k^q(«yk=ú¬]ý¡]×i«¯öK{òËž‚~pâ4}E¯4õk{ÔkŸ¥ë+íš^œÈ?à)ðuB}’VFÿ`OÝÖá§h[ÝÚ¥­.eÁqiËv'}Ôlu¿‘S§²êJ×¼âPVóÖzôY»úC»®ÓV_í—öä—<ýàÄ;iúŠ^iêÖö¨×>K×WÚ5½8‘ÀSàë„ú$­0ŒþÁžº­ÃOѶºµK[]Ê‚ãÒ–íNú¨Ùê~#§NeÕädÍæ›o^x䢬æ­õè³võ‡v]§­¾Ú/û$¿<à)è'^xóÍ7mÑ«œzµ=êµÏÒõ•vM/Näðû^ClCv:• ¬ :hsÙ!®3‰ð! Yœ®íh[}òÕrÚAÞ«ÖSë@ŽbpôWÐE¡­.êÛÔñ<—2êÖxø)ÈèpÆH¹BKo-‹nÚòѦ ÞR·åG_ <ðêƒ<¾Zì?<Ú€Gøh+'Ô–4øG²Æ‚C¯4eáQ桇ŠüIô2ôÁ‚ 6¸(µ-y6µ§Ïê€_\]G·ý¯uÖ¼5~Cý‚G¿­ÕÿšG>luúÑÉWóêú‘«e­#O‘ÈE‘XÛy4k>ô «¾¢tô|šF½¯/5Ÿ<êß­ZÆ:²dÕ#M\MË~MWVˆÎ±âÇ‹§Nm´Qy—xmë“}úS\ÿŽ+±acv/¢ Í=ǘ+èÄO¨žç>âÚà”•Þ}¯GÿˆS7zðƒ¶4qÊ9–´á•Oÿ‘åR¨ÌŸÒþO¿-Íø·¿34ã¿þF¡Yÿëo–\+î=´ÝÇÜÃØC꽆µå¾EÝýG9÷< 4ñêP[Ž…8ôW'miµàÐ+ >J-G»YÿÍúgÞ1Ï(õœ£Îü©çŒó RãšÏÿõ÷W%8ùÇø¹Î›õßÞ›ˆ‹±p³±öBøá“æ>¨ŒßåhÃ+ŸñG–K9 2~þC׎rêSÖqÂóDzÿO™2%ÞøÆ7ÆÙgŸ]Þ…3nܸZú¸dÉ’R'ca]H,züÐipÄÕÁG±Á”nÐÁk œƒ ^è:‚ðè· §!<à)ð*ïDNÙNÛʨC~`]Ô®–¡Èré/¼\éÊÝxã1iÒ¤8àÀ ìÎû¨ááìk `RÓÎ/­ÜÈ“‘PŒ §^êùß|˜á‚6’J£Mouåôñ_†h„ûµ¢£€bþ)‹¾¥ÂDD[!P®uF"]5Ó¦á3(\ëf ’¤ô¾äI½ÅDv¥-›cnÔ™GmŒ¤Mta3UD+ƒC,áobD¼3n¿§1nÅŠ+ãÚk¯‰ë®».¶ÝvÛœC9÷òbm¸&XŠëŸXŒK½W û—{ŠxdÀ%‹^ô³oRjêÈÕQ×á/Ÿ~jK_³ÿ?2VOdÿ7®ÆÛ˜wJ ›ñÿãûüoÆ¿=ÏÙOšý¿ý¹Èš¯×½û@³þ›õÏ:aÏp~øÙ ìüì`ÞÀÛ|þ·¿ãü>Þÿ5ûÿcïÿ~×üC¸ÿg޹&]«c­KhÒY§”Î5ÌÇ;{.»ì²Â¯ŒŸÚê”mkl¯ÿ7 ¢„R·™ˆn,С-à¸îüóâ–+®ˆ{o¹¥¤6Þj«X°Ûn±ãÅ´¹s×e•;õé˜괗ײð7ãßž·ÄÐ85ãÏÙ^ÌEæm®z¾øùÏ<"v5„—˜2ï©õ€“¿åã¯Ü£}þ+«OÈR€àj:8lSÀC§åÕ>¾5ã¿>Nõ¸³fü›ýŸ5BiÖû;kĽ‡õaaoO©÷yë½Ç} ]êSžx»_5Ÿÿë?kˆ —14âÙ9Ƴê,û?± ?ôÓ~baÌêý\œrð;xÐÃUóÿ¾®ÿ2_òýï?¶ÞzëÒwÇŸþÙÇÇ^0 “BÖ —J ú¿úUœwÚ¿Æu?<#fÄ.›l[LŸ+òîÚ|ëówÞ“fÏ. ôê„P=:¹èÆ«ãâ3?·]ð½˜×»:vßf£Øjî¤X»xQüúçÄâîŒ SæÄŒÙóŠOÈã—>Ó¶üõ-ׯ¿Ÿýå8ïªs¢kÆH,Øq㘹ٔ¸oÙ=ñ‹«.‹{î½3¦Ož³gÎ](åÕKßÁQôÛ¶šu6¬üôØÿ€b8'ÒÈ@ÞÀtå—Å¡r$™HßÝ›qÍ£($>r””H;ù…1e8u2L¢e()ÉÛ o&;Hšt‘I·ººrNÝ$:F¢·ðe.(m¦O¹¹fæ¿I•<­äWNòäÁ÷tŒ“>ÃCÃù<\{ F2ÓJ.r+‰JUYÅ¿ˆžLeÎ&-ô 93”'}™~·b zÑœü%Gƒ^UÙèɤR+í61úÉÑP¦9µé&óâŠLØÌÎ5î&Ê\c-pYÏi³®MÝudݵ#DW]ê5kSש²@/íø%E€ÊÃ+›Êª·¶­>xä-•Q¹Z¯tpâ­C£®}B§à·þЮeÀÛ¶N[êôÁ6ò–ZYø(5Þ:>qÉSë1Ö' Å6uä,ê -oghKâ‹<ÔÁÕtÛÚéô ¼òðvúN½µmõÕ¶ì‡zÔkÑžuhÔÕ£ŒPŽ|µŒö€Ö¡+C>ØVgm[Yø:ñÚÂ'.yÔ]ôtÆXŸ€ÛÔ‘³¨ƒ¶¼q - ˆ/òPWÓmk§Ó7ðÊÃÛé8õÖ¶ÕWÛ²êQ¯ýAF{Ö¡QW2Bu:vòÕ2ÚZ‡® uú`[µmeáëÄk Ÿ¸äQtyÐÓc}RlSG΢Úòvƶ4 ¾ÈC\M·­NßÀ+o§àÔ[ÛV_mË~¨G½öíY‡F]=ÊÕéØÉWËhhº2ÔéƒmuÖ¶•…¯¯-|â’G=ÐåAOgŒõ H±M9‹:hËÛÚÒ€ø"up5ݶv:}¯<¼>€Som[}µ-û¡õÚd´guõ(#T§c'_-£= uèÊP§¶ÕYÛV¾N¼¶ð‰Kõ@—=1Ö' Å6uä,ê -oghKâ‹<ÔÁÕtÛÚéô ¼òðvúN½µmõÕ¶ì‡zÔkÑžuhÔÕ£ŒPŽ|µŒö€Ö¡+C>ØVgm[Yø:ñÚÂ'.yÔ]ôtÆXŸ€Û·ß~{ù)ìZOÍ{K—.- W¢]óà 8 uh5Ý6þQ:}¯<¼ö^eÕ˽üüùó!>`m‹6E=ê­qÚ~ë[ß*ÿ¸Ý××kÖ¬iäHø9ó­òK];íégy JÑÀ €#d÷¨/¹ï¾¸ü߈¥¿¼"Þô¬gƦOZŒßoŸô:bÍ…Ç?þq|îìÿŒŸ§®É³fÅô|Ç…Ò¸Ž¡wÙËãŠs¿#7^¯Í‘1wßÃcÜô“¥ýŸ‹/ùa|íógÄ/3©0u朘>«°18µïè}ðáûã‡ç3n|ðú8îuÏCwxfÌ™²K1yßò+ãìk_ýÂ1rÁPÌœ>;æÌÚ¨Ðô¤ ¥44âb  ƒã2dζß~ûÒî' ÃÉ‘ü¿yè3z†‡b€LHªO±LÆdê#çÝ@Æ «»§ýò ÄsR¥Õ“zIÒdPÓr ÷äÏVOòõ¤¶ü¡L¾ y8#œpÉxq𦕠žÄ”“,åßRgI ¡Lâàwò2ßK²™Á´úÀg®<û™6³Ý£áLæp2& ¦lZΛõ®’Ö)ɘÁ¬§‚èÊ“:ý¹@ˆQú0ÈÐÎeÒ'û—ºJœÝ• šÈþr:§;“:MŒrÓùCŠQ&gGºzc»í¶‹ûï¿?6Þxã²?°>cŠu ¥^'1ú‡5Ìú¥¸ž©»¶xOŽuy\—òw®éȹ‡iÃGV½úL›ò›®ÿ"œì;mukôÚç:ü«€¾ê2ú+¯ý5õNýÊ ãt|jÀ¡G~êØ«÷¾š<2\ö×:‚ŽN_k¿¬)ðs5ãߎG3þíµIœ¿Ô)õ\¤-ùãR\‡ÆÓy :¥YÿÍúg8Ü·ØËê=ùâüqî çÞ§ óŽ:ôzÎ2ŸÍþO„šýß”`ä÷+çŠÐ¹)ó°s~A“XÏkx)õ|‡pðSÖs¿æo>ÿùŒøcаYÿí½’yTÚî}ü´÷'?ùɸ÷Þ{KÂfË-·Œ×¿þõq‡’ïË_þrüÙŸýYùNH[]õ\4æÐê9¬ ÇA:<ŽuŠŸÿÔáƒq­×8å•EfCãßÛÛ[|_±bE±q衇ƹçž[’5úðXû÷‰'žx F,8Hç„§èÐ5?:+î8ëÌxÁNÛÇ–;ï­|AÎÀ/®ŒÁ+­ÌŠMæ%¤«VÅW]=™¬ÙtÇ×]W}×^vV,»üûqÔ3wŠy{î­Õ1ø`ê{øªŒÜª˜0sflÒ³4n¸ü—18yvÌÝ|»u_âñËàØ ~vN\tÓ¹qÀ‘ ã©;>%VÄò¸mù5qçÊëbM¬Œ9Sg¥ž•qÅ/¯Ž©½Ób«ÔGÑ DÝXˆ—V„Fÿà?M¶ÇSž’˜Œ_&*2e‘:3™‘‰ ' K˜G†8u‘šVÒüþ¸í­ï‰µ÷?ÓÚ»$M2ƒ‘‰œ¼™ÊGcÈïð¨Pwú’Yò*qR‡$JÞê§ö„âQ¨d̘$H?Ê»qÒTö"Oó¤ ßYsòµr|G85C†S0¸™p$åxNrf;Õ'GŽX"“Æ žr‡dP.†áž¤äûHo gZ(éèÎŽ§[i£œøÁ_ôdvFµvg’éñƈÓD$"FìH,51zä<úƈÄé´©qU®ñ™¹>ß®ÁÕ«WÇ}™ÔåEÛdʹx‘Öƒ>Ë–- ÞŽÎN<<Ðä]¾|yáG<à‘¡ÍË)dß;׿ëºuür-­ËÓ ]ãò)^2u‘Çý ò”G^‹myÕ_Cx¡#¿}Ç%o]G>ø)ДÙºhCˆ òBë´áSG]×4éúƒ|­Ãþ #¯_ÑI ¼&qÀ+«mxÔauô­#O½jK>åÁK+BÕy´Që¬qÔáµØÖõ×P]ÈÁoŸ‘á’·®#üÊ++<²uцýÓ¦2ð©£®kštýéÔa‘·?ÓÛãCÌê1!NÆq¬:ãc¼©ÃÛŒ³þë9Á¼p½Q§8Ç„ðS˜?Bë´ë5_×›õ¿>fĘQ'NÛÒ]Ë5„z=&ÒÁUG¦kø°UãàA¶.Ú6ãß/ãl¬ë˜Q'^ÆH6ãߊE‹Åf›mVæ q16ĉ$Å‹^ô¢xß{ßþð‡ãÄ¿<±ð½õÍoãŽ?–’¨yÅ+^Qð¿‹ïœâ§ÆëõÉØ?Úøsÿ2~üøu}³è˜>}zÜpà 1qâÄ8ì°ÃÊwX‰"oÀÉyÕ_Cú=oŸ×áE)…‰'ìzðª+c§|îF¹‰æ‹F{29Ó•þ<Þ3˜o”§HvL§ºê—Ç<¿ÐXô¬³[¾óªØió)1uÒø¼í×Ñ=9õkëXC+–<·&oW×ó×½i}ø‡N7ß}}ÌÝvfLšÚ×ßw}L?5ÆeV‹Ò?ÐËW/‰S'ÄÆÛÍ,¼‡µŽ.4t¹Aµð´Â\ýwU&§¦O›ã{ûbíÀšr*&=#‡’‰Nää,eÀ3I1ÜËé”ÁŒÕªxèÌÿ(Úü·ïÆfoy}´ÆOH™”ÍÌD+uçA™LŽä˜°©’ŒI]ô™“/Èùѯ:µ+”¯ÿfü™\–zÞ0Çh»åcÎ9Çà߬ÿöZ5Ž@bG¬\«ÄÉõ Ž‹vsÚÆÈ@§µ:ÕžtlšõÿÈ$K Pþ©ãMÝøB· t,Ü'+Ç€øRWÞ1¨í ãXAw¼šñ_ÿ×9_‰'8®z-øY Þ±2¦Žƒczç˜(Wëpü¤ÑæB^›ÚOýOáûŸ1u Œ%qá{<ÿ˯ÍÞy×å^þ¨£ŽÊ'Fòþ59't጗½ìeñ•¯|¥$=N=õÔ¸ãŽ;bŸ}ö‰·¿ýí%ò×ý×qÈ!‡”dc¯/]rÉ%ñž÷¼'~ýë_Çûßÿþ¸3_Ѳ÷Þ{Ç;ÞñŽ"Sã䂿x>žñÿêW¿/yÉK‚_¢?gœqFùÇeìí¿ÿþ±c\A/4 ú?üðu¶ iz]ʪ7y x)@ÞÛïˆm&ŒIkVG_þKwïý‹£+o訃›´vMáéÏ`º‘ô£€ë_qWl²ÙŒèÏʜÁò¼©_Ѿ—åÜÊBƒ^|4pød…KVÝ›.˜]òK}kM¬^+–•kÕвXÛZÝ™ š7N<œ¼ú‡.}¢‹É„OiÚçÁŽ;íDú%“Ù¯L|ðKG]™qÁßLftóøP>ú4’Ï2­üÅU±ü§—Go¾çgú!»3|fte¬O¹^ÌÍ-ŠÓE^â‹«DȯñßH©–äÉ1.¬yŒ†÷Üôfƒ5™?ËXæín åúȤL&ORo7ÄLü ó(SÊö\¡ÎÔ]øR8©%ñÄûv†óº'“/¦É.§“ù}lÛg\y¯M7ßÜmgvùò8WÞ™?žñ¾œ®Ô—ª2™„ÏyCK‡Ñ‡ÏMŒ2þÿ{1Ì„ìŽ;”Ÿ¨g°©2î\´IÜ­X¹*–ç/H­\¾"V¬YVÚkVe}Å꬯ÌMkU¬¦¾juþÌýòX³byÁ¯Î$æ²LÒ®ÊÄêÊ<6¸rÕòX¶<Ïť̚UkcB߸Ølóù¹™w—ÍÐu´ÔkÖu ­ø–|~xþ¶ë_}µ]m×ûtl‚#FêðSì‡r¶…à©wò"¯Néú/uðÚ–pø®öƒ¶|ÚS/û#¥Úð T<…6EûÚ¢]ûà¹lÃÞv ‹âü]j?áåB¾ë8 v(È(WëªiêR¶¦©œ:± Þ¶4ôSУ­º]ãµUãQ§ôZuðÚ–pu̵ |┢“«ÿGÆÉ¸#.ŠcÓŒ Ç#âBŒœkÄ©ž‹à‰'x.Ûð€·]ö…fý矱¢m—àꘃ'ž^Æ“¶ò@tr5ë¿YÿÌ‘æó¿½R\¬. kŽÒìÿ% ˆ 1š1cFì¿ßþñÊW½²œ¬9ÿüóãþî6“'OŽ}èCEðÓŸþtô¯íW¾ò•ñÜç>7ÎþÑÙ1iâ¤xç;ßYö¢vØ!Î:ë¬ÂË8ðBßÝvÝ­œØ?þøã×ÉLž49Þõ®w=ê÷??SØßÐ…Ÿà(ޱ{¤xþáø;ßùN¹'úö·¿]úµ[þàO ðžž,àÉùÕ¤€×VÝ_~ ‡ÆšH8È¿˜Sà!+<~\oLíë “§Ä¸‰"&äÕ7¾ý! ŠTÄìŒiÙ¯ kŠqäÉ¢è¢M?¡'úfä¿ÌOŸ½œÒAWO_¡q‚b¸/O¢dÒ¡oƪÿЪ‚GÞpÔÑMúRßÔÉ“bÒ„‰yªfbŒï}Ý}%ýyª¥»7“(izꔵ±dB[_Q:ª‡µõ Y×Á´Ž´yñ¨P‘Ê„ï‘á ¿DE¢$ó2å„ÏP&Ž8½“¡L”¥ÏÉĈyz‹÷Ìðââ’‘I’4¤\†RAo& è ‰¨òŽšLNñRãVÏòxè¾ìKžº˜”c9.Oß s„û$ s,‡þl0FKâ’¯1¾vñm±Ë ߯Ú}M|ùc_ŠËï‰c2szàæ9?ƈÑÀCWÆ×~2dzwLà¨Æ@~ñë%ySvïÕñõóâùÏjLü/1ˆ›ÎÿNœþ #>/ÞrÂ1ñ¿=FCñó|+úŸüÜØk~z”‰®‘ôƒ—õvýF1bXRGž.â´gÊÉ«<¹D¸Óý1cÔ9ŒãÏ‘ª'4r~Ìœ=«dÅù f]»^&äž°lIfʧLÎyÃ{Œr3¯r p +w”n’šÙ$$23Ã3’¸|¸.G-ç5y€ÃæLê`¥ì@úÚ×?˜ïËÙ$®¿îÚ’‘wýãu}a­²üBŠ]ê®cÚxŸÈú×f­ »\îoдéìî¹µoèÔg |Ä–‚ mmãNõÀ§ê«ùÄÁ£ }¡m¯sÿG†¢>êú¥^q5φöd}¥à ¥ÖG½æC·¾¡Ûêö 6¤¡—:8åÁQh7ãߌ?ó€ùã:—€\ιz.)ç\…æücŽIoÖ~æd!6ƪYÿë÷<çT³ÿ·÷£fÿ_ÿ9Û|þoøþýµùüÿýûþç¸0wÙÓÜóÝçNÿÊé%¹òõ¯=8¡Â÷N,¯yÍk2ÅÀ½q”+$`xlèÏ_þç±:Œœ|òɱÛÂÝò€WÄ‘GÿøÇË“.ÌK/½4>ôŠïÿ ý+LÇw|‘!¹³ën»–ŠºÑÏgúoóýG ¦å6œèÙb‹-Ê#Q$ixü (¿Íç¹ @_„õŒðÚÔ­·ŠeùbÑ‘¼o͘1}&™z­üô|>!ï&òýÉ;y“ÍJu³úÅ“g/ˆ¥yúeþÄÉÑ5zEïdLæûkR_¹ˆe1.&ÍšSð 4ƒLq°­o4s~ ¯XS’4Sòqª)ù^š‰Ýyb%˪¡ü×üþ¥¥Ã+#6š¾YÁ«Ë> ‰KM£ ÍÀSç%«;婚vr&o ò4 ‰ïVɱæh>ÊD¢&Ey/2¸6:ýì˜þ‚ge‚a\¹q-7ô™\á…¾‘/u好=knнúà8åŒeÅïV÷þqîÍß]¦ðŇTLú—7´™ÉäœÜçMï@>†”³$q³òXͼYé'Ñ“¡$z(Oæäß”O|>U¢[_IÙàÜÎHêä£3ó ÉÇMežvéÊùÞš5·ž¯ØýEqv³ôîôÑ¸í¼—G_¾¹;o®IÔŒd‚!…5F‹ôñ¬×]ÿ÷³/YÃÄ¿ŸòüxË¢×Ç'ž#k3[ͽbŽ=7÷uŒVßþÓxÃI­8ò˜=c\Þüwg\GÈJå8ôßunüÕ›[ñìcöˆLÛ=2F‹ÏÝ|uœôñÏÄVãFÇû ÇheœþÒã/¸¤ô½þÓê> ιýKqÁ oŒþ=#öÞt\Žc‘7ŒyЉ8óÒèÇŠÑå_š›Ùw2¹91&Œë‹½öÙ;öÝw¿Œk~8åi#~E,s`9„Ô³2FŒÖÍ£ç¡Ñ eL¯ÿõµ±óö;æ¼f`‹Xtëm±Ù òP׆çÑpÎ5²ÙdŒçÌ™³.aÃcQ¬ ÞkÜâ„é—ô‘_ždº’ÜŸ¾3çØË†r.å”+…ä“­;—ùëžädï@.:k“«/³KÓrâX!Ï„².Y“®Sê®_i®_ñîE…V_µþÝ×qßDû²\ðpQÜû°-ˆ¼Åz-_û Ÿ¶äQÆ>‚§ÐF:uñÐÔ ¼:…à•ƒ?IÊÕ}“G?¤iS{à±/D—4t¨—º~iÀ#C\òi þºÿú&Ÿ4ðÔÅwú¢å³­¼þ©×¸Ç?c‚~. ¼ÔÑ)ˆN‹õZ^”Ñ–<Êè xŠýÔgñÐÔ ¼:…à•ƒÛŽSmWypöÝâ‘/Bté |ꥮ_ÚÄðÈPG—|Ú‚¿î?2µ>iàŒ‹6j_”‚׎må•U¯qÈrá7^êèD§Åz-¯ÊhKeô<Å~ê³xhê„^BðÊÁmÇ©¶«<8ûnñÈ‚Ç!ºô>õR×/mâxd¨£ƒK>mÁ_÷™ZŸ4pÆEµ/ÊÁkǶòʪ׸Ç?d¹ð›‹/utŠ¢Ób½–×e´%2úžb?õY<4uB¯N!xåàǶãTÛUœýF·xdÁã‹]úŸz©ë—6ñ<2ÔÑÁ%Ÿ¶à¯ûL­O8ã¢Úå€àµc[yeÕkÜÀã²\øÍE—::ÅÑi±^Ëëƒ2Ú’G}O±Ÿú,š:¡W§¼rðcÛqªí*Î~£[<²àñEˆ.}O½ÔõK›øêèà’O[ð×ýG¦Ö' œqÑFí‹r@ðÚ±­¼²ê5nàñY.üæ¢ÀKâ€è´X¯åõAmÉ£Œ¾€§ØO}MÐÀ«S^9ø±í8Õv•g¿Ñ-YÇ h ô>ô.[º,Ž<òÈxÁó_÷ #ñµ¯~-NzóIñä'?9vß}wÔ”—ñÞ}÷Ýñ«ü5ê…O^XpüA÷$E¶Þjë¸üòË ïS÷zj¾®a\ÜsÏ=qÍ5×”¤üôN¹l’¿d]û‚?^à;ã>¨Ç˜7xùµ'~|>^Aò‡wԠ˾ÃçXáµX‡Ÿ º>ô ¬ó_n„‘cäi;ï÷^x~l›‰“q3¦GWþLþ{¹ |ÄpwÞ¸çɘ{—,éÉ‹:Ä‘ÚQêSçí÷?”oEÎd§bZ}Sb¤/“@YøÿÉŸ¹JÚâÕ1mãK‡ÕD¯>Ò±ù³·‰[úYô ÷æ ‰‰1­wjLè^ôõ ä;/ûcõÐêXóÀšØz£]Š,z$z|÷zѧß( NqR"Ãû9öØ}èÏwiä½gyüƒG–Zƒ™ðÈ$SÞ£æÍ(’ÿÂF#o~·ûÎ2󒉜–ÄóÁ˜zÀÞqÇ?“Ÿ²cÜñ÷ÿ’ïí™[~äÔb+·¨¢´+“ wýìKñ¾¿,.¿çÔØvÜò¸îêE1or>îuóé±ñ¾gÆy‹¾;g²;í—›`’#$-ò&œÓ=LNû=yZ!Ó)éÍpÜtúËc¿¯w|ïå…Ùìu9ù2D²#ßi“Ç42“7ÏÜI§Nrp³O–g0•Š— _ü™ãü×}9îxÿ1~ÕqÃÍC1.g+3;C¼o&OM òˆU&nxDfC1Zußí±ãÛÞ/y‹q(¾xÔŠ8å‚×ÇQÛrö'KþÊ›ô‘<‘’[˜ü›1ê™PbÜÊDEwŽç`êMÿðs|¾©ÕZ›ç†Úñ'aŒÖ>xKžj:5ÞúâccRƘ1êˆ N!ñRä¤ÿ×£?ýË8rm&¾ú–Æçß7ÖþŸóãÄ…32©0&öÆåÛg§{óTÇåi§‘œ,DzøqƨpMIš|òÛãÁûޝÿÛ×b‡vÌüÎ*ÃÂ#gŒ*yrnÌeà™GC£1ÊsqãÍ¿Ž3Îüa9¶ÏætéŽóÏûi\’ogñ‹_ó3CÌ)ž±æQWž`ÚrË­âºëΈYù2qÖq\•Yå¾ÑØåÄÉ$]z—‰Ã̽d² Þ6ŽƒPœvɹAb±Ì?6×ÌPòZ›zyœ“7C©“Ÿ³ïÏW†Ï˜63n»ã¶’¬a½úaãÚÅŠ´ñ ÚÀßdý£Ã¢pÚ¤Ž^JmÏýKá©}Q|örÚ†¡ ¸¤ƒƒÇKy}µß´ááC›Ï"`g1@ÇR;؇ùÔiŒ”¡­?ðÐâ õ'²ÿ£Ç¢pÔ)Ô5uíÕqGZ á³?èë”Ó4ä(Ú§m|À[êøfü›ñ¼ßÿœÎ¥zN3—˜\ÒÁÁ㥼sµYÿí}Ï8»fý7û¿ó€õÒìÿì"ëÿªùüoï~η£óûÿùÏœÖçÎýÿ?þã?â´ÓN‹/~ñ‹íðÍïö<æôÅ/}±úý:Ÿ±cÈwI?Ó !ÿtò+wÐA^ô»·×ŸƒðÕ4y€ë?_ûÐ~¹%F!ÊT;C‡mÏË_<ê_°e\‘ïŽX>knôçÉšá|$Š«F>‘ÿÂ~eÞ¤õçMÞÆ{îY‚…<§ÐCÇìÜÆ[í•ɘqõ­÷ÅŠÁèÏ›IàêO>pWß²8Vwo›lýÔ¢‡?øŠ> Fçö žÓZÇ×-ΓÉ—§D¸Ù.ÿ"ŸupЦµ6IÞÝ×ùƒNba_Ñ¥¿àhc‡º™3ž©›01oÂó]¼¬—wªp²†ã*¼g%Ç™©Œä»8®}æ‹â–WŸ”|­Xu×Ñ·ñܘþŒbû¯6Æoµ Ö.º#ÖÜr{Ž79‰Ìü“ý÷Ý™7´÷ç᥵y:aBl»p»˜1|c¼cï¿JüqÀ&³â¤¯^Ÿ'fÅÿ}Õ¾yèiZÌÈñyó/ÎÛ¼™é¿)Þý¼7çsuŸéy*êÃg~)ö<ñÌè?ÿÄØhæ>ñë—ç£'wÄçÞ¼_ÒgÆÜƒÞ?¿`Ç꛾/yÍgã{§Óç—¯È1Ìxö ç¿¶g"déyúçÁ{ò×·2I3y^ì¸pÓöMw÷Џò›‰}óWƒf¥/›÷¥XÕêó>öÚ˜1{zÌž=;Ž~óâ¾\ Ë®ûjìñ†3ãW<1ÌgÜï 8)ì{Ïý_wz¬ÈDÇ=—ŸGÏž3s®mtôÉqñ½ý£1bqå#_ù—±ºë¢ÏÅ>y#?sæ¾qÜ[NŽî]Ç—dTû4PŽOò´V^o?ð]1´ìäØtö~ñùkWçýÒXÁœM?¾óæÇ¿|ÿ»ñ¦™scß¾2 å¼JƒÌ±ñS¦Æ´™Ì_,›’=ï›6/ã05ÍŽñ£œ!±ö® â#/™3fM‹—ýָ權ëoÌLñ. c=ŸgýàŒ±çQö‹y–“46š½QjkŲŠÓsCëOýéxÒ†ãÆ[nŽUùXÝÊ•g¶yY>¦´4®zÄ<2F­Ü¨¶ßi‡xÚÓŽ+®¸2.9ø‚óKýé‡[Îß"çr&är-’«#¸y[œ}ÎdIÖ3‰3qâ¸r<‘÷ɰ.(ÄŸ rif®»òæ›_3£Ÿ­ÌÒŒd",WQIÂðÜU>‡™€„bö¡dsΧ«£ôY#9“ÝcM eŽÄa¡ç²˜8iB±Ç1HìºNY»Üô³Ÿ¹ç¸OÅùÇõ 剬d)Úsw_ÂVm>üéô…¶Y/ø¹hS´SCðú!Ô.|Œ‡>@G—8d¡=Ñý½^öõºúÁ9/¨cSyü?7ö·öµÖWÓ‘¥mÕk»ÿfü›õßþ2èZiÖ{ßf¿qïqiöÿÇþþïk̈¡±´.Í}¸ÙÿÛŸßÍçóùï÷×ÐÂw/×™ßwhײ®1לùÜë¡q¡K6áû}øþ·gæ nºñ¦ò2`|ÌÄUW_7ÝtSì´c>µ’~ò9Îý6/ë=?ïQ.»ü²¸õ¶[ãSŸþTöÌÃÊ’2üú?@IDAT$öE]T^ê‹2ç^\zÙ¥qÛí·Å'?õÉxæáÏ,2ÆŠxÔû?xb$fÆŸ:—ñ,ÿÅñ}ö›ßüf9õî·]ÿ=*ƸÅÑѼÑ`Á31oâ7;äyºæ‚¸ð¦c“tp6êä=Å÷.Ž»o¾1òE¹›íPL˜>cã褓è¤cÂäY±ÉNGÄ}¿>;.¾ì†Øxó‡cöÜJÇX|_ÜsÇâX;¸il¶Ó¡1~ÒÌ$t¡‡Bôÿ¦Mž{nwH\ukÞt^r],ØtUÌ™;·ð.N}·Þ¹(ÆLݶ?¨ðÚW ¿ÐM?ÑI¼EŽW=y·…Ù¯м¡ìá~3Ñʼn“lgôòÖ6ëæó;­|‰ò„·Ë¤ÌùÓäKbÙ¹—_€Y/O½âºXrñÏbçï=_Y3÷|î+ù8׊˜÷Úãò…Èã31ÒŠÏ=9NùâqØNsã„S¿o{Õ³bNÏ“â¤kœvô÷ãk—|7öŸ?#Ÿ4¹/žrügâÎOí+þÉØþYÿ'¼hÏØ-OÝvÁ—ãSýUüø—×Å6™ôxòÉŸŒçÿóÑqñ/^[L?ù›Ãâ”ñ‰û<"nüúËâéoúNÜvú 2Î+ãÌo¼#nßô_ãÊÞ³§åÉœ¼«)#õÅ3OþyÂ襱Ã7>§~ù_â•Gì\NUÜóãÄ¡¯þDü¿³¯Ž£wšËïÏGÔò™®¹O}U\sϧb“Õ?£¶~F|çÕÇÆ«·n|wô¿/¾ý— ódÌÒøâûDz÷þ8Þ¸û&1ùÎÇf‡¾!Þ÷ïWÄ¿<3~þ‰×Åsöxo\y÷bfyŒ+—y.–ÖƒçÄnG¾9ÞwæÕñº='Ä>r|œsAŽoÞìgú,yôŽq¿S¼õo‹/qÑ•¯ŽM'öÄ9ï~ZœtÍëâç·!f-¹(^¿ðÈxçÌŸÇGŸÞwQ¼çøEñéÿ¼ZË"Ý~è_‘3¶à‘·è/8í»ÿ£—¢}êÈsãB®ö šIøëý_»È[úI»ÿfü£Ì×sˆvçÜ¡ýhŸÿÌC׸òÎ]ðÒ\7õ|Æ&¼ÎQäiá³P×uè®'iâÕ!^]ö=®?xðOû¶ëu:rÖµ¬~á+rÆ<|ý§ýfý7ãÏœâ¢]/ÌçxæM=/¡5ûóùï¾Ö|þ·×ˆ{3kÆ=˜ºqçþëzû]îÿØä/6;÷NåÿÓ?ýSüõÛþº¼ó…“ùìüºÓ6ÛnSüæWžŽ=öØøÑ~ñÏyÎsÊcO¼æÓŸútÙGˆÁÌYyÈ ï÷7ÝtÓ‚C%mH&ä¸(ÆÌx"oœ Cþ©ià”çݵ$”Ô YÞÁƒïìÆx¨W}@ì[¨Ã‡ÔK²"¢ƒia.”" ÐÔ<53%OÐ}û{»MË´EÏPl4gAtm1;6Γ/½Ã·Å?ýP¬Þ÷ìøÀ~ý×ÿ4úÏ{J>l’_\»×æ¯Ußÿ›cbfŽ[þ{y¹yÊä§–z¶zV|÷Ž+⻟ù»8á¥ÅŽÿt\ù±#ã?øfL>þ_ãÅ{lž1ŽI›ç„Í ;î9?.:çßâìÛïΓ(9?RO«kzÌÊÓ0yD+“ Ó3öãc:镼áŸ:sj,:ë»å‘¥—¸ ß=ÓŠýNxkì÷®CãÒ[ÞÏâTFöm8oêï»òÜÂ÷нòtOÆý£Ž‰¡çÏÀ' Oe$ÊœΞ—Ö“góL\õõß?øÔƒñÎ[MÏûIÇ;OÝ'ýÒåñÌÄÆmC™û^¼`÷Ù™oÈ“D™ÙèÉA™rÈnŸ²"ã@ÄÓ¬‘`à¤HÿmƒñÚo]¯Þ?“<ý«bÿ/æÑ¹Wø?ôÑÌ÷ÒƒX²liLš1ùóˆFw>ÈOÖÍÍñ~ÙËþ,ßë=.:ðà8÷'?Ž]vÙ1®ÏLôô9SÊK©9•6eÒ”rò…ŸÂ.ï€Éq*ó(Ñ“ëWyI4/ì%™È›Õ™³üüúPŽw>GŒX­Læð2éô¡¼{&Oµòù%Æî¼¹ñðOÎþn\úâšáÝ5K—<”‰§Ô›±âñ·aÞO“K˜õ2œvËRKÝ]$a’‡ÓZ¼Ë†5ØŸã‘.â=5C™¼á]IÃyºf8“:=éÃàHnðSâÖ[o)b¹¸ÑáþÐfÏ N¸à'8Êc­mÝÃjœýG—têÚBiÈê‡ttˆ£îžã> Λ! üÅÀ¶j{µÔïþŸ²u ñ…¶þS7žú®ßø }W–q¾|ÉWÓÔ¡NuwððP¨£Û662Ô¡sÕqÿ™ë~¯£_´¹˜û|ç;øàƒã†ëo(?±ÍKzI¶pZ~ÉÒ%…ï _øB¬ÉC ´O<ñÄxÝk_‹-Šíwؾ¼ÂaÕªUåóÝü"Ô`>‘sï}÷I"eø¹ïí¶ß®È€¯cÌú¥à×ýþ÷ú׿~ÝwŽ¢hôý㽞è§Ïûïž¡ÒÇÿüGóö\7n\q„¡L FÖfB¡'³_›î»lýôC×màtA^“Wg à࣠[$uzzÆÇæÛ[ïúì2¨àé$2v‘!ø;äD) š®þÄe‰Q¾=u8_[N0dr¢•7”Ãy#ÊËp‡ó$Áª+®ŠEïù`lý©ÆÜ??&åR¾|ñªnå—\›žòŽ˜ņ̃——OÚuç<Âé‚üò™w»ý#cásþ2¸e¿xýÖ‡Ç×~þ¦xׯùlW–ÖÚœ`òѱkN‹û¿9NþÌ™ñü§Íˆ/¿÷ªL¨äxòxV×~1)OþðÎnšGúóeÅ©;xQðàêȳñŒg?;^´ïìX=øœxÙ»çÆÄä[›CÖµEþÂSÞlf?rfD¼ÈŸçö>?Üó¦}pÒü8êÄOÄ-OÛ7¶:ø/â²·ÝQN™äKˆ2Öio4FK®‰WoùôXñöOÄ»_x`,~WO¬aò¦|mégêdnäIE$ò+x¬ÊÇÈZ›eÒò½/¼ëÛËW æ»Nà!F]±ì¡û£µùÖùRæ\tùØMùÉï¤Ò_èƒÙ2¼e0è&éÐo˜Y• Ç÷fR Ûå½*ã’š/¬JÖf2aI®´™¥)‰n_‹‹)[~ù+az˜ØLnŒÆ(ß8Ϙ”üä#=%?@ÿr³"&Y^óêצ®þ8î„ã2¸qÒ9ø%%^–õîwüM™Gœ‡Ó¦çð Æí·ß™'Y<(“R™üÀÇLj0_‡ûìò`&üÖÏ£|>3y†2Atqüe;<`ÿýÓï@³ÐfŸBFŸkŸû'¶´§/@øÇ’‡F©õDþaÏ{¼û¿þ «Nü Ôþ<ÞýÈ#uè'tí`›Ù†—K|q"ÿ¨ ¯>#ÛŒÿúùe¼šñoÏeæÉc}þ;§˜WÍúoï)¬AŠk—Ø4ë¿Ùÿ™\ÖÕXßÿ¡3oܳCÍþßþÎâgÒ|þ7ŸÿÌÖ‰k…5Ägyòûöý¿mý/Í ^™÷œŒáÇzn_t{é›}"Ébç?_å©ýÙåô¾ûßí(wÝu׺½ÄÏjîוA~â‡^бÃOcK,ÅË£NðÊÂÏ“òÃëz…GÁ[ÇhÚî{È£WØSH´™"O!‹reÀK7‹Vëa£†§îºáAI úÐ˦d`±ÓÚç…øà¨Û7í¢[ßk2^êÆE}øÝÔy©Ñî{ì^ÓÉ—s“N2&gDÞç äÅ ^ŽÚç½ñð—Æ´=Ƥ½öŒ¾{z,9÷¢XqéÏcÂn;ÄðòU1.ßÝ2m¿§FßÎÛä»r{bÁß“'r¢äMjWÞœ2žKoº4îš´Sìºé¤L, Çʤn=}b¾˜9)iø¡x˜›¼LþÜû«KòŸ×ÄKŸ—oÃþÙiq-ýÈ_ÕÉ{ø”H¿ò溼;$ãæ>)†ïX+Z=ùËY âÙ¯™/;ïŽøðñOÏ—·bùCËòCŽkÚ¹³ü˜OôeRC&~Ú7ÞƒC+âÆËoˆéÛïO—I4ØÚ)¦æO§/8â±âEÈÇœöŠ£w˜‹ï[=÷\ÿ– ¿õÒcc» ÆUyþÔµ9~鹓Ԝçä‡wÆšù‘¼YÇþÖ{ƒ×žg^ÿ‚xáÎ3ã×?øB\Øóüøè¶ùÈÐ í/™Š‰M¶Ý7uRœsí³ãy[,‹}ßûò5›§Iò_‡2áÅØ¤ÒÛŒk&L(ŒÕHkë8æÓã?3^ø‰ccú²«ãô·^GýË?ç;ŽÚ”œúHÁ´ÂÃ:ˌŠk$cÄK~9P“®2‰“c}ŒXEÃy ¤'’Mƒ2’Ýgÿ}Ëœzxå’ØîI;¤ŽL®ðUŽï#çQjH¿‰QOŽï2ê*1êÎG v‹+¯¾2¦O™½9ŽÌY~*sN¥Ÿô•n–¤G:דú‡2!•I˜k®º"®øå/JbfáÂ'ç˜fÜ3ÁuÁyçÅF³6Šmž´UÆ(×^Ê“äËùÅÊ Ä»dZÉ; ­¶Ú&~øÃö‹†Y'^¬Á•ùîœV¾t¼•Ï\åWø|§RŽCνô*}bòÊ„'I ~ù‰G¦8 TæX»¼ä˜Ä KáâT‰® G ¬ˆæÎŽ›o¹­5dR\Ó®Yöpê¬uŠ|%n£{Ø£­ÿZÎý=èdo‡Mô±ÏˆÓ|Ö¡é/r´•Ñ_íáuõ£{2hî½ê-„Ñ?µ<¶¸ÐïÙÿ‘Ó?ã‡uáF»î¹ð·P×WøÔ-Ÿ4u£ 4ùêýÝðȧNÚ|GE> º±% ýȃ£^ËÉ/:á§OŽe-Ÿºá‡—meŒ¯²ÆQýÈ€kÆ¿=ŸšñoÖ¿ëµáºvýºž Qw]É׬ÿÇÿýŸ}Êx?âì „šûV³ÿ·ç$q£›fÿoÇ¡ó3#FÒœS¬[ñÐäköÿßßýŸñz¬õ7€c?ãÌØ;þܧ<Ññç;ní‹ë—º©KÃëÔ½ iòÓÆgøÀ9'ý.WËüÆëAÄéˆø‚È?nºvÇH²p‰ SgÁà˜ Y; œƒ/tt"Ï….NÓXhÈQ„ÔÕ ä¢$êÊ!ÃsmèâB?¶¼ñ²_ÀÚWÚö½èãýÓ¦N‹¡þ8’™¨á¥ÁüÚÍpþBÒ`ÞPvå/!ñn‘_{RÜýÁÆ /]>þ´:Æ-Ø,¦ì·0¦°O /YS÷ÊǼ&OŒ›ÿò­±âò|,»»èïÿ)®=ü˜xà´/g;Ç$ÇåÞ ÿ)Üe³|qðŒ˜±Õa±ô¯>¯Úcvôl±_|ðYwÅ1Ûäã1_¸>¶xê b¿µŸ‰gÏŠ£?{c¼`ûËâÈç~!Š©y_0‘”|Ï7Å›ïó¼8|ÅûcÇ3ò»ñô¿ýQüŸÉïæÍŠYùhÔ¶Ç;Öð W~¾´6Ï+?éΛ÷<Ê’’É™zÒáŸýÝ1±Ã‚yùÎÌØá “âÍŸÿDì=c86>äÝñí÷ì'ìÿ¤|±îœØå/Έî]ŽˆSöŸÏß%ÝJ¿æ»}œräqqõŠ\¤CÊH&ZyBi\jÏûò¼qÏ›½ùGÅÿzr¼6uÍÌ—ïýª›ã´Ÿœ[sR&åZ›g¼ÓÑI»<'>ÿ¦ýó‘£íböæ{Æ/æî‚ÆìoÞÐæ8ðE"߉҉®ž”Û ýi¤5.ûÇoÅïysl5;_N¼ÅAñ«7~6N}ñ‚œ#]1nf®8s¼{2ÑRNÉ”DCƇ•äA+ÆãK&êõ¥,]kås=™')¥+çÞÄIãËK¨Ž}Þ±±×ž{ÄþîŸ ­ûþË5Æç»•Ø \{¬N¼Üwÿ}éV®¹t¥šÑËä&k&ýKô1¥r'ÈDT{£ËHçü2Ã< ¯Ù'þƒ‡÷×dç2äè$‰“qÌG¸XÛ¬q >Ôû{mÖ5~y¹öà(À ­øäéÔ‡M7it°ÇÕúÝì¡©~ä¸ÔK^õ‰Ç'ùƂڲò¸B‡_@}ÔŸ'ºÿ#§l]ÇŽ>à;¶Ä)cíÿÊ Kêà)BêÒ\tÓŠtÛ@}ÐW 8/Úm‚·íþ_ã¨wúŒõ7ãߎ§cA¬Œ_3þí¹Å|©ç0s‡v³þÛ{†k‘uجÿfÿ/rþqO©×{‹û½tÛÀÎýš¹΋6Å9Þv³ÿç÷¿üNBLê8C uxü\$~ŽE³ÿ·ÿ±Ç8›?•ýŸ>ÿ!¿s–1Âwç~½'ÔódOê\ÿÝßÿZùÖä|Ê$ ¹5„þEœcñÒ´<âpˆ:EÇJ#ÿ ‹‹t È4¡ž¢×B]œ¶´Ôx°]óÂ'k¨[äGöñ_ÝöÇl˜öäŸëöÛo]î›l¼Y±ÑM‚&O®pSÌ}$Ǻ31À |»ê€gÇ`9]§ ÎþV©/»ô—1ó¹g²fMÜùÿs^øì¼íI{îƒù“Þ×>óØâr+o”w»üÇ%wª1¼vU¬Xº:º'LŠéSûòþ;OäMyæò1¡|œljþê'Gò#ËWvÅ´|ÎðHþ*Ð`_L— ƒLŒðòìy'“ Ý­U±lUw¾¥·$¸õY›§}úÓöä‰}™‹â¦9¹';7”7Ó]yML2Ï‘vH^d;û¼&Ÿ'\½:ß©‘æL—‰<É2’?w­‘åù‚Üq™àÊGíJŒbõ²Á˜0%ýÍø-}x A¨¯íWêå)Ü£ue†_[Bw&ZÚÉ¡Uåѧ S¦”.ƒáÑ"ò%Ĩý‹\ù]þ"Òà¸I1>:ÛñsÑ™u(7ÿ9˜yãŸýÏ"ådMŽ@;ÑÑŠÕËWg"!c6-)1âË#gi2¡rĈGª†I|¥ÎÇ#³»™èáÆå‘1â'Ð3МG$Rʯ„1vUŒV­YçýøÂxÖsŽX£L`åP•ùÊ!˜ ň$NwoW>~– ÑyDŒJ‚†x”Õó¨·k\ܓǯ¸òʘ?~™÷®ïë®».v[¸0ç@Æ0Ç‘0·_4Ìžñ a–1&—Ôkf$]$‚ºóÄZæ¿Ê)›ÌïgËy‘!*ýÊy˜±â¦8áÅc™÷Þ•¿(·jEyÞõjý»Ï¹¸g¡a—:Å}‰64÷-÷ö#J}Fy!tøYsĉëƸÐoxY_и 5ëÿûû_3þÍúw½ûyi›¹Ñ¬v¾öžO|¬7ûÿŸöþÁÄ^{íUîûýnÊüàó‘5c¡.NÍÏOdêÏQxüœíÔóÛÜÿóóßûîÛ~êÁõíÿÍý¿û„N8'¼ê ÎR …62~ù€&)~ýÒ×Ä’³Îi?ê“ótñ§¾'k¦Æìµ6=ùsÈË~zIþöƒ1õ€}óý5‡¤{O’yä or_¾¿$ód ï!’wÁѓɵVÞpó˜S8MèËöü¹Ÿ® Ñ—7¿<¸SŽ)äÝ2gDxÌ&¹3pÝћɕ^ôåÝ1õMèÎw·d².oœ‡39Ó7ÚdgÊÍvÞ-“!ÜüTš,m~µªoRú–8ÞS+œ¨ÈµZ½yCã¾.Fi£;“3%F]Ñ›‰¢¡<µ“wéå$M9I‘îro?#éwèéq_ŒïËÄIƃÍÒ÷öroLjG•Fß•ïx—/µ®c” ʉ“#:c_b”ŽdÏ×Ũg\oLÈÇÀÖÇ(]H#¼ä×ugx¹ò‰:ø$Ž™tƨ$ãeñØÔX1úñüglõ¤'•쮋QN ^Ú;ô1b¾–ǪyDŒ†I¬0%FÙ‰uóh8Ç{z¾ìÒK/‹9ùòq×õ´tÉ’r²Š—çÿ™˜É¤C—™«®Ì$ ¥$;s~ð8šËZÊù˜Ó Ä‹w‘läDRWùb”ø”âÈNâ&O›7ß|syüÿÄú§î î)î=$xÙ/¸ÀA§Îi|¡ŽýÅýO]¾"2@ 8ø(à…µnpòÁÓ).ý®!:¼´§~ ¼øk2újl ƒ·oÐõ÷}ÿ·ôϘÐ?J3þüŸÿÍø7ëß}­YÿÍþϾßìÿíï*îĤùüo¾ÿ¹Oúýï¾|Z`Þ¼yenð“9b¡îw*¾+J‚óû¦t u× m ²øPç…º #ÛyÿÏ“3<ð@ùÁôx9Çiÿoìÿݯ|å+O±ƒvÎÇ1ƒ"Ÿ¸:8Ô=-SOYdHt(kgµ Í‚ x ü—v×>tê^6hwÊá#x. ¼µÚOí€ãEE¼©zþf›æ»<ò§È2QÀMe*Jû|qϛɼÉÊ#<ÊÑJ½½Ïùÿì€UÕµþ×m33Cï‚TPì˳DÅÞõ©yi&Oc4żüÓ_ò,‰/Ï$&Æ$£‰Ø ö^QQ@ªÔ`˜^nùÿ¾}çÀeœAÁ¸̽÷œ³Ï.ßÙ{ßó}w­µ­'¢Kr»AUÖ‹Ÿ=ÇZæ/²>gœh½9ØÊ˜l=?u(øá梶#ôz¤õ:þSXÜOŽ´"äþ"ñ"‡MB" *I\î.bèâ«bûmh.uB”2–’!ï˜8¢R »r;Ár%xr ±×µäF"Í଻jŒ:‰ŠHuȶ®K/ zJ qeH K J&/H6y‰`w£PGH7t=`DF\Gß#O†KJ‚u ÖGˆ»G)W„³D O.F£¶e}ûöû‡b¤¾“J±²Tk4¬• ´iìhÉ:›Dg/.ÆÒ a.¯æû wÛM_"nÄ0 0ºÇ꾡¿ªÓk|J¶ác‚¾Kç wú§Þ$Üp]S}³­ÄåjÀ€ëƳê°%Ç4§è]sŠ,b¢ùAe鳿Amaü°¯yCŸ£9Ei¢ëõYÇ•Wt^_:®cQÝuN[4GEyDï:å]§}ͫѾ޵)]§-ÊWŸ£tíóÔ~a:}Ö±(]«ÏŸ„ù_Øi‹îŸßÿ‡ßÿ< ëpЮÿõÔÑ\ÍÑ8Šæœ–翨Þ>þ}þWßõùßçÿŽžƒ|þÏ? ›èÙQ)½·¡ýhÝÖæ}Íž=ÛLh¼RÚ=+GõþGÌÿ1Ì“À3OÔÁ U&U(úr-|ðˆŽë\v”.:uV‰"2ÏWJýºZ˜^iµ¯MyGjWT/‘€ˆ´„D¼è|T/ÖµQùÑ~$Òè³Ò+®Õ¦}¥ÊÒ¾Ê.¬·\:ŽùÔ1Öø!i‰ÉQ_%Vä ,\‚h£c²¼`õ+¬SäÞAaõƒUoh‚x#öÀU‚#*Ykˆ„ÂB³\E‹ çµ$µ8«¬Xĵ²|BtL 8\D”⾋*®‰Z§ö†vhO¨K‚‹¬Ägˆ'õˆA’e¥Â©¶kH‡Ê£¢$ÕızP`×@±IƒÑFH+ÿ”˜Ü±p ^O´'Xq\$Þ1Ò ø×Ç(A¨eå¬{gաƆ±S8þ+++lpUbe7»hŒ‚Îã?šSt¼ý˜Ô±Ž6YÅÌ…Qi)K…“ÿ–ÿ*7ÊWuŒ6•Í'z×V˜NsŠŽ mJ¯vi‹Îé|až:§4J«?å§Méô¹°Ìp‚—hÞT}é(¶Âk”§®×¼¦ÏѹÂú„‹x)<ÕAé£ãúÝ«(Ÿè˜òÐ5…÷_iÕÞèú#¥ÕuÚt.j‡Îk[™ÿU¿¨Î…÷ªð^Dí(LÝc¿ÿ~ÿÕ‡Ô_Ô¯µE}£pÜ„¼(ÒêOýI›ÒésaŸ 'x‰ÆÒøø÷ù_}D[aŸQŸRÿñùóŸÿ…eá¼®}m…cQXk+LqŸÿ}þWßPù¤Íÿz&Ÿ9sfXn»ÐÚ</Ñ»p‰¾÷4†ô'¼ 1†í·hŽk\ûº¶p<*m”>:®tú¬Ð/Ûo¿½7ÎjjjÂ<=·Fití?âùÅiò¿èª"šTTa5Hç5±ë¸¶¨“é]Ç£k¢FGétZ¸¨¨Øzö®°Æú¦ÔA·:†ÛTŽ "ae!J \%Ôä]¡¨0×§x¸³Vì(Ð/íÌIðI":EKþÕ4/ª¨oDý&ê'z×ymÑ|¤´Q?‹ú˜ÏÿùgE«hL ?}†úÛœçÿoŸÿóýOøùüïó4WEc+š«¢ï¹è™oÍš56aÂÛk¯½Âw¡æ0¥®Ó¸ìh‹ÎžSßÓ8Ô{4ÿ鼎nÊ3šCu<Ê+*7ª{á5ú,ž¯•©•_4·n ã?öøã¯ 0UNVC£‰­ý~Ôèè¸Þµ^©´ºaÑH”Þ•>ºIQz½GÇ¢4:mú¬ë´é=ºNûTei‚ÖµÊ'JSxŽë|´–§ãÊCïQ¹Ñçwß}×<ð@6t¨5gˆE™‹®•5‰d˜¸,T3$¬(û$¢Š’EŠÞ9@: úRæ ,[äÊ”BHÉ"–èŸô’±pr1Ò=G@ɲzQ’˜1AO‘«‚ HñÎ;bŠ wðÂED9ÔŸÀ¼ Ž)¦ óî#ª£ê§zòMˆ>RgÕ€K(WjSÑEyw)Êû¤•ºH÷Q[I¨¦r<1R`ÅÕ¡ilÔƒsŽÑ' £d¢ÈÓé‰'ž°wÜ1ô¥ˆPG“£Æ“þþYÆ¿zs´EsÆHôE ÷Ây¨°}ÑuÑæ‹¶9Né´é˜>GsWT†ò®Óy}Žæ3}Ö_ἦy/ÂXùFùësáœå‰QÊ[ej+Ì¿ý~T—è¸Þµ^áµëŸyþÏ·.ÿÝáã÷?ßO"|üþóɦ>¯¿¨¯è˜úKô®±¡}¥‰ŽEãDc8ºÎÇCámÑ\¥}áÍ3Ú^Ñ*<…q”¦ðºBìu]totMtô^X®Òi‹î®ññ¿»a¢-º/ž>ÿûøžM¢q¦±½¨¿è=:¦tÑø ª­_Ec0:§|µùø÷ç¿åù?qÞyç]¡¢Ž}ùD_D,ÑÓ¹è³ÒkÓy¥-\:¦-ʯð\8Ñö"P£ªCJ§ýÂ<õY*;ÊGi•·ö#QFit­Ž)­¶(Ÿè¸öu]T¯(O]]¯ü´¯kôYuÔ&·yóæ…X5“&M´ÆÖf¤¾dƒXB¶lX¯ð!©82á,u"¯q8â¨7$lÄYò:‹åLB" ¢Œ,\‚H¢rù§|åR¤\b´EçTXǸ˜³h,ü‘o’sÁ2‡ý|àV®"‰% KZ^QwT¾Ü´ÈŒt`Àu²~Ñ¢Û¡@ £þ) %G'NúŠ 5#¬°QÌÅËá΄öKö‘;•ñ ½%°Ê ¶>\ìã'#õƒ>}úÛšêÕ6þü`é¢q¤1V¸ý3Õ[óæ…hÓçh®Ñ±hNŠÚ©±Í—:¯ãÑŸö£¼¢ôÑ1婼´é³ÎGùèå«-šÇÂ/QÞzòPíGiõY[´¯wmÊ·ð˜Êh_¶ö ë¡ë”_˜Úê¤ýöeDõŽë:m~ÿׇæÉ߇èÞéXû{à÷?”úªúÕÇõý¯R}üûøælõ}ÖøŒ¶¨OFóœúg4_*M47F磼¢}¥‰òŒæeÿÛÆó¿îÿј-«ú¬ÍÇÿú9.ÂCó—?ÿ培·Äü»îºë6dQ¡ëùK!ýú÷³qcÇÙˆ#0o&ˆj,XÃ(jŽå”ÓZfXJŒ‚¸À¥B°[)ø0áC¥^H¸Á5(‹ WæÅVʼna#+”pq0­!)ù…ÀÅmqdôhG¬É6I¦nÐopƒ‚øH¼á.ªxY餄´¶–ÙÖGù83\C™D¶®cT!Eâõ NHfÔ+ 92Æ KZkélUÁGêmE€›“ÚKyTDmNÒ–´(é²ÄêQ]£OF격âç{gö»¦X5¾9Ž€#à8Ž€#à8Ž€#àtX}}±laæS²ø˜ÿ6ÿADØç—kˆÌ+À ÿÅÓó/œ ‰¥é¿ŽkÓ5|æ5¿é]‹‹Mˆ…™û*C_‰Ã2¤Ë'Uzý:œ*TÇPWR*ëpBes^¢CÈ[çÚ %)œSÞC[8¦ÿm•Dò`ñA®F¼+e®¿²BÉê:òË Ê´° v ±Cç3:.ë "Òe‚I uE=‰$8†oTÐ@ôã " «h§8Þh.ùzb5““K¨–’K‚1cäÌ$KtNé"„DaÈD½ ÄpiB÷†{ %­Uk‰;jg޲Õ~í+v¯äÉ«ðDyŒhsh‹Äþç+IÞìÄ1±S°dü­ºùüº™%VNœ•©Òj8¤za®C½h£cô‰Æ(N‡Ðêfrç£W„­+cMãÀç£ÏGŽÑ¦çlÇÈ1ÚßýÞ¼y?Òs©ž‰õÆ‹žáùF¯ú¾Ö3äxÎö±æcÍû‘µ\˜cÄY¡Pṵ́øGðþmm>J6Ö70ÙŠÄËÍEüR®H˜xâö’ÄüyÂRÚ‡Ðk‰d©2"]YÞƒkŽ,AdÂu1>+ˆ®„ „h ¢†¼&ö (­BiÈ‹»r½QÁÁúC7EHqOäR”凬²¢T¾OIË5¹ ÐPžÄ,O°“AÌÁµGð¬vz\+-!7¤-…‘‚ÓıF‘XðRÝÀ‹&`D5°O*% –II!Æ:'‹€']V®N#ꩺIìŠ0j³Ø…°ÉrH‘{À(¬.%᪠#)Åå&Å€0’y8J“ùY¸»Ü{Ýa$q‚Þ bå¨|‰]Ábˆ:ÆhËŒq yqÏå2%ë"íJ”ÛY¸wÁú‡ƒd™•õòn0ùcy“#Ç(¸¸9FÞ6>Ö4ù|äs¶¯ùw¿¬™ýùÈŸ!ÿ5ž!ÅEÂS0ÍQüGRn5.â| *âœÏçý[›÷o‰±«\±Ñ –E–. ‚åI ðÁ'ï~Ãüñλ&ÅUô¸Å'‘—Õ d[nDr?Í—•Jš¼b!vDÑ!'"¹O"(.ŒÔ– ¢€DˆDxDZ(P”àqH–;²j©Mœk¤ăŠ¢n99¹S¡# P&¥¦ånÁ1ÊлD›$ ò±W$p=õ—µÄŸ`C> ©=ÊGeëž6Kn Œ$H£·ÞzÓ¼¿Ô2-MäL»À\Îe²ZÊ‹^À/ë!êHzHC2’Hãȇ©¡}1j_€$>é$ùäƒG±¯Ø:ƒ³¤f#_ÝW¹®Œ„ÅŒT@„1×JT"DùDedѤZ«LÊ{Ügu|¿à-ME}¢~¤’ó5TtøZ’€ÇÑuýˆFæWC„§°ŠVh¿cäAOé·Þ$ÐúXóùÈçlÿ^ã»Ó¿ûyòç#†Ôó{þÇÆÖgÈd2eC‡Ž°ñ»ìH[CÁóÎÖà"Î×à@¢Xz˜tN ÿ‚¯9ïߦÇZR$ÆŒ@!©† Ã!5¬$‘E–-¡S‹ (ŽŒ–e½}œ§E-÷,« Ü—Pè ùù¸(œOÇZ_°¤QP]I\+’!ˉ¸b¹ÐAâˆ:iYƒäZùLÙ±V,1R "fü—`lÌgFWÞ…<8.‹¥“ „þŸ´ò¢<²fáO[«\™dÕBíc žˆed< R1i‚%y+6 ªŽ²ØR%RöÎ;³¬®¶ÞöÙc+-/k&TOßGÀpGÀpGà“„€øP]m­Í;×´’æèQ#Â¥[ƒ‹8_“PãœÖy¿L<¶.ïßRc %…ù ¬‡®…a°t‰·"¬H¡–õCŠÕ~°†‘$“ÄME^D1,&d>Fb—V:½Î"ð—ĉY¬30#‘qÖ4ˆ#*‡÷DŠãJÍòÒD•„,g$ªðëHF‘ËP‡âXqH  bIq€”`¥i^$¹äi*'oYv`‘¡ø4ù‚°¦A@Ê[ßÐN„ø4ª‹¬G$£ŽkÀ¢IR8c†[d°ÂƇ-„Qq¨®a­Ýi¬m7l¸¥XBÛ7GÀpGÀpGÀøä"ÐÚÚŠ[_ÒfÏ›m£b£ÌZ =[‹8_sNë¼ÿãáý[j¬…(!_dF*¿*YªHEQ MYׄ%¬å*Äçü²}X°( ËJËâ&žM!¬Ðh„ .Çj&%ãÄÎ3çfÒÈ&˜¼$¡F"\g’䉂EŒIøA`Q:¹Q+Å8ÉHÍAHÉâ·Ë QpŽ|sÄn‘Yˆ©BÅU¯±q¨tÞµ‡òåbÃêF½•Ò?!Ö «…%¬ƒO‘òa2 Ad¤4I`â*\s$6q™¥-…QŒ=={T¸P´¾9Ž€#à8Ž€#à8ŸtônŠžü0®«·.q¾æœÖyÿÇÃû·ÄXK*†ŒbІ¥¦%r`a"QCÖ1²Bч,JL ‘¤C‹ÒÇ3,)ÛŠPƒoEh!­t©uŠŽ¬W¨a\ÇÑCrRv¤ÌH|¡ vX°ka‰l(VPÛ祗de²ÊQ^²xÑÊLÔANV”Îꇒ’ÄýIšbׯ°Röh=á¼LÕ¦l[]”¯âÙhÍÁt!*ä¦ÀÊäÁÉ "z¢u¢mò…WܵwKa”Ã%L…$äè›#à8Ž€#à8Ž€#à8 ~…7eá ¹4?RoÉî’XÇ×ZmUc“U”v‡[•®ãkP&‘¦i¾3 ?äC ¡Œð;x^~%á­Çiß[¸Ðºï~ˆ° ¯SœSüW?úç‰ÓN=ù8hP`¡…Ó>:ã1knj¶îsss³µ¶¦­¥¥ÙÙ?ÿÜs­´¢l§ÍXkèWÅðÚ-Åi7Æû³-phŒ(V®zŠ0+kmpŧÀ1Aòãâý2êP\VEäýGñþa´9ÚH2£e‡P_ärDW¢W/†ŽœÆ}'ð°<û²ÖÊN ܘT°‚¾f "ŒŒ‚#K¬ipEâj®çU"ЬS°tÉb©“¬¬¤%„é/R,rtæ¤ÄYõëD.KMÆE*#%œ¢:N½âL\­E1ÄÉ+8OYN‚uR|œ8YbÃ$°èQÞi7ÚÉ©=$ë‰1rÀ’ DcäcEQE”+9G-­ú°—Om”$•S»Ú0’ÅÎaß4FŠÓ“W“„oŽ€#à8Ž€#à8Ž€#𯄀xÊ‚¿²µ ß°Òþ#lÔ—~¡¨KMŒ‰Ë 0ˆ~Ei…Ä7Â×ZßPÄïÁŠCÒÂuIiyhÆv÷ÝwÙÙgc°¿eš¡1„¶€0ñB ¶li¾V_Woý}º½öê+VYU‰€Ð< úõëo“&înv˜õè^²EùZ{Œf¾ö*bˆqIZKº1ðÛ²Ò [º´ŸM¿s—øÚærÚ‹ÛÃ÷=h—^v™õìÙ³Óû\]]mW]s•xÂÉ6¤¿ÄiE·a¤ˆR0V8uš÷ïZeù¾‡äd,ˆ8C °;ìßh·YS¦ÏØ­ ¶"ïÓ«kX]óBt„nÖØTcE%Ý-µ•yÿêÖµöì²öÞÚY¶º©2pû^E}lT±¶÷À­w·þòþöýh[ÒF’a)kéXpC š “B+ÐKØg  x_‹­,$mC1hb9D‘ ™Ðadå‚v®Ó²Ü$`r!OÍ8ä2gU(õ˜¤ò”¼‹p¢Ž&•—KQx' …’ !)x,)zpŠXÅ‘‡¢ÒHLi•r&+:¨Ü±â‘tŒlT¾”æ hÊ2G®]´3Ðì€(+äBÖZÊ[±s‚ißýž=öèÃöî;ïZm]­•——ÛØ±ãìðõQcÆæÛBzÃï~cÿñ™OK#-u^ˆQXba‰ ‘Šº“^M íå½ý–ÆÊèÖ[omxÝþqÇg=zôX·ïGÀpGÀpG ë,[¶ÌÞ|óMüRkjj²’’D":âk¢YâküÜ~LoÉ6ØÓO?mUUUöÄSOØ>û`EâcdQã‡qÑü-Ë×ìI»ã®;àKe–D0è۷ﺖeŸž}î9{øïØ 'žhSœò‘ùš~C>í1ZS³ØöÙ÷>+JM¦ÍÛ!`U"Pôƒ×­é”Ó~€¯Á!»Êi.ZhÞ¯]ré¥öì{ØoŸû&t0X&`´ÎàøT¦ÌþcïïØ¥—\Š`stâñÖ¿ß°s†{+í,­Ø±]á´«Ö¶Øšj¬§VÕÞH.,\‹)Q ¾Üš]ƒàÇŠÊðà%ï/¶ªÕÕXá4Ú¹gŸhYkÁ°¡¿9¼¿«Ű˦VS3×ÊЭêÖÖZmË,X:I IJ‡¶Êx¡‹¼?äÀß7…ÑËUÏً`;–ÏŸ¸¦eµ½Tõ¬½²ê;bØq6yà~’ÖñþÎúQgc­½6¢{"à’§ B&ÒFN&ÜX¨®“Å—bÈ(ä‹Vªîª6"3:¢7·S€§e´“ÃU¸Ý ž0IÈRFÆDè»T†Ëè‡?»æ'6ý±évúi§Û9çœ;‚Hœ«hyXš›N#{--)¡&X±„™"K”_þ8úI2TùuÉ*ÖÎSPèL(Íš Å9{ã™¶|Å22h¨í´óŽôO©Ä-Ÿw‰H K*trü 8Õ+MžŸtMÐòU @+@òVjúÅÏnÏ?ÿ¼•••Y·nÝ‚YbMM=þøt»ï¾{í °/}ñ"ûë7Û·ÿÕ>ýÿA}7ÄHËVkÕ+ä$¦ºS>ªm2cS„eá&“¶ßÿþ÷6mÚßlêÔÍlÒöÊßn°§Zv³Ïž¼—•fº>7-}Ò®¿y±M½è ^³öû[¡ÈÍÎ2S_c«°ž*.&vP)÷}Ëo™æz[[ÏOl¥½{[ñ–/ÂstGÀpGÀØ 2<‹?õÔSööÛoopUCCCX‰I«1í´ÓN¶ÿþû‡gÿ mÆÎÚ…/Yi¿¾ˆ@uV1dˆUÏ{¶KbŠà·çÀ¡Dje)‘ K¯A•,Y”µ9óæÛ]wÜi“v›ˆP±¯½ôâóöî»ï„Ï|ã {áù§m· »ÙŒ3læ›3í´SO·íF ÜL<-ùùÚm·ßxSŸ>½mÄÈímÿ}÷·aÇZ÷âkln²E ÙÓÏ¿oƒú„csu/áÚRÔTÍMqÚg_–“án‹aƒŠaÑ’c%æ’T•e¬´4‹CÆæÎ›cõµ XÜ<`‡|˜¥Š“¬¥ÓH\Ùd—xGœ¶=F²ÑH*î-’aMý|kn]mi„ÐL¼Õ[–Xszl.ºqBý­«¼?Ž’QÛ6‚ÑK•ÏÛý‹î ý½³-¢ôÀ¢»²2¶÷ ïßX?êª6¢8»¿ýÝõöàƒ£ƒ›Ø?*C‡U‘yÆgØ ÇO ÚAW´<”ÈXCª¬.’0Ó 0ŠÙ›A`È`1“ï9$å ‚ÿþô¿¯D4y#(ÐÓgL·³Ï;—€u 7!¦<}¤0Éz&‹%LVV7tVÍB Ô¾¬„„“ fÐÉ”6IJ‘š,…Q“×rŠ<0ëBrdž >–ÕkëlàaV]½ÖÞzó-Û ³6™\¥e¹|˜V. \°ÂäFu¸^‰%äi¹nÅ©IpÃòJ4j$—Ô74Û}ã(•kläÈ‘A–Ú^ZZjõõõA}—õË«¯½fÿyñ—¬s¿„UˆQÅ,Üd a´š…[–%gc–52Ô&ßÃh‹ŽIÄùão²ã¹Á]³°i´ö »lÕvÎÇ Ö´.Õ.¾üÛë khC»ý¨=ÿ˜÷f{õÎ_ØõŸ[Pü0»èÊËmÇR:…ÕÛ=?ºÌî_\pš_ô;mÇŽ¬™jHÿuÒæa6ÿžÙO7ȤÄ>ïkvÚ^ƒ÷YZÒ@IDAT6ÌØ÷GÀpGÀp>6"¡F±avÛm7Ûa‡Â3¾Äš9sæØ+¯¼²NÈ9è ƒ>t½ª—Ì‚´„Ç,³^CúÚò7_‡˜µðÛíÆ]¡ÄäM Ð ­xèhy ò.s€¾¦ÕsãÄôl…{ýùæ›íÕW_³ç±^ùómƪ¡fq”+Òµ?¿6ü½f͚Ц¢T‘}Á@dUÖò–ø°|íé§Ÿ²_|1x œpÂI6\›ÅDÜ›qÏŠQÖö£ÆØ˜1cìõ7^·;î¸Ã^|éE„%Óüø½¹|-„Á 9jd;À(G›ŠŠºYSk-Xx›q26»ÀûÄeeµ²žÓvÆ×D€Ñ6Êi—¾¿Ôî¾÷^û*®O²¨¹õÍ+¬¨é n[µ°Ñöíy6÷¦Å}ã&0®nM}K톿K5r¶ïøÃíšk®´ãŽ;Ù´.¦Ž¼HTWîüF9íœë†ïÛ˜íúQfν¿ÚÖbpÐĊΊÇÚÐXcÝŠ›±ÞÉZ¯ŠVÒš=þÄ“ô­„í{ÀÞÖ³´‘Gœ[BH缿+Á¬‹B›ª­²îMÄ Z³Æ„••ö ŽN‹µ¶´XQ·îÔ-Oî»Êû剓†ÓKüˆâbT^c.þ[—ÇêcïÿÍÆVŒ±þÅÑ:ïG…cmcÚˆŒS~ø‘0t%&­Æã#X˜M=áxä Æ^´‘dŒŠ×Ê–»Ž: NËuGrâ‰Ä8«&å(äÊ«®b2{ËFŒŽ’—FÞ/?€S"KL ùë®CÆBe¤CР|cÎ1X²L0!P°&ŠPÁE×¥QÐârWbÖJ`ò¢ÉDRRŒ6bÄP«­¯³AƒúÙ²%+©Ë,;n\˜Ð2ŒX=*Äס̠ީ º¼k®Sìœ8ùÊJ–„A“­ÿûÅÿ!ÀïÀAÖ‹ˆãŽ=ÎvÛ}W–/¶,ì¥×^±{°néÞ½;AœZ¬OŸ¾ø¶ä1SÝvÌxô~)Õ2“«VktÏ€‘ıÎÄU1R夤w´{î9XÙüÁN<ñ„. 6%=Tv ÷öcØÀ)ë#™,¿µßŽo±÷&{þ–kmZÕöí‹Ù¨åЬÛ~l×ÏXaö<þxòn–^ñž=úà«Ô¤µ…ÝŠa¶Ûˆ>Ü_>ó2°„NÔÁ6ÿÁßä…œl©¶çÔÃì¨ÉãÌV¼h¿¾öv›qã¯mÂnß±‹% ùæ8Ž€#à8Ž€#ðq" —'YÔˆP{ì±pˆõ?¢)ÎÈ{ìaC‡µ{î¹'¤=z´ Á*fs·æÚ•VW¹Ü¶ßw|¦ÆJz–Y “êE/Yïí÷Ýhvâ"Úâ+²IèWkYçÃXôãrª¯‘D\AÞ'íi¯¿þFà«V­ú@"ˆ‘P#K€‰“&¿à*-<—jYs|¾VWSg÷Þ{_jŽ;nªí:a<-!¬Ä¹ÿþi+ÆŠ]^øÃu³]Æï‚áHÖþ6m×Ýc'N°Òâá‡ù.óµðC?ÉŠ^Ù£ââ kni ¢Wum«Íšs›õè‰hP4‡õœ6FØŒÎøš8ˆo”ÓN»kš]vYÞõé¶·¾o© øØfà°²¤ž—ý{úk[SÝZ‹µa]Ü`¿}þ{á_rÉevõÕWÙ¾x!ü ^Êí¦ ¶+Ë“ ÷·3N;°o™euÔÈVR·5«×XC½Y7Œ ššbÖgÐ`D’&›¿p™­¨l°~}Zm`ßR\Ñþn pÝÃŽ<ܺ!ý‹2;âý›…õohXeË«_±æ\ù¥‰O´õ(+G<|7YbÏF¼?ÝN´O†"Ò:ãý2¯Ò«;Âè™%O’OÚÆõÞÙÆõú¾Vz¾gÎáóq;œÌ˜Ê³à·W¾a³×¼mÏ,}Ò¦nãªó~´ÁX ü¾cŒâ±";ù¤“ì9bT-´‹ò¤°RÞœ¹sìµ×_ s9qa.¾øËVT“„ÔIa3óÂóæS¿^½z1A‹ 0ÝXëd1ë¢aÙ=-&D µÐÄ’ƒBÈv¼E7Zsfá&qèç¸gEÛ˜;ÊÒFæTÛüÖ¦ÞHØÛÒõ}í·?¶«í¿ƒXÓ)™Åv?B 8ܾzÁVª„=v·³Çì¾á%x.UìyŠ}îÄ1o·—©|Æ®6·ÝÑüîÈ#/°‘љއڿþ¤ýôïå’¢äþî8Ž€#à8Ž€#°åPŒm²¨)j~ùË_Ú¾ð…pnàÀ¶ûî»K¥ß”X³à©­jîKˆ2¸ŸÀ§ò[Î*†Ž²n¥p§ î7ð;ïfó¿Éæ?u[H’mm†´ZÙ€‘¶Ó1—ÃEÄ 9q!qyhá­P›%0°VÛmÏ×ÄÝðŽ)X¨Ü}×Ä/Y±åÇbø†ž·ûõëg+W®„GIxÈ×ÑûLÞƒ—-œÉמyöÙðöСÃl×]ÇWœˆ¯IøR¼ÏÚÚZˆ)|ñ‰Qat^yùe[¼x±=ùä3vØáGÀѺÆ×ò ÃÀ©5ˆÛì·_"pr=V0`$kN´®™k½vÙìdE„1Ü£víZ[DÛJĺœxø˜öÄaÒ¸>È×Ài,/qZXªUTTØžÿ¾•V|…+·dmÕûvÈ២|šŒ@ÔT—±Æº´•öMá¶Æ}ˆ5ØuO|ÇŽÞçú 9!Hdð qϱzÀÚ !Š<Å”;â´ ÕM\—÷ áNZkSÚj›¡´Y[¸¸Ê¾ÿcmõê:»öºVœìe U"ÒTÙ eöÈcÙ²å+ì‹_ü<öŠ_SüÞ¿INÛ†Që%kŽ{üºcú ±fNí;Ü„"öÕ$Ylj¬u¤#ý3Ï<ÓÎa® Ò¦ ’;r¯ò²‘H¨£¬˜ôܯ—TÐm$™é• +<‘ F)!£ i0°ãR-i„ øéÿ\…P3Ó$ÔH™ÞcÒvÒ©§?<ùçÕ‚ŠI¥“Ò™% 2–m¦²”–—ŠDÜul5@Rë0I=‹©ƒ2ù¤ä›ÆŒGQFÿ¡ãª»²ITbB†Ú½ˆNÚÔÜh}™Œ*+WØ;ï̱qcÇi$.qM P”¾8.]2-ÄV'AZáI®IrÝ’§¶O}´•wï†ì+H²üõþð‡ßÛOwÀðu×~àCbˆÕ/yÕ¾é!öíik-1á³vïWÚ‘£ÊÛ’nª>²žùûÌYß eZ|‚ýé­§ìÌqåöÎ-Ûç§Wsƒ®³£¹Îlׯ۴kδªGm×>bvÑŸµ‘X³4/}Û$­ìvôÁy¡æ•Üœ«íö«o²&ÛÍ.¼°Ÿ]wÝ‹½¸×:³"&ßGÀpGÀpþȲFÛØ±c7Z¼\£äÖ£ ÄÛVͳ¥¯ýÝöþ÷¯c5Q–O á¶l !ˆj1ø©¶’G¦Ûݼ·çk ­r¬ÆÀ3,#)©=F rms¬ÖÊbÝB»eQ³lV»Ï7l·±“mÉÊEÖ¿çP+­g#‡Ž±ûŸû«•Œ©·²~EVŸÀ FeSIvÑúuñYFb™Yù¨ÁCÛsÚÖÚL««i‚'"dàjÔ­±ÁÀ÷Ðý··že%öÞì%ÖÉ5§HZfKV4a©Ó`ûtgu&˜7f›0y*.¢Ô¥÷ƒƒMpZa¤û+£‡e5ïXmæ-k¦¯«=©TOë[1ŽxEÕVU½1$…V°ÖjXÆ;Gÿ–aƒÚ–cÕ²\²ݬ<ðsdWÇée]?B4à˜îEÇ­iλö0»øR‡‹\Fbn‰êGÁ3f“c µŒtŸšY2]žGj»ŒNB0fuI‰ áÞj#IJatèðæŒµ¤‚öÄ%šÐÇÁ+¹ɺ† ÀŠ-Ã{<³Ÿ_ýóuBMw‚îJ¤xøïÛ-·ÜVKZ? ×#¥NwÖYg`r.( ,•AÏÐÐ?tp3’Ôº•ÆÉ†·ÐiçÌy×–.^„š&‘`}Cãã'%%ÅVÚ½ÌzõëI¾qÔã¾V¹²’É(n£ðŒãzcàJè–•D'Ýæ´”]•NMH1Ü«öþ’%ÁŸSÖ2»Œ”X*&ž87⮿ÞaÓ§O·þýûssQº¹»:ÞzH1TLM@2ï «R‘OF7&(É @nP,¥¼K¤êl Øݶ­[·¯¬ ÇJ'K•ÍÛšìo—ìlS¶ÌÎþÉíö¥}R6í;gÙY{ßlUÏ­¶‹öêe•}ßFúm|L·_Mûœ oYh/.˜wmJ/¶iߟcgÿénÛc`ÝöƒÏØû–í»ö6ž€Âm¹–›íÀÑ7[Ñ”/cÔÍ~{ñÛ)Wd•7‰µÉ¦ë“^ø°í}Ö·mÊå7Ú£G ´E,Eìí±ý>6Þ~coï<ÁŽ?m+î3*Ô3ݸʚª‹× $5‹—„ª¥jgÚ_®zЦϭÆÛi7;ˤÉÂͺªW?ùW»¹u¼í0a›´#>¤ëÎä?̹÷W6}èè¯ÿ‡í”›Öîl~·~õjkh^ksž½×nzºÚF}®q¨±òƒŽ€#à8Ž€#àlm´X‡6-¢M5Ñ}–…¬A´Eé£4x‡t*†ç¼çþdG¶âÒ’à~Æ .òð mˆ!¹ôRœ*øn'Üf~„­œ·|B)ˆl´mârIŠñ'+pÁ#?&òµ÷¾ðüsöì³Ï‡¢'H¥RvÎyçÙcFÁGhïØíßÙÿîw¯KiË%êÿ÷ZÛá¡1¶÷¾ûØAJlÉ+°%¬S‚ÛIùÚžwÅçFl72Ä߃Ós³„1¾ñ÷7†–©]ú![òƒÒßnX¨ËšÕXm_“p%š/|`_Û˜±A•ü(Ú@C›ù{qll=óHÅ}.f¹5Ósî·š²X½4s ,‹°‘®¬±XÕjô4bB!_z8Ücy•ÁŽWˆ‘ÎËëA+=I ’PS³¼Éz7¶óŽýRh÷þÛÙï¾³ž+ì·Û¡öÕ›NµžF ×ë‡~ í1? ‹äÀ]ãŠýÓžÓRIõ¥7ÞXbõP°D}ƒNWÛ<ºùI§ksf/·‡œ‰hRÂ2æù­MÛª†®Rk¬x\ÎÎ_È"9wõ8Ä… y¿ÊÚ§U»%¬T7,´eµ¯[÷Rx)­ÖÊH; <±Á–¯‘û“ú/wN\Gl(ÅÄ aNÁŠSŒ nY áEƒ(ŽåUZŠ\A?RÆ`”ïQÒdzòÁ-ïNó¨Âs^(6°,6ÚƒäQ¶NÆZGÚH„‘,´n¸á{ø¡õ† KjÿY¡]Î8ýT;þ„º¬$sX®¨²Z ³;™ÜQqÀÉI” hP¼Bàª!(´ š:‘Lé|Wæuí7‰7òSœ1ãq;ûœ³h?¦h’’MdÖÜ­¨²Ü<™5%*†€’á|¼(a+—WYÿÁ<?ÀT¹Gî›LLE .Ôù»%‹Š¬oŸ>¶Š‰côÊRÜ©[j%Lž”«ÈQòãUAÿ(@"MR#‡6§§KÊ‘,T'M:ç R“œ:«NÒve±“cýx"~+½6öµÜç.j6B°LÈ÷T:Ù"ÑKêâÆ¶(ÝÆÒ´?—~ïÞ ÔL¸üQ»ék‡„Ó{ÝÿŒ­b9µoüxš÷Iv箦îì/ïüÕNÞ<ØŽ ¯¼”L´›Z‹ölø¢ì7OÕYƒÆÇz-iÝùèÔËÿjÓ~t²•#Îô|ì×ö¹%µ¦¯Ìä&ësžÙª!›Ã'Ù!Œ7;`]mlðÞ§Û7¦^fÔjŸýÜyëª0æ˜oÚ/ —…—ªåËÃû‹·ßjÃö<š`Ëø2Þþw»ñGïXú{WÚ¾ýò’LŸ1£môšZ{zÆýáïÆaGÛ¿y¬õnË*³øQ»ú¾Å6ìð¯Ø±#Ö<§íD¦¶z»÷ûÿe3šÚv­ÂÙKßGÀpGÀpþ!ˆ¿H€Ñ‚!²úˆ\Ÿ Ý T1ŮԦôÛº÷n£:ÓÞ{ò϶–çÌ¡;±žýËXi‡%‘?ðœÝ˲2NkÆêYnyñ[K­zéZ6é߬×v{¬+FסÀKD#ø9üŠžç"9ξ¶–x1·ÜrëºX4Q&Z2{ üI‹¶4#Ì”t‹±ßßzVô´ªJ#6ñ¶Y³ÞÁ+¡Êöœ4ÙJ‰>+!D¼Hœ««|MDX[BUÞ*n_Ÿüõõ¿ ÂXw<²„µˆƒG Lü¯” #“Þ ¾&Ëå­Çn(#ÜŠ<ál†ë‘Uá‘ã×Ô ˆcM‚°ôN,Þå‚rS/xc¶K”wæ[¢{95 aÁúíøš‚àÊ0B‡ø@8Ÿª)Ä(þ–}W»/Ua@¿\g×]ú® Ô_Ðl°Í]<ËJ+p…‚ËêB×+–©Ü²ä8Rˆlþ)¾Š`DYà´p[ !KVÔ Sål|¢ÑF5[e¶Äžv޽>ó}«ZUGÜ ˜­f%¨azØî;¶±†z{Îr,kb¶/Ʀf;ñ ÷Çߦêã´X¥‰ASU7/¸ü­­Fü¢ÙÃûìNû‹lùª×­º©O,¬¸gêÊråÓ‡àz¥û–.Ê÷uø·b¸ œ0NH'ŽÝÖh%–9œcë£>ÝXeãŠàö$×§‰÷²Sw9;¤×Ë—îû÷ðù[þØÖ4æûEQŒ•ò^=ÁHdc­#m$ÂHÞß77À0nhSO˜ÚÙ•±¦êH^` «Q¤Ä TKT6ݹ,"„Ž*C$t@©XQ¬bDYÚè¯Qž|Wq4aœz€ôÒ/’2ãp P (-º ˆ„:œTI:EÐvéô£FJ掩FÜ}  -:­–³£X Ë#kš–'+©uÛ Žè¨:âÆ¨,uŽ“œ.Юü'ƒj­²¹NuÖ$Y]]M€¤æ`rw òolmÂç±3C̸êjX–¬Îêê×Z-“ek3SõkÕÇšŒÞM‡¤‚Ô3&!ƒ²bRüÀ3MyœÄuM~‹&/½wö§”Qº¶ËºôÖ¸vUH÷é“w_Ÿ¾d„í{l¹µÎ_lµMsìµ–Úå;´M¨YŸ0ÿ©fá+v˵ßÄZê}þÍ4•/¦ö‰ öc‰)öãËOB¨Ñ&0ò›&ÚMÖ‡4å;M±Ï¢xOûü+:„àʽזƒÞZùÇʹ悜 NGCä`³¦~;yÁ±¶ï¡'Ú¾}z°ì¹ç±Ùm©Jí  .µK/½‚_[®´ó÷£-¾ßn|p~ÛùJûóu·ó¹ÄöÚ1ióçÏ·ÙøÔ·çÚêz:VØJí´+a?ûÙO웞l¬Yf×ÿ×eöLet¾-™¿9Ž€#à8Ž€#à|, fõ!m³gGÏ}/ŒkÓqJ³A»o{œs œ©‡Í~æ=[9¿Þij.¢Gû¿kW®µwŸžku«›lÂI—ÛÈý?³AÖâ0x ]p˜B.RÈ×Ê{”Ù‰'h»ì²Ëñ «ªªl%AeÅEºÉ\.²lâЄŒ¶­î6qâD;ƒXeÝùÑ]õ ióøšk« «LmÈ×ôxÞ‚E‡~ì–À"þñµš:ýd‹÷M¸~3ø|J1YôD-ŒP0DzªžàÚ‘¤ ¬—bq«¼×lÖÿQ¹¨IœG4ʱ(LÓì…Aȉu+Ü¡C¾f-pFÙ‰›Š?¶ÇH ÕHü“H“æO\°{‚ÈØ½‚ (Q°ðO–O5Õ–ÄŠ)Á}‘1B°Þÿ•·G;ŒÄˆRíDZ;â´ðN¹ÉeœgmbÅ ,ó"¼ýêÏØì9ˬ¼¬Èz–—Ø Çîngœµ¿Þ¡?1bʉõš³ÊU¸ëQöߨݞ÷w…ÓÆhóª†åV/—"¬£`ôÖ=ÕÇú”·U¯[UíûVœèÎQÚ§Ð #r³)¦Î²v‰±òUw"KÄ”·J ÷Jßïï £±å;“i~;p»CíŒ çÙS §G‡¬¼¨Ü.Ùër+'õ+Ëž LJ—ŽÜ mj¬©bÄp?‰eèû`À¢@å›úë‹'ÐñS§?u}¬`8£,\$Àü°ŠïôO6­c·sÎ>Ûþ„ËÓ›o½‰ëK¢¡î‰âi§K…ëäßþq‚k5(]ßÊ@­ÃòDšl¾d²%Q1o×J¦c­$$yäB$ šÁ#†Øðøva®“8$¥ ä^¢‘€[¸h‰54ÕÓ9R,»Ýd½+P`ZÇòxŠ#Aƒ™á–ÖõRŒÔhQðÉÂÊE+OÉwqäÈaÙ;¹=ý,¯¦L þg j¬ ÌáF‘¬o¢ºÆ˜HcR4˜9žà³êC!Õgº sJ”NÉÀpÒw¾E"ŒÄ«mQº¥ÙœsÙ÷ N•¿";Ÿ€Êlkž¿Özï}qpiúÓ·¾kG 5;ûªü/$_whcbκDí>„úp›¬d¼ýºzŽýÛ?³ï^üs;úÍöè¯^¶›>7±ÝïØa´†‡Œê¿.Q‚¥äÔ\¦ï¶R›|Æ)vÿÓW[ÕÚ6Pê—Û[á{®Én¿ö§\3íú«mé…?± Æ·-ñM*æ‹{ØøCíâ «ì²ëfØâeø¦öëh ð ²òGÀpGÀp-Œ€D÷Þ{gýWmøðá!ø®Šˆ,lôYÁxµ|·6¥ïÊVÒs°M<ó¶à™ßÚ¼°#*ˆóÁg}˜ŒÍ~nž•õn»ó]¼ºw˜}"È-4&Ä dC."¾Ö½¨Ä?âðÀWž{î»îW¿\çætãM¿·³Ï>Ë$P-]²Ôþxó×…N›ÔþçÃêW b*æpyá?yQw‚'†",Y®ÇÂæŠïý® áò‹º(榮ß¾"ÜÖàm¡èË›ø½–g{¬‡‚pcXC•!TþÞrËïCÀG×ÎC¨²ne,¥½’˜*ÅÝ,Õ£§%+z[ XÈFÿ¤õD|M–68ä#˜þJB8Ù#®&¾,nFð?ÞÅÝ䥸@í7ÅE5ÿU+í-—+²Ÿ`YO•D'žBŒTf>©»oe§§•ƒÌPZá­#sõ¶kr­UáV‹"²Ý@ú FU«klÂøQvä§&YõªZ»ï®ç¬‰²bñ2[YUo½Y©¬‚™¤YŠD¼¿+œVµ¦kµÀàù%ÉžÖ«|˜­ï8“øtPå"7¨dB«W‰¿ƒ¢„áN°å=ä¡CZÚ'+&¼Î6èGºÏyÝ1F“ìc/V=C¼î&<ÓÞþ‹=õþôõðS÷k‡õ«mÍsÖ]zïI½dµDrlb¬u¤D0ÆN?ý´ “èNê?¦•KzWýÕ¦¼6" A"f=nŒ]ÕF’!È}A“HŽ)!#ŠL¸ZQxeM–AÙm¥žyæÙöç[n¶™6E©"{衇lѤ/¸àüÐÐ0¹è¦P¹éåŠ ¨`–É&›¤+ò9ÀUÿ“?\ŽrR”—Å N&{ 9”ÛÜÒDÐÈ!­ò"Ÿ$7WJÆc6wîkij°^½û \6X^å6xÀ–Œ“7#†ÉNÆ-2c“Y"¨ðYçéR0sê*r÷Ùw_н,‚î¸ív¿ã.Ö§o?: éQG}‹¥ÂWØ÷ôýà¶õ£þЊð}F2M”"nˆÚ%;F®‘£U’ÎðÕyÕA•édSçÖV³¦£¤QºŽÎuv¬làpb¼Äì±ß·‹&öÊ'Ë­¶wï%¸ÓíwÅH;êàrûÛÓíµÊoÙ”~…õl²'oýo‹wÿ²Í~ìÎÕïÌi>ŒÃõ›¬Ìo´¡ÒžtÑ5vÒ…Ÿ³KŠwµ_Þö”]‹X£`@Èk>²’v´õd)vmóß[m6¦-FMq¥Î¯Ì±Ò˜¦Ú {ÚJÇÛW^íY·¯×ÿð»ñÅ&;çÛ?°½úoû&QÁë‡Ä¨ ÿè8Ž€#à8Ž€#ð!p1nÜ8#yÇî¾ûî°ê“ñF+Í;×^f•"Y@D+'mN1½GìaóŸ»Ï’"²Ti·‰¸µ6¦mиƒ:j‚e ?,'Äà@ú½Y¿ ËÓ â"…|M?§àc»î¹‡õü3nNXÕ¨îâHßûÞ÷à1„‡¨ZE02Žë½w¨]±ª‰\µ•zâ´…(ÐÆ×°ðØ¾¦`͸ü VÙ’X#R*¶-þ&A‚6p ö¸–ø[‹'XÔE¢Ñ¸Çm_Óõ"œF’p©§‹Ï³†%x €¸ciß^6oæj{-¶ Ö*ã9ç‚À'ZºYÑôö…оV…W„Dµy0îX⬢…|Má.Ââ4Ü0HÌúFðU 3©4AXìF?Ñ+#õY.)HáÖ9Ÿ½äu¼=Auɯ8Û#\/V›¦œ$ÄX|4Â(‡«í/ü¸N«L”¤7ž0û¥jl,.`o7÷$À0ZmÆp¡®S³¶Ñn¿íiûËýÏZ ‹õ„À$ ˆÜ€°Ó€eP·¢bŒ6äý]á´Â(™*ã^‹T¸8ŽP¦óWÌ^­ŒLìP !­™b¸>îp¸Å(.R’tI–/)âøÓ<õDî/û…ýˆb@ ϯ;ÂHK¿3ò»ó½[íé÷/„<|–@ó×Y7¯;>ˆåÄG”oÏ~^`K!šQø&ÇZ{md=FÜ?‚4·&Á»:K’vHZŠÉH}é#j#ɰÔ8ŽÚ•¦Cª¶òÓ4“ï¿&‡sM,µuæ™gØŸþüg{ýµ7¬¼G¹Íx|õÍØg>ý™0Àc&eèY1y¸)X“¨cRs¬ñ¸êÏtr©®uÒˆdõµd’75Bh „ÄòÓ Q-ŒÎb“wÞ›mÕkWÛVªk¬µå=l(ëÉ7£Îdi‡:v,Nl:`ð“s#Hƒ"†«RNf=øKìvÄãÑr~3gΤnf—ÿ×åv:.\ûí–;„ÂM·Øãϰ[þr+uAÒ"¯×H»Ï>ûr¸Aj/´”º$øåE†®Š¡ ™~iþÊÉG=¯“-²˜‰Þ;IFžtÓ[^Ìȧ‹ ždg/µo\x¶];üF›ºcw{úWÚYbíË_>W¥^vì×¾À K?¶Ãû¼Ýyã%¶ƒ-µi7½dGï«cýˆþ˜Ýö·çí°²×lÒç§1gN@äZd7¶"TA5›jÖ×{ÓõAŒyçv»ü¯uvÎ)S¬gÃl«ß8¾Æy§ÜöÙâ´Ù©Q6aÔ³oÚ7#œX± 1'¹¿:Ž€#à8Ž€#àl<ðÀ ¼õÖ[aÅ'­úÔ~‹D|0„€Ð ¸]ٖμ×úoßËŽtmxöKøòȃïÄ“e6d§¡¶èÕû¬ÿNÇt˜¥ø=gR Õ@d9‚á<ƒ—v| ¶,+ž}ê)–iæI¶ˆè] ¯D[t|ÕªÕ6ý‘évÄáG¡¢ðLžC<ø|mÏ=&ÛS”½f y>ö˜tØAëøšK°GÔ2ÉûQÜ Ê^â ?±çÄI´ksøÖ c)0sÒö9šà¸­$\O%0 ªyâ{lé®–ÀML«<Ƀ"‘«°ïô»ÇŽØ"û0øt ¾&Oq·¼@ñ5Å!•ˆ³QNËõ²–9y— íž¹×[¦´Ö}›ìû7\b‡MžjC .,>©Mwõ5¸kMÓ#fm¹}jôyùUˆ1FE,e"Œä„‘åGß•éCGœVC m›h²É‰UÖO“Õ±þÖ ËÚ%ª‰&bï¼»”8L¸q¬)õ \9AÍ æÔÖ£¬÷yÿ¦8­0ê‘ꇞ¿é–eYðkZ=ßZ »®däQT„¶Ð³òÄ`öáoEDâ¡ D¨,×/߸@£Nx ë`b4®l;aäévïÂ; _´¡H–¿ùWåòoCŽÎ[¼´õ£,&=75Ö:ÒF„6(ö»ßÝh=ü0}ŸN±‰Mæ©§ŸaÇwL»¢ Ö+*ÀRsBý²ÉW®<2Œ‘L~ jD‹³Î8+ˆ¯¿þ† <$˜ æ> ø(HY \'eIËW…%¼¥„’­X w¡$ù‡U‘h 9SªúN°¢Õšä2#•–ò–] 5 J³ò®®^m½ûôµ‚GU”õ²Cu2¬ÐD ´,˜œ¥âª7ù+•ð΀%ŸšfÉ_Pú”‚] ƒ3Î8ÃVV®dbApiLÚoû;–fþU˜Pˆ`]\\ÂjPý¬“±ýØäÉ{0Øe*·#‘,(â€Ѣr˜`5ð“Lk"‹ Úè¥Ûh"N—K•$†~öõ§_´nžf5É.n»øò?½lß;nxØ|äì¿v³ §|Û¦Nº>“5ÍÑ?féëSþŸMýÎéöõ©{Ó¦ öó?Ýh/žýûüÁÇÙnÕ¯ÙNJoçX´Á~ ÿ^”××C¶¼lº>LEö³oŸÏ_þšäø/Ûc×ÝC™O]bS¿üí™`_éù«¬þš5-Ÿk+æ$ŽÛšoz©ûÕ¯Yå~j÷_ÿ#»?d…ùèg¿g jæ~¡ýîÆë‚@“/©Äö<ù+vÁîýò»¼& NF÷Åôoýɦ%ÛíO3E·ÉovøyQN›šöwGÀpGÀp % 6²¨Ñ³Ë–- qEgS1jäú$k 5úQö°*fçvÛA¼7²e!ÀKß~Õv;bwHEµ5Ö4Û¼×WÚ Â t¯(²q{|¸_+Aˆ···Ù~öókí+—|9XÒÜõîuÖo4.^õÓí•ÄŠ*ÏAuO¡€Ö½gÊFíÃÍõ½lÊÐóí° ÇÛÏ®Ækà”¹w’žHF­ªêÛ9§]³b­õ.ÊÙaCjm{–my¯ªÅVW7Øšñ´•Ða$ƒ)ö΀þXŸLÞU­·†šz›· Öf/«%”œ]ü”þÕï0ŠwÀi[ÓÍXî”àiVnk× ¹ x´6OÞ•@o-¸oUmgýR#ˆéÓj)¬xŠˆQÓ‹¦¡E8X±Œ$IuÆû¥äàÚêGÃhlÅî6´ûH{qÕ³6wíÛ¶ª‰¾.+‰‚mŸÙvÝGÞ¿¾qý¨«Ú!’ìïŽ7Þø{»øâ‹ ŽvüñçLçŸ^˜ð:N±ñ£Mµµ bðHõ2bO}pkª%j6Ò]²&¢… šPÐ1›+ïDŠtí[“îfýz¦ù`v›:²ñú°<_m>Y7–T®nªS«% r]^¨œl˜ˆ½Œ­FŒkN'ñíg=" «-]s=£êù‚NZD¹Ò¥¯éŸ ÔÍ@« ç=8E,1ßGÀpGÀpþ X´hQl´jR äòüóÏßh­WÌzÀÞyôÛï¤q¶zé›ýâ2kXÛbƒvšh+ç¾ ÅHÛè‰ýmÐè~öî ¸í$Ùøã¾û»ãŸY®¼A‚º‘G<52<'€sÖõ¶#‡Îw´]uÍUvÊ)'o••‰à™9~ÌO´q]QûMqÚ‹ÿßý¶Kãj»`5«5'ff¼¿ý¹ß®ö4±cZ±rI×ÔȺ֪–UÚ08ÚQ'ìoîW‘Åûöìë¯ÛÀU¶ÓÃì—ÿw vÎû7†QuÓJ›½úek‰ã^/鎇µL+q|Êéç½S£­;^réꎸ£0*Áø§‹¼ŸŸçnŒ~ðê7Ö±‘å;Ø©#ÏEŸ@kˆsoaýëGíÇZgÚˆ´»î¾Óžþ…çºÁ‚j#ð¢3úÌ»þŽ8â›rÐÁÁÚ¨+ÚKwK­¢1àÕ›äB$¥$(X(}YÄɹ1 Œs ÜŒò|ÊI'ÛéX£´°n}sC#i%îÈ*GOâ àéYºV’ÈÚz¼–ÂÎ"þhyp 9¤¦ÈÇ1.›4öÓ |­à$Ä´¬k”FuPžˆ/C µÔP&êÜHdoŠc¢‘õM~cÞbP'mãL(_fWŒ„P®ZG0Rås§ëôªåµžæÌ³Î±)‡LÁwõ[Ì„-Ÿ¾²ò2“)ä$Lö ègͨ‘ ˆD¨1bŒR²š#( U´ºë}c–5Š þÃþ(ß¼jòý([ ¢ÇF啜¢:LPb½ N$m:·=éz 7^Ÿdð)î<· ëÔyº„õî—_ÓQšb‚÷ãï£m +íÁ’ö-¿ÚpGÀpGà€€,iŽ:ê({„_Ì7eU£êU-xÝJJ‹mÁKlÁÌ*+ÆòzßóŒ‹ÉXâYÔÚÌ{¿c³žyÇjª*Šø³|AÇ+R‰Äù´ ªªà›9ýˆ¡HwÀ×bÄ —5l¸}å+—„Ï0 ›¼çDÛ+¡×_{ÍÆg{í¹'+"%íÀC§ØÁS¦@ϤÐ( „øšøÑGákY;ð€ý­?=Óþ6-pÂ{Yaè¶[o…'ñ®ÕD$Ÿ¿œÄ®Ë(¶iä(Nû¥%¨_º9gu€Ø«W…}Љ:.QD*œê@– ÕX'¸®a”B>”9]#&Faªž×Á&F1¢É­ƒ$áPtþ£ 6åïÇGÀpGÀpO*úvS56Cw;Î^øÓsV¥Å°ñûØŽG^uËÎðãçî'_mËfN³YþÞÒ-Õ¶ýÞÇF—nð.~ Î~€†/ˆÇèM+ÍJ`錯)ˆ%¸”Ü H¿ÿ~û³Ô’`ͯ!Í„±@‚û'œ&¬‚´…øZ3¢ÌèQ£íÒË.A zÝÞzk–­X±<¬v$ˤÚN;ídvÅ BHЮÂײ"nRÚaÔ‹ º±ù¯!PÔÒZø)îl­Õ‹­| \³mÑŒ4>Ài7äk]ç´}zõf õ“ƒ`óÍoý—³ïiÜÓ¹À\ñÝïØi¬Ô³¬'Uç~ˈ¶DœVËfw…ÓæXŠ|~j¥Çïo寨Âá£liÏÞaEåx¯ àô°ŠÁ+ßykªk²÷ç.ÂÊæ=«YÄRÛÍ´! ÖŠHòž‘ñ¦x!F•M•¶ª~‰-mXhCʆ[yª·Uae3²çÎÄ—E˜I(à0.v”SŒe w‹þ›·6ÑŠë2ðè ï¢Ù‡ÄHØ(ÛÞNÜþ\,¸Š18ù ïï¬u6ÖÚc$7®t}#Žh AI¡®É˜d½6Âø¥J¿ Úˆ bÀ{s´‘Ø‚ùó¹[h²z Ò™”=D)3H%y1…‰CÁ]4ÂäªDç"´3i¸±*=EËZE  „7'¬Ï¨" qgä”A¸ š Š‚:騂A%†Æ*è¯|·ÂÒÞ*˜„¸&% wš AâQ†óº6¿òwêI½Ò{„”D­T)™N©êòGÔ P{5EkÅ*YÛhS¼Âc‹‰DÊ“LÕ–ˆQY·îöæÛob¢¸oˆ‚ ,x‘¬L õ¾©MBM1±s\°ÙR~ÞpGÀpGÀض™7¨q;íbµ u›ÅEôCzþñµüñkjꬬG7+e¡”ˆ¯nÏ“uÍVâk)~¬×êLúÁ;­:ÁÃZ±®HèWï­Ì×DwM.9p91;"©‘ªW¯>–"VJ\\t+pÚåU«í®»î }m¼Q¶j,Á£‘!C{ˆ"a=q>#èYœå¬2E”!þ$·È5.¬i/Ó(ö%ÄqMJ“ÏK„Êç_Z‚Ž¢”kéî”òÄ~¡H[‚€ÅÒX‚• yf]òÿ "7ƒ<³ˆIgPkxQ^¤çò8""¡Ç1¿Ñ²å1Ô$e„¬d£Æn1ŒÀ-Y”2îêjNÔÀ7GÀpGÀpGÀøE@¼`- T$דÏ[‹8_Ó¯öÎi÷o}Þ¿…ÆžBL|Z°• vQm‚˜E)I"ذ#5$tì`´¢‰’CYV͉³¤\– Âôû¼å .JqÜx²ÁßI&y?$oEè‘Û”òIÈÕH.F˜È¤]ÒB2‘Ê ”(?YÝÄÒÄ—Ñ2×r£’á BŒ¤–`¾#M“®Tç+,}¨¨²‚ŒVƒŠË’qF¥'©Ÿ!åƒSà…@?Z‰üãIÙ‡!ë¤$I^œ,XÿH˜"ê¾%0ÊÒ¦ò=lÁâÅ“ž!ð…úæ8Ž€#à8Ž€#à8Ÿ0‹²¦v­-˜¿Øzö!.¿o-.â|Í9í–à´Þ>¾~[0o>žIrÂîQ"G\—"G«\„d:ƒ¨’ATÁi)ᑵŒ‚ô†X1ˆò{”F’•úÛQqƒ×¼È!³¢JR¾H>qB à ˜p-BKVB yÈ¢GQ‘e]“Q¾²¤¡GiU(Y¹È=*†5L?%y ê¢ ÀÈUK¾—’TH+{š8–1™tŠz`³Ã¹°–9§UÏ8ù„@ÉeØ%Ú„_Ì‚bŠÝ£w4 4xäËÿ葱•–[Õʶ?RE,Î:F!ö÷#k[r¬ù|äs¶¯}<ßk>Ö|¬ùXó±¶­¢2Ö/m.RY¬dTg£IJàQŽˆ@Zþ[šþät•à˜Ôé‘ }ˆs Â$÷,®"O Á$G,Ìt8†%" #âh 5É? È%ï©-…‘JîÖ­âŸ|¬I@R…_ÀôYÕá}Ì£§ã$Ô&¡‡ÿYðŠcV$W0% éhwHž¬‘ô9´ˆ»­¼HŽèl¸>¤¡ý:,Q ñ*ÔCùI4Óý+å–ûâ~Éd3¤Rª».¥¶²˜’ÊäEñ†tïãÔIîrÁê‰÷|Tp¡œ¿2äv£¶s<ÜPÞùïµáìy?ò±æó“¢ætŸ³™ý{ïJ};óàßýÌŽlþ|D—`vðgÈðLûÏð ©çã41kššš©.^[‘‹8_Ãs®êœÖyÿÖæý[d¬½7wŽ˜xX‰I«%)Æ ZK`çy²"Ëh ±¼7¸L‘FêÏGB(Ú•…Œ¾âˆ;qòá?IDÂSH#Z/W§Œ<»•ÄX0Š ƒ ¤s0ôÀ±L.â(A1¬mX´)DÖ/ªb€q\•QùŠw#¡#'*&¹Œ,p¨„r×㬖—KUއ:©×á{œ²5æå$•Ê.ÂBXw]"eˆ+nŽc¤{æy?ò±æó‘ÏÙþ½æßýúÁŸüùÈŸ!ý9Û¹ˆó5ç´Îû·¦6’¸äË_¡Ø.²BÑ£±yQãemè!ÏŒ`Åh!«Šðë•,`(XB†bÏ@á{ÁˆsX¦HàÐ2Ü’QÁGôHÊå(ˆXW(>–q­Ö¤M×p.ƒ5NH…HX¡IŠ åk s )Zy*¦X7Rg°$‘…IØW\«˜;Á¡Šô2àRqª Ñm‘$ð¨XÙ~-–3ÁÔFùðYâ’ª•*?]“Q=9î9FÞÚDVk>ùœ¾#ü{Í¿ûy`ðç#†ôgHÎܹˆó5ç´âêÎû·¤6’øÏ‹.¹"¸ÐÈAEæ@q,P¯Fr]Q`_­¯›\‚ie°•‘k ¢Ž„™ªdò* Jj:K¢ÍÍ(ć!ºN°~‘Œ"© ²€ î-*Så%Òh.²…᳄òʼn&ˆ/D¦áŸôÊSÚø§ÎiäJ•!U^^X ŪâXô„¥8&9'­–¢ á©Å&¡‰k#ÇHýÝû‘5æŸ|ÎNû÷š÷ûó‘?ù3¤?g;q¾æœÖy?JAó²…µ‘äïnøÔßGÀpGÀpGÀpGÀp¶bŒÜ=ØŽluñ*8Ž€#à8Ž€#à8Ž€#à8Ž€áõá@IDAT#ð‰G? ßGÀpGÀpGÀpGÀp¶\¬ÙVî„×ÃpGÀpGÀpGÀpp±Æ»#à8Ž€#à8Ž€#à8Ž€#à8Û.ÖlC7ëâ8Ž€#à8Ž€#à8Ž€#à8Ž€‹5ÞGÀpGÀpGÀpGÀ؆p±fº^GÀpGÀpGÀpGÀp\¬ñ>à8Ž€#à8Ž€#à8Ž€#à8ŽÀ6„€‹5ÛÐÍðª8Ž€#à8Ž€#à8Ž€#à8Ž€#àb÷GÀpGÀpGÀpGÀp¶!\¬Ù†n†WÅpGÀpGÀpGÀpk¼8Ž€#à8Ž€#à8Ž€#à8Ž€#° !àbÍ6t3¼*Ž€#à8Ž€#à8Ž€#à8Ž€#à¸Xã}ÀpGÀpGÀpGÀpmk¶¡›áUqGÀpGÀpGÀpGÀÅ#à8Ž€#à8Ž€#à8Ž€#àlC¸X³ Ý ¯Š#à8Ž€#à8Ž€#à8Ž€#à8.ÖxpGÀpGÀpGÀpG`BÀÅšmèfxUGÀpGÀpGÀpGÀp±Æû€#à8Ž€#à8Ž€#à8Ž€#à8Û.ÖlC7ëâ8Ž€#à8Ž€#à8Ž€#à8Ž€‹5ÞGÀpGÀpGÀpGÀ؆p±fº^GÀpGÀpGÀpGÀp\¬ñ>à8Ž€#à8Ž€#à8Ž€#à8ŽÀ6„€‹5ÛÐÍðª8Ž€#à8Ž€#à8Ž€#à8Ž€#àb÷GÀpGÀpGÀpGÀp¶!\¬Ù†n†WÅpGÀpGÀpGÀpk¼8Ž€#à8Ž€#à8Ž€#à8Ž€#° !àbÍ6t3¼*Ž€#à8Ž€#à8Ž€#à8Ž€#à¸Xã}ÀpGÀpGÀpGÀpmk¶¡›áUqGÀpGÀpGÀpGÀÅ#à8Ž€#à8Ž€#ðÿÙ»À8êºÿãßM6÷4Mô¾Ò»Ð-JUPŠ€*ˆ<  ¢€‚Àÿ/äñxO¼‘CA( JŠhåîIBï¦M“4Wsÿïovg3ÙÝl’M;mߣéîÎñ›ß¼fZºŸþ@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ ¬á@@@@<$@Xã¡›AU@@@@ž@@@@ÀC„5ºT@@@ðC€ € € €­'.}àˆº´WνDkލ[Fe@@@@ ¿«Î»´¿‡¶ýM(ºA6~NŒ € € €D ÖDš°@@@8ltƒ:lôœ@@@Ž,n­®Oÿï_²¾š÷Ò-ݺ¾Ûl n½êª,„5@ã@@@8V“ÏttH{}‹Hg§HJ²ø32D’“YM(¨±ë¿aMÿÍ8@@@Žz;l ´ž \n—ÕŠÆŸ•%93Ê$9'K:ê¤iûNélnÖÖ6Ø[Û^†DX307ŽB@@@£VÀjÌö¼ïîè””¼|)þÐûdøéï‘”ÂBi­¬–Êgž•ª¯”ކñù5jéÕ%ªÿH 0Ü3Ž@@@@£ZÀÙšF/TǨéîꔤ´TÉ?éx}á9’^:Z|))’5¾TF}ü\É?nV`Ôø4n±›!Ö Ì£@@@@à¨0­gœ‹ã³ ^’|ÒÕÞ!þü<ÉÓP&95ML+ÓãɬO+È—ì™ÓÄŸ›§ë;¬Ð¦[ž.„5•ã8@@@8Jì`ÅÒ˜+ë6cÔt[Éè>ÚZ&I×ù4¼ñ™¡ÌÿL«ÝÕgw}2!Y¡û t!¬¨Ç!€ € € €ÀQ*fºMc‚—®.Iò'KG}½Ô®Ù -­VkÓâÆ—œ$mj¥áíÍ:Øp}`asÌ ‡"€ € € €ÀÑ `ÒÓºÆütkkš.I=RŠN]¨cÒŒ“Æ-åRõâ*iÞ¹K¬|]ü9ÙRôÞ“%]n®ª’}Ï,—Ú7Öhcšn`80¾ÏtŸ²»ìàÇ8^kâ@b@@@8ŠL šÐ3>+Œ}ÁÙRøž…’œž&yófJJ~®ìþÓRio¨—}O?+õ«×KJn¶´Õ7JË® él1Swk@cu‡²­úÔ˜# kl?^@@@@àØ5Ú¦Æj[“1j¤äΙ%þŒtéhnV¦~ž!5/¿" 6K—3 ·èØ5¦õŒv…2Ý¡’’ƒ sÙèFeÆ®1-vâ_kâ·bO@@@8ºÚÛDtV'³ØÝ™Ì,OmíÚÍ)Y’´µ$wèÌOú9YCÚ´égö¤hÔ¢yéee4Ö4Þö`Ãñ…6vªc€šKD@@@\‚39iû“ÎHKE¥Týû%iÙ»_Ù$iÒnNÕË_Ò1kvK—†2íµõ:èp§$gfɰŧȸË/’¼æiˆ“®­nÚ3H™ÄE&îno×7&¤q5¦õMô…–5ÑmØ‚ € € €Gµ@ ËR`¼š@PcB˜öÚZÙ󗧤iû.I/)M;vKý[ë­à%ct‰øRÓ5ЩÌq£¤ô¢ó%}X‘dŒ+•Ö}•Ò´õ Žq3[²¦L”–={¥îµÒÕÚâP n›‚o k"MXƒ € € €Ç„€ÝÚżjp£-aDÇžÉ[ª™M§ìþe+ IÖ±k:µk”™jìÅAìþóRéÒV3¦%Mg«NåݩǷwHö´É2æ’ ${ÊdiÚ½[ǻɒÊ=gïói—)k †DÁOá/„5á"|F@@@cDÀMt´ j|þÉÓ„‹ßw²ŽIã·ºBÕ¯Ù`MåÝÑpЯ&kÆ4ñgfJæÄñRù÷geç‘\]W·öm9¸sŒ¾ðlÉ™:ÙòË*-ùóçÉþ.·>[ƒ›@ÈZìs?:^k¼E@@@cHÀ1e·O³“Ô‚\ýÉs%§lš5ÂLZq¡´VVIÓ»Û5œgµºÙõðãº_¾Ô¾ºZǯi•º7×Y3Cul’níîdº>ܾ[Lw©ö†Fi|§Ü {¬Á‡;52Ó{[³OÙ­z"½ k"MXƒ € € €Ç”€'úÿ¤Œ,IÍÍ ì7ïý~I)Ê—É_¹V3ŸlþîO¤½ªZwÖC´%Ψ Ζ”ü<©xüiéh<(õk7É®þ$yÇÍщwJÕ +­2’t)³twtš©¢ã G†6„5ÇÔÃÇÅ"€ € € €€«€¶¬iÝ·Ï WJ–|P’t îê•ÿÑ–2âÏΖŽúƒ’œšj6¡4G ê¨o°ŠËž6IòæÏÕ†·IÍÊW¤î-XXǰ1­h Nœ'… O´ºZU=ÿ’Ôoؘ<)2¨1…ÖX¤ü‚ € € €Ç–€¦3Vw¤ÀUwkK™níÖdfª[£3?µ¶YSv›îMIÉ)Rñ׈XÓv׊O!¶–®n<øßz O[Ø|D†Ÿqºì}òïR·zƒt¶´ê~I:fÍ™ðÙÏHŠv2Kæ„1²ý7Iýêõšù˜ù½µÎôG?™µ, € € € €À1$`$¦e‹¾×nI>Ÿé×”$MMRûÆi=P«Óq¶”Â<ñáHî¼YÚ"F»1™ÃLУcÞt66I{]Žaó†ì~è1k Ó]ª[g“JÎÊ”œYe’ªS{›Y¦Ì9YÆIŽÎåKK Ì>Ô˜›@Ë£À‚ € € €Ç@0¤±®Xß[ØhÄ¢II²äj˜2ú¢IzÉ0©|f¹ÔjK›†M[Ä´²±–PÖ£mctZoŸÎðT¿n£t4µJá¢ùò”Z3I™ñiº´…Y¬lǼjk›Nmµ#:ذ}^kÇ/„5 Þ"€ € € €À± `Ò]´%ÏŒcZ˜f/®tk+3þLá‚yÖ.‹È×WK…v2c׈†-fF'SBwk»t67K²¶’1cÜr‚ŒùäǤaË;Ò¸i³4ïØ#^yS§òž"YS&XåÕ¼¹&8žM›“b5ì±68~!¬q`ð@@@ŽÓíI»*ul±‚œ$¥¦hn“$ßÙ*Õ/¿*©Årà?oH·œ:|˜t5é¾:¬KÚðbI+&­:;T‡v…jÝ[iwÓ¼sg´‹SÓÖ²ýwiw¨éV œºuoKËÎÝV +ñ±~1-}zš Þ!€ € € €ÀQ/èÃÔÝÞ.É™™’9i¼'ã—¶ÚZ j¿ÕÒ¦aãéúÓÚÅ)CZ*öJÁ s%oÞ<©}sµX±Jü…ùêø$}ô(~¦*üÄßå€5^}K·”KGmƒ5·O[Û˜jnÙ*›·Z›Ö<þ¬ mc¶iW(—…°Æ…U € € € p4 ƒmQ“RT(ÃÞw²5ÆLRjšvYÚ%•Ëž—†··h ˜Nm]³Mº:Ú%­h˜-:QòŸ«ƒ 7Jó»ÛeôÅç[AËþg_¦m;¥Kg‘Ê;n–äkש–]{¤fÕk¦QM`Ñ@Æ´ØñççJJNŽNçÝ&­ûk4¸iŸŽãÖаæh|ö¸&@@@p$(> OŠßŠŒþøyâOO·öËž8^ÒFË»?þµ´ìØ-Ié:¤XáJŽ;Ó©ãÓxÕ¼¶Š?3]šv©_»IöýýyÉš8N&\{¹ž8O»U5k«™dk`b_ŠžO[Òdêöâ÷Ÿ¦3A•vmuSýò*©}í-°8°o °±Ófƒr¹q¬B@@@£R@6­`²ÆŽ×–0s%95UÇ–ÑÙštZí¤dŸdë@À9Ó'KËž}VHcº:uv´ÊÞ'ÿ!ûþ¶Ìê"%É)²å»÷IWs«t¶µé´Üù֠Ħ\SVûÖ´ÝÝڴƧ] ÒKGÉØË/–üÙÓC¤93§Ê=w•¶Ì ,=AùLËš / € € € p” ˜Ÿº4FII_j²†/ff'튤¡ŒõªÝ£’Òt*nÍNº:Í~~I-ÊÓY¢º´ÅL£âè†îN dê$­¤X ¦O•öºiX¿Qö<þ”´V6ã¦VǮѴ›SªäÌœb5Ý:½w—þ˜ñ„Srs¤ð¤Òøöf9¸m—$ëôߺ1„OX¢à  € € €í¾”dµiŸ´ê´Ú2mªtkþbMß­ÞÑÐhAÓÝÙ¥ƒgHÑÉ'XãÐh”£³B½.5¯¾!]:{Tú˜2þŠK$gîL!ªIv<ðˆT=¿JvüæZ–†?&xÑ)ÁÍ{¦N÷­‹-ÇZ‚hRr³t[–Ž1¬3K×Ù»ÖØ¼"€ € € €ÀÑ- -k|ÚÉ´ŒÙûô¿´Mª5p°è´Ý÷UJå?þ%ß‘®Ö6)>ùDÓæ£’^2Ì2É?V:t:îš+uì™q’7Ž5#Tr^®ÌŸ/uo®—&¤Ø0¦iN’߯]®Ú¤yÏ^kœ›$íödR™n«Å_š¶ï‘–ÊJíU¥ëµµs!¬qjð@@@ŽbÓ JggÒY˜št¶§xTj^yC’uá U·¼«³4µéìM~É™3]»:iP£]¥Ì’®ƒçÌ,“¯¼n0­û«%}x Èi®¨ÎæINKÓ0Hm™£ÍjtÜ›viXû¶T<þ´ ÿÐé’š¯ãÛtжÒyC*ÿù¼†FµV—,3½·s!¬qjð@@@Žb3æŒé¢¤Œ†6-»+tíªÀçömàb‚Jt› RÌ83f\³tutXSw‹ŽkÓ¼«B¶ýúÉŸ3Ój¥S½b•µÍ—œjL§'ý¿ …Lת½}FǦ١³M•HwK³Ô­Û(-;wºLY¥›°¦§/a…Â/ € € € pL˜Ñƒ­$EN 1:哈-æÖ¢»T¿°R[Î —‚E ¬Ýk^þÔ¬|ÅêúÔ­-fjW¾&ë7i—©vmU£Sp›rí²­BŒ¸Øt»ªyIMK±+6Ý£¬PÈ ´©¶Âq.„5N Þ#€ € € €ÀQ.`·` ¶fÑÀDG˜ÑžHúÙ´º1-\t`àæ{d÷Ÿ—JÝêu–GãæríþT˜5ʬÑ`¦½Z»1iëŸicuer¶1ïŸ}:6ŽU¶¶Þ1Ç%iw)k1糞ÀGûWÂ[‚W@@@8ì@Å„6ú^ü5kKpˆX6›‡Mƒ›$ׯ Y¬@G÷ÖÅ—®¡‹ò˜ìrMIaï­˜(Ø‚&"Ø1Çô,„5=¼C@@@£^À2fq*5½Öi b3…·µ·™±Iǰє&°³Òh&ûsèÕl†>Ö«ý^W[¡Ùî<·ó½Ù&BXpàW@@@8¦ìÐÆyÑaë4œ±Ò8÷ ¼Û7bçvç{³£ó³ó} Þ#ØDÌ @@@@C)@ËšC©Í¹@@@@à ,|â÷‡üœ=á+ç^"¾M8.Ôcj q € € € €@bè•GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“GJA@@@"@X“F A@@@#@X“Go—’äñéOøbÖEÛ¾/Ÿ@@@@àøÉY8Éá0ALWwàÇ­ݺMÿZLxcÖ± € € € €Àa ¬9lô‡àÄÔ$geJö´É’5e¢d#IYYë¶Öêjizw»Üò®þ”Ô‚[Â)@@@èK€°¦/¡#t{rF†Œ8÷Ã2êãçJÖÔÉ’œ™õJÚªª¥îõÕ²ûOOHõòûÑÊ&ª@@@Jß¿&G¿—¡> eÿÐûeÂuWIöô©¡³wwvºC9‡®Ñ;ïKÖa‹’Cu·wH•†5ïÜóÚâf[`œºE… yƒ € € €À¡ ¬9ʇ辿LüÂU2îšËż—®.éÖŸ cLKgkç{ dºõÇÞ¯ew…lúÚw¤êÙQÍ9  € € €Ø&ö'^h)·Ü ãµEÏŸ,VK d|~ m‚-g¬ÁƒíÐÆy¥f?»uMG‡¤)³ï»ÇêFåÜ÷ € € € 0ô„5Co|HÎ0éÆkeÌå‰éÊdB_rrà¼v7¦(!M¨ÅMp» wº5°IJM•²»n•‚“æ’ús@@@ÖOBÑé§ÈØ«.µZÓ„Æ 1![@ëzÍ1&è 6É9Ù2õΛ%µxX¬£Ø† € € €  ¬I æá(*)=]&]$¥¥Noº3Ù­iú[!î˜Å6Ú2Ç´°É.›"c/ýD`=¿"€ € € €À Ö 9ñ ¬”œõÉ™Yf &l;c‚šþ¶¨qVÑ>Ö~ÕŠG\pŽ5Žs7Þ#€ € € €ÀÐÖ ëЗª¡ŒWfäù±Â3ë“Õ¢&â º¦u¶Ò1³D¥•Ëp …X@@@@`èk†ÞxÈÎ5yB UžÁžv;a's†> {ï{tV©`7©„„‚@@@@ \€°&\äHø R²¦NnŽX­jìnK‰¬¿–é3åj ›ÌI$}äˆD–NY € € € à"@Xã‚âùUf\]2Æ–º>?x`áhlÊ C©Eb~X@@@@`hk†ÖwHKOÎÌ ”ÈnBÁJÂNj‚š`df‡J)$¬I˜-!€ € € E€°& Ì‘°Úg6ŠJ&Ø&áõv”ëÏÉïX—ðóQ  € € €Ǹ€ÿ¿þ#úò»MhbZ¾˜–5Ž.K ½(»\}m¯9(Ú¬cA‘@á)'IÖ”Ig«{s­Ô¿µ6b=+@@8ÒkŽà;ØÑШ½iYc(‰lõb5Z|W{»´¨;‚µ¨: VÀ„&ã>{™¤äIwG‡xéyçÞ¶Ø>Ÿrë ’=}jÄ~ÍÛvÊË‹ÏXÏ @@Žtš#ñ[Ô,ßfÕÞŒ'xcR›.æ<¦«•.­•Ò²¯2…SI¥—\(Sï¼YBÞhåsçÎ’ü—×>~Å^JW[»kùÝvHíº•• € €¹„5Gâ½ ~A9¸ùi«ª–ÔaEÖôÝ>b;ac×Ïa¾ ™é»7m‘öê`7¨#Ñì(®óä[®—qW}Zº;;ãºJ3Õ{wG§t65éóS#Ë·Ê•¯Éîëxv:6Æ\vQ¯ ÆVÈ;~ŽŒüØÙRñ—¿Ù«xE@@A 0Àð çáMÚ öÕÕ*˜pÅþID¥œÝ©ô}å²å‰(•2†@ 9-Í é|~¿Ä󓔚*É™VÈ—]6EJ–œ!eÿs›¼oÝK2óûß‚Rä‘.œ•)ÉYYî—¡>$gGÙæ~k@@@ š>€<»Ù„)Îìyl©Õ¢"Ôª&¸~Põ†>¦†)·I[^Tÿû¥AÉÁÞ0Έs?,§½±\JÎù÷+L ©€³ûÓ!=1'C@@à ¬9Roº Tt©ya¥Ô¬X%¢¡ŠÕ &¸>4àp¯Ïo>Á÷;ÿˆÎU›¸.Vý­ûR”ü<™¥-lÆ^yÉ!=/'ó®@çÁ&iޱ˽‚úçDýš îÛX‹ € € ¬Û JòÆ}ÐWŠXÿZmÞp1Í;÷üػƔe6&l1?ý]ì FëîÔV5Ú­¦úù—dÏŸŸ ”d¶³úüLºñs’5i±q½\eŸ›ïºÇús¦×Žúg®?ü™é³{¡ð@@Á 0Àðà ã+Á„'&ìèê–né¶× °ö>ñ•Ú«qÓfÙòÍïËÌ{ï²ÂS¦Õ-ÊÞ£¯àÆa‚ç·ŽOñKÓ»ÛdÓ×¾#]--vI¼IÚ­FöÃ@3Õ²éÚ–’—+cJ%¥¨ æÕ$¥§Ë´»o•7.þlÌýØxl˜Ö3+Nü NÝýIU"Í­²çOKÓÖÇW‰ € €À! ¬9T؈$gdHöŒ©’1vŒ¤i°’$­ûöKËž½Ò¸q³´×Ö¸6{ŸxZL÷•):3/5Egûé6f†(³˜@Æ-´±×ë«™5Í;w˺ëÿ_ô®RùÕÃÍ-²á¦;¤u¯û”ëÃ?ô~wõe:ýǫ̀W‘?žäÌ,“†õ£îÆcK`û/~wl]0W‹ € €Àa ¬9èi#Kdô'>*Eï;E²&Ž‹˜9Å´\iÞUa=³óK³Îò4eço²¦bžrÛ ’6b¸UDw»†6 …Æ¡qi½c…4Ö˜Æ,5/þG6Ýù-þÅÜÒ8r1÷=9#=êTþãY1?㮺T&õ‹Ö¸Gá;û´…Õˆ%$¬ ‡á3 € € 0„„5CˆkŠ.ùÈ™2ñËŸ—̱¥¡3ÙሽÂt7Éš<Áú¡³ðlûùodço¶ZÇØûÄûºïoÏHÃÛ›e®”âŧJrNvÏ¡ÚÆšáI›nΘV7úÞžå¥ew…ìyä Ù~ÿ]Ÿ\‚žÂxw´lÿåïeøYŒÚÂ&«lªu©Åg¼O O>Ñõ²«5à«ú׿]·E[i¦ƒžtãçõùÓ.‚aKÝêõ²÷ñ§Bk'|ñ³’Z˜úÜÝ%:VÊÃbé%JÉÙgJ愱ú¬›gÜ<ß~mÁV)oý×£¶2² Ξ6EFžû!1ל^:Jü:%uRZªõ{Ä„™]­­ÒQ× 6ÊÎß=,µ¯¾i׫©Ÿù½î\Âíl—‚…óuzõÂÐtì&xí¨oÆÍåRñ—'eÿ²çÅôz_°è+ Ι=Ãêö&AbógOG]½4nzG*´5^îYá)'IñÞÛë<æCke•lûɯ#ÖÇZaž¥‘çŸ#ÙS&‰?7GÿÒÿëŸG¦5˜¹Wôyzçï‹UÛ@@8ªk†ðöŽ»ær™ü•Ï÷ÌÔ¤çò™pÄîšdŸÛ„(¦;’.f‘)·Ý(YS'ɦ;6^Œ™j{ý ÿOòæÏ•ç|X¿`Ÿ`}ñLJK³¿³…^;­p§ê¹Rù÷YÝŸìjY]§Bxs4 T<ö¤äΙa…wá×™9~¬µªdÉ™Vø¾Ý|Î;«__üÍ1Ó¿ñßQ§Ïy}u(¬1á…'ÅL-î\Z÷î»KNá©‹¤ì®[$cÜç.¡÷ÉYâÏÊ’ÖКž7¦üéß¾C žÐç8>¦)ù’1~Œp™qÊï½Ïj¡ÔSbôw£/:_²§Â/{¯œe!»ã~ÿ1A‹ Úûد)X™s›°Ãt¡|÷{?ÑÀ—Ú›Å*Óî¼Y2c :¬ÈÚnº¦­ÛeÍgo”ƒúgF_Ëpma5ú‹ØÍ„HûŸYW}Ý'¿Ž§dZšçiÌUŸ–}ý‡våûZÄ9Y € €ÀÑ.@X3Dwx¬~ј|Ó¬±bÌ¿f[_¾‚ŒuJÓjÅ,f pÌgólý2ê‚s­÷oßrw`¿üZ§_xÍõårìhI=JR5 2-:›¤­¦FšÊ·K³¶¨ "lêà¬çÎË!GžÀ~mcºÏ™V^áKgp€é¿X†øý¡îrÎý²Ë¦X3GÅó¥ß>.Á<ûmïW}þöê—tçÒÕÖÖXá’î4ẫĴ¼‰p8Ë ?ë‡ßÒ–8g¸†Táûº}Μ8^fÿä»V+—5×Üè¶K¯u]mí½>›ú{ÓF‹–=&¦Ëd¼KZI±LÿÖíâ×Ös;~ýGkªõÉ_ýR¿2'Œ“–> k?³Tÿû¥˜§în‰¬»9 «½][1õ š­ÓÝnÒÍ×Å]¿¤ÔT«õMæ8m%¥ç`AàXÈœrªL¸à4É( þYìk—®ÚizñIÙ¸¼ØAƒš¸Bš¸©ŽÆM×ó̺-¦kнì}â)É1Ýx\Ž¢ÓßwXSrÖ™Qƒ…ÿy-®Ð }ôH HnZŽ]góÚq°ÙµÌÍwß+E§lÍ æÜ ïGž¶Ž5õP\Ýœå›$cF;Wõë½i•3ã»_ï×1á;›î—¿xMÜ÷/üøXŸÍµ™V[iù«Ü!Û–;MÊ®>_r'F=…‰'6×Kóæ·¤êÁ'¥¢!ê®l@ >’³dt(¨9 µ>*•[$£d¬ä,š%Ý+_°ÊÉ?ãÚž ¦º\ö?´TÔëL“Ú]5{Î(iZž¸ Æœ°+ب­«£9¾ëèc/«¼‚Î>˜ØŒ €€%@X“à¡è½ï‘ü„.&¨ 0ý:c}±ÑÀÆ L<ò¼³Ä Ë‚ÀP äΜ9–RðdÍÛw†N»û¡¿Èø«/wÛ%SÇ‹É7[êßZÚßí òO<Îm“ÕõÏ ÜÏbŽw±õvÙÙLQ_ûÚ›R6p²ÆLWÞ¨ ›™ÚÌç“æKÉG—ˆé~䶘.dc¯¾TÞ¾ypÁ‰)»½¶NêÞX#­û÷KÚ°aR ¦ô,Ü­aëÌ€á ë7IÛÉS*yÇÏqíæfV²äCÖ˜®ZnÝëìóš×¦­;´®o[8§!y:~R¯×YÞ@ßç-ü”L»Ôùl6KÛŽ=ÒÞ ßXµ¿IІìQ¨-mtÑÖsO“¼•OKÅš°/Ÿ%ÓdÂŒ©YþšÔ ´2C~\±Œ?£LšW®}„MC®Ý× J—Ì ÿáûApÄ–Þ+ÌŸïþè²íÇ÷÷Ú`îó‚?ÿFÌøBnKjñ°¸Â6·c£­3­jò8ÃÞ{š.vïþàg®³IÍüÞ]:@úY®SÊ÷.%AŸrO•I¡ ¦Y>³T¶.}UšBñ÷œ'ÊL~Æ’k¾HGþ¹)|UÄgÓR,Z—˜ª>±\+ô÷[å3ÏÉÚÏÝÔk÷ÚüÄZª—¿(ë®»%îÖ]›œ¼üIkZíðrÓµÕ™6Ü´é÷¢×PþýŸF„,v9¯òJYôìãÖŸöºðW3µø›—~Î5ð2AÛk^µî>²5#X_-£ÂÏëóØ+.‰ÞÅLŸ­·o¹K§!ÿ›këo¼CÖm’É·^õyq=p€+']}†žèv©ýþí²y‹)H»¦º,µ[Ö‹ù‰¶äX]¨Óm$Ú9»>F …F—·«9ØË¤˜ñ=LØbÿ ôBìG_ýú…ÚLgk-öú–ËqÇŒ@wg—ŽÕrÐõzÍ€¸ó~õòÞ×—Kάé®û˜•f@áð–.æË|ãmrﲘçÔ”m1áPÞqî­OL˜°Û1 u´2"Ökȱí¾_E5ûÅXQùÞTŒ]­±o¬|Õu`õ¹¨+ë׬ÔØÇ˜VC±3‹VøýrîoŒ«žû·sU¯÷Ѻ‹õÚ© OÞ™ºF jìS˜Y®jVØ]@ìµCñ:J2ƒ_–»6/ 5=ON¨¬–p(Ž+˜ìÎ×”äPœ”s N ~´ö ðÁ“£@@à° DþÓøa«Ê|b èÆÔÂIÎβ.Äj£ëÔ²&ŒÂ´B0_r­Å”É‚@f–ÓjF‚Lwg‡NÛžfMåzžb•£-Êÿ÷>×=¢4¬­ÉFžŽ5•µÛ%g}0j¨úµÔ"¥æ¥W¤ü{?q;Ý­33VE[â™Æ:âXý}]ù÷¾£Ú7׈ TÜZ&™îO»üKDÑá+*ŸYnÝ#·2üY?¿ÂÈg^§i×*·ÅÔuÇý¸mŠX·çÑ'¥ðÔ…®×±ó€WTJ{u»dŒH‘ÖÕ±Ç\êëÃæœlýà—ä" nôËuŠã º†èÄä•N’¼ñ£´¥›v‰ÑàÏÌBÕºáUÙ±a¿£··~ÉËÉбuêCݶòJgʰ…“Dªˆ˜òª+¥ñµ×¬ñiòJHöý‡]’òt e‹ävXëœåX+Â~ ”]&Éö…µ×HêR±+²KXïCûWÏÞÇöõÉ/ÃfÌ“¼£´^)éÞÛ¤}ë©ÝöŽÔÖwöU@`»™ìÔY’^ªÿ@¢ƒêš,«Kíš4 ®Øû> øþåÐ2ü~8ïEÞÜY’aýU òÙŠý\M“aó§IJfŠèÔú\µKëÖõ²oÕ;¡ç%>œ¾öÊ‘ÒÓO•ìÒiߥ]ëôÏ€®]›¤fÕ¦P—-þ…°/C¶#€ Ð#@XÓc1èw]­m¿ jIÖx3¦Ô`g+;£ Cƒ)–c}V2tÐß.{Ÿü»T=û‚ëáf áqŸ½Ìµ+P¾Žc ӊ#|þá¸A¢až§–…ïÞçgÓíçïü°Ïý¾ƒÏþ ™˜’»ÚÚ¢Z;Ï`Zßt6ç ]ööfýr4¨.Lú¼äêÀ¾‰ZrçÌ”$ Ý–ƒå[­œÝ¶…¯3cµéÏi£F„oJàg Úsß–“‹ÌxJýïb2öŠ›eÄý‚ZR$ÿ :ÅrèsàMóãß‘µÿìùÒŸY2SÆ^¤ãßLÐ1nìðÃyÌâÅRÒ´[j~þ#)ßâ†L¸þN)žš!ÍO|]Ö.%3ï¾D²Š"í3‡M—1§Ï d8IÚâ«äÄÅΚqSž’×~°¼÷Jó)w¦Ì¼þ“’¥S9‡/ù‹—Èèoʶoý1ª^¼õL«~Kö¹Œ~ÎÀg¿LøôUR´hR¯ërîkÝ•¦i\öˆlXVîÜäx_,S®¿B ¦[9¶˜·Eú3F»ÕÿþÙ¸ª'pðýó/ÙwŸ'yé½Î~?Zÿ³^’gO¦ó¯káÏ–Ž·õõ[¥|_¯¢D4”›ù…s]Ÿ9õ4)¾´^Ÿ0&ÑÇô +1êDZ]«ƒk8±˜óˆ´m~NÞúÁ +,ŠØ… € à*àü¯¿ë¬ŒC ØÚ¥ý@­t46éXý"gÖ™gàGQ½v o¾ÈµU×6ÏÕk?> `ªçVÈúoZª bLW ’œ±OJa¾ŒÐqivÿñÑ^ÛL€“;oV¯uö35}øþö¶X¯Y„bŒñë`—mÙӦȨŸ§õœiµ IÎÔÆ[âók'måfÆvñ 6ˆ ;wËž}qMùÝÕÒjÁa‡[Í`½^ZLKÃhKÓ»Û¢mŠXožµ–}•CÖ˜Ö%uîBÉ|´ÿ-|VÐbº9—Žvéjë¹/Iú8u6´ôºÆ‘W\.¹ŽLµ«^[ùìÕ!Íí’R:NÒ4tñeŽ–¢î–äëX:zÊëU~è’y2ûÞsƒ­/L‹ÝÒZ­-êL+§ÜiÝ,ÚþFºšR4H³6Ÿ{Ê4[GsOaï•9ç|™qÍ¢SwÓ~iÙd“µž©Z^Ò˜ãdâGKæí÷ÈŽzûÈÈ×¾êÙã{—æ—)·Þ-czÌM+˜Öj­Jº$é ½iED’Y(ÙsÇŠ¸…5SN—ãoX³ÈœAgÓ9íz;ýZFJ®Îf…%Ò¹«·Í€ï_·¶ŽÉÔ ¦½Y§ÇÎpܽoM„Ì9»õÁ²‚³®Wøiß7½—)>öPþ)—ÉÔ‹ÞjXÕ¼«RÏ¥åe=Wš¾IöyWÉñ3ž“?xj€­lr¤ìŽ›%×àµí(—ÎN¿5íwš>~}6R§.–ï'm½o Ÿ@@ †aM œþn2JkÅ^Éü›÷`Â=Öž Ê”Û¢å² 0Ô ²íg¿±fDêë\QÖ€²dÉáËHöÚL}ì¶ÔDÆm_çºþ|éwþ~Ú×o‘báih[o„Ÿ5±Ÿ7¿“ØYZÞ‚¹Q».µìoûd(v©Î­ûåÀ†ÉX ]‚ŠŽ“²ë÷Èn-Kœ‡„½ßþó{d»Y§³J-øö¹j4Kõß.å1B ³{õŠRtñ(i|n™ìyb•Ôöä&f³äŸò)™|ñqZž¶¦¸è„<õKuÖyçZLjNÿ\ýãÿ“ò ½ƒkãËæ×™yïbz×6ÿùeíò>Z•žåjê¥þÁûe㋽g{Ñui¥S†Ëˆ[/“º[{¦•V*ôW=C{G“§„Ôtm])Û¾ÿ˜T…ùiÜ"Ãæ,’’3gIË+" ~º,РÆëšW<*å­Œ0ÎÌ$Ãç¤Kå.ýG;ÁÕw¾«ä­/Ùã19îGXË+«Â¿µ~íýlÝýÙÊœñ GP£­þü[Ù¸¼w‹¢ÌÒ…2éú ¬`ϯAÊŒë´5•‹OðÔÑ^&_ïjön”߸_*:{ï]zîµ2êLmu3b’¤öÞÄ'@ˆ!@X§ß›4`©{cœ|bà_ÞM«š6Îc´Œú5t<w€!0­Âªž{A6Üôµ¸Ï` 4¼q³äÌžqL®® ŸÉÌ e;·Ž³ów„¯=$Ÿ§ÝùUmI£_¬3zwE8$'OðIìÙã\쀋ën1­L\ýó­iÇ— ÑWŠVC»~ý˜ͽJÒ´‘†ê9áÞ¥î‰Ge󋽿äF¯epKºã?«¦·PaMí‹÷Ë+/F/µöÅ?ʶ¢á2Q§—¢2Yâ‹ìîÒëð²ÿ–oÈÖ˜çÕV'ö1)}}}öKÙ‹ƒûG/{ÇC?’V¹AƪõÌ›%£çø¥.fW¦èeÙUëë5}D üín*—w¾û˜¸$Õ!UkVX?nåM½Æjš¥öÇ_¶\ 6§sÐT_.Û¬ûÔ{[bîŸã~šh9Îìx׳•#ã¯8!x†v_ÿ†ëóÒ´k•¬ýÊzms›¶ŠÑÖ9SÏI¥+¤|—ã|}½-9Kòµûµì}UÖÝõpDÈe¶íZúS©Ùò ™õ»^Cø@b „þ¾{7¶Æ+P±ôïÒ¡­¬Å]Ìk¼KØ1f0ѾfL‰·è>÷3áRøâ¶.|>{VÀ„0á?­{u°L3ÄtcÚñËäÕ}F^˜¿¸_A}ÁfF+´W_ÍàÆ%g÷t‘2] BÓÚ‡í{°|ÛàÆZ +/Þ³ï»WJ?ó‰£"¨‰÷šÔýêÞX{ª¾IVù—rpo dòeKþÅ×ʉ?¼Cf~ú, I’ã«Cï^NñÓÇ^UK—ÅÝ}¤õ¹ßôÔNfškÑV5¹#{¶>÷p̲÷=ôˆhï-kÉ^rVÌâã­g¬BR‚ã ù´kW?þ+ÛSdé9’¼¶¶ç~³‹YÏAýÏý‹ë~ÄñleÎ9G²µÅ”Yšÿ…kPØj~mßXªÃX›%C .8Õzï/ãΛ ñê¥ò›¸5vYM‘êÁ‡Ã^É+ €Äpü`Ìýا€ù¼ç¡Çeìg?-æ_ƒ­íîOàaöÕÀÆšñEÇÈ0c‡T¿`µ[³Ø-xN·/Ý®ëp 9ôf¬ÿ,ù¤˜pf¨–¨ ë3U|Æé²õG¿´Nmº@™±Ü–ª>ï¶zHו^r¡ ÿÐé}ž£­JÇyؾÓê†Ø¥XtëOGm£ä?[òÌëóxvHŒ@Ú÷A_Sº£”ŽM²þ®[eä—ÉÈófÆ0IÉ•¬E‹­Ÿ1:urí3OÉæåƒÕqÖ8Þê86:+YÓ&G”kºtí{òRþƒŸ2nN„Ž÷Wtwõó[Ë.içï–’³>h=·ÎÃÍs<òcgËÁ-åQ»@Õ½¹VÌŒN‡r):ýIÕß—®‹þÜxç·#GvÝ—•1¼úºŒ¾ä×A†ÓGë7Â~,fŠöC¿tHÅ‹O[?f@Þ±gœ#…ggÍzdºH\óU™ýÌOeíÒ¾Z›ÄYs±Œ=u‘ÎX4ZRC­ÐüÚMÏüg:ÕšQ§¯’ºê]îë ˜Û‹%³40&IÒ„E2ó ­¬ú¸ÔÝÑ,ÉS-'|™9’«U0Ù™°zv®—·¾ù”[`&'ÿÔdÜN‘,Õ=³B§ÿŽ5«Wϵ™™½$àþ º¢×T¸_ý1Pˆ2|š¤»[u™Ù·âZø+g\Lì„ €@P€ÿrѣаvƒ¬¹æF)ûŸÿú¢jþ¥Ñ mL0ã _ô½ÝšÆL ܲ»B6}í;Rõì ½[ä A]Çþ×%2ù–/Y_¨Bƒx:êçÏÉ–ÑŸº@²¦L”µ×Ý"&ˆbAÀ)k á¼ãf˰ÓOuï¥ÏýÞ'žruHÞ;í=®‚9yõ +ã jr¦O9$u=’OÒÙ¬­ œÎ9.&Û%vlŽx›^lÙ±åP­hËþhýŒ<ãJs^ È8óJ)Ûòß²qà BÑÜ™Rvõ¹’;!Ø¥d0—ÔÓØa0¥D96C²ÌвÍmõpI)Òõn­4YÏ]ËåÏ­“IW|R Œ³Zm¤N˜%Å×èÎÈÕüòs²óåQÔ»uõ;nߺDÞ¿øÎs/{ì›öw·Çܯ÷FÇ_ãÍ­ZtÊq-ÄÌzÖ²&ΰ¦~47-±f!ë}~>!€ €€›€ã¿Ðn›Y7` <êWë¿ú]ñEõeV7‘”¢÷ât_Ò˜.!ûÿùœl»ï7rPǾ±–(_vÜ Šsm0Œ)ÔÅ|ÓuÖ¹CA’ÖÃú‚ÜÇ„H¢­}òO<^¦ÞñYÿåÛ¥»­Ÿƒš²Ìâ¼–`ù üz¤ ˜†sfMèZ”>r„ÕºÆêv‘&ø;dƒg‡Ûõ£>Ÿuo®qݾ2¥0Êïåðáϵ¯¼!%%?/B!cì+ÄnX¿1b[ø Óò/jK¨ðÁçŠe÷KÝÞOɬkŽÓ³¥Hî’ÓD6,Ø™§œ¯ÓF/rt i–¶ÍÛå viÖ±¦Ú›µ#Q·~®,ñß¾BÒ6ŒîÀêvT×Nm­²¥A[Ð…mˆö±½2Îî4Ñ èÏúýRþë‰üÚ´€:K N%iE¦…I†dœ¼D¦ž¼XÿüS½MýÓ¦ÏxùþÅ}£Â®Ò°Õ‡­sûØÔa…5Ö¦xq+‡u € U€°&*Í 7ƒ ó…tóÿÜ+»zLŠ?ð^)ìk)½Ø½+U_Ç åö¦5”ê­eR4!CºKÊ$_b·Üp¯Ë4™ jš¥þÏÈÆ¨ëÀÖMÝ¡n'îå ÍZï­¥«fly4¾±I†¦&ñ”jZ@=býHÉ4)»è|ÉjZ,eH¶Ži3S¾'ë}mis5h^¾"ž8öñâýÓ)¸ƒaZÚŒI"ËN9®^t:ùÀ_ ýÚ‚JÿþÏ@A¹e’ì:å,Š÷ € à.@Xãî’ðµf–¨íæç¿Ó|ó$%/WÏ᳦ù6S+[-XsVzØ‹„ØŸ¯Áp$»l²äŸ4ß'Ç´ê €l—c¿š²Ì{m]#)ÉV—+¬‰uû|Á}2'Œ“œÙÓ%{òDñëu·j€Õøö©×®bm•‡j€N»R¼…€ùb^½âeùѳ{ïÓÍÜf2-¹v?ø—^ûª]ÑZ†és>üÌÅ1»A¥.“n¸6ð{âPUø>Oõ¿_Ößû3\¯ øƒï•I7~NÊ¿÷×ífå”[o´>ºÃaÜÐl`h¦È’wÊb‰&°|ðÙøb¬®$Ú¥è°|ÉÝ/Mûš%CC©¤Ð8:¹ÚÃp̾M²ñß”ÌéçHÙu§YÁBÖyçJþòŸ»D9®-Å}¦ºXµöæýÛ#­Õí’1BC›Œø¯)o¾cÐßx»Aí+פÍ3œ")£5ÛÀËc=/lC@` „5QÈ1&ð0á…þ˜YžÌOÄbï±!Žná‰[yÁý²Ë¦Z­k¬éÅ]ŸÂOeÊ0‹yÑcÍXIééÒÕâ˜ÝÂÚ!ò—Ü93¤ô3Ÿ´·®f`ÙýË–ËÎß>(­ûø‹^¤à‘µfë~.#ÎþPÄ@ÃnWaºù™±nÇR³êsÅE®ãÖ¾ç$ ®Õá§U›vçWuü¦óÅP,ñ ìøÝC2沋$9;Ð’°×QúgËøÏÿ—5Öšk¿ÒkSÎÌ2ïë6ÉÛŸ1Rz1äôOóÀ’’la`¯ˆóUãµ–örÙ3¨Ñ½J3ÅYrÿvëŽ6Ù¡TÒ„¤T[íê_é‡}醴Ÿ”ò‡Ë´‹ÍlG:0´þ;Im°›}m2á8ëZvD¾Êå2¼rÿªְå€äk¨,c⿦a ÇJiß#ÕA›°b]>ÖHg“®Ö1m¦þ>ýgß]3µµÝšÉ¥@V!€ €@˜ß:Â@†ì£[˜~²xö ?F?,\ …'Ÿ(éÚ½ªý@½Ô¾þ–Ôè@©.{V™Yª¬ðÈ„1öO´½M½‚û$gê¿°ê—U3° ëböÓýÍ,@Sï¸)Ԫ­åP愱2îêÏȰ÷½GÞþïoHÝë«]‹då‘!Ь]^t¬Ü¹3cWXŸÊ¿ÿ+ö>C¸µ½º&ÐRÌtñ _ôùÿ…«dä…çY³X™gÙ ÙS&¹áÇó¹—@{õ©Zþ¢”|äÌ^ëCÔ»X[3¾q•^^mŸåÓ.™©ÃŠ<Þzišäikk©Þ.U¡ r¾é£{H0ôë#+± œ|Ñ Î‚òÞþÒœ<ÌtŠ–o}jë,K"ÃeØEÓ´K濾œÿPR·J§ŒÖ°Æ\s(dÓ÷[ŸzK¯m‘¾+a—Δ¿^µóý‹VÑŠeÌ©f@î)Ôûµã}ܯá§KÁ˜@ß©ŽÍoƈ¹÷9÷Kó.mu5UÿN ÏGz¤¤,‘Ôâajr¤ìŽË$-86HÛ–°/Ãõ›¤Õj¬’"éó'GÅñiðm_æ().º›¶L¼R õ qb—@ Sfê”>Z/myLê«gO=õ)[÷šÄÖyॕ^jâÜ"­Î–#[–†®Í¿àr™yú¨¸Orxï_ŒjîzRì ´–J]tY÷k’̾yI°µK³øÃªGnzçÑWƒ+‡Ëˆ›Î‰ÜÁ±&o᧤( 9Vó@bÖÄÀñò&3pëì~K†x±†˜@Äþ1!ˆ'bÎÏþ70CóBLð¢Kã¦réjk³‚“P ·À&Ô iÚº=f¨ìiSeÚí_ \¬õ0‰Šéfe–à¹ô£ nô_ÑM`c¾ M»ó&ýRœmoæõ0P·ikŠXKýêuÒª3ÝÎeãß’N ºì_ö¼Týëß=ü˜:ÎŒgôúǯ¸·þÙ¶ó7 ~\¯ê™3ΗãïýªÌüôYR:e”öìp ¯s¤ôŒOÈñ?¼SruLk©{S¶F´4ÑYœ‚@ê¢ dR” ¦vÕ[ÁŠRpÓÍ2irX’;I¦^›L¼00MxŒêhSk}°ÖˆE}„²ñçÏ…êš{é2ïê³tPe—%·XÆ~ð|™{÷ÍR6'ìz\vܪb™yï½²àŽkeÒ)ÚÒÉ¥pæðijøUµÀ´4Ñeo¹TÞu^›H–B<ïóî×–?e¡LÕçC£Dk9Ü÷/X ×—-÷,ÕñdÌ¢³•éýšýé…ÏtþœsdÞ¯ øÛ¶òaÙê ²\K[©ÁPõÖÀs”4á49ñî+¥´$lí$8þÓ×É´K£ÿãQø|F@€€Ë_o °€3Œp >\päc.½H ´ë“ :|Ɇ8Æ1qŒ‹&¥ _&}ùsòÖå×õ¬WãFà÷­uÖ”ÜV¦îv0c¿ÚG™c’Û«ÿýR`mø>f­î3þ WHrNv`†)ÓÍÄv0ûÛ‹ýÞlÓ÷V`£ÁNî¼Ù2êÂsô‹Ùƒöž¼aÖ@Ã/¼¤Ýà>â^sýâ=éº}ö3ã^j¿×š°híu·ÈÌïÝmý>éO•ÿxVÖ~î&kV¨aï?-2„Lré^ÕŸô±ï`-’3Òû8Cÿ7ûú¸fÓEîK®–¹÷ÿ0ÐÅ)ÎS˜?›6Ýùm1SÃüØk€ò8í×n©¥cÅŸY,þE‹%K¬öMÍÒÑ®-LKBmãÏkÝR½Q¶ßþG©‹8S‡ì}®\r/œ¤[ ¤è¶{%¯N¿k«DÉ-Ô¾7OÈ÷i †ú²{å"³Hƒ„”áRtãRÐ\/]zÍÒ©×ÚsŸšÿñKiœx‰vÙ «CĹã_±ë¡2bî¹V« R,8CÏÝ¡uô犿c›l¾ýþž.1»ž– ß)»a±5>OêÜÅ2õ'‹¥£ºRë«ÕMI—æ')³ç¯¾ Ö¬‰5hrüuußSÑÕñR’2'IÑÅæG÷2÷LgXìÔ·)…Ööбí»eç]…>†Þ„_ÛÌàµéŸ¦»oR†z8îE÷²§S’'øþÅõ/g=D¨ú®o:WÉÆŸ–×Zehh8kÑéØÛ z¿’ÍLNŽ«[5¨Yý‡~tsœ´ü»÷Hò­7Kþ}6‹ÊdÔ÷Ê}.Z«¬A©StüûÚº6¿ ÏÔ ÚRÌQo@@ ª€ýßШ;°¡&|°úqXw5ü?ë–4Á¥Wë­ƒ5å¶~1.<ù$×ÙXÌÁ;ü×j-ÃS&ž„ÊÒ/ÈÝZF·ÎeÊ3AMÕò`Xcöµ—à鴸贓õ2ëœ?ö¾ÎW³Ý>§Y¯ïGœóa«UŽs7ÞLj6»‘¹o‡cÙúÃ_Hw”—LH²÷‰§û]­îóõ+réhjŠ\çó<¿0±rý|Ôú†ŠÒgÓ´*{û«_·‚³~ßSˤ£¾÷—Q.tw¹×Õm[çÁø[ùX_æMaaK— âX¬Á¼»¿ǘ.jý]ºZÛ¤#Žú›îg+Nü ìûÛ3}¶²1ÏOí^——Þ{¶ì~ø/bBÀƒïn‹¨ZWkk嬨]½B7ï—®¦@{« Ðèìuþ¢Â^AMWýn©}ð§òІû¢œ¬VgÚ©½Xåè—V¿†þ`÷'³­â÷Èö'ÞÔP(°§ h  êuÀןé3÷ä¦P[Ÿ®&—ûlß{ôÄ»hذåÇ/ôœÛºV­£ &r#C¡¦-OËŸûŽXßÓ*ί_úSõºÒŠr{‚šöfi^¿R*žÛY“Ô3²”àš=rà™7¥­Î1ؽ¹gz¿ÒôÇ9֢͜®\*›¾ôý°V5Áíúâzmz]æÚB÷B÷ëª.—æêžãvÿD»okÌßÇ-ÁNíÒlÕS›ÞïšÖ<&¯ÝòK©×ç:°¨¹WcAy¾~üu j^ë}°óSŸ÷¬A6ëvÙùĺžgIŸ‹Œ©“ÄÌžgý%Ó܃g–×~ð¤4ìŠ>–žó´¼G@ý:ý¯ ǹÿ­ØvР{%¥¥iw£2É?6’軃úÅ®aÝÆž.CŽýcÜ÷Ö,{þÃ÷[S€[a‡ÝÍÈ>Ô æ'–l¼í¬/<¡–3ö~új¦Î5ãݘÅ|Ñ´f}²ëjŠÐnJæ¸f{¶&hÙÝ»¹u`pÿQ?O¦û+ø±¦7Ͷ¾G}Í—²WÏ¿,0¸k_DZݓc¯¼D§\¾ÁõÞïùÓòö-wy²Þ¿tµ5nRRZªÕ2ÌT²][D4n.·Z{™g“%q#ÏÿˆŽ>ßjiÓÑÐ(~m‘×Qß(õkÖËŽ_=¸õ³¤LŽäL%i¹ÚªB»;é—U_JŠt7WJãúr©jèG "ÅRzÊ$Ñí?_;jöHÃæ=RçRİ3%»D[Þ˜/Ç Ò¸u“¶àpÙ±Ÿ×Ó÷î~)™3O2‹2´ŽÔúmïèŒIÑCG‘=fÚÿgïK£ªÎ¶ŸìëLö•„$D»¢+ø ­ˆúl[ Ö*¶RZ[«ˆ÷¥jÅÖ Ÿ ê§ü•Š~Z@°&U”=@H!û¾ïùß÷Ü{gîÌܙ̄€@ÏÉ=÷ìç¹çÞ™óÜwAp|ºK«àiB_]=ZJ‹¨7ðhU‚®Y\ÆpÐ89À€3iTœÊR[BuàöMH?d㺣²Y´ÙSWM×­Ððºií}{×O«#á“5A„/Fv7ßqä Ê=ÆÆUJã`R×q?)cu—P?a4p}YB" H$+’¬±báqÌ—ì«$Ì»‰?üo„¤§Ù¸,fâƒßÄ—“AÙû‚½¢ U0eebÒ[kàGoB]’5Ü!‘%‡ûN¾Câß*©b?ޤÿ)?»…¼I á›l¶kSKÞ\ }Ƙ¨Ñ•Ĺâe)‹j–.ßeT%—X‡ULª·lw:^—íÈÌoK6½^£ö¾ š¶Ï’牀D@" H$‰€D@" CÀª`®K”Q¨„Òõûváxka&ÔÀ†uCH ø‚åKOnk™0iܽOË>¥cGE%°l†ŸÙD\ ©<©D‡M£1CÇÎJUl]7>}ÙÒ7ÿ5[w"–\èšé"{Âauˆ¶cÇQ»ó Ô}NöÜãÀ,LÃý{Ô9°º•M;VH=iM–ý–`»CìæÚ(´ä‘D02M" H$‰€D@" Ø! É;@<%R!$ãŒ{õiOR é²’=9Áä}ØnHèè LøÓsØ»øn4~½÷”%FºëÐðå×¢$ŠÜŸh,,¡ÂªH­GŽ¡þËo´ã#•gõ¦ã¯¿-òÙ&(æ'¡·E7ç¡hã±ÇŨA.«–ãq[ì€è¦dTM¦}Œøåí6f–Ò5®Ü´Ùr*#‰€D@" H$‰€D@"à¡Þí<[æØ#àŒ¬'~§5¤Ö! ùj6c˜pÐ>\‘Ò…§#2–éÑ=(Ž"þqοú˜´áþ-Æ™ô “,šÍ˜â?þe@cžb<\P% ëè Ž“ì˜9W¡ä/o ©Ã:F‰\W *©Á$PÝçÿÆ7?ZŒa7ÍCäå—Â'ˆÜ­’ÛàÆ=ûÉûÓ_•+­Þéþ¼èÇ/< ½íVwáúòg(^üò눚v¹WÔ±HMËm*9Ã$ðEÒG d[§|ÃGZ y< ˆýî•Â.žäÞèèú¿ºVJÕ¸‡–,%H$‰€D@" HR Ê… #AdPØÛßý$!c! Ü©¯6ÔÛ˜ NKQj @öx€í€d>¼£I?ºþ1ÑŽ=ªíw×7(DÍí:6 ¤ø„† tT‡%8+2`:«1åÿîqôiÄêXLİD‘/Æ’?4FV ÓˆšÎ² <ò”ÜÔˆð·[ ì/yª·|†ã¯½åIYV" H$‰€D@" üÇ# %kÜY*⨠á ö  &)¼ÙÖ Ön"ÞÂ(NÖã õ&Kþ€¤[n$2d5êswY’ùa=Sƨ?w#žxÃuH^t‚’†‘tN;j>û—°yÃ*,š_åÇ›Ã$“lŒR ]`ÂKg&(š÷çá0Í©9/_WBFÏuØü>ò€&ƒD@" H$‰€D@" x†€$kÜÁ‹É }]]Š„“7*#Ž´áNQv=úÑߢFxzÒ*Ñ8B.H†\¿¾ñ6´Ÿ8©åœÒ1þú«Irçw–9±„Mâüëà¿\!È·;P±ªúû?…Ûða7߀¸9ÿå ÄÁDXt÷W#;U%µMa˜ˆ/?va݃‚GŸ!’¥ÐyLÄPhÈýZx{Џì…|ÐH µ­®šZTÁ2T]+צ„ÿžŸ`Ñ,{h:ôÛÇl ÿúšC‘òÓ#föLøE„‘Áån4™S¼æÐZpt¨†ãÐNäw.AòOnBø¥ {)(¡·¹ _ïEé[ÿ öB$‚žä2ª$Ó$‰€D@" H$‰€D@"p"àõ´I CpJÄ]û]d>|üÂÃÄ0™´¢)Ú  U&i80‘PðØs({ï¯Zî€Ç ÔdŒyr¥0ð«/ÜY^‰C¿yµÛÿ¥O’8Nž¦&4ìÚM’@Í–v™ÄûücÂè°%Q°zØÞ;ï.¸íóNåÜ‹<]pÏ$ýx>ØCÍ;–0¼Ìç*Á%$œøœ®CÙûâÈêçÑÛÚÆUdH$‰€D@" H$‰À9…€$kNár™ÆŽFÊm?&ÛÙð‹ wh©‡¥=¾dÉ“µh$©O$,íb"ÅÛÏ\]— ü¯££´ÌÓ¦_^%§RîXˆ îû¥ C˜”òR÷÷ô‚I•úœ¯°û'¿25$ßÐP!½3kºP³b’FôÉãÑKÌpœÙÙêfìÍŠÒêvæàÀ¯ƒî:émJHþ•H$‰€D@" H$sIÖ Á•2‘÷¦°‹& $=>¡!$IÓ†Ö¢4íÝOŸƒCÐ÷Û»÷žüþëàyZ’FštK_w7¾¾éva;æTGËör²ž|ñ×Ïä iTrÈbÃGß Kר“6ÔFÍ?>Ãþ_®@_G‡¾´ŒK$‰€D@" H$‰€Dà¬F@Ú¬‚ËÓœ—þ y Bx†".¢¿O1ü 6¶{†ƒt¤¢îÅ„ˆöá10I¢%¬†ÄF—Eà2œ>È8ÿZ[¢FßÇíƒ>ŸH.ÁêPÑÿu’où!uþûò\" H$‰€D@" H$g-Òu÷P\& ì]Fó¹}š§}áÁ¤'$Í·@ÔðY•HØÑÈíÈ™QBcëiRmÜœQã…ÔŸß*È‹D ÷¡}¸O£ åsßLذÑgŠÿé͈5ª!Ó$‰€D@" H$‰€D@"pV" Éš¡¸,LØ)|nŸæa_á—\ˆ¿ZŒ‘÷ÿQÓ.ó°öÐïmoGíçÿ$ωU¡´À6k˜i#µ¯ÆÝûµäAc®šŽÀ¤DE2G#‚ýò؆‰™=C5š—§%jŒfª5¢ "–¢gN%u2©ñg•L“H$‰€D@" H$³IÖœÎk¤¦¥]õÇå)°w)–¨a"„½ wÕ”—xÃõH¼ñû®Z8myUØ×}¨úd ú:;IÍHñºÔðÕnXr?jþùÙ)÷Í*P¡#Ó­í¨xX<ˆQ]áÞ›ŽÁ#Rœ’ìAeYT" H$‰€D@" H$ßRÜàtaÏD«ïЇ åÙg¨ê>QßÉdÛ«Q “ê‘êº:zÆwPúÆ»ö5ÏÈyëÑcØOÄLȈT᪼·­]¸g5©S *l Ø;(PاD ãÁyƒ Ætô5…Â?6­…EV;ƒiSÖ‘H$‰€D@" H$‰À@@’5§ d"â:j¤âI‰ú鬬FKÞat74¹ìÕ;(€ìÝ]•¼Ñ«yùú}{„ƒJ€Ò£P7Ñ%yÕæ©«Ä*L‚°Ñ¥yµkSæ˜û1"Ð~²Qi~ð)º…•J?ÑÙ×"nPùêßPsúºv£åDdÜq5ú÷~‚#¹òùæ`瑸©H¼e&:?-CÍF×kÂhmŸÿÉJ$‰Àé@@’5Cˆª¯9cŸû½ jØÞ ö"·ÖÂîŒÚK×07gIÖt!où#$=c»YîmiÁ_=€”Û""{2¼‰iÚŸ‡’WסýÄÉ!ñ©7ņ†¾ b¿{%‚R‡>ÞèhÙÙÌ&ëZ$.˜„¾ƒ´1z9×&Ë­^ßÒ­²Ð) ²| âÓ5i¶+ÐCdM‰«öF?ŒÌ;¯™¡Ý\@dÍG®j8ͳíÛ¶Øð;ïEwá.”¬º%ƒ$ƒl[t}1í")Œ¬H¸ ¨ÛâºüÐäf ’ˆ²º9CÓœlE Ð'ž!¶ß}¦ì‹’„°8"kTçtÂlöE[“íD¾‰0 ¾‘´)—dÍé¼çNÛd›¿ïúÜ0Ñg´¶Ï‰Ê‘J$‰ÀÙ„€ô5„W#ù'7!ì¢ B½‰m¤Ivís«ø0AíÕˆ™5])a'AÒ]×€£?Ý?¹ßüx1ÝÿÚŠŽÛµöížú†™0æ¹GÉ¥øCˆšq9©| Gðð$DL¹#s7&þåE50wF[óOÚñ5(MBÆÊZ•äÑÚ¨ÿ½¿{GLÁöÑSðÏb÷ª·ÑX­7&$Ü÷ðàÛv³fùÒyØ»êiä-¼ù‡Ü¬tŠÅb²Õ»ÌK›ë)6(«;E èù•È¿% ° â´2‚³Ò•ÒFÄPÓNäÝ·žßáA‹²è™FàÛzþžéyÊþ$‰€Dà?©5D×>˜T€‚R’©=Qcß¾FÊŠ“¦1£VrZY‡Œo1‰'—yÂX$þà:áR\HÂ$‘¼|iY1iCbîá—\(ì¿Q!­˜S{ñ ²ËóÒ–Þ¡H(iD×㸫 •¡#Û b;:'×o»?¿B5ZKÛI] aY>(Ï##Ô–ŽÐ´` öZÚ(ž<Š¿¡Ü’OŸ1 £cE!Œ„¢ÇOEôäáJöz4núå¾`KƒØcáãGÍÐ:è.=‰Æ]dc§ÙŽØ uƒ´ë§ ÀL†U(ôÕ¢êã\£Å¾ˆŸ‰Þ¼ƒd§'©³¦ 0ŽIÀtÙƒ¢\MÌ —O…)-V¨öÕæ£rã.4ŠÖþø"iÆÕ„©ÖPýo¤þŠÚ¤ù"|äd„%ñLˆ"[T¡¼îi<ÍûòúcL# Sïnn¤_ï@q^5Ÿ .˜Ç!…¸áƒFj@‹ÀD&eŒBôâ7ÚŒ¾ê}8ôèz£"ÐÊô”þ ùO}dS†/§êÖ>ƒºµ_à’=/Ádö‚Ïèq‚<äµ¹è$LŒAǦûQHcLyòĤ›HÂÇ}…›±g¹¾ï)È|aÂ(_ èØ½û\§žëDREQ?Þà|‡¾ ¥Œ×UK0nÑe4+ç=ÕÅ(rÊ];É«V#nR*øêõu6££´Ÿý %ÿ/ÉOþ ‘1ÊsÇâÍÈ\5Þ´Vûýùk¶+xô×Dë8 ¡Ið ¢{ÚѶsŽ—Úß`úõNkúÒl„ŽFÄ7ÕénFÛ¶O¨ŽÝ½4À8Â’ÒAëÔ?Žp¦~{+ÈöÆ6Ç{#lä˜ú£ôh3¢ÇÏ@ô’Âëæ…Kë6×xݛәڎÒ}e¦{?iÖÅ$™";º v¡ðóüF§d›S`J¥îö54:l½hî4‡Î…¨Ï=hû¼ ›^Iô¬JŽ„Ý“}íehüëNTê“a±éDh2©í‡€‹Æ ¡–‹tÖ]DÏ ñŒò¥g=é¼Íþ™EåxŽÃfL¤ç›r º ö üsCÉdˆ=ir$êØˆ²o,†_~1“MBj§·‰Èò­Û¨?jЃ 0˜@ÏzÖô‘äOGA‰Ur®§uy¶ØYÇJtó³g3={ì;=µõæI(:Hsæçî,„&)k±ó`ŠöYŸ‡¯UÏŸ¿î§©cK‹ ¼ã&úîÚú9*Û(îÁuâ¢ý´®—‰ô©Êw¡wP Áý@Dwöptów›3’’¾'“&›ÐœëøÝâádq‰€D@" 8Çàß§2 >ÁÁðPwH®H•ìÐTtê @f ÁP‡¤‰èé߉Š3!#lòè笑&ÜÅ#/Ï&û5DÖ¸9·’¿¼%HV§²i_«¯ïKíCLJÅ–=h1QÓ´g?Š^þ³È:ßþÔç– 6c‚§NÈ;…%ŒœˆòMÞS@6ƒ*MÈH†ˆñ¾(×IЄM™$6¦]y{,Õ”H:²~wBãõ[rÀ÷ 8ß ‡ÏZ‚Œë‡© Æ"ꮟYïüëØ»EÝxøfbÌC e3(˜iÌÑ»QüÈÛ<, ¸ˆô–¡/)·˜ˆZ´Øò¢Cé1óÆ)iME¤M’F6^¬Eb摽#aûf z6¬Çc2–Ï': M‘d²ý¶¶ä ±´‹Œ¦Ó³T”4Ù“„ D?­hFØ£ßG„JxpÍþ$¦s”¹ì%Œ½ó2šm0eÇ•óç¢øŽy(Ô 7DÓ$"üA@IDATø“æ¦Q#h ²ÉÚ¾vR¯ <›‰ˆ+¦#áõøæÑí69˜v²ÿ°!D4郉n©˜¹Tg©?B’­y~é—øú³û=&k’nZ‚Ä©Šú¡¾?óÔiˆ<¸{^ÞiMÖÖ{Q!’âÒáOü«>˜§P½TçU]}}œÈÙqO,B]\$êû{ƒ¼¼ýšÊVFhS"̺ÅCåÍ“iÝåàÈSlɸ;îDLZ‚sš>Åîžž< ‘×— ò‘qÜÙfToÜ·Q;@Àʶ¶¶â²1n­K‡9LCÌÔ­ør5KOÒónÅr„'‹Õ«¶¨Âg^è÷_ÄA~¦Ä]̇fZòCoXd•R;ñ1*Wo㇞xÞ ˆÎŸ¢sKðEú¯îAT†jWMKŸ|1ͱ5Ï?"Ýs&|êH¼ ±_Tˆ€1éŠj•V‡Ž³g!jý³ÈûÜJTè²í¢é³ê6‡g ¬SåVZ 6§ÿb9¢Æ°ôŸ5ð³'²`+?ÿ±•;…õæi[·Q6ÓöZŽì!²†®¡›kÕíç/<üN£u6ÖY€Í:»˜¾C®Gì^òÒéa0ýz9.¡6Áþ~ˆ›ä[¦'¶¢F¬c›Òâ$å§·‘ám3Z±¹öD›cy™"H$ç’¬Êk©')\µ«’ ‚¼ÐWåϦ;[ΦÕÛÒJ†—ÆØ?¬FؤñŠôfcÍ– õ­õEý½Ê«S&jZÉEzÞ²‡…™¥Îyiܵ=·ÌÔ±ƒõíxB¶²Qjû: µ'B!x*©Bí³ŽjRÝhµH£001óô¡©}PñúÔUaS¯Å°&!ä»?CVñJäísõC1Y«ïC(Kí4– ö $õSÿø„_4ÁÁ%b"úº%DÔðƵ­ŸnDéÇ{иënD ½É½ánŒi_‰ƒº¦üVÓ›ˆo4¡éý÷p|Ûaø%]Œ$ÚЇDEÊÃc©µ˜³‘TßrÐÖ‹„;~$6+Qwüå¾k³9¹ð9 iµw+JßÙIyˆ›ÿ#ÄL†¨_߇î_þÇð5 ¹o¡¤t,î"²‡Ôɪ_Ú@r¾ô&$Y£m¼Ò1îñŸ‰MIm~«^5$åŸ5É?¾Añ“0bµºW¬³ÙøŠI8ûÓT€ÚÂD"Bàw) xц°h#IyZ?û=³ˆ¬ÑµuhÃ×H\Ϊq&Ä,]€#‹m¥SB—Ͳ”oÝl›§kÆ&ê«tG\QHJPìÜ d.]5­»º }&ôìVI¿E/a5Zè.ÍGKi'Iú#ÈL-¤!uÝ&ôŒ¸ÆjLYcƒÈ„ž8J^³‰ˆMý¯­yDj$”Îê`ˆ¸u5Æü{ŠNi!.[w³e®è¬Aka-¼ccBõ×(ßã„®D¦'*öšjÐL0Þ4´¾B‘¨M`€cG-/¦&´î܉ºÐXQଫ‘´xüÇ\‡QY_à°NJN¬÷4"jh¦í9$Ý&Ö4Ý“d`g¥¿ß觯¥´C¤ƒ¯ˆ©|R±Š©ÇÍ¿V!jж¦¯ U—6iCÞNì_±RWØXr¡n÷–Þ±7kJ@3®ß¯œ{¥aØoIZI^ФTjP¾ô ‡7ùX³Í*átÅ,Ð6É&¤ÌÊTÏ©þòí6yèìrPˆ¼÷O‚8â‚ý' ìˆ#­z Ê–]ˆ—^ƒÏ'^ÜÅë(#.ÖÆÞ‰úW~ÓnÂ7 ⋉SPü™vï&"eý­!ë‘wT–°)³T¢¦)y#¨kâË«fáŸO~… @ÌòÕ–)ëXˆšÎÝðÏѳ¨ÎMøâÒ+ðÏ9¿Àž…w¡déíȽên´©xµ~ô¾¼f¥Í׋%š,;‰Ôl~_þü|g‘ÕâÞ¨Ù÷7ìY£¬w!%gP·i̓º5ÝCê6ïáõž ž=Õ †}RÞK’E«_C!©Q44õÒZ/ÇÑç­÷Fªîµö£ gï»øæ©÷h¬LÐòºÝ†½÷¾"ž) ÂÆÐ0oíä¯xEytïÓýÙV•Ãd·¬þ‘9~)HЮ“ý]œ_p«r¯v嬥ñ¼my6Õ9ˆÂ7סàó2Kí£Ï?‚/ï›Iå’ô‹øVºñº§ë¨ ©f›èØŒš#ùhob‚‰T©ˆØ®÷®£І…è éZ•¨)Áñ{IòŽÈnñ ª¤9>õ*÷*Ïè[®IÊ–þžž[•g]O5ŠßÑÆA¤8“Š®©¦‘›s4@áó QÃ¥ùšìyõ+Q1hBºXSâ$v† jÐ­Ž•žÅüŒågÏÞ{ßæÊ}'ÏD’(lûÇíõv }´‹ç4©›Šç(á_©ýžps­ºñüåï#ñòÁÍï´„›˜x§•^°Q¬{eL=¨!‰Ÿ=÷þGãe/h‹ŸÍß¾‚â#ÊýÞXzyXï‡Xq?4£êk"6é;'bŽF8[[ ËžAt3IËîÍqG2H$‰À6¿EþÃæ>¤Óe—ÓÍH§€ˆ‹~6´«#+,qšFlô)ñú/¿Q êjé–ÂgiDg3ý¸saám^Úœµsž•o=Z$ {:£®êì#×À‡V¬BËá#¢:ÛÃ1úp?mDÎä?ü$öQÓQjýïi¿çFù4ðæÀŒñêf߇¤W¢(‰¤8ÊÅ$ÊÐ\Ô¯àDDÅ©³Š»Xˆx÷нá6!qKä´£öå¿©­‡¶}ï)ÄB'>Sï€ñ¾ÈZX‚U*§÷ØVi­H ÙMXuF®zcÖü ÙÿÎÁ¤ŸOÖrÉ~ÍZK\©rmЧÐcaÚ"˜UÕ¨îÝÐæHÁB+U¸hê›”ëì7ñ2¸ÚÊF?9—Ö“jWÝdK­¹ •… Ûâ3"CØÔH"i’Úb‘;ó~¯u«å ŽåJÐmžÌ®F¡•Äq_޲yf—äö¡ö+ßG÷‚} I¹Nº¯¯}AWçÚ3$>üÜPƒB«7¡î/V© - Rr'o,ÉÓœöܱf¢õc[õ(-ëÈ› ¡0!SKrïH÷ª™\y÷·Ñ½úæA÷ê”*U¥½0Öž õôI©ó•û´íãw 7Í%¯n×Ñ;m¢ÃzíÚõ±¡}°ÒÍ”.ü˜&pâTÕO"Ÿí‹U’Ä¥y“D©Rn˜*ÖFë‡Fc%"KW²gkX¯«¡EIuêæ›µݱ<†=k tij´³Ç×8&[S:Ѱþë©.vü³bD°}šÀ(DÐk,Š®EŵR“Ì‹ßÁ%6”?©1©TN-cÛªèÜñXæâŒs RShZKá´û¾Zgë½ }ݺï§-Øf„Ì„9.V½ß_ñÕ¤te»«möê²jvìAêLzgÐ3E³Í$òÛÑá`¸V­XšC9ÓDöw°Íp«ëÂUïÕþÒný¶Vc¦1dÄ™nhúoHÒX‹»Sž:dúëj'åIª® 1løž“5ºçdo‘îD_›$úôëWŸe§uÀåå69Ä›)‹õ©Þ~4i ýÝfD4+j|Zövø u3?R9¤ç«îņ'ëm°}¸{-Ý^«Ú¼lŽž§)Ïëj"‰m²œ´ 3&tÜ t?8[+ö÷CI—Í#É-R“Jò‘Rµ"+MlCŽ$tÊ+ù~wòÝçîd9‰€D@" 8çdÍP\2•À¨út+N¾õ’~òC…°`ã»ì%‰I L< —HÕê±ç_%Û*G•¨m('gð¯ž@a3,ñãFè,«À±ç× ë‰‡„1_AÎhU©áŠÚ9¾öÔ}¡¼U8n´mT¤§¥UŸlÎîÀi¼ìšûTÚ5êëœI#/H¸ŒôâÇ’ºÐND[4íhùw¡e šm›€‰YÆ?ÎÙnF®í†CÙ0Ä"ö»-u=ŽÐoJ¯ÒÃnnªºicg;wúóVö îu^¦®Y‘œ‰šDvnÈš¬³ {Sí¬ˆËtƃ¤œÞ„ë*‰ 1Ûe])Çh!yaJΞM›0ÅöL>ÙžÉ\4Y)ÈÉSLœðµ6‡ž!3 ó…ú’ϘéHÁcBŠ&éŠT¥0I›_Ãõ Ù¢§˜}MehÙý ŠÿÞ)áÑ[Z “ಶuM†º p.¢ª±ÍÆÉYˆkZÉõKÏ$”aY±o§¬TKvOW™%~º#Á#§"ý&² ¯‘4¶=mÚ‡d½“Â_êF^ Ü*# g¨'¡C)ìKæ'9Ùìö1SF$‚G·0Ý«ŒK7©ºâfü sÈžÙpúÀ?—ªÐìd~ÖþºÑk© ÉWkÛÒfO>:Hz04~ ÆÍ?Œý¨„¼/¾kª(ÖºÓQò"tÁVãɖƬû¾³Þ<ïÃÕÏΡ[«§ ðýÕx^pÐ ‚÷Cù§ùˆZ<¦9ôU¹–q?˜&ÔuÛwlvþ’£É‰€D@"p®!àê[ó\›Ë·?^": V?KFÛ´ð&xÚŒIHÚM•îú{áOä¢ú]›üoå„I"°q“¨ÑÆY¾á#A”ŒøÕb&Ùn Yjèä›ÿ‹ÂèµúPQÂÝ9õNíõÒÛAKàñÓw‰&K½s>Rˆ–¢."kX͉ ¡ªª=l,ÓضMÅd,4q´yN¦_¤$ÒÝc÷–Ž’Y<½ú "ÖÌN =õ¨è힯ÁÎ2Ûˆ«ÆûÙÖ¢3Údò û v bkIÏ·¯î¸!ÑàÙ( T[t ð†B“4Ò%ݲMM³È^‘ÈöL(IEEe)}uîþhÀq³Ý›ø™Ü‰FÜSSP²l†E-©sϧÆõ‰Ú=zžSbfàA[KÔ«¥“4út¢ÛÉæ×J^øÃ7‰Š²Ö×ÇzÈ–:ÜVãæí¤Š¢JÒè ±úXG¡ÄÁ‹Qd•xÐ=-ñ¤«‘õ뙂tê«(DSînRQR j?AFþÈ‹Où_7¡™ßÒ¸ DLÄD£§µ½$Ur:BPJ2’nšO¦ARÐC¯ «>%õ ’ò$”ÿ¿M`»;ìÊ;8-•$‰¼ÐNÆës¿Fóþ*$)Ž#_9¼¥ã “ ûvÙÕ·iŒKÙ'¨çT™‘BÑtp©:$Š’þEDðéDðE}õO`¼B<ôµÙ+®èK 2¨=öêÉ °ãÛèA¶j\-Þ•Š“Þ,0¨9À¢&‰¬õ Uƒ¹¨}eñXô©d_¦™¼B™hA„^z-žÊP%\Ô'¾Ã€Ñ·êvÜ+¯VHèøP‹!—N!uš‡º¦$•èê'U²-Ù–„Ú ŸÄe»Ð¸xŽXrœEö£‹¤vh2þ鬶ºÝYÁ!KO»iŠØ0·J°ÉŽM0«ä¡Mâ˜g B%q“q`=Ë=DD3nFr]¾‚Ð4E\¤˜K—ýK÷•úp¬g†o0=;*Ê\?ì+ª÷jÀ˜L!hŸm=AìL&jØùƒV5µ@XV¤µè c IëB”H¥ ~¶ôÐ ›AvâªZåa"žÈ+aÜu¢Š¾òH]³´ -›7“Ag[)Ee¬~è£çÛ@îÒ]ué*oÈûôZ5¥ÇßiôUÓOž½ÂIMÏö;Ti?<‹××P£û¡ õ{ëH­9QãIݸn–b ™¼•U·²‰€D@" 8çÐv-çÜÀÏö×}ž þøšCák"W°¤ÅÞ¢Ø-µ«Àn®‡Ýø}Ä~÷JøE„‘]zûEÆuYŠ¥êïÿtUÕ£¼ F`üš§<"ÕR/nî÷pô‰püµ·,iFHª…ù–¾õþ€Ee¡G !÷0ú®O§xcEã­¹V{5Zo•;#ej,Bf**?-TÇ6Ô é† ¨tÄg±‡Û\÷ÎÊÐYK昜ïK4Œ6€JK ìÒ—Ç<Ä÷ÉK”cÐÞ(’}ˆ}¶›DzƒHQU+|3¦ÁÙ¶i=lÎ^ÀZ½d$9ˆ\G¦5˜²¡e~WÛç¡=­š¥ÛÑI6]èŸ)›¥Th³A.§íÔJ¸:’¡a²”57‘ìÞ\†tÕðê@õ‡Š:ëß‘O[ZVã"²hî"àQ{²f†e3íG$g,QJŠÍß.’NQ‚ îÁ‘yÏØä;žX[ó›4›ÔÀžq4¦ìX‰xJ­}¦/ÂÉ Ú+…—%}ŽM\ׄÆíˆ*6>SVI"›šCrÒK®ˆí‰–0 ŽãhÀ,ø%""‰Ö­Á.1u©TR覗¶ÁÖa:``<ÖbT¼‰î}O‚¦²˜L^Óð‰‹M+=Ãøùc.T Û’Ê‘Y1Î{5˜¾QáæÒ:ru> ¦YD½™ïXÄw*"Xr‘$Ú©r¬åvJøŒBÍ·¼±y¸pîž®"aºÎÉXÝîÕyÁÓÕ‡'kÕùó×Óï´¡n'Œñ“§©¨0WkȦ „fÓ ‰ö÷ Ýóš‘}»û¡tSâ'ÏAðÌ©ÞD׎BËf·è†#‰‰€D@"pî#àü»îÜŸ›ó°ÚŒ}0J³/ãÉ9«ìPèij»¡î¬¨²5Nú2ËÂ…o¿Š´¥w $óøÇƾÑWNø?>…Q«ë ZåÉôeÓÈ--5, $Ü_ û:>±t1BGeè‹ºŽ³T ÏG?'ûs×-ÈÜSA iÚ,FQÈe÷.ƒ %¹·nmÓ:9In¦{µõHî€?VÓ­K4H ·r³Ò†ù§K0\.µëF;-݆süŘ`à;õŽ;ÅETìA‘Áf­™AɘcíAn8ÖÍsð¾á~»ä޵’vá~17À¬z§²17ÄÔœ´´±¥Ðôq®û]Ú”$ >ÕÓ‘–ܲùm-:à±|é6Õà® êõjùìƒë Mu¨VÑzÅLÆ´-ω,Ñö´…¸dÏ=×Ú­›^u©zÕòè«hÔóFOAòoWcâ ÷¨¶>Èxk€R4øÒÖ1jͼÝŒU·¸™× iG3‚³ìŠX£´C b2† Çg:¬óá·Ò}æ¬K?DÜz3¶ ÁãˆhòÎĶ[ê>w|ÖøÏœçxïûŒAªjë£e“‡kîÕzõ^ÿÝÆc],ü¢è,˜$í¤ÿ²oFÌÇRs©GÑ* ´¿&ÖFmb•äœ)hÿ)?B†CŒzàjEâèë.íTÙ4êɉŸB€ûeMFÂHRkÕ>$"ÌnY•þêÑNŒ•¥§†> y¯UWÏ_O¿ÓšQ©z9‹ú•Á:#5Æ(ƒ54ª³„áv׮h!ÒÑ;ã:"mH¬<;ýõ+ó%‰€DàüAÀþkäü™™ýL˜<ÑÔh˜`°ZšF:hçöåÜ=×ú2*oÐ6{UûÜ£ B*S^ÞV­_-?ì‡ßGWE5Ù»yÕ¨U…01hÛ¾°_D8Ìch7Ã.ÆÉ²è‹ê1iã ó„1$ÍS`_Íù¹}ŸöçÎkÊœSF ­¥MeïX%(5h)C[i;Bx“u¢Ðà ;™±É]‡†©!<-‰X…ˆ]_‘ª^¼Â"02ÁcÈ8iÑÇØõÔ6§#nÌ} µSW‘ZÖ0Ä?¾ aj>äU)0k‚üŠqä‘·i#CöžßŠÉÍDÀÔE˜<ò0©Í1±Aoú³/V °Ö£š\€[B %fÑîwh%/¿óÓ?CPÚŒyú²ãñ¥ÀÈ/ŽÆšvaƒö¿>ýØ è¬¥ kZ$bº~$ÁÔGžYúŽl#µ„f4|þê&¯¢·ñ ¦a99è¨l‡wÒ(„MVŒ½²­‰‚#†Sr+ñІýH$u&%”¡üÁ›z.6‰–“gPM††5oI¤ R¹|»%×(B/ö‡,ä_óÂ=+T±üÒg`±oÐKŒ}X?I ½¥_àÀcr°kÕfL{j¶Ð Ț ÏÖš°Û UIJ©Y¾µW¼ƒ(v†á¯¾‹á–’)CåÒgÈ»ØGh.|!é^`R‰Ç(aÞ›uzÒHª†B_½ú¡H¶ü©'d1ä=ɼø!ŒÉùŠÖA3|ÈÅsø„a–26‘Ö»MYW'•d„¼m&‚ȘvÖïÌhÜ[BüÆ2¸šñ“ñtš7æ$3&#Boû‡o9‘H‹÷¾‰ÖzkÍ‘Ô"ÃfŽ*U}ãðQWƒ5γܫDîf¾‚¦9d‡‹Æ“<¡ô\ñ*݈=dµ½¨Aä‰)îéûœ»_H \4¡ÉŽD ÷ÔV¡M!7݇QI»ÛIÝ…(ÜFR3Fø7íÄñÇbI†ßõ8& gäQ"|ȃžiê$¿‡QüÕ? ¡¡¨ŽZMGI^$k·¼®Ÿ’ž:þÔ{ŠšYo.Nn½)¤~×C˜˜O6’¾ÎG¯Ÿ þ¬6;ra.DÁÒW•£ùêÚ6ŒuƒX«®ž¿ž~§Õ”gÜÔ»Bë,ãéT²õ…p‚è›1‘Ö}Š!'Òý@ß§&Z³­'ܹzÈŽZ!Ì*¹Ù•ëç´#KH$‰À9ˆ€»{›spjvCVɲ ÿè(ø õ$ö(ÔQV‰®ªRUj±«xfN¾EÒÅ‹½ÐèH#/г+p&W’~ôTüíÿÐFÆ)‘$LÄDûðà´áBíªñ›}ŽöqXè‡?ZÐúÒŽ‘-\N_Æþ\kWÏ,Á7fºòœÛ_©Ë-¡Íâ(´;-Óƒ‚§Äð›îDìTV«"7»6‚v´æ[Źû…+oZ’vöV ©ngmT[ñ¨üyÏ5#ýöŽ3 Qײäõ‘¥Šç_qPÖÐA5ùŒ½9„ôÒf’)XC®J”··#‘ý÷>Œ_ÝŠp"fÌ3ç@¿¥ëo#5¦“õ=Ù'”¾þÂÒnC©EÐ&N„¬"k>ѣϯDI0D‘‚)3bi€ðüû»8ø7Gµ5K]¤O3¢Kd†MX³ÍKÛ3Ý»·á„M&]íÜP…‡lön.F♢TïÁí†ê@–¾›]«#i]ñÑRGŸèߎ/GÓFrÓ2ެ)V¢¦­$å“»è‡Z† V`G^>&¿ºaš­}ÁδªÑ¥`='3×o@’ªB¦ËDo5©õ© ù"BÞY“ê]$³Áb ª'ªO<¢€ú§aÛŸQv'bÄ:˜fYým'Qýä{^~7‚IºÑ\®w¥”𮤯c©¬”aÿS`ܲùtŸÑ¥:w}€’¼Qȸ%ÝÆÆ |ll¼fg7bf‚ùúët÷FšÖ¿†ÃŸ[ŸZ{¬NÕðþW¾žæÇk}Š.'çì3ךÀ1í¢¿¥Ò ¿Wcé^Շ췔*?cŽ>ÿ F­¸ æd¾ŸgZúê;±Å)Äð‡çSŸlŒZ mûÞ" »å$1©+ŸŽF"kjœà_³ùøtߌasˆœ{1¢íSÑ`WÁV=ÿ‰T³ç¤6a߆Otã²æYc,”y «±v£« È¾zqo÷ÓsÏ'*–$¶Òá›v1R—U¡F%Ô+?x¥ó: ü3'!Š>úÐUPBVžø©IßçNæ«/o´Þ†¶Ïתëç¯gßiLÐß ?ß á´îµÀ÷iå#oÁ´ê>rŸn]CZ¾ý‘QEÅ”löÃ0²W2UÿìïF{ÎFÇûAm¤aÛVtYãOnÕ>T¤3íۗ牀D@"🃀×?Ò&‰ï•óyÊìF:êŠË3k:Â/šˆÀ„xR'¢··*9ÁödzIO¤­¨õd¸êÓ¢iß™“=åñ]ôÞk›0–8~2Òë£!y‡ҙ°á¼Ã¿} 'דº‚^ZˆŠ˜'ŽÃ¨G€)KÙ|q5ž×ÉwÿŠÂg^F_G'‰0îå§û½™èçûLÜÐ*`’ˆmê|5o!Z ü#ÁÛߟ†Õ§´¡¶+0Õ9Zº<žCø’I:|šëÐdFOe±k{†3SÚyñ1›ÑYYhh€«›h#ÜC}‘Øwm+u›Vö‡8Ñ7qfôÖ¶ÃÇDû–b2îá“h³D›UŸÀnñ¶½Í† åñ$¡²%Áêd·Ç±ÌÏËæ¢_Ø€ d÷†Cíª ±g­•NK²´(š¤L‰iFíšHªÅ1D?IãOãí/ÂÞôyÆÆj§MGòèDô7ãk&ɳÜPwȱ-kÊ _œ‰îÂbò:•ˆöÜõ¨1(9o‘,eDšÐI6Äêè˜õôC .Á±Ÿ¿h<k'JÌׄ¸TªßAF…ÉKQ IØœ©FkÔýjú¡û(­qÃŽcHÒì>²¢J]н‘@Æy»Yˆîæ£Æë6íW«ˆ ¦5tÿƒ(lòE5ÙIÆ“;‰\hðð~2'ª÷j'Ý«|¯ÌCyžxÑó$ˆž]Ο;ZÁäIÏDBF½$]ÒíÁ½/ðì¦{>jˆç¨ Ìr4Ñ5yˆTC›P½òUZ2t‘1˜øÇEð例¸Ôq-òó)(¨¨’®l:=ën(ûpo­Z§ïÖóדï4’–JH "om¤(Ø„ò£§f;-Œž+~‚X B¹¡7¾÷Ôù$]‹K˜FR°;ðåê¿Y')c‰€D@"ð‰Ày/Y9u Ò~ñS„M/ÈËUfB¥Uˆa5 _2°i&²„?ÃÐ&`Û½ô:ÚŽ[ªœ®ˆ¹øN®Œ…;á±Ù5"­—ÆKc ˆ¡Ý//oâXzE<„ô×ǽø¸°qÃêLiâˆá?½Y¸/|öKú±çþH¶iHšÔ^,ð`+WD_TX ˆñôŒcí {<µ;¾@Õ'ÿ 7êÚûhK«2rÎ!ÐC›Õ|uÔƒý‘ªk£ÒumMåh£Å€ër§ ÆžjTjÞT 7B÷ÜPª'7™ý´„‡F€Š>ŒÊØ×9ÝçSzEªÒ ©õÿÖˆBÊÕþõò/Êଉ:wõr{ÇvÞçxrp|M΀åë6¬·µcžˆà`’Ø›ãQÃ=ômŒ£gŽ ÑOªÑfês\ÄéÞ(׌|»³n‰ @SÝOîIŒ¹èÙ8Ë{Õú<1nÂ>µ­’ÔDíÝ8·à9Èg†](EÌ“Dë »œ5\Œlâà,ðó©ÁYæ¥elÝ›[Ï_O¾ÓšH¥T}_7¸5jžÓÜX+©ó/³nÙ¶ÍÍÙËb‰€D@"p>#pÞ’5ÞD€Œ C½)·ßb!>„ I£XìÁè "*„m"J|ÉþGüõ×:ÑÅ8úô˨ WÛ§3°t «bù…‡9ï†Çª~Ø 8®§ö ÅvoúººàM’:Lêp`©!/šÛ°7 lÃ&´—¢µÓzôvÿäöƒë„ÞwC¹íÞ†ê-ÛE=£?q×~#îþ9‚‡'9dÇ_÷=$-˜‚ß?‹¦=ûòe‚D@"pv!±~%ÂTÕžÖ-vDÄÙ5TËh&%*qÒBÀ⊥Îi‹Áß×]‡úwv¶.dÃqÀßø~ætaàÚˆÌ Ë&uR¾!jëˆxê§ÈÙ@Ëkç1>ÙdãŒO’ªÀã—ÇmË ‰€D@"pN"p^’5Þd rôêÁK¤Â‚È /_R/Ò‚ž¨Q%Y4C»,qÃ*G qÈzò!"AâQüÒkZÍ!?2QÓ|àãad‡6F>ÒxX퉥fZ‰1h’5^dÙ0däQ†ÓQÃs¢ µÅnÀM£3²FÍc—ÛBÚF”tý'éÇ?@Æï– 5,§66ª¦@»h&þåì½ãnòJ´ÇuƒƒÍµSýrÙ Q«Ër2S"ð‚@úúÍHÍ6‘<^€p›-¦MR5G—~tÖ"¹êLº9“Œ“}Ò^åÐ_SîÄÀ­’Fþ–~‚o–*¶‰ÎH²“ÿPòÉ8m7¹OA©¨EïüŒÔ( •¦àa™0M™„d’r¥”¦wÞ=+Ô+ÿC/Ô §ƒ´ëH¾6¦ëg cÜ];?q_Zo½Êj‰€D@"pn pÞ‘5lÿeôêߢ†‰ &+,6`øšèHË%ÒÒ´=ס4”H'i’ž¦f”¾ñž¥ÊF¨ßŠþNÆgáB$‹3vDC?©@±M™æýy¨û׿E÷<>^Þ> Åu ^¨ ;84'ïÅ(¦¨¤ýÑ÷£‹†²#üâI¸à¾¥K)ÛÔÑâ<6pœõÄÃøfÁÏÈNÉiPkQ E³×*¶=ÄW@\ŒPc묪}v”U ³¼R\?1LOݼdT"p¾!(T(½¬DMSŠyÓY½1ð6+Ï-¨ >yï]çÛ¥9kæãÍ*7bËèþ¼Ù(þ·/ëäþ€Ï±’GV?k5†>{>̳m'ÐWQˆÚõo¡è¨ò›À6WžÕÄ]¬üöSÙw"EèÕjÏêÑËÁI$‰ÀiFàü!kT¢aøm?*L‚haRA# ø8PÐÊ0iCq&yD;$ÕrÁò_ ‰–ú/¾¨•Aå×üc*>ü˜Æ>G‘ш#µ5&j˜0bµ,6¬l'¦«ºV™+“êøE–È¡ÐßÕ¶âã"nóGß>.*Q[Dt$Ý|yÎ †„…t’†“ÖÚ“6Lذ'*žGÉ«ë¬økeOñÈ6…b®œ†ˆ)Ã4&“ E;úíª©EÓÞƒ¨%w™Uÿ÷OÂÆHpü"«KÎAJV݃æ¬qˆòGWÙ×(Y»ý¬ŸEÍÒ§Qpb2¼ÉMº_tŽ>µþ¬ó¹;Àjœxn-Bû«<²qRþ?¯¡;ž\¢WÒw†Ñƒs³däÕ(xþ âÐÈ@uÖpÐ÷1‡>2âÜLœ=5ˆ~–LJƒ¨ÜŒc/•¡ý t×”œ²1c ªD@" œ_œWÞ Lã²pÑÛ¯*ĉ{rÁk¨#.aC;l‹å›[~î@–¸Óœ;eXZ$ýn²#sÓ;-‡àè@ígÿ²mŠçFc¾ò Œ_ó´˜3Û­Q¤]8«쵩úÓ­Ø×}ViÛVœž±œK>| ~‘á D„Œ ¤ÕTñêï!9¤nÆÒ?{n[*H"­È ŽêüxiKnCìwÿKHÑXÚRÉ(Ë9GxŒj`Ñ'þç]òˆõÿTÏW ^Z¾Ñž "}›×:gÛEbܪ]ŸhRš”< û~¾ìŒ¸c×GÆ%‰€D@" H$‰€D@"à ç YÃÒ¾¦PÅÆŒ+BÁt¸¬J h$ÛI1}ú¼iãã~5‚ÍqáN¨Ùºüa7¬’än=gmwÕÖ ´ù‹öìÉ®Ìã£t!ÕDñîúFô´´)ͺ9vû1ÄÌšŽ,òêÅÞ®l©ýÙ×±tjŸBŽ$ŽØm{HF:Æþa5öÞþkt’b$‰€D@" H$‰€D@"p6"`5îq6ŽÎÍ1ñf>â’ Â@#Œ7Ûs(&ˆbN¨m–Ø0íPdÈxl,X3ìnìùˆ“ânU£r¬ŠÕq²\!bxLŒ…†±}m̔ζbºëˆèd™ŽQ¬0&j´ëáªm­Œ:f¶£ÃF–MY™¹âWŠ=#WõežD@" H$‰€D@" H¾%Î ²†í’¥$)j›ôÓ(«1QÁ’5gmP][[ÆÇc>…Àž•jþù™BÒhÄc¬Z|ä,Êgéa7ƾ¬¾ž³8N#~u'ücc„}ÅX²‚½ErÆY]ûtm=ÐQH‘ZTÜ÷þ‹Ô«Èë– ‰€D@" H$‰€D@" 8 8/ȶ⬠²F %àº6ãb‡²å³¾­Ò·7 ³¢ÊBv|íÃ3 ¸P"É£úÜ]®˜˜7w'In¹£¯¼œ¤Šú¯VZÅÁ?\W'6ܩɥ‹wö¾u¾†àØL¤fg"˜ >Ëp!0m.F®z#Ï=7=Zï²sd¼çªgï(µë}®¬Ï³I92‰€D@" H$"pÞØ¬Ñf*6ãƒÝÔk¹M"X²Ã?&Z!†AD5}FÓ4lxìú¸‹A´)ÄÁ{ĸ?<¿¨ûòD€°LËáZ± ½ííö%Ü>O$#Îìn\fCÁ§ôóä8]Çà)ˆž9•}zª­Ÿ•õ~²Qi~ð)º…•gvˆ¡W-D´( C×/›n*Fí¦õ¨óÜv¶®¡s%š1›žET’?šÞ~ {žÚîÖÀ£ç-Äð¹iD0^„–5 C$•‹š˜H˜îÇ‘§>2,“¼ì›Ðsl ׿¸(Cj‹;>‰-†e\%F/Ö—Æál¼®ÚÐçE/^‚¨(ГÖ8-[žAùãl™zz°¹Þ.Ö§g£È@Êoç#e¨ztg Ÿ{¤vV-BTV*|щžÎ.ô5•¡tñèñl@.KG/¾QÃèÖ“ëÐ%N2S" H$SG`v§>ˆSo6àÚ†œÓ§Þ¸µµÞÖV¥kÎÙc[6š“6Z=Ù¤åkyvÇúœ¯°ç¶¥H¿÷.D^v±-ÞT¶Ë$²ä:tDH¦H$‰€D@"0´œd ÛFa) ÂÅôPzƒÒð¦M¾Ú¡cgu­–za‚"8e8˜ñ‹ “N'+Ð^RJž›Z\Ï…ˆŽ¦½°gá/uÅeŸ4þ‰q÷öã'Q÷¯/Ñ´g¿ë6ÜÈ ÿè(… cre(Hû~™˜¢ë¨ô®ªÁ“KöMŸÉó´e«ã»_®Þv&»°¯>Ó’¤¡½´Áó Ðοôɸ`ÝgüÉÈß©;﵂*³¦7ü×5êvY“F…¢µˆˆ–µ¶åãçe(D '¦"á*8l‚#®²–iÉ=Eòc€ñÚŽÎùY“Ö¥bX°§³Ù0]&žA†àz‡.~.Ÿ¡#X4&ȳy$¿°,¦†Þê"´ÖÂÛ…À$ µTË9…ãè%È~g!BTR‰[r*ýu ÝȪ‰€D@" HôœdMwCºªk˜¯lòi#~:6ù‚"ò ½°X¡çqAP5§}ÐÈ £<û²s[j;lP7`y||ÍÊ›A®Þ×Þ–ü#¨Ü´eÿ»Q!mtõ,]¨í01¦¹·ä a$8m¸õr»CÕµ§©ÊùGG" .æ%k|GrgXÅÉ£ËÐ_„½éó,*‘‹îÁˆÅóûqî{˜Èš‡=jòÜ)¼¹sÊœÍDÊö!vù¦dÒ•%gB§-Ö®³é#&;Uw€ˆy €-ëui@Ì$­L ž*°ÉÌ K Uè-üÛ¯Z1TÍÉvÎ2Ò×n@êV‚eÐÃ[ô’ލ©AŪ»ppí©¯eýxXjgÔ~¼É–ãÆŸ¸k¿‡ü‡4?WcÑ:úø) JL-œN )µøi…sî=½¯ q“g!í#›S42“€£uÔSº¼“h J¹ý,—Ž´ì10ÑIº+KP³y•"}‘4ãj„¦)¤^_m!ª6æ¢Á ä@Iú |ÝÚgP·ö \²ç%¡öà3zI|,Dø5£àÝYòå”oР•É¢2è<‰’å/ŸHŸ„‰1èØt? I½!åÉg“n¢7Ýdë¤p3ö,·’l_"îÒLŘàMm³ ‹ú ¯¢pƒÝ†nÚŒž7}‡6"MŽÒ&©1iu·|€#ªÝ&’®‡@²á‡Ôº –>{Ù´äy—Á㔫¶£ÄA c:F¯] þd£•/Ü…Zw¥p¶|ŠŽÎÙ¡!øgM¦Q¬ã¡¨a õ+žVZ²§#ëucœŽ°teüýÕÅ8j)©D¼®Z‚q‹.£9*ç=T¦üÉ(wöè£q³\_‹8ÂM Íh&E‡ÖlWχöüÛ‡iž]¨\ú‚„çš$ù@ט¦Õ¾éi²lÚÙ&Ê¡–ÆD5_ÓžÒ|”¿ð°Ã|øÚòÚjÙp?©é)mš’”û€1¨^»'v(óHþ-I¶áå+`ìDkî|Ô¹„Rô²‡1üŠL²§Â¡yûqŒÖ³¶nB ó”k†ÑZ8Œ’×YÒEq2웹ø;T—ÈõM;¬§”U#ÔˆŽimçˆ*â­ë1‹g!„ R¾@$•ÔH¤]¾Ñ5ÑîÒ¿#ÿ©íˆ^¼ïJõ¼; pxÑcÖv bv4Ô­yÌ[k•éˆÑˆ"tQ˜°|¶NÂÆZr Ø˜E©Ešq|Î,q¶>í=#͆))^^ðì¤ëñ5JézØ?ƒâfMQ‰šNÔ¾²¾7/D˜NÂÆ¾iy.H$‰€D`¨8/È£ö³/øƒëáåCï¿42b(n‹ÛQ ³+ëÆÝûN ÿ°‹& ü¢‰¤†3~áaÂ0oO/©WÕ åPySú†¤]ì6“ƒè1 >þôLcG‹ñs^>´UáùhØÐü„‹mjŸ¥nƽôöý|š÷ç¹îQOäèã®k ˜ëÀä•*ý2`éAPç-‰b{Éœ¤˜¸lüm`™„ðÙW¢ö¹'PxÄ3;8Ý–‘i‘4–vÁ”E¤¦fΚ‹DU…!¤é |ù¨n£©VÉzP³ÏÒŒNÚL,X lâ´Ò†7ìÑï#BGLô'•+µF߃Ë?¾Ù@£&aÙ3°`>Ÿ÷{mPˆž7OÇÜ)ˆYL$žJR(”:1ÓÞF½y6'E[ê‰HV&.%2´øûנвYœŽa·ÎöWÚñ5m®­›xGõn% ¦u9Hv[#o; ;B8zÅA-”ˆÁ¤Ío/Rç݉æÝµ0M"òÄœ‰aôÈWÇç5m‚yJ¡+ï %¢þ5–zÈDÄÓ‘ðú |óèv›òâ$0YÿÎ!òSmT-a"")fÞ6|sÕ=¶äƒc ¦0¾×¾ð©öÇ(Šë{.ͤö yïŸ0þç“¥"èšE̺ÃÞ¾»Üné;A][¸âB¤˜£íƒ¨×iÃ>—6ìºuÇ ˜²Æ)>û¦Ýe·áŸ‚‰[ÈØ´JŽi læÏEéOf u@Óܹˆ¿†×ÖÀæu8¸S+ $,¾Is¬w ]ë-[3±É7+óïÓa!kŒ¯#sÒdÄÍû®²§vôöÃ{ÒHÌÖ¯óh’…snG)tñK˜´ü2uL5è ²ÆyØŽ¯|‰óCQxéBT}%¢¨– "I¹êݹD¦Zî=×M¥¯ßLžòôsSÊ‹ëG×£ìŽY8´ÅÚÆ¡_®CغÙh~òÜ0Ù·.²fʘD@" H$Óˆ¿d/üy2¡´yvDºD Cº@/y5b Å&2wž…¨é®.C'‘?m»ó•&hã¦=Üz›jК·Ûš*q@IDATye–æ&ÍÃ…‹3,ç–AЖT!jH*d7IÖXÊ]q³…¨é&ÉŒúÜ}èdi‰HþÃj%®þöW(NOk˜ö€¥]h­æ†ÈkEÇX%Ù­QÙ­ÑÍe¹l¡£Ç^ø½âÄ„ˆEÓ•túuÍX Ѳc%=yÍ&zJ³À­Ù‚©Sݺc®²×E´±“„ I96çY $û¥ÏÀäЕš¨‚ob,DM': ËÐM×»i²º¼ü-óä5Â×´¹Ð*Kvó‚èÒF¤­-hD Ib5æjׇK êÖE¢¦“ÖUýî"c²Í”t2^˜«5'Žãw¼d%j:kh ùêõæìh$­Û€Š•¶_”ç>Ls§«q>d !›K(Á'$Ò´>’”BYW¯íŸŠœäµúëØ‰vgn>º;•Š~é—aâ¿_RN´¿jžW:’*™Á÷N{u3§kª•³?.z“uD “OVÒÒ¾°rÞòÔ]øœˆšrãl·R#Wióf‰—Áxc^x\ý<€äiΚÑТõRZD×wZ›è7ƒÑHxÔ—C/þöî0ªêNøÿ/!$’y!ÂK $AQƒ«!hÝj´[èÓ í.qÛŠ]•¶ »´Ri±º«hKµ[tÿÐÝí.ôEÐ]¡«ûZTAË$’@B^Hþ¿s_&3“I2¼$d†ïU23÷ž{î9Ÿsg’û›sΕ­7š@Iãû~¶wâ' € Ð)—àÒ«SÊuÞ™š[E^µZ²~4ßÞ×;`ã&Î7W'ØcõÂÐI‹Nž’C¿þÍùæbF2îÿ¦ Ÿ[(‘½õÞ³º¸"{‚&ÖJûFs{ìÁ_›!Éù·È§Oü̾½´©ƒS+ƒ~¤LÍ—ä/L¶ŽãéM㚸ûºy:ëÝ€Mlf† -üº|úO?sSvþ£S–†2턮屆A™£^hÛµUb×Pó=WS+•&pZKå1½€¬-–Á_Ñ5UÅRª·Vo½8]cŽþY>þÉ+-C£Ží‘ÊŸÿDF-Y* I£$-5ÂàÉÔo”ÍBÝ›¿’W»yVËþ¦Þ dÐÄ’x÷ùüßw¶>\ 5:4Æ'P¡iLO‡îð›ÃzWY) » %]ï°¡ºÙ^½?L–És[îðRµzU€£”Ë‘ùúM¸u!å½ùi-w¼Dï^å;äIç¸È_dÏ?“­;ìõÞÉzÞ\¶M>ºñÛ-sí,yY&Ì6ií¥Ò§7Æ$™¨ÃºÌ°ˆWéEµ&)ux9¯e¸Ç™õ¿­sWzR Y®¥NËð¼îèɉUKƒ–Ë´tlžöjXnêR ýßsÅ;¤|Ój9]5Ã*_lîtÝ^¤ÿÌ|5ƒ¬GÑÛ%W¬pžjaÓœõU{d×ø»[ê¢='òõ‚¼‡^¬¦,xBç¿YèîÔòxvì}·×© tÈÛb{ÈÛ˜é’}Ëã6¡tsÙG²;ÀÅÿé§ž—ϵ·Øi¿a9&(•eÕ5^âLðÉ«'…[¡†]¯Ê¦;»/%{ÝFë<µW”{zÄX¯5—·r¦Ý£µ-ÄéI¥ç[ŠlôÏo¸T2'«wD† ÓOé¼"9óÔ«7V¯ -m%9_‘8ï^<š>­PÏ3§Ý†MnIÃ)'­!P:œÊäk–º#~=¾ZzùD¤Ü$çO’mOm±Óúý¬\ý¨l[àÔÃÙfÿöòJ8õ É[4Å ˆUËQý éªIÃ#ÍðAk©–Þÿ°Nnõ ¿³×,˜)Ã>xU{Ð-¶W8?‹—<¯C)“ä×4³iܦ-ÚVÚS-9­Ã÷±O†¼@@:IÀýò¹“²ïÚl¿ò;9¹ù]§—ˆ~Ÿì'Ü ôó)Ž÷¾æ¹kެZ#g^·q„Ž'Éþѹê{÷Y+H£½tL`ÄêÑ¢ùš[T[ÿœ.fÈ•Ig†1yê'’þõ¯œW FtãôoüŸ–úkþ ÿ¢š`ˆ(±6™çzü”¿ºUbÒRíÔf]g/N1½£ôxVÏ—Î8¦æmõ¼Ò¼NUZCÏ:ã0Ÿ§g4½¥ÚYªV¯n Ôx¥;ùáa}åµo\é›Ñ[škŠå€'PÓ²CÉ‹oZw7ŠNÏlYÙѳ^ƒ$}ÉC2rÉdÌò$W‡ÉLÐ!)îrb…}µ¹gÅ6gU¼¤Ì›ånÖÇ,ãc¸ýZŸ[Á¯Íúôä“5všC-ö Ô˜Õ+~!5N;U럧V´jÌÖ‹ž—3Î>Íe›}†Íˆl‘ؙԷ=LÄN/IÎm¥ò#ùØ+Pc¶š»T*=ßðÛ{´ûs÷›Rã¤Öžq&ñŒ|kó´æ·ô§«ÜUa^j0ì:§I¾ÎÕc­k.Ùá ®$?Yàé¡Q±Ä+PcR.¿_Žé°+³¸A)ë…×Êzò²W¯•w—½ç¤Ðž=:Ä$Ø¥‡öî¸mßûþ­× .åÖpšRÿÕÖë-ò©Îiã?×Ρ¹n£€;éÊr9è¨1©öxz)i dãJߠĦǵ›íã Ì™us€#²ß/¿…?õœW±N€Ç m3Kt;ðgž'ϽÖŽ¥=\œó0Ì&]²´×N’ýTl¦žÉOæ{Ú±òÅïûõrÙ"Û§®òt$ Ø›Ms«Ûú‹Vû ÎO ÄŠÞ!éæçÝàc?Yvzùì×)/’¥¯¨1½§ÎhO0ýío-1UøU¸»ý5&ÁŽõ<éì~‰ž—št¸1CtL°'kÑ|1·Ç®Ø´¹¥.nÚ}®Òù/ôØfq‡ûX™i=«“Þ3z§’¾×\-ÇÍ/«ÑxUW,§uŹ35Ò#¶·}¸¶Œ.´0&?óO—š}¤îhГ„\è/ã~µÒPá^¶ø£®â¬ïФÞÖ°¡ˆÚ*é5`ÄöíÝÒ+F»nœ«Š²_¬7Ì¥l0‹_fϰN*W?.ÛÝ `uÂß“‹ò¥¿öN‰É+DYeÍù‘W( ÎDžuÛßh}Ì:œty€ì½V%> ©ycu‚ÕxkmdL¼èœÚí,:4"@Ç!÷¼^‡ƒ´Zœ h߸ƒÅ.ƒITûÎús¸lÑá1õÚ ¦ÝzCçÿ)>-}'h¯¤”áb.Ùûß1ÖÙ^.'œùŠ7ìuæèH–íxøù;Ó=óÕÔ| Ÿ)ÎâÖѼL˜û²ÜPèn1ÑÒËs%`PJ‡ÚlÝ뽃ý|ÅZ9³h¢Õ[D"ª[o?ï5Éå×½ãܾ͞ùzg—%CæÏ’$ 8Ú±LäpŸ³­†ýD¸í¬;Ô¼ã[ööökYë>$ƒ×½Ü2|É4q]’'°æîk†¶ ÊÌÖÑ6ƒ$Q{ü”jðcPîp+“fÈúXÕDI×öî™3Qß'HmK§×M¶åi]×r~·u—¯gåd±™÷)ƺ½µ ò™ýZ–:9ù«•-/=Ó@ìèßzæô©X2É&(qç¬ëíý>ÑÏ‚÷ÎâMî±fi¯®‡¬^]=sgj½g=ó5Y)rò%³pºN²íN-Q)ƒÜyD@º…@XkL@âô'{e§ÞucÜ¿<%=úĶظìNpÂ}i=:ñÖsÓ»Åô€Ñ@És÷ŸH“g‚ Ÿ½Ú|a&ö½êþoY=ULÏÓ;§ÃÅ-—–Åìczؘ2dýèayïkß”úr&ÔÖböÕýâ²Gx†[YA7϶ös×›ú›´ 2Wµ·xݽÊMÚYgö°&Y6“0»“[Á•`ëÐ^ÁÜöuò28àcQÕvÏ™¤ 2lñ„¶I:èÉÓjG}ϸá"s'¦Ó¼/æþÔoÖ½òùÆÒßL4Ü+[‡àiqVˆ¤ë^ìЦ™“be«¬Ï•ìmÀqR%Î}ZFÏ›"1ÁÆ=ÜÜuØÈ±·Ý­Ëw´^ìšœáé”§©þHÀ½NWˆdÑXºe Õ cµ?…$æKÓ»—EsÙÞ–;<­xC&7Y“¸ÉiPl¸ÇµzÝZO9Zîâ$ÒS£ ¼hPªÕ¶6ݪõsS³Ñz÷Ê™®OŠçé¿V{‰X¾Y"“ô`ÞK}©Àð^uvç{Þ/}ž[ö²dd·˜ñÙÃ~qîÈ¿sÔ;‘™'@`Ê;‰õ¼¥“yÙgtv«î ÷¼8±n‡4Ì4öμ5ÆJ?'S³kµ’4X3N1‡ëÔô}’9ÖÓ‹Æ{¨·sŽ™¼½âKG78¡s›ù ëï#r¢÷€‘Œ<Ç©“3™T/=<à´WσÞSUòîò|¹myoh &]´×‘È$³îÇ2pt²yx©¼šµ € €@W „W°Æ¹?ñöù辇$û±JìPý+Í^L/,1êæŸ˜ð»€·&Öuî¥Ú*Ÿüð§röpéy·ÍðûþNzÄÇÙÇ6=jüŽÕn†N@Á3ÌUúÇù_ß!ÿõßÚÞM«ezÀDõM°ÊoÝÁʤvòj{Gg‹I§Vûw¸Ë¥NÐ\ß ¥x]ÌdÌÖâ¶ÓÅȵ7m«çAÃÉJ9öß¼Ø\ÃgíÉd> šön’£oÑÛÙþhh:ñy›’Vúm÷93Ú¹èmÙ£|žN4\ð uáÙÏÌ1±b•Nªj_P5—½'»;¼xlÉKtq Üy4thRÙ©ÒÉUÏ”UHC¹^ì.ð½sמ溮ý¥¹¥wLû mÕ‹Fwõ%Šž^ºCêth™¹n޽c–v‰1"g·¾a=Ú?ÖjœHíIÑ#}¢ •Aöê³ä˜×°•F3´ÅÊI{>­/’zOÀ++°WÎîoÕëD´ÇRMÖ^«F¢ìr®|/6ØäÓ¥i f„jÜÅš x×^©×rÔ§äˈ™ôhkiöD"ÚJÄú:i¬Óäm f•­×@_ è¡Îo¶óÛô–m³çê5:_ç»A3½åöê-"ogëÑÆiËèdÏ3 dHÌp{?½vËÜCNÙ›«¥¶£RjyZ}… §Áù Š‘/¬‘ú^/íèx—p{•N|l‚”¢ç±éä¿Dì*µÅfž¥ø; t^¢µ¨yF5nÛê$â[wè„Óû¥^¿GO½W'¿˜÷· x €\œ€çÚáâ²éf{kÐÁôšøðïæIæCß‘”Û&ÛwBÒbZwÿqî¼(Ö&ÎE¼;L©±ú´þ·ßȾgÿUšêÍEÌù-f’ÞÄ›ô[=“¯ ‚˜cš%ØÀ‰Ië½QÒfÞ)%ÿñ_b&S¸8‡°Êkö=ŸcyÏͧ­ã<ø¥[ylÝ’>k¦ÞÚ<«%Èæf¾urëåìçN}ô÷¯ËÙ’à/ÝÇíc/÷£à¤”|ÔzˆÇÕ;Ú¾ônß•R¶Ëžh¸GÎX2£Ð3TçôÆÕÁeá¤J¾#ËÓ“¢ú¥¿—wéE®g™$æi°Æ½^ó¬ï‚'»ß“³u³­ Ñécõ€E­çÎ?ÒjK[+Þš²9z)Bfõ°1é4ØâÕcƬ9¶Ñ^£·ðNmÖ˜»kíñ ®Tè\C­‹Õz©œ»P>µ“ù³‰zs®ó Ë9¯^I— X’8z¸Söj x²ï3ÚË)Cƒ5BïÄe‹ÞI©^{2éÉv¶\öÍ[è7Ü(С·è%{h[Áceø4§ˆö4:d,WÊÉ’{e Î9=a–¤ZƒßL[îõÌT[åô§‰ÐÞV9@Ûíœ,é oN]Î>àsX+íMÖÓ¶TË5=}ÏzÉš¬eŒÉì OHéÔ…mïr‰·x‚L:$+õíeÔ* ÛR‘³ÛM2íéäjtΨ&Ìñ $'.Ð`MvÇÛK\²C@Úðü½ÓV‚\ï*jô³ß™/;î_ åÿ»Éºógb_ÓÓÅô´Ñî:óÜô 9ü›ßÊ{_ý¦|öô/í@Íùtr_³$þÅV«§ŽÙßýw> Î>n >™Ã%~¬þõÝÖâwLÂ`2ëœõmíæYï–ÑTAŸ×)µ79uò¤ëÌ'zÜÆJ½Ñ/ÿ¯X3ÇrÚÓ:l°uqËè¦×GkH™¶»9/,_á¦ÍÇ^v±›ÌmÙ/ŢàÌЈȬI-ój\‚|ÏçzÏDÃæî8 ò ér9¶ è¼J’”é\àjoƒ}>ÍFïÈ95V Z¥ô¼1?€sôu&þ ¾Â{åÄ® ßäg[÷48ñØæVCbj>ðî}#R¯·>·—xI[óož¾Òž³fµJ•6wŠgÈTSeKý[%ì„vO!ýø(Ùì¨Ñc sîÖ ‡õɲ±Ê ô÷'£™ä³­­Çth›µh¤¦ÝÓ£vk‘'ÐSºõ€µ9"%Ûs ñ³^mY¾Ï=tΨEs¬´>?fÜë¹å½œuÛÜ'EÇ/ÎjO! Ž*¼_*Êìè¾™:×ï¶ågl p¹p–${ýú+_÷žÓHëùÝÖçkú¬±ÎgˆÓ*ÑãèÓÜðMí&í äwèáÓ²üÖð@¸¼á¬1¦îEº>-ÛP$~û{òþì{­¹g­|EŽÿ÷›rêÏȉÿ÷®ýÃËçþÕ ê¼ÿõ¹òÉÂǬyj(¹ËðlK¼÷ ¹Yon/uRöäãžmçõÄ3_Ì^Ù~ãJO °OÞùmÆyåDâ,ÿÎË2zÑÃrÍk%G{ÑX‹×¸Ìùš»êÛ!O®tnÍ®)Ï:ÁK ß IïɳdˆkžS ã7ll `9Ù_ðƒö^˺g¶dŽN¹à,Ø@0îØ‡ðÖ0=C4hR­w2ÿÜÅÌKc!ÖÐ(wå%x4ùƤè·ûV @3l'°Òááœ}­á;ú<&u€½‹Ô´¿™4÷økëeøýßÔ~î:½«ÉÃM¨,î6ÍÌwLO£Êt,¿N¬l-^Û¯³Ö?ýœD§$ZsõXmäÎ;ä}@ÿúx—Õ©·¨1w×Ò^(&Pct>&Þù…ÌóãÒP£“@÷%£æ}]*wk ¡·Ô¼ñº”^à—åùIXú-é¡“p.¡ó9¼+5%µÒ3u°ôÊ!}²R¤öwÿ,;6­VѶë« ¯×‰†gfxRZ³Øó<Ø'åz÷£sš‡ö“¸‚‡å6ýw±Ë…DµíS:o© ™ü´u—š)e´ÞžÚ•ÔªˆAÛ­Ø,µÚS¤·“Ãi›#ÐR¦=2RÌ$Îf©Ü/‡[ Ù"Û–¬—¼§ìÛ1ÇŒž®ÇÓíô^?k‹žK^ëܧñ:ïÐm÷ʹ:Ç+ðT½êÙ æ3 dææíÿØQÚcÑ;™úÆËÀE¯è?ÿºàõîŸÊ§k¯–ÑÖÜ9fÒàB¹Qÿù/gtj5{\³e¥œ.{ÀÚf¥«Ú!½‡øì~Zozw+úf-:7”ï¤ØE²mþZÉ{þN«WSŸi…z ôY­Ú¤vãrÙé¯[Ð?[ÎÏgåOOŽu&óÕùk[#Ukfx†eu”aÇ#Þ²¥—[Wíéô*4ùî|p•ô}­Ð:÷ûè]ŸnÜ7³Õá*~ñ¨¼,Ò¹›ê¬¹›ÄT^{_o|`q#:6™Uw©·ß%ý&êïéÔ“R¼ëõ6ra5 € б@GwtœC(¤pz»ØÁç\-·ã¨1÷þÿZ¿K•s|kÞÍ3Øž2%/¯±†t™À‹ RxêîÌ0y›×N½Ý€ ö|~¥Ý+È9þåx0å6½œJ~ý{¨š©‹™,Úô²qëáÿèT×›ö5éLðÌô¤ùø{?”Ò5Î…¬»Ÿ›>äÕæ—¯I£~Å•=A’¾t»$Ýz« ¸ÕéÉÐ`¾{o¦6¦7²««>>ËÙñð?Ë©½e›" ·Þ.ÿf¦$MŸdjškÊôœ:é³G MÖDµº¥ºÞó{ tÖ•/Øì¹{”œÝ#Ÿ¯hÊ“ëMöš åãý{Ø›Îl|I>óôni‰jyòtËî—·"f-u-û¸«ÚÚ7ð>Eòîø‡¤¢¸u>bî€ôävýµ¾½'Ü£zÔžzj/:Ÿ¿¢ã ™õ­ÉLm–6λs5ŽLc»¾vüD@ÚˆøcƄֱ¶³›‚О<×<ÿ3I¾õ è]¨¢ô;þ‹ Þ˜Àƒ(Ìð$34èÓÇŸ±ók+à`Ž¥Û’oË“±Ïý³Þ.8Æ ØxÊaösÒ¸Vï †˜×Ÿ=ù¬ìfsº¤Þù—’ùÝûôvâCìpLf«>7A*kÑtf¾¢}Ëž—ꟴÔÛÞ?£$yä0±¯2ŽKy[Ùç[Ó¨IÍJsµzG33’àˆTúÇvÎ7Ï`ÒëœòŸŸnõŠ©Ûø ùSáÊ`öj3™çBÔÄܺ÷䊵mîÝÉò dØè$ë MzǪCk|‡%uòÑ;Î>/_‡Š ’F½J¨–3[×ʉV½qü³É’ä:4/%I"ê*ä š_þe’ )ÌÐ ~=ô>V‡Ö]¶"%N%ñz.šÎQz+û“Z–€A³KYBÓŽ:÷McU¼öK9"/cý/¸Z9ù2ìö±"Ûôœ ü>‰›Z I–m¼D•è°» ÓYeÐ÷ÞL±L¤j›ÞM*p€óÂÊ%ýRûIsEy×|f^X!Ù @B@€`Í¥n$'hý“…’>{¦=WŠY¬ÉŒ/äX&°bþ™EóÙ³øI)ù·Wì×Aüø¥/JÖ’žIznï¼ü÷ռͤĵ løo¤HúÝ_¶‚`q£²† 08‹ÖSùÁGîË Í-ªÏì-–«¾;W’òÿÂhG3Ù²úTþæÛ6w‹uõÇˬ@ÒÁþMÆ_- Wçèðœ«$*>A"{FIã™3:Üé¤ö Ù­C5öúÌOdUÀ zu‹ÚPäG^p5ú*ÞL Æˆ× € € pÅ㦓šº×à4¹þwÿ&ÑI‰v7èâ>s\'¸à2“þ¾7ë[Ò\ï3[@û99½Ḭ ¸œ,éŸ{õ“œ$ :,ÀrNmÛnM(læªéö‹3Y´O9u]D¤Îgcæ¨ñ_œúû¯æu÷H|ø÷‰:ÿƒxÍsR'Gî$»7t2R @@@ «èYÓIâg—JÙ‹dð×fxæ›ñ C 6`cÒ™ÉtÍ¢Ïü×ïÏ/Pcö3Ý×ÌIczû¸=~̦V‹9^wï}âNm‚6ft˜)¯®knò ÔxoëîõiÕWÖŠÈ!IÖü4:'­³˜É_ÿ‘@ËÁ# € €\‘k:±Ùþj¥$O¾Yb°ç®q'½íè˜^†æs:±°ñ©ÔÞ/Gÿð?íx»W~8kƒM×n&]´Ñ Ú:\{Û¥gÝe(×[YïÚ®ÒÖ‰vËKåØòU?áêe«-F@@‚`TpNœ*õŽérõÏj÷nqîèä“™éÍâ.n°Äéáb ÒOÃÉSòÁßþ}û½bÜcÝiÉÌc0柵¸ÃŽÜ S3÷Ѝ©/+—ß{„@Mg6y#€ € € ÐÕqhÅ*©;V&#~WÌÄÃÖ¢šægÅ«c™ƒ%"ª‡çŽMæÖÓ{²Ô¾”Øé‚òr@@@¸| ƒêBû˜´Tò·wË€¿ºMzðÈf"à3ŸKéo×ÉáW~'çNŸ±†Puû‰Ö†• € € € p¾kÎWì¤ï•>HâÇŒ’¸ìÝ¿¿Þ³8Ro;}NêJÊi ÔTíØ% '/Á‘È@@@BM€`MW·X°Ã™Ìí§¹«QW·ÇC@@@ಠ0ÁpW7™LØlL0Æ]Q$Ã{ZRcÖKé [í×Wt‡òZ½ô¦Ü"¢=¬¼°1Kp=lÌçòLý\Ö]ÌgIí®Í²û±UÖëŽ~\è{¿£|ÏoûÅÕ¡£cÅÍ}B®¾Cäã;Z҇Úv‚5¡Öb”@àÒ ¤æÊ5ógJL¬¶™7)OâWý³ìøS™ÿÆnû:6çNÉœ®þüU±/.¢¨½ì?škÛȤ£ímìvÑ«£$ó￯A4§ixSƒ5Ç:È4a¢\ó£¯ù¶s†Hì‹ok°&ø%uÚ7eØ—FY;ÔUm)9üÎÝ9åÔ;%½à&9WU-uuÚ$F—ñ2pæLÉ(~K>žúœèÎå¿€²575˹†kÏ@sÿDõé#ý³³¬íÍ皥¡ölPGÉ\±F†OÖ“K/xk‹H}•HtÊ I*È–Ä ñòfž¹àÍ–þ£³¥q—}&R c–ó\œ[2í Ó[Ø% êÐm$ŠÓ÷-¨x¦Wi³¾,I™´«ÖóA¤§¾‡’ î”a[WËŸf=î•òBžú×ÿBòðÚgÆdÌø²sQ×]K½n‚¨1óÕë°'kÑžQ±±­çБÿ·ELšÔ믵6ŦãÇÊñ>òª€ïÓä3eà4±Þs&Xãß‘N[dìzU6ݱ¸eçOHÞSÓÅ|åÐP²_êëš%RϽ”ѳ¥_ºÈ¦O·¤õz6fÝF8ÚiªåLq¹>ÆH¯ SdhîD‘­“5Øè•8ž^îòšápCòÿ¢U Æ¥ 6`‘g>—§èGIµ˜ƒÈ˜xéŸ{“ ºgŽ”üí4Ù£Sà´¿\Ø{¿ý<ÏoëÅסýãõ}ôÉ©–èö“½uä²'äì¼…r(è=:7!ÁšÎõ%w@n--c ý˶éè'rüÅßÈç%Õ/ñ£õÂä–r<„5†:ùÖë¥wVßþwÔ6'ì^#¾ß ¶Þ©£í­÷¸ˆ5 cdÌÂBéÓ×Í#˜ž-ƒd¬¨©Û²Z>ý÷-voš¨óù3(^F~÷íÆžèXšÚŠayR„Γçb¬ìîɲÓêùa—=sÙË2\/ÆozZƒ …N…‚,é±÷·K°!žÔu'OÉé#¥ÖEv¼Yï W¨ülŸÔWjÔ¥ƒ%ùI;PS0 1I’ó*œÖÊ®ùñ¹«È~s“^œ½¨4M$¸X“O6Íû4ð…Åžu™+Öi j¦Œ/|\¶¯ð¬¾€'~õ¿€¼wIšœ/©7Æwy°Æ ipí5VQª•HS½ZÓ^3Ç?øÐZozޘ󨩱A"£ìž›©7\+;?‘sõõÞÕhy PçßÖ­‘<»vî*y¹éý6Gn6š³{ä³¹wËAŸPY: .p¹á+Ö[šÓk!ïÌ[ÙR}§CŸNëçÁŸµÝÿE—þNòãHÊ%é&PÓÁP§`6&û#_ž,»ÝÏeý¾iål¼ôÙsã÷ýŽÞ}_vVÊõ¸ìÝh6/¾ö“$¥`ºT®^(Òa ìâLçóWJ0ù‘@H»û.ém5{_“m?ËSîšÆj©ùè-9Öê‹ÏL¥½pâ†ô“ÈæF©ßÿ±”üü7b¾‡4Kú7äÇåĶFIœ6A/øzè理fýïe×ú=v"ëg”dÜó-I=L»Ç7KcùçrâÅ_é;‰ÏA)ý}µ ü›[¥§NqÐðá+òá¿ï‘´›¿(É·Œ’žÉñ¥ßº×—|,¥ÏþF‡EÉð{î“cÌ|$ãG tÎZ9ñÔ³rÀºÆl¿ìæÈýrgÈð»ìr7k¹+ß¶ Ôd«ÍŸõ½%ë»ÿ éqVš†ý[äÀ/_—Jýöwì|ýzxÿ›²ãÅ­^ûÇ«ãw¤WÃnÙ®=€Ü¥oîW%ãKÃäìïWÈ'[½z3%äj>·JÓŸ×ÈÎ÷‡Io ÔÔkÐ¥:ašö®±/~Ü<=öU³ÞÚsªÑ2ܦIôâÉ,^ž¨þ¥/JßуµŽЍ‘ºíoIñ¿»Ã¤â¥Wz¢ÈÑ?Ë‘·eÐW2í<Âí§~û.îE>-žw·œ¬zY&Ìž¢…YÎE¡©tŒ[÷€$ŽNÖ^uRWüžO½ß/Ø0IƬzX’&èP Ý£©®B*–}_v®Ø+™ËVJÚälÓGÎÕÕËÙ’mrðއüö7Çéܥﰡžœúl¿|þÇ7Åô¸1K‚Þ½ÅÌ1b.ºâÓ[sØ45x3ž='9Éè™r´çI  [¤Üs=IÒ gHäÆýr6÷N¿`ºfR'9›ÖIDL›?CN:nRž|AFi 'Ê …¨:"'^Z*=UdT/ÜnX!ŸÝq¿Oϧ¸Â'dôÔry×SŽ:éYð„ä.½Iz%˜ï «¥jíJyÁ*;Ÿ¶~F;…p¶.•”}ÏHìø]S.ã×Ý+ÕËÞ¾‹æJBŠæ[µC>¾ñÛÖgbò#O˨™7i­í±U®Ç{VÞ÷ô|i©ùî-NîùzNýÀ>§4Xq惵²Õ¯ϰe/ȵ0¥2çÍé+ä˜L—Ì;ôÎKr“Ü´i½DÖí•]z.vEO°ØÔÏ|$fá@Ë)yÚCk¤Ï¦ÈQ; Åf糡½~mqpÞRI+ø¥Äædë^{%[Û"Få?­šVùì Kr÷ýX‡VÄèø«rdí‰Ì¼IFï['FÚ^ ä†Ý¿”¹ƒäìÆ"9¶~£œ­Š–ÞW óMþð‚qÒ¨ž£k‹äÔ®r‰Î+ýnq÷íüÇ”qW˘¯{zE˜#šá*n Æ¼®:ð¹T—!ñS Ä-·=?[âcÊåèêÕRöA¹õ>»áç<G§gé{õaIÏM–ªõ¯hë9òÏJß”j9µ¾HÊÖ¿§öôF¹c½ôsNŠL×scZ¡snl–ì3ûÌ›ºãß5‘éÙÒG‡”ºï¾4íqæ~ö”ègO —ô§~-c4în/münêsV?KìßîæsX?KjõO˜Ë½˜÷8  €W @/»Î '䔯ùÈñÝIb–Zt9)eÿøSÙoõà‰’Q?Z" 'Èàq¯JùÖã2D‡“õŸ6HJÖÛnÓÆXÕ9½þ-»ZîO ÄTUäé¾c$#a“_Š$^c‚PÇ¥âU½Â4‹w{M»?#ò÷ÿÑ?IŠö°q—ÚKæ3 ¾˜ïÃk>ú9øÌ9ö™]FÓñü¿Jtª ?ìtv±]_ª»¹†ãc‘T7KÚhûña«î•>æü«¦y. wÏÓ!û”akƒ3ž•!+Ð ‚jùüöÉò©WO£“½ái½°Þ&[gÜÙ°z§è…noç3@KÑ|îœN[Ûª< gÎxÖ™I‡û LÕž‘bæ'ñ_z™‰Y›Ë­óÉ[[¯M`CÔ㓼›äF½ýlÖ·[¾v®>N>y­öhrsÈÒ Ø+’W ¯Õ•õr®ÙcüŸuÑÖ|5M[WhŸg= MÐ,srØžõ´£g£ûÄŠ‚¸/Ì·îkÛ×ÉÑuæØùö\&ÑûåÃñ3¼>§Ñú~¯ÓÞòë9±^²&ÊY)Ÿ9YZõ×çæ|é£{õœ²Ãc"‡yYn¼'_rnÑÎ^#žÓ¡:1Rù«¯É¶§ö:{·<$M›*ƒ#¶Éû³·¬ì‚gѦž³Äôëç>•:? ôÅÔÉÌÛn㵯gÇ6ž¸NžÍ:õø_ü@/P«¥lE‘®Î·&Ÿm,?àIÌ“HÓ¦Yç©é qdŒ9Ǫ}Þó²à!Éß7[úOÕ¶Ú ÙlzT6]õ¨WN˘wÞ“Ôézb?µØ³¾½óÙ Ç4ï¬FŸsçq©º!n0 ð¨©Š%×z†âíYñCÉm†ŒX’/勊<Çêª'="¤îT•ì[÷†ŽÓI˃YÌgΡ7í®I&Ø×xölÀÝšJ¼Vç= =Ö2¤¹ø ùäÉ÷eàä2Xb¥îûb†c4ùÑåú9+Oxí˜%×ÎË—æ²mò¦ö~s—…ÏÉm‹nÒÞ®YÎ{«ƒ¶Ö/ÌGÓÑ»gø ™uókë±­:˜Ðj²‰ËèrfÝ÷eëƒEÖsócØ*ý{¦ƒß5Öd>Íæ¯³Ì‘Ì‚ ©[¿Tþ¤Ã­eÑ*ë33uÞeç†GÛýÝ$9É2ðµ;¥ò‘oŸWÝìuÎO‚5ãJ® €!"Ðlÿ†÷”6óïÿA’†x^ê_¡æNHë¥o† è·ÝµCeø”Lír¯ó8A€žCEþä^Ð×Jåï½.Ÿ~¬—SÚcE{v˜%íºÁÖ£™ïdèÍ·Hž Òog™0L{Èl±¶›§ž÷ÔØ«Oi ¦ïHýFS{ÑDhÙ#L%.Ut’]íqã,NÑôUTÇeßÚ[zéÐ"Ñ»#¹“Íg«·Ë ?È3OÛ_öov+&Y£vÑÖýî¿^z¤ö’ÒÿÜ$i·èÝ,¦L‘Øõÿ¡ßr„1z°†r|kK€Ë>@£}û $èP¨¾Ó2EV‹ŒÌýrZƒ[ï¿WÛ/÷Ö‰M5›E¿A>(eÏ­‘ñ’vÏ×%aÈõ2üÛµ—ÎÝÚ¨šã’ªs™ôÍÚSÉúëoà0ëÛZóå¿8îq™qú þ ¿ ü•rpã,íz?Öú†6at’4|°²U ÆVí:`}~óš'¤ø‘…Rº»ëe˶$õÕÕ:¸â= ÖL—¸©úbƒY㻘AÄUwÊÍïܤÏb$Æ£t1s›ìtÒ›»ÓÔ­õûœšazúT·ê­q¨p• Ó€^?ÓÓâ)++Ï„Ì$½ˆÜ('›³$qr’µþÄšÍRw^êGoÚøázâl ¨1‰­ ¿aBV&ü£átK˜£fb šãǵ“ÏiI?NÏ­>m–À;Øf"φzíÙ2]nÛ7ݳFô"öè’ûe×û'2z×öŽŸ6Ué…yD’Ï9h¯H=ãšKÞó{Ï›3['7·œ×^cf±~y·Kç³}®®´öwœ,>"CRìWÉãMõnXúk61o’µ²~Ó›RUõe½“ÕùÕßÍÿbÍäÕŸ²²I¥çwK³~ñàî×Þ.ƒ^^/Iúñ™l “*—hêBk—²’/Kªö<’§ì×Ù…×Yïóðùõ=Q‡@FÈ©'—újÅýR1ï=IÈ5çÖ^íMÛA[oÐ^/:ôpào׋¼ø¬ì|j­o~m¼j¯ö.Gä W Æ¬ æwÏá¦Ú¤²w¶é|Kæï.]ʶH…ö ŠË´?5ÛûÝ‘â|§ë~^ï+Ÿctñ ‚5] Îá@º€¹0Šh‰jX+_ýŠö~Ñ‹’„«%iÒ0½0«Õõ¤§}ý }¾4S¿éñ]¢Ó½{_øn3ÓÞ™xŠýg‚ö¢h_ðD]s« ¼Æ/­ùc_Ã@öR«wªñÝ;ò‹’uï­íÕ;ÄNÑè›P_µ¬ ¢ì‰µÖp§¦Š#¾ùèÌ[« HßTVŸUÚ…Øì3f”^¾-•‡îÒØÕ’–!Å©·è…œÎÓ³KçïðÙÉ~qjý›Rÿ%íÑ’{‹Ä®þLR=½pìoìäªãRú𳞀Oå¯Èµ¦çÌ@óÇýI½ó›2ä/Gµªks·e‡ºŒÉjË+¤A/Í-‚/åÒ[/”šŠ«5Ëë@'KÝZÑ*ûZs× èx=×ó­4ïìh•Ƭ(7CÓ¬” ÈqôkÓ%K‡M}òQ¿9îzÉVÖê=óïlÅ)6}Š•ïÐÛò46û¶T>¬ÃûÈà›o··Äi=§KÞz»ýã›a0ÚKÁ|b¸Ëî¿/§uÞ‡F’{Çd¸ÏÅ“›ê<ë‰ö÷Ò+»–æM›õ¢+_ƒ˜m-õ‰Ñá»´­õ˜U[èíŸÝ[·ìcÞëÞKr^¶:êatÀú\ê¥ÃL/–EÏ—ôýäË—÷ç·¬vžÕ\5Iç±I“f‹¦»-æýæYôÍ#¿|‡T}^"å;vJéæwdäÿù’˜‰‡Í{2¦o_é“–ª¨‘Ö.5e>=¹ù=Ñß ¬Ú;­4¦_'™‹î”¨]oXó?y'4çÙù,‘zÇ7ÿs6øýu_ïE{|ÜðÔ,}ïûž ÍsowñMÞFÒ–D)6'dÖkï·J[—`¶u¯¥^6Ÿky—ÄôÓÞXm ¥ Tr>«nÓç&R4P²i­ìY^äIzP‡¡¼ï&©k>ÕaCfxê™Õ«<Û=O¦Žµ‚r§‹[¿êÊ4oýìn{ñÞ¶J¶Î‰–‰OÝ«Çý±þûToÔ ÃuÒñÓmdÐQ¬ÝšëœÞ1n&Áü®qÓÚÉS³¬')‹^ÑÜ~K]–›Üîï&¿=ºÅK‚5Ý¢( €@× œµƒ =‡ÊÀÚ‹å3»•Ÿn“ÊOõyT£ô×`ýguƒ4™ÀIÏÃrèÁg¥Fç5Ñ."Ö«¡½àko±ó°Sؘ9õÜãR¢_ÄybE±½$¢V‡di2ï?‹ZòM‘ +PS+gÞøƒ~ë9UuN2¾«óߨŸ´$õyDÙc'Y{Döôÿ³ %äã“¥ÿ ÿIWú[nuûì ‡Ò×>–$æÕwÚDž”©{7HåkÛüsq^ï”Ó{kuøU¦¤ !±: «¹¦XŽî2î]ÈR&5:‹Olµß’ŸK¶©Uã„[ì@Mƒµzy”oÓaf)2vÙ?XP_ÈQ/Ç>µÇˤxíÿè¹Z/C¿ ó°¿Ý¾è²ä,–ýFö¬Î1#R¤ó«,öÌoà“·5gG½¶ýÓ©Yb›ŸÉfŸƒóæè?íþ~ï2â¾iÖœQÚmçŸ;ýED”ÕsAç¥É(øKJé…•sAí)€ÿkφ–'M&X‘-©SµÏÃg½Nž{È™@7ñÆdxKòKö¬GDt«¼úè0/ñnø°È'½ô¢×;lâ‘õ ûáò~ábïÕ>/ß®ïû‚A-Ÿm~{Øs¬x¯ÔóEcKQ½†.xoÖ癹zØ2âÈoëå{Y¹¿4êP–¨X»÷ž9oÌÄÔæ_å¾ý²Oßµ^A™Ñ=eä̿֡Q=¤Ú™)˜Ò›ß!ÍU:Üh­>[+ 9cåš™³õ®\O;Cê¬yeìÞEÁdi¥qÏÙA3ôœ½¨÷Þ$™¨ó˜ÄË~)Yò¬ìYa—!{Ã{2¸%Ît¹ÚKX¯½šµ·Ä›~wBŠÓÚ ´—_goÛÿúz9{â¤ç0cï#f‚é`L­þ•Ëy;ð§ŸZ#g4h’²l’|VV ïèr9´ ¨uâ {tãôÖëué!às$`b³rÓJÙvãJŽ—³¨Pç2š)7ÄK‘ÓÛÇ¿ŽêàŸÞ~½6ˆß5¾{ž5ŸÁZïᔾ)4`ÓÁï&ßô—ÿ•÷ß—¿4”@.¨–ÃëÍW~=%~î’Þî…@™=0ß—–Ä[úK¥ÎaS©C(Ì¿SÇÊõÂ>È †æpr› `ôÔItÇK“‡•×±2+ø¢Û\¬?ØkŽÈÎ?lsÒfk ç"¡Í½‚({Õ'Rk‚O:9rºW>ýr¯¶‚.-ß zmôz‘~µ´ÌÖ ãÌo¿ÚÚÚT]i=Ö|´^ÎhþQï’ä,-o¥ÞÁªD™·±Yÿ‰né-ýÿî«V°¤áÃ7ÏkI$éã2µ—’}ŒºcÇ5¿Lì™äY_¦Oi Äô²ÿpnÚ¿IŠ·š@n×áWæNa¡²ÔèùSüêëÝ=ê¬Ir?ß f:dîâ—IríÊí}qD;u:7QÌ´­6C'kï‡ý{ÌŸÊú~©˜Æ¿<åÏ/”­ãÒ‹€ÖÌÃÒ…‹æ4(÷úÖG ˜1sPô½jxë´^kN<ö¼øH}ì…V6^É?½¨ Ú¤VqŒ¾:TÍé3Íʺëãf˜Éeë¤É?ˆã&0ÞCX¼×·ó<¢¸B·²æÑðIV8Ëê•X½¶Ègµ;´+&7ßo}ËË“: uDÊuVï–µ~Ïœaz~k;õ¥þrøí­úžký)Ù÷ª tØ7Ö'm v>‹“ÃÚ«©Yoï}¡Kù‚…z—¶fIzp¥s®m‘k÷[C¥Ü }ƒÉÛœ³g4aò‚ 8g}£uÕ9Gô>n ÆLzlzLé)vI÷\á—kw ÔøñÂ_öjo×µ:QpµôÖù ®™–¥sÙù Ws÷µ{¶%êß;¾ËIÒvªy¿Èwu0¯v¯•ݳ¾,®/—Weµ?Ás»u|°Ž×øîwæƒ:çQ²¤Î÷]ßò*ÈßMí}&¶dÖ%ÏÖt 3A莧t•ª£ ;LýÓ2~Þly×eäÝ_•1:)°÷/É«ÿlõÄéó•ñßùªdNû¢Œº÷>¹vÙ„—VYkö¬Øl¥™¨“ç&ç˜Uú¦^$¤åilÙs2f¾ÞöÕJ™%C–ÌÒPN•«Ãºr1w~:úçíbæ9¨Ãï¼ñ¿>e8þþvùlÍ«R­w…:}ä¨5œ¥ýòÉ®'·iPa¢Ü¸{½ŒydVKÐFçñHÑ;?é}¹ö8)_w@·eHú\û¼³lœN»§Ûsç¶Iõj‹ìUë$E‡ªy_$›~iñz7¦1…Nþzkïku>˜æ’"ÙývàZµ?Nç¿¶yÓB9ªR÷½ï_eÌÜ|ks¢/oÑDí±9`=Ëõ|I˜(yëÜóEç#™:Grž|À2<±h~ÄËÐÝkô}eç9cŽ û²]Ÿ»J%⪉2Ì9×üËÔ™¯Oë-ß‹lzjo-wé7â*ö…|1ÔÊÝÕí>ºmߺ-öÊûKŠDúŽ“«—ÙAÎòKåd™Î¶uß+’»j±ó¾Óy?òòeØü9Î{ÕÿpEòñ¿¸çìÉ)Ì·äè…w¡úΰ­ý÷jëu¯ÜBû89ùú™ðOÒ×;ÌùÜVÆ^ëÝsaØö—=çBÜ-æój±¤Yíï•ø yZ¬ïŸsúþ1A—rªxÑ ŽôzL(”–9›¼9rÃöô3©\JõÆ Qâü§e¢ž_iyö¹—7K†NHÒî?î¯Ò{u°ªs#˜ß5Þ9›I®OèA}ïÛ(ãæçÛ›ô³wØ’Å’éœËíýn2CCÍ×r}gϱöëçSð}°ìêò@0¨Ö;(-”¡wß'nÉ”èì úÏ«zµUz+GÓ+C—’WåÓç´óÉ=y}õõ’tµ½º¹¦LÌo÷ —bÙ±h…ŒùîפÏQ:—Ë(gÏZ ±½‹Õ29¬£s½ô¾évkì¹9vՇ͒pM¼çèÇ~¿EúOçµ¹&OR®ÑPG­#ôö›•½äÅ'uòÁ:üh°$Íšiå׸÷ciʸZ¢Ü%Çs@}R·³XzŒÉÔýìàŽ)׉Ÿ?#¥^‰ŽéDÃu¢á(½™hå›G¼¶zªûx\úLÒ`ËÑí^“û¦­¯2ð½uÒgßõž {o®Ïm•÷Ö[Æß¯n×ßjÙ™ ЙÿùÙeMrü–ß2^N, Óï²2k:ú‰œn&qýòÖ—õGêp£Ë½œ9zLï@¢CŸê5è¿hÀÆ\DêU¤Î• "~éÍ}Ò‚gDÿìö,uzËè½óïס<žUz¢…²k|’dÏž.<žÖÉÉ—•÷Wìµ®yH'Ò}NFß3E®ñš[âÌêz©Ï¤ ›¬ùÜ\ëtþ]‹ŠÜ—]öX±k·œøä«7’9hïä$I½n¼4Öž•Ò­úÞÑeŸc0½p¼oëmmðãôòoËÿ?$7<6SÞó°õÏ;™ñ´ßåÚ£¥N·X·]ÖÇ zÇ­ª|õÿ¥Ü¶@{(¬}TöšÉ_5I潘ýZ.ÞŸ•]/]'×ø´…öúÓž(qÎP³õ¨=w‰ \¤·S_dçvNï óQÞBï¬}žGêÔèMUå>ë|_Øu”fçÔųé2PϧZ³˜ãí¾ñ~û…ÿOë|y¡Õù"•É1+í*Ù<'Mrõ¶ÃßE†;ûŸÛ÷ªüí)]·Mïs§ŒÐsÍô¸8zﵞI¤úpê³}VþC¿¯çJ¤çXCn›¬wë­ï¿I6T5oÉ©}<ÛÛ|bÚ¬¾ÚÓöÛBÍJ ·Hús$yÞZ½ÜÞ"ïßx·ä¬zF{Œi0_ïjÓ²ÔÉ‘­+åD€ÀÜé¥ß–wö/–qÝ)ƒé¾Îùaö=·k¸\³X‡Xé¹pÖÿ÷“¾öœœE²Wƒ>ã¾ãõ™ “i—m­“~)v"k2c}êÙÅ)œïùlVê.'€î$ÑUÎûÅZ¡ç½z.üÂ÷\0ó •¬^lýÞ1å —åœÖ«±¤ƒÚlX(§Ê¦IRü^)YãŸVÅO3_XBÊ:I/(”ÛôŸµÔ•Ë‘ù-wöë°­câ¥oîýw§Œöª\Êž\Ñæ0´ŽêÐÖ¹ÔïOì'å=$ã7è°°ûž‘ÛîkÙX-ER¼F?µù»i‡”nZ+'?x@M{P'ó~кãÖ›m íjɹsŸEü1cÂ…÷Áëܲ‘; €]*Ð/}5‡I£^„7W˜áMß75Eû…è¯ÏÚ“] œƒ½6VoÝÛü®ÓáTþ·µg”ôÓ;AE5T[·œÊN#UÇZ•¯Ã²k™’ôû\¿¥üXªÇLÕréÝ­ÊKt¶Bÿ%ýNûÎRû_“wŸzËë¥}/É{,‡ÝÆ R£A¸V½{¬ýâu2ØÖn—¶€—&·”ñc¥bç':GM€@ß!hâø{ÛýÖ^üKÓ§gBµT¯)jãuý†~jš"4àY$'Ü ~Û™uG@@@n'@°¦Û5 B@@@+Y€`Í•ÜúÔ@@@ºÁšn×$@@@®d‚5WrëSw@@@èvkº]“P @@@¸’Ö\É­OÝ@@@@ Û ¬évMB@@@@àJ Xs%·>uG@@@n'@°¦Û5 B@@@+Y€`Í•ÜúÔ@@@ºÁšn×$@@@®d‚5WrëSw@@@èvkº]“P @@@Ë)6í‹24=ªÃ"ô›vŸ\û÷¹¦ .A¼¤ßeJo0S:CÙó¾3mg¦3ÓiKáàù¼I§ï¼ïsùý>çü3ßœçyĘ‹Æõ£«!Q3zTÛÕÇY—Ήºx»m"Æ/\sïÿnLºjAÔ¦ã:ó‚˜ôïÆÜÛE?2£>ÝÛ—ëRí½}ù¾ú|RŒ\py bEŸ9?Κžˆ°æÄÆÓÞ @€ ðg&°õÞ»£aÉ–þuÕÚÒÇv³bÌåócp èu›Êêœ3/›{º#~wÍbçs«£yùS±ñžŸÄž=SbÒ¯‹Ì!z9w[/Ÿ¿ß>nˆ wß[ûÄŽÖÕðK/3óYk”Olt49ß @€ @€ÀŸµÀð~4¦þÝ‚\×ÞfÛúßÄ3w>ž~ç\uŒ˜Ú¾ŠåÀ¶Õ±ù{?­oµowÖ—¯¡;¶DÍEóâÔê¾-ñÆSÄ‹K^ŒÉ_¼.Fîy<ž_ÒpØnØü¸à;sâµYõ32öµÍˆÑçWVÒìŒí‹¿qù§bèÚ_Åîe¯¦Ïêcúׯ‘çTÎÝZR8óv]´­ÿu¼¼.u3bÖ ócèêÇo[ÿtüéÎGcWÍœ8ÿ_®ˆƒ">p×­)®y=šî¼#Ö7V7kÿqÊEñO‹7Z«WÍ‹9÷/ŒÍ¯ÅþÖúݲ2~wË 1ãö»âÌë/‰Æ;—uÚ±ýíq÷}÷šnÇêËwlÌX|ex©%†-˜Õ­;c×ÃÿkWT|"&.ü|L\0;jR¯U£m ]ƧòiåÕW½•ïG\ò·qúèÍñöÌ¿ŒÓ&ÔFìx!ÖÜô‹ØÝçøŒqW}*Z¼çí”O/Š ó§Uv¤¹òO‡çJÔœ³nüL ­¿òÚÝ{W×Gý9é÷©_‹9ó¥±}"žýÑòöïߣŸÂš÷Þi  @€ @ “ÀôOŹ×^ûW>k—<5“gÅðQ•0 &ߎúA¯Ä¶Ûî‹Ý­“âô/_gÜwm´^s_4¥-ÔNŠ¡ ÎŒ–ÿy$Ö<¶&êæ"Îøäâ¼ß›ÚbÒÇÓª‰%Ä®ŽÓM\xq n})6¼Qsg]uÍ éØ¿Š7‡´F㞈i£ÆÅ aÕä!ÎüÊu1bò«±qñbkLKÁÌÕ1´åéXÿàŠˆùFŒžµ;~¯,^û†ÍгnüdœýåÕñ̃+có^g_5*š|4ÞL‡kîÔ¤ZF\|a ÜýûxqE[œsëÂHÇïîMÇíôZý£§cî³Óýe–ªÿà×ÇÝw ¹_äutßASÎŒÚ){bçCÄö•Ûcd CÆ}îïcÊÊïņºùqúe³£å×??=×§Žþ`Œœ?2Z;‚´ƒµVþï«Þç—UFrTÇǵÉóÇ+#ïMAÍÑêƒ&ŒJ1Zûkâg¿æÕ§ZïŽÍ«jcÊ׿gÜñ¥hþæOӱΠ~xu Þö‡ØxóãÑ2hJŒžYMOm‰Óg|#†¬{"6/ßÑÒÏUUç<ÿ¹ êd¨:& @€“ÀôO_˜þH_ÏþlyìÚ³7šV­ˆ†ß¦Õ0Ó?Ã†ï‰ÆoÞ6¥?Þ×Ī[îŽ7㬘xIûme+«W*û>ÿèÊØÝ¶7¶.ûE¼òôΨ½ü¯cÿ-ý)d™0ó`9cc̼‘ÑòÄ“´ÄŽÅ¤co‰Æ—ÚWŠD¼D¨>N›:,ÞZñXlÝ“>Û³&Ö/}%íגΓ‡JžÓüB¬¼÷ñò¤Ú6­ˆ—½)jÆŸY=vÓ·Ät™Ô®u ±uUC º½†¥e@-{Ó‡cSÈ‘Þ>•Šî¯­›S5#cDK-N¬ïŽõ÷²å?¾-Ö­H=$ß—ùó$0¤ZsÔÖ§Õ6-ѼâÅØÕØ«–ÇꯆhÝ[é«Þê}}*ž­ ±®â¹=GïþŒÿƒIMçñóÆÅëwß”jÝÍ{bõ-?¶ºi1.]K6þŠÇàæÕñÂ-¿J—Mí]›^Œ†¥•9³%ÞÜÓ’VálˆÆukÚÏÛ½øwùwaÍ» ît @€ Ð]`RÔMH -íºª¤²Õˆ™“Òå0/Ɔ.»l‰×76Ç)÷ZÓ­ë;]æ”>m\¶:]¢42ꬉ]kÓ%<éÞ1Õ×ÌK£6¶Çöߦ  ¶6…)Û㵃ÙLû~î}i¥ÍÀÑ©†Ž×Øéé¯þCá@JPvTݕ.w§©\i“ˆr–Žö§c¥Ëª:^‡u\šsðƒÊÿ)ûâÍžj<Þ¾;ÿ辩Tã¾ÆÎT>ë8HãÒxmmŠ›¾W|ä†ÏÇY3ûxŠU_õVï들¶½Ò%Ô:z}š7%Ý( û§¯‹ó®¿6Î[œþ]eò¯¤@§MmÏ-æê¹ªeûCX“íÐ(Œ @€åHB¿µ£—~;½l‘>nÿƒüÐ÷­Ó„ˆ—ÓeU1õâêz§^>»z™Í¡+’Õô¦Dl]òt <ÿʘ{×éq×wÅø ÒJœ_\•S9mË¡"Ò›Ãetþ´ú~ZÝf¤§Emæ);ZðÑŽÇi×z‚TÝœÙ1°uoÒéíuœ}w>\?|{Úâ¥{oŠ5÷<Í-ãcÜ?~7æÜð‰Þùø]Þ÷^ou³n_W?ëG}Õí¤ð-­òyýÿbßúWbßKéßúçcçSOÄÎêÜjK÷JæïƒWïßû x% @€ @€ÀŸƒÀÞ8Ð\õ ÒMaîºBfתíéž(ããÉ8°¤xeÈèÚxû𢔴ʦ늎}0%{Ú³’MKãæy1æ’ù1hjºšé¶Î+x:¯9Ò²yOº©pºñð·MaDºÄjÝ1ÜÏ$Õ70…9§yØê'Í+–EËçÅ̯\+o êîZçܟ^m+Ïü,ݯåsÓbÿòŸtYmRÝ ãÇñ÷Ý~€þúv>gOïw¯K—Ý›.ãJ7MþÈ} ÓeG¦K֎ܲ¯z»¬JêØõ˜êkz-íUo-<ÖõpJjðùó"–¡Ü”YýüÍÅ7.ŒÙ÷_ÜñM jþížØÐ±(¦rƒÛØÝ£oü~ŒîØ¢míSéáJ/¥ßÚ×k4=¶<¦\veºYð²®÷,iíyeMûªú˜<¿D¤Ë’fΨyà°q1rÁå1üÇ‹c[ú¤ÇP¢ºeúѸ<öl›_½ŸËØT㎛oІî+>ŸŒ•‹_‰Y_¿2ξ£}UMûî-Ѳü‘xæán:Üqüí»½ó-GõtIÙN«˜¶Wýüì{Õéɇ_ûŸ{ìÈ>Ó×ý©÷HÏ~ÔwøÔÑpûÝ1è;‹ÒÓÂîŠ3~¾{u´üqMzד±ö?FÆ®úBÌ]Ðñeº¡ñÚ¯=MO¼£ÿኘ{ÿé2¹'ÒM£|TúÁýÿøï©î)¾çt @€ г@M}Œ}jz4ÒÎôT¨·ºl3|üØ”îiÒ´éàS›Ú¿žvíqÚÿþ ž[šVUŒ¯ìÛ”öí²kÄä¥G`_Û®¹¥ÛÍŠ»m×é×á±(νjH¬½æž.Ížzýí1ºõ±ô}çË©:íØím¥îé&Á»*÷¨éóU“ê—¶HO*:ê¶éãïpß½ùöYrõËš>~dulZÓØñÔ«Žô«Þ>NÖs}cã¼ýjì»-=ªSV7llºÙð¾x»¹òä®î\ÅyD:Ó›±?=«ýÐ ‡ÓÜ3aH´nK=tߥºNÆWVÖœ UÇ$@€ @€ãH†î-¨ØÝØ5¤é|‚éÒ[zÝwÚÓªœOô;¨©{@Ýô³­Û ˆ'Åi“EÛò •Múõê«î®hKõÃ=qÒÎïdßý¯³kÕ£þî{´z»¹óï=ŸcT5$ê~ææ=¯öqSæŠsSÇ¡;Ý)'ͽ¦ÊãÂ3x k2% @€ @€À TžÈÔÓS„Ò!'öº˜4ïôô®%^½ùØ.mÙµôñh¹tQœ}ÿí1eÛÎ80¨>§ǶâOKŽ-T9îzßõ$õÝû Oð›>ê=ö#Y·~5†Vn4ÝÜMVÕû±òÛÃePù‰Š @€ @àFÌœ§íx665yíÊðɳbÔôúh^¹"úqeQg“Ž_?¹>=B»%šWý>¶6v½<«ÇÞ…Ovßït }Õ{<çó¡‹bøè¶Ø±|eì:rèçÙì#¬Éf(B€ @€¨ÜŒÙ‹ @€ @ aM6C¡ @€ @€€•5æ @€ @ ++k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JÖ”>ôO€ @€d% ¬Éj8C€ @€”. ¬)}èŸ @€ÈJ@X“Õp(† @€(]@XSú Ð? @€ •€°&«áP  @€ Pº€°¦ô  @€ @ +aMVá @€ @ taMé3@ÿ @€ @€@Vš¬†C1 @€ @€@éšÒg€þ  @€ @€¬„5Y ‡b @€ @€Ò„5¥Ïý @€ @€Y k²Å @€ @€¥ kJŸú'@€ @€²Öd5Š!@€ @€JøWëÿðÏ¥#èŸ @€ ƒ@%§©¬¬iNonË¡ 5 @€ @€JèÈgšÿÓà®8J¡²IEND®B`‚pyramid-1.6/docs/narr/project.rst0000644000076500000240000012313512642137120017647 0ustar michaelstaff00000000000000.. _project_narr: Creating a :app:`Pyramid` Project ================================= As we saw in :ref:`firstapp_chapter`, it's possible to create a :app:`Pyramid` application completely manually. However, it's usually more convenient to use a :term:`scaffold` to generate a basic :app:`Pyramid` :term:`project`. A project is a directory that contains at least one Python :term:`package`. You'll use a scaffold to create a project, and you'll create your application logic within a package that lives inside the project. Even if your application is extremely simple, it is useful to place code that drives the application within a package, because (1) a package is more easily extended with new code, and (2) an application that lives inside a package can also be distributed more easily than one which does not live within a package. :app:`Pyramid` comes with a variety of scaffolds that you can use to generate a project. Each scaffold makes different configuration assumptions about what type of application you're trying to construct. These scaffolds are rendered using the ``pcreate`` command that is installed as part of Pyramid. .. index:: single: scaffolds single: starter scaffold single: zodb scaffold single: alchemy scaffold .. _additional_paster_scaffolds: Scaffolds Included with :app:`Pyramid` -------------------------------------- The convenience scaffolds included with :app:`Pyramid` differ from each other on a number of axes: - the persistence mechanism they offer (no persistence mechanism, :term:`ZODB`, or :term:`SQLAlchemy`) - the mechanism they use to map URLs to code (:term:`traversal` or :term:`URL dispatch`) The included scaffolds are these: ``starter`` URL mapping via :term:`URL dispatch` and no persistence mechanism ``zodb`` URL mapping via :term:`traversal` and persistence via :term:`ZODB` ``alchemy`` URL mapping via :term:`URL dispatch` and persistence via :term:`SQLAlchemy` .. index:: single: creating a project single: project single: pcreate .. _creating_a_project: Creating the Project -------------------- .. seealso:: See also the output of :ref:`pcreate --help `. In :ref:`installing_chapter`, you created a virtual Python environment via the ``virtualenv`` command. To start a :app:`Pyramid` :term:`project`, use the ``pcreate`` command installed within the virtualenv. We'll choose the ``starter`` scaffold for this purpose. When we invoke ``pcreate``, it will create a directory that represents our project. In :ref:`installing_chapter` we called the virtualenv directory ``env``. The following commands assume that our current working directory is the ``env`` directory. The below example uses the ``pcreate`` command to create a project with the ``starter`` scaffold. On UNIX: .. code-block:: text $ $VENV/bin/pcreate -s starter MyProject Or on Windows: .. code-block:: text > %VENV%\Scripts\pcreate -s starter MyProject Here's sample output from a run of ``pcreate`` on UNIX for a project we name ``MyProject``: .. code-block:: text $ $VENV/bin/pcreate -s starter MyProject Creating template pyramid Creating directory ./MyProject # ... more output ... Running /Users/chrism/projects/pyramid/bin/python setup.py egg_info As a result of invoking the ``pcreate`` command, a directory named ``MyProject`` is created. That directory is a :term:`project` directory. The ``setup.py`` file in that directory can be used to distribute your application, or install your application for deployment or development. A ``.ini`` file named ``development.ini`` will be created in the project directory. You will use this ``.ini`` file to configure a server, to run your application, and to debug your application. It contains configuration that enables an interactive debugger and settings optimized for development. Another ``.ini`` file named ``production.ini`` will also be created in the project directory. It contains configuration that disables any interactive debugger (to prevent inappropriate access and disclosure), and turns off a number of debugging settings. You can use this file to put your application into production. The ``MyProject`` project directory contains an additional subdirectory named ``myproject`` (note the case difference) representing a Python :term:`package` which holds very simple :app:`Pyramid` sample code. This is where you'll edit your application's Python code and templates. We created this project within an ``env`` virtualenv directory. However, note that this is not mandatory. The project directory can go more or less anywhere on your filesystem. You don't need to put it in a special "web server" directory, and you don't need to put it within a virtualenv directory. The author uses Linux mainly, and tends to put project directories which he creates within his ``~/projects`` directory. On Windows, it's a good idea to put project directories within a directory that contains no space characters, so it's wise to *avoid* a path that contains, i.e., ``My Documents``. As a result, the author, when he uses Windows, just puts his projects in ``C:\projects``. .. warning:: You'll need to avoid using ``pcreate`` to create a project with the same name as a Python standard library component. In particular, this means you should avoid using the names ``site`` or ``test``, both of which conflict with Python standard library packages. You should also avoid using the name ``pyramid``, which will conflict with Pyramid itself. .. index:: single: setup.py develop single: development install Installing your Newly Created Project for Development ----------------------------------------------------- To install a newly created project for development, you should ``cd`` to the newly created project directory and use the Python interpreter from the :term:`virtualenv` you created during :ref:`installing_chapter` to invoke the command ``python setup.py develop`` The file named ``setup.py`` will be in the root of the pcreate-generated project directory. The ``python`` you're invoking should be the one that lives in the ``bin`` (or ``Scripts`` on Windows) directory of your virtual Python environment. Your terminal's current working directory *must* be the newly created project directory. On UNIX: .. code-block:: text $ cd MyProject $ $VENV/bin/python setup.py develop Or on Windows: .. code-block:: text > cd MyProject > %VENV%\Scripts\python.exe setup.py develop Elided output from a run of this command on UNIX is shown below: .. code-block:: text $ cd MyProject $ $VENV/bin/python setup.py develop ... Finished processing dependencies for MyProject==0.0 This will install a :term:`distribution` representing your project into the virtual environment interpreter's library set so it can be found by ``import`` statements and by other console scripts such as ``pserve``, ``pshell``, ``proutes``, and ``pviews``. .. index:: single: running tests single: tests (running) Running the Tests for Your Application -------------------------------------- To run unit tests for your application, you should invoke them using the Python interpreter from the :term:`virtualenv` you created during :ref:`installing_chapter` (the ``python`` command that lives in the ``bin`` directory of your virtualenv). On UNIX: .. code-block:: text $ $VENV/bin/python setup.py test -q Or on Windows: .. code-block:: text > %VENV%\Scripts\python.exe setup.py test -q Here's sample output from a test run on UNIX: .. code-block:: text $ $VENV/bin/python setup.py test -q running test running egg_info writing requirements to MyProject.egg-info/requires.txt writing MyProject.egg-info/PKG-INFO writing top-level names to MyProject.egg-info/top_level.txt writing dependency_links to MyProject.egg-info/dependency_links.txt writing entry points to MyProject.egg-info/entry_points.txt reading manifest file 'MyProject.egg-info/SOURCES.txt' writing manifest file 'MyProject.egg-info/SOURCES.txt' running build_ext .. ---------------------------------------------------------------------- Ran 1 test in 0.108s OK The tests themselves are found in the ``tests.py`` module in your ``pcreate`` generated project. Within a project generated by the ``starter`` scaffold, a single sample test exists. .. note:: The ``-q`` option is passed to the ``setup.py test`` command to limit the output to a stream of dots. If you don't pass ``-q``, you'll see more verbose test result output (which normally isn't very useful). .. index:: single: running an application single: pserve single: reload single: startup .. _running_the_project_application: Running the Project Application ------------------------------- .. seealso:: See also the output of :ref:`pserve --help `. Once a project is installed for development, you can run the application it represents using the ``pserve`` command against the generated configuration file. In our case, this file is named ``development.ini``. On UNIX: .. code-block:: text $ $VENV/bin/pserve development.ini On Windows: .. code-block:: text > %VENV%\Scripts\pserve development.ini Here's sample output from a run of ``pserve`` on UNIX: .. code-block:: text $ $VENV/bin/pserve development.ini Starting server in PID 16601. serving on http://0.0.0.0:6543 When you use ``pserve`` to start the application implied by the default rendering of a scaffold, it will respond to requests on *all* IP addresses possessed by your system, not just requests to ``localhost``. This is what the ``0.0.0.0`` in ``serving on http://0.0.0.0:6543`` means. The server will respond to requests made to ``127.0.0.1`` and on any external IP address. For example, your system might be configured to have an external IP address ``192.168.1.50``. If that's the case, if you use a browser running on the same system as Pyramid, it will be able to access the application via ``http://127.0.0.1:6543/`` as well as via ``http://192.168.1.50:6543/``. However, *other people* on other computers on the same network will also be able to visit your Pyramid application in their browser by visiting ``http://192.168.1.50:6543/``. If you want to restrict access such that only a browser running on the same machine as Pyramid will be able to access your Pyramid application, edit the ``development.ini`` file, and replace the ``host`` value in the ``[server:main]`` section. Change it from ``0.0.0.0`` to ``127.0.0.1``. For example: .. code-block:: ini [server:main] use = egg:waitress#main host = 127.0.0.1 port = 6543 You can change the port on which the server runs on by changing the same portion of the ``development.ini`` file. For example, you can change the ``port = 6543`` line in the ``development.ini`` file's ``[server:main]`` section to ``port = 8080`` to run the server on port 8080 instead of port 6543. You can shut down a server started this way by pressing ``Ctrl-C`` (or ``Ctrl-Break`` on Windows). The default server used to run your Pyramid application when a project is created from a scaffold is named :term:`Waitress`. This server is what prints the ``serving on...`` line when you run ``pserve``. It's a good idea to use this server during development because it's very simple. It can also be used for light production. Setting your application up under a different server is not advised until you've done some development work under the default server, particularly if you're not yet experienced with Python web development. Python web server setup can be complex, and you should get some confidence that your application works in a default environment before trying to optimize it or make it "more like production". It's awfully easy to get sidetracked trying to set up a non-default server for hours without actually starting to do any development. One of the nice things about Python web servers is that they're largely interchangeable, so if your application works under the default server, it will almost certainly work under any other server in production if you eventually choose to use a different one. Don't worry about it right now. For more detailed information about the startup process, see :ref:`startup_chapter`. For more information about environment variables and configuration file settings that influence startup and runtime behavior, see :ref:`environment_chapter`. .. _reloading_code: Reloading Code ~~~~~~~~~~~~~~ During development, it's often useful to run ``pserve`` using its ``--reload`` option. When ``--reload`` is passed to ``pserve``, changes to any Python module your project uses will cause the server to restart. This typically makes development easier, as changes to Python code made within a :app:`Pyramid` application is not put into effect until the server restarts. For example, on UNIX: .. code-block:: text $ $VENV/bin/pserve development.ini --reload Starting subprocess with file monitor Starting server in PID 16601. serving on http://0.0.0.0:6543 Now if you make a change to any of your project's ``.py`` files or ``.ini`` files, you'll see the server restart automatically: .. code-block:: text development.ini changed; reloading... -------------------- Restarting -------------------- Starting server in PID 16602. serving on http://0.0.0.0:6543 Changes to template files (such as ``.pt`` or ``.mak`` files) won't cause the server to restart. Changes to template files don't require a server restart as long as the ``pyramid.reload_templates`` setting in the ``development.ini`` file is ``true``. Changes made to template files when this setting is true will take effect immediately without a server restart. .. index:: single: WSGI Viewing the Application ----------------------- Once your application is running via ``pserve``, you may visit ``http://localhost:6543/`` in your browser. You will see something in your browser like what is displayed in the following image: .. image:: project.png This is the page shown by default when you visit an unmodified ``pcreate`` generated ``starter`` application in a browser. .. index:: single: debug toolbar .. _debug_toolbar: The Debug Toolbar ~~~~~~~~~~~~~~~~~ .. image:: project-show-toolbar.png If you click on the :app:`Pyramid` logo at the top right of the page, a new target window will open to present a debug toolbar that provides various niceties while you're developing. This logo will float above every HTML page served by :app:`Pyramid` while you develop an application, and allows you to show the toolbar as necessary. .. image:: project-debug.png If you don't see the Pyramid logo on the top right of the page, it means you're browsing from a system that does not have debugging access. By default, for security reasons, only a browser originating from ``localhost`` (``127.0.0.1``) can see the debug toolbar. To allow your browser on a remote system to access the server, add a line within the ``[app:main]`` section of the ``development.ini`` file in the form ``debugtoolbar.hosts = X .X.X.X``. For example, if your Pyramid application is running on a remote system, and you're browsing from a host with the IP address ``192.168.1.1``, you'd add something like this to enable the toolbar when your system contacts Pyramid: .. code-block:: ini [app:main] # .. other settings ... debugtoolbar.hosts = 192.168.1.1 For more information about what the debug toolbar allows you to do, see `the documentation for pyramid_debugtoolbar `_. The debug toolbar will not be shown (and all debugging will be turned off) when you use the ``production.ini`` file instead of the ``development.ini`` ini file to run the application. You can also turn the debug toolbar off by editing ``development.ini`` and commenting out a line. For example, instead of: .. code-block:: ini :linenos: [app:main] ... pyramid.includes = pyramid_debugtoolbar Put a hash mark at the beginning of the ``pyramid_debugtoolbar`` line: .. code-block:: ini :linenos: [app:main] ... pyramid.includes = # pyramid_debugtoolbar Then restart the application to see that the toolbar has been turned off. Note that if you comment out the ``pyramid_debugtoolbar`` line, the ``#`` *must* be in the first column. If you put it anywhere else, and then attempt to restart the application, you'll receive an error that ends something like this: .. code-block:: text ImportError: No module named #pyramid_debugtoolbar .. index:: single: project structure The Project Structure --------------------- The ``starter`` scaffold generated a :term:`project` (named ``MyProject``), which contains a Python :term:`package`. The package is *also* named ``myproject``, but it's lowercased; the scaffold generates a project which contains a package that shares its name except for case. All :app:`Pyramid` ``pcreate``-generated projects share a similar structure. The ``MyProject`` project we've generated has the following directory structure: .. code-block:: text MyProject/ |-- CHANGES.txt |-- development.ini |-- MANIFEST.in |-- myproject | |-- __init__.py | |-- static | | |-- pyramid-16x16.png | | |-- pyramid.png | | |-- theme.css | | `-- theme.min.css | |-- templates | | `-- mytemplate.pt | |-- tests.py | `-- views.py |-- production.ini |-- README.txt `-- setup.py The ``MyProject`` :term:`Project` --------------------------------- The ``MyProject`` :term:`project` directory is the distribution and deployment wrapper for your application. It contains both the ``myproject`` :term:`package` representing your application as well as files used to describe, run, and test your application. #. ``CHANGES.txt`` describes the changes you've made to the application. It is conventionally written in :term:`ReStructuredText` format. #. ``README.txt`` describes the application in general. It is conventionally written in :term:`ReStructuredText` format. #. ``development.ini`` is a :term:`PasteDeploy` configuration file that can be used to execute your application during development. #. ``production.ini`` is a :term:`PasteDeploy` configuration file that can be used to execute your application in a production configuration. #. ``MANIFEST.in`` is a :term:`distutils` "manifest" file, naming which files should be included in a source distribution of the package when ``python setup.py sdist`` is run. #. ``setup.py`` is the file you'll use to test and distribute your application. It is a standard :term:`setuptools` ``setup.py`` file. .. index:: single: PasteDeploy single: ini file .. _MyProject_ini: ``development.ini`` ~~~~~~~~~~~~~~~~~~~ The ``development.ini`` file is a :term:`PasteDeploy` configuration file. Its purpose is to specify an application to run when you invoke ``pserve``, as well as the deployment settings provided to that application. The generated ``development.ini`` file looks like so: .. literalinclude:: MyProject/development.ini :language: ini :linenos: This file contains several sections including ``[app:main]``, ``[server:main]``, and several other sections related to logging configuration. The ``[app:main]`` section represents configuration for your :app:`Pyramid` application. The ``use`` setting is the only setting required to be present in the ``[app:main]`` section. Its default value, ``egg:MyProject``, indicates that our MyProject project contains the application that should be served. Other settings added to this section are passed as keyword arguments to the function named ``main`` in our package's ``__init__.py`` module. You can provide startup-time configuration parameters to your application by adding more settings to this section. .. seealso:: See :ref:`pastedeploy_entry_points` for more information about the meaning of the ``use = egg:MyProject`` value in this section. The ``pyramid.reload_templates`` setting in the ``[app:main]`` section is a :app:`Pyramid`-specific setting which is passed into the framework. If it exists, and its value is ``true``, supported template changes will not require an application restart to be detected. See :ref:`reload_templates_section` for more information. .. warning:: The ``pyramid.reload_templates`` option should be turned off for production applications, as template rendering is slowed when it is turned on. The ``pyramid.includes`` setting in the ``[app:main]`` section tells Pyramid to "include" configuration from another package. In this case, the line ``pyramid.includes = pyramid_debugtoolbar`` tells Pyramid to include configuration from the ``pyramid_debugtoolbar`` package. This turns on a debugging panel in development mode which can be opened by clicking on the :app:`Pyramid` logo on the top right of the screen. Including the debug toolbar will also make it possible to interactively debug exceptions when an error occurs. Various other settings may exist in this section having to do with debugging or influencing runtime behavior of a :app:`Pyramid` application. See :ref:`environment_chapter` for more information about these settings. The name ``main`` in ``[app:main]`` signifies that this is the default application run by ``pserve`` when it is invoked against this configuration file. The name ``main`` is a convention used by PasteDeploy signifying that it is the default application. The ``[server:main]`` section of the configuration file configures a WSGI server which listens on TCP port 6543. It is configured to listen on all interfaces (``0.0.0.0``). This means that any remote system which has TCP access to your system can see your Pyramid application. .. _MyProject_ini_logging: The sections that live between the markers ``# Begin logging configuration`` and ``# End logging configuration`` represent Python's standard library :mod:`logging` module configuration for your application. The sections between these two markers are passed to the `logging module's config file configuration engine `_ when the ``pserve`` or ``pshell`` commands are executed. The default configuration sends application logging output to the standard error output of your terminal. For more information about logging configuration, see :ref:`logging_chapter`. See the :term:`PasteDeploy` documentation for more information about other types of things you can put into this ``.ini`` file, such as other applications, :term:`middleware`, and alternate :term:`WSGI` server implementations. .. index:: single: production.ini ``production.ini`` ~~~~~~~~~~~~~~~~~~ The ``production.ini`` file is a :term:`PasteDeploy` configuration file with a purpose much like that of ``development.ini``. However, it disables the debug toolbar, and filters all log messages except those above the WARN level. It also turns off template development options such that templates are not automatically reloaded when changed, and turns off all debugging options. This file is appropriate to use instead of ``development.ini`` when you put your application into production. It's important to use ``production.ini`` (and *not* ``development.ini``) to benchmark your application and put it into production. ``development.ini`` configures your system with a debug toolbar that helps development, but the inclusion of this toolbar slows down page rendering times by over an order of magnitude. The debug toolbar is also a potential security risk if you have it configured incorrectly. .. index:: single: MANIFEST.in ``MANIFEST.in`` ~~~~~~~~~~~~~~~ The ``MANIFEST.in`` file is a :term:`distutils` configuration file which specifies the non-Python files that should be included when a :term:`distribution` of your Pyramid project is created when you run ``python setup.py sdist``. Due to the information contained in the default ``MANIFEST.in``, an sdist of your Pyramid project will include ``.txt`` files, ``.ini`` files, ``.rst`` files, graphics files, and template files, as well as ``.py`` files. See http://docs.python.org/distutils/sourcedist.html#the-manifest-in-template for more information about the syntax and usage of ``MANIFEST.in``. Without the presence of a ``MANIFEST.in`` file or without checking your source code into a version control repository, ``setup.py sdist`` places only *Python source files* (files ending with a ``.py`` extension) into tarballs generated by ``python setup.py sdist``. This means, for example, if your project was not checked into a setuptools-compatible source control system, and your project directory didn't contain a ``MANIFEST.in`` file that told the ``sdist`` machinery to include ``*.pt`` files, the ``myproject/templates/mytemplate.pt`` file would not be included in the generated tarball. Projects generated by Pyramid scaffolds include a default ``MANIFEST.in`` file. The ``MANIFEST.in`` file contains declarations which tell it to include files like ``*.pt``, ``*.css`` and ``*.js`` in the generated tarball. If you include files with extensions other than the files named in the project's ``MANIFEST.in`` and you don't make use of a setuptools-compatible version control system, you'll need to edit the ``MANIFEST.in`` file and include the statements necessary to include your new files. See http://docs.python.org/distutils/sourcedist.html#principle for more information about how to do this. You can also delete ``MANIFEST.in`` from your project and rely on a setuptools feature which simply causes all files checked into a version control system to be put into the generated tarball. To allow this to happen, check all the files that you'd like to be distributed along with your application's Python files into Subversion. After you do this, when you rerun ``setup.py sdist``, all files checked into the version control system will be included in the tarball. If you don't use Subversion, and instead use a different version control system, you may need to install a setuptools add-on such as ``setuptools-git`` or ``setuptools-hg`` for this behavior to work properly. .. index:: single: setup.py ``setup.py`` ~~~~~~~~~~~~ The ``setup.py`` file is a :term:`setuptools` setup file. It is meant to be run directly from the command line to perform a variety of functions, such as testing, packaging, and distributing your application. .. note:: ``setup.py`` is the de facto standard which Python developers use to distribute their reusable code. You can read more about ``setup.py`` files and their usage in the `Setuptools documentation `_ and `Python Packaging User Guide `_. Our generated ``setup.py`` looks like this: .. literalinclude:: MyProject/setup.py :language: python :linenos: The ``setup.py`` file calls the setuptools ``setup`` function, which does various things depending on the arguments passed to ``setup.py`` on the command line. Within the arguments to this function call, information about your application is kept. While it's beyond the scope of this documentation to explain everything about setuptools setup files, we'll provide a whirlwind tour of what exists in this file in this section. Your application's name can be any string; it is specified in the ``name`` field. The version number is specified in the ``version`` value. A short description is provided in the ``description`` field. The ``long_description`` is conventionally the content of the README and CHANGES file appended together. The ``classifiers`` field is a list of `Trove `_ classifiers describing your application. ``author`` and ``author_email`` are text fields which probably don't need any description. ``url`` is a field that should point at your application project's URL (if any). ``packages=find_packages()`` causes all packages within the project to be found when packaging the application. ``include_package_data`` will include non-Python files when the application is packaged if those files are checked into version control. ``zip_safe`` indicates that this package is not safe to use as a zipped egg; instead it will always unpack as a directory, which is more convenient. ``install_requires`` and ``tests_require`` indicate that this package depends on the ``pyramid`` package. ``test_suite`` points at the package for our application, which means all tests found in the package will be run when ``setup.py test`` is invoked. We examined ``entry_points`` in our discussion of the ``development.ini`` file; this file defines the ``main`` entry point that represents our project's application. Usually you only need to think about the contents of the ``setup.py`` file when distributing your application to other people, when adding Python package dependencies, or when versioning your application for your own use. For fun, you can try this command now: .. code-block:: text $ $VENV/bin/python setup.py sdist This will create a tarball of your application in a ``dist`` subdirectory named ``MyProject-0.1.tar.gz``. You can send this tarball to other people who want to install and use your application. .. index:: single: package The ``myproject`` :term:`Package` --------------------------------- The ``myproject`` :term:`package` lives inside the ``MyProject`` :term:`project`. It contains: #. An ``__init__.py`` file signifies that this is a Python :term:`package`. It also contains code that helps users run the application, including a ``main`` function which is used as a entry point for commands such as ``pserve``, ``pshell``, ``pviews``, and others. #. A ``templates`` directory, which contains :term:`Chameleon` (or other types of) templates. #. A ``tests.py`` module, which contains unit test code for the application. #. A ``views.py`` module, which contains view code for the application. These are purely conventions established by the scaffold. :app:`Pyramid` doesn't insist that you name things in any particular way. However, it's generally a good idea to follow Pyramid standards for naming, so that other Pyramid developers can get up to speed quickly on your code when you need help. .. index:: single: __init__.py .. _init_py: ``__init__.py`` ~~~~~~~~~~~~~~~ We need a small Python module that configures our application and which advertises an entry point for use by our :term:`PasteDeploy` ``.ini`` file. This is the file named ``__init__.py``. The presence of an ``__init__.py`` also informs Python that the directory which contains it is a *package*. .. literalinclude:: MyProject/myproject/__init__.py :language: python :linenos: #. Line 1 imports the :term:`Configurator` class from :mod:`pyramid.config` that we use later. #. Lines 4-12 define a function named ``main`` that returns a :app:`Pyramid` WSGI application. This function is meant to be called by the :term:`PasteDeploy` framework as a result of running ``pserve``. Within this function, application configuration is performed. Line 7 creates an instance of a :term:`Configurator`. Line 8 adds support for Chameleon templating bindings, allowing us to specify renderers with the ``.pt`` extension. Line 9 registers a static view, which will serve up the files from the ``myproject:static`` :term:`asset specification` (the ``static`` directory of the ``myproject`` package). Line 10 adds a :term:`route` to the configuration. This route is later used by a view in the ``views`` module. Line 11 calls ``config.scan()``, which picks up view registrations declared elsewhere in the package (in this case, in the ``views.py`` module). Line 12 returns a :term:`WSGI` application to the caller of the function (Pyramid's pserve). .. index:: single: views.py ``views.py`` ~~~~~~~~~~~~ Much of the heavy lifting in a :app:`Pyramid` application is done by *view callables*. A :term:`view callable` is the main tool of a :app:`Pyramid` web application developer; it is a bit of code which accepts a :term:`request` and which returns a :term:`response`. .. literalinclude:: MyProject/myproject/views.py :language: python :linenos: Lines 4-6 define and register a :term:`view callable` named ``my_view``. The function named ``my_view`` is decorated with a ``view_config`` decorator (which is processed by the ``config.scan()`` line in our ``__init__.py``). The view_config decorator asserts that this view be found when a :term:`route` named ``home`` is matched. In our case, because our ``__init__.py`` maps the route named ``home`` to the URL pattern ``/``, this route will match when a visitor visits the root URL. The view_config decorator also names a ``renderer``, which in this case is a template that will be used to render the result of the view callable. This particular view declaration points at ``templates/mytemplate.pt``, which is an :term:`asset specification` that specifies the ``mytemplate.pt`` file within the ``templates`` directory of the ``myproject`` package. The asset specification could have also been specified as ``myproject:templates/mytemplate.pt``; the leading package name and colon is optional. The template file pointed to is a :term:`Chameleon` ZPT template file (``templates/my_template.pt``). This view callable function is handed a single piece of information: the :term:`request`. The *request* is an instance of the :term:`WebOb` ``Request`` class representing the browser's request to our server. This view is configured to invoke a :term:`renderer` on a template. The dictionary the view returns (on line 6) provides the value the renderer substitutes into the template when generating HTML. The renderer then returns the HTML in a :term:`response`. .. note:: Dictionaries provide values to :term:`template`\s. .. note:: When the application is run with the scaffold's :ref:`default development.ini ` configuration, :ref:`logging is set up ` to aid debugging. If an exception is raised, uncaught tracebacks are displayed after the startup messages on :ref:`the console running the server `. Also ``print()`` statements may be inserted into the application for debugging to send output to this console. .. note:: ``development.ini`` has a setting that controls how templates are reloaded, ``pyramid.reload_templates``. - When set to ``True`` (as in the scaffold ``development.ini``), changed templates automatically reload without a server restart. This is convenient while developing, but slows template rendering speed. - When set to ``False`` (the default value), changing templates requires a server restart to reload them. Production applications should use ``pyramid.reload_templates = False``. .. seealso:: See also :ref:`views_which_use_a_renderer` for more information about how views, renderers, and templates relate and cooperate. .. seealso:: Pyramid can also dynamically reload changed Python files. See also :ref:`reloading_code`. .. seealso:: See also the :ref:`debug_toolbar`, which provides interactive access to your application's internals and, should an exception occur, allows interactive access to traceback execution stack frames from the Python interpreter. .. index:: single: static directory ``static`` ~~~~~~~~~~ This directory contains static assets which support the ``mytemplate.pt`` template. It includes CSS and images. ``templates/mytemplate.pt`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the single :term:`Chameleon` template that exists in the project. Its contents are too long to show here, but it displays a default page when rendered. It is referenced by the call to ``@view_config`` as the ``renderer`` of the ``my_view`` view callable in the ``views.py`` file. See :ref:`views_which_use_a_renderer` for more information about renderers. Templates are accessed and used by view configurations and sometimes by view functions themselves. See :ref:`templates_used_directly` and :ref:`templates_used_as_renderers`. .. index:: single: tests.py ``tests.py`` ~~~~~~~~~~~~ The ``tests.py`` module includes unit tests for your application. .. literalinclude:: MyProject/myproject/tests.py :language: python :lines: 1-18 :linenos: This sample ``tests.py`` file has a single unit test defined within it. This test is executed when you run ``python setup.py test``. You may add more tests here as you build your application. You are not required to write tests to use :app:`Pyramid`. This file is simply provided for convenience and example. See :ref:`testing_chapter` for more information about writing :app:`Pyramid` unit tests. .. index:: pair: modifying; package structure .. _modifying_package_structure: Modifying Package Structure --------------------------- It is best practice for your application's code layout to not stray too much from accepted Pyramid scaffold defaults. If you refrain from changing things very much, other Pyramid coders will be able to more quickly understand your application. However, the code layout choices made for you by a scaffold are in no way magical or required. Despite the choices made for you by any scaffold, you can decide to lay your code out any way you see fit. For example, the configuration method named :meth:`~pyramid.config.Configurator.add_view` requires you to pass a :term:`dotted Python name` or a direct object reference as the class or function to be used as a view. By default, the ``starter`` scaffold would have you add view functions to the ``views.py`` module in your package. However, you might be more comfortable creating a ``views`` *directory*, and adding a single file for each view. If your project package name was ``myproject`` and you wanted to arrange all your views in a Python subpackage within the ``myproject`` :term:`package` named ``views`` instead of within a single ``views.py`` file, you might do the following. - Create a ``views`` directory inside your ``myproject`` package directory (the same directory which holds ``views.py``). - Create a file within the new ``views`` directory named ``__init__.py``. (It can be empty. This just tells Python that the ``views`` directory is a *package*.) - *Move* the content from the existing ``views.py`` file to a file inside the new ``views`` directory named, say, ``blog.py``. Because the ``templates`` directory remains in the ``myproject`` package, the template :term:`asset specification` values in ``blog.py`` must now be fully qualified with the project's package name (``myproject:templates/blog.pt``). You can then continue to add view callable functions to the ``blog.py`` module, but you can also add other ``.py`` files which contain view callable functions to the ``views`` directory. As long as you use the ``@view_config`` directive to register views in conjunction with ``config.scan()``, they will be picked up automatically when the application is restarted. Using the Interactive Shell --------------------------- It is possible to use the ``pshell`` command to load a Python interpreter prompt with a similar configuration as would be loaded if you were running your Pyramid application via ``pserve``. This can be a useful debugging tool. See :ref:`interactive_shell` for more details. .. _what_is_this_pserve_thing: What Is This ``pserve`` Thing ----------------------------- The code generated by a :app:`Pyramid` scaffold assumes that you will be using the ``pserve`` command to start your application while you do development. ``pserve`` is a command that reads a :term:`PasteDeploy` ``.ini`` file (e.g., ``development.ini``), and configures a server to serve a :app:`Pyramid` application based on the data in the file. ``pserve`` is by no means the only way to start up and serve a :app:`Pyramid` application. As we saw in :ref:`firstapp_chapter`, ``pserve`` needn't be invoked at all to run a :app:`Pyramid` application. The use of ``pserve`` to run a :app:`Pyramid` application is purely conventional based on the output of its scaffolding. But we strongly recommend using ``pserve`` while developing your application because many other convenience introspection commands (such as ``pviews``, ``prequest``, ``proutes``, and others) are also implemented in terms of configuration availability of this ``.ini`` file format. It also configures Pyramid logging and provides the ``--reload`` switch for convenient restarting of the server when code changes. .. _alternate_wsgi_server: Using an Alternate WSGI Server ------------------------------ Pyramid scaffolds generate projects which use the :term:`Waitress` WSGI server. Waitress is a server that is suited for development and light production usage. It's not the fastest nor the most featureful WSGI server. Instead, its main feature is that it works on all platforms that Pyramid needs to run on, making it a good choice as a default server from the perspective of Pyramid's developers. Any WSGI server is capable of running a :app:`Pyramid` application. But we suggest you stick with the default server for development, and that you wait to investigate other server options until you're ready to deploy your application to production. Unless for some reason you need to develop on a non-local system, investigating alternate server options is usually a distraction until you're ready to deploy. But we recommend developing using the default configuration on a local system that you have complete control over; it will provide the best development experience. One popular production alternative to the default Waitress server is :term:`mod_wsgi`. You can use mod_wsgi to serve your :app:`Pyramid` application using the Apache web server rather than any "pure-Python" server like Waitress. It is fast and featureful. See :ref:`modwsgi_tutorial` for details. Another good production alternative is :term:`Green Unicorn` (aka ``gunicorn``). It's faster than Waitress and slightly easier to configure than mod_wsgi, although it depends, in its default configuration, on having a buffering HTTP proxy in front of it. It does not, as of this writing, work on Windows. pyramid-1.6/docs/narr/renderers.rst0000644000076500000240000006010012621241570020164 0ustar michaelstaff00000000000000.. _renderers_chapter: Renderers ========= A view callable needn't *always* return a :term:`Response` object. If a view happens to return something which does not implement the Pyramid Response interface, :app:`Pyramid` will attempt to use a :term:`renderer` to construct a response. For example: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='json') def hello_world(request): return {'content':'Hello!'} The above example returns a *dictionary* from the view callable. A dictionary does not implement the Pyramid response interface, so you might believe that this example would fail. However, since a ``renderer`` is associated with the view callable through its :term:`view configuration` (in this case, using a ``renderer`` argument passed to :func:`~pyramid.view.view_config`), if the view does *not* return a Response object, the renderer will attempt to convert the result of the view to a response on the developer's behalf. Of course, if no renderer is associated with a view's configuration, returning anything except an object which implements the Response interface will result in an error. And, if a renderer *is* used, whatever is returned by the view must be compatible with the particular kind of renderer used, or an error may occur during view invocation. One exception exists: it is *always* OK to return a Response object, even when a ``renderer`` is configured. In such cases, the renderer is bypassed entirely. Various types of renderers exist, including serialization renderers and renderers which use templating systems. .. index:: single: renderer single: view renderer .. _views_which_use_a_renderer: Writing View Callables Which Use a Renderer ------------------------------------------- As we've seen, a view callable needn't always return a Response object. Instead, it may return an arbitrary Python object, with the expectation that a :term:`renderer` will convert that object into a response instance on your behalf. Some renderers use a templating system, while other renderers use object serialization techniques. In practice, renderers obtain application data values from Python dictionaries so, in practice, view callables which use renderers return Python dictionaries. View callables can :ref:`explicitly call ` renderers, but typically don't. Instead view configuration declares the renderer used to render a view callable's results. This is done with the ``renderer`` attribute. For example, this call to :meth:`~pyramid.config.Configurator.add_view` associates the ``json`` renderer with a view callable: .. code-block:: python config.add_view('myproject.views.my_view', renderer='json') When this configuration is added to an application, the ``myproject.views.my_view`` view callable will now use a ``json`` renderer, which renders view return values to a :term:`JSON` response serialization. Pyramid defines several :ref:`built_in_renderers`, and additional renderers can be added by developers to the system as necessary. See :ref:`adding_and_overriding_renderers`. Views which use a renderer and return a non-Response value can vary non-body response attributes (such as headers and the HTTP status code) by attaching a property to the ``request.response`` attribute. See :ref:`request_response_attr`. As already mentioned, if the :term:`view callable` associated with a :term:`view configuration` returns a Response object (or its instance), any renderer associated with the view configuration is ignored, and the response is passed back to :app:`Pyramid` unchanged. For example: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config(renderer='json') def view(request): return Response('OK') # json renderer avoided Likewise for an :term:`HTTP exception` response: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config @view_config(renderer='json') def view(request): return HTTPFound(location='http://example.com') # json renderer avoided You can of course also return the ``request.response`` attribute instead to avoid rendering: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='json') def view(request): request.response.body = 'OK' return request.response # json renderer avoided .. index:: single: renderers (built-in) single: built-in renderers .. _built_in_renderers: Built-in Renderers ------------------ Several built-in renderers exist in :app:`Pyramid`. These renderers can be used in the ``renderer`` attribute of view configurations. .. note:: Bindings for officially supported templating languages can be found at :ref:`available_template_system_bindings`. .. index:: pair: renderer; string ``string``: String Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``string`` renderer renders a view callable result to a string. If a view callable returns a non-Response object, and the ``string`` renderer is associated in that view's configuration, the result will be to run the object through the Python ``str`` function to generate a string. Note that if a Unicode object is returned by the view callable, it is not ``str()``-ified. Here's an example of a view that returns a dictionary. If the ``string`` renderer is specified in the configuration for this view, the view will render the returned dictionary to the ``str()`` representation of the dictionary: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='string') def hello_world(request): return {'content':'Hello!'} The body of the response returned by such a view will be a string representing the ``str()`` serialization of the return value: .. code-block:: python {'content': 'Hello!'} Views which use the string renderer can vary non-body response attributes by using the API of the ``request.response`` attribute. See :ref:`request_response_attr`. .. index:: pair: renderer; JSON .. _json_renderer: JSON Renderer ~~~~~~~~~~~~~ The ``json`` renderer renders view callable results to :term:`JSON`. By default, it passes the return value through the ``json.dumps`` standard library function, and wraps the result in a response object. It also sets the response content-type to ``application/json``. Here's an example of a view that returns a dictionary. Since the ``json`` renderer is specified in the configuration for this view, the view will render the returned dictionary to a JSON serialization: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='json') def hello_world(request): return {'content':'Hello!'} The body of the response returned by such a view will be a string representing the JSON serialization of the return value: .. code-block:: python {"content": "Hello!"} The return value needn't be a dictionary, but the return value must contain values serializable by the configured serializer (by default ``json.dumps``). You can configure a view to use the JSON renderer by naming ``json`` as the ``renderer`` argument of a view configuration, e.g., by using :meth:`~pyramid.config.Configurator.add_view`: .. code-block:: python :linenos: config.add_view('myproject.views.hello_world', name='hello', context='myproject.resources.Hello', renderer='json') Views which use the JSON renderer can vary non-body response attributes by using the API of the ``request.response`` attribute. See :ref:`request_response_attr`. .. _json_serializing_custom_objects: Serializing Custom Objects ++++++++++++++++++++++++++ Some objects are not, by default, JSON-serializable (such as datetimes and other arbitrary Python objects). You can, however, register code that makes non-serializable objects serializable in two ways: - Define a ``__json__`` method on objects in your application. - For objects you don't "own", you can register a JSON renderer that knows about an *adapter* for that kind of object. Using a Custom ``__json__`` Method ********************************** Custom objects can be made easily JSON-serializable in Pyramid by defining a ``__json__`` method on the object's class. This method should return values natively JSON-serializable (such as ints, lists, dictionaries, strings, and so forth). It should accept a single additional argument, ``request``, which will be the active request object at render time. .. code-block:: python :linenos: from pyramid.view import view_config class MyObject(object): def __init__(self, x): self.x = x def __json__(self, request): return {'x':self.x} @view_config(renderer='json') def objects(request): return [MyObject(1), MyObject(2)] # the JSON value returned by ``objects`` will be: # [{"x": 1}, {"x": 2}] Using the ``add_adapter`` Method of a Custom JSON Renderer ********************************************************** If you aren't the author of the objects being serialized, it won't be possible (or at least not reasonable) to add a custom ``__json__`` method to their classes in order to influence serialization. If the object passed to the renderer is not a serializable type and has no ``__json__`` method, usually a :exc:`TypeError` will be raised during serialization. You can change this behavior by creating a custom JSON renderer and adding adapters to handle custom types. The renderer will attempt to adapt non-serializable objects using the registered adapters. A short example follows: .. code-block:: python :linenos: from pyramid.renderers import JSON if __name__ == '__main__': config = Configurator() json_renderer = JSON() def datetime_adapter(obj, request): return obj.isoformat() json_renderer.add_adapter(datetime.datetime, datetime_adapter) config.add_renderer('json', json_renderer) The ``add_adapter`` method should accept two arguments: the *class* of the object that you want this adapter to run for (in the example above, ``datetime.datetime``), and the adapter itself. The adapter should be a callable. It should accept two arguments: the object needing to be serialized and ``request``, which will be the current request object at render time. The adapter should raise a :exc:`TypeError` if it can't determine what to do with the object. See :class:`pyramid.renderers.JSON` and :ref:`adding_and_overriding_renderers` for more information. .. versionadded:: 1.4 Serializing custom objects. .. index:: pair: renderer; JSONP .. _jsonp_renderer: JSONP Renderer ~~~~~~~~~~~~~~ .. versionadded:: 1.1 :class:`pyramid.renderers.JSONP` is a `JSONP `_ renderer factory helper which implements a hybrid JSON/JSONP renderer. JSONP is useful for making cross-domain AJAX requests. Unlike other renderers, a JSONP renderer needs to be configured at startup time "by hand". Configure a JSONP renderer using the :meth:`pyramid.config.Configurator.add_renderer` method: .. code-block:: python from pyramid.config import Configurator from pyramid.renderers import JSONP config = Configurator() config.add_renderer('jsonp', JSONP(param_name='callback')) Once this renderer is registered via :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use ``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or :meth:`pyramid.config.Configurator.add_view`: .. code-block:: python from pyramid.view import view_config @view_config(renderer='jsonp') def myview(request): return {'greeting':'Hello world'} When a view is called that uses a JSONP renderer: - If there is a parameter in the request's HTTP query string (aka ``request.GET``) that matches the ``param_name`` of the registered JSONP renderer (by default, ``callback``), the renderer will return a JSONP response. - If there is no callback parameter in the request's query string, the renderer will return a "plain" JSON response. Javscript library AJAX functionality will help you make JSONP requests. For example, JQuery has a `getJSON function `_, and has equivalent (but more complicated) functionality in its `ajax function `_. For example (JavaScript): .. code-block:: javascript var api_url = 'http://api.geonames.org/timezoneJSON' + '?lat=38.301733840000004' + '&lng=-77.45869621' + '&username=fred' + '&callback=?'; jqhxr = $.getJSON(api_url); The string ``callback=?`` above in the ``url`` param to the JQuery ``getJSON`` function indicates to jQuery that the query should be made as a JSONP request; the ``callback`` parameter will be automatically filled in for you and used. The same custom-object serialization scheme defined used for a "normal" JSON renderer in :ref:`json_serializing_custom_objects` can be used when passing values to a JSONP renderer too. .. index:: single: response headers (from a renderer) single: renderer response headers .. _request_response_attr: Varying Attributes of Rendered Responses ---------------------------------------- Before a response constructed by a :term:`renderer` is returned to :app:`Pyramid`, several attributes of the request are examined which have the potential to influence response behavior. View callables that don't directly return a response should use the API of the :class:`pyramid.response.Response` attribute, available as ``request.response`` during their execution, to influence associated response behavior. For example, if you need to change the response status from within a view callable that uses a renderer, assign the ``status`` attribute to the ``response`` attribute of the request before returning a result: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(name='gone', renderer='templates/gone.pt') def myview(request): request.response.status = '404 Not Found' return {'URL':request.URL} Note that mutations of ``request.response`` in views which return a Response object directly will have no effect unless the response object returned *is* ``request.response``. For example, the following example calls ``request.response.set_cookie``, but this call will have no effect because a different Response object is returned. .. code-block:: python :linenos: from pyramid.response import Response def view(request): request.response.set_cookie('abc', '123') # this has no effect return Response('OK') # because we're returning a different response If you mutate ``request.response`` and you'd like the mutations to have an effect, you must return ``request.response``: .. code-block:: python :linenos: def view(request): request.response.set_cookie('abc', '123') return request.response For more information on attributes of the request, see the API documentation in :ref:`request_module`. For more information on the API of ``request.response``, see :attr:`pyramid.request.Request.response`. .. _adding_and_overriding_renderers: Adding and Changing Renderers ----------------------------- New templating systems and serializers can be associated with :app:`Pyramid` renderer names. To this end, configuration declarations can be made which change an existing :term:`renderer factory`, and which add a new renderer factory. Renderers can be registered imperatively using the :meth:`pyramid.config.Configurator.add_renderer` API. For example, to add a renderer which renders views which have a ``renderer`` attribute that is a path that ends in ``.jinja2``: .. code-block:: python config.add_renderer('.jinja2', 'mypackage.MyJinja2Renderer') The first argument is the renderer name. The second argument is a reference to an implementation of a :term:`renderer factory` or a :term:`dotted Python name` referring to such an object. .. index:: pair: renderer; adding .. _adding_a_renderer: Adding a New Renderer ~~~~~~~~~~~~~~~~~~~~~ You may add a new renderer by creating and registering a :term:`renderer factory`. A renderer factory implementation should conform to the :class:`pyramid.interfaces.IRendererFactory` interface. It should be capable of creating an object that conforms to the :class:`pyramid.interfaces.IRenderer` interface. A typical class that follows this setup is as follows: .. code-block:: python :linenos: class RendererFactory: def __init__(self, info): """ Constructor: info will be an object having the following attributes: name (the renderer name), package (the package that was 'current' at the time the renderer was registered), type (the renderer type name), registry (the current application registry) and settings (the deployment settings dictionary). """ def __call__(self, value, system): """ Call the renderer implementation with the value and the system value passed in as arguments and return the result (a string or unicode object). The value is the return value of a view. The system value is a dictionary containing available system values (e.g., view, context, and request). """ The formal interface definition of the ``info`` object passed to a renderer factory constructor is available as :class:`pyramid.interfaces.IRendererInfo`. There are essentially two different kinds of renderer factories: - A renderer factory which expects to accept an :term:`asset specification`, or an absolute path, as the ``name`` attribute of the ``info`` object fed to its constructor. These renderer factories are registered with a ``name`` value that begins with a dot (``.``). These types of renderer factories usually relate to a file on the filesystem, such as a template. - A renderer factory which expects to accept a token that does not represent a filesystem path or an asset specification in the ``name`` attribute of the ``info`` object fed to its constructor. These renderer factories are registered with a ``name`` value that does not begin with a dot. These renderer factories are typically object serializers. .. sidebar:: Asset Specifications An asset specification is a colon-delimited identifier for an :term:`asset`. The colon separates a Python :term:`package` name from a package subpath. For example, the asset specification ``my.package:static/baz.css`` identifies the file named ``baz.css`` in the ``static`` subdirectory of the ``my.package`` Python :term:`package`. Here's an example of the registration of a simple renderer factory via :meth:`~pyramid.config.Configurator.add_renderer`, where ``config`` is an instance of :meth:`pyramid.config.Configurator`: .. code-block:: python config.add_renderer(name='amf', factory='my.package.MyAMFRenderer') Adding the above code to your application startup configuration will allow you to use the ``my.package.MyAMFRenderer`` renderer factory implementation in view configurations. Your application can use this renderer by specifying ``amf`` in the ``renderer`` attribute of a :term:`view configuration`: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='amf') def myview(request): return {'Hello':'world'} At startup time, when a :term:`view configuration` is encountered which has a ``name`` attribute that does not contain a dot, the full ``name`` value is used to construct a renderer from the associated renderer factory. In this case, the view configuration will create an instance of an ``MyAMFRenderer`` for each view configuration which includes ``amf`` as its renderer value. The ``name`` passed to the ``MyAMFRenderer`` constructor will always be ``amf``. Here's an example of the registration of a more complicated renderer factory, which expects to be passed a filesystem path: .. code-block:: python config.add_renderer(name='.jinja2', factory='my.package.MyJinja2Renderer') Adding the above code to your application startup will allow you to use the ``my.package.MyJinja2Renderer`` renderer factory implementation in view configurations by referring to any ``renderer`` which *ends in* ``.jinja2`` in the ``renderer`` attribute of a :term:`view configuration`: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='templates/mytemplate.jinja2') def myview(request): return {'Hello':'world'} When a :term:`view configuration` is encountered at startup time which has a ``name`` attribute that does contain a dot, the value of the name attribute is split on its final dot. The second element of the split is typically the filename extension. This extension is used to look up a renderer factory for the configured view. Then the value of ``renderer`` is passed to the factory to create a renderer for the view. In this case, the view configuration will create an instance of a ``MyJinja2Renderer`` for each view configuration which includes anything ending with ``.jinja2`` in its ``renderer`` value. The ``name`` passed to the ``MyJinja2Renderer`` constructor will be the full value that was set as ``renderer=`` in the view configuration. Adding a Default Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~ To associate a *default* renderer with *all* view configurations (even ones which do not possess a ``renderer`` attribute), pass ``None`` as the ``name`` attribute to the renderer tag: .. code-block:: python config.add_renderer(None, 'mypackage.json_renderer_factory') .. index:: pair: renderer; changing Changing an Existing Renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Pyramid supports overriding almost every aspect of its setup through its :ref:`Conflict Resolution ` mechanism. This means that, in most cases, overriding a renderer is as simple as using the :meth:`pyramid.config.Configurator.add_renderer` method to redefine the template extension. For example, if you would like to override the ``json`` renderer to specify a new renderer, you could do the following: .. code-block:: python json_renderer = pyramid.renderers.JSON() config.add_renderer('json', json_renderer) After doing this, any views registered with the ``json`` renderer will use the new renderer. .. index:: pair: renderer; overriding at runtime Overriding a Renderer at Runtime -------------------------------- .. warning:: This is an advanced feature, not typically used by "civilians". In some circumstances, it is necessary to instruct the system to ignore the static renderer declaration provided by the developer in view configuration, replacing the renderer with another *after a request starts*. For example, an "omnipresent" XML-RPC implementation that detects that the request is from an XML-RPC client might override a view configuration statement made by the user instructing the view to use a template renderer with one that uses an XML-RPC renderer. This renderer would produce an XML-RPC representation of the data returned by an arbitrary view callable. To use this feature, create a :class:`~pyramid.events.NewRequest` :term:`subscriber` which sniffs at the request data and which conditionally sets an ``override_renderer`` attribute on the request itself, which in turn is the *name* of a registered renderer. For example: .. code-block:: python :linenos: from pyramid.events import subscriber from pyramid.events import NewRequest @subscriber(NewRequest) def set_xmlrpc_params(event): request = event.request if (request.content_type == 'text/xml' and request.method == 'POST' and not 'soapaction' in request.headers and not 'x-pyramid-avoid-xmlrpc' in request.headers): params, method = parse_xmlrpc_request(request) request.xmlrpc_params, request.xmlrpc_method = params, method request.is_xmlrpc = True request.override_renderer = 'xmlrpc' return True The result of such a subscriber will be to replace any existing static renderer configured by the developer with a (notional, nonexistent) XML-RPC renderer, if the request appears to come from an XML-RPC client. pyramid-1.6/docs/narr/resources.rst0000644000076500000240000006533112621241570020220 0ustar michaelstaff00000000000000.. _resources_chapter: Resources ========= A :term:`resource` is an object that represents a "place" in a tree related to your application. Every :app:`Pyramid` application has at least one resource object: the :term:`root` resource. Even if you don't define a root resource manually, a default one is created for you. The root resource is the root of a :term:`resource tree`. A resource tree is a set of nested dictionary-like objects which you can use to represent your website's structure. In an application which uses :term:`traversal` to map URLs to code, the resource tree structure is used heavily to map each URL to a :term:`view callable`. When :term:`traversal` is used, :app:`Pyramid` will walk through the resource tree by traversing through its nested dictionary structure in order to find a :term:`context` resource. Once a context resource is found, the context resource and data in the request will be used to find a :term:`view callable`. In an application which uses :term:`URL dispatch`, the resource tree is only used indirectly, and is often "invisible" to the developer. In URL dispatch applications, the resource "tree" is often composed of only the root resource by itself. This root resource sometimes has security declarations attached to it, but is not required to have any. In general, the resource tree is much less important in applications that use URL dispatch than applications that use traversal. In "Zope-like" :app:`Pyramid` applications, resource objects also often store data persistently, and offer methods related to mutating that persistent data. In these kinds of applications, resources not only represent the site structure of your website, but they become the :term:`domain model` of the application. Also: - The ``context`` and ``containment`` predicate arguments to :meth:`~pyramid.config.Configurator.add_view` (or a :func:`~pyramid.view.view_config` decorator) reference a resource class or resource :term:`interface`. - A :term:`root factory` returns a resource. - A resource is exposed to :term:`view` code as the :term:`context` of a view. - Various helpful :app:`Pyramid` API methods expect a resource as an argument (e.g., :meth:`~pyramid.request.Request.resource_url` and others). .. index:: single: resource tree single: traversal tree single: object tree single: container resources single: leaf resources Defining a Resource Tree ------------------------ When :term:`traversal` is used (as opposed to a purely :term:`URL dispatch` based application), :app:`Pyramid` expects to be able to traverse a tree composed of resources (the :term:`resource tree`). Traversal begins at a root resource, and descends into the tree recursively, trying each resource's ``__getitem__`` method to resolve a path segment to another resource object. :app:`Pyramid` imposes the following policy on resource instances in the tree: - A container resource (a resource which contains other resources) must supply a ``__getitem__`` method which is willing to resolve a Unicode name to a sub-resource. If a sub-resource by a particular name does not exist in a container resource, the ``__getitem__`` method of the container resource must raise a :exc:`KeyError`. If a sub-resource by that name *does* exist, the container's ``__getitem__`` should return the sub-resource. - Leaf resources, which do not contain other resources, must not implement a ``__getitem__``, or if they do, their ``__getitem__`` method must always raise a :exc:`KeyError`. See :ref:`traversal_chapter` for more information about how traversal works against resource instances. Here's a sample resource tree, represented by a variable named ``root``: .. code-block:: python :linenos: class Resource(dict): pass root = Resource({'a':Resource({'b':Resource({'c':Resource()})})}) The resource tree we've created above is represented by a dictionary-like root object which has a single child named ``'a'``. ``'a'`` has a single child named ``'b'``, and ``'b'`` has a single child named ``'c'``, which has no children. It is therefore possible to access the ``'c'`` leaf resource like so: .. code-block:: python :linenos: root['a']['b']['c'] If you returned the above ``root`` object from a :term:`root factory`, the path ``/a/b/c`` would find the ``'c'`` object in the resource tree as the result of :term:`traversal`. In this example, each of the resources in the tree is of the same class. This is not a requirement. Resource elements in the tree can be of any type. We used a single class to represent all resources in the tree for the sake of simplicity, but in a "real" app, the resources in the tree can be arbitrary. Although the example tree above can service a traversal, the resource instances in the above example are not aware of :term:`location`, so their utility in a "real" application is limited. To make best use of built-in :app:`Pyramid` API facilities, your resources should be "location-aware". The next section details how to make resources location-aware. .. index:: pair: location-aware; resource .. _location_aware: Location-Aware Resources ------------------------ In order for certain :app:`Pyramid` location, security, URL-generation, and traversal APIs to work properly against the resources in a resource tree, all resources in the tree must be :term:`location`-aware. This means they must have two attributes: ``__parent__`` and ``__name__``. The ``__parent__`` attribute of a location-aware resource should be a reference to the resource's parent resource instance in the tree. The ``__name__`` attribute should be the name with which a resource's parent refers to the resource via ``__getitem__``. The ``__parent__`` of the root resource should be ``None`` and its ``__name__`` should be the empty string. For instance: .. code-block:: python :linenos: class MyRootResource(object): __name__ = '' __parent__ = None A resource returned from the root resource's ``__getitem__`` method should have a ``__parent__`` attribute that is a reference to the root resource, and its ``__name__`` attribute should match the name by which it is reachable via the root resource's ``__getitem__``. A container resource within the root resource should have a ``__getitem__`` that returns resources with a ``__parent__`` attribute that points at the container, and these sub-objects should have a ``__name__`` attribute that matches the name by which they are retrieved from the container via ``__getitem__``. This pattern continues recursively "up" the tree from the root. The ``__parent__`` attributes of each resource form a linked list that points "downwards" toward the root. This is analogous to the ``..`` entry in filesystem directories. If you follow the ``__parent__`` values from any resource in the resource tree, you will eventually come to the root resource, just like if you keep executing the ``cd ..`` filesystem command, eventually you will reach the filesystem root directory. .. warning:: If your root resource has a ``__name__`` argument that is not ``None`` or the empty string, URLs returned by the :func:`~pyramid.request.Request.resource_url` function, and paths generated by the :func:`~pyramid.traversal.resource_path` and :func:`~pyramid.traversal.resource_path_tuple` APIs, will be generated improperly. The value of ``__name__`` will be prepended to every path and URL generated (as opposed to a single leading slash or empty tuple element). .. sidebar:: For your convenience If you'd rather not manage the ``__name__`` and ``__parent__`` attributes of your resources "by hand", an add-on package named :mod:`pyramid_traversalwrapper` can help. In order to use this helper feature, you must first install the :mod:`pyramid_traversalwrapper` package (available via PyPI), then register its ``ModelGraphTraverser`` as the traversal policy, rather than the default :app:`Pyramid` traverser. The package contains instructions for doing so. Once :app:`Pyramid` is configured with this feature, you will no longer need to manage the ``__parent__`` and ``__name__`` attributes on resource objects "by hand". Instead, as necessary during traversal, :app:`Pyramid` will wrap each resource (even the root resource) in a ``LocationProxy``, which will dynamically assign a ``__name__`` and a ``__parent__`` to the traversed resource, based on the last traversed resource and the name supplied to ``__getitem__``. The root resource will have a ``__name__`` attribute of ``None`` and a ``__parent__`` attribute of ``None``. Applications which use tree-walking :app:`Pyramid` APIs require location-aware resources. These APIs include (but are not limited to) :meth:`~pyramid.request.Request.resource_url`, :func:`~pyramid.traversal.find_resource`, :func:`~pyramid.traversal.find_root`, :func:`~pyramid.traversal.find_interface`, :func:`~pyramid.traversal.resource_path`, :func:`~pyramid.traversal.resource_path_tuple`, :func:`~pyramid.traversal.traverse`, :func:`~pyramid.traversal.virtual_root`, and (usually) :meth:`~pyramid.request.Request.has_permission` and :func:`~pyramid.security.principals_allowed_by_permission`. In general, since so much :app:`Pyramid` infrastructure depends on location-aware resources, it's a good idea to make each resource in your tree location-aware. .. index:: single: resource_url pair: generating; resource url .. _generating_the_url_of_a_resource: Generating the URL of a Resource -------------------------------- If your resources are :term:`location`-aware, you can use the :meth:`pyramid.request.Request.resource_url` API to generate a URL for the resource. This URL will use the resource's position in the parent tree to create a resource path, and it will prefix the path with the current application URL to form a fully-qualified URL with the scheme, host, port, and path. You can also pass extra arguments to :meth:`~pyramid.request.Request.resource_url` to influence the generated URL. The simplest call to :meth:`~pyramid.request.Request.resource_url` looks like this: .. code-block:: python :linenos: url = request.resource_url(resource) The ``request`` in the above example is an instance of a :app:`Pyramid` :term:`request` object. If the resource referred to as ``resource`` in the above example was the root resource, and the host that was used to contact the server was ``example.com``, the URL generated would be ``http://example.com/``. However, if the resource was a child of the root resource named ``a``, the generated URL would be ``http://example.com/a/``. A slash is appended to all resource URLs when :meth:`~pyramid.request.Request.resource_url` is used to generate them in this simple manner, because resources are "places" in the hierarchy, and URLs are meant to be clicked on to be visited. Relative URLs that you include on HTML pages rendered as the result of the default view of a resource are more apt to be relative to these resources than relative to their parent. You can also pass extra elements to :meth:`~pyramid.request.Request.resource_url`: .. code-block:: python :linenos: url = request.resource_url(resource, 'foo', 'bar') If the resource referred to as ``resource`` in the above example was the root resource, and the host that was used to contact the server was ``example.com``, the URL generated would be ``http://example.com/foo/bar``. Any number of extra elements can be passed to :meth:`~pyramid.request.Request.resource_url` as extra positional arguments. When extra elements are passed, they are appended to the resource's URL. A slash is not appended to the final segment when elements are passed. You can also pass a query string: .. code-block:: python :linenos: url = request.resource_url(resource, query={'a':'1'}) If the resource referred to as ``resource`` in the above example was the root resource, and the host that was used to contact the server was ``example.com``, the URL generated would be ``http://example.com/?a=1``. When a :term:`virtual root` is active, the URL generated by :meth:`~pyramid.request.Request.resource_url` for a resource may be "shorter" than its physical tree path. See :ref:`virtual_root_support` for more information about virtually rooting a resource. For more information about generating resource URLs, see the documentation for :meth:`pyramid.request.Request.resource_url`. .. index:: pair: resource URL generation; overriding .. _overriding_resource_url_generation: Overriding Resource URL Generation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If a resource object implements a ``__resource_url__`` method, this method will be called when :meth:`~pyramid.request.Request.resource_url` is called to generate a URL for the resource, overriding the default URL returned for the resource by :meth:`~pyramid.request.Request.resource_url`. The ``__resource_url__`` hook is passed two arguments: ``request`` and ``info``. ``request`` is the :term:`request` object passed to :meth:`~pyramid.request.Request.resource_url`. ``info`` is a dictionary with the following keys: ``physical_path`` A string representing the "physical path" computed for the resource, as defined by ``pyramid.traversal.resource_path(resource)``. It will begin and end with a slash. ``virtual_path`` A string representing the "virtual path" computed for the resource, as defined by :ref:`virtual_root_support`. This will be identical to the physical path if virtual rooting is not enabled. It will begin and end with a slash. ``app_url`` A string representing the application URL generated during ``request.resource_url``. It will not end with a slash. It represents a potentially customized URL prefix, containing potentially custom scheme, host and port information passed by the user to ``request.resource_url``. It should be preferred over use of ``request.application_url``. The ``__resource_url__`` method of a resource should return a string representing a URL. If it cannot override the default, it should return ``None``. If it returns ``None``, the default URL will be returned. Here's an example ``__resource_url__`` method. .. code-block:: python :linenos: class Resource(object): def __resource_url__(self, request, info): return info['app_url'] + info['virtual_path'] The above example actually just generates and returns the default URL, which would have been what was generated by the default ``resource_url`` machinery, but your code can perform arbitrary logic as necessary. For example, your code may wish to override the hostname or port number of the generated URL. Note that the URL generated by ``__resource_url__`` should be fully qualified, should end in a slash, and should not contain any query string or anchor elements (only path elements) to work with :meth:`~pyramid.request.Request.resource_url`. .. index:: single: resource path generation Generating the Path To a Resource --------------------------------- :func:`pyramid.traversal.resource_path` returns a string object representing the absolute physical path of the resource object based on its position in the resource tree. Each segment of the path is separated with a slash character. .. code-block:: python :linenos: from pyramid.traversal import resource_path url = resource_path(resource) If ``resource`` in the example above was accessible in the tree as ``root['a']['b']``, the above example would generate the string ``/a/b``. Any positional arguments passed in to :func:`~pyramid.traversal.resource_path` will be appended as path segments to the end of the resource path. .. code-block:: python :linenos: from pyramid.traversal import resource_path url = resource_path(resource, 'foo', 'bar') If ``resource`` in the example above was accessible in the tree as ``root['a']['b']``, the above example would generate the string ``/a/b/foo/bar``. The resource passed in must be :term:`location`-aware. The presence or absence of a :term:`virtual root` has no impact on the behavior of :func:`~pyramid.traversal.resource_path`. .. index:: pair: resource; finding by path Finding a Resource by Path -------------------------- If you have a string path to a resource, you can grab the resource from that place in the application's resource tree using :func:`pyramid.traversal.find_resource`. You can resolve an absolute path by passing a string prefixed with a ``/`` as the ``path`` argument: .. code-block:: python :linenos: from pyramid.traversal import find_resource url = find_resource(anyresource, '/path') Or you can resolve a path relative to the resource that you pass in to :func:`pyramid.traversal.find_resource` by passing a string that isn't prefixed by ``/``: .. code-block:: python :linenos: from pyramid.traversal import find_resource url = find_resource(anyresource, 'path') Often the paths you pass to :func:`~pyramid.traversal.find_resource` are generated by the :func:`~pyramid.traversal.resource_path` API. These APIs are "mirrors" of each other. If the path cannot be resolved when calling :func:`~pyramid.traversal.find_resource` (if the respective resource in the tree does not exist), a :exc:`KeyError` will be raised. See the :func:`pyramid.traversal.find_resource` documentation for more information about resolving a path to a resource. .. index:: pair: resource; lineage Obtaining the Lineage of a Resource ----------------------------------- :func:`pyramid.location.lineage` returns a generator representing the :term:`lineage` of the :term:`location`-aware :term:`resource` object. The :func:`~pyramid.location.lineage` function returns the resource that is passed into it, then each parent of the resource in order. For example, if the resource tree is composed like so: .. code-block:: python :linenos: class Thing(object): pass thing1 = Thing() thing2 = Thing() thing2.__parent__ = thing1 Calling ``lineage(thing2)`` will return a generator. When we turn it into a list, we will get: .. code-block:: python :linenos: list(lineage(thing2)) [ , ] The generator returned by :func:`~pyramid.location.lineage` first returns unconditionally the resource that was passed into it. Then, if the resource supplied a ``__parent__`` attribute, it returns the resource represented by ``resource.__parent__``. If *that* resource has a ``__parent__`` attribute, it will return that resource's parent, and so on, until the resource being inspected either has no ``__parent__`` attribute or has a ``__parent__`` attribute of ``None``. See the documentation for :func:`pyramid.location.lineage` for more information. Determining if a Resource is in the Lineage of Another Resource --------------------------------------------------------------- Use the :func:`pyramid.location.inside` function to determine if one resource is in the :term:`lineage` of another resource. For example, if the resource tree is: .. code-block:: python :linenos: class Thing(object): pass a = Thing() b = Thing() b.__parent__ = a Calling ``inside(b, a)`` will return ``True``, because ``b`` has a lineage that includes ``a``. However, calling ``inside(a, b)`` will return ``False`` because ``a`` does not have a lineage that includes ``b``. The argument list for :func:`~pyramid.location.inside` is ``(resource1, resource2)``. ``resource1`` is "inside" ``resource2`` if ``resource2`` is a :term:`lineage` ancestor of ``resource1``. It is a lineage ancestor if its parent (or one of its parent's parents, etc.) is an ancestor. See :func:`pyramid.location.inside` for more information. .. index:: pair: resource; finding root Finding the Root Resource ------------------------- Use the :func:`pyramid.traversal.find_root` API to find the :term:`root` resource. The root resource is the resource at the root of the :term:`resource tree`. The API accepts a single argument: ``resource``. This is a resource that is :term:`location`-aware. It can be any resource in the tree for which you want to find the root. For example, if the resource tree is: .. code-block:: python :linenos: class Thing(object): pass a = Thing() b = Thing() b.__parent__ = a Calling ``find_root(b)`` will return ``a``. The root resource is also available as ``request.root`` within :term:`view callable` code. The presence or absence of a :term:`virtual root` has no impact on the behavior of :func:`~pyramid.traversal.find_root`. The root object returned is always the *physical* root object. .. index:: single: resource interfaces .. _resources_which_implement_interfaces: Resources Which Implement Interfaces ------------------------------------ Resources can optionally be made to implement an :term:`interface`. An interface is used to tag a resource object with a "type" that later can be referred to within :term:`view configuration` and by :func:`pyramid.traversal.find_interface`. Specifying an interface instead of a class as the ``context`` or ``containment`` predicate arguments within :term:`view configuration` statements makes it possible to use a single view callable for more than one class of resource objects. If your application is simple enough that you see no reason to want to do this, you can skip reading this section of the chapter. For example, here's some code which describes a blog entry which also declares that the blog entry implements an :term:`interface`. .. code-block:: python :linenos: import datetime from zope.interface import implementer from zope.interface import Interface class IBlogEntry(Interface): pass @implementer(IBlogEntry) class BlogEntry(object): def __init__(self, title, body, author): self.title = title self.body = body self.author = author self.created = datetime.datetime.now() This resource consists of two things: the class which defines the resource constructor as the class ``BlogEntry``, and an :term:`interface` attached to the class via an ``implementer`` class decorator using the ``IBlogEntry`` interface as its sole argument. The interface object used must be an instance of a class that inherits from :class:`zope.interface.Interface`. A resource class may implement zero or more interfaces. You specify that a resource implements an interface by using the :func:`zope.interface.implementer` function as a class decorator. The above ``BlogEntry`` resource implements the ``IBlogEntry`` interface. You can also specify that a particular resource *instance* provides an interface as opposed to its class. When you declare that a class implements an interface, all instances of that class will also provide that interface. However, you can also just say that a single object provides the interface. To do so, use the :func:`zope.interface.directlyProvides` function: .. code-block:: python :linenos: import datetime from zope.interface import directlyProvides from zope.interface import Interface class IBlogEntry(Interface): pass class BlogEntry(object): def __init__(self, title, body, author): self.title = title self.body = body self.author = author self.created = datetime.datetime.now() entry = BlogEntry('title', 'body', 'author') directlyProvides(entry, IBlogEntry) :func:`zope.interface.directlyProvides` will replace any existing interface that was previously provided by an instance. If a resource object already has instance-level interface declarations that you don't want to replace, use the :func:`zope.interface.alsoProvides` function: .. code-block:: python :linenos: import datetime from zope.interface import alsoProvides from zope.interface import directlyProvides from zope.interface import Interface class IBlogEntry1(Interface): pass class IBlogEntry2(Interface): pass class BlogEntry(object): def __init__(self, title, body, author): self.title = title self.body = body self.author = author self.created = datetime.datetime.now() entry = BlogEntry('title', 'body', 'author') directlyProvides(entry, IBlogEntry1) alsoProvides(entry, IBlogEntry2) :func:`zope.interface.alsoProvides` will augment the set of interfaces directly provided by an instance instead of overwriting them like :func:`zope.interface.directlyProvides` does. For more information about how resource interfaces can be used by view configuration, see :ref:`using_resource_interfaces`. .. index:: pair: resource; finding by interface or class Finding a Resource with a Class or Interface in Lineage ------------------------------------------------------- Use the :func:`~pyramid.traversal.find_interface` API to locate a parent that is of a particular Python class, or which implements some :term:`interface`. For example, if your resource tree is composed as follows: .. code-block:: python :linenos: class Thing1(object): pass class Thing2(object): pass a = Thing1() b = Thing2() b.__parent__ = a Calling ``find_interface(a, Thing1)`` will return the ``a`` resource because ``a`` is of class ``Thing1`` (the resource passed as the first argument is considered first, and is returned if the class or interface specification matches). Calling ``find_interface(b, Thing1)`` will return the ``a`` resource because ``a`` is of class ``Thing1`` and ``a`` is the first resource in ``b``'s lineage of this class. Calling ``find_interface(b, Thing2)`` will return the ``b`` resource. The second argument to ``find_interface`` may also be a :term:`interface` instead of a class. If it is an interface, each resource in the lineage is checked to see if the resource implements the specificed interface (instead of seeing if the resource is of a class). .. seealso:: See also :ref:`resources_which_implement_interfaces`. .. index:: single: resource API functions single: url generation (traversal) :app:`Pyramid` API Functions That Act Against Resources ------------------------------------------------------- A resource object is used as the :term:`context` provided to a view. See :ref:`traversal_chapter` and :ref:`urldispatch_chapter` for more information about how a resource object becomes the context. The APIs provided by :ref:`traversal_module` are used against resource objects. These functions can be used to find the "path" of a resource, the root resource in a resource tree, or to generate a URL for a resource. The APIs provided by :ref:`location_module` are used against resources. These can be used to walk down a resource tree, or conveniently locate one resource "inside" another. Some APIs on the :class:`pyramid.request.Request` accept a resource object as a parameter. For example, the :meth:`~pyramid.request.Request.has_permission` API accepts a resource object as one of its arguments; the ACL is obtained from this resource or one of its ancestors. Other security related APIs on the :class:`pyramid.request.Request` class also accept :term:`context` as an argument, and a context is always a resource. pyramid-1.6/docs/narr/resourcetreetraverser.png0000644000076500000240000070545112234375161022637 0ustar michaelstaff00000000000000‰PNG  IHDR½eñŠ IDATxœìw|ÕÚ€ŸÙÝôž@ $!„Þ;RE Å *6ì lWñН"VDª•N¢ˆ”@è„@z¯›ìîÌùþØ’ÝM€ "Äï<ü–Ý™9gÎ;“dö¼çm ‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$‰D"‘H$É¿åJ 9?Bˆº@4à‡üYI$‰D"ùÿGª(Jú•Drqädõ*D< Z^WV"‰D"‘H$’+‚EqøIQ”ÙŠ¢¤]i$çG*WBˆX`ÐÿJË"‘H$‰Dr±˜ (Êá+-ˆ¤z¤bq!„ðB¬TeЕ–E"‘H$‰ä*d0XQ”Ü+-ˆ¤*º+-€Ä…!R©H$‰D"9/]¯´’ꑊÅU„b•–A"‘H$‰ä*g¨"àJ !©ŠT,®„^@ã+-‡D"‘H$ÉÕŒ"»ÒrHª"‹«EQdö'‰D"‘H$’  (Š2cæU‰T,$‰D"‘H$É_Æp¥\>4M£¤¤”òŠr4UE\ay =Þ>>øûù]ai$‰D"‘H$'R±ø—a6›9wî{öîãä©SQ^^ŽªªaS-I†nêF•ÄÎ;Då›bwëà|>ç6B Stè z|}}©F«–-èØ¡=uêÔA¯—Æ3‰D"‘H$’ÚŒT,þ%TTTðûÞ}ìøe'‡’’(.)AUUEAQ¬öâüYTÓÈ ÅÂýXuŠ ¬¬ Žìܵ›àà`Ú·kKÿ~}hÕ²¥MV‰D"‘H$ImCÎ⮄aÀ. é¥öMMMeñ’åìÙ»—òò t:6]Âiòï2Ë· ŠÍš`;Ñù”Gcªi(\”ûéªÍñA±}°+BÐï:FÝ2‚€ÿK½‰D"‘Hþÿ\«(ÊÑ+-ˆÄi±¨åœ=wŽÏæ}ÎÁƒ‰è zt:«K‘€æn=6ï§ÊýŠ»õ®(¸dTg™°ŸÓ©pÒY¬ŠªÚŠps¿R…¢¢"Öüø3¥eeŒ½ã6‚ƒ‚jrù‰D"‘H$’«éØ^‹),,d鲕$:„^¯Bh¶—pèËU'õ•íí¯ªaÖv•/„fÓA´ªc8 Ó»p¼ÜÎŒEQP-â¶ñóº ””–üÍwK"‘H$‰Dr9‘‹Zб¼œÕk$aûÎE•ÿQÕäàŒ³ƒ’b‹|°Û(ŠMp¾¨¤Òqáq„[ËÊñªžÍ.@yy9kü ƒžáÃnÆÓÓó×!‘H$‰D"¹ZŠE-DUU6mÚÌÚס©t:]UK€““  „bW(œÚ:eŠö~•¦ §vTêö6޶nÇ\Îmoë|¢ó¡ (:ŠŠKX±b5áááôíÓûb·C"‘H$‰Dr ‹ZÈÞ}ûY½ö'JJK1èthšvž–®Ê…öˆ§ {qaûƒâ@Qmam#ÐpµˆØW“¶ö|c)PX\Ìš¦m›6„††\°½D"‘H$‰äÊ#c,jf³™mÛ¶“––†ÐÜc$lÿÀ9~BEªÓ{e{ͶOšõ\šó¹l/G;·6šëØš¦Ú "šm|«ÕÃ.CåË=®Ã:ŽÝú!4Nœëƒ;UJRØÐ\iBuݶçî!åvnÍ%ÖJKËØ¿ÿ 7ÝxÕI*‘H$‰D"¹JŠE-ãÜÙsäå廄N;Vœ‚£+çïné^/pþêªUàö®¸í¬®M¥ln}ªŒ_:î*®(‚s©©äæåS¿^ø¤–H$‰D"‘\i¤bQË8vüf³ÙÙá g›8¯ÚàWQÝުͅ˦pTÅvŒ+WÁÍFRåâB‘Uö*:ÒÒ2ÈÍÍ•Š…ä_‰Â%FJQÇK"‘H$’Ú†T,j©ééX,*:½SÕë ¤˜­&ŒºÆqÔÅ@ƒŽ “†N©<¨wÕF¸è9.VŪ\¸Yi¥pU|ì[Fc)999çR"©Åœ8q‚Ï?ÿœ’’TU¥cÇŽÜzë­„……¹´+((à§Ÿ~âܹsøøøÐ¦M¹õÖ[ÉÎÎf×®]Œ7Ž€€€+t%‰D"‘HÅ¢ÖQ\TˆªZ½mrïj³pÆš£ÉÙ9ÊŠPt.ifJZ8ö[w+¨šF‹&õ¸¶C ¿í;Eâ) ½N±+ì)lí'v÷¨F.7#‡p?îrÄÈ]TTtñó'B`2™¨¨¨Àb±`4),,¤¸¸˜òòrÌf3:///BCC©S§¾¾¾øøø8*œKþ}”””PRRB@@~~~—u¬””þ÷¿ÿQRb-yÓM71pàÀ*Š…——õë×gÕªUôêÕ £ÑÈÿû_<==9|ø0Û·ogèСR±H$ÉE*µ “É„ÐT„ÎZxB³Å&TFQTºD §°v¬ºƒVÙÒÕßÉ壦iøùzrË iGt …s™:òK:M)°¥°µ÷ªLh«¸¹@ ‡ÞâÜÃE.§h‹ÊÌV`6™ÿôýºEEE|øá‡ìرƒ¬¬,ŠŠŠ0™LX,4MsÔÑét BBBhß¾=½zõ¢W¯^4iÒŸË"›äŸGÓ4öîÝˬY³øã?èÕ«Ï<ó ­Zµºl®IŠ¢¸ôðð¨v,®¹æâããéÖ­>>>´hÑ‚;vššJ›6mPUõ²È(‘H$IM‘ŠE-Ã:á(šm¢n3Y¸†;Ø]”ªºUgÙ8ß8ƒ¯kN·NQ”çfÓ¥¹™[zøb½‚¦¹Ödzáì%ì.Qî®QŠ›Îõó°f‡²Ëiõ?¯‰Ä—NQQK—.åÀ5jŸ’’Bbb"+V¬ ::šþýûóÐCѾ}ûË"ŸäŸåìÙ³¼øâ‹lܸMÓHNNFÓ4ÞyçêÕ«w¥ÅCUU4MCUU, 7æèÑ£x{{qz6‰D"‘ü3HÅ¢–!4&4tB«œ¨ ×w;Î Ÿš°Å9¸å›=_U´nV‘7´ÆÛ`¡T³àé!¸©‡‰ÄÓì<¬ ×»Ex;ÙvÓq.šŠÊå4N ‹@½Lj…ÕÚb6»ZCôz=èõúÊ`u›Ë”ÙlFUUJKK9räGŽaß¾}|üñÇtìØñ2I)ù§8sæ ¿ÿþ»c‚®ª*Û¶m#//ïªP,<==éܹ3õêÕÃ`0pýõ×Ó©S'ñöö–nP‰D"¹âHÅ¢–Qe’í¿ít@h•mEA¶ÌN.Šˆp±8¡ iÁ>Ü{[g"½)-+Í‚Ð4Â4î¾’3òЦ§‡Žñ#béØ*0Z± Tkà6MuTжS]›Ã݃àí%:JŒ®™¨.4f¥ð•ŽOŠÍÊ9³”¦ ¸ Óó{xxÐ¥K—}©©©;¶+**(//w ÷÷÷¿$ßxUU1ŽLEB|||\j!(//Çd2`0ªñ+(( ;;›œœiÕªÕyåÐ4ÂÂBŠŠŠ((( ¨¨EQ 88˜k| `u1+++sl{zzâíííÒ&//œœòóóÑ4¨[·îÏ]\\Lvv6F¨S§—TBAEE…ã>‚µ0——WÏa§¼¼œ¬¬,rss)))ÁÛÛ›   G¡EÉ•'ýØ^¥ ÓÎÓŸkz]ƒï~]„ÅÈ;v‘gè4°ÆíéÐ8ü2Igáðo[IL1Ò¼}O:6»ðßÀyQM$þ¶“ô2•†M;Ñ¢aÈ%u7榓÷#ó¾^IVžõÙ¦ø‡pMŸA ¿aÛµÄÏóê\Ÿ– ~‹ÛÀ™b•mûоYØÅ;U{"•S‰¿q*³Œš?º5t^á\Óµ-¾^Wçý¹ZI;z€Cç2ñ¯Ó„_xþ ¹*‘ŠE-ChMÓ\sÖ+¶LJNÊ„p”´v.=çû`EÕ· ˆâÆžu­ MC±Y *• ³Õ•IÑPµâ·¢ W}ÛkœHõàóõ ×Ùc%\‹å]Ô¡Iq’Y8ÈÓT\Kü]=„††ºlÛ+xÛÙ³gÿùÏ\ —MŸ>AƒÕxŒŸþ™yóæQ^^@`` o¾ù&M›6u´1›ÍÌ™3‡Í›7ЪU+¦M›FLL ¥¥¥|ùå—,_¾œ½{÷RPPÀµ×^ËêÕ««TwÎÎÎfãÆlÛ¶¤¤$Ž?Nzzº£P ^¯§nݺtèÐáÇsÛm·Õx’¼ÿ~fÍšEaa!C‡åñÇ 99™~ø+V°oß>‡ræëëK«V­5j“&Mª¢`”——³xñb–,YÂîÝ»ÉËˬîL-Z´à¦›nb̘1tïÞ½F FYYß|ó +V¬@Q¬ÙÌ&L˜ÀèÑ£«(Aç#77—7²zõj¶oßNjjªãoÕßߟvíÚ1tèPÆŽKll¬ À¾‚¬ûâyî{s=a,ýõ#Zž?ùBîÑÍŒ0œs¶£g­aéÓ7_á´ VüïI^úê(÷¿½‰ùOý9ÅB3yï¾~|žäÇ}ï®cÁ½kÜ÷ж•<ûÀ4ÖI®r,áçÕ¼ó<\?f2¯ÍœIÏÖ—KÁú ˜r™7u8óùp÷Ûëøâ©¾î<Š™ïß{˜çþqiýŒdßžÅtˆð¼x[‰ƒ„ãΈòÇ~|y÷jR±¨ehB³¾4Í¡T_øN¸N¦œ9¥eÒ4Aï.u¸wT4z¡jØ2P¬ ©Õ„P4E³¸S„bU&ÆöS9~Γ¸ƒ#ëeå€Ê*ô¹g‡Rlæa»^!.¢”\! \¶ýýý], Ž¢zv‚‚‚0`@RƒjšÆâÅ‹Y½zµc_×®]‰ŽŽvi§( GeãÆ€Õrrß}÷ÌÃ?Ì¢E‹.:ÖæÍ›yâ‰'HLL<ïýVU•ŒŒ 222X¿~=K–,aÖ¬Y\sÍ5¸³iÓ&òóó¨S§BV¬XÁk¯½Æþýû«ô)++ã÷ßç÷ßgË–-ÌŸ?ŸFpüøqž{î9–/_^¥Ÿ¦i$%%‘””Ä¢E‹˜>}:S§NuɨTf³™ÄÄD6lØàØ×½{÷U´Ö4Õ«W3{ölvíÚUm€’’vîÜÉÎ;Y²d O?ý4¡¡¡R¹¸Bèõ•u?Ì%E,Y¼á3o;ï é/+ç9” € Ãeü¹):ôz@‡ÎûϧVtàˆõ<5<0±aî£ÜÿÔBÎ[ÚvìϨÛP/XljmñlØñ ‡’3Ù¼ôþHHféÖïÐêꪣ¢èÀ+@‡Îó¯¤bV¨Ù˜-*-®:cq>gÎe €:õ£©âï²à'"âcëí—Šg õž)^:i­¨¥HÅ¢–!lJ…¦«,ð Øì Ωfíñ v{= ¡8*_kD×÷âÁ1Ñû Ì£ÍBaU,„PQš´ Íö:èlnOÖ*Ü^‚o2“ž«çxšÝ%Ê)!î åUÚ8¬×XÙHÓ´*Eü®, üẂã’n¶qãÆ >œS§N9Ü€vïÞÍñãÇiÙ²åEÇ8}ú4{öìqÙ7~üø*®9z½ÞÅ5IÓ4JJJxã7ªU*ª«Ðl2™HJJB‡‡QQQ4jÔˆððp¼¼¼(++ãôéÓ$&&:Ôãããyì±Çxï½÷èիׯÅÏχba2™X·n>ú(€UéªW¯eee¤¦¦ºülÞ¼™^xyóæqäȦOŸN\\`µl4hÐ!™™™”””8úedd0sæL"""7nÜeª(5Q…,X°€gŸ}Öa5±S·n]š4i‚——%%%œ8q‚ÂÂB8À“O>I=dÅì+† Å3{â–’n¼Õ•-1¦òíÚc€?zC >>ÿDláòöWOSSÃïñø¯¹{Ú<2Ê€è¼÷Ÿwyx|Ïʕ㇧,ç8 ß}§ÿ»”¼ÌŸ™öâLâ–Î&øjÓ‘5·÷?…÷¼ü-ã_mE§Ós|óWô|™0íÍÅ<=¡rBŠ/O¿$þÿK쿯ªT+j+R±¨m8‚³íí¬;íê„c§[ŒŠpÄ``Kåã­chߺ4öÄb.GjWPlÖ !@˜­/[V'ë2‚ΖcV±¥ÀÕѤ~·ôôâÓŸô•V¦ uÄ|à,³ëEÙtë–s½«tΕM||¼c[Qš7oNHˆ«óõ×_Ï?üÀáÇHKKcÿþý5R,Ž=ÊéÓ§ÛAAA\wÝuUÚ)Šâb)1™LìÞ½›eË–ÖÉqTTmÚ´!,,ŒæÍ›»´hݺ5mÚ´Á××—#FЦM5jD½zõðôôÄh4rúôiÖ­[ÇüùóIKKà÷ßgÁ‚tèÐÿó^‹Á`p™¤'''3{öl222ðóócÀ€Œ9’ `4ÙµkË—/çĉŽ>7ndéÒ¥lß¾mÛ¶Я_?ÆŒCll,BRSSY´hñññÅÄî6tèÐËRgdß¾}¼õÖ[.J…§§'ÇgäÈ‘4kÖÌ¡X=z”%K–°iÓ&222ظq£Ì$vÅÐFüÂQD>'~9Èž?Ò~mƒ*-ÓãØsìzo¼½()©à|Fº²‚,Ž%¦ð ð'ªq B|/üU[V”G^a)ŠÁƒ:õê㥻¸ˆ©´€sé™hðñ &2âOÆ8£óñ³³É(¯Ö¼·ø[&_Û¸J3ß:Íxä?_Ÿy„—>?È¡í›Ùs¸ˆm­ PU :¡ 3èA˜I9•ŒÅ#˜èzèî]q^ÙyÅÖ”ä/ê7ˆ®QÜFqNY…¥hš ¤^4uÏ ¥ØòÔåf™]„¦×Ný°šÅŠ<¼ªL–ü}¼±:HêðöòF¯×WYˆÐ„@SUt:=:‚± ›”ÌBê6 <ÔÏ©¥…Œ³©”š,ÍŒw`]¢ê…¹KCÓ :ôúóüò UÕPtº*–Pa*%å\&UÁÛ'€ˆ¨zÕN2+ztzca)ùUd.ÊIãlVš0àëHƒ†Qø\à×ÜTZ@ZF.*¡á‘„ÔÌÅTR»ŠE-C8Ò¹j•1 ¶»ÃJ_Í —Ö¶ªPlUz®ëÄð¾!(¢Âºj*, [V&[ø´k·Îú`Ãî¥(ŽmÂÀ&Ngx²â‹æð˜rŒ¾x Õ}—#Väês…RU•¥K—ºXbbbèÖ­®+T]ºt¡[·nÅÂl6³cÇFŒqA¿}MÓHHHp $îÔ©Mš4©ÒÖ½pZqq1‹-âìÙ³x{{óàƒ2zôh¢££ñ÷÷Ç`0àçççrŽÈÈH^ýubcciݺuצ   êׯO§NæÙgŸÅd2¡i›6mbÿþý´Z —/ºÄÄDL&ÁÁÁLŸ> &¸T.·òÌs÷QÇølÉâõ‡¦±;ÛÈôW_cÏòOYþÃzާå¡Óé‰lÒ†ñ÷?ÅããòzLJ_¼<…OV­çDr&x{Q?2–N7ÝÍ_~ˆˆKÏÏ€&ÔÊŠKÕZ-,z~"_ïÍ`ÔËÑ9s O¾õ=IÉ…Œ}å[ÞŸ|-»WÃó¾f_âa²‹hš™àˆXZ5oÏø‡§qÇ Ž$Ìy“·~Nžœ½€ÍÝI´f¿üKÆ·ý¾~ëülzÎ/k>ç?ï/ä`ÒJ+Áá4kÕŠ‘÷OeÒÈ>NA•Å/NâË=©ÜòÂGtË[Æ“o.&ét·Ïø–9ô¢"ÿ Ÿ¾ô ‹·%°ÿä,ª'¡!õiÙ¶ Cïy†§Æº~XJ2˜÷æ,–ÇoçȱdÊ5 ž4lÒŠ.oä¹W¦Ð8´:3¡¤¶"‹Z†ÀVy[§g …ÃDa{îë*ÛšUm™xKá!*ªj4{ý ëz“­5ÆÍÂlB±:°¢ (:kì BSöQ˜ÐW£¤Ì“5¿*èuNrÙ® j%=»a;à¤{Ø‹òýS\,V ¬¬Œ/¿ü’·Þz‹¢¢"ÇþQ£FѧOŸ*탂‚¸ýöÛYµj•#&cݺu<øàƒ´nÝú¼ã±fÍǶÁ`à†n  z?fgÅ"''‡ììl<==™1cÓ§O¯¢ðT×ÿæ›/ˆêííÍ<ÀòåËùå—_ÈÌÌdýúõ—¤X”——£×ëyöÙgyâ‰'ªXP¼½½9r$ªªrß}÷Q\\Œªªœ9s€Þ½{3gÎ4¨ººÜ´iSÞxã IIIÀh4òóÏ?Ó·oßKÊu1¶nÝêçáééÉĉ™1c¾¾¾Õö‰‰‰á7ÞÀËË‹O>ùDZ,®0æÀFöì̪ÝGØ8ÓGRßù›±" K¾C(Þtïõ õwý§Ê9DE>/L¹Ÿ·?[‰ Ô‹iEÓ¨Î:žmس}+ÇvNá‹ÿÍ"ж_–Ї'ÜΗ?Y(ëDRׯ‚¸u+ˆÛô~à:á²ägóþô1<¿03Ú –è/Ž&íçÄÑýl\µ™7>ù–'ïéó'üÓ;ã~%«È~Ü{ßíѸÇþH¾“Pðõ¯\ý//<Êúõ Ì®à‘UÇXû«ÕÝ‘z9(z(Ë9ÊÄ;†°$á §Y‡ŽÔõÕsöè~Ù²Ž_¶¬ãHN ‹?˜j‹rö¯]ÍÚÌb¶lÙCYI øÕ£c³œ:žÄ¾][Ø·{;IÿãÃ&á㨫%,}ñ¾)-½mÚ4#+釳29üÇJõþ|ÿêøË$¬‘rh 6äaöšÂìø9nûÊH+±.mùæuÆN~™ÌA\Ó¦ZY!÷íçÔáýlÜ+⸣ocš´¯ËΩ(üºOdà‹7¹ŒV|z/‹>›ÏÞ¸«ç}øè³‘UÿyŒ‰o~NN…À'$‚fQaœ=y„§’vdÓ IDATظ~ûŸù˜^›€—b•ùìá5l؃Éë ÞOX˱B›Ìef0æñØ£™·~àEËŽ©ã G÷¿ññ›ÿÀä¹–Fw°ÞŠ|ž{àVÞùnõ[´§e_²Î$óÇŽ8þØÇžätÖ~7›H9ý·pµyDJ.‚µÐœZùRUkQ9—w[±9ÕéeÛ®¨°Ð$“‡o%¦>X,F„VZB”‚(­ ´r¥ –!4#B+Gh„jFXL‹ T“µˆžjF¨„jA5™©\ÁÄëKi­QnR­qšŠæ,³ý¥©•…ñìŸíòÚ^ÿ¤?Tii)F£“É„Ùl¦¢¢Âáóÿõ×_3zôh¦M›Fzzº£Ïµ×^Ëã?~ÞIÏž=éÑ£‡cûøñãlݺÕÅáŽ=ÃNÓ¦M2dÈyÛ;O–íž!C†ðÄO\T©¸Tüüü8p cÛd2±ÿþ Z–ª›ÌwëÖI“&UQ*œ2dˆK!B€ž|òÉj• ;íÚµcذa.û¶mÛvÁ{~©˜L&¾þúk—à â™gž9¯Ra'$$„©S§ºÜGÉ•¡Â¢£Ëð{h¨‡¬ìõ¬Ûár<;i+«ö•£÷ a䤞˜LUÁŸ¾È›Ÿ­DŇ1Ó>$nóV¶lÝB\\¯ÞÝOafå¼wxá›í¶‹ßx¦Tø3æþ7IHH`Û¶m¬|*±å”«þ®þ<ï9›RáËø)oO||ÛâÖpÛ5 0—Ÿåŧ¦±îxA•¾ÇÂÉÜLÌQiQïâ]¡a„……âãU91Ô ø{@ÚÞÖþšA¯acùâ»e¬š=™öÿ<% §ÐùFðòk‰O !>ž„øx^ºËšÆ{ó·±7Ëv¯o«’UV’Âu·?ÂÖmÛHHˆ'>~“Ç€0±ðõ,IHvR£¢´”ÎC&±1añññ$lZΈA€…mËr<§j²…¿ƒ¯Uæ­k~ädE ¼üË—ÿÀÓ·´G”g3wÞ|2K-úÜÎÚ­ÛHHH >>ž¸U Ѐ)÷,KW¯ÁD÷½…Û{Y3Û´˜,·_};7‘˜^uÛ2nÔt úé#î¹€œ Á ·=ÉVÛ=NHØÊÔ[:ƒ¥„Ïþó$ÿýéX¥Ì¶û·f-ÇËxø¥ÿ²lÙY²ðæî—ç’OBB ñÌŸ1½Â¾µó‰KJ»,÷_re*b-ÃZ™ZEzGd…#z¡ºÂŽmMùë˜|[ ­bASKq¸: J+…ÍJ œ¬ šT¸ªÐ¬FEh(Š¡èPÍ Q¡ðèM:žýÚ“Ü’Ê4´•2»Ù!œÇreT‡¹ùŸáé§Ÿfîܹ„……a00pöìYòòò0.í{÷îÍG}äâÆãNPPwÞy'6l°£ –.]ʰaÃhذa•öBV­Z…Ùl¬53úõëGûöík|¾¾¾Lœ8ñOÕ`¨ -Z´@¯×;‚srr(**"((¨Fýu:C† ©’²×___ºvíêÏÒ¹sç‹Þ ½^O—.] qŒ'''SZZú·Ý“'Nð믿:¶ëԩøqãªdí:111 4ˆuëÖÕ(ó”äò YÌÔir #ú„ña\?~³Š»z=h[uÄ}¿˜l‹ ºÝXúÄgq]è°eÎ7+h3üqæÎ~»“J£æíxé³oI?qŸür‚µ Þæ™ñ}‰(ÜÏG+âxÛ|5ÿq‡›Ô-SÞ#4ØÈu÷|æ2Ž9kï¿÷=fn¼ïæ¾ÿ¤ÃžÑµÏÍ|²àsŽ ¸•ÙûÙúÓnšrþ…ˆêoD9E爉­C°·Ûº£¹€¯æNrVIÕâDšF½.ƒ™4¬G•ÕÊ>£_`ù7¯S×éÏîtÒ~ð„n}eæÝƒ§kÔ¶#O¿ô2³¾¥T˜ªþ]„µÌœ9ïÒ>ܺ ѱËÞó5ûzá·¬t­\ÉØëžÀÃIÆ:nä«o>¦MˆuÊÖw¯¾ò2 ·N'75ŸÓéE´©ságÑ_Á# .o/XËÔ1×8öY²ö“•Žâ_‡'¦½ÎÐN67×À0z ¿;ömaËŒo±ˆRkì¹>œ·ç‹_æxðvÊeD'[†(eÝâo0º÷¢gû (ÏæoÏ'hyÝ|õÕ©gûuéÍ{ qöx–Jgë׋yzèË.Vÿ:¼9o Óï¨\Û°îŒ@x½î½ãzêÙÖ¬ZwìÏ럾ơ‡gãf$5_%:Â@Ê‘ý˜ РÉíÌzq’£}ýØæÜÿÜ þ»ðgަäSaþû|$W©XÔ2ì1•™dìùfòÎRy¨²‰µÝ¸Á~ôïªG³”ÙØÓËŠªý\Öj9è GQ hšŠ¢èÕš-JQ±uƒªÓÓµq)÷ôƒ9?z`Qmãî¸(?T*DÖRÜUðO†Xdee‘••uÁ6:ŽˆˆFÍ”)Sˆ½`{EQèÓ§M›6åØ1ëÊÐÁƒ‰g„ UÚ§¤¤°{÷nǶ}¬ ¹ð¸‹ŽŽ¦k×®”ëb˜Ífòòò8vìgΜ!''‡ÂÂB***8xð ‹…¢¢¢£ÑXcÅÂÛۻƊ’ûD=**ªFãDFFR¯^=—LTUfjÊ©S§\‚ëcccéß¿ÿ%£qãÆøúúºX=$ÿ,B¨xû1hÄí|÷1 {7’Zü Ñ Jϲ,î gäýýñÅÃéydý›ËØ»—cûÒA©Ë¤{ÆQ%=€WF=tsy̬B²JEq[HN)¥?wg•Ø‹^£žaø¬ÏY¤9Æ9š°šßrJPôL~j"î^é¡mûrm¿vXº‹m‡RÁ¼.ÅíÏb¢¢Äê«ãâM•„Fælþóæ‹=k¬Úè|÷;)Š)U!'ýÈ:þÏnëtºjÓêõz¼½½ £W¯^Œ=š>}ú\´*´èèhºtéâP,ŠŠŠØ´icÇŽ­’IäСCœ>•Ó/!Äßæ ¥iiii.ç‹­±bå,ãßíª&¹D˜€>úÒ(d.'Ýů‡ÎÝ#г·³3ñ4Aí¹éšÞ`ur¡´ÌH¡ ›Ò®CÓ*ÇbÛRHÍ*$5«€°‚BJTðmÝŠ¦ÑU]}@×öìÊê¤}Ž}9§ºl>{u ëü½°ØÿOƒJÜïÖ熹ÜréON½7>ÁÖ‚—)ɹ–C¨ó¯¦g“šÎñ”lǘzQÁ†ŸVqâlÁÞž®Ïw вÍ#«fb«Ó –°`¾ùn!ÇŽ'“ž–JJÊì?DfÍ©¿Ú¹¦ŽýÚU'<½;4À(É+Ãb<°}½ð ¯ÆŠh·’[¨é×à¥c;oX§º]bð¦m뎜ؿÞ~ƒô³©œK;Çé'Ù{ ‰ê~‚Á-ºq]Û6HÛË÷˶òÖC7 ÀöËIÉ3BÄPnïoÝ+-ÉÀ\fõ4X5÷UNÿX‹Ów›‡‡ž£û0—k·ÇwXǹÉܺ÷0:Ç|ÊÞ3¹¬ýôuÖÎý/-zô冞]iÖ©;à  Qx¥K°X$=BƒX½l_d(èÁÜV+Š»»–]¥¶1…&.è»ÿwsóÍ7LNNªªâááA`` ‘‘‘´mÛ–®]»yÉHƒÁÀСCùá‡0X,~ýõWŽ9B›6míTUå·ß~sT¨öôôdĈ—¼Ê\£É·3f³™µk×2wî\öíÛGnn®ÃërPÓÂpî«Y5]Ýòòòr™´ !þ6—£òòrGý ;111-ÂçŽ\©»ÐPUn;ž-£8¹3™o×ìäÖcØ·Š³%Ðö†vtëTµ,å–æPøúPÇ¿úLoŠ=¥¶E ”Wpª РŽ?!¾Õü {=î§g—bÐ,l]½O½RÅšëéåMxx8žÞž—néÕ{V/€âã'É+.#6ÀI©7„2ýù×Ü:Y¸kD"'ÎæUý6Q“@«’òŽo\ă“ŸfëÉTkSÂB oBhxGŽ: au«eóñ÷@(ÎEéì)ÌMW(o¹]”jƦ>zâ^ýv%Ù…åøH‹¶})ÈN$53×í|Ü7n8nØKé®ïÙtâeF65³rËŒè>~-­ϼœsùäÙjúíÙ²ŽD*ߥƒ'õÂÃñ @uÑ\UæÐFÝùqËæø߬ÝFvf*GwläèkqÖ76宩3xeÊx¼t—Ïä{'³dO’M~BBƒ §m›F?¾‡ óÕ•õQòבŠE-ÚcÚ‚¦SlÉ[Þâì%çTBƒu<8Ê—þ]½PÿŽªNÂèf±er2ŠM¹09­@è±hôhœÃCØý£7¥ ¯f2%De¥pçëÑTíT,î¾ûnFuYÎ=bÄfÏží(¬wêÔ)~úé'Å"''‡Í›7;¶Û¶mËÀ/yUÛÃÃã’&­%%%¼øâ‹|öÙg”——»kÖ¬mÛ¶%**ŠÐÐPüüü8yò$óçÏ¿ªc.ç¤Ýl6;”?;!!!RQ¨­@Æ„;úòÍÎdö¬\DæóýYµx=}nžDØyôà:¾!ùe&Œ& j OnE ù€w˜?uÃðò ÁÈÎ)!ߨRßÇM¹`®(Å9v."ÊoÀèÙŠE›¾§]˜w•ɼÞ`¡¡ó ²fù¹¤g§ž®Má”älfÉæt™Ðã‚=Ì9I;•t c€%ÿOMy‚­'³iÇÔמ`P·N4lNÝz“$²Õ°óˆ.(/«>‹Zò)#eؾ’jÁŸáþ?⹿£¸ö¦{™8i ÝZ6¥nÝ:Ô«§¯ßÃä—¾¬Ò¯Í°1ôy…„” –}ÿ×ß q;C`· êH·Z7 È­eÖK¸¹sLÅB§7 C é¼‰ ¨YÆú;óâ{_òä«y;ô;¿þ¶ŸƒûvòÍ’Õ¤§œàígŸ nó^LÊëONaÉž$]“_z‚á×÷¦YtuÂë(NÓ¢MOŽÉ»è˜’Ú…T,jBÓÐ„Šªé\”{Õj{…Qµbµ‡‡Â}Ãý¸¹7 ¢ÆÕbÅZeTgx£à¡o[‘=ÕV`Ï^œÏ>¢ÕNÓC]A„Ð1¸mé¹,ˆ÷Âhήü8_›íÁfϘ‹µ(Ð_«šzi\Ή²¿¿?£Gv(&“‰-[¶0yòdüýýBpèÐ!}†Nddäe“É.Ç+¯¼Â|àØÀõ×_Ï<@»ví Â×××aÙ¶m .¼ª‹Ë»ñÿù^ü[è<ðNZû|Å‘ì½|øÞ;l=eßHF ípÞ>ž¾|ô•|‚}NÑ=ºU•6‡w¬£ ñ",ØÏ€@¼€â¤NΦUh}—ö¦¼Sl]{çt³¡èt D&Þ!Ñ4mVÕÅè— ß³ïxMº÷§QdK¾þ¶ýn¤qÝÙÈ6²lÎ\s ÑîAÜNlZú%Û’JÎ{¼: ÎüÆá¬,À‡—>ú„§îtMS]—…&Η²Rãç_Ž0¹Ÿ[=QFÜᣘö­" õÂêâtsè·(¢Zöcá· hâü<Ѩ(Ì®¶ŸGHs&ŒJÂ최Øû=øp¼¢ºub@¯(G;<}€Š„w0M›VuÓKÞ·Ÿ9NhãVD6:–=+f¶,]ÊÁ´lZöÉÒ¾Ç Ú÷š‰‡&¬düà;Ø[aáè™<(-dòÀÀ¤—_ç£w»œMË,D5•W?”¤V#ÓÍÖ2ìã„ÝMH³rkš†¦jŽÏª&P5Áàk}5Àoå¼J…¢€N§ S¬&{„ÀdR9“Vʆm¼÷õ9Þ_ZÀ;«üùq/'ӡ„]@§X_öJÛöX k¤¹ ÍR·ÞÈÝÓÞÑ ÂjwÈí"»p¼ìnPâŸÔ,.3C† !<<ܱ½sçNŽ9XÝkV¯^íX  åú믿ì>øëׯçÓO?ul7iÒ„Y³fñí·ßrÓM7E@@€‹kÕåt‘ª Ø]äœÉÊʪ6>çBØÿ–%WaM;1tHK´œÞxiÅ&ZÜpÝUW±ÝúskоÍ[…–ÁÇo/ ËmA½øÌ>ýÆj…lßãfùB³]iX×D2ÿ{ë+ܧæë¼ÍÖB Ö¯hë8-ûŽ¢QˆœÇo̭ͣOæï?2éŽq<ú裬ßv’?ƒ_L¹ÍšùÔï‹ùìÒʪûýÔˆ[ô:Óžÿj7]Šra£aŒ›‚PžÅ»s¾Ä¢Y¿“¼¼¬?6E~ç7ŸrÐ-×jRÂü°e/``@·.øê¹DkÍ?±Ä*Ÿ¿oꇸ.Rœù}-ó[ã<< n56 z3õ}aÿÖ8žye.7Œ}œ§¯Š°×Ò:6 0ñÙìOÉt›Ã›21íÎ;yøÑGùhA\ Œ<›¼ÀÔ©Syé•U®ßÊ:OZuïNó†½D…Š¥\žD5t¯ÙTÁן/ =» ÝeË^(¹2HÅ¢–! rÒ­Ùâ-´Ê¸ ûËbш®§cì`BõØç;v%B¯W0è­îR& …ÅœL)aËîBæ­(à¥3X°Ö—ù×áßèÚ | ÿ®orÊçAìÃËË"ødc}¶&úr< K­Ê†ÖÔ²z]¥²Mƒ`_#wtÏ¥y= ‹ÅYvá$»æº­ÕÌ<[[hÚ´);vtl²k×.Àêµ{÷nÇä´eË–´kW] âßGEE+W®¤¤Ä:µÑétL˜0 &üéàïÿxyy¹(ˆ`Mgk±\Ú2iEE…´t\E(žõ؈SªR/FÞ܃  |Sz†µåÑá#80‡Qw?Çï‰GÉÎHç÷í+¹í®Éì:W!±Lyôn €³LºÑZP3néLîŸø&Žœ åtóÞx„_øªÊ8¾±ýyê^kjÖõ_Ìä¡Çßæ¤“¤¥œ nÙ'Œu/Iù*у¹gÌ ?y Œ>ƒ¡á *øýƒé è9Š9ó¿aý¦MìØµ‹UËñøø ÿG õDF\ÒD"4²)žA~@*Ͼø<›ÝKÊ™ÓÄý¼’‰Æ0{Q0•–óËOÛIÏ+qIsžsd-“&>ÌšÍñœ¾ùìMny‡ÎYŸÉÇ÷ã×Ç)sŠÓhÜýz6oHEÁQN¦”APÆÝÜÍåü:ÿF<ùØX|£ ó¹ûîgؾï0é©)ü¶é;î6š•GòÀ£<5¹>w¼¸~ôpöüü_ý˜#§S).Êçà¾Ý¼ñüÓ¬8:Ÿºtëâ_Àz‘@o¿ó2ßü”À™³)ìÞ¾™çÀ=ÏF™-p~÷º$§çü-÷Urå‘®PµŒÊI·õ!ãð³M¾íîN>Þ ÷ßHûf((lI›L²óMdæšÈ-(§ØèEvq0g2½0xÓ0¶5íÓ¢o=ÂëÖ#"¢>uœò{çå‘‘EfVEyéœK9ÌîÓI˜Š2ˆ .$ÌL€¡€Ð@¨ uƒÀÇ«Òuªyƒ|&öóbÆòŠ+¬V;Š=ØÜI‘ÐlÁêÿüýýéß¿?qqqŽŒBkÖ¬aòäÉüúë¯$''V7›Aƒ]rø¥röìY<èØŽŠŠbðàÁøûû_°ß¥®ÌÿÛÐëõ4lØGm“'Npúôi—˜™‹qîÜ9ÊÊÜמ%ÿ›†±¼Üeé¢çà4ð}—3¥@PSôêçÒ¯Ähýy™Ì• áÐgñTv.ïÍ[Å/ß¿ÅuëU'ä'©ô~‘<òΗ kgÏ"çɤ9±¯ø.¾Xµ‹% žççUsö¨àlz>Au»Ó¯µ™¸ø½T8êfè¸í¹9ÛŸÁ6îæëŸaù¢÷©ï«qê¬5ÃŽof¿û?:DYƒÈë¯g)åU¢s«Ç?¶ 6®ååÏðÅê=ðS&ý€¢×#lаÞË—_ú€{Z¦Ðsôk+*­˜ªj¦XåU<’|¢º0åþ1L~å ’·.dPßÅDÖ æÜ¹t@Ç­ãÇ’·{9[çð콃8üþj¾œb_ŒQðõ÷c÷š _³€úõë’‘auòô‰fæ³èc_ËÏíªf¡´P*öP5kX“ó–9ýŒ\)¯°*eæª ½†OfàÜ5lÚwޝ<ÄŠO#Ð È-2T?†G&^ÏóW°Ã<®?ºU›¶2°©Ÿµ³o,wïËÊ}ßÐyÌMtŽ­ú]ÑsÜ |²'…Çç.fý’·Ùôã<…ûsöôYL€Á«O͘ϘîŽ>6™K«“yÌ4F¹™eÛ±pÆ#¬žûÑuüH>y„‚ 輘øä Œëh<òÐD¶x•‚Ãë˜0tQ £IK9‹tx#Ñe‡X¶ãs^¸“½éï‘ðáã˜ÍÖ¿¯“ù_´¤øÿ ©XÔ2„­†…æ4ÛÂQOΑAïöAAŒ苪ªœI+ã\†…ã)åä*(> )S£(×ü‰ŽŠ¡}ï. kÚŒÐP<= è/°üLhH0­[5¬ñf‹JAA'O#qÿ8s%³€€´“X ì]Ló•˜:ê‡ÀÐŽ$¥z² ÁM«¦–ŸÓz“ÐÜjlü 6l .t¤žÝ»w/GeëÖ­ääXWmBBB=zôe—åÌ™3äåUÏ…‡‡_°¢µôôôÿ÷+íM›6¥I“&Ž˜˜””Ö¬YCëÖ­kÄ]PPÀ®]».ÙÊ!ù{hÙ}(ãLJݶ3þN±ÓûòÒ#SˆKË%²Yz´pV²½è=òN ÛePçÊú5zŸ:¼ýÙwôéö*}·Óç2±­»ö$:&šñ“ŸaÌõ®ä}Cš1ÿÛt~îE–þ¶Ÿ¬¼<|èп+=û2þù$:Žë:VNú¼BòÚOëh;ï#._Ç™Ô,„ÐÓ½w+š4oÔgfrMóÊØ Å÷M@íàÍu]#¨)õÚ÷á³¥µdóÿLòÙ4ŠfE!04œ†›0îÁ©ÜÒ»-j^ï>Móþí–‹ÐFí¹ç®ñäG÷&Ô×=ϪÌø„€ðæ,\ú#9…`ð¢]§ž ¿ë=€í?ôÁ÷³˜4…Øu±™äÓ?úFg×óÝÆídæÕ˜è¦m˜üÔË îS9ŽÁëÆÜ…©×õ¬§æ[· wLG²hJÃp÷ª '$º“Æ'@:7¯î™©£ÛM÷2Þ?‡ÖšU9ÙŽï—ýÀ ¯¾Å‡OQb4áÔœ¡ºñÐÔ§èÙH‡ŸwûŽœC oA [€ï{á®S áÃÈ{Ç\]@Ï@îúèb®À;ó¾#9% ‹B»½ˆŠŠá¾Ç_`xg7%]ßÃxßlZ]ß¼Ê鼂bøbé2º¾ò’Ž“š–E^^!¡õ›Ñ¹i ÝõOÝ9½í\ƒïɲ€HÞ›»ˆs™¹X„žæ7v ÿˆqýôSæÒذaâÔ©S5ê/¹:QËKDZZšHËHyÅ5êSZ˜'ÒÓ3DN~aÍÇ1•ˆŒŒt‘™™%ŠJVÜšŒ$òr2EjjšHOÏÅßXfc‰ÈÎιÕ]·jU³5Lãcê PÄ‹ßýaÝU^,rsrªï[Ë(ÌÏYÙ9¢¸¬¢Ê1Ë%|\µ\defˆŒŒLQPTý3íR¨(+éiâlJŠ8›š!*Ô 46—‹œìl‘“›/LZÁ„Ùr¡Îç%WÑ⟘ŸI. i±¨mˆÊgpTª¨<,4Ž'ðúÜRZ´jGÇŽ ¨ƒâB` ?õC ÄÛëò{ô„û£Y‚1W“ŸW—À¨î„ÉÞ}ðõ–",#¾ÞŽ »JË…â¸"{°ú¿ EQ¸ýöÛY»v-§ÿ½û£º8ü»3³»ZõbY¶l¹àÞÁÝÓK½SLI:)`Ò‰! ½…@€/&”PM'ÛtŒqï½I¶$˪Ûfî÷ÇìJ»kɸH²lÎû<ë•vggîŽöñÞ3÷ž{Ö¬!3uêÔú+×^¯—³Ï>{k"ìÂÂB22Ö…_³f óæÍ£W¯¯°D"žyæ>øàƒ„Çõ0 Ù4M.¹ä^{í5¶ms§c,^¼˜É“'Ó®]»„\šxŽã0uêT¦L™BYYJ©ús[Ì@¸ _;¦íÑkR3sHݽz‘ Çñ¤QP°gÇÙ;9yíÉi=[)i´Kiâ=fãW¬££õ–/\ß®§l(2³›®Sd6×÷€á#¿}Aóì ðúÓ)ðïæù·|äµk*‹ÃÀ’¡‰ƒŠGƒãØhLj®ìšXÏ -#Û¶Yºl)Ë–/% ã899ÙtîTDA‡ö¤øR((( wïÞôïß.]º““ƒ×ëŲ,,ËÚåt-6‡ ‡ÃTTT°~ýz–-[ÆòåËÙ²e µµulݶ•6Q¶½¥^˃£Áãñãõ™êÔTжã|¸õ)N:é$}ôQ€úŽ)À!C=z×ëÇ7—C9„Áƒ3þ|ªªªxàÈÊÊbüøñx½îz$áp˜-[¶0uêT~øajkk:Ä‘Hä9¥gôèÑ\|ñÅÜsÏ=õÍž=›K/½”믿žSN9…´4·ãä8›6mâ‰'žà™gž¡´´”^½zašfýÊ`{ZÁ\ˆ •t/„h›$°8Àè¸ådŠF4Ü»†ƒiÊ•°üî}]]€å+–³|ÅrB¡–ižžF»vùx½iiiôë×Þ½{Ó³gOŠŠŠÈÌÌ$##ŸÏG(¢ººšÊÊJ6mÚÄšµkY0>K—-£ª²Šp$DYY95Õ5D"ÌúàÄÀïOÅPnðcF³(líÄE òâß–vôAùE’‘‘ÁI'Ä3Ï<“¼«”bôèѵJ;ü~?gu¯¿þzýÊP_~ù%×_=gu#FŒÀ¶m/^ÌŒ3˜={6••• 2„’’’úêÓ?ÔÀÂ0 ~ö³Ÿ1cÆ æÌ™Sÿøwß}ǯ~õ+þýïÓ¿<¥¥¥,X°€eË–ÈÉÉaâĉ,X° >°‡ÃX‘Ìq¨¶m@SÙD¢´¢mÀâ@£èÕýh9œ†ÌmEýdÛIìœ(¥0Œ†ñF¿ßÂqjjë¨^·GÛ8ŽÃ·ß}‡Ïã#5ÕO8¡CAƒ‡ ¦°c![Š·°xÑb6n܈i™‚!µuDÃ00M7d0LŸåI ˆ»‹>0‹'g§a ÛqØÍÅL(†ap衇2jÔ(¦OŸ^ÿxvv6Ç|B͈–vì±Ç2qâDüqÀ½j¾dÉî¾ûnü~?Zk‚Á`ý*VC† á¯ý+wÞyg}`êWGÚûcª¥ŽÙ»woî¾ûnn¼ñÆ„¶¶mÛÆôéÓ™9s&J©„€!''‡o¼‘K.¹„Ûo¿½þñP(ôƒ¯"ÄN¬yôªC÷]í²PR*ZeÛªUÄŽPQY…ÖËV¬`Ñ’%Ñ#™¦7MÊÀôXX¸¯uÜ´£±±“ÆRïwz_(,Ü*ߎãÐ’ëͺ—‘ð{kéÔ©‡~x}`¡”b̘1Œ;ö{^¹³}yéééL™2…ììlyä‘ú‘‹Ø4·˜öíÛsî¹çrÝu×ѽ{wžzê©úcÖÔÔÔ¿®)†aÔo¿'Ó¾¼·ø×îÎ1÷öXÇs /½ôÿú׿xñÅY¿~}ýsñ9†apøá‡3yòdŽ9æÇÁï÷×3 H5Z!éqâöw+„»á`œer@ÒZç_»¼ó“ .äÝwß&öFC‡h=¾ÔK$Ö¹H|¬‘Çã6uðQ*šN­’öM4¬id·É­Fc›†UÜÈFÆäüýïçŠË/klû$ðÕW_QYYYß>|8;îþ²Œû¢¸¸˜I“&ñþûÏSO=Å)§œ²ÇûZ¾|9K—.­ï vêÔ‰ÁƒïñÈǼyóxë­·X´hÛ¶mC)E§N3f &L W¯^õ¹ ,¨¯»aš&£F¢]»vî·ººšY³fQSS¸•«GŽI^^Þ÷¶iÆ Ì;·þ¸Ýºu£ÿþß›Ü^QQÁÂ… ë—Óõz½Œ;6!Y=^$aÙ²e¬^½ºþX}úô¡G{t7mÚć~È'Ÿ|¦M›p‡ŒŒ zôèÁqÇÇØ±cë‹ÆF‡Ö¬YƒÖš¬¬, DnnÓÉœB!(WJ-Ûß ‰$°h#v7°¸òçWñâó/bÄ–QHˆzüõ”RöëËä-*סtã±JŠQ´jd+ݰ¿„šµ=ödtzW8áé§ÿÅÙgŸÕÈÑ\Á`'žx‚›nº©~TàꫯæÎ;ï$'§%Ö_B!X´Q2êSر åN¯ˆvøëÅÇq#î¥ä I{VvnçÀc§C&NsJ(‰l8mÝNÇ=¬µF;×CA‡{ÐÒ×_~É?þñú ¢C‡œwÞyT!„â€%ŦgÏž˜¦A8Ž-ˆ«T·­;ËÀˆïÅïnÎB|tÐXvD,@Qõ¿Õ/ ¨“·Œ… “§vÞ&ñÞql:ä—wpM Ù²e >ø Ë–5\h9úè£6lØ~l•B!ľ‘ÀâsØaCÈHO§´´ ÃP8š¸zC×>!Œˆ®µ“¦2©“·‰‰'~ðÃ!!BÐ$Ž—ÿ‘:$†7±ª ¿…ÃzöèI§N¾§‘Žššî¸ãÞ~ûíúåYÛ·oÏÕW_]_ï@!„â@$Ŧ[·nŒ5†iÓ^Ã0ŒhþDüúP1jç˜!!Á!Q|jCÂl£•F醺MÑD.M¥ë¸})Àq4^¯‡#Ç#+sKÒ¶QëÖ­còäÉ<ÿüóõ+™¦É­·ÞºW+A !„B´%X`RSS9ÿ'çðÍ7³Ø°q£»ZMtÔ"yUÙÄZšX ÕÈ *ºM⓱էÜÜ ·ŸÖ±!‹¸ÃÄîë«2GÛ=®£’B¸ÄŠX.ˆÆ]¡gÈÁœ{Þ¹{rzÚÛ¶Y³f o¿ý6=öXÂô'Ã0˜4iW^yå~l¡B!DóÀâtÌÑÇ0ñ‰Üwßý eÖ/Û´ÓÈC”Nœ…„Žû¹~ÙØ¿ª!¢þ ¥âö bœÄ™Rñ«OŪjÇvk›jd0$6Ã*ÚfÇqHKKcâÄ èÖ­Û÷Ÿ6lýúõüîw¿ã•W^©/0••ÅgœÁïÿ{ü~ÿ~l¡B!DóÀâ”™™Éå—_΢E‹yõÕW±Ì]H|*q~T¬¢÷Nœ´Jܦ±ýêFæZ%oº«viwÔ²,N?ý4Î9眦7>@äææbÛvBPѵkW®¾új&MšDûöí÷cë„B!šñý›ˆ¶¨k—®ÜpýõôíÓ‡H$ì.ϪI¸9q÷±›¦áñXG>þ?ºpKØ.iŸõ÷:á5¯é›ãh"›qãÇó‹k¯¥ÃA°ÌlZZ£GÆçó0jÔ(î¾ûn®½öZ *„BqP‘‹ظqã¸ï¾û¸êª«X»vm´ê²ªÏ…€F jj2í:qšTà 9ÔçMÄv¤ãîUÜ>tÜ6»â8ZkÆ?’ûï½þúÿ 8X–Å 'œÀ¢E‹=z4Ç<]ºtÙßÍB!„hvRy»ØÝÊÛùâ‹/¸ùæ›ùꫯêSÉs£vSìuz§¹TM­ç´Ë½±«úñÇñz½œqÆüío£¨¨hZ|`ÐZïõßD!„ ¤òv%=6b_ €ââbž}öY^~ùe.\HMMMó6°…dgg3bÄ&MšÄi§&µ„Bñ}$°h£$°h#ö5°‡Ãlܸ‘?þ˜—^z‰Õ«WSYYI À¶íFF!Z—R ˲HMM%++‹aÆqÎ9ç0|øp ÷kÛ„BqÀÀ¢’À¢hŽÀ"&‰P]]ÍŠ+X¶l[·n% bÛö¾7t˜¦Ijj*……… 8¢¢"ÒÒÒdŠB!ö„m”ôèÚˆæ ,„B!bX´Q²Ü¬B!„bŸI`Ñvìß!„B!ömG5ØßB!„h㪀ºýݱ3 ,Ú¥TX´¿Û!„BÑÆ­¶îïFˆI`ѶLœýÝ!„Bˆ6ì5¥”Ìòhƒ$°h[Þ^ØßB!„h£ÞžÜß“À¢ QJE€÷w[„B!Ú˜€«”R’_ÑFI‹6HkíÎÑZ_®”:DkíGþVB!„øá (¥6OÏ+¥ªöwƒDÓ¤³ÚÆi­;¹Èè’B!~X 2X!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„Bì!µ¿ „bŸ(@ÇÝÇKÞF´}º‰ÇTÏ !D›"_6Bq`iìÿm•ô¸üß~ðÐMÜ'?/„ûµ¿ „â{54( •tÓ4„ˆ¶ãûF)âo4òó÷íK!Z|Á!DÛÖTð`4òœÓÈkwõ»h;tÒÏñÓÚT#ÏÅßœF^'„­NF,„¢íQÜ$‰Þ§y@7 ÅÎRqàˆµ@9P ¬j¢™4Œh8Mü¿!„hò¥#„mOc£n‡ÒÀí@ZÀaÀqÀÀ_FVš7=Óô¤¤)”äûØv8¨ÃµÕN ¢Ìvl{#ð0˜l¢áóaã~&G1ä „hUX!DÛ‘œ'aÄÝ,Š#€« ËÓ®ç€ìnGœHѰqdvÅJIÅòùQJþ{?ÐÙá‘`Áª ¶-ÏÚ¯>dÃìéÔ–o[¼ <lÄ 85 †ÝE| !„-N¾y„¢mh* 0iè8v®.î~Ä þg^J¯£NÇ“æCîZº‘ý’ŽþmÊW¯`ћϲhÚ¿©Ü´v1ðwà ÌÎÁE|`!Ÿ !D‹“ÀB!ö¿Æ‚ wtÂÄí  ü)¯Gÿ~‡_ó[ú6žT‹pƒ£Ë¨¾ôÁð~Zˆa¹·ík7ðõ3÷2ïÅÇíH°î?ÀŸ€roìè-~Z”œY!D‹2÷w„â®± ++{œ~I§ßóІŽBk;²¿šÜŒ˜С¡šìPey1=Ñ,äÝí +êGmÀýYÅ?gWÒ´NüÙYô8òDÚ÷flYøõ@EÙHàk ”†…Y¤¶‰¢UI`!„ûOòHE,¨ðDo ¸Ì0­?qý©nº_fvx¿µ·y)0ìV¼ö0ïÞv Ÿ?vßL}€…o½NL²ºõ!Åo|opaz¡äé|ò§È>ì82Í|ò·ØXQ@×ÑÁ0;@YžƒãkO;î-¿OOºŒ:žâ…_w®.Ù4ø†ƒ‹äeˆ…¢Eû»Bñµ;AÅÏ@ýiÜMS¼‡_u+†×Â9F)xTˆ%ÏNæµÛn¦ÊiÏÀ nbøùá ÎæÓ?žË´;ÿH]’óЕˆýnAíòY,~û*«"˜‘lü|ëçmÂòCíâ×yíòc™õÎB,_ë¾Í– @~¯>œvÿ+t¸Bˆf%ÿ±!DëilYÙØH…7úó¥þ¬Üýí!ü¹¹ONEmƒ‘×—a×þ‰ÜL›ï½…×&Žáßçgê¹'óñ}ÿ¢¬´Ã”åSX)ãøÑcrÞSÿeâ>`àCØðÚ#¬[P‰aÅ%e+Ë›Š±É{.?þÕ/ðy]/yˆSn>m7Ù¬^$}O>ƒ¡Ý €[€4>jÿYBˆf!…B´®ÆjUĦ@ ~6úç“UÇÁCžÕŸš µI×SÍE/}Í)¿û#ƒO;Ÿ¼‚Lʿˬ‡¯àÅKNbÓºJ”ibBw:]û欲áÐsOFJ¨Ý±}ç ï˜ØNPŠ9ÈÏ)¸ÓæF]~ ý‡7©4>j!A…¢YI`!„­cW£ øEшñYCò‹ƒv¤¢ž‚àÖUlüæÈêÂàKÇ)÷Måì'g0éõ/sÊXª×~Åœ÷>G)­5©é”rk[h¬ôö€‰j*¨ˆ‰M}:Xç”%qlHËÏcüMwaz¼'Gã~ÞbŸµøéP`!šBÑ:[Z6X˜À1†å?öš?âI÷âlÉÚI ¶~x/ÿ>wó¿Y„`¤¤’Ûo £ñ|@¨¶¶>0‡µ[û"úúPÙ2@ƒÞͯ²ï @"vºq,ý|¡…»BT>‰Ó¡’§D !Ä>“ÀB!ZžŠ»ñE&pY¿]à)ydýª5 ½†ã3`Þ½³y]žtðøÁ©®dõGoÒrRA)Lo«¦OgÓºj¼Y`oYÊ/OÇÌ."«S»ŠÄ´vÜÁ [c„+B5Å4aøE7àÏÎ œŽ¢ÅO‰’¢„Íêô_¬Bì±[üԓ؈E¬ÆÀñ¾ôÌ!C/¸˧ˆ÷C+[™cCîa§3þôçùàÕòÂOfÓeX !°eë¾›Gj¯1 :z4èb”•‚½â Þ¸âhŠô¤jñl\UB÷+ÿLQO?+‚A¡@t¶“ÆÕ¡#6ÚoN'RL‡UOüœ·#S8öÊ“ÐûT3 †‚AƒèsÂ9ê»ÿqðnUn;îæÐXü0æŠ !ZŒùý›!„ØMU×öFoYÀMÝÆžÐmäå7»Ý~(,?8víRÙ±y9ëWQSº Ç›A¯Ó®çØÛî¥cQ6NíVæL}„ìQ§Ó-ÇKñª¥8©ùô»äsÅ$|&K¶ •ÇŸNš/Lù¦d:–n½;aeb•o£V×`Œ ÷è~nwú@)ÈÈïÌ¢7¦8‘ð`Uô)‡†³ Eó„ÍB†>…¢eÅ‹Rxô~$J=uÎï{{yÜA¿ÔN˜pꪫÁ°°R3𥂎ö|w,âñ£à'÷rÆo~]V‡ãñãó»Ûà€òºû‰ÔEçûø›úU LèˆÛѶ£õ”;ì½ÉW°ðµ§?~ ºè}Ó0zñ ¹„-Ar,„¢å5•´m'vq¤·óacX£1ÚM4ÆòàÏÍ!%;Ërë14¤Mh´mcë°ƒ`¥úñXîë´­Ü#\]øIC$@ÂÒ²vt?¨ @ƒ7Å ßIçaz¼ƒqëZ4V4O.4 !ö™BÑrâ“¶cE¬X™ ãz6þœTôøZ±ÖnÞ…¶w^V)dtêFVnŽ»°“³—+Çþ@'ûDBÐyØx²ºôÈÆ’¸™$q !š$o !DË2h6*¿Ï•Ù©{‹çW˜>Øôú“¼óÈ£hÓ‹²#DMZAwrÆOäèŸ^NšI}‡ÜtJùò×g³dÕVFþùFÑ›° †]Í‚‡/ãëO–ašhaY8¶ Jƒv°²ÆsÖ?ÿÀŒ[Nc{ö8ιçQ2ünÖ®\ÍG—Kä”)œqí…¨ïé¤&l_ô2/ýêh‰B†[tÁ‘sê9÷ÆKرøUþ{ódòÆü†Óï¾+â.?[öñ ¼qçÝ úËÇŒ™Ð•šU_òÆÍWR]J5Nz\ø'üüHt6øoþæ&¶Wbgn'ü‰“ﺕ̰Ð.·RP4b _{¦‹ , a->×BF,„{E !„h~ñAEcS¡¼@¿¼žHk—I$Ô­1@W—³}ãúýâAz÷ÈdëÿcÉÌ·XòÀ§Ug]s>F´ ¥ï±tÁ×T툰䭙 Ðe†ö#Oe`v –ßËŽÿeÁwói7ìRú1Óû‘bÚT¯¦*ԷặE˜ºõ‰ì¨Ú­©EÚ—QŒ½æ÷(Ê™óäílÞ”ÉÐëþH§v^¬.#Q€ª¦¢x;Þÿ+Î?“Ã:¸KÐVo§rË"!¥@9!v¬]IhÈYyæxµ  Ùýz Àʼõ»[¨òtà„Ç_¤gw‹¯n¼€9ïß·ýzrƵç¸Õ@v:v8¾ŒÌœºíe]E$.7û¼‚ŒZ!ö‚BÑ2’ó+â;p€Žíûqçö·J÷Má5sèwâ% ˜ŽsÖEöÍ;¼ø³S)ýøÿ¨ºô|²|n>Á†ß§&â^ÀÞ±ðuÊÊJA:D”—n§^Í!†»Ýú¾©,øùMrÁõL8gˆ[7½}%Êô`˜ÉIÊÍ—ØÍ$ í€7¿/Ïï‹O…Y7ýE6WÖÑÿÇ?¥[WåÖ¦ÐÔ'HèÚµ|ýÜa*þ3 IDATÓ2øVü)  «>ˆÑÊ@kè6l‡?3Ò0J£m0Xñþs”—:ŒœòÃN‰cÃQý›Çš·þɶóÎ"?רy”Iélwº”aqu3” –å.©«Ìè½áôDZ)qß±!«S72;v5ê¶—õÄ p5‰ÉÛ²:”b¯I`!„ÍO%Ý⃠€"?­}‡#pZ³`›ÖØ¡ N8Prû!·kWVn­¦¶Ò!»À@Õî`Å‚¹(£ùù°mÃwlX¸ŽãºB°¡¶v t§ é`€HúÀ¢>lPÊíÊ—æe뇳xö…Œ<º¨É]Ú‘0NT|Í våfJæ|€Ù~}Ç‚ Ø6x» gàÐ>|ôζ-ÛDû#ŠÜ:Õõï ¥Ùô¿—Y3÷êaRòºÐó„ŸÐ±GŽ ¡ ß2î::ÄöYï²µdfjÇŸCQ¿ÎèVú(:EÉâoûé4Lе°‘ !Ä^U¡„¢e46Z› ÕÑ—ž™Ò¾ÏýV ÚôAÝæ¥TmXOZV;Ò² PP»a ų“5àŽ¿ú,ŒºÍ¬ûî Â{zýZ)ÖP[ºšÒRjK˨)ÛB°»ªýO»œŒàf¾þçmT†¨OOl80Ášáš:Â55„jêÐ@¤®”ª kIMNv®A$úžmÇ pìh ‚ÚšòŠó™†Ãâ'®å…«Îå‹—_dó¼™|uß¼pù©ÌŸ»ËµË>à½_žÅ ÁÛþ sÿó ŸÞs¯^?‘ ë«0[é2Ÿa@á ‘‡©$ŽV$¯%„{DF,„¢å$à 3 »àIKoõkÂÊãA™P·n3îú%Å[#zîOÉLl(_6­Í¥ðMÖݱâýO©¹ô'¤{w¿âµò¦Q»äM^8»/†å­ÐºœÚti¡µ[ó¿„ŒÒù|òáÛÌÿl=½);^+ÅdÍ+óòÂw°LÖTê`N|ð1²­TÌ”4vŠ¢4Xþl HuͶÄ÷iAÍÂ×ùøþGHr§ßÿ…]2Ùòæ?xõ—Wóù£Ñcà=(_–Ò¨¬>œñÏgéÞ;‹y÷]Ê{O¾Ëú…èÞ£ë™ ²Šz ”ÊÓZgÛhXP@‚ !Ä>‘ÀB!šWòô§äQ Ð¥Ý!ýQ†;¥¨Ue˜h§‚®<Œ™E°l#µ/=/ü G^y2O˜Õïý#e0}‡ÂÛÅG¿ÑùbÆ l^ÿ'úõÎ#²›+X©HU8œ‘WÝ@†×Ak§v=_üéÖ‹¥ÂN‡]q ß}t³ÿùíOíKr–‡¶2@÷#'`Ø!@c¤áõAm‡h´_­<±=$§\ÌÌgÞ!X¼›þÍB£ÁŸÓŽ”ÌOÝŽò®Àjÿ¼Ê²³Bˆ=&…B4¿äüŠøêÆ~ (·G? ìÖJ‘Õ‡üa'PŸ•‘K‡§Ðmè,Fér–Í]†Óÿ›F;üù‰ï_+ÌT¥mL4:n¡áЖbÀƒF¹Ÿ G÷ñˆ¼5ËzkðedãÏÉÈ'1ð•êÛBˆ}"ÉÛBÑüâkÄO3Q¸#iþœ¼Öï¹iÐá0Ž á:ˆ„¢£ L;ÂÆo>¥ÆÉdÄpâïä˜Ûä¤;¦C÷B¶/[DñÚJ¬ä¹E-LE—¶5½>w™Ze`x}·€^2'éýcè™Cw†×n3ŒÌ<І‡³}9‹Þ}Ç Þt¨Yò?}»””އѾO:n:˜äÉEW,eÍìÏP𤀮\ͼw>ÃÓ}™9Z;nCöóõ­ÁJõàMÏÈaçϧBˆ½&#BÑüâ‹äQ‹Óãµ¼©é­ÚÇÔZã8 Ý¥_“ZÚ¾–u¿Œ·Ó™Œ¼òjr£Ó—,?X §3íùWØðí7ôèutüNpvšÚ¤ÑÚA72åÉÑÔ?nX»¾X¯5ÔnšÇâ÷¦£Œ:¶®\ 5ŠEÏßCIŽ…¯×Q 7$n>“v—u< ¸è·ÌyóÊ*츄svh´]‘°¢Û.¢ÓË0ï®ÉHqèÖÕË·]GÙŽ:†]r íòv]Üû Aû£o ïÉ·™óç«ðr7Ý{¥±ôé{X·­Ž¡WAN;Øn;îÐPòyr?O-Fƒåƒ”Œ,€,Ü~€ÍΟSàB±‡$°Bˆæ“|µ79ÏB¹¾ô,ÃJmÅ¡4¾TRsr°,µSŽ„RP[²š[²é:é<Ò ˆÎ€"‚N§Mê;±eÕ lûèú7gú3IÉÌ!ÅïKÚŸ_f;ìì 7A½~º‘of¤§bDjYÿåÇÔÕ„QDÛgfÙw̸ï7îñ¼)¤x`áÓ·£5t¾èŽ‚òf’™ƒÇëuß® )]Ç1þâ‹yï©ðú ·ò·åÇŸƒ?=Õ­Ä­“f5EÀÓe?¾û)Þ¼ý7|qçÌV&¶Jcà¤{÷óãÐu‰íÔøŠÆqæ½ÏòÆooâ³?LdVJ;àaðU÷pÄÙg¹y>?¾ÜLY?raX)¤dàõ§ïátiðç僻ÜlËXoì³+yBˆÝ&W#„¢ùÄ‚ 7—"·óæ2¢?Ÿ˜Ñ±ËoÎfºÊí~Hëȳ#Øá†/¥ñQÇ& azS0)aêÀð`z­„×DB!LO ÊL| €21½žúÇ”;X‡öøð„ÖðògR²¹¬ñÀB›ôšô 'ýìH;¾%ey1-‡H(ˆßvÊp°ÃëÇ0@iíõ‹½® ¦%غj5˜R !»¨†nz¹]ÃáíÛØ¾a=¡`„”vEäv)tKX;ÑsޏӏbMÔšH0ˆaùPVë}{üðéýáÓ&/~ ”5Ñ[-î ² nñ< ,„»IF,„¢e4Vy[>Óò(ÃlådÓ´¬¦»ˆ†‰×ïo²ãlúýîñÏ&ž&^c¦¤ì´½V`D÷£UŽÿË+„´ƒÚé—F£IÉé‚VxbÇNÞ*¶oÃØ¹Ú=^üãZ)÷}|O7Ùƒ·]Eí£Õ»£³˜võ2' VF>íå'¼¦~tÈ0ñ¤˜ mtÛ“ÒúÝvíæ¬Ð7–_!…{L !„hMM+ñ–…±«N~KùžãírªÏ5ùšïy\2{ôlòp wšÑNi MívwÛ·›ç\;{^cDkw:Õ®žßùÁ=;Fs1}>hX¥¬±€B‚ !Ä“ÀB!šWc+BÅwÚ<†åÁ0½û¯…mD«M ´Ë“ 4½*”äX!v›,7+„ͯ±äíúÕvTtªû…"yø$¶°€‘t/„{D !„hyñFÄŽDБ֫¶,D<Øá ¸‰ÙvÜÃB±O$°Bˆæ•ÜAK®Ä±#aíØ‘ýÒ•S¦»êQs{6,w¦Œfþ–ŒükÍbÛ;7"¡¸¹åšGØêÓâ[¿qBˆ™ü§!„-#9ù5ös؉„Ñvë'(궬gÛâ„BzŸ;·Ê€ê5Ë)ž¿‚ˆÓz_(Êt;ý-Ö9W@p[¾žË޲jTSo,|ìî_ ÂÛ7±eñR‚A{¿‘PýˆE|Šz}Å‘Voâ  …B´¼ø„غH]­Z½cizaÝ¿ïà¥+ÏgSq-æ>.ßaù`Åã¿æ­[¯¢:L«|£(j–}ÅÂ×ß j{]Óþ}:ØÛçòêg2æŒÆòìPWŠ7_`ÃÒµ»õÞ *¾x€©—]̦U»43¥ XYna¼ÆŠã !Ä^‘ÀB!Zv„ª+u¤¶¦ÅºqJE§(Yî}}çÛ]SAmy N¸!yWîvFòöIÏ›žÄ絬!©·sçÑ„„cìâùúc4qnL”}4…·n½‰Í›k±â¦a)3n£1†éž's§vÙƒÕa§~¿Éç’ª¥¼{ÛÌýh:ª‘v$¿W퀷}_zOjŠ­Ým•±óùiiµåÛêp áI@!„h²Ü¬B´TÙ‘p$XSeµÄˆ…2Á©+cÛÒETn«ÀLÉ$ÀH² RÝ Ã4ë;»†µ[VQ¾v=ÁÚ Þ¼Ž´;d þ Çv;¹võ6J–.¢º¼+5›vG’ÕÎ-€§”ix!PAñò…TWHiWD~ß>XÊ]|ÈôBÝæUl[½[›¤t#¯{WLíSá¶a-¥+—¨ àÍëB‡‡’’ŽøþBåì(®;Hå¦ÕTuÏÀŸæÅ0 zõJV¯ ´ÂÞôë‹ÇÚy±¿„eÀöeß±mÝl¼dvíOAïîØ(”a¡L‡ª5Kغt Ú“A^ÿád·÷c×Ù¾nvD,ßDå–rÒór1 ¨Ú°‚›Ö¬sHi׉Ü}ð§ºç3cÐ9Û=‚/+ Çv¨Ú¸3»=^»–ÒUK …i…}ÈëV€a'ÎSj–χ‚H‚U;ª¥d…ÍH !„h9:éjk·—¦ì² Ý^P&ØÛV1sòé|3s!žá@˜ô'sÜ-ÿ¤ÿ‰â·ÆôÀæW⽿^OI±ÆJñ „i7ô'œt÷CtêžGpýB¦O>“ï¾\Q¿¿¬¡çrÜÍÐgB”é!X±Ž¯ÁªÙ+£ûNcÐeOsܯÎÁk–ÿ=ÅGS®bã†û´á¥ïÕqÌ•?#ÃÛçMãµÆÖ¥(lºžr'ü~ ¹é^ìèy²L˜÷Ø%¼ûâ'L¿þp6ýâ=ÎýÕ1¬yãQÞùõÍlø¼¬©¥çEã˜n ;ÍJ,´§ÀtjYôï;øð®»©õeàUABa®z„c®¾¯20SV½|óþø1•awOáøÉÓÑû5/^| A`õ³¿å…E_qÁ³oQõá=¼zÛíÔÔ8˜¾vt8êNøÃÝvI£øÝ)<ýç8ûÙéôéeóÉGRZГ”구_².zzzrä”—yê¡î§¥Y?$© ªÞP}T'Ý !Ä^‘©PBѼšê¤Å‚ (¯Ø°j+;CÁ†÷îç›™ }ÇË\öö<ο÷~ô¢wøø¡ÉÔ…ÁŒŽT(ÓBW¬aú=¿dkè0N}úK.›6ýêf*¾}žofÎÁ2aõ´¿òÝ—+÷×w¹ìyœs×ê¾ý/Óû3! †eRSº‘¥ƒ8õ©Ï¹ø_/Ò­›bÑË¿§¤\S»ô#Þºá JÍœüÏϹôåO6ê0–>| _¼ó†QÇ·¿¿–­ÁÁüøÿæòÓwæqüå±î­{™ùÜg) ïÏv ×Å÷2áÔÑ`t`ì¯sôù£(ÿú Þüå5„:Æ™ÿþšK_ûŠ#Ž;–•SÉÌ禡“¾éL Š?~’·ïº›Ô§sþ ßréKŸ0tH/=t³Þûez1Ì*6÷9¹—>Ì%Óæòãko ¸ð-ÞÿËm„:Åi<ˆÏ]Ïø%§þå!¼Û—0ýº[¥ËéS¿æÒió9þ²³(žþ–º0:]Ê[ƒvGR¼¶~=“PêÙüä幜ùëÛðè•Ìé~j#ÍŸ ®ÔUn§¦l+@ Ó ûÜJ !„Ø#2b!„ͯ© Bã^ƒ.._³´g£StöQ°º€€™Fjû~´;£gx ( §¢B Sk´vpŒ4\zC:žÈÀcã„ÁS{Y)÷€íö7C5ÛEÀJ#­ íÎíÇYÞBvXíÝ´_;Œ‘Ò™þü8ƒÏÇPpØ’™¬{àÊKÊ©y7e¡Ù½û0ôºH9d?~ôïäçƒqhVÿïe–TSW é™iÆÏ‰2 ®¢”š­›5°9©Eº‰ß…b·H`!„-+ù p()_³´Ù»mÈ|,YíÞà»É'³ì¾B:Œ9…ÇK÷£ŽÆŸWpÙ±12ÚÓsü‰,ÿ^¾àFÊ–, ª¦’P •»¿ÜCO$3g:_ßt )<üt.ÝÇ׎íÚ®¹=òqàø ÍŸÇÖØuµÔ¬]Tðé}—³ôq B‡«X¿<Á¹Ô8™ô8ïßÿ&¯]x(yýè~òYô9 '@ÇGã„m´bëì5]†Ò±gWì ›·a¥w¦ÃÈá,¶•Hˆ|(pÂ%”~½ï¨ë)(L'rámß“ƒ{P¾n ¶ccGré5äp¼ØÐ^F o¼ N;õ8N; VV!½N>¥o?Æ+÷|Å–Ël/!âø0ÌÆ'8¡9”Ì7÷Áô¥åKCWDp"ÍŸc‚ºíeªv„v=b!„{D !„h~±«½±dvÒÏÛª¶l ¸c)ÙY4W®…ã.á©ýY6ýJæ}ÆÚ·ç­7'eüõLúÇý˜†ÛTuK>cÚı¾Ú ËñçÒ÷¢“HõV1÷ñ;ˆØ`G è„k˜Øe(Ëg~HñÜ™¬}í!V½öþãoåŠGþ€'ÅÂçG»óku´oª”ŠöZ éÑo©~ÇqІI—ѧàKëŽOAß«Þ µÿëlœ÷%gÌü§ogþÓ·3øš©œpÃ…¨MŸ#m€‘–Šé‰›7ý h NÒt"íD‡*/½¡^´,œ£A;îÊ« s34(íF(NRƒ” 5 ÞäùËÏ£¼zž2‰!£N%Å,aæ}O¢¦ÿȆ'.èPŽ;wÍK;P¾n9@9PCÃg29H !Ä“ÀB!šWò’äé%¥¡êʺ­Kçú»1»™t• ³æÅÙâΘ+oƒ CpǾüé‰Ìš5Šmµn"`*MÉÂé¬ß^IïßÌ…僺%3˜uϘ^ ¥Vüç.J³aÌ/~‹S{²µ|:iß~9ƒªí! C¡µ¦ñ~³JϪè5q2}ú¤b;@ –…S#ß _`3³ž}Šöã/`ܯO'´#DõÊyùÊSÙ0çMá ñ'÷°5îêV–2ü´ëïÅ~!Û·U—•V`W®bíûsñv¼”ToÜ• †7—¼¾ŠUsfRQ};Ù ­ °e¾XMʈ«ðš ÃØÎú•óˆèq†›<¾qÎtðdàIñÛ«aZ˜^ØúÙ“”—:yÏtFŸ6O:TÍ|œv O“ùª¹³øwECñ‚ÙqGÏœ†g$˜BìIÞBˆæ—|å׉»( k+¶,úÓÓÄö‚2Û>ýŸÜvsÞŸM؃$®–Ϫï2j¯ûà”—¬ØAùü/yóöë©´¡´d#‘HÍÞÉŒ_ŸÇwÓçcÛpê„j!Õ‡é1vÑÕ€—¢£®Äb3¦\CñÚr[·ðí/åÍ)·°fõ& äÛ'Ë´[ÉêE%8F;TE¸6‚™’†ÙHMmÞÎöeK¨©tèpÌùèºå|6e2[‹w`×óõ=·²º*Lß‹N =5iÄÃʤÃQçà”|ÄÌ»ÿBÅŽZB¥«ùüžÛ)‰hú_|,^4Êçaãÿ̬צ¨®`õóå³·æ“;v<z¤º£*Ö­¦¢¸åé8ÔT¨ÚÁÖÏÞæßÿŠˆ®¦zûBÁØ8È®µd¯ÞŽÀæù³VãÖ±Hþ|66j!„»EF,„¢ùE'Ö$tÔ‹ 4Û¶-›ßÑŽ4±‡½:ªE+þÉ‚Y?áëçëN݉¯¤ÆÎç°ë&ѹ“—‘P­ ‡Ÿ@ß÷°ô¡s¹ÿaÚC»‡“]Mñ?'ñEawúÝøË&Ç»—àËN]n^IîĈ_]Lû|‹:"Á@B‰5;!ìÔql ޏˆq—Áÿz†''ü~¥©ÓÐî䛘0ñÇ™&Ç_w Óîþ?^<ý#2;[T¬*Ã×ñ0FOº•TqµüÐ6ä9‰ôÈ›ÌüÍ8¶núgÞðKŽ™¿œžx”ò*éieToStÊŒ9íTw¢OÇgÝÁðkùæ•Ûy|棤[¨ÙªéyáŸ6~zÛtB™2˜uËÑ|¦Ü‘™ôÞGsÔ ¿'Ý'§7=z÷`á«æ…‹8ã“éØmßüéLæÜ©Ðt~$íò¦óݽ“Hé5–A–¡`´Ç®±un®F}¦ƒÂqÂÁf/[g˜P¶z5[79À&¦èÅn±iQB±W¤Ú¦B4/÷¢ð©€H2€l 8¯}ß!§ÿô ü¹ÙqÛ›ƒ[P³|kæÎ¦jG5¦7“¼žGÐuô,jV.¢¼¼ŠvƒF‘šª¨Ý<5³æ¨¶ñ·?„.‡O ´ékŠWm oÀ zäS¹ð+Ö~7‡êªZ,_6y}ޤëÈÞ˜ ª–Í£²6Bþ ax ÷Ý7¯aëºä Mz†Ó ³å«÷Ù¼vŽö‘VØ¢1G“á7ˆ8`(‡â9°eáB‚onG GKÇC:4:MLElûf&Õ2ûŽ¡C·\T$ÌÚÏߢtõz"6¤wîM#O$%E5zn• ºv«?{—ŠÅؘd÷Ì!‡‰Ç'PÁ¦EËÉèÔ•Úå_±ms ø2é<æDÚuÊÆ»K·VoZÄ–%+ñæu¢hØp‚°fö,BAMjÇžt;àêÙ´r;¹CO$7¥”M«Ë(èwi~Ͷo¿Äiו¼.E(í®ÚTµèvD<ô‚×l¾nO Ì}îiÞûÝÚq¦Ëq‹äUÕ@môŒÞ"H !„ØX!Dó2“†ÀÂ\¤áY@&0Äôx5ñ¹/TÇ!ÃpšqäÂðD‡LbË«à„Ýß•Füï& Ì:â¾e¸?;Nâþ”›×\ÿzÃr‹” ¦é>¦µ»½é¡abrÍëëx(·¶îÌ"÷Ú¶Ó¨Øþˆ®e7²#éíÆpÛ÷š¤6kÛ}?õSÈœÄJÞ†Ùð¼n8ñïSÅOÛÎØ¹1¼ œÄ÷;ßÍ•{cyáÍ_^ÆÂמ^ ÜìˆÞâ‹:Ü "„Bˆ=$S¡„¢ù%Ï[·ãîcµ-v8´yýìé::¬Yîì¢Cª#‰=EmƒÜuLêŒïjDÚNª½ Á5½X§|·5¶¿=ÝnÐd»â±‹®µc'>ßèùH:Ÿ‘¸c:Wç{o)ª·n§xÁ,€%D«°ót¨øiQ’c!„Ø#’¼-„Í+¾CßQ³“î«€Å+?žÖ¬£B4ÆPP¶zåk–ÙÀBÜÏ`,È.$q[±×$°Bˆ–‘œ´m'ÝBÀÊmËØÛ–-Äôî·vŠËþ÷ Žm¯¶’ø™lj´B‚ !Ä‘ÀB!ZFòr³ñ¹pôñÁª[×~þÊ@²ÞšC47Bµ‘o7eDÛ³ÿ¶Ê€Úò¬šùÀ·¸yñÓòâ („{­ü×+„ÆjX$çWÄnUÀ¢eÿ{™ÚÒªVë|ÍZ?£ÍPàT—Q²p µ•½:ŸÊtÏMs|9*áí%/]B]md¿¦6ÌžAå¦uÕÀRš*dÄB±O$°Bˆæ·«iPÜ‹XÇîÛ-óg‡Ö}ù‘»BQ S ¶Í~—EïN8¼¯¤77eAݲ×xéÒ“YøÉ"Œ=œ^f*Íö…Ÿ°ø½÷¨m†scø ôƒ»xáòÓY¹¤k?sJA¤&Ì¢·þƒ ¯Â­_¡q?±[òˆEì^!öˆBÑr’ç®ÇwæbÁÅF´^>ïÅG±Cv‹wô K³öÿ~Ê›w=BmÌèR¨†Ñ°üjl‘Rîïõ·¸*ØÊHü½þñøåkiØgìõñïO™ K±&×hUIX¾µ±}ZIÓžìµ%DjMN-ktÿ ,ÍÊ·îåí?ÝDu¬¤ö4vv¹O€ÐV½4 IDAT¥¥„Cv}{bç®5‚:Ã„Í æ°ò£×à ÜÏ]ìXÄN…b¯Èr³BÑ2Ü‹7ÉI²ÉE˜½aÎg}6ÌþÌÓ}Ü‘D‚-רڒUT— TIÅúu¤õìŒS¹‰Z•CFJ­‹âë4€¼®ùèší”®\IUi†/ŒÎ}È)ÊÇpj¶¬#²Èì\„YßA¶©Ú°–ˆá'£ ¯‚%ëØºj ‘øó»×ã,GS½a-ø³ñyª)Y²òÿÙ;ïø(Êü¿§lɦWH…Ð!ô.ŠJWEÄÞëYî<<–;ÏrwÖÓ³w±`ATÀ‚R¥ƒ ½‡ž„^vwf~<»d²P$&âó~½fvv3óÌî ó|žo#,½IQ”íÙLþ¶TWyñ$µ IÇ,ºHíªêP‘³‹C»wPUîÅ“DLËDD»Ä»¢¢¢£¨uÚU*sv’¿c UeU8ã3hšÕ•°0(ÏÙCyá!,%Å»¶Ó¦9N·†¿¤Â›(;X‚êŠ&:£5±i ‡³yi.¨Ü¿“Üðz š´$¡m"Â#{MC ¨Õû·SYìCOÌ$*Æu¸ÞH}`)ðã‡/bú}ÙÀVjb|ìV³£¹CI$É/B ‰D"9ùJ¬ÕcÁfëýÕ•Ù+'¾Ð&­×(ªvò‡u hÞRÏZ`êñœùЇtéñg3âI´ÉJÆðkø7Gv¶ÅÿxžçŸÅîÏfå¼ t¹óu®ž²š«Þû‚ôô–~ð"…>­Î<­bÙ‹àW…KUÎÒY÷1]‡ôEIç¼×Ò¡{ëŸÏæ%?ÑóîO¹~ê\þÚ»4uæ°âùרRÀ·g93ºŽ}’9ë¹ïÅyŽìBÁÔùqÞzM”æÖ=áä}û _Üz Âs΋Ò­ký‰ E•Á’·§º´h"”EÍ5WWŒ…e[J$É/FZ,$‰¤þ8šÅ‹˜1öÚÖ7Z–¹jÉëÿêÕ¬ïÂbãDUç“ÙE#®]?SPžFZÏ„‡›˜†ÅÑŒ¡/}CÏî‰>°Ú ¤ÿ_ÒíºË÷€Ù¼ÍÓRÈÏÕ0« i¿‹hÿ›fÏâŒËGí(!{ú‡(î³è=´ »&aw‰ÂÀ§? ç°X&$==ÂQÍÙòáL*.;üÕ¨‰ùÊ·´h‡q` UÕU€…‘H\ëdúÜûQí>Å“áwÒâê‡iÙyûwB± ü¬A„¿ø*ªïX¥½ ؼT*µÝå"®M[âïxްÌþ¨ñ)D¶é@lz2êO‡HîÕpÄœuz\O—±ãq‚™I“¬ Š6yQØ¿b:»øè~ï œ~ÑYàƒ¨ñoR\z–a`¡¢™eìøìV|;™ƒ­ÎgÔÓoÑ¡M¾ztyÓ\°öóÉlû~ªø(£öõ\†fˆ’Ù $É #……D"‘ÔGs‡ º ø– ðMõÀ†Õ,ŸðgD|ú$cúýX–~Ó‡XÇDÕ³hÞ"£J6¾ëHR '³à—‘·z……TãÉj&¨ mè8,‹o?œJîîá _ÁOK÷Ðì²GHŒ…}Ù›€j–½z;'º±,¬JöìSû‰ò*ËðâJëI\Ó8üÕ F4%¹Ïö}ú“Gµ%&³ -]FëA£HoŸ @«‘£ØüͧLyû¯änÙDeѪ+½$ëuDT‡ü–GæØÓYÿÜ4¾¸²+áñíiyîhÚ AóP*½˜¦W>0ü6pÞYŸ3ٷq=¥yû¨*-%¦M Ês¶*©g´Çª¿ŽÄ® vJx¹M4›'} @dß¶d¶JÀ8Žú5h:íÞËâWËú [j%³»å…Ö¯ÂB"‘œRXH$IýQWÚYPñÀ9À` 8ÖÊžîžÞóL¥å€ÁõÈ]ÿa ‰ªV°êޱ̜>wÖ:¹œvÉ)~÷› }‡O,cÈ]¸?¼†õK~ÂÍGT)I´:]ň!³MO"£]˜¦‰¥¨¤÷ŠîJ'Ìa åeXyr¿Å€‡&Òjà—ìúq ûæ~ê—ÿÊÊ×¥ý¿f1ütøü†sÙ³­€Ìs¯¡ÃØ!DDš,~é),ãø*Ì4Úß:OÖö­^¾eß³fƒ¬™ð ÿô#Æ_v8S“ª€æ/dÞ#cYüź&sè¥Ä$%°{öÛìßã_Bu5  Ú£P4-­JÁúŽ&1"3ŸfÁ#|Û™PyR~¸Ú(`Z°àù‡8¸}c0¨Fˆ‰jÛº=Æ"´~…D"‘œRXH$IýA÷“x 02ÐL`ð*°óUUºçtR»¤ë¢n†R{i©`ÚÁêE³ÑúÞÌå/¼LR¼ŠË3gþÓéR-â:MJ‡Tr>y–ÅúbÚö'£s¦´ˆ8 —Ì‹ÇÓ¹W"~(¦ÁÆ÷ÿGIT:n—Z{«‚oßFV~ñ)±ƒþÌ€ûÆRuÛ¿(\6/oÇÞ%«)p®c϶ƒt¿g ƒn¾gÙ YúÜcXªëØÕ¶U lK?œ@RÿK9ãÞQø‹½”nŸÃg7ŸÏžÓ¨ò].>«(¨.ðæ¬bÛì9$ô¸‹qï>CT¸†ƒJJü½¦ 4p$¦&…Ù¨“ÐüP¹m“.Cò}Óè¬ÏG·Ûž§kú­8Uïÿ¶Ã¾%=#ãdþ¶ªã«&¾Áú©ïú¢"Ÿë˜]TÔ¼-……D"ùUÈàm‰D"©?‚Á°~  p0xèXÞüXˆÈÚS|›¿eíoÿy3ÞòÊ:ë&üªN©*ØMÁÖ]ø«ƒSíVÍBSQœ Ji U ¨<òÿÞÃÒe©®*§¨°Ë5>ƒýúQº}*Ù›³‰zqQ`:ð&<0ÿÉÛØ»-ï¡|Ö=~#Sý+[6lE )ý­¨@ùV~|ù!f‚3ʹÀW;æ~]ñý¿ïÂôyQN’¸PT„®#PÊç2ù’A¬_¹U30ªTLE0Ô¨6ôw.Öú˜xNþwZ ³^þŠ´½°vÍåëëo¥Ø ¦O¡Õˆ±Dh€³9ÝGœ^Qo"®ÛhÝ{7Þe“™0¨ ¯ôn—o¿Cü€›pÓèøª*±ü>PÀòƒ§Õ`º^{-¾ïñÎvLÖŠwÿtþ„ž s ½GÓ¼K6¾~/öˆáåsû±ë`MÓÃÙýõS|ûÁ"TMÁÓÐkÈ3A‰iÃ;¯ÁÜ>…/lÇÛ4çõK.¦"¼+Y×Þ‡Û )m{b•íeê˜lÉ £Ý¨K)]?™·Ïˆã§g0ÿ«­4í–Eù®¹LúÇc8Û]ÀàÛÆãÊÊ'#ÒøßmXþýZ]õ(Y}31M¼> ü>h}É=tnJÎW°ô³å¨®“ó»êNØ÷ã*fûV.¥ÊòдG?”âMp“Ò½š*>£eì^ô ÷D ‹%¡ýÙ4mîbû¼ïP<4ë×§XUäý¸” G ©[£ÙŽ£*Ë¿eï¶í†O“ Òû &2RÇ4Lý´” G MÛµG T¿ÖŒJö/šÁþìx½ îø4Rº ¥IË, ªó¶‘½h•^Üñ¤Ÿ6íÐ v¯ÙCxÇ$ÇûÙ·n;1-»•äƒ>嬘EΆux+ \±É$÷DrËd ?XÕÅì]ñÕU&Mz#ÚUÉŽÓ)Î;„Gr·ÁÄ'T±cî"Ì&Y4ïÒ·òWÏ$gë^LË'¥3½ºàtBun6¹»r‰mÛ“È(Še9Û(ر‹°øv$¶KýÕAúº öý¸‚)CɾÛO a+Ad…* l« ÆŠj¹âB"‘œ0RXH$ÉÉ! Œ.Š€YÀ»7§`%n7BX¸̓áÔˆŠ Ð:ûŒ½9쬻ÿ…'>^øåÿŠáŸ¢Š Ô¦ TüöLEŠÈ.d'Xù _ÍvÍ)&þÐLGЍ4}8Œ#`™0ƒ}Õ!þξ¯`œf ­V ¯V0!ØWûþ,Ul³ ±oÝ)ÎË´‹ŠÐó²êÞÿá㾋Àë §˜%¼›T]ô1øwÁs îCdÜßµ¦‹s VØV5ñ÷¦Á¯ŠŠ*öµkñ¦?p-E{¶o¦"â**BÂ.*ÊÂÂoÌZ O"‘H~1RXH$ɉ£­qÀe@4°ˆ=Øgû¬h:à4w …S#0"©qÑ–Ü©wÌà_ µGo1ˆ®‡t´’ߺ ¼eÕ¬xÿy–¼úÕe%kÀ!„x ‰Ò@«l¯ ´Ðêïòj’H$¿),$‰ä—…puœì&_!b)êBA #Å…! ì–‹ˆ@ |& 8ÛÛ*kÔÕô¾þ¢SSDEmƒÚ.?’SE E¿÷Žy3Xöæãì^>¯ a[FE"h­°[**¨u¹@É+H"‘üj¤°H$’Ÿ‡ŽpK \x½xa(<Îߢ[-‚â‰aêhAáÑèÖ":ë‚+h3è"b›µ!,.  é%j¸bT|ÕPž—KÎOËY;õ]v.˜núª*v‹€]ÁPnke¶**êr’WD"ùÕHa!ihä5xjr* Rbâb ' L¾A¤óü%……]\8©A·¨`‹@ »åÂ$퀶NODBRû®Ä·hO\‹v¸#bp„G¢œìµŽSé«¿·_y ¥ù9lYÇÁí(ÌÞäC\ƒõO*b!((*¨Áס¢BlK$’zAê$¿%Jëum;ÚkIã"t0bÕ±^×¶ßN BL\†”-ÞBdv*?Áý*¶t‰ Š B8Ø-AanÛæAX:\÷Ó-p£(ºªjм~ïX¦eZ¦áGàBTD\ƒ~jDCPDØ­•Ôd€ª¢FTÓË4³‰ä¤"+oKê“Pñ`TÕõ:ô_ÉoÃñGöÊköösöÙP4Eb_d[‡€ï€-'aÿöó¶Ì’¼ÖyOØ¿»à߸K‡~ Bœ„cYÓðë@¨ÉBÞ7›Ð{#X\ÑGEÂÙVM°ŠŠ [³Æ º>÷zOJ$ɯF I}P—p°û—‡~Ö y-@‹Ÿû{Ø’ŠÖjvW û icԸأ±E«Ä½ÀbÄ îdbQsÝ›u?GPãÌâXݨ×UÔÜgGÛ—¤ñ¼6ìbÒD\'^[ ŠŠ*jÄ„}›]Tø¨íúÔXî?‰Dr !……äd*(Ô%ˆ‡› ˆ¥&ëM Ù1ö'iœ„Jªùós݈´—ňß1P9àð9Ôã·ä(@s„›Ó%@*bÖÿψ@ììz>~°¦E])>ƒ×}¨•"8S$º¨-,‚V ÚÂ"TÌK7¦mlA×%»°°[,‚ÂÂþÚîú ÖîËBf’H$õ€’“E¨UÂ.*@<Èâ³¾ë}€@¸+2Z‰JNGÕXÖ©:vŠk$LŸªÒ"*XFuU°‘s"ÈÔ.0Œ%ü6 D¤‰½ÈC¸9}üðßNÐr´ZÔÜC¡îOöÁep hº­SÚ†Šz8å/Äß-¡±H¡"l$¿†º¬öA1»#2%£}·Kn¥Í‹ˆoÕ›¿‡%š|ÔýÎ þ¦Š(æPYXÂö¹_²òÃÈùiéDV¥Wn výЀң] ðOD õÍÇèQ<5–‰þW§IÀ—ÀÚ9Åz"T\Ø›Ãւ²Ý.Dtj ûÐd ’ÆK¨[`]ÖªPq â¶['ì5*ìîORTH$’zG>h$'J¨¨ζêˆXðÝvy§ ¯Õ{^óWZµÀ2Á8Ù¡°’F‹¢‚¼Vü2Ë'ÃÛ@=”ü!0ýЬï`"“Ó#.Ôq×À-Å…D"‘H$),$¡jvk…p§§õ8‹ˆÄi­Ô+– ®¨h2Opâú;–;”D"‘H$’F ‰£ÅWh@Ð+óŒ¡èn¥Áê+ènÐ]G6M¥>†˜¿rŸŠ7¨Ç¸Û4'èŽ_wœ#|Œ·tñÕË÷u’p¸!£×EiÈu¬”³øL$‰D"ùc 7t$ »µÂî¥]Q‰É{5ØNÕ¡pÕ¶®øEU°,ÑËÅI|»ž¤uî†ËÍIMƒ«˜`à9+:TlYÈÓ¿§íؿѴ©¿½o ¨þ vL{—B­݆õó$ôÙ¬ºû­¨phùçlÜdÐõÂÑxÂÔFi2 HìÐðø&1e¹=€]‹…aRsÝ6³H$‰äƒ’Pêr…²€NQ)ÍqÍÛ6XQ<ÍÅK§1÷¿ÏáˆKÁíb“Êûñ¡‘6ò/ è?D‡—ØGzÖñ¾ªAÑÚ¯XøÞg´¿éIZ´N<¶`±âûQu¨Ü5‡Õþ&Cî¢iª[ …íÇ1ÊÙþÙì3î¢ëy}QÌ:öy¬!sÈgt¥Š5ïÿ‡½¹:ýîü;Ñn0ìÇS¡pé[üð¶E«¡£÷Ø„ÅÏ9Þo„é‡øæ­ˆJm¦•ä¶ãÈ,e>j¬k'AŽI$‰D"ù5Ha! êÌ£!üÛ[Ç4k‰'! uÃtÐR@ÁBw%qÖ3hß°P _Þf–½ð7Öù+{]ÎÐ+ºâ«Õ!NÄï3õš /µD‡æVÓ|^£4TåodÝ´ ¤\ö('TWÖÝ7ÍŠü†ª*ªH[€ª¨hªÓ…îéT‡ø² z Ýîþ”¶V:J0…¯º3`y±„¨2|µ-1µÎM]ëŽ0'»—LcÃ&‹¾ãÿŽªaK lrÁ×K%&JÇ4…eEÓ8”¯¹ÀôÙ€ÃuËÝ£“Ц3ûZš D‡¨mQ ân’H"iTØï•Ð0–miØšD"‘œRXH‚(!ëöA["‘Ü©wCô+ '±é­ˆk†?`=ѳZrÚÁl^:žÒµ[1´®h(ݲ‚u_N¦¨ ÕCR·t8w8.U<=5Í$gþ$6Íýʲjœqéd º’6}3)Z½˜uÓ¿ {Ê Äè×Ò¬c»Úsã è*ìÿn2›- ¼¼gT Í_BË^j ”e/déwóÈÛW€#:Vç^EFÇ4¨6©.9@‰Gª"âTÙSß&{M6†å 2­-­‡_FlB¦_ˆŠÒM?°ö«©¡E&xÚEt:«';>~/¦Ë IDATüÝ ReÕ[/Ðù∋ «e•ð—p(_!ÉB¨:w=¦M"O¦â ¶}:]8†0§¤n(,R»öeͧo¤ @1GÆYH$D¢÷D<ƒˆƒ ¶Ø@óa!-øÜ8LàÊm­q¯å!Ä|°åûŸ‘H$’#ÂB5ƒ3û€Í¾žJJJ§>'5vá× ê4‡x**¨”åeczfºË?áó»o!?¯Š&}ÎÄÜû5«&>ÚÕÏ2öþ?ãqZl~êf¾~ëMüQid´iEþ7o³âƒWøÄd’Ë`ÓœEìžþ‘­ºÒ¬sma¡i°õØöÏcÄ5'5«%¿žÀ½F×|ȹWDQu(:ÈÜñ`9<„E…Qºw7«>—aÿI·Þ±¬ûo¬Uo¢ýÙY¨¥,|èjMý=!0‡AQÎ~–̜Řÿ¾MjЇÂy™2þz;ˆNIÄW¸‡Uï¿Í¡^Ä1÷# ö`°öÃgI9{,‰ña‡c;Tòf¿Ì·«H9m.ïf^w.›÷ß²FE1?}ü"Ûçìdô«÷à¢áüŒ,šdõEi‚eÅ#â,êºNƒfÒb!9p‘ ´Ú-€ ™šø·`Zˆ ÕÎ@)P  F4ø9R;ÇŠ,S€8 qY¶f"DÆ&`°ØE'ë H$¿O¤°±g× ¦ñ ú²ÇënwxL³Ö :{  ¨*†ÿÞ}ŠÂæá˜>ƒÊ½;)/Ïa÷¼OÑ;£ûˆ¾P”Ïâÿ>@~¹‡sßœO×0 ÷±ðîKXøñƒ¬ìw>=3³™=ñMôvW2îµWIMñP¼ö;¦Ý5–ÿ|€+¿ŸËEá&ïßu?g>7Ÿî§·«Um\ÑÁ»{!sž~†°ÎçrÁ³IkKéÚùL»ù"Ö=ÿ À£«ø«ýDöÈ'_&¹Y »§=É”{ïeÙ«OӺ˓8âpN4 ö}÷‹§~CæÕÏ2üo& ›^Ï´§ŸaÁçWré}Xôܽ(ÌdЄiô>³5•ÛgóÞ%#Y3õSnzoeéÇæmpùäÄGS;`PœáàTP5¨Øü¶í¡Ûß?æÜ[.A-/cáý£™?ãäçÞE³dgƒ JË‚ˆ„¦„'&«åyûS5Ô]ϤõBòû$!Úíâ!hŠÀ^ ؃Èò­1Ð/AXªNrÿ""#.ЧT„¸iŒîCÜ{Ûm=°Xˆ´lH$8¤°©+Æ"ØÒ£’3p†G5`÷(*¦QΦ¯žg§[Ç2-ÌŠ2*J‹ˆÖƒÄä0Êw,'wóvšvŠŽ:aV‚—J×›odÕüëÈ_¸„|öRT¦Óïæñ¤%{ðVBL§Át>${_ü‚œ­iêq :µ#F­ªEëæQTæç´1‘Þ:oDv9“Þ£ûñé‹ )È. CÓAÇ[&53o%dž{#éï¼Â޼-TWÒ¾*`À¾…¯â'–ƒ†àÝ¿|K!¶ïPš$>CÁŒåŒ`Çæ}¤ŽyŠn§·Æ¨wæ@†>ô"y185E×EÐG0ôþh_'¹ `Ã7“hšIÓ¶íéø—h[9ŽXþ“º;Œ¸f­(ÏÛŸAíëÒ^ÇBŠ Ic' ‘6¹ ôºÍV/P äkÉ«ÀN„å¡ a}ø­e~i í Ù®"DO4Ðq>§—×Þ_L,·"ã7$’S),$P[T„f…rÍc3Ûáp…5¸£‰ehÎ$Î|zmšGâ÷Y(*Tí\È‚ÿÞË®)O±eÜMdV¤¬âNï‰Ã¤ò;©®ªÏKua.ЖôÖ™‡c5Lbc›ã0 ¿q8ÊÛª#«ªBÙ®9YdÔ²ÀpFÑÔÇβÚ_©eø‹½@.ßÝ=”pM>>†Ê"'±I¨Ì/ÀðCtZ4Š"ºhx!ó‚ki¥Þr,3ØïcŸ¦ß"²ëE ¸y)KߟÀô»>GuG‘Ô©ÜÅé£ÓQ¬†ûÙ- ô°pb2Ú°gùütÄõ襶µ"(0‚©g¥;”¤¡Qƒîî@? 3b–? aÈV³ÍÀn„5b7B\ü0V’Dßg¶G"„Sg ?ðÄýú0ø áB%ïS‰äD I(v´^4Ni†êv4‚zªê$.µ q-j‚·]ZR¼e;ÿûåha­ÈU]ð@V4ðæãóVaá@±L ˜êêj%R|Æ„ŠÊ"ü*¨ºv¼® †9PtšáÇ Ì™+:TïÙ(˜Š& •^ªJ+QÕñËG…éEŒÿk©–Uôdøã/åÓ²PTŬBh†»z¡ø|ô+:.œG¡IËîí~þü½e`I×›ž¦ý˜{)Øö#»¿ü„­Ëæ°ì‘ˆÈ\Bß>i˜ÞŸ¹¿“% "F§6áïjQ“Ö Ic áÂÔè‰PwFˆà‚@[¼Xæ ¤(yw½R , ´7.Tgc»•À;À\„›—D"9E•·%PÛba¯.c=ñMÐãVó›aY˜~¦ŸÃÍ燨ˆx0+8´'Ÿ°Ôd¢ã!÷Û79T.85Øþý”U+ÄõèNTjk`/k¾åg8˜%{ØöÝD¬è6$6‹V @Õ]h!2Ü0!ºuST!;¾ÿŸN¨åy¬þú;ˆÍ .-Pð—±cÞ7øpEAåÖ¹ìØ¾OÓ&x¢=‹ˆ…¢j„7ël§ÊÙ„ƒúÒrði$Ä™,}ö^VÌ[Cxóθ"vÍ_Nq9¸"A-ÛÁô»ÏaÚ¿¦Òï,PQ‘üX¿Ý:û'ÝÃG7^ÅA2í/Ë9~ÆÈïo•ù% þ¿„¦ƒ'.D¶'u»CI‘!ù-ñ€Û€3ñßÖÏEÌÎß \œhw Ü›Š95EE("|*p%ppÂÂñ<ÂÊñBŒÉñˆDr -’ öZvqá¢ÃcPuÄü[bZ&æQ"‰ ÃÀÂGeNzÓ³iyîÌ{w2Ós®—£zíwÌ}í="»Œ kX{⪠m‹86¿q;3õyœ‚}¼—H—/`¹K…–ÃÿBä䋘wßüÙ÷í,cÍ‹³}gßÚGbý/À×Þàë£é:° û¦¿ÉîUd]<Žˆh.ÕEuî&V¼ù=Æ]GLtXmk“e‚ab™àiFîÓ˜ýØMTŽ»š0#— ß‰êŒ >#¥¡½¢-pEÄ@M¦œRj[-@Š IýâA¸÷tD̾÷G¸5íE¸/ÍæËÔÑp"¾ ´¦ÀåÀ¥Ññð&â»ôm‰¤q#……Žvq¸œQ± ïe‘@dj:ºCE±õÇ4 ²Us’RÒ(Û<âÒ¡ô¹ëUL"Y>ù¾Zòšá#î´óüÈ{$„éiϧ'ãxòÖ¿>ž ºÓrÒüúWyÛh,Äg ¡uÏ.ì™ûÚu&=«FX`âIç¼'>aú?ngÄÙø‘åsÒáªrö÷  ¸Â‰í˜E».=Øþé#¬+ó¡G¶ ÷ýsƹYÕE(ª ¦(JÑy>óóþyó½ -)ƒ~ON¤Gÿ,ü>ȺýªÌ«Xôù«LÿÖ‹–Lç›_¥ÿã0ýÐaÄ8¶¯}Œuïˆ%Á@m1#ìFd/ ´Šª¾tÑK_Ƶv¾“Èð¢X@jµŽKW  EQQExeû³©(*w$1™¸œ5•¨U¬ª*Š÷fã­öâˆN!&-Å TVÁª®ÆWíEs‡£9Ô#ÜÁ¨® dïª*«pÄ$“’$¢‰"IJ@Õ º ‡Š’21M‰LŒÄ2A«*`æ­]Øä¹‡Û^ù3ø„û¯¬ˆâœ,ÅCxR aQ¬@<¹¢ŠBz•yû©ªòáŒJÄãA±Ä1üee–Šîññu)V Ò90×4¨ÈÝMIþA,ÕEDÓæDÄ{Àßp5,‚è.صh!ŸÞrN©¯¼ì~„›I"KN9"SN5b–ØGÃwYòû¤ Ð+н±ëÖsÙŠªƒÎþ‚só€g™ Ú#‰Dò‹ IPKEðµCÕtUs8Gx…B`4\×›€ª ô²eŠMá)™D¤Þ7jDˆø nbZ¶gkŠm‡ÏÕÅéÂåta¥üšåtQ-Ú܇R [ýq%$ãNâÀôåùlþö v¯ßyZÍç ?¨a1Ä·Š9|.öÁss'¦Ø·eÔtÏ2A Ùfëê³b›U°ÄñÜI„5Í›ŒÚÇkP,ÐTUW®y¡×©¬e!9¢™šç­ÏÄCW"R¤îC¦Iý­ø!Ðz÷¥ÀCˆ€o‰DÒÈ‘ÂBµ]¡Bm.UÕTUw4ŽÀíÀ29va?‹c€û9éV­Úûcö%°®èà?¸†yO?@±« ý.=Õ.HŽ×¯ýP¿C÷Õçú- T‡USU„+Jhûµ+‘‹xà`Ѐ½ ax1ˆÝÜ`½“YŽÈ"Õx‘=ê=àQ„ •D"i¤Ha!Q޲Ä¡hºªéŽßª?,´äÓóáFLW±iIÇJÐÈPu'Š¢©ýÿ,»ÅBÖ²q-3à#Ü›¼@ð5p¢ZtACuPrL£+¾‡!¬#­HI£D Ô¡.Qâ½£¹IN ÐÝD·h4"ףƈRëŸPK…=3”D¢!¬£€Ó™œ@T€¾‘¹i]ÃtMrT"êa|‰pz ¸øBJ$’F„’ vW»k‰a™¦eúdöÄâgÌÏÝIs‰Â}þ£üŠº~ïñ+mÿlÙœ¿åóaY†EMuíP÷')*þØÄ!Ä` "£Ó~D1º§3ß…4ª«Zò ÉEvOGd“Z€(¶7 ™°A"i4Ha!Úƒ2+d»a>ÓðûäÐíP‚Y¡Nä»S@ñ•±áÝS™qÝõ<"BÑ |Ó÷¬Y¸ö£®"&Î}Râ$óû\( ~¦ß0©pä)ã,þXD!,cŠæÀ.j Ö­Šªs’zc&¢àà½ÀDÌÌÝá(‘HYéRJèL°a~Óðy׈­>‡':\ ‰VQ}XüÌ­Ìÿ]üüŒ›­®ãEìøì?lZ¾¾Îj Р图`ÁSO‘w U;ι瘊•;ç1ý¾«Ù²nç‘ûk LŸË4L„_µ´TüqiŒ@TmÞ€¨dÝQÙy B`<Œö•¢âÔå ÂêrD5ïo¨qy“H$ ˆ;¡ƒ4ðZ¦iø«+ÍNs‚ª¨p8ÅvEG˜¨qô0Тvƒ¢‹Ï¨ºØ¦ˆÁ´}›½æƒæ5Ttwí}+:8Ü 9D­U |^Çu: 6MŸÀ¶eóQ=õnS´Àß[â+îëptP.±]3øº[,MbûÞΘ7'žu8NCu€øž4§X¯uLU'xªº tUüQ’ÍÚiïQx°‡ë—þ:õ€ ¾ª _µ¼­ëJl$W§¤ˆÎCì.@doê <‰¿#Üž¼ ÔGIÃð0|ÿpMƒöF"‘HW(É1QÈ|Õ%E ¿­ˆ"s¹?|ÎÆÙs¨¬¨ÆߌÔ!—Ó¶[s¼»bõwsˆéq ™“±L0e³fúô¶£èpZ&Ek–½zi§ubÇ´/¨JéOçž-Ø9÷;Rôcÿ¬)ä©ôºü&b#-ö~7­ËÖãõYx’šÓfø$¦ÆbšPºv›6“‘•ÉÞÙÓ(.*Å“L‹s¯!5Mgí{/QYaÂîu¬šø1Y#Ææ¬¡êàÝ¿Õ_L$oÿ^Ð=ĶëKÇó/ÅðKSÀôå³}Ú{ìXº?n:¡ãˆ¡¸œPí-¥¤ ŸxÃ"LB§dý,6ÌœGiqÎè&¤õ?ŸÌîY(~°T ê ë?™ÀÞ [ðã 2³;mG_IÄÁõ¬žò%{f¼ÅÆèÈìÞå°KWC (PUZ„áóú×cC_‰’ú'Q,m0p!"Mì2„¨˜‚(Z'‘€¨~~1Bh¾4þƒ™Iƒ ……ähX.GiEQAòñ‚ŒëÍ»ßý;Sž|Œ*GQ±Ê÷ídù¤/¸ðÝoH>4Yÿú mîëI«nÉ~ðå­fÁ¿î&ìªæt’IñŠ)Ì|ì ܱªJýÄ^OfÔ~æüçvÔ×£¨.*ÅÕãrºŒ½Š/ÜÍW/¿Q)x<Åûö°ü«/9÷Ùhß.‰C«¿`Σ¯áŽŠ5 —Ó¤8/Õó–3úÑûØøñ»T”ú r-k&¿MæÐqD¸ÀªÕ»W0åÏ—°sÝâz ÂUšÍêžgÕ+÷ÔSD+ šC%÷³ðù‡Õ¸›`•`Õϲkó œÿ­”®ú”oÿ> 'ŸE\ÓXr¿z)÷ÝÌ¡ê("“â¨>°›¥ïᜠÏD)ÏcÞŸ.`ñÂ%¸“šáR«(þä%Ö-YÏàaÍÙ8ã{öÏùˆÈæiÖ³ ºuÃ!Š Õ%E  HKë©JБZt "Uì^D îwˆbu2wš¤.ª€;€öÀmȸ ‰ä7G> %v¬¥X/®8˜‡á£ÁæŠU ª¶/bÎËcEÏ%_må¶¹Ù\ùÊÓ(‡V²bÆwXŠ·SÃaÓËŠªãô€SÕ®SnQN<©3²’«þþgís³¹ñëå´ï•–wbÛ†Cè®pp‡£©N¬’ÝÌyì*“0fòfn_œÍ-Sæ“o°îÅGÈ+2Ù?ãu–/\BÆØ—¹qîNþ4g+ƒ®>‡â¹ÿ%?íbƽø4}™ÊyºÍßp¢DÞŠ¢¢Â@\“ö2ÛÏi^ˆð•oŠÈä4Qwâ`#RTHŽÏÄuÓ!FÛ6ho$’? RXHàØƒ2(,ÉÙéõ7œŠÅ;—‘sÐOûÛï&3# _5$žq ÃÿñúvÅ2ýü¬Ü®Š‡¬;^§mÏŽ¸ÜŠeb-ox‰.g÷!<ÊEÞ¢—)'‚Ì!çcí$ws6ž®Im¡Q4}e• `€Þ”ž×ßK\œÓMûa£P(¥*ÿ ª#ä  hGj2i.ûKD‹«é=v$Š,w Y×ÝNR¤JÞœT£cágÞEŸ1g£ø!,5‹—ÞŒJ>vå¡2T‡‹Ò _±?¯„äÓGí%ý6|‘©´ê‰çVmÚÉÞMËñG¦Ñï†ëˆt‚¡G’uÍÓ úÛ“¤&E Š!*½á}Ž‘F·4gÔ¤ =Ú|¬÷$7Ð ±QQ¹/0èŽ(ˆö"ÒÝIrb,A¸ÏiÀ4„L"‘üFHW(I(¡³Á°ÿÐÎÍøª*p9£f覀·0€èÔèÃAʦN—Æ£ªP0ëû#»VG`ˆ†Nxl"¦·&¥ªD$4Åô %å-ªÊXôÈ~rꀅeù©(TqGæãõ†b[™˜G·œáb¦‰eÙ&ÓC:¦(à// hpeoÂ4±;Å=&ORUÞ*ñ×$¶ÍB7Á´À4!<&!»UÀ_^…ìøìï¼ÿí¨X(ŠEUq!šƒ·ì ÞCÑ=Âb]X~±¿°Ô,N»# Ëy‹ŒÀq~Œ®(à+¯¤h×€i¡høNJ~.­KŠnÀnD@ölD i‘œ,6!Òо¨°~0§A{$‘üAÂBäh³½&S¼7_e®¨¨ß¸[öž¼F^PM/{gÌÄhÞSœ‚Ìd¤>ÃÏ•:,ãÈhäÚÛ¼@ ƒï&©˜†ª†jVc©ÑÄ„A.ŠpÎ9¼“Ÿ?ÎU4Ž0pUW×ÔŒPÀ,=„·´+Éy¸ßåJj‰ ¿¿?`Yfȹùñ›Ðyì“tÑ£BÔQï’Ø*_Vc™ Á~€7o/;׬&ºû`ÔÒ¯¯ª‚Â]Ûr×hð‡²‹ )0'éˆ ì«ËýÀbàÀr ´áº&9ÅÉÆ!*uް„ÍnÐI$¤+”Ž oÁ‹"oe¹·xï.”†ºb,p7ïˆlÿn)~\P±m“nɼɳÐcÒѱ¨Ú¶ Ó ö,›Cq%h¿ ß–áÝ|ÊðÐü¬>´Øôq¬yé>~óCÍ÷pÌlI(ª³¶ñIJ@J$¡Îy‡}{«pyÀé‚}‹¦q ¯’„Þ½qa hppÑröyqE€jùØ9ÿmªpÒ¤yÒaË‚eš¸›¶Åဢòb’ºö¤åàÓhuZ/rg¼É¼—_¦ÜŒ$¢y{ŒÂ\vþ´ -\nØùùc|rÛH¶l>ˆª‰ŽªºÍñ ~£z@Q ²ø ¥¹{ò¨¹&ƒËÐ8 )05éag¯‡ââ,DJÐÙHQ!©Š«.Q“€3¶;É©´XHêr- °•bY¹ë—§¤÷9í·îŸè”ñΦ÷éÍYúéÿ1#¾’ÌnÖ¾ño*]1tèןðä2ÜÑN¶Oý7sÒ|DTl`ÞËω±¿)NÅ´LLŒZn>–e‰Qêá:¤ ¹´ SYòØh´CÒ$NcÓÛ³~ú|‡#`-µ|X&†xOw¦¹ÉÙ°ŒUO¦ÓÈ‹q룆ª'™Ì ÿŸ|œw^ÊiwÞ€–»’ÿy%¥3ÝG÷Åô‰QôþÅ|ó§+èyíT®ûŠE,%zàC´hOéÑÓWMDûAtìß—¥“Æ3ÝUBûmÈ[0•>û”¨sï!2)‚¶ƒGñãk±à±ë0rnÄY¶ž%Ï¿†»åZg¥be»Ð-Ÿ½NRü4ë”Õ`éflX…eÅÔ Dër׳ ).~{D õ9ˆ¬N ÀOÀˆØ‰} ×5Éœrà„åâ3D¥vé%‘ÔRXH îAZÐbb¶17gÍÒU­íùó›uÐ5<~~…ï?—°îÝûØàgtgÎ|äm:öi¢ô¿ýÌþ9~|öNpxè|ýC®šŽ3> ˺'Ž˜” <‘îÃ^Kª;š¨&iDD{°,!bœi=9ï¥IÌ»÷V–± bè÷ïà›X=ùI¶Nô£„E9úÎÿaX.bäs/0ë™Xøï@u×ñ Î}ô â£ÁÈìO—Ag°aÙ'¬kÕžô.Y –nVÑ ç§Å â+Š©¹.íµ£ Iý“†p1d"q€¯€54ÌI(•Àõˆ:(Ÿ#YÈ$ÉI¦q9SK‘=ÃÈïF¸2x€ *°~]“Ž=οü½Ð=n¬†šÁÖAñù(=ƒßT ‹mŠ;RÇ2îEš¨yPQRŠOx¬3𞢈êÛ–eÕ*¯]×6u&̪2ŠöíÅ0„'$㉠;|,Å %4&ÁÛ¬@•loY)¨ô0÷‘7œ"*{Wæí£4ÿ ¦FtzKÂ<ªˆ AôMѨª¤ôÀ>LÍCDr ]TÊÞ?ñÞ}b £?œKëv1˜¨¦AIÎ.*˼¸cšž‹¦SAu€¿¸ˆ²¢C GžˆÃx_qà­¬DuyÐZƒì+ŠÅ‡WœÍÞÿgï¼£¤¨Ò>üTèîɉ Ó Ÿ"bV‚˜‹ÙÝUYsvÕ5ç´ ®®†5îŠ L&T$IÎaÈÃäÔ]U÷ûãv15E 0CÏ ÷9§NwWWUߪ®p÷M3~ü™’´(@Z/J)hË€J âXÎo¿ Ygâ4äèo1²"öÀä¡P4DR‘´3€yñmŽBÑôP E¬_o­ÙÕ\Qœ»ÚÎ_½Ähu`_ì8Õ4-@JÛ4Mº-9ž\2– FJi© ª:Ònß_hàÏúÕ—Í IDATkD·H!«sO¹œï·„Z,m®kU~e‚)©Ûßï¸C2T¨y;Z¶“åÛE…l»&ÿ…P"i»Êª…¶\nÛ¬o˜ûõ×`%âD…‘°ÁÆ ¥mgR5ù»Â©:Nôä ңǩÚ÷0MB©©Ûݶân@þªUæ®t„ä¹¹3«…¢~=‘nNуK€ë€ÉÈBv EC§ëóð p2²ð¢B¡¨#Tð¶ÂÅ/*ÜNœÛÝ\Z–¿µp±úàû·#\Sg]8uäº'5þVm6Q 7"W´ìôw¢nZ®04›åÞˬ)«èpþe´nŸVMy1L_Q²)· Xíž“JXìr€[€‰À4` 2nâxd ì»(Q¡h\ Óo@f‹jßæ(M e±PÀ޳X#ÃÅÀ²Õ¿|; ï°¿IIªºqqÅr ¸ö:ü¹œÔœî„‚²&ESÁ CîïSŽ“ l¡zìOMñê¬Ü{RC¿"³;U SÄžŽL[¿¦)uÂfdv²‰À¤®<ž R(š JX(\ü6wrGˆÃÀ¼5¿|3 ¼p‰YYqÑV@Bóö$¶ŒZ2š¨Ðt—–±â§/ c'ld5›-êlÜ{:#]NGú Oî¾VDZ] E}°™tà+àßÈÌQê>¢Pì%ÊJáâM7ë·X¸¹U¥Û6­øñ˸×7PH\ª¦&òtrgN!õÒ0° yZÄ¿þ”ÉŠÚ“ œ ü˜…3#¢ó_G‰ EÓe:0wqWœÛ¢P4 ”°P@u(¯‹‰Û™sã.Ö!X¾xÒGØ‘˜ñÎ E é°àË÷޳YÁ¸ÞsÒ{®ú«q+jÆzO?c‘îOW" Ø]…*»“bà3à6àd¦(…B±(a¡pñׯp;nÒ ÅBºCÍZÿû¶.[„®éõ€nBþ구›>`>2Ŭëåw…ò EÍdBfÄ™…,f7Oq²Rö†¸µN¡ˆ/o SZw‰s[ŠF ºYÿä&@_–¿uëÒo>‰K#MM‡?N 0wu 0yNºâÖk­P¡v ô~Evž@fÅˆÌø4UûC±ã ­+îñmŽBÑxQÂB;ÆWø·Ý]™ªï×y¿FéÖ4u)ê ÂÅ•Ìù`4 ~ò¨:÷ÜîX)gA‰ /éÈŠØï¿#³;} Æ!­B¡” «süÕ?R(öuá(\bɳ<¯Ï4³`íŠÂùãþƒŒSkM3K&~Äæ…³ËAÄaª[+¼ç¤²XTGz!ÅÄ‹@p2vâVT¥a…bg,®Etÿ%ÎmQ(%JX(\¼1¶ç5â›l !fÍþßhŠ6ä¡qj±¢I¡P¾­˜™ï>ƒb!2‘›êØî«×¢¶¿ÇX$#-3±£‘bâŒè|eP(jÇÀHd ÚCâÜ…¢Ñ¡„…‹wô×/0¼V ˜²måâ¢yŸ¼†¦„…¢0LXüÕX6Λ^LeÇÄþÀm¯˜ØEEOd&›yÈŽ´N žůi E£æ dìÑ ÈŒi …¢–(a¡ðâÞö¦ø û¦õÀä™o?ÇÖ%KT†(Å^¡P”»‰i¯B1 XƒadÚSWÔzƒ·÷GkE;1øY=øuàdàD¤u"?^S(š¥ÀÈ Q÷ƹ- E£B5+¼hÑI÷¼žWÓ7†K‹z•æmNíqÂÙhª°…bOÐd&¨ïŸ¸•?M؆ìoʲèTTP%2ü󚺰è†ôû~i•(AÆML6Ưi E“d+°yMGVêV(»@Y,~üµ,¼A³•ž)‚ìüMX:éãÈ‚/>ÀPÜŠ=À Àò&òÇ'oà{d'Ùµ’yÏ·šRÎ6U‚À df§ï"b<2ÃÓà} (n­S(š>ïŸÏmâÜ…¢Q ,ŠXh¾ÉðL¦ç5 á$mœ?½c—cÎ ¹y&¢)wõuŠ€â ›ùü¶ )ݼ~6ðU–ŠRª¬åTþ¬PM‰vH«Ä3Àß‘–š«‘U‚×ǯi Å~…ƒ¬ÿr%òºü<¾ÍQ(>Êb¡ˆ…¿ ·7pÛE®ðL?å®^óõý—Sž_¨â-µB7À*-gÒcב·lþf¤µ¢Œªó«’ª˜oV2oýЦ",@_à%à'dðõ|d†§AHa¡b'Š}O.p2óÚð8·E¡hð(‹…"~‹…kµðÆ]xc-`Kẕ=ËóóB:#`"šBwOQ/h:Çaò¿ïcÞØWÊO‘ée+¨Š«p­^‘áµV4…ØŠddJØG€QHñ?äé»È v;n­S( 3¬õ.B^—åñmŽBÑpQÂB±3üÁÜ^—(=:¹â¢ (Ú´à÷®åùŽGœ€Ô•[”b4„€Ÿž¹écF…ALþ@ŠWLxÝ \(WXøëW4Fº×!ëMœ…U·"ÆDTì„BÑÐø ™)ª50!ÎmQ(,JX(jBó¼ú'WTøF>¿ñé ׯæ D0%¡Æ[QŒDÊÊù~Ô­Lã‰J€YHáP ”P]T¸îv®K”¿êvc ºu¢#ðp;28t9ò8(ІG°xYggu|›£P4L”°PìŒX‚µ\xÅ…ûÙD¦Á,ܲxN‡M ~µê}8i­š)ËÅþŽf®Íåëû¯äOÞ(¾fSÝRáuƒò ŠÙÆB{d0öàÏH«ËÃÀÝÈ‘Ï-ñkšB¡Ø ‡!ëÆŒEÞ— …%,;Có½÷Z+übÃû9È-X»¢åòï?K3Òhѽ/f¢ÞtBmµBÓÀ‚°aÁçÿã«»G°næO›/‘~ËnL…×Rá¾zc+üifÙÔ¸ø'p:2UìýÀÀ\ä>*ŠÆƒ@^»w#>~ŽosІ‡ªh¦ØÞ`mXDVÿM’Á§É@*♀fÀÑš¦õéxÔ ¡C/¹•ö‡G(-€a»‹TCï*jÏvÿ9] ŠH©`ÝïS˜ñö³,ÿî˱íEH7‚-Tw*Aº¸ŸcÅóÆW4Ta‘ \ŽÌæ´ éîô.2»ŒB¡hü<†¼Æ–Ĺ- EƒB Å®ðm£SBtJF Œ”èûÏûÄè2þš¦wÌ>äh£Ó±'Óá°!4ë|z €n˜ªjw@°-lÛ¢`õRVOû–•?M`íôÉŽ ¯~GÆTPU«ÂµT”°c|EÕÓÍ6ä í¶À%À¹È*Ù߯# ÛǯY …¢HG¦…ž…¼îÚýH¡ˆª7§Øþ"yna¼±-þ)!º\Ò×¼7ÐÊ3³ZimÚ“Ô¬ fB‚º57f4°Ã”mÝLѦµ”mÝ$ìpe°X€L›ZŠ®ûS©oòZ*ÜlP5‰Š†r¶ŒŸ¸ÈCftz˜CãŠQ(»ÇéÀ‡ÀiÀ¤8·E¡h0(a¡¨ n…  Ê-*).ü#É3¹Ö wt 3:¥D×uËê¹B¦®q€ìh;V#ýõ÷åùßP:Âõ»oR” ƒøó‘îMnŒ„[ø®ŒkUxã*ü–ŠHtÛ Å*ŠLÛ™)æyàk`mÛ¥P(ö¤°h DÞ÷ŠýU#YQ²îVãövÊ5ß{¨^Ùi¶– ÙQÌGžnF)ƒêAàuÙé·‘©=»Ó5TÕùºAx&÷ÿvÏ÷Õ •TY#üÙŸü ÜBxÕÅDÎWBºîñž^Aà ¯K”{x­XÞ í}R¶%2»ÓÕÀÈJàwŸ “ ( Em(þ ¬c£Pì—¨Ž–bO©Érá­wáNv´RìÊZ{–¹É‰nû1d5ðÛB” T]ãÏÔËjá ®€ð¿z³†yc4êSXtFЉө"?þƒt{RÙ ÅžDÆYàäÀ‰B±ß¡,Š=%–½?€×í0ZÈs-LuAáµVÄ»+|²Ãz)Ð Ùy\‰t¿RÔ-Â÷¾¦ÿß+¼V Û7Õ· Ðc€?#]ÖŸ"]žVÖÃï)Šý‹02Îâ+`²®B±ß¡,ŠºÀ+ ¼Ø~+†á›ç_v´RìÎ9Fš¡ŸF!³øw{oµagÂ"V¼…íûì­yá§u),23€+Hèg‘•r·Ôáï( …Ž|îtŽEt)ûJX(ê q;¿hð‹ ¿ñÙó¿ßa =ð12·øµÈΪ*„W?ø;ÿ;ÞZ~ë„cýº 0¸h†,f÷:rQ¥V(õEäýæ"¤UT¡Ø¯PÂBQ—øÅW<ÀŽ‚Â»ŒýÝ97¤»Óë@²C¹ åê·/ððׯ=Î/> îEùP¿ Y±xxi©P(Š}ÁÛ@Gàd $¾MQ(ö-JX(ê ¿Hð§‘õO°û‚ªâ*A¦–=˜†rÚ—ßk,+D¬É»ÎÞüYÌîp¤Uâ=à  ¸¶ÜÌkê­P4 ¼wEoäsèZdb…b¿A=´õMMÖÇiOSÌV"‹à}Ü ü%*âEM‚!ÖüºÝ‘)…/EþçSg€éÈó¢1ËÐÿYݯŠ}‹ÿ>æ_Ó}LCÆZˆì(«û¦) õ Rìkjê8íɹh#S‡þˆ ƽ9Z®ÎëøQÓ·®âÜbv—#3¯!]à>ÖÑoìKj²ØÕÆš§Îs…¢nÙ™xØ™Å5–;çÿ“EZŠýõ`R4Tvun ¬Ýl¬ÅzŠºe_B7G³»è ,Æ EÁ>jC]Ë%PC¦ã­é;Pç¸BQŸìʵÓð|ÞU¬ØKH‹ÅQ@i}4V¡hh¨”¢1s²ÞPà×8·EQ?t®ÎDæÿébðÒbÕXñǹïS¢¯þå”°P(êŸY#,à`g¾?U¶W\ô~Aˆ¼YmV( ê¥h¬œŒ´Vܼç¶(ê™Õé/Àù@.ð90XÇvÕ±R2ÈNË馮½Ó>SOH0ÀQ·h…¢AàÁú"‡ÒJñðòº­©—·‘ã”ÕB± Òq*#“?^s[u‡[Ìî* 2EìÕÀ7H7·¦B,Qa ;$)-S5ó‹lº´§1ÛdŠ&‚¦A…W¼`ÒâH @^ÃÞ+ÔßçO!-¬§c÷E{Šx¢„…¢±@f~*nFU6m tÎBdg!ñ&R½NFSÂ+. ä½X SG´K‡VY4ng/…¢© Ä ¯Ó€o ×*–°˜…,”wKôU=³M%,ë‘Ú'›ãÜÅž“ô®ŽCVN ø/M»˜ß ʵV"* @³½% E|ÑÀr@H¹`"‹qzƒºubÇX¸<|‹,Þú¿ún®BO”°Ø¨MªÊº4NAÂû;*X»±Ò ¸YâP¤›Ó­ÈŠ¢8¶k_£{&“ª{±$T¡P4, ¤°°©.&\W¨XÏÖ™Àà&ä½NÅZ(š,JX4}v–ß5<ËÇqi£ðð*®¢1Ò ˆ=y¾Mî@>pËãØ®}M,‹…‰±\, EõXXT=Cmª‚¹aÇ Ýð4Òjq,ò9¦P4I”°hºèÑךráG€–È€Ù¥Tut ú ¾÷ñ <å¹6ÎmQÔYÌîÊèëV¤8üXÇvÅ›š„…ûªP(.®°pƒ·-äu¼+Æ_‘V‹[QÂBÑ„Ñw½ˆ¢+ëŒÛi1¢ßŒ¦w" ‰èr†gòæØWîË[1Wд²5UÚ#ó·ÿ)`¯Eº>bÿšï}¬ëÓ$~×™B¡Ø5r°Ë}žzŸ“;C S£Š|þ*M%,šÞQP‡Å m< |tqªyèSÍÍÌ6†þryHt;^¿ïšDƾb0p_túyþ®b÷ÐîNO# H= ü ï#3yíïø â¹×–IõX …BÑ0ñ¸ÏHoÌÔΞ‘?sI+TÿKÑ$Q'vÓÀ?úé ƒrdÅMey*ðiGS¿ùµ–fÊ›m,þÞÌâ›öÃ’ÍÀ‡È4Ÿ­£ÛõÞ@½7Ï}%,²‘õ*Æ!;¬Š†G²Xá»À ¤8}8YâG”ëZ,¼×¬{ݺ#  …¢áâÆBmOM퟉•ÈtéC>õÒ:…"Î(aѸñ»ƒó>KÚ}«…eEðÜD“)ëC`–M0bŒÃÄM)ûõSòë™ðÊ/!JÜ »ƒ‘2¸àu÷$ÇÆÔak. }IãÃU{?ÔiÀº•ð¯I!–l3ëë¿ó^«~QQ[ñ P\P T(âÉ~|Ël”øc)Ü O7C…Žôy¿øh@‚qþmEàùÖ­ƒÔnô&ºÌYiSrnÏ4»š/#³YôŠ.å ˜ºp | ÜD/G?¿´›ÛRÔ-Ý‘£jS€ÿëu(žVǯiM†X"cÏÑ!\¿¯®”Ù¸ö(“35Š‹-Þü®”«>JÇò&¤ö_Á~¯q¿³–÷€µ«áÙ"ŒþÉ¡TótêüÝ0ï¶5ß6ñmÛÍ‚ ¿ÀË?V2}ƒ)ïv^¢uÿt$Üw˜U5*îÛNGV‡’B¸áS‹I¹A0ÝÅbWŽj{âÈVS»4b·Ó¿Œé[ÖçÿOvu üi>¼¶îèö_ýQã¾ï LÔÜ]Žu ¢Ë m^ÌvHo#O18ªEIõ*;ûïj:îXµ nýÔbfQ bfïñ^«~7¨Ú² ?xÒ­T¡h2(W¨ÆCM#œî$€ÞÀÍ)ºvæéºqK–M¦Éž™ƒ´4adK‹c5ã®­úÙsÃö¡ÀãÈÑ–2ªÄŒwüsw+w¾¦"}ó/ò€µ6û–2â¥H7´b¤Õêu`û¯SH}«ÎÌoé‡YðÃ&ùaÐa)¼vV’m6l€s^…©›à£YeÜ~rg”A&͇¯šE4‚‰ƒûèœÑ³Œ „á§…°©Ò¤gk‹Åkaö¦Á=4ŽéXÁ¶ðÓRÙáXLüÃdX·ÍÓ¡p+|:Gcî“`Èà öpb¯ 2aý:øicSôl½[[䯆ï7›-3 ôÂJÖÈm/^gñÛ² ²}îNô̆6”owÐ,.€ï–Ãúâ¡ èªÑ§u¸ú]EƒŠbøi•ü¸bƒÃÂ\ƒTl¬fÛÌZs¶˜:};8ôieU•C³àçE°<ßDètjG¶×ì:dBiL\¬±¾Ô )Ñä Î6·Š@L_ÁŒÿ×"²ÝíkÁ*(9¬]˜Š˜™«Ñ'G0¬Œ„8±k%«r¡C6䮇? ƒ é¡Mš x |¿¶Ušd¦èÞ9L«T¹Ý•ë ÏÑèš.øe¥Î–rŒTÝä`ÅrØTVØaÊRã;Aš¿Çu9ú}ÌÜlP) :¶ î!)$Ñ5ÀqX¸~Ë5° ƒ~µ‹@’aH0ÙÑv¡•ðãRXYd’˜ Ñ'[p€÷¸GàûŰd«05º·†Á], óà×\ù»sWÙôÏ0è–a×Ç]«.®×w‘5-ÎBº+M%,>þ8 ¿ £ppÝ‘ f›‘ÍmŽI¶÷Þ:ºî©©‚ÃmþµÍìðLýB¹'#ëÌ£j<Éò¬åFq8Õ·´Í~¾yÙÈÎl`â^´^±{4þŒt{;YƒâfàK¤ÐSÔ õ[ÆÉsdß9=+È£§:tJ³Á‚6íàå³áµùôƒ 6Dàþá¡)Pu G=YçÂcÒxõü"’*áÞ`ò&›6)y%‚p´wøðªT¶-*æáhu’²ò—±H¾9ÁEEœ;FãÇMÙ*¹ÞQ½RùðªJ"åaî~+Âr :vKaÖÕ%<ò!<µÔB3<¶Ãó_ÂÂhÓ>ü¹”ùëS™q[˜$·ó®É¦ ŸRYsM›WÀEoÀ×› UªÅ¦bAZVˆ'ÎÈäŠÃó«ÄE,„Kþ'?¾3±”5ùɼv\)&ðï+Y¾ÊÙ€¬´Æßâ¨v¥ˆrxàCxègϱ ÜzR*ÿZ,®Þ;Ÿë—Èwaâ]Ç"âã+S*á¡ݱ©üpî6°À.…;ß„Y9I¬¹:ÌÚÕpæ‹‚Î æ­²±ÚêL:Wçæg¬“µ¹‰0é)”­/æÏoô¼ªöuë”Àè‹ †´/eìðÀbîi:s¶Ø¸·ëÓ¤óÁŸ ¹ã5˜R ÿ·sFë|vG*§´.®nU°`äx¸ÿ;°’¡!·XpÄ¡)¼wA9šEbPðÖ·e<™%ѽäô œ‘ÎMDzeôÙàéËÒ¸®o>å[àÆÿÀ«KªÚžfrýù™<Ü/JᎱ0êWð*¸û/H¡kI ÿ˜.çü°”eE™Œ={›ÌÅÔðX‹Ló~ ð2öB¡hô(W¨†KM o X€C€×3tíÑû²Ì6³-Ž©‹8/š›ðÏ–Ÿµ…ÂÆiÀXdEåTªÅÝL~qMô:Ç˜ß 9J~{tûŠúã`¤Hœ=[‹L{2ð6JTÔ'~ßì½Ç†µÑ¬kó 7/•èh?¶÷ðÔÙž<­‚Þ™•üò;Œš"þ .I¼òWƒ» Þ›ZÊ‹2 !S.³¡ÂäoSxøXÈ2!Ž0v‰à¨CàôÎ2¹cX‡§ñÌçðã&AË,“†'r̓L ¦.,ᾩ©t膫–•rÙÛÞ_hWž‘Ä5‡G¸e0´‰Ž~:0‰{O² Åà6 E‡4¾Ÿ'EÅë×§°ü!Á¼ m[%OýRI™åY)“.—oüS ã.(%QÈöp³Á¨KSÙ2 ^=¶U0aµ:|ñ-<ô³Æ™‡¦²ò15wÁ°Ö6OŽ+ç«å);ºEU“ãaâ›ÿ’ΖQ0õRÐò+9E§Ð†¤´ªSACû`t–¡IO°k®–Ä´ËÂìæ­±8kHS¯wè(æú107à­k’È{>;6¬¬ ó IDATàΉ¶ A(¯t(%òÛ½!–ß ÇgÁÄEe¬¨ðú­pbGÖm‚üz§ÉÐÅÕƒ¯uøu6Üõ 8(‘…wë,¼Gðh?øez #§¥¢é`ê‚…‚sÏ‘ûüÓ_!£4ÌCì(HÀÔ!dhòpéðÌðêR›†¥°~̽rl›'?*c^a/gHQqò€–=«ïAmàÁ"t;$È»CÝà_—¦òú‰Û¤/VÝâ·0î©ÅQ¯"ÓTÁšaC%ôéšÄ=ÇC.= †dÉíÛ;ȇ–aì$@YÔ"1m£NQ%ôéo„çOc:՗׃б™üØ<2R¶{ÛpÖÐd®;´˜æiðç£å(È¢B]Š„)Т]"ÿ>§‚Ž)‚ömaÔé“×ëÕïÃ:åÃëKàØCSùב…4O#ûÁÈáÐÖÛ®j“Í7ÏÎ9)ÇO.£_›D]ö8<•WÿTÆ‘,V/ƒ¯JàêùkŸ22BpÚqpm/˜>+Âòéj–àå‚C³+éÜ®:ì ‡u¥!RšAZ¦F‡æ6ÿ¹äÀ7³A3‚<}tψš wž§dÂø_lòˆDHáµÁrŸ>î= ò7…™¿5„Ý®®i8›á½л{ )¡uØ$¨Ì‹ðí¢~ûŒ`ÿɦKä´ƒ†Ã_úd&CçLÀÐh›¥‘š¼ãñl`üŠ,šw9ʃDÑDP'rã&×'Ó3ïàæ–†>ðÖ 3-‚n”E}# Õ€û[Xœ”¤qÛVãø+ì~ÈJËoÈA5oJZojZ÷±n'íä—V#…Å[õ³#û%=‘õIF AZ¾ÊãØ.EâDïaÇÁUiâ° °Ð -ѽ{ó =ÒŠ ’³à ¶0#çk”F\a¡10‡íƒ )Ñ! Àˆí.'G4"ØŽüüÚ×ÅŒùZ./¢í+Š8TX ‚»O…ÏŸƒM€nj<8Ü ]Èl ìè:¶%ª<¶vÂ!ÀÁ³4^[Ä«C¿n!Îï«qòA޼WúÄ…ýl mû}Ôïý=†!+‡†-Ù“Ï55¶n(cØ¿M†|”–—Ù€`Ñ6c‡m2 Pÿ.vµ®?Ћ)Ê­úïv†ÐÎûàTýL·ìZô¿Ù²MþïÏ[ÎÓL4!cþXoe‘W¡c;¡Ó­Y4î$(­P ض<NÔøm9ÚŽCCü±Òº%Ð=­hû1Õ’ oÌÈ•ç_Ä6ÒŽ*‡YîLöí—&¨(' WWpØãAÒpÐ4ÕÛ"6çY,Xì9ÉÅÛa¯^ðv¯2Àò/@D_Ï_ ­ÇÓãÜ…b¯Q¢á°+Aá ý௮<=9öhó&8õ““~gDë°dÁW 6¯YolsœAÈàJMë¯{Aôµ;ÆW€|ô¼ƒL7»¼÷b!8Ykâpäÿðà¿ÀÜ8¶KQС9°m 3sc G·)‘WU>þ ˜@O5}M’.‡ö-ÇãÁ! ²=d@`lïkk$¹¦Q‹N°¨Zï´~‰ÛÁ¶I!p #CŠ.1{ìt[0a±ÁðN{x èÛ ¾¿Aðýré+t¾˜æÖE‚‡f§0ç AÇ„È.òáwƒÆtMî˜m :uJàòº%@À 2Úg—ï ,œ¨žÐ¼ÇZƒ²B(€=^Z°Ê•UD ·/ÇÚ±ñ®š' R#z8¢E8ú]JÈDC§Wz%Ó)64OCÏïÖ†ˆ‰AyŽTm*<1 š&(÷Ö”Ð@XÕ¯j'À²¡w»n<:L8¬á ‘ `h‚>mʸë;v¨‡a[°±Z7«¯à¥ze*0 YöÏqn‹B±×(W¨†ÁÎb)Üσ7Û™ú?žkn¦}Ú6 ÄwDF@’75³™” CÍã€÷€["ÈM„è­RêzìôzY\ŒQW¢bïè€ß GÚ!‹1õîB‰Š¦‰ƒ‘&ÃÒü·l3ws̘7~ó6DØ\ Ñ=+B³¨ ÐÜÜ2&­L†,[S×Èù¶ÐHò˜Ûáºf–Lá×P̼ú%:|‘mñb #½­©ßƒDU­‹ Õã.†Pu«/Gšƒ"…‰bÏHCº¤Mî~ŽF|骦hª8pÜÿÁyÝåÇŸç•3ø_6ýF?Fc] è;ÏÒ, Î8r4ÈË‹pÁË•úTA¯Á²r -Èy}j¦Æo9’å÷Å¥•œø´ÆL+ÈGÈF½ôE1ÇŒLdà3W|`sÛ§¾Ù’Œ¨€{?‡õ圔ÀëçëôL‚Š¢×5ØPaÊ ìü‘W&•pɇiU58b¡Á²uðäØŸšÂúÃÀë`S¾ÅÌ%‚ˆæ{xEݵœh›þv:L]çpûÛ%ŒJÔ)+w¡ /üE£gZyõ:Z5‡³ràƒJØV’Ì¥ýDׄw1l¢ñ†•À=?qàÏšü2#ȳ#‚œžS²c-‹ #:74ÎûKª™ð`3‹öµ­œÝФ;Ã[…æ VZÎ<àIdp R0½‚Aÿ7°…*ëY bE ‚ÀAH×±áH/úé g *Õ­’ È ]ÉȴʉÀÉí3õ¿ÿp½ctnMÍ…ÖvE4*kÉ:X¼U#ŒFR¢N÷¶]2œêÛ5!o Ì^Å–A0 Ó'Û&'ËÙ¸¼¡Ê-h™)!9/¿ò+ 5Z$ɽ+,‚Õ… 4èØÒ0,ÞkŠ ,LÚ5ÔVÖÙ)̇¼°\·U$ATªy» 4ÚeÉ꥛¡Ô‚”D®Ív¼Ml+‚RM£}ªÊóe±´å& ­£EÖbÝa¢m_Q¨‘‘ 9©‚Ü|ÈÊ„WÁ8°>Ìdh™=Æ6ÌY k B!œ‚ž-ìšÿ7"¥0+ -ƒ´dÿkc‘`{»Öl„UÅ:F@§w Ó†<[£CšÀ®„u…Ò­*͵:Ø» S!+¡úo•lƒé4Š":ÍS5lg‘ýÿŠ ¡È†v™ÑŽ€•e°±ZgÊ4·Â‚E¡Rƒî­ )Våqó×Âò<0:Z Ͷ·« U•" %.,ÐÑýr"dEá†ÐeŒÁ¿F¤qõùò8”ÃŒu°®Ä 5Q§G›ÙéT‹Ö[ ëK AèÞJÐ&­ê¼Þ¸6Tj´Èdg¼™pñøÆß!;°ó4Gàì1>›ŒA>ËŠ£¯¥²ÕT"-vÿYÖii~ùlT(%JXì;¼y®cÅS€¬z|.pCŸ ÑõÑfg¤65 rÃpžÉEVÄ‘îQ#-•ÀwTƒ®5ß}mÄ;^¯4þ‚€ÙÈ`ì Àªø5KQ ö°pÍï[î^a~ügüËéžùbóüW´Fu§[ï¶½ížåkš§±ó}ÀóÝ®ö)ÖúÞí»©»…oï¼í[Mø×©æÇÄŽÇлmï1ØY»bmËÿ[±†rÜí{ÿwÛ5í׮޻¾÷~_V ïO‚˾ÕyîšT®;°PvÉwµÍÚC¢ î|VtHç½ó «§®-ûFX€t‰ê‰Œ©,ÙŲ EƒD¹BíÜ[ª;Þcø&­¸%IÓΑfè÷5³héV¬h¬h„1­-NOÖwäñ×EagÒz1Ù‘‚ªÌQn7j—.Ýû²VÈp¤…¢˜†,b7 •*V‹Ý‘ç»êÅú¾¶ó\ÿXÄjcmçíê·w·[W›õýóv¶o5±«uvÖCMëìl[5ýWþŽ]í_m÷Çßn¾Ÿ—}-³ƒtoéT_vgÛÜc¨pö0èÔ¡³á?Q^D¸ŽL¼¡P4:”°¨_j²R¸nOrÔò\à¦~!£ã¨fǧDsñ5ü›à®‰îÙiá¡<³Ç˜BëEKf…ú°˜êÕº]c·²^@sàdüD_dUìG±ËâØ.…B¡Ø;èßþ›fÒ»“CŸÅ{g¡«‰ôïIõ´e —ßó®E E#E ‹ú£¦àl¯•¢/pKŠ® »,Õäþæ2LÃÍo÷Ð&£[[œ–¤îÊÓΛ¶@ºF}ŒŒ pÓëÚT/¬ç7t7eLähÕp¤ËSéwû(2À=¿¦) E! U8/Ûªn¯®êsÛu‹¼„toý?`N|›£Pì>JXÔ=5e|2Ùž¹œTàBà†ÃBf›Ç›Û›©>6//‚ªD­ w@:=E÷íÔ4‡Ã“à‰mœg ¬gË…8 ø'0Ÿª”´±=«›ªÀh œ ü¼¿xøXÇv)šn$—@IÓáÞgkQÝ»V¸AÙ§c»oÙAáælºi>¾AZ§¯®‰s[ŠÝ¦1vW2;³R¸¡fý€;Òtí„3 nϲHví.0{ÀðA `ÆxX½¡qT‰D_M '^ ÎF˜ôIµ€Ìf&ŒlaH’¦ß›gž6­Âê<ƒ,äZ/¼âÂë}ÛT)àX`ÒåÉBú×>L¢îº7Šý ò6Á—Ëu¡§ö©$UÝùc²h9™&ýÛY»»è<û°Rôo× Sœîµ äÞÜ'çΆ˜¹³ h¶ÙÅÈçàµÀÃÀ†ø6G¡Ø=CWµ1àf{ ݹ§ƒÑï3Û€O2O˜”-x¨E Q2¯Ä —¿GÃÈ—à’¿Vü7hÚÃÿ…'_{j ÷½wÝ\c0æÐÁ·Ùd™m3tmð&ppt)÷øy]È\ÁÖ˜3šunE¯{YÔî~àHà"d …ŠºÁ€%Ëà¢w.xÇfyEHÝùc¡ÃÈ·áªÏTîîF‡òxõ¯–%nOÓñÄ{ðØ´vc?ÞºL];z’Á7+“jUý|·0`íJxjRˆ¥ùfìóSÂpísðø¬zhCÃâMä³ïOqn‡B±Û4öÛ]¼ñe».OèdF—ü·©ß1º…‘õe;‹®kP¬w œ|RÕ¼'C;­Êd,ÝN i!pÍÉn‚;}ïïš:žåý¸ë»Ûµ=¯Þ¶y¿÷ÿ¦ÝN'Ÿ]>e®ˆ½Nt›É:ÜßÂâÇö‚“’Ì¡ÀÀ]H1UÎ`Y`¤Æ#SÄ^}&2mìËÀš¸µNÑxñ^.ZõyY™pzŸ ö3I3ªôŸÞ+ÉkWõÚZÝ+Ìo{Õb,ãÅû½ØE±œßÆ«Õr;þï¼ÄZ®¦;†€s‡ÂÍG:Ü{ó®¶ïb@E!<ø©ÃëË·—Ê6p40v¶Xû·+vµN¬c£ÇXÏŒc­`Jaq×§6ï¬L‰ÑÝ;²¿]&5÷,jÚ~V-…[>µ˜U˜\½L¬÷? ÀuÃ4.è^!Ÿ!þóÁÒóÿDZΆ-L6¯×!¼(eßsbe|òÞÚ p9pÅéÉFÚ¨6½¢Å‰j4áÚ@ǃa@oÈ/[ƒn‡BïÃà»_å¯{ÂãaÃ"YbuÈ™`Á¯ãá‡OA?NÛVÃäqUÛíp ø?Xñ+LÿMJ¢mé6º¶„Å›`è™Ð#–Ì€qÿõ«B½IñÓJ·Á7ŸÁì P‘ƒO…$ ô$8ão0cض<‡Ÿ'Ÿ)Ìœ ãÞ„ »êæ.àÀø¤­Å³Fó'òÅm[mç`ð3U1W¢x»Ý½hh€>ÀYÈ@ì°¸ YÌNUÅVì|·òÊ ’“‚œÒKf¶KáË%¶NvNˆš—q^?A(äÐ,œ øzü¸Æ$‚N÷ÖÇuwèÑ*Âê0m‹šÎàÞšaî|XXj’‘¡1¸k„@%LX …“.9&ý[VT«.‡ïAY @×̳Wh,Ì7ÉJ78ዾn±:¿.€o—š9:™É:}Ú ŽëRIjJ‹`Ò2Ç4èœaöe%:¶49·o×ÃK Š“þ]§t £ À„`ü<…ù&)IýÚëœt@9‰±,Å@ߎPiVÊ´¤À÷Ët6kAC:iÜ6\}ÀEƒpü¸*lÞd1smCZ„AƒDC°j=L[¥Sfä´ÔØ%ŒáŠ;¿/‚ÛLtS§s+‡Ãs¬šŸ:©KaYIBH£{ÁÁí¢ÇÓ‚Ù«ÀH7H(³™¹AGè&ý;9tJ¶øv±Æ¦2ƒŒTØÝ"-(‹öÍÝ 9-aõ:˜¿Õ$˜ qdW‹™‚й6°a£ÅŒ¥¢ÄFK Ð?;²]„•Âô :-²thé+F¨Ë‚ˆß-רXb’ 8¼›F¯–ò·Â´õr±Ù«ý2 º&Ùü¶:fÆõ0¯0ÈàžaŽî!¦Éß\¸ ÂAv!ÁÔU:…–&ƒºUrÅ… ËWÁìÍzno.ƒ° O›í¢6¸„´b+Š&Š·Z¶ëª“¤Ñ©p0«½©‹WZÂîŽ=¢û.¦Nqï»B!ÄÇÏñŸqòý¸»…è‚]⨋„Ø&„();ðæß…2T[Q²RˆÓZ Ñ!ºBŒ+—yí!Úy~³+B|0K~WTV}{«~bps!: Ä QêÈù–]µÌ¸ qÚqByW qãùB,Ü"De©_;?|\n1ŽAÄg&(žº!GýÓ©*&ŠþÞñ¸†@àd§à¤\Ÿx6JwÜñÜDäyÜ è„dÊc À×ßÁ7k5F^–ÁÆÇlfß9‘7|fPé]É‚.=á§¿A+N”Áï—‚C‡5[ì—ΚÂì«!;äðѼ *ï¦Â=¿À)Ǧ°èQµwÁY­à½qE¼µ*#¦‹ÓKŸÀ³ 4.?5µ#aáßáÈD‡§?.åç­I`B’ùù#NJ&÷1uäæ…ù¶Ààã›Y{œÚÆÏް¬0‰  †K7 ž»,…-£à½á°yc%7`’Ó¦^i: ?1“·”r|/(ÜfñÍêDy7.ƒÉó ˜ä‚>EÕ]qL˜ßoÑxï¦$–?$˜}-h[*yâ7Á©ƒá­AòO~êÒTÆœG¥%ËÒÏ[c1lp2S®ÓÔ¬Mƒ€.O¶ôÛdµNañÃs¯†®I‚Ï”“gذ n˜v‹D¾»3UwÉ©6aH8Ò²ÕpÀ³À@¯8·E¡¨5JXÔ¿»“?–"êýÉ™ÀúÕï´ÒǶµè¢ö4*#/²!÷W˜»–|«‹ E/8팪ËzòçÂ…ßf›£aM9´h ¢¦L”Í:HŠˆ£‡Có,ú残ñPöÇß~ Æ~ ó?î•A㽔㫟¾¯‚×_†Ê4(Ú,{†HÌýÊl°ŠaúWPXfŠ×À=çÁô©ðñ°|„Ò¡U æc#@×àª,›²þSD^â IDATœböÓd§}25+T‰ W`xßž Œ,äèSm k‘ü éÚô!Ðø;rôù<`"2ˇBQ÷ØÐ»• ¢Òá·• ŠaZ4ÌYGëd&:8nJÇ óåtù±Ëÿ³wÞaR[~«»'mÞewY`—œ£ ’¢Á€QQ1^Ã5aໆ{ͯñš#æˆbBQP "9‡e ›óÌtw}Ô43;Ì. ‹Îïy¶»«ªOUwŸªS'5ópã.;ÒâҪЖ•Õlõ‰ÍT™¥+-ÖAÀ”h °~‹da¹bG{´N Öû-×Z OâÖãüL:Ó¦¯KbšékNÔùï‰^žardË Ý|Š!­T:F%qÿ¹w h€ÏëæÙsuîcsiU®Ú’¬X 6«ûœ;ÈÃ?ñsﱇøËâç5fÃ=ªLõÇÆmAªüÐ¥|<^Ø©¯Â횀Ï)©™©ê¼iBÓ– ¼pRy©Ð«'ŒÎ…¢jIA!¼ÿ¤¤y¹ïø’%¹¹0ñdIÓæÃŸD]ÁBƒ@!¼¾Ú¶Nàîáåä&@çŽpÏ ½<È×+< ƒß„N­¹sx%Í3à”Áª‰«Kà¤.5ä¶‚‘9àF`‡t56Œ˜ÌE}+ÉL‚3†óÚÁ‚‚«kè”nRS4RSà¼ÞhYÌ\©£h;¼½†öI SâÎã[TãùãVƒj?ôêß\ ÿÀç†6é€.h‘!HJ)•lrèaI<rZ©hf‘Co™ÒÄà ãtÌ”ôè c;@y•¤¸FãÛ5°N î9ÍËQ­ki• Ÿ#é’(©=0b ~ ¬.Ûß„ÄÇî"îc±kD‡uÌ"]ÏZãu8s\²áº-Ó$ÏMþõÝjÈPp ÈêoÎQܵE2ýFÁËSÔ9]‡Us¡%8T¡Ü¹nå ýùWpÉùpØHÈj ƒS·˜2Yi6bŠ”6¬]¤ 5t è(·AÓTùŸ†Ã®‚› !YÍ2%¡u#¤Ó` Ôî$%›`U‘rAÓ*¡²´ µ¹«ñ‘Ðί57V¦'N,–®Ú‡¢ò^|AXSädíÖû^ìiÞ‹NÀ$TBº ”ë ŒEEtZ|Ž ¸˜xÔú8ö!|©pzoøa¦ä«M’9ë`a>¸ Fv” ¢?‰ÚM^º®†Î7«¿á£Ú4©²½ ìQ Ë¡¤¼†™+ VÓÓGçÍ_üœ/[G¶Ôq»ˆ™C"A£_ž„ Ò:dEˆüFtɰxç+ø÷T›êb¡¹=­š!É êƒ÷:yI~° !¤lÛé³dâÛÜývÈ)+ÔÇ¢Z‰%ë÷ß Z0¤?¾ž˜RÅ“ŸjåáÔƒg\‹Çá2°lÅš¥ÅŽ7mèÕF#Íò70 GaCu-T”CyE€adc ”TKа¨)b#BÕ úa]A ýt‘†DÁ– “ ’ÒŠP¯4Mw+_3ÌÚ$[;l;j÷Åm›éì'ënÉà-ÛX¡ùLZjÅߪ#“ “Ø<<~\ÅBãônrçm‡u…ž¿j<þF9O¾#èÛÁÍ™iŒîãíÛã'-\hMÑÏɆvMur“ªU¿tÈÑ•ö%èl/ÝepP¶G(ô¤ThVc±œm•À[À¥¨$©›ö/9qıkÄ‹úQ_¢;Ç9[¢–ôÇÿêáÖÛßÞDrR²Nç¶'°€ìÞ0¸ØA(*ë.lß9Í`ÀÑÐ!Š,uÄ,,Dè¾´DØøür Œì ãÆBÏÞP± æüÖm¬`Ä¡š ‚àO¼íR`Õ¯ðÛ˜³Îý'ä6^CÁj¥ùpЉÐÉÝdì!zÇ¥Y K„ºz¼\|ÁR ÷ÔŽNdÖîÈX'Žp±«'r ðÊæÝ…ÒÑ”E\o‚²©‡ÊˆêCÙÚN楻י8âØ‹€ †v…Œà·Õžól&]š{8"·2fàbúÚ6÷qi? + ñzÀÍÐÈqûI˃<«òƒÜ^ p5qqúaü/}íÇ凄d~mÍzEi `t•s@ÊðúP×%[×Â1ÏC±iѽ‡±}‰¥µ\÷eŒV‚Z Ú OË®{Î9¯5Îêï¡W–‰ $¸$ÒäåZˆÄË‚–íáã+%߯Ì[§ñöÂ7¿kóÒO |yeÖÞ];þJ I.‰1'ìðo @É& ´ÀH$.]ÇcZ4©ÝéÙ ¦í2½ÜpD`@ÕñnMÒ­u ˜ŠµêiÔY ˜výøåX¹®70 ZuUB@h;šhPU=BëØð ŒîWŒƒ/¿…ä$µÅdùÙñ:I‚¦úÂBÏ…„æ.x!'Èk9º¯³K;åØv*a‡¹HŸ—Ýõ½¸åÃÑ&tÜ ¥½pÃPÎãó€gCç¢;}I\¨ˆc„®í O†€âZ&Í2Á€þ}<¤¹b0#!HLRzÓ .:*Èÿ¤}Zš À–’DCÒ<z´€ZÛ&¿JÒ> knÓ7ŠÊlJj¡eŠ‹¾ÙU ›{J16·B± éM\¼~±Æ¿Ž©Åm*Ÿ ø}lC‚Ç .Ð:x¸þ„ W÷ ‚%©5!Åe¢5°â–ðÒWðèÏ Œ>\rÇ‹e·Úüç0XYPËújO½E×E™Õ–;O 0tð&€­ Nê— rù1&§t°x³`«½s¾·[Y—j>s¹|XŽ0Ú"ȯù:5Âi¨ÄëME6n2ƽÕðu±ÀöH]&Ò ,†ØÁQu‚ön›‰ flƒÞ=´NŠaffÃ+SᥩœÜ_rï96‹&J®È‚-k‚”Ô¸Ôp p…Úÿ#î0tAz:øký|•Ÿ°#hùÚXR®g[55?]Œr;‰#ŽF¸`Q ®Ðõà<àƒÞn}ì‡Í4í™f&YŽã`ìPõÿ·Bq蜪~…ïg«ãÇ‚g7n¤‹§Â†jHk Z¾øPmAÅœCÖDÞ œG_oQíÌøLµ#ô–0l4Œ¸ž{r½àÉ€–V­„žÄf0ö h’µ÷·†B]?#ÕbFžÍ•©F¯Ï 4›ªE罈9Ê‹òxÍË%Lü¼ •; Ö¸f’Æå³B1*¤EEP‹É®d=˜– C+8ª`[ÜùVÃð2ð1+ß·˜ð‘Éo• Ì~Í€«á®jøÏŒòK`éfXº„O§‰;FÖgM³~UEùîz%lø’tNé %EÕ\ñŽ‹ß6Á¢EpÁ“pÿ ‹`¤öÀ_œÚ–¯ªdüGI¬Î‡Ùsáœ'á¿¿ØèÑA9·h;Ñ|:§’Gf&±"îý>[/ÜÅG»?–͆5«‚,)Pþq9yÐ/~Xê'߯s^;µ§K×½oUòÈìD Š`éX^ Þ$—­8´e1g¹Åæb Mì¹-kd¿Âàˆ¶ÐÊ–\ûf-oüæcÆ|8î%ÁÆ bòtÊÞ‡xh‡š∣QãÀ‘Ùÿ\ˆˆ_¤éStŸC€ÿKÖÄÐëÒtƧ›¤ì ÂZõ‚ÚMðñguµ .àÓáÒS¡]_èú½ª##'76Á’!ãáM?Â7ÓaÜñ°e>LûtçhPÑðeÃÝ“ÂÇß½O?Íáý1pzxøu­t üº z¶‡{чÂÏKá¤Cইaë¹°ê¾e@ éôùýš ©|¿kjrR®›‹ô3~¬µ¡¢hLFÙ¦:¢cå˜FY('ëgP¦l±p0J¸x%`ÄGã…„£‚œé°8¸¯‡ƒšT„íB"¾3iÛôë ¯TÀS`E~-+óÕµa}yü$?n hp|W¸TKA÷®:¸ {–ú¤MtNïâoÿ9r昫DHA$;Á5¬´yã‡*4Ω=\L™¤²&À[KÓ—ŽÁ¤ ŽœºP–•}´éÕøï6 ok<·Äâ«eŠ’¤ÿ7ÒÇeÝËvØÜׯÿ J[GÙ¬/ƒ;ß(çk”UÙx î8+‘îÉ¥u%$²²aHLú­œTò¯ åNÃ%•7hkŒ8ÂâžípçÌ*ÞûN1+O¢Î¿ÏOf\»Ò˜‚á5'ÃÖZÁãŸUòìgŠŒô ƒÿ“@¿¬2( ³Yg¬EˆW„ÙsY¢´Lßãæ·*›#º%ð¿ã*!Msàè,xk^9'SY~YxážðújèØ.‘ã›–ÅÖ^ipéhøé›ñ/Vq—O£¢ÆÆðyxu´Es‰¯¤Kø¨‚u&¼>° ðQ¦N2ñZ°¹ú“@ Ôš%Ž8þê‚E¤ÉS´@áLm¹ÀmÀ˧$½§çIþ‘n©0tû’ùØÀá'Ã9AFjãVÑÚ¨Iè÷ì¦Ú@Z8ã*8ìˆ}4OB7/|ØÂä‰L#%×Ю^Ž&ìvî$?ŒÎ}ñwÒêÅG]øæ˜¹ÁÌYK ]hæX³ >]ì& U^ÍY‹a}¹Ø;_š£ qþŠ2˜¹X£È¿‹z{"Ô¯…ð[¡¾{ý¬†·¿ƒç~pSìß ã!`Ýzøq•Žß ;ü{9š€—Áò"cï®lÜ OÌIÚá3GƒX ¼ \É_Ǿ Ž¿þÊ‚…#LD;fGÚן¼ÓÎ¥]ñFŽæ}§¹IGûg7ÃÆÝ÷<=ò¯`ºI_Âig«,Ý{hÕx.=gÏëÿ^„â_ÞÄäÛ\›³“\}tåwqáQ¡Ü¬u²vÇJ¬G}°b Œ~GcqM"TøI‚ÏIùýÞy>øNš¤S`¹)/‚³‡7×§üñÙHÀ¶mðð—¿–¸Á ‹—ÂñÛÌ*KÞw³€ò¸ì¸ï×Ýì— w= c^ƒk¿ÒXSãýãG‡W§ÀÉoºØôRYOs1«{Ï—£øËàŒwþ’ºw½35 îx½šg§ýýP·~ÍÚ'p~çRpÁ¿FÎëÊèkë&ÖŽxÔu]‚mªD}‰)pÓh£süuCSGþ"ù¡ˆqݹŸë·ÀµšÌ+Q1Z¶„»OrÑ-©:ž4šÞX4Gß?šŽXãQÇ ®E‡IկнÝ y“™E€¾©5áÜõ…Ó^$íQ‚àJܺMé6¸öƒ _mKŠ ÃÙz‹5¸áß#5ƶ« Ï[±žGT½:ôF·*Ÿ×þÙÖæŽ÷ü¬ªtÿõV&{sP‰[/åÀ·Ç_¥½XŸ"M[@Y þ¯‹Ko;1S2:%´bþ³µ6 'AÿÓá˜Áà°ð øæ ز}çòŽ[q—Qpüñ™k‚¯§À²ÕàN‚~§Br6l„ÆBº ~œ ßþGŸ‡ eàãɰôW5"A y?u*´k¡2#Íø~^ ®gwƒÁÇÀÚùÚ V9Ìø¦ ­ûÃáÝmºÂðÁðÝ è<FVÙP½ > ¾û¡nLzP÷(-„éÓ t>x=0üð¯ƒBŽ ©.Xø=|< j콯ì =ë“SlñÂE®Î/—ÿTÑÀV¢˜µc@`ÎÐÕJqü¡ÁOËàý ‚{.5Th4ôooãI¬ Û au… [¦ä§u°¹Ò 9Ic@»Y ¨¯Å€m[`Ö& :›Z„â nÙÉ"3)´RÕaõF˜½^£ÌÔÉL•ÙÞ¢i’Üá$<{ ¬)Ñ©¶4rRáð¶A2“ ¢ælRÍ,Þ`±>²Ó`H§ MÝŠžÕ›¡Øí&O˜½I£ÜÒÈÍÔ8¢M œ…Y(»¨HÇå1è“ë'XV‚FÇô»!ñý2ȯ0HL´ñw\rúUS ÓW ¶ûuÒ‡´Ò" d-,]Ûmhâ–U òÁ£Á¢5°l»FY@§I ôme’—ªÆ¢d;,(1èÓÒ$5J¼²~Þ¦Ñ9W’“* MPRdÉzB°r“É’.ºfë84¯^ ›ü}ZÙ$GhîW®„|ËË€Öµ ê`‘˜TÎÑaÁÜå°¼ÄÀpithjÑ·¥~˜½ Stz4UÏv{!,.ÔhÝ\Ò:Yõañj(wÖÒ䢡pÿcµLþ5•[¯Ö¾qÀžBåiêŒJGÁ"–@}Åáí]€ !Fý3U×&d˜4u±o–‡peÀ¿&ùšËÓφ•³àcaÕººuLàœ‡áÆ«!Éé™pþÕpåéðc\|Ñ‚By)œ~,ß Z†Ûs*\pü¸_÷L„–iáë_÷]O>úÂÄÿBM5øÂeÎ×ÖgÁ¸ãÔ¹ÃGC¦®oÏ¿ ™‰ mœr<~üïɺ‚EèÞ^~>{®ûüë~È‘ð†ƒ»Ž‡v†]^ÞïmHÈsÁs9AF% ÏÍÅÚ¹‹üÖàaà]”>Æ1‹.œˆR{Ñ?Ž8HøìdùÝ® $˜åp‚îG¤ñÞÉ…Ìù Nÿ ºdiÌßâ$¶‡Ã:'óÉ¥dzaÅB8ãu˜_¦>O§©Gà3@Ó4ʶà ÷ÁE—¥2á 2fΓ' Šüa9þ Ž^^¿ºøj¹ë}ø÷LÀ)"Hy5tí”ÈG—Ô°î'›«>Uä?ønU2‰ R*þ"¼vkÇfTòÄ'ð¿MÐÒ¬®ÝCÓ¸þŒTX¼ñ5\þ”†Ô¯¹Ár“ž“ùò´²º ÞP„¡;ÞÜ:3)7M§\Xô0`í2¸à5˜±-\¦u®‡»ÎõpfB9?³j_+9z«‡¯6X?ßÏiï‚߀TIY¥¤e /“Î7Òº’é?À©3_LHfx‹ ÐaáB8êÉ“W¥rY“R5êðó|›‹¦ªûÜÔr¦ñãùY²uøò{¸üG›§þ™Ê¥ÝÊ@BM1ó0¸:y˜=¦–á Í`òˆ"(‡[^‡ûçA0Ô'Ý¥qÁii<Ó§”±ÏƒÕÚÇ‚‹+IsÁ‹ÃÿýlsΨ ^9¾Êá̧ÁÛÕËŒqÕ4ogsrŽä¹9×1íÅQ>–£´ÿÜÏ´ÄÇè ÇX9)"´*âÏ…À;}Üúè› í¿M÷¡PjIzðÿ)¡¢d \q" ?.‡ýáæ«ë> »rφ W·;ÃäO¡I+¸ýFh’ UµªËk¿‡sN‚><Ð)¦< gs–@J[è× :Áµ·CËdxï1Ôî}Ìd¸öf8¨9ÔÔªyÏç‚þ'o"N:ž¹z[Ñ9íU¸ä8öJ%T<{=ôÌ‚î-.¼RÓÜ)i•Ú}´” â÷ƒæ…u3áì¡pë3à—pÌ(HIþóŸpBŠäë\‹+Sv B<†ÊÎÝ5t9Òï":3{|ú‹ã¯T§ùЯƒA‹Ðî;ܺÀú }:,IAÐÇW7øØt œ–sVU± 4Êáö)°°ÒÅ#$±}"„·_U“ņ9pË5ðÕ‡0s¦*´¡ßè“K¾„n€’ÍðòDøf$´„Î=AÕH.˜ ·\ ófÀs÷©vS3¡f-,]­Úܲ~ý¼>uÜî`èÔ>».=î¸o÷ÆYÓ¡b#Ü8¾ý^ù¬Úîthªí•¸„,kjòQ3´¾ýtàà”Å \KÀˆû^Äñ×ÅŰ:Ó$žh½vè›¶%†Æcç éPC‹–pÅð˜’Õåë7ÂûùpÁÈD®>¼’ÌT¸üt¸ ¤6*¢œ¡ -…ü*Áø‘^ŽïTCf2\s4\¸AÏo:<5Vçå3ªií•:´Îht´JWí5K$úvîšiAz¦ÇF×Ò!SÒ§\Üj M -Ÿ~… ÛàÁ1:‡¶Ò¦%<5Œú¤Z0c »ytŒÎÁ9ÛÃ'‚+š!X»>*s‡&riï 2¸i¬ÆšôÉð㪯!p…b,ŸÀXLH"ñzí2”à‘¦‘–UȆ¶-àÔ<˜»¤†u5˜ðÊBHJusl‡ "t/M(‡7–CVS÷[CÛ$hÓî9 ’k-¦üb0¨PígA‘›²B˜Ðpé‚Å…A6Uº™¿jt#:Zj3Kƒô À2Y_aÄ9êîáC”çã©û›8âpp šBEšò¥ˆ™ ©Iê﫟…«ž*M´Ì„,B h 2’C3äq×Â1×B]ONV›ÊäÈ¿¶ç‡20„4Õ¥*e¥° HÈR#Öáh˜¶MÕ·%ø’Tû9I°ÙVe/…RBKêRÅø]®aPHÕC¯Ï´waÔA0äL2Š·Ã7ïÀÛÏ©¼»zË4ÛÖÁ¢-JÃA)TÕ€H·ØçÏlh²äPŸÅ}Å®—™UØòà¿(;VÇ4Ê$¬±ˆô¿ˆ›FÅqÀà ‚mi4K0W±coKÈNÐèÞ´Vm<b( DÐl+ƒZû‰×tèÕÌüºméÂfÉh´nâuœc‚¬ ¹IO| 3{½Á¯…&VèksÙfèØ”±ù† ¤ç¹ÉñÖ(š$%ƒôK°m–¬_²‹vÉ©332à0 Ìè•®Ê"³«—.)å; &›eBOX KÀ-á¥ïªøy!AÓK ‚´(ªU´J@†|넺5µ™ôUëÖëÌßbSê¬.ö|Å-¥Pc%ÁvR‚Ö)$ÃðCàÅ÷ÌXŸÊY%ÌÜ]zø8(£”Àæˆò~¨­¢*?G?bЄ °Ê¢›ò›ö‡Cg—dÚRÒ àÒ-–ä»X¸›xèŸU¹ãÝPÓ•de©­ˆûYìÛ€7€‹€W€’ýKNqx‚E¤ùItä'ç|_`Bª&†Þœ®siºIrdÖÍý†ÐúsæWP¡ƒe‚‘5PF„þH Ìÿò·áR3‚0@B~…âÂ~?ø«B3khÂÑ´ztQ¡“—ÃÏóÁí ko¼^”w_ë¹öÎõdô %¼üôücÒˆ9Ú¶†S®€¡ÇÂùaaÁn¼iÁ¨è&B&÷ö•„îÎ22QpC¡1zv­y8Ê<êUÔÓrŽa%Â5ã¾qø¡•zÐ1[l œó©B˜EhÂùtȺѤµóf´@†äEò ÛËÀãåË`ø ’.¸x Á Y³<ȹïìaeQWèN€ ©>æd/$f2 İ•Ù'KB­ *íÏi=Ý:€ßTsA’Ç }šTÕ]@ë¿?ešàÂA..f‘&ƒœýšÀ ZŒ‹ˆèRâ,Ä%ךO±ùvµE^9,«<1ÐÞ!øD–µ-ÈIõpÃ@ ËH$nÝÀcØ´Ì®!- zäÂÏ ¼ã4Mt1ªW€‡ß3™ºX# èí!M¯Þ)'RŠ—(öÏ—'“ö3-qÄqÀõùRDj)Òs«Žðê÷e[î3ÇO”@M à»à•ÏÕ¹àáG¡¨Hiœ‰XTCm™ª»ê+¸å~uÍÓî{rPb¼BS¦D»¤Á†@h3£l Üq”‡îyý$ÒÊÍðÑ ‘\h µüàJ€‰BÆz–Š©”מùºåBÛŽ0o7‹Æ¨ö–00Qò•×äb#ç‘Rkb™-îæGëh/œ}ÖˆâˆãƒTq C²±Òþ}‰0%$'ØL]¡sZÇÐy?|¿"dZSêtÉSå×nÐ P¶z?ƒIc »”Ziðñ?Ý Í«æ~­êšÜ¡Ôp5”.æšU"5®­-¶Í 2¿ÈÇÈV• ÁÊ%0Û‚#£?g Ю |´¤–%e ’U 6À²ô•’Ä$uˬæ..VµCSòÉ×0½4áZTZ8 ~][ü‚§/Kâ’>e Ã¦y´$º®¤—XTÄãßïÖ‚´DýìT€aˆØccARœÒÞ]PËf 3;‘S[TîѾщîœãAÀëYºvÛÙzÆçy‡ûh<ÌH”Á‡_&< 'Œ†¾ÇÃã“áЃ²ˆÉ[€§>›ª\³Î¼®…ûÞÑÃÁg‚¿bwo®þsIXø5¬,…ÞÇÃmC‡ž0áUåÑ2 ª·Dl5Ôdh"ì9†©90ø %uìÝúB“D  ÿ[Ö$$jp[–É´\î3ŽAù^L2T‰™Ü#CÇ}/â80!!)ú$Â’BAu}ŽË±«ªHÙRÒ±5› /YÅýs“Y_÷¿ ¯o·^—=MIïnÐ!Eòà'Õ¼1?åkà_@~­ÎáÝ Ý°˜µFcy¾`êt8÷}›’j¿²œ%¼l/;‰ïl[çÐîà³L®|ÍâËn¦Ï…3'C@Ö#0b0hv€+ß´™³ÁÍ‚%pÞ{Ê Ô6¡KÚþ7µ‚gð±¾Þ™ cÞ‚÷׃ËÒ@D4›äQ£ùË‹e›ß͆Ó'C-’å[ƒ”WB«æ€iñŸiðÓ:ÁkÓàÆ_,œðuÚƒ²% WX¬Ú¦ïÌ™$ Áé¡ ?À—k$g¡“h×yX¶-! N훫¹â}/Ë6 æÏ‡qOÀßÛH·’ò†w…7€`Dg è2l}šùÃm[P¸ p»ÉM6Aƒ7gBŸ{ݼ²25ž­¡~HTèÙ6ÀýLKq4Z…ˆñs g—XÍ€k€±g$‰·eštrâo7¡ÅŸN€î­à¼á©Â×f½ÿ¹#4„¢šÊXðÜÕnºnxn•_ý \s=š!‡€²-ˆ„Œ˜¢#dÃÛÁ­WÁÄGáÔ+Ô l#ÜtüZG»Fvî‡Ó 8½†Á \{t} N¹AýÂcáûÅu'ÇlBÀoÀú$!ú€ý ‡ø$SsM&•êw–pã&Ó<L#,`8) FÜ<*Ž ðÀ)½àâ¹–Wxé¡2aÚ"ĦP&D"üZ;Ÿª&$z*=®û †c„à¸^nº-ò“–ÈμȆa}á‘­pÕ´Z_¬ è⦷ `!! žº.}Áæ’jð ¼mîæå1éRy¬éô=Nœ'xnj%ÏMUtœ=ÀCêšZ^žVE³–ÜÙ­˜s:i¼:¯‚CçîÖ8£Æ»ËÀ )±MNÖ—Û¥ÀË_—±¬2Ùç—Ô zö}ÛC¿øIº9±«–xDÈíDSRÖ?އ-5‚û§WñútU$1Å`âIœÖºк ôñÀ×^/rü  ®r‚<(× kZExS­>Yzxè”T@A)Ìß$¿:©qÌßߢ´_°ó“#Ž}†Æú©FúR8þ‘B… ®Ï3´ÞÿÉœŸf¡GØú6:بÅ}×3•‡œæ‡Å³`æT(­Q=ËìI>غHE‹²Ö£à¸£!U‡µ‹àÛ©°1_mùeu„-KBΓ)м 8_°ZEJddBé(-S õ¬~0r´NU‘¾ûT9k»_4m[¡p³}·rº‚Yk”HÞ0èÓÖÿs¾‡¶ýáø1“µ¥0óønÚÎ{öð$AN•H¯0š÷ªaóÊp¹ìnàÕ `Qãc‘,«êL©²ª$< <‰r¤sL£œ¼Ž«¤$îŠøw‡ÃËœèu^T¸‚DÔZËŒÌK×ÆÏ¸ÒÖÛæðûÌötX¿ú>矖ÎýG•€6–‚ÇÙ‰PS Ûk EŠ÷ 0k”ûWF$‡b¨o‡Ÿ7 jÑißLÒ6Ñbk… /S¢Y°©RR!Åh*ñÞ²í٩г¹µÃ,¦¸@Ü èÞÜbëVX[©Ó6Ç";Aåͯ†æiÀærhš^ŠÊ¡RZ¥„Ê+¡°V£u¦MU%¬,†¬D(õƒÛ£ÓLX´»Î9-…‡—ïìÈú⬇ :I ‚¾-M¬Z0]åSãYSs  Ø¯“™,éÖÜ&#bk‘ŠÒ• Ú¬­„Ÿ6AEP§iœkQ^ KŠÍ›JZ&U ·@i@'3zdZ¬/ƒ&Éì†Ò(—›¦ü^ŠŠ`}¤§ê´I«ßf[)TÙ‚Ö2̾mØP >_ˆFaÑFXS®ãu Úd›tÌ¢Î{[REº ]šJØ'ƒ°±D¹õe;Ñ]°l.ôzQã¿%sE¥Å¯(ƒ“žÒ9ÿôdÆv(D`Ÿ@@ §>ïâãÅÁ)Àó¨)¡ÿ«P&H~”KM}ÏÖF3÷ñ½ãˆc£`í)Ò9ÛÑRä×gK6<·gš´rÓxŠH„"~Dîí0špl¡Hû`§¼³ËïˆUÎ5¨»xwÎ9eœ6#üÁlÓ¡1²|ä’XjC žNd›MõÙ9Gß#šf§}Ù@ûBíØN.Ó¹¹6˜ÖBà^Ôn‘Ó£H#žT/ŽG° ·=“ }̹ÉO3Wˆ 8o°³í0NP÷-äO‘uíˆë •Ž#}Í9ç|a‘ǰ󽨇fßÏ‚A¯Á£ÒxyX)zn}ñìåÉ\Ü£,öâÖá£ÞX_¿œ2‘œ z"ÛŒ5æ²2ÎßÑý«úkœvE£CC¬:‘m9}pÎ…è¹äa˜n%1÷úJÒCtNù®ü&¯¯3iŸØœóÀ,<(Á¢8aß;Ž8v ±Y¿Gš=9ÿ;Ÿ àDà­ö†váÛ9ºç¥œH¨€p/ÜÔµÊwÙ[‘ËÇ«$òZôÂ[*)–íN›"FùHQtFÔ±Ó·è~D"úÑm;çjc#æì4‹oò,NK6zið"p7JøµˆíoÑX{Ga„¨×ŸÞÊ/ÌMSorä‚4Ö"R²ó¢ÕÑÛEn>ØQ×*Ï.®Eê£cÝ«š{w„ óàÍ)¥¤_'Hý—àîÙ‚Óz9½sYý‚ž³Yâüv5±B¾F—l3rÜ"ëÖW&r,¢ûëÞш5N»¢1ÖóŠÕ–Œ:§Ãº¥ðU¾Æ­§h¤;Ék%tk Ÿ\bÑ>q? üÀÓ(ŸÓ¾û™–8þÆhL>õùTh@pƒgŒKq¹oo¤…#PÄ™MûÚºáíf&o%êÞ› åekL{0JÀø”ºoiôžbq4^„¢}úO‹2Oåþ3CÙW°!± <}5·X°©Ü@è‚6MaHûj¼õ%É‹ãÁ‚´¦ðÒå:ÛE˜šIh—à‘î>>@¹ ¸p?ÓÇßI°péW!æÀäMô|!NM݉îâˆ#Bïâ˜4‹£à¼-F—O«ÍWëPÑ:œLð‘†"qÄÑøaCû¶€ ì?³¬}  œt˜TÎÎ׺;»üqü>HHË‚A9ÁýéâÅž"€ (ò<ð°xÿ’ÇßÉ*ÒLÄ1qµÀO6¾¬ÖY¨¯ú eùWßýû½pÆç@{Ρh.ŸWé¬1m€ßPñÅ=„ Ö¢ƒÄGã‡ã) 'è÷þšEœbO·Ç3Ëú`£¸fÄÿ Ñæ¤qì9œ1¯Ž©mœkî>V bIÆG,Ž}ŽÆ$XÀΦPnÔóîJ[Ž®Ü\yÔFÁ[ez¸ôÈ㮀mßnŒDÈ_Q0±Ü#`Ü•pP¯g‡TÀ ?œ½ÅàÜ­Võ²€ý*pðÊA7Ò=>Ž8|XðëJø|‘ÁªýÏŸI¿,#|\Yß/Ykujw—Wؾ ¾Yé¢ö÷î†GÜðóBX°Ù}àÍG±àØ ü‘¾DŽÏÞ€ðÃRŠqþsQòµtÙÏ´Äñ7Dc, n¬$gOʃŠÑ<~­)ß8k‹U{v‹U~,&cý.‚»‡!‡îyU ÈìO~ ×Løë Ðç¸û1{bã 3P\÷§‹umÔ˜\a.nD© +PQ~"·ãIóâ8ð¡Y 'ÃñOÛ¼ò[òŸkT«ÃöÍpã;:W½ÀêJܰb%œô”à´w=l ¸wo6ÓໟàèI.6ÔzöìK i%ßú&/ô`†Žo} žïiœ³éž@‡ÒíðøW.æmuïùVH(ºÓ;?Àk <{}ðù 8a’Ƽ²äœ÷ ^ P¾qıOÑX>ÑhÇíèp³:*$cðª ÷L®.?b£Æ£Å†Êw ,Õ4`Éç0ù%˜·Xõ*RåîDÖ[õn¾¶0r$ 8\‰9ö¿&a“…È`§‘ç"à9÷ŒŽðáÔ‰4T'Öx9ÛzÔ¹È:ÑϹn@éV¸îý _oK ›xE÷%:LzîýüQGFfŠ.y.²½Xt øƒ)£ÚØÓñüû  ¸8‡¸Ö"Ž}ŒÆæ¼=ÕD N@ÓåÀS–=büvûÈiU†ïž,“^·ƒ ÂŒlØ)½`è‘°bd·ƒ#‡ƒU3߃¿Q îÈàimá¨ê8£œ8 ¾ ‡®lõÃɧÀº/aÒdèr =Úæ@°æL‡oÞ€Úv:¤š0ýu( „&ð<8~Ø¥ðùduÿþgÂБà³á·Ÿà›w`ó6ð&Ààq 7By2œx<Ìx¦O‡×ÀàþäÕ³àý—`ÝVõô¼Ð{  i>Ø´¾üPeGŠr#„Ê]øD±Á½%E–¹xXˆ1OèÿhŸŠ8þ^ðM€R :tÎÕ!ìпoÚeÿa |µÂ ÔÔHNÒ†´÷“`¨¤d_¬Ô±]:ƒ:X4K° _ý E¦A‡\ƒÞ‰µ*K4PReñéø%ß@¸tvƒÁy~°`åF˜·Å e3H­6ùlN¹mpP+Éñ]¸"rRÌþ ¦­1¨²‰:ÝsmŽj@”À´¥¡ß.sÑÆBWëIÉÒM’O×ꬫÒiÓLcTw?ÍeÌвM³`˜¯–Dgမ+aE‘%4:4‡£ÛÇpV·`ÑJ¨2A¯´ùn‹94>M²n#ÌÞ ã·mr4µ œ< ¬Úó7kÔ Ó,]£_[?INˆ’XÏIÀ¢Õ°x›Ž©käfi håß1f+×C•¡ÑÒgóýj(·\tm%é“mòË2XV¬ãö ú´µi—jƒ ¿¬ƒ¦if™Íì-:~M£{+ÁÁ-T—Áw!X¹Édis»Ê¢ÈtqH« >g. œuàMÕé™e…™›‹WBeP`WYj|šY°ÒÓ5:5±wÄÅ›¿¤[£O ›êRXRšÀµ°ºÌEr¢àȲCÃ!T‚¿÷Òaõ:øi‹Ž ä6#ÛZêl¬sǾÇÇ( ú•À?‰LûM°ˆD¬`²_1PÉh¾°njµ9|Î&­íøTÁõM,Ü5, >=î3ÛÂ@y¤$‡Ë{!Ü9^ž^–ú¡ÇÂ-—¨2­…Ç#.„Kÿ }SÁrG‡ï«`e2<údú@Ú 48ã"˜>.{þy;ôhw–À‹©6] Ýë§ÃÔàÊgáª3Ã;VcÆÁúóàÚ±°ªþó45ÁïƒDUs¡ÏÅpÉ£N…ëσŸ6À„ÉpÑuÉInué•0ñxþõ?käÿ8BoÞUpS‘Î̳˜ÌŠPb“?FÍXïp\Ðøë# 5±—£2²o6›øöÎ;<Šj㟙ٖBzèMšt¤ƒ‚…&â¬(6,?±]{ClX°a¿z¯íÚ®E•.(Ò{© %½m²»S~œv2l¨ $8ïóœìf§9sæœï{¾ 2=ˆDVÖ¸CUkܾÒ[Sàî_@-w9‰!C’™|A[wÁej+1åÞXêÕ*B+„'>€?4•«.LäÓ¾eáã Þ›YÌ›¥°_}9ÓÍkW$sW÷<&,€Gf©¤')hùkQ©^np3ï^ˆóÀÌeLþð’¸+’sV>0e\õ­D~ÈLà ѯK<(£aŒZ¾ŽáÑá£)póO4ÛL‘¹æÌZüû¢"â½ðÑ·ðæn‰Æ±«³„ª8>ÉÍ9õ\L[§Rn‹º-ã™=JãT©”KÞ“Q¼ j®ÎMœ×—àæù+é,àÚ)ágúS!›³9­¤€W×|ÿ@-†5) ¶®‡³ß‚KÏçì_F ÃMÿ†º¢}¦‚ Þ‚~ýãùjhá~õõ€V×ËÒ»ËX½Â`àD‰æu–ïR1ÂϾSKŸ\ë”Eš&Ü6ŸüNƒ½¡û;w@"Ÿü£Ú.£zÎÿǹÀ›À3óÜÍ'¶:þ.¨®æöµkkj kj Ø|›­ésÉÕüçír1¿„ê+ºéá‰SÓAÕ…(š“_‡aÝáÇù ù`À"'¯ ðÇ7p׋âÿõóà²K  Ô ¸|P¸Þ¯O„á· Rñ͋г6Œ¼ö–ÂAÝm0i®8Ï€‹D[© œ×WüöùûÐýA*жÁ˜+ ß ˜¾štƒ[Ù2pÅBÁxý1ø#.¿ J6ÃåÝ ggøy8æ‚‹ ^ ¸f0”dÃíý GKxécPâaàÀêûÌ$(ÐàÉ,ƒ3$~+Õþ¾æ# Ò Ò/íY¶!zvpr£ñœÏ.E¬¾|ŒˆÚòð;ð9"çÉÍ@?A­šþ!CÑ>xçWP‘¸ª_K‚‘mã˜2­˜µE>aVx]1’Xe–$¨åp+VÁM"P*3°C,Wwu‘ªE!^ž ·¼á¥«²|äÆ^n>[¡u²xÑ?ü¥”©ÛÐóáÉPb@ïö>>½NáªÖâ¸Ö¨¤4•¸½›Œ'|· Šep³R´ýD'-ÝËs—yè×XLióט–™Õ$E—œG IDATI–YBQ$2wRqý?âÙù¬Á¶G ®i¤óöœ"ÖùÊiŒ}‰ðÃh“[ÇðÇ=:§Æ€!Á_{uÎê›Hæs°àHviL\$(ÁîÕpÝDHH‰aÎhÙã๾³–3vj<á‹@• ট cËx–=áfïX¸éTÿÍ*áÃeIà†–høDZöiøþrÐóCL^¯óàðD2ÆÁ³§ÁžM~íæcµ¼ðW–Ê Øû<,ºš”…xâû±-¼ÌYâ+ùþÊ®î h*37‡€7A™"3°ƒeÀ6À“ßÉí[ù˜¯N›8ÑÞ¹|—v»Ä€¢€¡l*”ùþÞX²ž…×ÁªMe<þ“¨·d9nã*¸{ ÔmËÂ'ç_"òZ\4?Áuqð7Au5…ªˆP‰„  "V‹­9vS u£ý“¹j×Y~%î™T¾qÕXŽ“µ Í…È)Fü^O˜0X !–w@,™OO–Á(€íÛÄ9$`Å\èó |³ ’j'¼,V˜+®¹k)ü±ú7‡¶ý §4ñÂoÓaû*hRGœèªgá²±bÉRR N5j%‹óùs`ËHvφŸÁ¥Ýáñp÷3°m-|÷6Ì])âC.Û£î‚{_Ø8P³@u{Däkð\Ž›7òCF™¡mE×L"d¢ ñäÌÏrau{·j1ü=` |Á…ÂGçDpŠ¡TÕrˆɩЯ)|´Í`ٖ˶€×-Ó¤¶ÄýçÆ‘ì+bW”^j°Ñ¶ôm)Ö³ôhÉ3!7¤QˆL/}›yh—Teж)4I–ضÛ`KžŒñ.¹ë Ÿ %!°¾*º®±x1Z‚=¨@bmk ñHBØ€ªuM§~#ÖJfÒŸÅœö'œÒÐÃ…†v´ØÒý,t0Ð Ðt 7ª]›KÄ)ì7אָ€ž¹v2„4FRÌøX—`ªÎÚäD()Ä{- A†$£¹é8¯ TòC› $+¼›¤B£Ze‚h ¹ÀïÚ¿|²ÑTUƒÖM ÞcˆÆ“ ]ƒpËB‘ŽzxÔr%À=àîßý,ÈŽ£eQ ßî…[.v“ìöh¼gºQ„ÛG9œ!ÏÅ+Ñ*5Ü~* ba‘ºqœÐuƒœ\@S¹úƒ MÑž¥AXW¦Ò"OůCü¡¯úw·ˆlÜ7!|.œyÈA•¢º »æÂJ*Ì(æe”Ï `Ÿ?J€¥@æoeZÏ!»¥¦ÿLtI£STOŒ»äÁ!Õ²böP3Ž ¢fd—K´\J+xutL‡]à—™°ê7x?´ Çœ/Ù¿ýýoƒsÎí,@‡_€²8á)G–̆ŒÕâ/-YÌ_BVí±!„`€”X‰ŒõpÕÇP„A«ú>n>Ô½e<>ëðÔ7n—´ÿZú˜×†„7¾¿Ygö&X²Sfê•·¦yõ/ßý3 2ŸŽa@‚ÛØo`zQ„¿kRŒÂ•gzhàRQu Y‘¸Ã-[K'Å¥PiM3ˆOñpíY‰†ŽfH¸\nb\:)µ…+—® s5—bi%kx€ à#z'Ó£5œÛ@Ò\¯Wyä/Á]\ØòÀ:GE¸?ȶ°,2R¹jŠ„á²¨7 LƒÈ.÷.Q  E6ˆu»HŠ âu²uÛQŒ^>BŒ;Tª‹)” ÃR¢i+ʈq%¡®av`–’ðöL`j±nüö|žZÔ—´¢êv»aHGÂïÂ#´®EÏó`©Ý¤b\ÞnL‡Ôq\"ÃÂ/S Óý¯A] g üø£Ð€ä2,ýî¼î¸Æ?É !-‚EO:u9ãnøø3HÉ€{þýšÀÃ/ˆ§xJ{¸à– ü&®Cô…ÿ"Læì!4—!ü-#Èi%@†­Ûà¶Ifeø¸ç/ÛŸ2ød˜ˆœŠ¬‘Y¬#…ÕÂ~?¶‹ÐKӗú¨ï§Á÷ËCìÈwA)LX$Â`¹’Ýœ’qJž¿>ÀŒ]ñàçZ›%6tH±q§x±ðÅòýpïÀ2R=âE’%1ÌX¹¢Hå$cÉ@8:!\.KàŽŸ|4oîå¾a:ÓÇéLYŸ¥D}*BÑê¶l‹*”#ˆY=ÅÀСwK77ŸâösƒÜÚ+DNŽNf‘‚Ç>ü+POU•ÜÑÅ-ácFu²y7”#‹9GB¤@˜9-ß'Q¤±?¤ëÏ$â½’!šÒ¥‹m[@ûºÓ÷óŸЮ¹‡õý²>ñÜÜŠX{J—`Oâ|^(ÜYEF9²¡•élÛgìϰ­ÀÌB‰¯„Ϲ,ÉÄÅaœyš›[…¸}P›º„غ¶†|¸«é4‚ñ5"¯ÅC'º"N~T'…¹îe.ö˜ßC–ÿm–¨åÌ¥ìD$&üÿ2`ç’€Ö}Øn©åu .ùñT•ºUç"YÅÛý6j—]Ów¸‹ž'hXR}èu4ˆ…ÛÆ@³Zԡǰ{2ìý 欂=Åq?~~¹ðëÇpñ‹0r ìÌ…M2Ü?Nï¿.‚œPyb"*U›vpÿ àW`ÉfhÓFLŹbXhÑ Nï­Ï…;ï¿¥¶€z©‘%*û¹«aMû„…Çs Ö‡Ô• ä$}›è¨¹áú©e<û‹—ô|™ÏWèx€‚R( ¸Ið†wÐC\û¶ÄÓWÅÓÖU|ÀËt$Œ]’dJ‹áÍ™eì5jñÌ9!âu¥Ù¢±Òãô¨1ݼìÌÑùm«—¾uÊ¢®Î‰Ý ˆ›{„xo™Æu_i|v…—&îþ Ï-йq¨Eñ—_vpÁ?{|õy€k¾vóî07ÉÁON„/6é¼ÑÂJiÔ{2öÿ‰ò;àq+¬XæçÞ_ây°s1+ÖÀ£s¡is½ù)Ý’n°bƒÆæ -’4H€kÛŒš`+· ð‘äó‹9æ€F/Ûrtælñpv½ãàë…~ÞkG¤Æ [K¡®É,$ˆS ^œ\Jó˜Xº§øy÷WXžgðgzˆóùÚ0É%q~WxlIˆ[?uñö….Ò •×&À¿¶¨‚»Êø½ 7‘eY¦k…ÂíA–„‡«ÓÛûxó"IŠ âMëºI6Tî]¿n]// óÒ9±€P¸z…ÄGët>] ¸ÑEbæj½ûÌÜšÀm-ýœ× >[o°!3ÈÖ,m<âüÂÿCœ×tRF]‰î9l„ëÒ zö‚{7ȼ>«ˆÉód|äë2£Ære³‚òZaðÁUmá®ß\ò¾‹?î ;ùY—½Nv¦iœÒ>¹n˜TÂÀÂçQd®ÏKýóÔ<«Ð«|T·N-¦÷ê°Ê&ÆÅèKc¹¥ƒ¨—^ŠÛÙp43Ä®!¢Œ‡}¸^:uSd¦M)æýÉâ·f}¼r©LmwˆP=83>žYÀúâdþ¼!4¸¨<=rã<\ߥ´bm¹®jgpû¬—¼ëbÁ£îdöç£Þ¯HË–>έ[F¡aD&õD=IãšwaíÄŽbwv¨ÂÄÎä!í;ÁûÃ`̤Rz­ k×™›/«ÅƒóO¼æ»úâ„6õAàJª:¼µƒ¿-Nôäo‡U3aͼíb¿’¥xÃ%A 쟱–ÿ½ás¤]]ÐúêZ.×Ói* =QÏâšBz2䮇ÒxhЊ3 gŸØGI‚zMA/=›¢«›Óû@÷öP¼þ˜.|*|:ìY#D[ð%@ßëà´S!ü6¶ª0x(3¾™ž<@ýΠ—ÂÞõ g ÒÉv»ÎJÖ.¹_A~<ÔírdnˆÐ=)Î}ηëaÊøë/ñ’»Ã?.‚zq°z1ÌuÏ€naÍlذêÕƒ¢p›TU•@3à“…±9°CÕ@ƒ5ˆÆû«ÎÙáßCDD4Baj)ìÄÖJ*Nz3(Ã0†ñ™$I NpUž–$éÃ#%¨“ñQ4Ó~?ì.“iš¬‹XX¼¶¸0$‰Æ)=qU)„uû@vCëtÈÉ9‘Ì-¼N”H¢^Rx6Û‘ k³$TI¡^²AÇúšHvm4?§;aS®Œ¤H4L7èX'¢EÉËl¶aRø2ó@÷AÃxq^5(¬Më%A¬-ž‚Ýãyµo1÷Jà–i×@£AûûZNl/ÄD…æI(P¶:Ž…g$2몂Šû¥jÖîÉ%ѺŽ[†í{`{Œì†VõuâC"8FýtX4 z£ðÊ ñ H*௙äZ2]ªøÂϯ¸²ƒÐ 9b‚¶=VçȲ‹†)*§5Ыnt• ‚Kßw3yMèà}„)l“ØRÊûØUGoó€ï€ÁDZçÀÁ1£º (oòdF|2mMn7åI†!¶úˆ ³X †ÏrÌ)@æn¹öÃÉ27&ªB¬¨j‘ÏÔµ˜w¦¡O&ÌA»¢õLSL•­aº›ç´^Ç’ùÕ´™…ðq’e?ë>öºZ³†(–ºF«g´c¬®ö¦˜m¾™™gÍdya%ÈmR™`sîËrñC‰jj)²€ n¸„‰ïv…·/ /BL &y°:l›fxVÓ<{^‹“–T†ÑÜ0ŒÙ’$5<ôÞUŠ{%Izõ0ö«…ÐFôº 4éMÄŒðçrÙ<Ä ÆŸœðÿæøubˆ…‰hïºU ²ŸÚœ¯…!½åû¾áñÈ$]º&0÷ºBb]–ãí׳žÏâ^îK^‰ýÇÙÿ·ÿf‡ÕÐÖ~m3Z4÷·Ž[ö7Û^Oó7û=j4°c?_´kXÏkmhñ4´ïÏwËEVðŠÎkÎ AAÞ>û^¤ðÕݱ o^tðµn{ûØïÃÚ—\‚Xôš óÖ­ ÜÒÉ¢Á±>û3ƒçªãObáEø ƃˆäarà ÒPL¡LX‡s²NEVÓk¾s…غZ¬FùÝÞ#µ%¤w»iŸ~êt¿ËóTšJK/U+ú™dÀD´'p( »«g´ýí×1aŸ@+ÚïPÛŽôºÖëG# ÖsUU¯”DˆÆwó\<›«“¡©{6ñÛ‰³Ï¥ÍÂåBàj` b•g1"²Æ"¢ŸµVD(NjR†!IR…â°(䯿2P]ñ´jÙÈÖ…T2·l¦HЧyÓ†bÅøèQѤž´BˆA@_Ä3ËBh«EDiÚCاøašÌAuZÈ9”È­·Úÿײ-ŒPX-Ó t=¬&Œ¶¯Ýø5Ú9wŸC½eö}¢÷PÇ[÷Ö–‡ûÛ¡p°cç6’V€ n ë TtÏ2P˜]ÆKd—\pº—!MŠÝVÑÚ3Z½ÂPu9+hH‘Y=Ú9íÏÕ1y:™¸.&œØê88Q‰”Íb®‘™Ûì¶ëVaO¦g%A"šŒ`–[¿,V{Î-“ëŒI’¹5Y¶©1ðï –•£9.¦”¨A„]üRÄjS1àZ±7¿"úLPè„°—¿¡½X Lf!ú’Ÿòë}'BqXðï^ÈÐó¯#_MâÙO&qýÙ–œM¡}Œ~>“ã³hÊ›4Œ«9Ü 4Bh$ú#HEcÄóžŒFh¤Ö"ú‚ƒÊ‚—÷†6ÍeRS‚ÄÊ UðÂG×K¤¤–¡É1 .Þ.”\ nï'¾²}UhÛ¾%Ó¥Q¡cõ|0øx19㞃JEu%&ìëJæ§µØ †U{aÕV˜%Æò»/|Ìf`_†ªw¾=[oÿ“ßå}.M¥c ÎDXÓ!‰|ƒÿÎUx6Ï WS3ˆDî1ý"ìÑžLGmó·=ÀB`"‚8´D©=‘€$à'Ä*ÐB„fÃA†Jæ¾=–d0îÁè7ãkšÔ C’FqÆnö$U¾óp1iÒ¤.À‹[â–@° ñœ¦"LÛ*r¸vP0 e#hÙT=à'2 îb€¦Þj¿Š†÷+_U*>Ô!1 .®ŸîÌ·Ç / æÁÛ㤕†êN, ¼ö®|×÷`;k×ZXíß­¶¦&Ã>ÇïÀö)~µÇ¢ ¹á=I2÷$«øL?€¿#ˆ–ôQ±ÏƒŽÁÍ6RÂûŸH„»ç—ÀÃ9.攪e Åj Hæl+¡0‹ù)`ÙÇô§"Le~FØÆ7º—ÿFD“Ú„Èvú3"‚P´*SH(ŠoŒÎ–•?ñâ;Sxëþ¡û·º\ (ŠÅfHgÛÚ¬Û²U‰¡yÛŽ´mZçW™;wîeÀ Äs˜‡X<8Z? G‹ŠL[œD {0Týè¥säõrp¬Ø„*q"ÇŶZ'jR*«I‰•HXµVsk½„“UE¥˜HÑ­À”,M_0&G-šéby)ÕÉ:ºb„í*m"c¡ï50ü*HpE·éU¸Æpémpã½pÃ=0`HÄAûD@‚" žÎrsn†ÄœRu0‘A;AÌw…TÜ'ÌbÕd-áFô™%ˆz ÂTê•ð¾ë6¬·ªü¾k ‚‰+o¹‡óZ$ðß§ç›õî&I,ä‹Çn¢k×. »l8Wþã\Úu8‹1o|Žÿ}ëx‘oâI„¶C*8pà ‚—sÕ}'º"N.Ô$bnk15¦H$²ÒlfD¶fç¶ …–b “eáïóÉÓüê¶~»$žÌrQbfQÝ ñàµÉ0fì±Ç£Ð_C¸ûMÿ24it )ƒ¤wM†Wß‚'_†§^÷¿‡ÇŠ}Ž'¹{ãÌ-‘°ÓÅ㹡â"Ãø aþ²Ñ/¬$³û€YLbj'Öp²–üµ¾Æ›gï3€ás<‚0¿Yˆpî$TU3ToHªŸ¤¶=÷ä¤â<5úqòUʽ[nÁ‚ÏŸáºg> ià]ÌZ°˜?ÿ‰aÊxî®›ùhâêƒ^¥N:þª½8¨Ñ¶ ¹ÀÃÀ ÀY'¸.N"Ô4baÂN0¬Ú k‚2«™‹I.¢ ”Ñ„Ë2„þÔ<ݘ36W-hŸ`v? µF!"J]‚ s€ÿ L¨+µ-jüÅ:_|3÷]ÙŠU“ÞçÕ¯âež@.¯}0¥~¾xçÎèÔžv½ÏãÃ÷_&5¦˜ Ó~$P3§t»˜ÕwÌÕÑÞÝš¨ã{`.ð,´׃cFMð±8ìâ±aû´;t[Åk{)kHÚåÅöe@梀ÖãÒÝÒ)·û]òC©*©fLöãˆoƒ®†^D$üå¿ÁׯƒVúõûŧÅ—Â쉠%AïAÐo0$ÅÀÎÍ0m¬_(öUÄÆ0ä&èÝ ´Ìÿ~ý‚–±2Ôn}‚¬Ã/s Ggq’gÇ•˜}†Á°.Ð" þ¨â6 óÉEåȬ¨…2±–HÙŠ|)¬‰îL-—©ñ²÷k_:Ò‰¤˜.)À©ˆèRƒ!ÿ$DKMDølç¤/®Ë¾Æ¯S/äͧîbø€ à’A–(ÉËbWv!mN»„õ¼ûJnÙ•Þ‰>¶ç—¡YsÔlÔdÁă¿+N†÷5€HÞ9 |xb«ãàd@M'=4­ù»U£Í/#ZÔ(ë6Ÿå3øÙo­Çç«Ý§ú•„q©:Ck‘à¢U}—žv0þ `1×?ÿb8«|1ž¹GÔ¥NxýM¸. £†ˆ}ÍdN·Ü ãFÁG_@Z ø×ЫMäœC/…sããÙž'XuZÁ¿~‚-`Ù$˜÷=Lø/ÌW`çváuP·=4k *œSVå9A2B06ÛÍÇE!#dhëÚ€\ô¹±f̶F|ŠF(Ìb%—&¡°æ¦8ä"Ììæ#Bž†ˆ.uðVø¼ë¡l#’S¡:&[:f$¶>ç^º™!7¼Å3Ï¿‡’¨ 2z(ˆЉõ¤à-w„ŒbH Õôù|?¬„Bt Á¯™ÏÆÇ¸´ýŠ{3pŒõ½­É$c!ð*‘ü™'¶:j:Nba"šöÂ>iÛCÓšß­«ÓÑ’ëYCÓ®2Wµ—î¡åõ%nylZˆznªvX §÷¤bÅ$¸óH8žú\¿MƒëÆÀÇÏBÆR¸çfð%ÃÈ!P²î¿gÀÁ}×À ~ðápÝk‚Tl]wßîðÖKpöèðƒM¨–Bvðà¿©Xô-Ü# ë”Ì IDATì)€/‰Üwj+xæ]èPÖLƒe‹«¦‡…‰Ü„|…Gr 6…B¹À"DäŸRÊ›ÁEû4#>™ûZ‹Õª©¨ŠUe¡¡X ¼‡ÐfôÎGó ݶ"Æ|DbÇ Ù¨vÈÊÊRRRRdE9|©¸ÿˆÑ\ýÑ<>xã•[Kø’ð&zY½yÛJ¢YŒØ7{å~/(¥[TÜ5ÕS šI”ªŽ´#OÂå‰Ì8ppb!KPÄ´·"ØšŠ7>÷‡‹3ò88jœLÄÔ^°Èáe펦½0¿ûYz§‡ ¶½[ê>§TNy6 .®¥Wö¼áÇ•Pz÷‡i?Áƒ#¡}kذvˆšú³`áb¨Û>þ2ÿ„³!¡-ìÙn È­`@ žü',_ úøog¸v¸Â 3î:0öhØ~ÿº2ƒBC±¿Õàž÷á¼î°yÜw+ì΋ìSY`[ÉvóeQHÓE¶ä¥M€=„¬U3aý P^Sa%VR-{vUACô«ÉáâB$sk tú׆ë¹ÈF„\,G´Cѽh*⩺Z@ K=O=ûì³O›6­NýúõÿŒ¾ú<ñìCÌèsÛ55 9±.—÷kÍÌ—gðð¸÷ysô<[yôáGÈ.KgÈàs+½kGXm³íÁ(ÊöÚÕÿ“%²Ç“Ãà˃š „UðžBľ>¼­á | Ð0Ä(+dž&4àÔ0¼/d­ƒ»o€u[*7—…$ö/ ÆdëlWCYD´Vg}S#ÍʪͰ «–Âê”}"mßÍþµ˜„xWãŽ@DøÚS¤#¡ÙÈv"Ç„¯Æ^$XfñÄÍ"ÉB.µÎå©áë4Bd®n $#Þóa|÷ÆÅÅ­‹mFù²†aà×@SË'7h|Æ%ÜyÏ%Ü÷ê·èRRÍà ÷½Æ¢—òþ¸›˜ñ΂¥Å©^®¸ã5F>õ(š´ZÁN.ÌY©êÆëÛsµdD~/¢-݈>à"ò¬j¶Îƃêû"¤uþ7£JÅ–íVvMÇ÷ˆ#/g#ÆvŽ'+±€Šµö†ŠH…Ý<*š#Èfê°óÃ"µÛ¬29퉉‘ ’Lå 7 5F_ ½Ï>çBûSá´s óÙPOO÷EöWŽÂ[ÏX±Ö®‚µŒ½#|N9l4*GŸWj§@‰††¿Í‚§Ã‡àÊeðÙwì7~7_#!þ¬ú–nâheA‚Mx(ËŤUÕ…9ÚRÄÀguζk*¬&Ov …]KQQ”§ê¢V»éwa¢P¨ú›†K/à DXÛh LAV²|šÅED+áFDÊÊF¬jí@„ïÝŽˆš–.¹@ñÂ… ›}¤ãÄÕëÉôi¿ÒèT{ZwŽ}nCn&Òˆ:pÕëÀ¦Ì`èO3Ø´k/šä¥Y³Ò÷4b¿Ýª3¢iQ‹ ýLb‹g|D†Bù0Ç88vXI¾Æ¡ëK,Ån*[Ó5 æÃ ïž:±ÕqPSq2 ví…I2¢åÂÐlÅîàm%1á}¼áÏõ@æ¶Þýú½´þ¥Äå~*M¥¥—ÊfJ!ÏÁçÁ‹·ÁuÏ‚/nÿî»ZtíÇð]j¢¦ýÎâÇw¯À?Ljmç<#ÄÀ=«  ê¦B˦0/GÜÙ“_ÀgÂ#@ס,^ Ýž…ÇG­ÁÜ©°«,"æìšÏ?ëgQ¨2 ¦Ãûy Oçd¨ê^„–b ‘U$;©°›@Y …ùi'5yrØ.Ñ #„ÓDÕ3K‘ЂöÀÅ*B¸µ†•Ô&®˜dú}[BÎРüoµrÁ#õ²Õ Ñ|+ìæ˜Ö1)D„@˜¿›¤Â!T¬ï£9ÿ›¡Æ£m·k,jÚÜ k€'€€ŸŽÝþÄ¢GŽ:”ÿ…Ýã`>Ö}g[¿,V{Î.•ë<’,1*YÃs¬Ú ˆ¶`ô  >y‰pj3±½( $UÜAíV0|”…3Úµ: N?ÚœwÜ%~Kk i»aæl8õxîmxl ´¼†õÀØ´†+.+ðÙspA?èÜ|î»_œKNçôƒäÌþíØ#ÚH°´ Ír1Õ¯ZŠeˆœ*M„ÝÔÉZLRaNöˆO'ÛŠ“:‘üÇÕÁ' :ÔÁs ²Ž9ö…Œ‚®Ë–cÌñƃ³Mbq"3ê8pp2Áª¡6XÌ÷ξ€e.PYå»ITMžCÞC„CÿС¡qàà°ñw!&¢™GÁNXÖ%±0ñY‰FŒå˜-ÀÞ=šÞþŽl:O(Q|/¤êôŠ3Ž~¸ñ³ß‚igÂÀ>ðéï‘mëf‡À²íл9¼ò \9Öv‡vçÁ„óľ›AIKhw.Üõðâ¥Ðx6œ|ü«Ø§l/Œ¿ÖlY7%๗à£×áü[D>Œ抖Lh=û@à¯c3’ Xƒñ¹.^Ï×(ÐÕíÀD<ë LØýÌÕ&kfv{Y;©ppl0RÃ0Nˆ™$IÇ/5ãÑÀîëeŽ/Š¥H–í^"fiVm…C,8¨X ìG#Öùÿd›G‚À“ÛG€1'¶:jþΓ’d+ædn®ZíÌ͉Ýþî#b÷주´Ýº!Ð#E–I‘¹5I#Váè† ˆi ý¯‚n-@Ò`íBøõ È-5Nì ½»A`7Ìýâ»ÃCÝXXµæM„z} kXö¬Z ñipÆh׊öÀŸ?ÃúU )P»-x س:L¡hÒÜ2eBÖÑj¾FP7 ü¹°oû‘÷¬ðþóKàÁl…yeZ)Âb5BP–èΞ“ÂñÉ,vÇûŠEMŸ ª Ãpõ}ÿD¶iŽ$IÕÑùÐkÌbŽ/[<–b:n[‰Çßyüvà *`÷³¨ÈÛ¾xegN–P´£€×!ˆzœ‰)br`´"™ÈM‚aä½”'1”'Ömn„={; [Ÿâ{.U§O|xÌ9Ò¡Çšª "bŠÝ ÛüݰìoýÍL–§pà0h¥Y¦‹¯Ù:XÎgµò6¯s4–ßiðbŽ‹× 4Štc —b7íÐÁ’ÜYys ·f϶‡µgÏvààxÁcL‚`w”·F‚òP^SaF„rü+8¨˜³c4ß's.ŠY0š¿^Uà`2[eÊsbüùh‹HàºgÜ©lJþ¨‘ò‰C,ìÚ sâ·Œh“¿¹Âhj-¬ÄÂJ.L!¡>Ð=N’šÞ—¬poŠJâÑj/NH0«Xft¶Ä€V,Ah)üˆú`~Ö²Ödwv-E4BÕ'Ⓝ¿ìcŒI¬cŒ•PXC̺,Ç:c·•{P»¹b43h{ГœT¤(ß+ú¬l¨@3D”¨©À-8Ä¢ªa_ì´þ_c$Egr*ë¤o×^˜f ÖÉßN.ìÃüßÔp¸ÃŸíÎ]½Jüs©:k¥ö¢&C†Ü<•íâýBÃØ„ÐRìå@_ŠCùQ˜ZŠŠBÈÚëBáàDÂ:¾Ø0ìšRkhYëqÎØíÀAÕÀœ+ìš‹h¡*šgŽu6·š:J¶ÿeËïö}+„#÷gÀ=ÀÛTn†*Vò`ÿí7ë1ÕÎät ¢i/*2]°šFYÉ…Ý4ÊJ0|ásÔzz%šý3Ñ%=”ª’î¢w•ÊÅÅfˬj-Å:aQž8XMž¬~ÑH…9èÛ#>E{988Q°-ÑÆ»#·b9Æ<‡*Öà VŸ »ï…=ª`eøìÙå»,bîc¿FE㱎bN}¸†˜¯ÿnŽë “I¢E/µîoÍ>V­àLNã`äÂÔ^˜$Ãk+ “xX÷ktoïQÆ¥ê M8‰µ’HÚýtŽ›‹BzÐ`BK‘¨£å¤° «–™#¡°›>Œ­ê f¢"í¨bù´ ûÊ¥*vͶ=b¤fûnúŽ–TTD$¬šÊBnHš€tD^"s­Š±AGø‰ÞlFdè>Ö€ò¬}%ˆ§Ÿüþ4Ãë»)O^£åN©6ò3AG«½°:wÛº­ÿ›¶Ôi@w´º#É%=¢RïDÇÔ©L„y÷ÄB™Ñ9°)¤ç!Å"NÖÓR˜9)L? ó«éÓÁœ³O––tpòÀnÚ Û¾G#©pààø šÐf]9Ž&àÁÑ™ÙF[d0#Tša§cÓó³%YnžÔ¨¹ìKLAv¹ù‹æfÈU]Ó•(, ¬0¯‘»k.¿e9BÖ1µVkŒjÝÒ™¤V5£5“54­ÕáÒî{dØ}/|@  g+·œôLªÁe Fùl5l “Ù.>-RUUdö\†ÈèlúR*â“PD‹øÍƵ&·œƒ“Ñl¤íQê¬Ûñڃ㇊Lh+2¯=Òùƾhi~šr…Ž ÿtÇÆw­×¡›ïÔÁ—S¿CâÒë㎉E’Ô@ÙQÝ ƒê ÃÐÑ‚e¨R 3¶‘±|>›gN"kãª|] -Þ~'«Ó®=«6 ‰êÈ`_UjYÉåÅå‘$"Ú³çq\Âde[*åÄ Ws©Ö0"‰Ã®‚¬(ÔmמVý.¢hoFíìÍkÎC8w¯æÀha'ÕüÔ¶ïÑUSE•ˆæ BˆÎöm éI‹åx/ ]}:rus¡’`I)Ü´×Å Õ²bÃXŠp<ÚM$•<SžT˜Åž£Âù©¢0²8pàÀAu†Ý§ÂÆþÿ*I–_èõ×ðØ¿ð%& ‡ŽüBŠr—Oç—ïdêÓ÷°ðƒWXü¿×YùÝüe*I§´#.Î!Céæ©|{ÛÃÄt:“´:ñ6¿¢XùÒm¬\´F½NÇu¬Dµ0’‚\¾Ø#²½¬’œ|$o6¤'+ÅUðéSÖè‚Ç]Jt’œT "aá¬IMtDÛM ¬;­™<­ß­Ÿ1ác™Süj¯?vIõIQ¸5I%NᄉÖóKà±…¥!?ÂÂ4{ qðœödwA¢›ú}.¿ˆ˜p®ì®#ndö˜KYþû§LûèL†?tSXBwãòyÁÐ ä"ûjá­¥ ‡@ ÆpÆ;¿ÑÛpã1ÂÂŽ ŠÁ"?š.ã©åC‘A³hW7èþ2üÅ%ÈžX|I1 ‰ßƒ»W“»=Mq#Ëb5¾Ü}˜ÐJ¨ª„ËçAR@– XT‚‚»–ÐT<,ÚKÎ_«êÒþãeY˜™fW&Ù1“•)ù1Ü^ÜE,´ÒÁ² ®ø8\.q-ó‰*nÐüBJl9pàÀƒš«”!Å÷¥Ú±ù€GßÂã=*ó§ýP ò6ÿ.yh;òR\*¨–YS‚»É ÚîÆž/擟WD¬äF’2Yôþã$vL“^§±oö$<{!#‰Á·Mþ’ìˆËE—€’Ì}>Ëfí aŸ!ÔM•Ø5ï~¹} ¼<‹®C›²}ê»ü0æv”ô®4=£?]«YÿÍ+”)õèÔ¾>1± ý.âë6Àçv•Y$B{6±iæ6Ìœ@b‹Î4lPmÏF~¾y(¶î£é9ï)cÛÏß2iã_¸>Ž/½>1I‰¹˜:‰Mˆ¡`Írvü1@ß«xÏFv,˜A‡’RdÉKÁ–elžü›åÒ;÷%6±Å+×±sÂ7døhÜ÷’õ=l˜ñ#3ò¶ûŽôT••ãnä§ÿ}AR§A4j_Ÿü¥³˜7v${J .¹u8Áͳùñ¦l/Šá”þçâ)ÛÅŠÿ¼D7²rìÖI†.¯‡þc^%{ÓªÙ›Ö<Ü…±¬r§†£±¨ñ°›ëXM£ÌbwÞVmŸV‡e3ôjûÝìrŠ Ú>‘«v™\¢ÄKÕTË8°&Ç ²CðL®›÷ T½ÄP· }YY3ÙÕìÉî˜í„uàÀ5sÖÞÊwaÿѯ‘Ô¨îQ…“µÂÐýö®¹iõR012áK?˜OYQ ’[Â0 ’=ˆ—ž!=ŠOç˱úÝ÷è5ât0á‘}°ã­X>gïšÌ¹&.öÍžÈ§× gù„Wi{æS¬þøB¾ö\ðÎ4ÚtI&”ͯÿlÏšÙ2äéoè¹ê~˜™Æ9/¼G½Tök+ö7œ"Üœ{<ó*É)ñ¬w7îæôç§Ñÿú¾¸Øñí{|}û(æýo£^¼ƒ¼—¯G?OÏG>å´µø}þ¿Àã-çÿ`žÛÔȲ II¢Í£_2xø ¼‰UK ÀMǾåì+à2tæÝy³§m£° D|Þ/Ìùü êyˆK_~žÔºPú×v&]Ý‹ _¼Gî‘ùÉ«l/öÐëá8ûæ3p‡B¬'“ß~¯ÒD3]…„ºué÷ÐkLºëâó%E1‰²k*L²Q¥pˆEÕ!Z¤(³˜æRvaÚÔJxˆ¬à[}ìÉá|áý–;´nì–N½®Ø-K‘æâØÉE˜}‘¯ðt®ÁºP(A(6ñPžHDó¥°Gy2ÉR´hOÖ²8pàÀAMƒ)ÎZÍŸLbáB$À»³Ë5wɧô9çáúè``hqiÉ@6œD%Œ`1 áŽÁi@=†_ER”€¯íÚ>iŸí¤, Šð´’„¢ÂÆmk1¤ð/aÕÇ„BàRw¡Ë Ù;ÈÝ¼Žœ•[Ièþ:MÛ$ÈÃÆcçÐvĨzi]óxº†"ÇÓêê'IKG ”’µø 9âì5,~w=º.¡ú3=àÿm>%þèš`RzЦÖ:¬Óu w™ô¹ä\¼aŸCBê9´<#!—LƒgÃQrW_ƒ8%Èúï?À¨È^Õ­£íÚBÞúõüµaÞ&mètÙH¥TÜ4¿d8µ>ý]­<ÑF B³³ÐýÆ”yo<~/°Xg6%åÉÄqÑ^8Ä¢êauž©(Ç…Ýÿ°íNÝvbaõ¿ØÌ ÆÎw C=”„±)Ãôòµ8H°'Oä¸ù°(¤… 6 :m6rMCa×RD k%¦?…=Ü®8pPS-”½çšôÖÒ»¼GøTT¬'ÉqÄ4èÚ|2wäиn}1ÓZ¡’½æ ŸÏ ! ¨…Ïk 5«ƒ'¾)È{Ë/2+ýF)ýú ;=^ ÃCÅ“~* -Z¡úó).Òb‘ô°D«CÜ)§’ÔBþûUÉqI²pb7tP0ŠÙ6ñ-2e00À0Pv"õ”„C,CFTR”0T’äE6@ÓѤtj7«„óN`„]e$ IÈ@þÂI,Y?Ã"œAjŸZ—GÇ0bbNÅ#œÓuÀŸŽ;6©’x:tqýöÿÙ;ï0«©´ÿN’Û§23 éÒ‹Š öˆ{ï~ֵ캮îZÖ²êªëZVÅ^{+ ˆP©JïÃÔ;·ß›œïÜp3—;€Â@~ÏsŸÛ’““ä$yßó¶ÒÊù³Ïn$S<ÏîRþ‡¸D9ŠÅGvƨlëE®¸‹Me޲ܡ²3GÍÖÌŠëCNXCsÚøGIжn¶|8¥{ùb­Ê?k% ’É*2V +ÐÚ²B4¥PØ3>Yiw-+E®"wÖmÁ ÎvpppphÎd»@YqV`m¡ªÇïuþ_(hUJr+] ,¤&(íº “˜óÚ#ôßûÜ*¤ÒóÔªꦌã§ikñõ»ŠÖ>âs ŠÚº*„ÒÎlHIZ9dŒ¬l¥F* Þ¶úÐWtlç%™¨ªNíâyþ =«ñåCtU=RMG¬»`É[ÿfî×.¸åJ³E!6ÄlÀ ™eÇŒHiè„Ô©_üF^<Øfq•t»†R ¥™"J¨ ×®gã ¬Ùi©€¸lR‘è$½þïYö>n0©pÅ¥]µ€`X¥uûbfq¢á•ÄB—h¯YM"XͶ.Dfè_^Â^ç^Ï=k´4Œw1³Deg%µ»—o79Ë©cñÇbšsÕ¾°ÜŠ,á|sµ/²^¡ôz•Àç:|<6˜ª=p¥Â¸úô©Þ\Ì€% 8kÆÙ•zrAÒ˜¼ Ì'£<ØëRäêKvM KÑÈN'k)NÆ'‡]…ìô²v7(?prë>{ô8ü„mäebèÐæÐÓéÔ1ÀÚwàÓû§>Gó&$u³'ñáÝÿ$‚—½Î;ÝxM)š™¯=M]¼ùPóõLÿl!þƒ‡S\äKÏÚKt öèÜYÎü¾B)Í£¸]>‘ùàÊ#øâ™gq·îIYŸöËâ«qC|å7|}÷uüüé·æC^ “Rl™±ÀcK IDATŒ­(í? ôéÌŸ: _E>…mò©ÿêÞ¼ðH¦¾ÿ Š+Ý.’TR¢¸¡0Ð BAV|û5j¢K'óó3ãÍ<±¿™4(ê7‚€¿LOE pE–ðù cx÷Ö¿ÑJhÙµ ñ¥Ó™ñá¤ÜF‚ù¯€³0Ç™e³ª¼ÿ!µ-‹ÅŽÁžšÖ¬7U¡;[ÉäN°±k”æk%§­eÏ·C.õ޲$sY/ÒVŠ'kL+ÅòTª3…ìR+eg~ÊÎød¯I‘b·¾8±Ûûõ”ý¹)A&{]‡mG®{}¶€“=±–*³9m©°ž×Ös¸ ðô×ßÛzßK¯#ÛŽQAÓ Œ’J騚W¾†Bãô®B—ÏL]J ±ŠGCUÍ*ÓnQÍkÇ ¤¦ÅPNû*Þô^ª$Ãq$*š_CH[UjLo#a@2¡£xTT%³]¡šÿKÝT„ìiq…4«hknHE *($â 4\ ÛvsŸ¥4·ÒAuƒÐ!™—ÇüOqƒžn[õ˜UÄS›+‰45½}ë7 4­OÂü®GR¤’ªÏ¦5>¶Š ⑊Nj;í˜$uÐc ·ÃÓFóÀÌ—ŸfâÍçWcÍ›C¦Æ˜5Ù›=½mû°­tø]Ø3Ge Ü›s²¬v7(ë»]¸·RØ~ ,}'¬˜Wí4˜Ó£À,àL+„U=Ûﱩ²¹ÒÈæªI‘m©ppØÖäR$ÒóP¬Îö[ºÒÄïÛŽ¦¬Ôv× …ƉN쟛ÖÄ 5)èNÞ+PÚªu‘§’ÊÎÖ´‘º™ðIõù6¸¦I0²Ž¤4 N z4°”„è¦ýû–-^Nqù‘ˆôÙ‘†)@+3U¬‘b£3¤'ÍöMR6¥CꙀòìõ¤™„DŽã#uÐuP½æv7r#K÷ËÞ®žþ®ØÝÖ¶ß(t#a 2~KAÂ^¼PB*fÖÅÐÒ©ý³ãeŒ$4Ÿ$$-±¶Û“FOB×Cã«Go-iX³|_`¡Ù» r™UFy»¥ów‹ ë&j¥¦µÜþ²]œreÊ®^ƒaY7\˜mÿe)½aYŠõ˜ ŪtÛÙJŦ2>Ù­vE&;Û“c¥pø#±•ÌõäNÚ‘b6e¹pppØödO,Ù…›/i4~æ)4¯çFvÀ¶=ÍlpD§á#)¬hÓdñºmü-⣴%bRÀHÔ³ê«)äw:•a—Ühf^²‰Í¶Ýtb¥ ÿÿ¶d»¿©[ÜÜè”ÙöFàoQLÏ'óÝÓ÷Ž^ÇLªcÉg*™zÛEiw‹û앇Øs¡Ò´ra¯ÚmwIJ`Z2b˜3'ù˜ ÆTÌà«”žµ¬õ²+Ùñ¹ { …õnß'‡íI¶+¡õ2€=Á?ûµQ[–ç ŒíaƒvppøÍ`^¥Îòãm`&æs%{‚­9X-r)VŒ… tQ4mPÏ‘§˜3ÿbñ»1@)èÁ1.F( ˜2ä°u(ìqàhf¾òh·d4<3ÖÂEc«Åv«má(;/–0n F¹»³cì™—ì5/,ÀÊJáO·Q™V£±µ#Ûba5Ka·RØo?ŽRáðG ²>Ûã”ÀíQ…¼ñpÕ7…LåjÂÁÁáF(ðç·]<<Ù°„pk62‚4årg„À~Å»yZöðÛ¬;ÒØ†Þ~f¶ zZ÷Dq‡®ÞÊù³†b&ã±Æb’Æ“nÛG±ØùÉ•š6[±° ôIÛ»õò`*nÌ˺UºÍµ˜5(,Ó˜¥(Ä7ñ²+*¹¬N Y‡ESE©$é{]Àmà³ÊJ:88ìxp›Aóe=S`ã ¶»2‘}Ò€0¨ÕžC(h]Ú(µênƒçK€ÔPÜÊæ×qø]H<…~Ú ÜÊù³ú¥@™1™+#Û6“×Å¢ù퓚˂¡“qkÊU|Ϫ`a ¤Õ˜Š…uÓ¶ÇdXÊC¶R‘´µ™m1q\Ÿv$Ù)-- %°CŠtŽÅ×Its,ö‹$hº]\6¶¹Šâ)@!Äí÷9xGömÇ!@‘Q¼ü/&ÿ÷dÉ(NyãYò]lüÁî„4 ó°‘Ì|ù‘®@k †¦‹åmS¥ÝQ,šöËÏ.Àg×´°}Kø·+Vf(+IÄ*LÅBØÖÉ^/W!¾\Ÿ+…ÃŽÄR–³}œ]éïÛ2;–baÏëcO`²³“+U¼¥XtròJ;îsX£ŒD» B}í\¦=~'aog9 ·]úf ‡ÝEL¨éA`lBùH¯giCHss7ÛÖ:”÷D eE^¸ru`ù/—Õb›ÉnŽ-ªù‘ßÛ[a)¹Rņ1ã)êÉ(UéßÒ¯Púe/~gܶ»WYõ)þ‚+[€ýÁn¯vkeDs ‡…̘uýf @;+ö‰ «ÏÖ>x€ž¥]z‹@iËÝs†^MsAÊO›Á×rÀŸNÄ«€¢šõ0T©“G1dºÆ…ɺ:ÂU5$SfM {[ŠË¬O¡:‰†0†4ëb¨.Ð#’á„YCb7—p}E-¨è·7@_2ãÑRv­lãk˱X4O²3Ge ÷v«…¥thd¯-]¾ŽÆùŒ³¿s}¶·oÎÂ.tØo˜–baå‘ß™‡Ý•ŒbaM˜e§ÆÜÝ¡² oÚ• Ë ³gE¿¡(nuÛW[ÞÉ Ä—Må­k/£¦.Dð‹1þìzºj_üçA*Íê÷¡f}+F¿ð:¥‰L}ä¯Ìÿæg CA ´£×¹gÈÑÃÑT¨ürSž~‘.£‡³ðµ©©Ihߟ½/¾’š wðÓ´_‘ªüAc8캿Rè;Ûˆù¸ó\´ê9ˆ_>y«™ø%»Åb»(í»¹>×ìi*K”=x;ÛrIÿ.ÉX)"˜ k»ÅÃî eW.ìÛwpØÈ¼mU¿uý®³³ÑïʪÉÎ>'¼³±%Ç«9Óì¹Ë‡ •=«º3EëhOwmÝ{TÌú[÷„öûîBÍEõSPVŽª¹p•µ#¿¢„ëXýÅ«|ñ—ÓùuÁz ;vÁ[Êç—Žbê+áë>Œ>£NÄù™/¯?©/LAõQµ„¥_½Ï„ÿAÄß…òέY5ùUÞ8}_¦ŒŸCqÏ!ä«–¾z3_¼0áÙÑ{¿ã ”ìÑ ¡¨å@Eúç\®PÛ”+qØZ,¥"Û-*;pÛ*én)V°v”+kg+–Õ¾íR ÞÁa+Èž5´,Öû–ß@…¹V°f-…Kà§•PeûÙy­póßÖÓÍc‰k›i7ÚpÓÿïRX£c+Žs8ë6½L0ÁØï߯FXâö¶B@}-ü²VÕípÙÝŠ‰²öÒ~`çVѲ-vk…tpùÂvwK¿i€»Í@Žºõß–µ Õñ·3úîóh  põ?…_œÆÉÿ‹ð·¯2sÎb:žû'=ö4Ãn¼ƒ“žxƒÖíó™ýÒ]Ô4€ª™nûQ÷sÊóop“ï3hH[Ðò9ð÷8é‰8ñþÇ))òœ7Ÿ”Ø}]#Eºà-(ÊÚÒx\f+îÛìúr\¡vìîQqW²žþœÀ´NX·¸ór¶ÒýîX(vf²³BY7Q{v¨-kE‡±ŸÀ£Ó3×g†|vŽÚ×Ãø·¥  ® &þª¢xTí‘ …—­¿Úˆ7ÀG ¡^w1¬{ŠŽ9²b©pß‹ðµËÇøs¢äïŒ'Û j`f• WI©UÕç· ÂÓïÂ+©|>>-Dž–ã˜JøËã‚ÄžžBÝ$aú"È+RéYªo›ó£ÁÃ/ÃMs¡kŸæ\\kÇwKµÎvÕØÙ-¹,¥vgIK·¿´Æn¨X€X­Çãf|I6”ùª®‡˜Eùªƒ™ëT4·B¿vIÊ “P¤!‰$a}X%UZº×KVÁìu ª¦Ð£ t-1+üÅ"PE€?E:¬­C‚ו5N€nT‡©$hVu þr:H1sQ§­6K+¡&.( @çÒô M G*„ëaQ-*t,"¿ÙN2º¯ÍÇ\×!®ƒ?-xGâà÷CCÖE¡}±9ãæõB2 Ë‚PQ~¯Ù‡Êj¨‰CaZIc¡C$e.—ˆÂŠzPÝ‚öEÒÌc@}ê£uqÔ›*$BðK5D¥BÛbƒV…™ýWHGC²rÔÄZ”å³!Üøž‹B52#2„U àq *Š$.ÍvL5 ¿¬`JPV h_lþŠCUDR…x <›ÍŒ¬ š3D…yPä5ÿŠGÌã+%´jÇ ý‡@AA­Ýüe0o½‚Û£²gŽÅ†yì‚PÍy $¬ª1·ðBi¤¢°&lŽÁVEàÙ²‰c»@nÏð¶3+› ܶ¦Zç•¶ÆSäÛ= ãm’Lî!ÂãÅHýH]MŒ–í½è:(ñëjW£»4@¦5™-–8RJn´èص?Mo øÉTÞÞnqŽb±k“Ã8d ™,O¹Öq.U‡æF®™˜-Ÿ;5๰.iJX·žìåľAHÂûÁ?tøÛgº€šˆ€"xð¸í AmÌö5U§m;cOR8¤cpíc‚k ¤‹äÚ¿uÜšA«r/ã/‘Ìÿ2ÎÅ_˜óɺcÔ¹áìbnêUËã|51„¤À§pæaùÜ7¢åËáÈçê“оGÓ/ 2ñ#8û3CS8aOɳ * óþ§gB,]ÀGM[¥…wü¦z}¼}~ŒèrÉŸÞ‚q¿‚[ƒ„¿¦Ôol|gðÑ7pa ZµÐ¸ûd7gõ‰pÍs‚ÏkÝ||™AOxá3øëW\ë¦]M„#S8`_Áäé:?{‡ûæCE—ßœ_O" 7>¯ðì/’ú¸P$E…ÿ•Ï?‡5ðÔgpû× ¢]€ÙÇ)®I0ìa…ºŒPÈsÖòùd8ó=…¨ÇÃgWK†”ƶ¤E¶¥1Û²3+ÐX²^~  ¯eņú 9 UAq—>¨É÷øîÅ—éxóyøü°~òxMŸI^ÿ«)i u†#–üV *:´À”ùBl¬¼Ã6¼¾œ¡¾{íÊLÎO·ØsÜžš;¹26Ó@ ÖÕ™ƒ¿u{£öˆ˜§Äaä`øòªS®ŒsXÛ0o×N„Ú˜¤]™‡Sª(R²ti”1/K†Ý €ŒJª#’eËh-=ÜY%–”,]ãúO|tlý[Èt7úW¸èWœäÍ7᦯$Õ1Ø·‹‡CÛI‚‡ßó茺õ†+úÔD fÏñ½üó ¨‰T´ôsù¾:}Ê3»ÞµÂE×£ñTBšHÈ´š¨†äî÷à…¥*·Ÿìã‹àª¾’W>oàþ™ùOG©0kœü hù?ÇÍs'ƒMqÑ‹+êÌæu!‰n; 1XÛ IHt¨n0xà#¥ÜÇ :ºSÔáÉII~LºùÛ‘.úç¹ë ¸åk8z?¯ž¯reoxù‹½çÞP²¹¶*É3_Äé?ØÏ+g+Œh+ùøÛÌ*fP_¸¢y¤OîçʾsºÝB5Ëà¸gá¡òà^^?ßà°ƒ»_kàY ˜¹öW.óÙ*÷ææ¹“%Å2ÁÕ/ÄùxYž)Ü¡*"PÜðÉGpÒ›PÐÎÏÓçºyðpÉê_ãœô¼ÁÒ¨!¸ü9·HpÁQ^;O帶:oMòFU7¿ Czz¹ùPƒ"µ ¥@‡‚6лTR1˜:3ÁÚ¤˜¼j#]ºøèZhºX­ ¬‰›²'^‚ÿülPŸÜÝÃþ­$µ :w¾æõ%~W@Ýz'P„©ŸtlïãË’´:Ç>(xw…dy ìÜ-`ø3(qñÀ9{yB ›kZ1íSÀggÔã‰ÀÑÁ{K ž™ÿ7.?> ®7¸ó-3‘»ÀÅ'éôïOcpðcðk\åoÇz8©[°± m(ŠèIX‡<¯ÂA}—Kë *¥>cãu øø[¨Oj“¬h(BG¹¶"-¤€AýüL87N‰O'Q IÚtðòÑ%:ÝK"¬]÷,€“)àÅSƒ Â˜ƒ3Ìé£\‰’JÁñåóìI ì[_<KÖBÙ!pj¸o®Â¡5öï6SXØFÎÄ`ULáé ýœÛÇÜŸ}ZHÖÜ M’\?ðhŒ½P㘮f:­î^ØûÙ¯/Ô8²£¹š"ЃpßthÑÚÇ»&iÓ" ºú`Äq>XRÄz-¯¯ƒS,àá£k8 æ>)X™tó—}àò÷¡w'Çõ6¶8åǸà¬ÁðøBÖ&øºÒÏáJŸVªÂ˜Þ Š,×uM‚Õðäbóûá‡ðÑñu$jàGS×<6ÓÍÛûEh€Åá³k½´X‰tµ‰…õ «`õ*ó@ŽØCPì5Lç‹-£9Y(ìä²’ LÅ"WZ¾{OÕ€¿”®#OÅß»F”¶ì9ê|Úv3³eIDa7ì-Šû+‹^ÁÚÚäõ:ž}.½‹~ƒÛ“Šƒ·]_zu…y RʆžF·Ž}ñx D ]:W‡¾(»YÝ\øMÅ¢3õz®‹mŠ£Xìž8Š…Ã®„Èzß:Ò@Rf„®þ—‹AmÈÜìƒ5z”„A‡úÁ}“à»*Éôu ôά:´¯öyQ†ø$ïªbnЙРH¤Íþˆ9à>Š”Ì]«’ŸW4$¥Å’Ÿ³ž„Õ1@(\>:Àáê@—’Ѫ6{€¤@sAÇrAø×$Ý{µqsx_…‹ŽMÒ©¤¡±b!€$ü¸´Ö>†·É¸X6&÷‰S^)cËNͰþJ¼¦ÁæMjp/Ý‹L3Ò¢•¦±åû ®xÉb˜ÇïËš$4¤XY œ( ¯Ê¡½Óý$¥EPä$ƒ:èHÛf“ 6¶ÞðÃ<È«ðqx‡ð©@ÜZ¬S¯›3¬í»ú8°MÈÜg}÷€ž@U,³¿B…Úz¨BÌ­sÓ»*ER ɲµ::«VKæ§ãOŽšÚPÆ´eKøìJá­!5‡^Â9ÃСì†qI³U:´‚ (-Ó8¼[¼Q;B@<‰ôøÿyvˆ¡¿¸Hf¯7œ¹ÂÀ_ C `q>^ªP¶^à+ T`Êzƒ™Ë]ü°4‰ð*ìÓi‹Nû†.[ر·âwš ‹·>û¿¿EùnL, …9ô¶G‘IÐ# ¶ÀQ>ž#›Rê –ïÉ~·¿Ë^ 1 Ã@óûq©L+§…{fä°ÑèÐ Açs¥«ÉXzb(¿ßù¤ßß/¼]ÒFŠE.ámª`8ŠÅîI=æÍ¹hGwÄÁa;±ÑRnÉSw:ÈuéŠKBnJ[DMáÓóÁØé'ôÍLs{-jf@.J™›´„Švù™ø„Ô& ™¶Ž(Š)GIMàV%º¡°WG—*ñ*¸ÒµpƒÛÒT(öän\lL wŒ1èׯý sV$¹a®ä¯Êe'µàá½k?© ¨ ù­; ٸ‚˜…šmÁ3È9«‹šÍ1S¤·ë‰H–W¦¡„ŽmÝtk/håObDMàq±¡Ÿ2 VlÉ£Ó€š0(m¼Zc­Ãín´‹´Æ¦0U1™7 AI>U—TUT¦³ÅjšÆÉ`@«$‰UémØWT ¸Ôl0Ù‚¾Û‘à/†ãÂŒo$sVE˜Pm’üô+¬ÛHâRTP„mÌi’”TØ¿‹U‘””›ã{ôž0n LŸÅ†=ʼ\Ú3´wR<1YcjZ•jì]Û2%¨i²÷•¦âº\áròv„Q¹ IDATdßv$$­1œN´Û¾[‹¥•vÕç5¯-i&Øð YcJgÍØ·å€ÇŸ¦”•ÎÙ±X8ls’˜Æÿ;º#; i÷‘ýÛ˜wâ5Qþ2¡7NˆRœñZø÷»ðÔ‚øF TÈó”|µ,E" nü2VÖ˜MviÑXÄÜ"?k!ñ{ÌYéxúiyÌÐc¬ƒÌYë¢*£À„áæ·a©õNÜ;>Îðöy ¯5 ˜òº1+›pHÄ &ÎvÝý¼q@„š3îŸÆê<õu’ @Ö1Û£|Z©Ò5J\)Ð`Ö÷pÑD/Oœ›$OÑÑu0¤Ø TTW‚ÈòÊ¥öØS4óÆuê>n^g~qÁü…ðcƒ›nQ‚ëÍö¥!6=¿-LE`#TèÒ ’«STÅ]”¸ÒS¥1øføòUª¹î¢å)Ö&ºøuP &³ÑYEN„q Úúx÷Ê "ŽY€± >Yìbp۳֛ˮ fLL±8÷I…ž‡åsSçz³{ŠME¶Š&sì§Üpdw¸ÿ;˜¶$Î÷󘜶Ÿž©rdÃ02V²³p×°:HÀ¬ePWqÄút×§°rE\phûv‰/ ¦,L ½Ð¦[€vÞšM޵]„¦” ЄšæöîÖ‹ßÃn“²­ hd’¢g[Ö²_[=RàíÝ—5@َÃNE ††i•{ÒWõø —Ë_tqøO-ŒÜ×ÇÁÝ og.7ñëÇ=çãÚq.NËà)ô0ªc|‹oÓÖlz´Aç¶· f&¯0W~å£0þÀËÝoyXç–ï} žþžX­pÞþnʼ‚`CŒËß4¤Ó’! ý8Å¿šŒ8B`ÄáÎñ0ê9XrÑ¢% hkΤkªØxzKƒ}zƒ‰rû'~@pÜø6L¯–äù}¼PS“büÏæ\Ö”oà±àse7¶ R¦@ÛÓ NŠòýj/xá×9pìÿàŠT‚RÛ"«„ ¬¯ ¾÷†hM”¿à%œ¶¢<þ1LˆÀ Ãò4*$ÖG¹é=? :$àÖ÷ ¤©Û-#)I‹2Ø¿-ü°$Ìã3òÀ ¡j¸þ)8ñeƒy‘<w‚6Üñ¡Îü:èðÐDxu¹_ÍŒ5AA8iîÄ+àª×=,‰ª¹çSпt*Äê >!iÕÙÏ!m¢Y+t]Ò¢†•šß7Âß'x¹uœàÐÿšcîÁ¹f}•îå°Oaf}{ Ú—@«FLá̾»¾FAnÁÌþÒ@¨šeupøƒ‘€PU„ªBcW¨\®‡Nð¶ÃV±(e³ó—»”ðÝ'ÜæfŸróW'Y)TîÞWâϾcH8a_x੉AÞÿZ%3¨Ö—á£KIÑ~P1Ëàúg#Üÿ¦B]Š $zÜ@G P è¶ CBÐÒ:f†çpÇÑpÞëqö»[¡‡Oeq­NªÀÅM£½´öGY©C<.7Ä X} Ç!œ`@‡ hiHnx¡žŸ+‹yöèÚL€±„ÇÀs/Nn`Ê"7í]I¾]*ÐËϵƒÃ`@4¸¿ Ñy–‚ªKÖ…áäá~FwlȸahðçÑ0å)ƒÿ{2ÌCãUBQ18æ˜<ލ¨Gpë¾pù”ƒnS)Ö«‚’}úû9­{¡ÀÐ"xÿË«ññö™QÞŸ/×$9fX€Nþ†•XDœÞY2½ÒüiÔžmóÂö7dCC‚n;¾$ø¶&ÁíïdšØËÏ{™ûå)†=+`J= ´N ü°k˜³ðkì]±åJõ.B.AMMu{v·cá°3¡¨¨š‹”®{ØÎJ8ŠÅîÌ*`0à›YÖÁa÷A‡Š.ðæ•’q?æTjD ß-èׯôkÀ¥)ðµ„/“ó=üPé"©ƒß¯pHoÉL×%üi ŒŠ« îÚ pž1ºU KuHBE;w)|¹Æ…‚½[‡È«€w¯•Œû^ðKF\”iœ:$F›ƒ_‚ðÏS5!Ù«{—„KFAËÎ*I)(k…üï,xoFJ ìÝ8K§jŠ„ëŽ‚A­àÃ’`JÐgûí¡rlÁÎþBxþ|ÉÁÓ–7(h.A{(ÝÍtWê3ÞóÂ'KU")…öí5Ž(ðí …nÞ0ùe0ö4…]ÃÚ÷àÞÓ ¸c$3».ḃ k¼±X¡&®p|¾Ê!}`¿vµ€’ÖðÌ û¶N·%ÁSÿ=C¥´$qhÛ^9f5¸èÖ9ËAÛO<}žäÀï?WBÇ œ»w„–颣€=pÑR&øhJHWèØÎ͹ÈžŸÖuèØ¾¸Bòòl•E *~¯JŸ 'õ©ß;rþIн|¾\“nÚ–*œ< F‰×ŒÍ¹ÿl8t‘FQ‘N¡.=~˜¨QìÖ7™zöÌ×Y¡1´gæ£ÃI‡CÇ!*y-S¨:tì_#ym–Âò JB Z—º8}p„R¯4×UáÚ#aðEx^ .CÖ©äúnQíŠ]\‚šåj"¥ÄFSU6…PÒßÇ•lk†YÊÜr€Ü®™¡œ¡¾ûrp>pP½ƒûâà°5d%Í5/é4˜µZü@_¯&þþúù¢pTÿ)S›jÕš—¶­¤ÈrüÏZÎr3²oÃ2@§Èu.6T9Þ è[°ÖúÖc@%SeF³ý§’™²Ú¶Žéï)2õŒ­ßrÙ(Ó°7ƒpÙöÕÚ÷M¥ µ÷Ó¾.Gq[ý·o/IFé>Ù™½-ëxXUÄ­mYûŸ«-ëxXÛ“l¨ÖÝäþXçUK¶Ú´Ž±=ˆÅ6>¢Aè{ì¹WoœBM ä(l¨Ì½Ñ1M;Ïl؆u|¬öµô:éý}u<°<À„kôØÔÓÜ>NìÇÅÚìq¨Ú~³ö=;ŽC#3¦¶å¬óa/8¸)¸f¼‹¾H~ü óyÔ€YÈ+ D0ód%sô~G£bA7æ}ÇGæ¾ú E¹ó´¦úÚбuì»T·™Œ!ÕÄ=EqAÝ·ïðéOÑýìÛè=¼?2Ç²Š TôÝFým(TÎû™gŽí R^ÌÁœL¥ß£˜Ñj rßmû6·¶‡fË g 88䯴,áJ'wlör)6V\˜¢‘ý–$#.ÙK)Êgoßjíÿéé6ìm¶ßR¶å¬v›zl$hä´a;ö}ßö~æ:IÛïÖr–@jõÙ.H6>fö¶¬ãaµkïGSm¥lß­ýmêxdŸ×l‘Ös­G±uŒÔW؇¿† Mž’É€¥ÛÚÉuL­íXíYÇÇ"eëwRùpÏ)Zl®J‹}œdK_¹Æ¡Õ7û˜Ë÷)[›2ë·-U*š?›Kk ÑõÄ¦ŠŽì^2¼ž9coaÆ„éˆ&Æ®â‚úY_±hò{¬ü雜’Šªê¬úø%¦Þÿ8¡Äf}Ûͤ®›¶³Ì´Ê†¿›ø¼U8®PÍ )eKà4¶Òcó믿î8~üø²K/½ôŠ®]»n ‹…>BÌÛm98884O¤9ƒÚ¡Lå‚î^þ¾Ãö™JuÃGrwOԿÑäÊÒvd*î2î!®:¥¥l7¥ìeý¯€Œ¯fþ ·²¶wKú’3‚‡Š®âÄ=¤¤÷þ+áT‘bÙ×/óÕ érÖÅx›N㽩>Y®VVÖp™¶º ±ñ²Í‘”©ØZ¶×¦Æê6ÃQ,ší€¶¶‘¡C‡2tèP€›·ºG.ÅÂÁÁa÷EB^1ë*¨š ÅåFñh „JÚÂhØ® Õ ©†Ñ`)T|%e¸\ §C¢ë:ªG#\O,á&PZˆ¢ë4T­ÇUX‚ÇãjÖÊE´®L—Bë¨l—úŽbÑüØ™ç¦R‡]‘¬Êßµ¾`Ø>7ã‡õ&±\¯v[{¾¶sDÂ5k›½ÅBQ$«Þ}ŽYŽcÍâå(.?²ÖxŠödØÍÿ¤8OE¨°nÒ ÌxéIæLš‚AF_ÆWÜ@yÇŒxœú%+IT-%Y»‚êEK‘) ‰·´=….¾s/?L˜†p»ÒûâÛè> -ú;Ití*’!buAd2IÝÒŸò%©”D ´ ¯¬B‚¦Âª©o0ý÷±à›o0T?]޾‚aWÝHëöÔÿ0‘)/Œ£ÃÇðËÃg³`EGF<úrÆL|èIÚyÇßw'^±s _M"!R³ ÈÆ®PÛG±Ø…X8õMî{i"=÷ÍÕçŒÎ=’¯Æ=ÌK_. ÿ¨s¹hÔ ÔK‡f…„ú°9Ó˜ïå· «ŠéÖðkä­’ºø½àý½O { úî†Ho96d³µçËa[¢áêÊfm±P=°zÜ?xãÆÛ‰úKé=æZä ½w?K&¯eïëoCm¡R=í)Þ¹âjdO†Ýø,Úš·™úÌ]Œ_gpÞ3wãªù™ÎCU,FC5(ÓîáÕs^BHi¨ô¾é>²®Â2ŠÚ·§vþW,ž±ˆÖÇ߀¢¤/wšfî0ýóy$êV¢GU&\vnM`è’ò}¯`ôÝ×à–°þ󱌿ê"REÝvëóèK>gê³ÿ"X_ÍI?‰lø…%>ǼÏÞÁ(ŸÃçWˆ E^i)«>ŠU«o¤kû¼FVæ‚ÂUëÀLˆð‡Lþ:ŠÅ.DYûöÌùôž}þmÚ ˜Ã‰ý2õïêæÌÅ]ɼèÞ¾òÖØK‡fƒ4À9O)ä÷,âù‘5›âÎZ?Zzÿúí[ÀÛûÕsÆK §œXÀå=ë~×£î—eP#5¶MᲬ» ÔUÁì>mS´ð±q†²0\ø´‚ÚµWF×þ¶óåð[ÉUïÜþnZ,*W5ÛÀb¡@tÙ×LxàvRùs«¯Ò¥{ª€üäϬ|¢ EU‘áJ~xøïÔ©åùì— Þ§ ¡ŸGÁħcñœ«éÝ£3Cÿö 5Kùæž+w>Ã.:RÒ÷lC"m>ŸŽGÁúîféì;¦•I0„ŽÇÞDñ~•ÌÿæÍ²÷uÿ¥,OLJ|¥ÝL—¨ø¾ºçD=Ý9þÉÉôê_†0ÎÀþ•ÏßšÈÒYõtòz!î®qÆc1åê¡ÌYÏ~O¢{j2¯^s#)½j6BëVÔ“I?bmsÅb¢¸ý`ºéÏì{î-Ü{çÃò­»=Ì£»•9A—?øF÷(Ù°Ž®ë¨jÓ)E’‰R¨¸]›K;âàà°EXÞ­Ö-Ý>ûž}›·f§!ãFdOٚݎ}k]ƒmøÂ¶Ýìvsô7ß#ˆ¬¬,èÖúM¬[] ¯ÿýúåñÐ!qü (ò)x‘ %´Þ­ÏöïVjZ™ù|뻂YºÆ¤Ë$e›2[l®vOãMýßÔ#xs}ß\»¹þ·ŸSû¹O§s= ŽþXáùÿ+à˜ÎÁœŠY¾„]’mªŸÛ ÙÄçPª\M*FãsÛL*TM{õë5Þx]{–‘Š™¿o˜Á Éše¬›±¥¨‚øÂϘ¹ nªõq Hõš*ÔA½épÄQëòËhsÝGŽ@¦]œŒd¦M#ÂHåôÛ1„Jiÿýië†ê/3î<ºvåù §ã1 ’ë–²¾¡\í¨žù)3N"|õK UI´² Y †Gc1×RVê%•Ôñ÷NßÑü`JswbP·l!˜iœ›Ê×¶MG¥£Xìb :ýj®™ø,w¿ú:nÿ8e/~˜ø ÷¿÷5]‡^Æ_. À“ßæŽŸb}]_A CŽ=¿ž3OºEÓÆsûϳ´²©xèsèn¼ú"*;nßš=Bf%î@\)øf… ‰J èX”ÊÔP¡ª¾[­`…Ò|I¯V:i÷–ú³jr "߯—‹A¸aé*XP£RÚB2 Â,|f»z¾Y ë" ÅÚ&(ò‘³ø~xæ!Ò³ßi7œ••°> ù>….eFF°#!‘2ýàbx›$à+R¥t3½®‚Ç€¥5_%3/ý’*¥Å‚-Œ éX«#PgHB …2_Å"- ¬­‚µaÛ-éÖ4«E:æ#Ú+ƒàr ZJÁ¨Nm\ä>_>xâ<!Lë’®CÜ0×5ÀÚx}‚¶…VJŸÍŒY‡-Á®ªÙgƒ°.RSI¤ºŠ@Yi³s§ „—NTô芴ú¯€°4‰†u¬7€hùãD¦ÌÜËBÕ¨è»áX¹íZFïûÿùûÍT»:ðèƒ7ÒÖ/øñ³G9æ¤ËX§wdpßv,›õ¾ó+æ½Àÿî>™à¼‰œ:âtç·gø^=¨[»ŒGoº˜9Óë˜ðÖŸñ6sÞÁa‡¡ÁÿÞ€ÏÊ=”Çb|»VâRuò Tî==sz†@‡>?}"X6H>·By ŸéfT‡0W¼¬0qòUŠb)æ„@SSôj`t«0Í4è—àäáù㣅ÿ<³€SÛÔsÂÓ 3ª$n¤KpíÑyümhˆ‹î‚Oª%qÝ®ñú5. g¶«@2w¿ ÷ü Pˆê‚½úøyh là£Ép͇‚ E…îí<Ü~²›cÛ›éa¿› W¼&˜¡(ÓOcýª{î_À©çã/àœé>F·MòÒw:I U‘‡›M1v’Áìõª&ÖÛË §%(÷é Ã+“àʵ1s»]»ùyaL’­’ pÒÝPÐÃCtm‚–J„*èXæå• %K~Œqúg€¡sñ£ LUÌÓ#k3âBÚêä± t-äõjùv\ø¡ÆÈþðÚ×)VÅÀïU8n¯Ä¿;¹’mrY+ì‚ÛÚhÍúT¸z–W^Ú,ã‚To@¦ÍcŠ k¾dî{Ó®~ Aó—P !ÒïNzâ&”DãʪÛK*I£ºÒ£66âü^‹ŽcX„iµÐ *ŠNÙ£9ùßÿÅe¤2Ù„‚–ï¥aJ¤DêvÓŸºK\Bpå¡z¨IÿœËj›Ú0›q8‘CS´è5‚;¯?ŽÚyßqˆQ¼4³Ž1×ÞÅ {Uª]ÂÝÜHmÞa|6ýG&OžÌO?Îà‚#;óô=70»2Áêùß2=˜äºG_àÍ7ßçóißñç£÷fÑâwXR¿ \m; J ª"’šU1 £zi$tIUMŠë?„ê„Jí*8õMø¥AÒ¦¥‹ öV)Ñ –®Mrë©›VªˆA}m’š<…n~A4aðýâ0·Lƒöm\´òJB1ƒg&E™´®½. ­“¤'öÕhå2øue‚ ^HòKȽqÎ ë$U1óëó“àÑ9 :ÖÏÁ C Þž䎯}÷*€´©€k€<àôƒü\78"a}ƒA~"1˜93Ì;kT.<ÌËeõüõ)˜žððð™>ÆŸ/9¹£Á?LJyuY>—½KZkÜvŒÆžùáÆD ·| gæçõ‹á߇I¾æ²wðÂÔIpÜ« Ê=V­€£ž…_…›»NvóÐQ:ÓçÄ™ºF²:d ¸ë–G÷Ü6ÆÃ]ûIªb\òjŠD‰—'ÎÐ8»|òC”±ó @ƒ‰Sáœ7 ¢ÜÇóç¹øÏ‘’5 #ó¨› ŠF^™çW=À³çj\ß®ŽòŸo= é—ô„Â9‡ø¸°WlcW( U ’ª¨0sýë°¤:Å}ŸIŽ–Ç §*ô:ÏfÒÚ‚? GÌ.M¶pf9ZŸ°6R[ W®Fi¦—ž¨’¬]¸á½v!“n>‡%• ¨BAJOIÚõï@tæ[,œ»–@™‰o¡#Ö@<¥7Ž3‘ ÿLT‚Û9öc¹¬Eó º@ÉòÈ–æGFU…p@sÉ$F ªbíãß9çl˦÷„$´½WAQ±+"6TÀÞ½z}íÞëµ÷Ƶ÷†]i* Š"EBï5R7ÉÖsμœÝd ¡¨¨WÝßç³Ùì)3ÏÌ™3ó<ó´øL7³¯ôã*79êI( Âо |2¦–5?ÂÀ× ˆÉfÂòRø¦‡Æc»¹¼s5ËAß`Giw×¥p[ʽ˜UE`™R–)Ý!ØE0¼@2¼(¦@K”H=ŠO5!!ÎíC{h oWÃÎåVy"¼†I N——/srV+TÃæ`/RÙ9@z Ï—(Ûqé úCÛ¯%µ†Æù‚$4õŸ°+¬ÀèY¬1¼£dx°a•ËÀ_ÿZš“O.6)̰¶û‹ãà¨w¼´4•A•>v›*oœïàÜb`Ðë4dÑá¿÷žÇ•½<àÏÖêv^9WÐ3[çØL˜ôìò ¨‡‡>…Ô<7_\ ;Y Y6ùq=³v$0&¿SBfnÓ®ô“Ÿ¢Cøb­`u•I~ 8£<±Náø^*ZÕ7ÖVi²ó«ãŽMäñ‘U OÀ°7 VW+Ÿ÷ r ÑhNÀˆü®¦Qµ{ÝrW›#†ÿn eoF|HËŸA9h@öáãq=?›%E©¸„ªo&°i‡›ô6ùTm3¦’”A÷KneÙUóÕ?N¤ö‚«HN´Q»á–Lù˜Aÿ@·®9è&Ø“[’Õµ«&¿Èg÷æÐ¡s>UËæR]VÌ1O_…Ó¦(Á:6¾ó8æª4l­¤ÛÐÖŽ¸©:Èkß¼“˜}÷8<'†Y±†-+ÖÓ÷†—ÈËÏfàÕײíºë™rÉ©ì¾ì"’Ü‚šµóøé•8üÕÙäÙLÓk2L¤Dš‘¹IZç Ôý†{$ÕÛ×5FcΎߊ U›Öòyë€2Øs|rGî˜`ñW…» ×_~&ŸÌ¾“SÇ]KQªõ¨ =J¦=à ?Ä4%BHB:2³ó©õú(~1ÏÞ½•žz›¯œŠ)l´ïÚƒ¡ÞÂׄÛâŠ!†_ví\™[ 6è—dmö› ˜†IA< ym¾Â ¯*”{TÖTX»yM7Ø:Ûh•\ ´H„’rÁám\P˜6Õ²”@e„4 &|ìåI6ô‰ß´<Ç—íÄ"¤¹HMÒ:—Ÿ r¡ÁÉù9,ßÎQ]TÎ<.@ûŒÚfmþá²Bzóåғ윒ç±|8ЯÌ^^O{5:fº8¥·Â=gyÉN B½•]×”à š2Or2Á¥Â…êx¡…ÁUÎêâÒ/Á2(­) .í H¶ï6`RºIgá.ÐÒ\ÞÂÛÐy=‹ ˜&™}U;CZ­kTª¤ I¡]b˜á—Ö"«ª-Ì3qÞ÷ª€´#lÛijY´ËƘÐuÞ[#?Þ >À=ì’Ù^ð‡!)Á„`ƒ2«1MHÖNí£[ý+ ?RÝ>%¦±øõˆfÌšî›X½¾©tÙü\=Îîüoˆ+ T¯™ËöÍ©I„‰šÜüÂ4¶ýø-ASmÖ|ÅZ >‰ôÞ£9ñÆùLùm¿ü0îÃ8ù¿÷RõöHfnœó"k IDATNBUUŒ¤ÏiëÌ{æ!¾ð2L)ÐâR)q1- Ò1"ÉëlIt¿âi*«Îaõ;ÿf½n¢9ãéqÕK3œêF‚ŸBrn6e‹^¦tIæÈxºÐÐzZCÖ¯cÁ{1ýûOP4)Ç^;NÃAÖQWpê#*³xïî»æpÓúìÉ.vÃzñ9yÄ%Ø@QˆËnE +i€æL"1;—¨eÃW_RWhVp0QHiÛ…PÅjê«üû¸’ÛN^‡¿‹Ÿòî5?ìê£Hiª];¤~1Áâ/ ©ÙHNp4“SÂñ×?Å#ã‡áè PQí¥Mç6x½5œvéœ}ùm¬Y¾/'~Ä{~À3ÿ÷Oú÷Èy½3öUe 1ÄpÈK¢aJEMçªf²¾ú\ƒ†•¡·²\Ra„e&UãŒ0¸e€ï×BQ® EkäTÅbÆšgin{¨á9J¬g`HKeд+TÀ® Ö—[NÕ ±ÙlŒî ]Òõ†2’QwZrZ”–ççBS Þ.܉MiÉZÓH_1üR4µ[o*XlعlÁ@3àGu9sbÊæÄìç&"ìͰyRÇÖþBN:¿ ž¸®6Ë KÕÍQÅG’ätÓú’§wÚ¿ð â2³°‡¶ðþѺ€Û­ %H©ÐêøËÈ?â|jw—!…grŽx§5¶ÃÃÍÔÁѺ7Ç=[Š2t]âLÎÄîv4„G6Ôë2Îÿâ’=¶Ç•€=…þ7¾J÷ñà« b‹OÄ•œdÙü€pÐâØk8ó°qÔ•—#;Îä ñvëétË9ï^ªB8æ‰o‘B!äÔasî°‹QùèÖ{Ù¹mw³¦S¦tÐnÔø½Ä¶uÛ÷qBј—)èÚÓwPñ×A@ÐSÇÎå?lÄš±š ‡TSAL°ø CJ‹C0£toé™Eôp¢i‹±ÿk<-U€ Oßx²™÷çÎdûcçsíTƒ·?ý‡ ×a#hë-çŒWæá­õÿ1‰!†¿ä>v­ìŠÉ”yP¯KR’âøê:ƒžynæîÚ[ÕþsV…H8ö„x§ÎsrD‹Zü50›‚a ²²¼ûÞ@fý‰q¼{˜—š X½ n|Ùà¹!î/È´É_¾T zàÃc2¸%Ngg%,^g¾¥óN Œ, ûjŠ}0Æ,‡Šo^â£Þ›vÂÝoH&.°ª›À’ìL'_Y‹-hP_ _¬S)*x–Ãû[uJƒ*E6TØU Kc¢ûc_Iꚢ€’º™<¦Úb T«ÞiëUúä6: Ky5‚Ø¿YFs1â·@t¯FïG3q°Ý³s[h×ê%¶½úcþÆiÊ Zx3g¹ ¡53P¤ Ž$ÜñN|­7R6TUJø¼TÖÞ{&K·§ÓkÜ8ÒÒ©Y¹ŽÏܺí:Ý.„Ë2,!ÀGbAëF»™Í i€Tl¸³Â¶xÍ\'…@ì'¾E£U–#5 gZøwÓH3™@bAB#M ¡rE£}#€ÒØR„é(àè>A—²y °Ç§bžr,ºiìê¹N‰=1ãwb¡„OÙÊJ~”ÀZÇbäÓœ0|H,þÂa}›刔PЛ[n;“³n›ÀQÃw2ô°ŽìZø%ï}ö=‡Ÿ÷ÓãÑúvcó}÷rþ'sî #pÖ¬æÍi3Ik]HÏâô?ª91Äð·€ Ñ|(( V”*,šï/µv¼kuñ‹˘RPÜ TØRâú·TÎíacI‰Îë«LÐþ{m<ÒªöI:&ó¯pÒ-ËO®px\õ×î€+à­…s^×Ð7Ég{ÉÎ6èYgÕoW­­v)¡ÆoRV¯‘–ÜsIðéL¸w»Áô«Ý o]O'¤j@2Ò‡Hî_UÏ“ó¸¾-þ*¸õexb“ä¹+éÓ¡šà2·MIà™Sk VÂ?>€:öoC½/HS`O†óÛÀ“ß×ñX×$®ëQƒ·.z ÞÙ¤0ó;­ëX–`˜”Õ°íR ¿š:lQßa(v˜¡àÎõs¦åôÿí )ÁžœŽ#5}¿ì¢iBR‹Vû-K¨PTLå¤Gù`Æ„†Ô88rètÑc y*ÂhRM4ã~ ZÑ–æ8ãŸAÓ^÷ w^þ~­¥’ãxÍ¡jï TØôÝ—¡àn ”æ^“½µ¿1Áâ/Œ¤Œ¶ôë×öyÉ…S¯†m¹Üûܼ¿nN›‹Ónx” w\K@÷ïdâ½!îyé^xþITS'>ëX^|òNúf»þ¸ÅÃ_õáРþ(c}(ì~’øLG ‘¹vUó€ U‚ÄÀªE~– ÛOEÊ‘@}ÈúÏo„—7 ^ÀÄgH’óáî¡ðÏoaÑz?‹Ö‡ PÆÇE­ªšÕXÔ%ª. N9&½ç§ÿ}6çÚØ°#ÄÆ€Âí‡AZtˆ0tÓ Gi®”PÝ m¯ „|LH΀;ûÀ¿ÔÒc³6É’%%F’ã»°Á€tɤùAHãÃkâ’îÝ#ÜìˆÃáùƒ÷ˆÁà|•å:Kkà¬#Ú¥š\3f”Jnx¥–>Q ú 6ú`Ø1 Œk_Ì… VÞžSË—‹TÐMA fC¸LÝ‚3Š•ðÁkkl»aZén}†\}|÷ªÎ?žõðh ª» ×ÇÙ5Põø›ÄϯX,ÃTh•k’`˜\õR=?ÊGTî•öª>(òuÔMŒ(: ªÐ4#1,öeaت€-¥Kçå‡ê „ªþæ$i{@{rN»sœKõÖxë<(¶DóÛ“Þ:Uù}’ÿ(ȦÂÓ¾®ûÍ)9HHØôÝW›±Æ_sãò7!76«üÉ ¥ì,:eùëk¨õ‰‹OÂí²ïu>P_CyM=Šj'3+ý+$¸@ñÚ¡ -†~"9¦m€pq€HÿßÕ©‰;>/’Žïn6ïœü;Qº|%|_©Òº…ÆÐ–P`ó:øz§ŠïâÌNuÄÙàÇ•ðÙ: Ÿ©P˜cã¨üz¾Ù à3†u1Ù´Íd}u¸œVÂËa›×F¯Ö‚î-‚„êॠ!¡2°ƒ¤8Å Û´zL^­QáWPTèÞJeTßÞKMØüiÊr-ÕÅ1­¼ Ãw‹aòf•AA¼ú¶¶3º[ÝÞË”Ÿ&•(ôê…‰&~L]-èT¨Ò!IgþjØäs3ªG½yJ釉 aÁvC”¨p|ÉayPS o/Õ …Óúê仚lW*P²Þ_«QæW°«‚.ù*çôò§YçkÊà­Å k<.‡ c¾`t7?öpD%Ýß®%6âÜý²|œð0œ?&‘»zغ¦ïr3ºs=ñajÚ2jvNìDU PŸ– íôϲžuY)¼±Xc{­Bœ:Ú8³C½µ‡à³…ÒÊFÿÜÆœ óÁ®'PuøìXéµÓ©H³ž‰ÙØß„`Ú ÉqŒhëe÷6˜±IehI¦ÃJdX_ “W*tè Ñ=5øÇrC üã}Í -ÀÊ\‹¥ ò^,WøPøó¿ÄÆFr™Ûhœ\XsO<Öü“þâJN»è¬7¾!³C±e2ôg°LùQ©&»ï1»ÿ=(6¨Ú¸‘·Ï"ëvn{˜Œõ>yŸz¬÷ÊG㻥sˆÞ­˜`ñ'á,~Ä‹þüy ÂT†³W7СaÙ9IwŸ5>5|}DǶÏߣœ{#¿#¦íQ÷D–H9‘Hû N“{!R®E¯F#HïïëþÈ“‰Ôßôw„æè]w¦1R_¤o¢²þ6ôO„ަˆÐi£®3ºO#ýÝ*|>Οfç‰ËœœÙÉ:|8NÿPá™Ë¸´KMcÑtÛÃåGú"ÒÖÈóˆÜ‰&fÖöè;G“ë›–yц7Ma§ÑmX ‚Qmoú þHü¹ hÍÍ?‰áO Ü|Ô­O¥ö¾àÊ?—`ß š–¾ó*ÓnWƒ”h*"‚…ðÓønEL£~}ý‡¢~Wü/ ƒ¡Ôˆ!†¿9šc(š ÅýÛhò {/ÑBIô±fòìU×þÌ!š+7úwÄp_0›ÐÐôwsý!›oJ¯Ù MM}smhJwÔÿ…éઠrÖ“7[Ù1&3Öëtikç„¶õûfê›ÖÑ´­MêivoîyE—Û\[öu}ÄÁ¼™0ÀÍÖÃ/AsæOMý, ,«¸%+¦¼9´ÇYWì7úB±Y’Tb f¶Þ`Õô÷AÊ•À.·…"cÑlò‰9oÿͱ xŠC$Y–””äϘ1ã”ñãÇ?ÿk–°ìPÐC 1Äð‡Á€Âb˜rÜ3 ¶×IUá’á.®;"DžKÿßÛ3áF„)k*TDv‚u,Qnù®Õ? ÙºpŽÖê°Ã1$ B@íšETûãÉnßîgG ‹á…¢…¦B¡P(P¾~›çÍ0i4sŠŒÃèèP¿‰![L°ø“A± ¸úÙ8ùÚk¯½ØŠ¥Êucí®Äö@bˆ!†¿LèÜÞéN*§Ð˜80&Tİ7šÓXD3s#¼ ºß·eÙÇ/·i9àp+”é¯`í Ö½|>?”ãìÇÇíúý¢Åðë  íßL¥Òc£í°ãpÚ¿Š¢ÁÒ÷žÇ¶c™@EÁˆæâ7‹1Áâïˆ8`V®§T 3ÖÀzË2·Eø{8°ð¢1†bø­Ð¤ý¥¢ÿ+ôDÜp#KldO/rì÷ØfiJÃÁ"’Íùîÿ'š‹ÕTk1C©æo˜=µMùºÕdµÇø•þ^ŠQ‡iÅb* Ç1ÆžŒªPŸ¡M¢F a…(á ¤Ü3bTÃý‘óæžÑ ”ˆTd ÓX¾ˆÒ¤ìEW¸Îºöu^4†kÝŸð´QíˆôO4ÝB%*™žÕiÐý-ò¿ ûj ÑHãþú4BCäº=ÚØl!–¾u«7¨äyn%iîW@µAÅú¬ñ±ÄÒVTÑ(TDÆ`Óðȇ|æˆ ?hÀÀ€&ÇûEý¿ øéw£(†þ—ÎÉðÛ)Šÿ@H¨õ‚! ù—DŽ>Ô}#¡ÖRDÇ!(ïW 臚¨¤ºøíF'îpzê!(!Þ Îƒ]M¨ª§ \±ýDSÿŠ#q:†Ï-óUW ùé£WZ »éþCP­Š°i*J)_°ˆúÚ ñ-:’Óµ=¶p(XE³~¥ËJ¨«©Ãž˜AZ»®$¥»-s, Ô³}þ\<ååG©EÝÈh‡Ð«ÊضôꫪвÈêÜŸÔÜDô ¬£bË&´Ì¶ÈеTlÙ‚p¤‘Ý}*Ù¹l1Þ:ެVäwFS,†[µAݦ5ìÚ¸†®âÎjMF‡Ø#̽ ¼¥+)]±š€Ç#5—ì^‡áŽWšÍ¢h`ÔV°}É|<ÕØ2HíØ—ŒÜ$Lo=•kÖ [‘’“„©S»©¿šKzA&¦·†ŠÍ›HlÛ–Úµ‹¨ô¨äuéÛZœm1v-§lK=™=†œª¬ÚNiÉ |u>œ)9¤uèFB’Ý ñ¼mþ Irf*»W.¥ÞS‹=µ€¬NÝqÚ¡fý*¼5u˜A«Wâ.n­¹d†?VL}‡º²í•À·D¢?ÑæPÁâ"6 ýýà&` ûÅ_s`÷ÀbøëA@ î´Î–ä&ò×1} G‹ºíÁR#i—zˆû9mòÁ‚­‚ÙÐ*Qþº%I½n|¶æ'3edõþÀK¨ðÑwpËL•¬ÖN>ã#Ó~ Œ[¿a‡êo–À&ŸJ»<ýòüP×½ ³ÊlÜ>ÊÉ]j½Lšr8õIÁég'pe'Ïׇo4ÕXèìé,ÍØUs—}ð¨.§œ/2‹‹¯…æÄ·b }òÍ;Âãé0î_}íõ¸œàßò#_þs+ol¸-±p}ïy“¾½[¬ÚÄÌ;dzhê̆ójB&‡ßÿ)}Gô§vÅL¦Ýp)W­Å™™C`W)®‚ô¹ýu ïL¨t“¯8oBGB›௷Ü5³{ž‚‹õl\v¿=®{†£.½UÀö©ñé]ÿÁSY®ÕIÁñ7pü¿ï"9UP>s"“ïËÎ~œ‰qø=^R{_È w=BNaâÂ…¢Ý¾ºý–ÿ¸²±y9êž7éÒz'“/ˆ>â5.¼û<Ì ÈÀv¾¹¶k²ïæêWoÅ·øK>8{ ýûR¹p¾ì!œþÐ=,3˜@÷~×ͧ*ЊÞ_†}×LýçÙ¬[¹³¡®Ôž'1à®èÕ=“¥O_ˬsÉ(ÈdÛòUjhsÂãœôÀù̺ø$ÖmÞÀG£»rü‡[èÚ9çk- Ê×®eñ[O| T²ç¸‹Îº.¢Møb®>OL–îçüœß‹bøŸ‚¥[àŒ7¤4n½ˆ¨ïèY3ò[‰º†&÷4Es×EÊØ×µMëØüïkß]¥Õ’5erÿ+Itù¢ñØ®ÝpÂó‚‰ë¾öG³ ¥å°ªRìyß/ ˜£b…ÐØ»_šëßÍaÓÊl¬6ØZ--yBÝOYs}])?ê5b60öM“çÖ¸Âén¯‚ »u<~µ±ì¦÷6SO¼KÁ©E B‘¶èÞ¦ôì8<˜±.öqü¯¦Ñv"‹`øað–úk*·/~{Bƒ Î/…Ðlv®ÃHÄè÷—3îƒ/(ìžÂª—ÿÍŠo–£Ú‚,¸íBV,« ÷orá—k8ý_÷¢ošÇwÝB×dÕ»²hêL:Œy˜±ÓWsγ¯/v1ë¾§ñ”—ñÝ=×°qýVÞù>ã?YÈÙþ箾»ïZv–I4» M©]=ŸÎ—?Ã…“ g×ì\ô ›Êâ8þ¥ï¹àÕ÷ÈN·³rê‡x¼Z3‹iwÿApâ ó¹ø³… :m[¦ÞÍ7O}Œ"¬øøaʼٌ|o㧬àÄ\JåÂYüõW{õ™Ð½,™pËÜH»kÞà¢é«9ãþ;Q¶}ÇüWÅ´¡ª ÈÆà•+?‡ÂÒ`¶¿€–çÜΨ‡Ÿ&+Ý e çãî7žSžy›Vé5|}Íy¬Ûh0èž)\üåNºî<‹&1÷‰{© Ãa#P[I宜úÚbƾñ)­2âÙ8ëQJwÛ8ò…I´íÙ{N'N{{1…E™¿Ü*l¢µðµÇ¨//+Å2c{ÑÚŠhÿŠèã!¦±ø{¢xh.çDPòû’C ÿ#› Uú$Õ‰ÔAH+£rœj=°+i`Ó¬ümUà7)‰’ìDr+xCàp€=e›Öq» ´0“]S ;ë!Î yÉa{ܰ­®_— *« Æ´J‘vUBih´Ë›F Æc•)A^²$ÞEÞ”ª€M•ͯ$ვ°£¤*h“!‰wX÷×ú¡ÚoRã710ªB°¶y¬ Ó™‰’Œ÷oÃÌhy9l©Ø‚Â4Ó2õV?Ø”=ë÷ù $Ò<*œ‹¢d=¬¬R±i m³¡KfÈl¯CÒ$ñ᜻ªÀg@|¤%6FÿtjòKæoŠ€Jaޤ]ºiÙf¡Ô¦ -`íØàQÉJ•ôÌ5ÆkaîfA­.Ht)çèdÅ[çëëÁ’¦¤ÚkRï·$ÕG`WMÊJá‡í N—JŸ–!’콯hBR|r£¦ÔYçU¨¯…ÍU–YU~¤%°oíGx¼UTÃn/h6…‚»ÝêWÓŸn'xë¡´ìÈO¶n׃4!ÎÎì‰ ‚7|\üõŒÈί`o­Eô®q+^ 0³äã—Ç ;E´=â(ˤè—À@Z†?ôm S¢#ÇÜ|FecÉ2:¶ZDzÅKisæޏä4²ŠnÆ»îK¦½3ŸÍó±}ÆkØ3Oáˆë¯'Å ´iLj—òã7qÔ¬œÉ†…% }˜œŽ-É#/cèÚ|ðÌT6-XKvo ݯ“3ø_:·ÅcÏfÑuwÐ÷Ægé>¬;ˆÞ¶ü «*’µÓÞ¤¢*™£_xŽ®ÃZa„`ðÍO±ñ‡~l[9‘úªc ¼=H]œi-é4þa™}¡u§=´B½l ‹g. í°›9î’sqÚ £Í- Z·R³3îà&ïDÔ1È?÷N¼ýF+ý˶0t“ä¡—sÚ£ˆ‡ò/þÅò Ûè|õû >ûxdÒ¯¼šå“™=û[Ê·z‘ÒDuå0ôÉçè80Awz;Ÿ-¿FeYtÂ’„R-HïÜ™8Ç/÷±Ðl°ùû¹¬œüÀl¬\0‘HdÑ‚EDcó±ˆá7Á§Xáa»49¾ Øõ»é’ðW³:! *,ü†¼.ðKÉýoÕ0wc*oö­dàÛ6Žé¬0ù»†ÝÅw)_hpѧ°ÊÞmWTNéëâ©‘^v¯5ðªÊÉG'ðòð°‰/O›çÚxý:ÇdÖñƸékØé›&ÜÉÁ]§+ Èò²x\ð‰Æ‰} &Α ãùþÌZÞ›ÿž/@±ãîÅ.ž:úåy‘¸ <ð­À¯„.IK±ó§º¸ª[ÍþÛ¶÷îs¸ñ+O TCÒ:ßÉã ì›C Íâ›y·Ž%ëã˜zž—¯æÃE v6)ªÊÙGºy`¸‡dH¿Uæs»Uƒü6q¼v ‰ó6Ö¯AxüøÏb!Cøðè*+÷~ž™¿nz^Y'ð­u3Á¥0¢W/᥶z>¥`8î½ KÚU®žÓ= Ç‘ÌÛ§U6Lt›69á!…Õµ&&n§Â£Ü\;°žm«à„×Û]C³%Ÿ¯4˜&N» _ßDÞ=¹–T—dåR¸òSÁw»Á¯›Ø4I¢Kå§Äqmq-ÇÝK½VÃ&éaPe_ŸXnL^úÊËme •>‡MÒ:ÓÍ{tÎðïi꤀gœü˜`䘮ìâaÎl¸tš`s8!wBŠ»OtqqÏš½þ:¸ö]ÁÛË!¤ I óÜ5ÒÆ©Eu”,„³ÞU8¶Â”ùëë%.‡Âˆñ¼tއÅ3àŒ9 7ŠçÚ>ž†„…㟂™ÒͬKý´J0þ:&…F´w´ÓlDkÀ ²"ä÷­šýèÿgwùWJb³~¬,$¡ëp2²SЃ ¸Zö¥… º)ñïÞºdó×OðÖº÷±™&BT®žx6,¡|…í°ÁÄÇ5æÃÈ?í1 FîO¤>¤Òúˆ£qJêV}£>óf]¦H$1w NÍ6¥j -; #B YŽŠ_Õ¨ÓÃ÷Äêç30M‰Ð+عµá¨Åg8È|:Îïîç‹s»0''Ÿ–‡ŸAûá§ÓºsûÆ à øwnÄ燂í°ÛÁ‚!ìô½î9L!0KçtÔ¥Ô.GZy-õ0×-!¥Ë‘¸ì–“wý¶ (V~z5óŸA˜&B‘ì.Y ¼5¤i—–KV»LLàW\ š!1}!¤a ¥´œËu0í j øªë˜ýØMê=+%Xc¯©Pr›ÓXRÄL¡þ¾¨^`ïõVÆÓ&† eÇcˆáÏ Ú¶†ÛIlÀ ý㸥P*ËB<÷e€Ô–qÜ6B_gpþ[°Ysðø9&Ž“œÓAòÖœ:^[—H›<(T &}¤<œ-Yzá­yP•î`@V?‚ó¦A»b7o³ñŸÃà›%~ÎzGP¨:lªÔ¹÷sIQ'ÿh°d)Üô-ŒêâƒKà‰c%+–y¹ô#Ó3gÂ-3 o'7/¼z†$/äúwCl `º·Áò¥pÍç0¤o<ï]¢òÜÉ’Ú-~Ž}ÓIB>Ü4Ôš.Žëãä†ÃBTlƒKÞŸÃÁ³çÙyw¬dd ƒç§ÖóÕÖ$Ð`Ê×pù—Þ!ŽwÇÛxp¨dç/W¢RcZ|†¢ ÐáÑ·àß@»žnžZ}àJÀ{ßÂ+ Î¥ñ\ÔEPë3yï[/”¤bS ÜkR镨¢f;Ó U^“Ú& ¯Kè,•’–™ vSR]gpÓ;A¾ÞÃ5Ie¥Î?éd¤¨ÄiPë3ùjv-ÿžŸ~¸ô]˜Y*Ñœ cØè™*©¨5xèË «ü*CŠ .¼­—h³qD °G…]¸Q'=ÉÎðQ0Ð (­1yô+“Á½Ü¼5Fa€SòþwµLžR®“ IDATÙ˜@¯žP_g2a™ vô­Ü“·@—V‚¼¿Pm¯Þ4*T0êã{/ÊV,ªYðâý¿x%•dÐDšš¡H”%K3išÐ¢ðÚ6‚ü~G“×û(z]t/Ãny‚íÒ A4•àº?€¡›H´Ä¸FnA3d ‘˜¢í*2'±q÷?<˜M£ù‡/MÉäMËþGQÐï( ŸÍ7?Á°Ë®ÂnWéxÖ]œõÊç ûç´j•˦÷äÓñ™8á]Ì&¦“fÐÒHSìÁÕ˜?z(„ Ó#/J³cS„B{ !ÑÇ„”Z¥Í cÈï7œ¼>GÓïŠÇvÓýdä$ EÑX… ‡Œ‡J…',ø¹ÏÞÍö¿­>DzH Ð(XD„‹èˆPÑšŠ˜óv ‡¯ã€îáßËé­s£Ò¢-v£_GÙä;†þ|’tƒû¾ ìê`X§J6/CB‰|u–‡TøË [7tpÖÝí&Ÿ/‡:¿‚– çõ„+çú™µ#‘Ó‹<¬YsâãMñ!¸|^€¯·$ÑQ«FNâæ½3êqÚàƒ©Ö Ñ­½áݼ ï NbðAÐ §÷vòÀY!Ú¤ ÃîRXv®ƒ~Å6N=ÞÎèb(^[ †.Á÷Ï…ä47ŸeÐ>-½ac…`–ß Ôg™ŠùêƒÜóܹPÐoX"ÓN©!%l¢³?z ÂÚÝÖÏüD…þm†ô•äÆ+¬©¸‘ —ÑÔ&;ü[iRGȦòøén.ëîá«ïá¼÷ ÂbÊFír°4(ºä¸Ã™xª‡êpò ‚ÅU’·4¹¥t*TIö©Œ®rjï| ó'YcLq:øÏù^f<¤0o‡ÉIG»yü¨*ð4N »%ðõùõ$ú$gO€wÀîFó²…‚% ùê`§ òTN讣9¡8îþ^à 4#¦™ ÜpÞ‘qL8ÝG¼Û„öðÕ2Xä³|6Ö˜uD"/ž] ; ßó°²Rå¬^pq!<ºÔÇ “îé~¾Yå¦ÂI]Ëäïï!XDí_É~.X¹¢"ßÛ€?¼úÈ)Å=”Î'úÙ&QŠæ nÅ<*v{Hj“ˆPÀ³a6Û (Jr¢Æ¥ pcÀu£Pí°åÓ·Y¿ºWV[Ò 5ªJãA‚BƒEwÇן$sòÝGâVCT.˜…~B[”PØô拘Jî´,»¬±¢$  ¨85'H•–ï¤Û€$BAP}¿ñ4ACG ìfÑ aô¸„W %8ö6¼›0åäþlš;à£qE˜vì™-ì^SŠŽeªªáaÚù½Ù”t£n½EÓ rRµ2–¶n`Ç:9ßßR‚–†D’Ôûd_~$†×bð×M|Ž-;ìÎHȽ÷‡Ek°HüYÐì°ê³É,yóI ̶ÐhågO­EÓ€¿Ù[,þÞ¨ž^ÿ®æÒèrÖ la9u×c¹H6•x›~bˆáÏáП¡ véA)Ó^!ÕÁ™ÏŒñE‰ÎuÏh¬(ƒ•&[t‰CHPaDgH˜c2cœ^/-3ÎÆ¨bÊÝPãOÎõÚ‰“&Š*YºECgËnh'À†à¨vœj=™n ®~¶Ž7ól *¶qVÏcó| aÄáPØÚÏ´Y ó·ØX_%Y[¦[vîj»-ó¡[òÀ›Õ|>ËNßÖ*£û|0¨ØîXÑžÿ=#Äô•!®šmcÉ.Ø^a]¤*‚`-¬«‡¡G*´O¬·–:!©1½$I‹ÁßXRÇm ÿlbãÀy#¾ ¹ÖÏÍ[œñ” }–‚T…QC4.êVÅöõÍOHûÒ†ääÚ9·s=vŽë }gÁgÛ`]¥‚‘¹Yåê H(‚sÚKÝ:¾»Ž×ygÁ SmÜ[«²¡Üš.|ð…µÓ  íå -8·» Óm‚ s-Áâ@ÏOš˜ ­S&M¯£c‰Þ­UNïÎó“`îígaÀø“ çz/ÏLVY°UcsµdÅNƒ¶V¦7‚SúɆû³“! Øå`‡úÀãkƒ¼¼:'ãýL_ñ©F´®?pd«¿¢7â"‹¦‚E„és`™Dýh„‚-fÜ}E¿¤œ úõ#äÿ5*v¨ú‘Yw܌뮛qú×2ë¾›1ì…´ë?” ­Ú°òíkÉíF§þ©ž7‰Iÿw)uùGÓñܱä Éògßâ›gŽfàYGS=ï¾}ó+ì}n#§÷PÒÚe³éý›YØ£ˆÎ‡w¢bú‡Ìþ|:ñ‡Óª.fÕŽÓJáøæå‰|yÏ?‰»÷NÒ‚¬xívf¼ðmǾN÷á6NzˆÕOjêëä$ã-ßF}´”$Å›`…¦uäö¡kßlN~„¹}{Ð÷¨.lùè–”¬%ÿÔ±$eàJKbçìY<ëH ³ƒÌüÏuì6!¾éÛÕË®Ë AÚacÉËx…%Ï\DN뉴í˜GÙ¯ðÉ· zžK¯KǸ3„@HÔTQV²ŠøníQ†&CsÀÎ’¾ºû B~ß|`{ ÑZ²¦ŽÛÑþ‡1Áâï‹ÈþËü©kø»k²‹\3¸K°xø°ŽÆ±Ó4 F4bF L)˜Ùª-0òÁ×u‚nù ­½Ü’ ¯Ä–YO›BÓÖšlÛó6Aqk'½sj©GÔB’ÒÝ&†aE"r'ÛÅi~ŒÝaÛÈM½ CzÁ×xzž`ùNƒ SC<6UÐwpÓÏð0cŽdÜÇ ¸z¶ä§ 4¡²¤ü š¨C«ðéEðà•u» ^˜ä¹Y0¸«›ÇùQ£ üغŽ{Áò3éÝJ•(Èt+l,±#¬|wÙàÇg80+Â6…Á­TæoñÀ$‡çA>™ ‡ÁnCáËÕ‚o·¬Þbõøry-g$syÛêÈ#D‰˜{H¨ÛÇì”èØ³AGk³…»'Òt Ø5’4°‘mÂö¡†Ä³[gôó‚å0ÔNã0·`Æ:ý ·Išh¨k–${Á”[̺ÒäѯU¾ß&™>×Ï;s!¥¥‹‰cUŽÎ¨kœ¥ÃúèÇ&Á _Avš s¦JQŽIO¢GÕ«‰³Á?bb ûåô+„.X¸$ÄŽ˜º wçòýÝ ØS¸ˆ8ËF›Cù±6çìá|å­Üúùã‹Nzì}2‹‹1W™¡×cÏ, ´å}^=ú¿ÖA-‡ÞW?BQçl `𿟢úÊ ˜qí0¾ á*èÉaÿž@F†‹„óþÍŽ•Xúôù,}Ú*ÂÝf(ᄌܖ »ùA>¹åf\{$s[$Ø^C\Ë. ½å)Rã!Ta`èÁ=#7µ%2f(„¡‡0‚ÚoG]>“éϽÈ;'¾f©Ü‚!r†]ǰËÇàH€®W?ÌŽoàÝS»‘’OMÙlÉ=qáH*{ú¤8RøÏ·¨.ÅüÛeþíÖá´>g2è²+p&ÆÓö¸1l~äq¦_Ðé@B›nddQ#uLa½C: ´F_JëáÙ#;&héÝzÏ#Lûç5L×§A•Ðq8ƒnˆ7˜ÁF(¸g@ÃÀ ŠÄT´îÚ—Ÿ¾þ/ŸžÝ™ã>ÚB·N¹åÀ­Ú |ý¾¸ã"jwnÝÌ|a2"ßÑÂE´`ñ›oþÆ‹¿š¬Äòµx Ë *2ðòm‚s¯JÒÇÄÜQ©Žžï×` Ò¨ê¶'mNŽ 1ü¹Í„690i)|]-¸ñ‚$níQEb<”-‡+Â÷IÀ õƒQÓƒ<=ÇFI¥Î£44a%„óн(ŽIÕXK€;¶ÂÜR=³ýì(³êЦbÑr(uÆñÚ%^¼µ°¹ zKòÊ~ ¼2W¢&ºøìJ“>„ž æ—x ¬Þký^¿$@°ÖfÀ# ½Ì+§·bÅ›×ËFfÚ(©Ryæ"'t®ÇÁG%–ÁŽÃ¨°£Z±øtØàÙ7àÓ`ÏçE­:Ä3ý"w<-ªå± ÜܧÖZm4ÚãÈ=éÕ½ðáAFºÊu… /¤Ì[ ·N”%s–KƵµü|AÉòrÝ`çzøq&'«6,©p1¸µ—Ý›aã.ëxÛÔ0ýð™³ÑI¿|À× L$î\]`m¹ÅÆ»8··‡yßÂëÀ%öÖï£0YÎy÷Œæ´OÇWârà”ÖðܾlVí¦ÿgþH‹›j,T£BÙØS°P±¬&•¯]>òÓkF¶:éá·ÉéÖý€š S‡ö—~@Þ¥)8…—Ë JâZ÷¦e÷"Ý" ¥ÇIŒ~ç;¶,_Š·Æ#9“ŒÎ}IÏKC÷‚–ÖžcýŒâçâ«ó¢Å¥“Þ©?©Ùñèõ>p £_ïÃöE ñzêP3Éë}é-RÑ R;sò3_¢¦¢¬^Èì?Ž1"©(ß ©k:èyÏ™vãÀÐí_ùC. lÝ:Lé .§-9=úç€@Úœx=g·ì˶5+ñû ì‰Ydu:‚¬ÂÔ½ÝM\†râ+óÙ¶¸¿?ˆ=±9ÝúŸ¬á@÷qÓûdªÊ*öx2»ôCÓ+¨ ¸Pƒ×n£'ÎÇÙ²3†6{Jí̉ç£æ¶Cuåy!g¾Û—­+–á÷¸R³Éê6”ÌxüµPxõÓdzC$Ú-里£/çÜΧ_˜¿ŠÎ¿›szŽ$dªd´Ì8¨MÍ »W­fò gS¶bÑv¬ÍáªðØòb­&¡"ÀÞÚŠh­ELcÃ!C´¶KH¸KcaÇ·¶³)½ïH…s’tÐÛ¥s{¹VðbþlúbÙˆFL£ÂûWÀo¬j‹!†ß & M‰§Îú±—r:¼ƒ…€¢d“D'Ô”Â?¿”’²Pƒç$ý»BÎô ÌÒâÙÆ !ÈʆîY0k—Ï×Åql/5epÅ«‚)Õðu7ÉJýžõ ˜6 nßbðÕÕq ké¥8r4i–ýs~Z¡@Éðßù ª&õº¶ýl‡)ðùb¸vfˆ‰×%0º¨–b7%[Ö¢vªw× 0wñóÒN'Tl…»>·®¯ J”$8<Þ_äãõ¾‰ŒíäaÃJøÏŸ.›U¦Ë¥âtÁu#àí ’'&8¡ÐE—x¯Ïì9ß?@†ÓÜcFрϾ‘¼^¢MžûOvQ”á£@…R@‚T7´“°Ô0ùðË:rêlL™kPêo~Ö¸ìv±1{‘βz 6•~[Ã-’ÿ¼ïÇ[ã zkˆO7Z'† âkUG•”û f}|a=¢€i™Õ!Í8,÷óqŽ‹“r}¿* †ª 6­ÑïëÜxf"QCA+è²ÒšèmêÞ¥ë†eFât òSLÐá«oaÆFHÌ7 û6kŠÑýáþ—CÜû5dµqÐ;×÷wýÆJ£QÀˆ˜WŸV¬_yʤëÏn9üŽgh5èpLÃ2sk¶" ®‚®Ä…µO‰-Û7P õÆÅØ =¯í :4jL0Â%0@q§ÑæÈ“Ë6­ûÀаߢò; Ãæ‹ÒlŒ …ÝMf§îVHæp¥öälZôÊÆ 3èHp·ïL40íR‡ô®ƒÉè:غI„#$…Ë0 Hî6˜”nƒ-²Ãsо¢g™!°¥ÒöèÂÆvaÍ)`(v2{AÄ¢Ñê×d±ÞÅBv¯¾{ö¹ÝEz¯¾ ¡—#0BàjÓ•m»6ö©ÑØ'®Víp‹Æº¥ öŒäd·°úÄáJ!àP‹výF­Š•oë‚ù|y×åÑBEš ÔwSÿŠˆöì77Y 4ÙIÁ^àáëîÓàÒs4ç}:96†`ª²uNp+êåb|IÐ܉ .O§aª'ØÈÿ1Äð¿ 2Ó @ƒ{'V³´4ÿv« „$åý;¸Ú}mrÉ„ZþíV©óÛ NÕàÑ7<ô‹çŒŽuäåÀq-ᥠpzE‰^ÐAÄÁ=§Â©¯„8þaΉ*eu&»¥`ìnË­fÞJ¨aÏDvÇž pÌÃ*ƒómx*tUÃÈÁƒ |צÍöÑñV·* èäø*œòŠ›9çéPhþe<¥¼¶ÐäÌÇëyº­ GgÅ.IǾ‰ôK¯Ãá.xìýZ¶•¹¹ºS=®ï F>ê%3NÅï—$ºйê  W$ñÔi5lx>ĸ§tîHV(¯6ÑmܼF¦ jƒà Zæc9EðH8sNK?IbÖ>îš,Yï7ÖÍI†Ë»g¨ˆx;f~¶9c '““m\2Ì$)Æ×̆¥!®ÿœn•,;”¡Î´ý`xÆJ‚ ;|\·&\”Œì—Àéíª©hÈð£gù×'½X\äâÖ>Zù¡OÌ)Ó¹îU‹«ÈNP˜xw…øf½‹^ýá¸4“;`Ñr/Wˆž ¾5ýQc-ñk 5?}Ji9Çy ½{ÃàoN¬aÊYèÌ+5h•ëàò®Þ=ýVLHN‡Aí¯”Ô‘ÿ 6¬@q ’µ›ëõq2·eUSGØÄ/r«im±"¡y èØŽI„Ï<‚Û8(ˆ÷Z¬ÍßÑ‚…Ξëm€Æô‡‘ˆ‘t†»€+6¬þá¥ÇutÍD¯³¯ÀîvìÓ©[Qïò~ö ¤a9ÿïó¼¹³;3–6{óÞÌ~såI}ï"b÷ç†àÝo;e£°´GQç¦ç÷q4ßž=èhz¬iŸ4W_3ÐìðXöÖ‹ÌyüVµ5«€/°„ ?ùD ÑNÛ¿y˜ÙbaBÿúØW¸ØHžÚÈñÃ;Zjj÷ÓàŒ¤ýÌ@vépO¹Æ„=hÀ{ÀýÀv…Šh{¾hg!ˆ 1ZDÆ´ Kçâ7þ¿«Sw|0^$ßÝܿݷ„ï—Àw•yùNNÌ­câOúµ“tÌ Z£X’µðÁJµº 7UcLo/‹×Â’]6FôPèšiqUkÖÁ7;m èÓB« ¥›áÍe*Û¼* q‚þí'ùA@E)|¾N£_WAaBÈzkX½&®Q)õ©ØTAq®Æ˜>õ$ØÁôÃ[?À²Ý6l6Aß"•a-|¼·@¡Òåâ¢^õ,Y eA7£zÔ³—u” ›·Â‹5vxþŸ½óŽ¢Zð3eKzBzèEŠtP)Š‚¢ vA®Ý+~v¯Ûµ ö½z½^E±aÁŽ»Ø°‚€ "ÒÒëfwvÎ÷ÇÙÃN† E‘dÃ<¿ßd7;íÌ™ö¾çm†O£kÕ’`Ã7?üßLÚµ48®w-/†÷Wš„ÐéØBcbßZ¾_ _=P0¨y-×Â3‹ 6„ ÒS é%R‚|´ŠÒÓ8¶K•|‰‡`Ö[ 2qp5/σë> òæ%‚ni 4"V®9?l¨’‘й™0joÔ2¼e›/.ÐX]n¢›:û÷°…—›tè`2ª]ˆŸ×À'kLr²4Zù"Ì[eRck´hmrZßRü°öØÿXñ1ót«´–_kL²Ó &ËÔªü¶ž]bRÒÈËpL¯J–¬!“ah‹êRxu‘ƆˆI^sºUóѰ¶ÂÇÐîÐ#_^+ß­„›L:ÀÈv.÷¢˜;Øk 4ºöÐé¥b3ÌúFgI©-tZ5Ó™8Т{^dë(8ªËaæ—:«Ë RSuFõÖèñäB“´ÖAŽhQÉ‡Ë †÷·i›"w^[!ÛÞ¾«Ÿ!Ík·Xè¦Î„–úøàê#ó*·Þ_"t¸äyÓ?Š|ÜŽ¹¯@êK5HaI I’'Ç”S™PÖ õ|J!þ|JwM©±ÏAÀ°vûÜçÌ+é8ütŸNEtÛ£ÛIŽº!­¶¿Îÿ/¼™5_¼FiÏÊ‘÷‡ºW*cß«b“[Áp×°ø+›ïÑDqÆR¸ 54ÎÎ=.ÝL»=Ï¢“ JÜ­Ï.3¸¶H°"b/BÆ^|[BÔ8+=:5eï±è±«ØµŠHÏg§CƒøÕ¬0˘ÄÇ%•jm;–SÖîǹ3‚ò9QmScšîõ”ã¡r>TËÇÿQÇöT¾Uû=VñšzF@·—³ý±¢g[þW¹-G{`Kì:ûÄÇdmǧªîsl‹Øo*}D-Ü;ú5Ïί$K'ñ“ÃÙÄö§ö¡¶©¬¯Î>шŸWÕßÂ5);¬ k—*ÅÂäÝ«Sݱ"n«užsµm›øùpïO]±ê©¥n<‰Ú–ϱýD×­ê/µŽêkµ¬éê 7ê­à¼fÔq;¯°£ÿTÛcÇ"m„1·ƒ58›'•n•Æ·^š¶b¡Q×:áG>ŸœÏ¨4âJ…úž›ÚÃÌ@°}‡acô^GžB«>CÈlÕ_0æšó—‡âzìb’š®ƒU 6°~ñ×,~y&«?›+¬PÍj¤R±Šø}¡”u¿T·\¨X g¼Ýb±ð\¡š&J™€­nêµ°puKCvC3É9–¬üº#—\l™‰YQF¤Âe›Í>ÏTX÷#Mºêuçl“ØíÑøq ݉\:œBžZ>‘ÈcQ¿"ãüݽn}®NÓ¹ n[…c¾s{ÎïÛ3½;|çöÜómÏ‘ÁªÎcq“»ýê·Z¶Ø"ÕÏ}G²LêŽÝmrï?Ñ~ÜÛª¯¿¿ETEä«c€‰¶¥'øMµÑ}]%Röv$í®s;îölo}çø¥³ß¢õ|WËÕÆ¿O f¬ÐøÑ4yiDì=âáTc•­*p;Ý‘5Ç'Ô­µ(´jCW|0§ÇŠætÈnßÅ߬C7òºö&-·ÁŒ ß,ÛìÑhˆZB%ÔmdóŠe”¬ù™â_~Œ ‰eÀ/Ä‹ßÕW**‰»@)+…Ê•Èê/—¹<Å¢éá´P8-J©Ð£"§—œbæLonÑ+È»ä´öÁÓ­,FÌ‹ÅE¿GíAȵ_ßTãmê¡ésõ” ÆFCß•ðÁEc …·é?¾[°!3 &1Ù1iŸnø>j h‘Zšœ´o G·/oøsÕ8pZçÝö3·BáVÅœÙ{l`)RÀÌ-]³¢]éš-ùøÍLTð·¦9cì›’Z§ú°)““øD–WÃÈø‰5Àf¤Â!Oá´X¨O•fVm»c+v›mËS,šî4²N×'%Ü  pU¦®½¾™Î¹9ÁúÜ v”غg5“Ö‹)…ưת¢O!SØ>‚ôTF~§Ã€cí­¾{xxx4Ç—(4k÷ifÉ×wci[C¢Áä 0™ˆÈžX·b[8*j€Í©P8­Ê1Îr|bßk Èwz Ò+À‡z‚mBr äʉ²©Ýen™Çy¨ÌaÕÔ­¢¥P(ëD‰­îLPžbá±Ó$r{R.OÊ£:ˆ,v7epÀh{O¾Íð´è®½ÔôÂË­£Ü]læÜ^½®ØÀ©ÀbâÞÂÎÌQÎÀnÏzááñgPƒæ.ÚÛø#8E¢Æ*J$r_ÚU4T¿×Çδg2ÛìÁ8-JiH¤8—s „Ê}*ŒT0T0¸Ó¥J (ªm‘à{²A„V ÃìhæãÆN¢ÁT§ä<ïN¥B½sZ,j“²T8­N…b·=Y<Å"¹©ÏJát}²‘U³§¤jÚQeÆUy*¸rW#d©+ò,F¥Á”BcÔûÕÑÞHëÅãH ÛOÝq-w@wcyµzx$6W@J ¤üÑ'» ›* 5é»Óm;&Vý¶*„N—|Ÿ[ŒPC&* ¼‰QQ˜ø+ïê¾PV†7_M§ð¸½å”pi9&¥PPŠ­SÕº]ª’Y¡P˜À^È"¾?O­ÐTp ûÎN¥ÂYûD)Në„srº@©$»%XÛ§X$/‰” e©PçÕŒ¦ôöë{Ý–+—iíÝUÀÀ˜Ó:ÊÔB³ÅåÑi¶èÜ BR–[ËKIëá±ÓP¼ŽzÐà´¿¥qF÷?àç®C¨Æ?d0vx×,Ý}#Ñžþ.} 6`òýÍ}2ꦖ UòÍí[šýá»1cÁ5kvÉäñqeîBÜÛFƒp5,+„¶y›ÊŸ{zj­€ã044•[¨ðb&þ<*g›NeÀ9b­¦í)꽟È*™•‹(Ðhô¶#%“ù˜œ¸­X¶kR®LnÅB¹C9 w…me©pº{Š…Ç6q+êS^([Dࢀ¦xV–é»!7B³Ý­ï HÓáöãÓ5.ÞdLø&Ý©\¼„¼œDÛñé)ÉÜ®#n‘A¸ÖÁ±įxµ^}Α|Ç]"4ðù4Lî+Æ8×I´MGº‡¨ë7EÙTáÚ϶,0Û¹ IDATÜ”Ô~œwq}èÔ=6µ^-<ùTfù™yœI—Ôêºí4`É ý„Æ}ç¥qrçʸCI¢}o¯ïõ«û÷m‡?²ßíÌÿõ7Áªìí<ðœýçHG»vŒyÜàš“3¸`@i<»”Ûá&ѹqoS“ƾ^%¯¢žãöø#¨÷›s0MᎯp+~âJ…šœ.ÏnÅ"Ùp 8y¹È´øK`çtîFŽ;¾ÂiµP ‚R,ÔTëúT“»Þn©°]žb‘<8 gæ'w€¶\ßÓot¹3O06=6ìØP/ÃÓïD¹¹ÈlÿïÒè½ÄþÄ­~âAJÎã¬/ÈÛãQµ " € EÖ¡]`‚] «KAèÐ2 Ò‚l'¬Z°tš°¾*£ms)A¨­„ßÊÀ„vÙ±)$ ¿o†²FJ*d Œ˜Ãcns˜{……©ËÂdÕaúAº"¨Ð*ÒüÔ©OPS›ª!-Òu0 éÎlyÊTWÀº ©¸´ÎøUm!÷“š•å°¡Úå ç'Ò ‹àרv:ç Rr;ªRnûì '(ÇŸ }lI ”…l*Bú–×p%]¿JJ Ä‚9 ›®‚uåPÕhž)ÈÏí±#ŠBŠêäѱ :*χ³¥—@aÒR4Úd‰øI@MR‚P]ë« M޼*Ê`M)DÑiŸk“N<¢L—çv]9ÔØ-³¹éñ~2 ðëõ<æbý·¹~¯ÃÔèš/Äú¯¢ŠklJj5„{ˆê@Ö•BiD#3EPM¼>EìoØ ë«5R‚Ð9Wà‹¿ÏÓ½…êXàTÏ=êâŒ'Lô»Óbጭp*&u-‰â,’ htƒyÌ€O©›–·)àV.¢ŽO§;”óZpºH9 ·¥¢Á"¶<Å"¹¨O¡P±Ís 8ë´L385Ï¢µ³(TC" Û„;[XªWj—…í~È´´oË%¶Ä^8• çÍÑŽÄÃCâ‡ÙsàÎe>ï῟j´”É¢“ÊX².~æm[Óh•krÕ„TÎîY†…ÿ½ÿú=•a™µÌ^C£GëÎ\ô·à·jAÀ¯sìÐþwt)l\—½/­Ôð™5QèÓ.…‡NŠÐ¿e„âupؽ:“ÏHçÌåœ|›†ÑÅ…sVÚ`häyì ám«øò38ûMee ùuFu7(³"Ž’Üo —¼¥ñs…@7 GA€i“|Œo_ɺåpă:#‡jÌû:Êb€OÎלSS×rûÞ„©ó4ª Í´kàÖü™WÁ »5oQA«ËSøäÚ(=Ócé\ðþ\8fŽ.¿øÁ V›Î©Ù•œ<Û`ܾ6Ï"(n™Ê’óB,[`sÆK:ëkÁ'ša0id w«dÅBÿ¢ÎäÃÓ¹ad¹|-›0íøïÚ¯œ£3¤y ÷¼&ÛZø}ƒú¥ñøÑÕtʶ)[GM×èµï‡ù2`î&ÚÒ*þþl¨Õˆ"ÈÍp÷QANè]6¼úœ3G£ÐÒð á39é n]Aú¶®3 ìX¡À©ó5,M*G½»§ò¿£Úï50K#"Óf•òé/9¼sR «W‰³t¾+:D£:£ú¥pÏ1aºeF5pýÍ‡Š°Àgj èàÉÓl:k8¨†ËŸÓyl‰Æ™‡¹etšçõgp+ê§ÅÂ)\º e©P)ä·Àl„Ñ@Žã·À“@É}lN[+‰÷ÊC„ºÊ†S¡p+ FSÐn›:‰” ƒøè…:‡Oµ5 o¡nÙˆ” Eìö9<Ãæý¶6geúºk0¸ h[Ê=*“ÈÌëáÑðľ…ë"LûàÊ!~ýŽ~¾ø¸õ„Í«,ÎYÅœß2d,CVþ\Íû¿Ü{’s; ­®æÌç]{¥ò؉:£[ žø°šW~Íà†—aÖO:žÂìÉpËA6 WVsÝü”-9Ö6—ÛTÄFûEàù/Â,®Iá‘Óü\ÙVn¨áÞù˜ðÛOpÂ,Xbù¹éøÓGGYº0LqYàÌó>„£gƒÑ"ÈC§¸ï0AѪZ&<aqYӆ ›{æFÑš˜:J§}JÝØtxl.\ø.ìÝ#…ç&›òßLåÄžµØaXSåÖ·»¹q$X›lÎzª|~8ÅÏìÓǵ‰2ãÍÞY•ÉÞ]Á¬µyv‰MY0¡v#Ì^™ù:}[ÔðÄ[pé»0¬W*ÏüÝÇ5ûÙ|þU%Ç=’Fµ š å‚ûß S–à†C ÒJª8ñyhÛ5•ç&ë<9AЪ²–Ó±Î2)Zÿ÷4hAfœæcö©‚±ùÿ}­†O7dÈ'z}èðÕ÷pÅ<?"•΂ÿŒ¬ü¡š3žÓiÕ ®&0Ð8b¿T¦ ®¸æaø|½ÁµG§0ûL¸rˆÍ_UóÈ‚4ðíoÀMó#ú¦0{²É5#_þTË™¯¦޽Uü†a¸øi¸k¾Í¸}‚\=²­±fíJ¹=9³?©L@Î@]g14Ue¹™Ê½"öéœÊ’t*õÑA®>k´Ar7twÕä>gê<ªs›¨š¶»ž*Ñ™¨^EƒIžÅ¢qSŸÛ“޾R«Ÿ œdš‘qG~”nø²ÚZùàÁ–F¦è‰3VZ¢?RÁxŸ­}E•7µSo¬G籡š¦qÅ„4nY ÀÝOÃʨÁœ3ƒŒßK:©hÿcqß|GMëê>“Îð3¾s%VxñÒyzR yY6Ý€÷Vil®ÑÁ‚‚¶pvÛtn;²4Ó >™Ḯíô°dç§ñúùóÂÐ>X +Ëm"xâXã7xüäNî_ z§ÃÁÏ „¦A%Üóò¼òó"  GŒ~6Ì#ß7ㆶ!¢ÀÀ¾©¼uz-¹hÝÌD:DËàñ¯¡eË?¥–‚ô(ô‚VÀ˜j™±°3.æá`SA€“‡ËW¨Â†–p|oÁmŸÁ˜þ "|¿J¾MÇOç…*IñÁ¦50h/G`b¯2Р«-Dj5|ùpF'¸á‡Z¾/LcÿvU|½~¬„ ¥ Šk¸ëm¸w&sÎ*Ç C `ßVñMa*ƒõj™j¯[ o¦Mf-«¾‡ßÑíMÆõ«†tM…é‹ :$µ=}œt¤#º•ÐÊ‚gg ¢; ¿¹RJ{ïerH?ý - Ÿ„m²›éœÖÇfÚç0¢ŸQ=K%Т•ÎãR¹fd0®üïk »æ| íÚ¦òðß´ʰ XðnI”ò(˜:üVbqÑS÷~«ñcÒø÷ø Û‹£ñØQT”Žíø®TL§;Œ3~Ò)8ÓË7•·0èž`Þ¤wCSJÝ ž”Ît³n·8¥tF]“s·õ£Añ‹Æ‰;@»>×§!À5¹º¶ÿõ¹çd[˜¶ØÝî"ÖÆIÙ6ÃRàÊB_ÿg+#3<ü("n‘QÊ…ZÓ‹½ðhØR‡öˆÝ²!øå7Ð4xè3‹æû°Ñ(¯²(·m6¯’æ ƒ kQ !ÐòÍqP;È D!¿Ù5€\y8|¾¼œž2Y¼Z°ª –”E9DO,KXŒdÐ9³JŽsa@Þ¨‘ñ_A›>ÆìU.ÇÀtÔZÀF£¶6•AÁÔ7tr…ͬÝ%‚Íú56¡Ù¾úÈ VoEJƒÂbXUCF¥PV#ÇbMÔ]0”n²Á’}iGDâLTQ¨‰=j, ¢ ˜hŒêï#ÅÂм üwb„w—•qá'& 7Áºb9.¡i2Nâ°ýᦟ"<ÿs:û·©âý%`¥˜Ü·Š_×ÁbŠCL™í#*4tM°ø×›…›ì›[öé¤MZ X™ éO¼ZÁ7ßøÜEã¸>ž"hipÿñÞYfqÞ{&ßoÖXWhÅÚµýë¬E¤™pùŒ žkãc¿&“úDø[A š ,@@$æ ¡¥À§Ú|°¬Œkž0øzΆ2›µQ&l,‚_+aïA)´J«–ׇ WM€ó­*2lð™s?«f.ôû˜<Ò&(ìØãÏ ÞeÎ7·R2T4L”­Ý•»%gN6”çH·¿?Ž” ¶eãK„ë»Û%Ê­\¸¿»ƒ¾,ä)÷h½ášÒ€IÀ妘yw7·èÜMidw5ÚàéÖ,1Ò®/—¬·ìÀ­À×Ôõ!U¾„Ðü=< &8TݘüJ¨ÈæG¦›Ùº¶¶Á–B±ÖR'5&ر+ÙRóp úëjà²û5¦¯´m¥Ñ'G§G– \%ZÏ] €¬€¨ãÅ-ˆ=ô5©ü¤™&éFh˘WÀ”ÕÄVÓ¶ p3lŽíÈ0L&ô‡ý;†·]Û¸C(·!§×‰œ2M0 íO‰@šÚ7€¿ý cgh,-ƒÁtšgh4OüRßo¿°_6¼óEQx~ d¯Œ2–ƶ¥i°|“FÄh@Z¦Ÿ‰ƒ4:eÆûÛtxDç¶ù îúÐä»õQ^ù(ÊãAëîi¼ó÷(Cò0üRƒ;h4ÏÔÉKÑY½t2 CúÁ¼³à?_è,^åÁ7#Üû¦Æ€¡™¼}bE˜4WÀ‰k¼°Bг@§C–ÆÞm5V—è¡0TЃ¢Ž8›š ©ºMdSìgÛ`dG¯V…¹rn7'4­´<÷ȵ3OW"%Âùé´T8ï¦dS.ÂÀÞÈÓD´GZ2ÞBVOfÜJ…úL¤\$R Ü.OJò‹Æƒ3ÿ†ó¡áN#Û ¸.C×¹&ÇàÂKŽÖ5ªËj'‰µ}r³(ÃRá’ÍæÈ¹ÕVdQ½Ç~—ê}¦´v§ ¹ÑÝX{(ü?3&Gé`F@;o.ÕÈÈÕ·ø;üªk:¿¯…V ŽÈdδI`Á!SÒ=±µl!C]‡õå¿Uè–^ l*†’*!GÑ5™ñªuË ¯œ_/è\Y ï¬0èÒŒòø6ë#+ ZùaÓ¯V|\Õ„_~… !AzÊÎ…ú™®ÌI[þÓà­¯aq±Îý“ƒœÞ«Š” X³^\bȸü¹0¾+Üüs ÷~¤± Çz Ðe6,Ø`3Ç–l±®lüæýîcßü ¬"×~uX³ ¾*ósë¤0¾(¬Ù /½—}YÍÜU™¤- ñS™Écçú9¡{5tøa>ÌYj oãü©ãZô¬õ¥1sr5•ðë&¸ûÁŒ¯kX4&nzh1%Ԕ˿°BðcÓ¹ut%9é@1´^¢a ì ha@åFQÇîþáõ™<9¡œHD0vx*/]Á‰÷ËŸVòHÏÎÞ»¤nyS]ÛZ¡Þo‰·2‘(SR²) m•c¨´íÄŒ%}icMv)î¤5‰” ·"Òèð‚·Î„ÓäéÌWíŽfí4™ÓJ0%Ï"%Y\Ÿv=ƒ0§ÅÔff³L]»¸©LAÝÀn§ÏiSñ/õHfà‡}»AMUˆ»ç‰Æª!?ñ&ŒDðôêÔ6âk€%äp^› ]*µð¿¹ðn5lŽ%iÞá‹_€™#:@ya-×}’B ²(Ú¿ß„BtMàoƒÛÀ²uÕ<þ}ø R ·=Ç>!øhsþí½Adå@§0a%/,“Ã5EpË<¨õéŒ$ã2¶÷SVÍ•Z½ [17ª‚\AJ ÿ7¾%R[ÚtüÐ*-¦Î´+0²“´Útȇ1Íà¥CÌ[Ÿ (Úg> žÓØ °•ç™ Ë~€ YL_…ž ;AÜØlM‰ ây€Œ¹i®lLEd;gN‡w>ƒ£fDxsM*)Yн3´MÐ05-@Ø‚ò ¢Äö§Ñ"G''"åpïÛ°>,¨´lrò kKX¸¢’WL–,…{Þ‡µ–NŠ.û;?™pÇ8hiÙ\þF-¿Tø½'í_‹StWáN4…›À²žÛâp¤Ç†3€¹)MÛ:Ïn7¨F+ùy‹†Åi¾T#JXVY‘¢@àòM;æÂlü>· X)êC@@ƒæ[ŒI׸b“1þãPtp7ðÒX¥¥UALîÀnhš½ãÑȨŽ@e­ÃJÀÄðÊrû_.çÕ R£6?VºÊàúýJÁ‚ÚTFê^¤µÒmH±  A(ªÑ¶ Œk ¯¾YFëÏ „eÖ ÚdDùv^9“ýÙÜ>´”* bËÇJu-„\~R•µr¿5Qƒ FEùðG˜ýZ)_}i@­Í††‰Mu0áê#`þLÁä*¹;Ç 6d³ªFpða™œ×»„uË °XF°AKƒ©ãaÙ“ÇÿÛfï.>B›#,/… ŽN縂2¨†ê° Òª_bmŸ¹\úp%…'¤s¤YI9‚¨CÊݧ'¤|åØé!Z¤„B‚Œ4 Íâ§B¤ŸÍ JéØÉç aß¾ Òe|H ¦ Ç=]÷è4÷Q¾1B™fpû¤ze•Q\&M¨–Pþk0tè7®z´œÙïùÉŠX|±Á¦O§Çt©â·ø?·{‡M~ŠN(™i©D™üD--ÏÖ©Ø„ê±bº?ܳ8Ìø{,F´óQUlñM ±ŸyÕˆ0è0õ™®ÏaÖ%tφ©ÏTðÈá° Ðhˆrßkåä7ÏæîcJ9|f„#§[´ÍÔÙ\fãË0û‹T ¼ª#Ò2Ö©\œóA5ç¾”Åë§„1½X‹¿‘à3‘»Ûøm[¿76Òb1éæäÚÿDÆ\–  ?RÙHòÆYÔ'›$úÝ}lo‚d¹èšn…©T¨˜M£óÇW îȵ•Þ¨¯§]‹&‹VÝ]lrGi”J[¼Œ½ø¸2á®4™¨þ…GÓFYù|ÈŒ!A ù"ʈ}ï4µë^ø»–5®Ÿ½k\9 X¹¾(òqh_‹¼@ÌÇI—Ùu^ü¾,òc˜Ð±¥Æ¤µäÄŠÓ-þ„Ò8©g•4Gà™mZiìß^6®d#¼±Üd@oèÙÌ¢p=<¹À`m¥Nv†Éø>Q²¢a^üÁG—¶>Æuªæ•ouöî¥Ó#ÓbîW^`2¬­µÅ©âë°6%ÈáÝBøM¨)…Y_iüPê#=MãÀ½Ña²šûÙ§}€Ò ðÔBåå&Á€F¯vpBŸZ|:T—Á«‹uöÚËG¿ÜÚúï8~Y Ï.4ù½JÃðé íª1±wÌ£! ïü¡ôtŽè\™8ë sÁ’b“Ýuúüõ“Éà½Ý2£[D¯—Àû+Mj„NÇ:û…X¼ ¾( 0z ÆàÜX¾Jº7ì#èœaÅÛnÀòÕ0{‘IaH'+McPñemŽH5¼¾@£u“}ZÄ´CЇ'èüXn‚¦Ñ6ÇàÄ!a:dËþwÌ[mR‹N—–Ç÷©áë%ð]ycGØðS”ªÜ‡u¬ÝúØuXþ<ó£Áº*Ÿº·ôsÊJ²b×ÔçßÁüR“‚?Ç÷ªæ—Õðì÷&EaüLƒcZD7[ÌùÅOŸž&c;V³r¼øƒIIX##MçнZ†!O}§Ó¾­Áð©í†+àåE:VºczÕ²“lõ£Ã%Ïû˜þQä+àvdp®JµYƒÌVK<¿—“jÛ$«\ç~ztV]®yÉzŒnš¤ŒÒTNN²‘H¡pf}Hmý"¿¦rf†aNÍ·ÈuW³Ýˆ]¡Wj\Z¨ómmt-p ð2ñ—ŒÓLØèR¯yüå4Œbq§¼0u¯4esTå äÕ*ëiÄ3 iÄëÏ«¶©§ºÊUõ]]Ý*wŒÛ†Z>@üÎPø‰ ªvªõuÇoê®RíPÙ€Ô˹¾÷Ôp‰ÚŽêgÛœûu£"ÎTŽ8QϾ}ŽÿU»U9Sáªs—¨íª­Î~QíRçÊÝ·êü¨ß”½Ym{{írŸ7j}åy¯® u<êÜ âIJÕ1ŽõTmÇ2ÎëH÷1:“œ‡ëiãÁS,<ÓXŒ Øþ¹Ûâ±x®P»—DÚN+…zÝ \×ÕÔ÷º#ŽÊLÒŒO»‚Ø1.x'åŽb³àžR뾈`(Ò=j ñ´´N·(5¹¶äá± QÞ¯nlê _n·÷:):9Qv¸DÛ°ëù½¶žï ·@è¼Kê׶׎DûI„ó˜¶Åú򾮩)íÛ¹Œjk¢c«ïÜáúݽn¢såÜW¢vìH»¶×Îõ ÷îÿ·u ‰–q¶?Ñ1î̹öðøó¨«š{$ ^ðöîÃi¡HTA ¸˜yr†¹×ílŽÊ´=‘@@3nknñ|KÝìêÓOžÆWÌœÝÎ@xð¬sÉ‚²™yŠE’á)=n·'§Ë“Êø0xª½iœÿhs#øD+‹¶Êìì!‰YmŽÌ´ù¸Àæô s/nZwŽPï^æ¨=ƒúÜ<<<’‹ú\X÷T›ýžLÏõ-)ñ‹¿–ú³•R¡™À?€§K5¿×6Êi91›´÷MŒ{g¶²x´¹‘ÚÞ4ÎFÖ»IÜØ™ª×©`€§\ì l%ˆÔŸ¤ÔÃã‘â¾½{xÏAE‰yŠE’á) Û³Røbó6ÓµëîÉ3³_mcÑ%€÷èÜb}tJN” ¢Ÿî¬Á,dººfÈþu*ÎóàY/ö¼ ~äÀ»O=œxŠE’âoïzÜÚîŒO ³ÔL®4[Þ“eŸ´=8@ûÏ  “f·Žðï3ý–"ûòͶ½p#°€xP<Ô K>êÑ4¨¯j©¬r쌼ñððhXtd¥wI¢{×cÏćT,¼k Éð‹]‡³6…îšœÅîz×déÚa—çèÚ¥9Á¦Zìnw!d§ÿ£™ÅÁ©pe¡oÿ׫"Ï÷O"Sª@0•Ô]ïÂ;ÉFb…" Ø6PZ£SZÅÞUéf=<<þšÕa êV›öžÉ*³WŠ1Éð‹]ƒÛõIY*TÖ'4ë\8"h´½3ßfŸÔ¨÷øÜ•è„—[G¸¯Ähvc±=­ÌÓ€ï‘×»N<;»³b·§`$7NEÂù]M‘pTˆ«_‡;ß3°wÿYVmò\ð<<hüVx eYv+Þ³yχ—n6)ñ‹?‡ÛJát}RY‰Ð˜’®kÏË2´ks-Ò=+Å_ƒSƒ‹s£ì„Ë ÍQŸ‡¬^ÀÈô´5$¶^x5/šN¥B•Dû]f®.ж^-Ï€­3ˆý™ìaÎkÆ) iÈ‚}A %ö[á:ª=gy:äÅ­è«goY}£6ö¹ öi³µbá±çá¹B%)žbñÇp*õe~"öy8pu_¿Ñåî|›Qi1 ïVùk04 ÞZÜQd¶¼«Ôº+$Ü,'@ï4µºã.¼³”\8ÓÎ*¥Bx…€ÏÕ¸Ó‘qN©HÃOÝ b;ªT(ÁÇG•dÙ±OUù;%¶¯yÈ*²žÀ¼}Zñîæ€º-âÊDMlªªbŸÊš¬î[Ïš¼ç¢\¡<‹E’á)§•­T  p‘ §šéóÝš!ß«K±{aÀMÍ-F¦jÚe›õ£†£ý[€×bK©8¥d8Ý£À;cÉ„ÛýIŽ:],¢ŽÿCŽõ¢ÔµZìȾ2€½ DNlJ¡njc' ‰×®ñHŒôŒMsðîÁ¦€S±P– uZŽyj¾ÓÊS*öL<‹E’â);‡;ã“;8[Í;¸¶“Oïs{®à¸¬ˆœãÝ»ŸXŸJ¼Œ2­È×áþ²ÈýÁH¤õb-që…R*<÷¨äD#§”S ·H₌»bûŽ(6rDm0Ò"±=¾>&îå±5ò|ä Gµ+ðî½dÇé å¼Õ=YûT¿©û2êXÏ»ö<<‹E’â);N}µ)TA6äçgOL7SoÍ·èèÇ{$6äš0½y„á)¦ïŸEöß–…í>ÀTàâiiUÜÔU0”0êÑ8q*Ê¡”0qå_wÌ$˜·3E‹‘Vˆ±ÛY~ð"RPö\ êG Ÿ©! ‡–wñHnœÊ¾²R(—(¥\Ô²µC)Þ³wÏóX$)žb±}ê³R(·'%(\ÕÆÔ÷™Ú N϶dnnï–h<ÄÎű™ÃSàªB³÷ãåÖ£¶¬Úý`ñ¢<Ê5ʵöVß=epÆVè±Ï0uÝÝÔ¨i˜xñÄDÁÛ;Ê+@7 k=óW3€R<¨íŽzÇþ7ç©ÏÊ“ì(eÞmITÛ!Ǥ¬õÅZxìxY¡’O±Ø6N¥Â99´3€“KN5s¦ç[ô â=3Zø`fK‹!#í–qÞZËV±ó‰»Ë¨Ô´NË…wV/NáCŒB]¡TýîÌå´Vì¨EA)(ÈÑÖD”÷¿ÄöçQ?a /2Ù…:_J+ÆSÊ’:5e¨O¡ 59­^ŒÅž‹‰÷ÎMJ<Å"1õ»3“úW5ÓõƒoÊÕ™œmáSŽ›Ø9:æ/m IDAT;7Ê!ipÙfsèËUÖ3ÀÀC@[gŽJT÷£qàt…R–&÷(·SáPV õ©îõDë¹ #Žþ¬V]ËTw c+<¥bÛØH7Ò3‘Y»êüãõa²ãN9«ž©NåÂÐíNí=o÷,TQaÏb‘dxŠE]¥‘UŠ„ ÒÖÁš§”b6¿§y”¾AË{ô%#6t À ­-f–׉+6EíÀMÄ…Bƒx¦§rá¬ÞíѰ8Ý¡œ££Îùj¤ÔGÝ‚­_¡¬W#³‘™ßfÏÝÿ!³BÙȪï¯!ë³hxÄÏ×¥@g×<Yÿ£ ÙÉ‹3Ëž3ã“3#”R(<7(ÏiÏb‘„xŠE·Rá¶P(í¹pU¦®~IŽ©MɉôŠÝ%7t Î̉2$®ØìÛonudp/ð(23M¢¢z±µë|z4N«E¢yN e¥P.6ÛŠ¯°‘BOààPà àbà§Ø6 ‘©QOm3’`{qj‘–ŸÃÌ3ƒ8åH…Í#yq×£P£R"œ±^åmˆË\ÞyO2<Å"±B¡å&R{>¸lß ÙîÎÜ(ÃÓ#Þã®)! Oæ´‰ð¯³ÙmÅÑJm±2sÔRâ¾øêå祥m\(«…[±Hä‚á¼×ësƒH÷ŒLàïÀdäèùÀ;Äk_¨ÑÖGq÷ ­^¨mFj_Aý1™ÈÌPÞ}•ü¸• eQt*î:4^ŒÅž‹ç •¤xŠ…Ä]ìÎt| äHåÅ)š6éœ,]»>×"SÍñhZh0%×âÀT‹6™c懬^À]ÀsÈVgÍ ¯¨^ã­\(¥B'ž6¸>…Bs¬£„žƒ€k€öÈìa÷#­Ê*âXgðmìO©Ø6QdAÁ[€VÛX. ¯âõgòã|>ºïOuÏ9ÿ÷,{6*+”G’±'+î4²Nןãû8àº^>½Ó]ùphFÔ{Ôí ’"˜ÛÖâæ"³í¿K­{jù+‰»F9ÓÒ:_˜êÝ‹ês§ráLE«¹&¨k¥°‘#é{×c€÷‘.P ‰[1¹89÷í±mÔHä;Èû¨ÎÖ奈Øg¯_“D/ΙDnOÞÛvÏÅ`k$`O}P»ƒ³AÚÊ$ß8W‡ÿ;3Óç¿1/BKUÏcÏ!v‡¼U¡sE¡Æ’ptr”õ ¤pé,ÆæÌ`âv7¥Pd—³ŽL3‡Î+°™œ•ÒŸ@† ö ·? Î…'çÂÃï ·CË6;KýŸa£Àˆs`ú èÚ6±â`Ç\Óîfù»N¹¨¯Ý ÷¡0ýq°¯TjmLx¬•ÅS-Œ`S»xY8MYÂüį5§›§`4<-AøŸ"}ú/@ž»wð^b Å߀—5+<<<zþû UBëÎpÎõ0t Ü8N=Jìºj Ľ롟*Ë~@ƒ¨Â²Úg@!âîWà7ÀŽJˉQU.e$ j ‰xÞ hÖö†p×ø²îT"vœA ¬ˆÜ?6ߊiL¡\ÖçƒhDîÇkÏ+3à§VP¼1^) ;D\\ó'[_m/k[8úî]Cñm¹UßhÌDª‰…ÜÆúGµIõ{$v– ¬hü¸Ôö±6ù| ,¨qç¥DDbç#{ÖÕ ¹?tñÃK­#ü¯Ä̼±Ä¾º0jnB °ªÞºU“m ÂÔMHw†: øLq¥¦iî—Q²ÀÝß‘YFîÖíÞ&zÔÃHgSÏ ÊÃÃCaðdžm=˜¦¤X$ª ­\Pœ"y7àÊ4];úÒ,ƒ«s-)+ºÅ!„CÏŽ)OÀ?N•ùa|ÀOßÀÆ0Ì~ z‚V=¡ÓÞ0¼?Ì}~\"[Ðé`8| |;¾] §N²e`t‚1#à¥; ÐRK`“Ž9>zf=†ÃÄÓ W(ß¿ï?¥¤÷‡ ÇÁº¥l£ÇCЂù¯Á+€è ‡ “ÇÔj¼þŒMP‚³D:ŽŸÍRá÷Ÿá…Gá³÷ý! ý8¸î&èÝ~ÿ žy¾™/goØ •­–ârJÿ0|(è!øî3xóIXõ«ìk}'ÁÄ“¡Cs(ü æ>ï~ãÏ‚Ü  N¿ ^x~ù9ñUÚ¡/\: öëe¿Á³á­WäYNm #ƒCÆBnl\s_†O_†š˜Ø÷8˜ð7èÚÂåðæ3ðò£‰c7¢@ç0î8Œ(,˜s_€%ßL Îϵš—o4F|Š>LŸù2 E]—ªòs“ªØ-„è§iÚÐnC¥¦Õ9y­‘–‰KWþ H+Å/»¿uõàFŸ¥ ÜƃW†Ø£Áp+Ê¿=™¾0;65G¹Z3(`ˆ÷ÛèBt#ñÔ!vby¹v¥g¢k™Î©B=Nˆ3Ž¢k¦¼'„B\y¢m¢!.ü—üíÉ©B ì&D•ÂŽˆ-Üw¶?oBD…ˆÆ~{äB!FbM±ü¿²<>ïÝBìû,D©"ÛVMe|›/\'ÄÉÿ²äÿ¶-ĺ…BŒÉ¢c¬í]¢‹Oˆÿ½[),ĆM±ï•BÜ{ž<†;_BØBرYå±ýTm⼡²OÞþVâNBtï!ÄÛ ä2‘Uaù}Õ—BÓMˆÖñ«„¨Šµ»¸4¶á¨³ïbyYü8¬Z!.'vì!!6Å·ùÈå²&\"DY¬m› cÛ²…˜u­<÷]ç!F-Ä/±¶V”QVkÿ !Nîï×nÑQÝqs3C¤kš@fÚ?v æ + §!í)Êî㌽HJ„¯‹z°£QQUY%*ªkί ÕˆÊʵÎÞ^Bè@d¶®Àf`jì7ÆG{¤BqdC7ÄÃãQ1»è‘d4• §’R.Tv/¯;ýšvßy™f»wÚF9(};®{à3¡¶ŠW×uå0ªaÁðþ«PU‘˜ÅÎvDs+·hDº8ÕX ™0ÿ-¸ò,xùsGe³ùn¸ÿ®½ÚåÀ«wÂá¸É°¶FŸÃF@mªCÒ èµ{`lw˜ö€t×r¬¼®þ—_øœ2VnŽCèœ9J×Ã9ã`¿vpù½P›Ÿšóã*] õƒ—¿€ÔpÑ Rm« rLþŒ«aL?XúL#GÂ;  Ã8é,hÕ.¼R£ðð?aH;˜r3 çL€U¥ݧ¿)ÅïDÔ®‡›Ï„áûÂìOAO—±/Yíá¤# b#œ¼ì“ N“}³ßPy^=2}pÿ90 þ~ ” 8ú4èÒJZ-¶ì;:fIkÔ9rŸs¾€œÎЧoÝ~)\å­608`ŒE¦Í›äÉÞ*öBYÖ’Z¹¨PáO\8i £ÇËë_þ^wf´’‡oœÌ¸c'³ªòÏígÍš5-MÓ¼X€Ìðô 0™µkÅŸÛºÇ_Äpä集nˆ‡‡G£Â‹±HR’Ùj[ÅîLâÁ³c€k{øônwæÁ¸Ì˜Ô¸C¶í\Ó6ñŠ;‚n@áb¸z,,ò2¥xL¯o€ý·ݡt)<> Róaýðú—pÎX8t$|³ lü¦^ ›€âY0ñh› þ ü¾Q¶«²ÖÿTW\À° ²§ÞùÌyW* /LÆÀáÝ`ìÁP“˜Ÿ¹ž{E ù7OáïA›½dnh,à#·;0¨†gƒß+ PÏ?-]Ä †Á› c:,yî™&Û7çFè?òÖæePQ ¿,†Jkë¸Å«÷ÂŒG¤Hòê ˜8šgƒX wM…´ÍðKt 92ÅŠÓ§‡ Ç–Ãwï“dÛ ‹êîÓ|ü%ðí›èí»J¥Ž(ØN-¤nO¼ˆrg‰Ùæî’è½5BŒîBºâø‘WrrÆ_8ÏTÒ£EÊXþõ§|¹Îä¾{ã?´VaÖÂbõ²oøä»ª¬DA5;ÎÒ¥Kû"ûl2ð>ÒZáѸ9 ©Tllè†xxx4*<Å"IIVÅÂ í®  ²®Àœybº/xk~„¶~v\TÓt©èšDÝëE|0æ4èÐ ^šÝwãZI× ´X&TLôX÷Gª`ÝHZdË£Éî Ï/$ž÷6&p5Ë–Ë ä‹dP²ÛP‘¿ëf\xÖŒ­e5dÊ/ŸÍ• ƒ‚%¿ÀáÝÁôÇæ| „j¾…Me²€SO BFH…ž…} ¤eCÛòÿÍë¡B.Š+õO·˜š¨an{ì~ÅïR7`KCý¦ü­´NºþyßÖn/= ƒö‚~‡ÊÉÃü÷à±CY¸ni@I!dv^‡ÌTÇÌí$iÀMùƒüºye‘6éLjÝYÝùØRJ1ŽÖ]sKØÒ+šÐ0›`ãû÷sÏÇp×?ŠÍÕ1}|¦å:Ý6›×­£<!£Ysšç¤ow?C† ùbĈc>úè£Ð®? ¿€T /2Íl=º‡‡ÇŠNxÿí‰$›b‘ÈJátR¢îÀ?;™Zÿiy:“2#;'¢iHW£Ÿ7AAØ{|²\:²€|f „kîƒöølžtuRóTæ'áöŸB*,[ Ìz\˜Õ4y$—À sc¿k §Bn|2´v±}9HÓvΉF‹ýq&è4£û?€Ü4Gõñ ˜rn…E7@”Á³ÏBQ…ŒhÈmk?‡¢®±mñqè;²*àëM;Úp¨‰:Îgì7+ Ý΂ÿÞ ‘ xûyX¶Xpß´˜’¬üþqôÜöÝ{Àб°ï`˜t(|õ]<ÃT4.¸N«–À«ŸÂ‚EÐü˜²ƒnáŽÌ²š 7™=f”YDà)à_À¯Ä­*wX4¾¦s+IŠÑZpäi–¼Â½·Má¨ño3¼snÂÅËV~Ç×ýƒYo-Â0Dµ x)÷ß|¹)õùÆAnnnù‡~vp{4^z#•‹º!•Ò#ÉH–‹mY(|ÄÅÀfÀ?ÇNL7ûÜN0)+&£íŒX¦¿Á»/È]Lžãö“ã¤À|ÁeR©X7~ÿ¬¹n»L¹~0F´sûÖµ¤Ðœ€¯ƒ/ƒ/…MÆWä¦;¬#Û8}J¸ŠFꦴUÇ·²H~s†cC@ÊP8¬ØUðÓ÷Òú0òDy{×}&Éøò PˆÖÑ ¢6—„…¯ÀM—Ãõ—Áûó`ðPèÙÖ~-Ûѹ'tJ•Yª2Ǭ·á?CŽ-3B ¢Ö¶)‰dG!`Ø`Ù-óS&ÀÓÀŸé†]¤¥§]¸í0c&ÝÚ‚U •EëPU …@k˜øwHëÇž £öŽï'ÿ9¨ÞvÓœ™m30®ÜìëñLed† ˆW„WÖ ˆ»F%¯Õ¨­¨aß“/âÂçžä®§óà ÇpéqÝ·Ì÷™°ì“Wyô›õvÉL¿ít à¨qC UæÑWïeñÆ3ÐÂ_ÿN<’‰ÞÈ z¯Ú¶‡‡‡ÏÊc—“(l̉Ÿ dêÎl ¸(:,Õ'–v)?ëM%»3Sg„q€s>"­›Ørý2!.=^ˆö*=mo!>X „¥rl†„Xü%B<0Eˆþ]„XW)Ä’7„èÛöÞÙB|·Vˆß 12¶Î1d!Þ[Pw%¿ 1õo±ù'ñk¥ïL¢Wlžƒ„øj­…ß 1º…Ý 1¥–-Ħ¥BÖÚ•n!.½Yˆ•u÷³ð5!Ƶ¢BL›%De©?¯ŽÏ·-!^Pˆ½u¹Ì+Ÿ !J„8¤³ZqÏl!j}eÕñÂíB @ˆqð0!üìØ¡%Ä·¯ qp¦ìËÿ¾(DmD⣶N7{Î…BTV qùñ2%n{„˜0AþöÖt!zµâõoã)r…⻹B|»RþöÑ !!Ä÷¿Ö=îÚR!¸Dˆ½\éfÛ#Ä©S„Øàè§šMB¼óš…!ùÿ9ƒ„èôÇ®±'[¢¡ d1½ñH«[}iiÕ»béfC¿ÍÃ[!úœþ¨Bˆu‹æŠ¹šÈé>^,ÛP$n<±¿´ê"~,âµÿœ) _¼¼hsm¼tû9Âï˯~_”`[Péf=?AàCàŽ†nˆ‡‡G£äkàá†n„ÇÎÓè”ÛŠ¥PJ† ô®mfèOÉѹôÿÙ;ï8'Ê<þ¿g2I6Û;U:*Uªè©€b;Î~žþ,xžåô§ž§œÝÓó~gïí¬g×»ˆ (½Y€¥o_¶dK¦üþx2dvHVY²Ù}Þ¯×óJ2™LžL¦<ŸçÛ²t<Zãêˆ @ýO†aCÀ¯Báf˜ûTT6Náš”CN„®ÙP° V ÞtUB(É9bF>蘠 ä€j@°"Òoð&ÃQçCŸ.¢@Þ7ÓaÛ¦°sŒ’3…I]­c[Ù šP[!¬Y= ÿp(ß?.nü»ìêÞGÁ 'B²Š~€¯ % ð¦‚Ï V†Ÿ Ýr¡`,øLl¿xñ[8¹œ8~ gŸx*ŒF5¬\+ˆïÓîdi=`ÌI›ÛVÀÒY‘êÛJ&ô©!X»ªj{|i^HJ‡º áºêd‚„`-¨¹pÜ9Ð-~X ß~ Y‡Ãà°e©(`è O‡.âss>†WEŽ6÷1Ðùh8þ7 TÃ×Óaû0ðw¢€âwŸBuí/;›ØP·kL«Ñ+G€WÝŽo·sE«ÚÝ*°,ëàÔhïÕoÿ– #FSyÒ+¬xù¦ýí"Î|àu.¾óI:¼Í£_²â‡ŸXóôùœuËÏ,Ù1á"–‰ÙÝÄo§¼ÊÛK70iPV¬n|ü.JåmIë# X\ÌU/‘Höæ»p»<Þ‘4Ö(,Üñ Ýž,DrÔÿ\l@ëøPžÎð-7̲QìX»{ï…3¯îÉVäÌšä w§~u/³—ÛÉHíĹê>>ç^f†?oK²h˜‘íw9÷§=´µë˜ûRáÌÛáê?‹´³'†׊÷ì~C䟋öÛœÛsþ;?Œ‡èG¨e¹ó·;÷ýýöcï +ü»í!¨—Øû"ûÉ^W%r<¸ûß\0LxªBãîRƒrÓšƒ(ê¶ÌñMvµngÕnûWÈ#ßy¾¹[&‹È¸–‡(:Ùèüõ×_ÿæ˜cމMXÁ­L>a,¯ÿ$ƒPJ­^Ïϯý™S¯~—¾ÙÀ¥cº„·`ðØµ§ð×—×0såÆõJ‰Õw),‡nÀ&D‹…qî‹D"i},WÄ»#’æÑÚb,¢ ;€ÕönNS•s®N÷p{®NŠ=PlÉ^¹…B¬õì!X¬÷÷g™½ÜYâï—l˶ó4…JãÌP±¶çì‡ òàš¿‰¡å²Ï¡dk仚Úövcý.ûóM±¯ßmß¹- ‘ÿý!Ú~:Pg®ËÖ€)EžãæÕ5/Þ&Á¢±ìÛ×QïAˆðT„ a*½*9üh·4„ë•»ÙÕÂ3t„À© ·êðc%Pé÷û›•âÕ“|wß}/_œ1™¢j“ì´NX@ß#†’åy–ûï{Š_¿—®é6/~›WÞœAΠ?1¤[LQ!I,z"ŽòxwD"‘´JZzd'i!Z‹°pº>¹›=<ôgS†ù=½Í3ùMŠÞÊBÚ *P½®=rLX:Ê«ZÏÑ”¨X0:Ó»HZ„$")$‰Ä‚¼>$$ÛEq5{ä¬4 0¸µ»æöÏ‹ 2BÒJÑX@r8jo„–Gš5'ž“/…òaðé{PÓØ_-ÞX='RŒOr@Ÿf1=`pG‰§ÿ •ÆK†Å«ÀcÀNÄùc¨Ù™£œ´ôÙj-ËŠË@QÒ­&‘°«êʃD"‰…õ% KX¸«gGs}²™h®®:'UK}0O§» ÝRè@ÏSá?OÁœÿÀE“!e=/ðýXu ¬úêõÆ¢Á2ûƒBÞ&8û(ØPÜtæ'Ió° ×Ïv48.àñÝVjýéç9¸ø’ˆX7ΚŽ-µw)Н3UE¸‡É+EbPJ¤dMœû"‘HZ'râ!9˜ w6wmŠ‘ÀÍ<êñ÷d«\–¥K+ESDêOØ(ˆ\AŠc§k’‘bÕYçÂrd/uÎ:‡©:0ï8óñ¾»¾„ X–(²@ñD¶å® {@ IDAT­»úm÷Iu¼ç!R#Âï胇ةqÛ ü>Ã`L¦{¾[zxø7Â-ÊG$-­m½°+±´ØÙ¤(ʺ–Ú¶¤M²‘Â8!2$‰Ä& --,¢¹=9E……¸±\ \7! å>–¯sx’)­±°÷Kßsaò9—zjj¡¢ž¼IäùÑ~çÂUB~:˜|ûüç1á0¢y!#RÂÁª2ü”)^׊œ-Ç\ gƒ†Ð4X6 Þ}7R¤NÒÒ!% ”p¹@2dä€× ¡ÝP[©Â0òpþùîÓ’áÅGEž¢c¯„ß 9Ëá¤3!µ½ F]Ç €å3àÉû¥‹•Ý|ðVç'V¨Éw”qÃÝ üXDD°ëDäaø“{¤£<»$ñd'¢>Jr¼;"‘HZ-Òb‘€´¤°p‹ {»>Ü–®*§LÉÔ¸);„O–DiøýãpÇ5"/©Ñ¦^jwÀÛ7‰yëq·Âc‡,/”•@f.Œ=zdà w°¿ÀWƒ6Œ8>+¬ ^¼ž~úŒßùþôBxï݈£MÎxôÐ%:@ë )D„Wƒ¹oÀÝ·Ëɸ«àÑ'!ÝeÅ‘'úÔ9n¸ ú޳/‚Sj!9ì“uèPèÐGACûÁì—`E‰t³²Ä.¹4ËdTn,ÖFMê¯O/!¤š³&¸3®%*vK$Ía3BTtÖÇ·+‰¤•"ïQ HKeQq[)l1ag|J.¦ä9ezW‹ÛrCøTäaÔÐë¸ùHÒáտé£àì`Ón0BáÁþ@¸s dXðäµpbøíåP\§] #ºB¨êj…µÀ0„e¡.õµ`Z"ÞâÓ{á”ápï áõV¸Sz-„ê…;&4¡®F\%¬"ûÈ9 'žuÑy$WËÊV•;Âb°xwOÆ5w¶(·“šDr0)F¤“<&Þ‘H$­ûþ$ïö HKY,Ü)eíf}ŠrþéšrwnˆtYìnÿ0€ßÿ2T˜óÜ}·Øo?Ô‡@QÄëCNÞPò3lø zjl,†Q]`Ìxxô1˜ø ¾ >|¾ý\üÇH𶂘O,ÿQÔaî¶sïþx€òà’a5Þüò7ÁU£`CMΆîÜKÖAŸQPSÛv@‡^Ð'Ÿ=Á{ÏÁ‚…pP]?Ç=¿ë¦¼Îì…^þ’£36IáúmÂÂ:ýPà`*"c“'²¶Œ^’ÄZ„ÛÞoâ݉D"‘8ZBXÄŠ©ð§wñ{z<œk1.5<[.‡8û‡Õ †ô,˜öœXæEìa;ÛúöÏszß‚ªD†“¾°û“ ¨ájw%òo9QÃßáiâPQÂï{P¼ eZÙ–äæŠ£ ¥¼8-2´ÝÓ'¨*‚¹³ !U1³¿„Ëá$ßþï§öŠ#S,¾LÒùW™vÈÆã»Mëxà_ÀbFƒ(Ï%’ƒ…g!*ÉG™¹H$í{ô g-,Üh&/ŸæUží"]ÆRü”°+’zµXd™¡CXUb™]ìîç…ðÔËà ËtX3O Ú§¬aíçÿ±ïËK²<, Ö|o|¦£OJ,Z'…C<ª°¾(@m¥ˆ+‘ìT¸'_çØ€ª^\¨œ¹]7ûç"2ñ؇‘ n‰$|8»Ïs_$IëÁ™»R’` W(á`³X´ V3µRcr¦Ž"2š‰WdZÈî ÁuÚîø'dûD¸®¬ý¬Ë„Ìì…+“!8²‰8¦™°gbÀçŸÕ‰ ä“¢tÁŠJÝ&S¿*Šh^-’:6¬cP€íâyj6ÌzQ Br¦†·­Ê²G‘‘ÍE­õðf•J‰aT#ê\G]DÏ9 ÏBI<Ø ,~ <œBH$‘÷¦¤¥‚·ímÛ‹$ÄMäϺõü‹ôÚ?ìÐØÔ@û4šˆÁx¬Ö€¸¥6µN=@ìÛüÓCpÕmðÂû0ñl bïø¾Ý ÝGÃóŸÀ„ãàÈqpãƒðÖ8n´øN¨+Û:&ÿzŒ†k‡«¯Ž^Z­óð‡¿ÀÙ“#N5 P¹ K!µ'\r ô§Mž†î9°ø#øn#t?žzÆ ÃO„›…ÿ~#²?ÙäMaûÞOv¦ÖÙ¯J8ÂçÒû»UŽÝ¦òJ¥¾¶Þ²®GÌ›D²D9¸%’xò0èïŽH$’Vƒ=6•Â"9 {ãG gŸÖ½[­_½¸Nís®Å9û놓€@ÞèÝ'zà±èA(/¼`ÅXǬ‡õ³à‘§à˜AÐmÜ5D¼?cvd†wbpÞ9U +U T¡°•ؼޟ ç w¿%\­<*Ì}^tôAõˆ~ þ­h¡bXñ l o'ø#¼ý ùœ}-œy°@èÛàµG`ËÏpË­ðÂ0êlx÷,¢Fèxü€îªâQó‰cÃë’ 5FO„d-ú1£[~€ô.‘vÿr¯£Bå6øqYÛ1´*P‚{K5žÞ­7ð9âÜ‚°Ö"ä«’ÖÄ×À2àD¦@‰D"™d$a9Ѓ {ÐbÀó"*ûƒ›”póÝ€s5…/NÕ´ÿ—§“ï¥íF5À…ÏÁ¿®ˆ½Nu1, Ç{³ ®Ÿ,ãàìs!à 3Þ„ é0ë}.‡CÈÐy0ŒŸƒ 1±f¬Z?,Ž8˜@ò!0ö$8êXÐLX5–̇MÄ?j9‡Ã€áXÛ[ö¹H kE À°3àø“ slÛ æÀš¹â½Ðy8Œ›C@C5¬škVÃú%?úvÕ³¡ÎÃŽ™­Ö.ƒžÇBg/$õ‡Çžlº¬ÖsOÃðS`xÏØëlø.8 vÓ²v»–&¼ïgT+L)VYÑ`o_!޾Úðc ÂVSGÄF¦©ÌÝÖÎö©á?Ž~7Ðçép_®É)imÌzÑyœu"èõQVP¡n7l\‡þ@”uÌjxû?pöÿ…‚Yðõ"ؼ rûÂßžƒsÇÁüçàÒ«"ÿªç`§óµ‡v53¼®æøœJc{V¸¨v#|ì}5©˜`Ñs~Ÿ»O¶Àñ…××ë7„·ã#âÚÔëh˜<¼fk„š³gÂ!C¡w~t+‘懭Kà§ £DC*,Óx \·ê-¾¦?#~Y ¢~z "Æ¢–ÆÂ ò/µ¥³N’X±skÇ¢DÒÞI–ïoW$Í¥%……1|ô]X¤iˆ¨ 0ѯ0äê ÍsWŽÞvj[(ˆAñ¾°ûºØ>_;BévØUy½ ?*6ÁŸÏ…EKÛvUj1<Ž……"&±sÙIW“HhQ± n,ö°¨Þ¨fßUQ…¶°¨A…vTOi±´Îþœ‚‰¤ý@¸H¾ Üç¾Hš‰gß«4‹X·5Wó:š= ,0 ¸°Î츰Îãà…®m¥t=ëÞTÓöcO%ì,… ,ÈÈ‚ê0û#xønøf‘X¯-£°û‰}ïO·Õ&QP Þ‚§Ê<\Q?éæ&àc`B04qyªw,³…„ÓR!…¤µ° a½>áþÍt+‘HÚ^àJ`r¢!á8ÐC+gµm[DØ®PIÏø”ðcª«%‡×ë —ª*½nÊô(7fë$Ëšê{8³#ø’À¬ââÈ€[ÒvQ`E Ef×êvåâåˆDöë“íþTíz]GDl8E†IÛ a—$6ƒÁÜ÷Ĺ/‰$~$ß#âïs_$ͤ¥,îæ´`8-örű¼ØÜ`œSkvøºÖã=Ügѵ-»÷4ð^ª†Ú ¨ †+cǹ_’–#œã©2ËŠ`Mƒ±˜¬'"j-èXî´\8-NA!e»¤5Pˆ½wÓ]ñíŽD"‰>à D÷¼8÷EÒLZJXØÏÝÂÁùÜ->œŸ±€"`ÇÝJŸZ¥dúðpd’…'‘3øHÜ{®%±Zè{ìá¬eQÞk(°±.+Ôxt·ÞPcYK³º%ˆA˜SL8ŸÛâÂéå¶RØ{[ Ika5pp"p³-Vœ‘H$MãG‹åHa‘p´ÄÇÂñ§B–&öþ±WÁ¼Í0ñtño  óÅðÅ&xüñp‘@ ÇÙðÂbX¸m‡/–Áä „«W{þ*°=“wj\Rh躱ø‘FÖÕ1Z4k…ÛJ Ikf-ðàZàŒ8÷E"‘|ìѼO% -QyÛ¶6؃;é§]ÁÀ‰-<ì öcÈñ˜^g3P¶Ë0‡]VÄÐ9µšvo®N7_”­¶ "ŽcvªZ;Û‘½7í0z'bÏÚõ$ìàxÅW= “zÀuÇÁ¿‡@ ¤¦Ar²Ø¦<îΪdoÓ®ð-Ü–˜&”yéÐ!G¼Î Ï¿}RaÑPîƒãÛ^†Ý;aêì¶FöìÏÿíV¹¹~ é¥ÀBà'"•?$@õAJ–ø+쪇ü¾px?–ÀúoŶ<„ŒÃO†”ï€U‹#áø þý¬þУ TþµÁ°È0ÅçO¼^ˆŠ¯Ÿ‡Ë¯sò7¾×]§œÎ>X{6>(PªÃ=%^žÞ2 ˜ý-PNÄÊWGcá¶ pÖªp§™•±’D@¦€Ó²¸öH"‘lä=*iia{çËwºH9›sFÕ-2œÍ@Ì^ý2Gþ~‡M®ñzîÉ ‘ß^\f àðäÛPºü=`È¡P[_MƒòT8í4È À®-ðöƒðïçijÛDøû=0r$k°³¦¿ONƒÛŸ‡cz˜ò&¤^…áØÉ³®ƒ«úAÏ|¨,…Å_À-WBYtšÿ'3ü*Ô”ÁüáÁÛaóÑ߉WÁ·Â! ®Ö†-%–Cb (wzFÄÖÖÏËÌ©ë‹<¬hU ÅD„B´¬ON1á\ÏN'k7û­#œ|t [G¦/„žGÀáC@ßßþO ÇëçÁûÓ!·?Œ ý»ÂÔ©0ì"xòyèœK¿„"5XX4v­ÿM…¾ƒ/?ÅóDÀ¼‰pë_¡C¼õ|»¬e"‚≵ÜW¦qM±ÅVÝüøQ,Ìm¡°ã‘œõ)¢Y+œñœ®OÎóK"IJ±Ew êµ|ßîH$’ƒ@¸ QËâ›8÷EÒLZz¨æ¶N8c(œ‚csWUˆJ%"»2¼Ì^w0mi½±þÔŠ9¥H£Â ív[–Ø£ÕpýpéIðÖtñÞ²à¼QpÙÅ0¤gCjO˜p5 ì_= g:.úM8m"L¿làÅ`êPÃF­5Ãÿל÷=)–åv…o€±Ýaå,8ÿX¸ûJ¸ø|ØT #'ÁøßÀùW@† ¯ß —L„‹ÃkˆmØùÁÌBøy Ô… w¼ü* ê 3Ÿ€—_n“¢ba LÜæáÎR½¦Ú´æŸ#r÷׳÷±o÷Îâwö£S\βí¶XH$‰Â4àzàAà¬8÷E"‘´<{Fñù´”+”{0£²·¢»DÙÎ@nç€)„PµDÍ‹u–µåÁr}ôŒ 'ýÑ\‹ãSÛxì…X÷=|ó­ø'·—ˆå˾„r„Ÿ‹ÄÞÞ¶D,Ë À˜>âù¬—ÅÐ7(œ_¬„+ÇÀ‘gBÏÞ`–Á˯…ƒ¿-øïWpÁé 8  7ÿu€Ãß®Ÿi+h0áR/•ëì6Mˆ»8˜»àÓíÉíú䌥ˆ -Å„¤­ðo "Þ¢ ø*¾Ý‘H$-ˆ ÞN`–°°±ƒ¹%×L"y‰œ.PÎ8 g¼E´Ùؤðã* pE½1fÒN¥Ï­Y®Í2H±·Þ©¨ÃM †w©ê סðDîvéA,ès$ô.Ö7Mؽ …Ù³¾ãsŵi¨8Ì©áTMë–GÿPº[<÷ÀïƒÚB°‚‘ ò];ÅðØ‰ ä GŽúmðïGÅ"é×í¢Vƒ«êà¯ÅfCµ3ïJ„ÕÁ.vçÌv¿®'z€¶;–Bf~’´5nzo Šè­ow$I #ï] ÈÁ°w`·ÓiÉí6-Û9ˆ²-¶Ø0ÀôjÓxk©1rFÐx$ß`hmômƲ‚à{áõ颒§]Úϰå;Ð4±M#="ÆM0œçvà0˜¾.,p<0´`ÀîbÐ ¹$¥ƒ±Kl§_?‘HØrlTê«`õ·°a´³aƒîãeî/·Øi[id·ÓØ%0š…"š•Â]ŸÂ™ñIVÕ–´Uê~×ïï'[âÚ#‰DÒس—òþ•€ÄËsݶNØÎ’3þÂÎÛï \µ}Ïmÿsû¹³@X5°øxN­±uÂV…'Ê<èN;I»Bº ,'ž µ+`ÙwÐs\r?Ô´ð¡BNþ¾31) ,Y#žŸz-ôé"öþð¿Âq ¸ >6oæºë!C ˆêˆ×Î8 gsÆTØñv,…3žBÖ¨´ìLQ ¢ÖEv|»#‘HZyK@âa±pb»FÁÞƒ¢X–‹¦RÑÚq:b>¼(-3­¡×þ j¾òtõ“ø‡«ªŠ¢÷IŽJtZø¹7ü·ª ¤€Tðé0ý98y:¼¿v4ÀÈÁ@=¼ý”ö"`Üô2‚êðßãu9xÂf„äd˜s/Ì=Ž ÿýÖoƒá¿€/?¶ÁKÁØ—áä« ÇQwãÐÎb~G/èÐ(„IóaõŽÄ Ü‹²7+Tî(…Mº^ˆˆ¥( "œm‘à´Ti:–b_id¥ ´u¶ ‚¸?^~߉¤m ÓÍ&0­)$Ör=w7w:Úh¯£«÷¨¢ !3ûƒj55_U””àc/O22aé\Xñ½|ûsÀ¬„ofÀO?*¤e@Ñ:˜7¶­‡9Ë!= é ¹)"6â™ÛáwÄ©\\;@U¬˜-¶=À¼°s«xîMƒ$?|;W¤‡]¸R:A×^е þ/ÞÏ=#¶¹c|_=º‰uôðÎ󰦖σï—D~—â_¶X¾à ¨ %ž°P`gþZäåö2C¯0­µÀlÄqh»=93 ÙV ;­l´‚wv} YE["ih¿AÑ;!2ô¸öH"‘(RKé¥ǹ/’fÒÚMGS‰Ôn¶›ð…ýŽØGK &ªÂsS½Úƒy!ºúHÌᘉ‚jD‚›Cˆá§/Ü@ MuÄ^ÐÂÏ- Óaà1 b ”׊÷ ¿ïõ€/Áj±nb/ÛûÊ@ {íﶇ·]Ö’Ú"رK|Æ>ÂB@Zdu€ú°«T ‘=D¾Ûþ]µáçKT„­VªÜR ëBfbðó3«g»c(šªžíÐvg})*$í—qÀÿ€·€ëç‰D"Il:_ÏĹ/’fÒÚ„4 ‘½Ö²ûgYhÔWA-å¡<ƒ1É–<-Z &<]æážr‹RÃØ ,Bd| ±T8[´ŠÙ±³ÁÙîú(òh´wÜ¢Au¼çq­×”Àø5¢Cîz/'!Š]ú~Å6Û#û{=‹÷èžà‹õ¾¤}b[eTÒÖ]¡Ü8gIœ°hÖ ÷²XéAîREÀö­º™þ^•’©¡1<ÉDsÞ:%ñCŸêá²BÇvõµ–µQ•·”Ø®Oβîbwu4™î4²ÒJ!‘œ.0*âÞãq=w»È¸]g”»’Š8_¿NNEX-‚ð;d‹ü‡îGû?°›ý MIû%ø¢Èåº8÷EÒLڋʼn­€UövSqÇWDsr¾vº¾ˆ€ÀíÀÇ•–5dJ‰>|fPKº?Oghrx/°,x©ÜÃ¥; }ÂJQ€°6Ø"ÁY¢–Øõ(œÁÙÎcÂ)gòŽÀ` 7ÂÏÚD]ÛÛ9„®¬!²Ï´Ç{bK᜼«*€ˆ¶RD±BðÒØmÔžü3Ÿ—wÏö…}÷Ф=_DÝV‹hÍí"e"£:s®gK€3ƒúÑ+¶©îËÉfÄKrpP`Kî*ÖøO•n$ìŒOÎX »F…ÓåÉ]9ÛKáþÿep¶DÒ÷ µ-(<ˆóEz§!,ÃÒò;Ò»ôÀŸ’N¨®†P}Šr 5Å^œn’@¯¯£!XM°¤PmM1"¦mðð3‘cÄ=9#]£Ú'¶åJ ‹¤Å¯Ö €Û|ëq5o¸ùø1 [a?&;^;Ÿ{4DÞôaç¥z}ÿÌ Ñ˼L¶4á9¯w*=ÜZj±)d Yalà¶J¸-Îx »E,Q)c)$’n+…Ýì몎ˆq¸8'»ç¡ùýN<“žGŸBV·^øR2Ð|~ =„©ëòN•àXº¡7 VSòÓ6/œÉ¦yÓ)/ø±Q°ð`‘©7wœšœ´i_ >®>‰s_$ÍD^®#D» jDLµBTØ#ÉÑ1šý¾èÝMS;ü#.Ê0#_ÉE!¸¹Xã*Ý0EåÎï€ÝD¬nw§}e|rŠ § Åî$’Æ8ýåÝ“5 ®§׿ö9¼÷ðKþB¿ñg’’—…>“,ûl’w¨6ƒmtRÂwÙ`q9ç~Æ·¯<@ñ†•Û€#F]ø#nS™]¯ý0!8ÿ |ç¾HšI{ ÞÞ_b¹HE«wá®Ük†¥(ØmZêÇ5VÞ¦M•d’&ÿ‹3ª.Ø©2«Ö(³DµÝïnNMUÐvi»«g»E…;@[šk%{‚ÆiùUq·kþÀmCÏ¿&礼H±Ç ù:X¦CT@l'UÙ®Yv3Á2À— ã Aô›pÞä”ô¢µß3ê{ ®×UDbplœÏ-$m™NÀï®r’Bk÷Æ"bKˆv{s(m1áî¦ §Û,([Þ`f\­&wQø-93÷kQ Ò€[Š4n*µØaXë€/ŰœŸœ´Ý•´£¥“uf}’ñIlœ~lQa[z-àà©´üÎçN¼çeÔŸþ‚/9#$œ’ö…e©ƒ/9@£Ž¥Ãá£ØµzÉaµåÅcæ""Ç”yÇlÛtÎ>BÔš‘$RXDÇjâ¹[dDîªÝÎG_·”š–ïýj+·D×”I&Éòßøe(0¿Fá¢B©5zUHÄRØÚõĶRÎ íh™Ÿ¢ 9’HÑ,ÂR¡Ýdzz:îôǦÒë¸ñBP´­Ï㪫)8y†ê0ÚðÅ@‰¸1µ–¦¹½{Òóè“)Z»¼c厂ÑqQLãqŠ=ñç|-i{tÎAÄYlŠs_$ÍDªþ¦qv;MûÎÙ8/»›Š»°›«Ñ3ØçI(ÏdBjø:)/—ûFzî/Óx¸Â Ò´~"ÒSêÄ®ždïmÛåÉFØnq(ÿ!‰DMTØqi*<›Óû°c'=ú_:~8z}ËõDi(eí;/²»ÞÄ2"ÊÅ´T²ú¢÷ÑãÄ`ö×àñÂÖ^¤`cƒ¯º†oµ¼˜á9µåKLx|P¹£Ïn¾ÍßÌ\\Žê6‰\›£Õ”’´-Fï— ²‡I9G¾oÜ-÷lµÛzáv‡Še½°)¶Vò{ÕJŽjyê·ðÉ2AM£ÀòZ¸¤PãåJ=Xo±a©¨ z±;ÛõÉGa w,…-0d±;‰dß8] 4G³'ZîLëØõw§?6•N¡7´`OPB…,¸ö|–ÍžKù¶ ­ýžÂ5K(˜5õŸ½ÆŽ]õô8f<MÙÿ“95ú¼Ò¸ŸŠ¼IÂb¢ù· 5¼L /s  pÿEôÍù{U/xýáþhbÍj8lÚãW(žÿ k®D €v¦"HïÜ™‰÷¾LFמc€Û‰¤o·-avÕvgÁEIÛÁ>Ò~¥]Q¤Å¢y¸ý;aïê ±,ÎÙoØÛŒk"‚ÕŠ¶êfú´j%#€Æ‘IæÁ°@' ln€+ 5¯Ðëk-–!²>•±RiSá¶TIJR¸kSH+…D²ob‰  ükìÕw¥ 9oòA( ¬å)B‡Ê¨3N"9=Zéýz‘,cÅÌ/Èþ{º÷ÑXùê³×åÓ¡{Žpcª-áÇ©S°=…Ž}:¡*P²d*‹_}–µŸþ—ó¿¢VÉ#§Gg4 vM‹íEUtê—Ϫ7žcÝôÙ¶r=I#5+)ê•ãÁÎEŸ²øåÇXûé»,^Däôè‚Y¹‰Å¯f|îˆÉÅÐrï,@ó¢zA _?<@uu& Š? ½x ¼…ÀE}ø›~X&µ;XùÌ­l`Øo‡QòÅ‹üïº?LéF§þÝ©øa+ß“Â+^gâ]'£z}Tm[Í´?_@RfW|žZʶncý—?p޻ϓáowáñÁæî烛oE÷ç‘Ù)—šï±ìç8þ©Å 鵉9ßÌSºÐcð¡XP¿‹ùÜHî™ÿ Ï±#¨ýy>sÿ1_j* A“ŽC'sÄ =øæÁ«h𤪠‘ÖeKêá—qáç?ñç¹ë˜üê;dz×ðÍ£wÔÁHêéwé\öÅz.ÿ|9ƒ‡fS2o»NœòÄL®Á€‹¹øƒèÞ;ã ŠDË‚1WÞNþ€¡‡7q‰ò9î¤KTÛÃ>s¤°H@¤Åâ×á|Ú¾ø±Ü ìæ,´æ¶b8-&bF¾¬Ø4¼®Ä2§Vó=”§ÓËOÛö†g§îVù[)l é%+Åfךpgyr¸sº<Ùécm!á.vͼ.‘HbcäÜÖŠ>À¥#.½‘Nƒ·\¨&P5/µ›¾bá¿WâAÁÄ$T$7€£ÿïCôNÝšý!«`²ð¹a8Žœ~ƒ˜ôÞz,Í‹0C!¼é½>ù"R’ÀòdÒëÈXüÉ“TW¡ôNÛ34R4¨üy)»·UÑåÜ›é=8ŸP5d~ 'Üý ÕôC1ª÷#³”…nùyÑ >zzÔ[&z—Ã.¸‰Ñ§ǣú5s)¯ÒxüqhÁÍlýÎÀŸÙ‹œžÝùiërÊ‹P%ýF\2™$Ú‰¾ãNdÅë©­ B®*â?j5T~%PÓ€”¼ Æ\y;ÿõ¼ñ¦:ÌNòÛè>k{HûÚ"…E"…ů'Z½hÙ¡ÜY¢œ"#š«”ŽHKk"²mŸV£YV¯v¼?WáÜt£í^F(Öá–bW«tS·X ,EÔÿÎô±n7(·U™JÖù¸… D"išX•µmañǜއutæ1¶ T³¡Ž´ã/猿^…fY`iY$çv'=?U¸Ù8æ¸-E >[˜ô<ëvŽÜXÌòæÃÂ›Ñ‘ŽƒŽ¥ï%w0ºk–iÈJ#%Û‹AÏŠâ;Å¥jw•´ÀÓ£#Š!fäu’èsÆ%¨¨\ýùÞ?f¯"&LJr*FC¤ˆe%‘’œeˆì,6¼p›ßÍÁ²,«†êJ´ü#i¨ i`õHj’Ä+ àKLËEôQ±+¢Ç½úp}ŽŸ”ôÃÌ÷¯@TæÞMÄ%Êien«wÅö†t…J`¤°808/dÑf¿c §˜9£Y5~J6ëæˆ?ìbàÌ ×sonˆ^ÚÜeô³J•[Jaeƒ^p Û@ãøˆhbÂ)(Ü¢ÂK!­É/#Z];h[*ªzòð ¯#µc6z]|:iY&ìnt8tš[&!P¼`™&– –íu¤€b„ªÅsË-õ޹ë}F\»¢óØôéT~˜ÿ.³·ÖÐóÈQ=ªHCÛÈ©8Æ¥ÄO’WŒ˜ªö8ïxÐ)˜;‹:½:ÛŸöìÙËŠa¢îmšX–{ÌebY%g™ 1äÎxh.¡…âK¯GWSéØ1™ÃD )"ã•ÖX­­‡¯Â¨Ë¦°yÁ£‚ÕiHm«…s’HŠ‹¶t…J`dŒÅÅ]«Âa»ä8³Ùqî´´ÎÔ´vr0Ç„/V†ÊŽÛªòq¥Ò6duÖ.¥ïµµõ¶\ƒ,‰9DV`Qn.ö0·N¯ë‚ÂÎøTGt+…»z¶KMHÈ4²ɯÇm±°¶»“Ÿ{IiIè¡xuÏÂ2t ³‰ˆ|Ù]Iê˦¹3ý^?Òw³è…Gi’MEóâÍõQüÚ=|ž®0èØ#©/øŽµ[6è~2)>J Óh<ö±,Ë5óoéÜk,ýO9…ï?Ïg·¤ÑïÈÞlþèJ‚)Œ¾ôtR2óÈïìaÝÔ;˜™m‘ãÛÁòן d"\“,˽}ËÂ4tLS,3uèxÌÙ ô «ß¸šOR¶Ñ­_>Û>y†U³2àªkIIS×U'›·!˲@ñ£fäb}õ žȰsÎ#=çàã[¨…Áç^γÞl†B£ˆÄZèDŽEi±hØ®Pqr¦”ü¤°hÜ5ñÌ9KîѺCì-2  ˜²¬‚‡+ŒÑsj=éäZŒK5#ßÒÚQ Þ€‡Ë<Ü_aRi›ñ$»¿ÕGáÔv[%¢¹>¹3=ÉX ‰ä—ãœvZ+ìûȤÌn½sûŽÿf<Ï0ÅGj÷þÐ1?æ¼µe‚•Öcþò ú?ï`Ó»ÿb£âãÐsnF-ýŽâ¼LLCá°«ßf÷ŽËXùÙì˜áì¯%¹û¹LúÇÝd§)ìÌíBv·¤=ÙŸ, ¼iÈíq(É©þÆ^Eèj*Gßò øndåô—Ù6KGËìÆQwNeôIƒ1¼pÔ=OPù¯GØðÖ¿°Ôtºy=6NÅÌÊFµ@MÍ"»Û`RÒ2ölWIJ&»×¤eå%ܸ”Ì^Œ{è¼ÜÂú·ïfƒ¥áMÊfð osü¥g „Àß¡;™=»w.K´¤ìndR‰×ïÅòè;ñF6®’ÕSߤDŽÓÉÌ?8éfÝ pÈÈãè6z|ÒæyÓ'!’yÔ²·K”3¼$1‘®P Œ4TÇ£}SV‰Ü”íÀGà·$GKFX+Žçö{> 8*E¡×ÍÙÍÒ ´vë…këàÆÏkô:D@Þ „Û—3–ÂÙ‚®åÑ2>E‹¥V ‰ä×ã>"׫¢nÅ¿‡þáê!'ÿó)BqŠ­°±ã÷. j`ÖÖÜ]‰êO%™"F¤á`eE“ªí› V×âñ§‘ѵ^Ÿ° öDmô=ŠøþX³úŠ*jkKv YøÒò¤k˜zø;50ƒuÔí®@IJ#•‚bEú´gû®«™.çì‹¢¢TíÚB¨Ù d°tǶöÑE£¶ ßY¦üà£ùaåÿÞæÓ›Î¯Î6¹'Ø÷ûù#9@œ< Œ¶Æ¹/’f"+o¢òq.wÇf¸Ÿ»+w»k-Ô›BPûU­™7/èñ ö[tŠï= :Џò?Y¦qI¡Åªs ¤½Žˆp¨i¢Ù±)n7(gæ'·ûÈ›ŒDòkˆU³Â~>ÒãõM>~Ê#ž´N‡tW™_Šeš_j*^¿/2xß³X–‚?3‹Ô¼|’33ö¦7Üô{–Þ”Tü©ih^5û`‚¢i±ûkûÑ–™`¡âÏÈ"9+ Íçe¯˜ï}õßÅ«áÑZÁpA¤ô\~ürZR}ey "[ í‹ï¾J—#€“€#â&% „ Þ>¸8] œ.Oö Ø]TÏì b¶ÝßÓ¾®36¸]å_źíqÚP`M=LÚ¦ñ—½¾Ì´Ÿ"êuÔù­Î‚Îçvpv Ñã+¢¹@Ù7‰DòËiª ž8®ãÀáÞNG4$'Nk@¬U a¡8¿Í2›øÞýèS³¾Ë}ÿÅÛ³\_~ ¦™‡t¢ûèñGYDŽEwÁ<ÛC@’xØÿŒ±H@¤°8ø8­îÂyNa´-Zæ(gué:`ðy©a.ºµL¯;c»—uuÄ÷òþîWÊUNÜêazPß|‚H%[‰ø­ûúmN‹…Óª©˜ éú$‘8Ü¡ìŒ<éÀQ}'œ‰7Y“gœ¤E±Lè7áLEéô¤q§¨$.²ŽE#ƒ·ãC¬¬Qös{×¼pt‡\Ëì–ÞÆ7ÀÖO‚¡±ßmW;ÿ=[áò C\næ_­ pG‰Æ«Uº,GÄST½Ø]´êÙîíhŸ¢Õ¦H$¿ÅÕ<ަ‡{);-ç‡%-Ži@§Á£IÉï”V]¸ãpD–DÆ çñ*}m‘‹DªúøâNEë,–g[0œu/Ü–‹J» ¹­ÀG;uséEFÃy;5 8x7>¬T¿MåÕ*½ ˜Ì*ˆX)œ¿%ZM û77åödêɺIËà´V8O(À¨Ìn½ýyýŽÀˆ[ŠÙŒÒ¸àµêÍ{õ_ú?´†Ð…}ÒŠ*CXøÓ3è>zÀXDg= {PÚJz,ùØcS),),âO¬ô³NaQOãØ‹h®Qn‘aŠÛÌönµ^xÜV•7*Âw²–ºì*P¢ÃŸwjœ»Ë´~ ™«´íT±n×§j¢ $g&(·ë“3û‡3P["‘8ÜÕ¶¸4`@ÇÃŽ$97#a‚¶÷Éÿgï¼Ã£¨ó?þš¶%›N ¡„" RÅ‚ *Š…³!zzçYÏógogÇÞŮРEŽzo „ ¤ÞwwÊïÙM6KÐ#’À¼žgžMvgg¾³;;óy?M3Ð|5U%Á ãçW˜ÿÁTãØÜ4Ô¢ Ö½ò4;ÖïFlîwbMG?¬û÷qÂÅ!xú9=HêæYX9-ÿ¤…%,Z Íýrv2ܱ;8±;°kwpþE}¹ ÁÆù.à—½ª¾å¦\M›£ï+—xL`a9\p@æýRµÄcð°Èçð\Šà.Úþ×Ê¥ð‹Š`/…údaÑ4þÜ ¿¸1K]·oÛopó”õGš·®çuA‚Œ¯`泩¨Ð‘Dƒ‚ÕSX3s)šF7ãOA­pë>xŠÍëw"{-Žf¾½žmþÑc=â:ÈZkÞº—…ï¿…ÛøƒÇÞTЪSwl®°p æ¨ýçd°¨h#¶øcX òZ0–°h^4Ö<ÏK­çÂ?ëï÷ZÔ'.üÞ‹ÀШBÌÒ®s>,õ“)1³L86NcÊuøwžÌec½[MÅôRl kpèV}^–À‹ànÚ¢Â*-haÑôz+Å…Ä ¢Ø¶mßAÍ£”ŠÓ [’í¾˜dGÝÕ$›¹Žè3”•j¼ŠJÖ}Cê’4ED”D)Q–ÍíJ¾D8;ˆGce_bŠ {ÀPEÁl``© ’¹Ž€¹m›£ÖDsl’â[G0·)Éæ±H’ùœì2þ}ï…ÚmzH$»ï³²ùÆêÛ†è (²92–Ne÷ÊÅNš™®«™Ø…Ðø¦×êV, γ°hYøYÍqºÂâXÉÛÍóbèÿQÕ—à\·»¾ÎÝZÀkU“R‚-mèÕÙt~¸ZˆV‰ø³MõX[÷çI,«V+0k‹oÁê–†õ ‡à®ÚÁý(êKжšÝYXüõçWV…JOHÃÚ$ÿr¤åØúÃdì=Îúˆ¬-Û1l.Úœv=/8³FH®›ÁŽ…Ë)-(ÅID‡Aô¼|4Q‘³¾`wZ5”î`Íïrê˜% Aö’¹tû–ΣºJǕ؋ޣo%º•³^Q%ÉP´m9[gþDyiJx­‡]CA}êõepg§±fêWåæ!8#‰îw!½ÏÃîýkY³h=­O=ƒÜåÓ(ÌÎÙLŸÑ7âÉZFÚâŸU‘Èäô½âFv@£²MßN"'mšä$4y0½/MD„Y#dÏ´¯(#„Ø8'i‹P]å%$îz^}31Qn6}ú%•xËRYóá‡ô¹ú"BÅãú}ë„Ƶ"þ¼À‘#HP±{13ÿ1†Œ%Duîå¹løâ5ö]þ—¾ô0žkYöâ]`ArEã²8À–©¯£W¢D' VäPZô&¥n…ónƒQ¶…_źß6Ö>… ¿™Hê¬Û¸bâ‡Ä·380y"Ë×lDQ"qÄÇ!UPœÿ.{7`ôÄñdÌú‰òâ*tq;gK‡‹ÿFt˜Ýì‘tDˆïq*{—ýÚ³ó{.u…Eð¹kÝ7Z ¦-`ѱB¡š7õuä4º“º»ª¶ØtΟ@½øy­[K»$þ“+Så÷—4†ªàâ,™‡ Ôê2ÝXÌÁì£ÑX_ŠúúSÔ×E»¡fwÖÍÁÂâ¯#¸1^pžEbTR2Š]:þ QB¶ŽXF¾»Š ¶së”E$%ÛØðîÓdð¿i*…¸äóõÜüÓïLX”ÊÐs{’¿kž=9›!ÃZCÂÅ\ûãBzl×£bTUÒëî¹cÞÆÏÙDß~Ñä,I¡¤Ô[Ç!ˆ çí&åñ‡©° åÚ©;¸{ñvÆÏH¡kw;«Þx¼|Qðß~ÍÃæ§ÿEFjç½¾š»ogÂüí 5€ÝÓ_dϦl$» E†°ž2vÚ6&ÌÙÄ 3O¡*ÿ ñc^güoiÜ6e6­CöoNEaßûO°î·mô›0•;íäïKvsÉ=c9´æ 6Ïž‡a“±…8=nú„;æ§rçŒåtêÖŽC{¦Sæm˨© IHnCxß ¹áû%´·£6ƒC‡˜¤î1@uC¡‚†EËBÁ´,Z –°hÔW–6И{,0êK”,çZéa˜W®K^,V+.> ³¦Šú/Ç‚¹Ów eFf ,®R³0›Ý­£6G¢±fw‰Š†B ¬2²ÇàªPþÅÄG&t4ùf‚¦Cä è>´Z„u;WÜ„^–FöÞB:Žyƒ[¦Í¦u¸‡} –’:ï ³Ê%›9­m€ìðå é*¢2˜þ×Å!q$? ²¨*«®s­D(Þ·’û ˆ<‚ðB²Ön£ÜJÂiý0<ÛÉÉ8„èKVµp6n!´ï´íj'gÃ6(%áôá@1Ò÷ªÇE÷Kï§M‡pW+¢N;œÑœ~ã-8e°·éD´Ã¬+U—²qÕ „VÉ$ éDIÚv²vì%nÀ™Øí*Ù[Q«Áðºq$ÎéwÆ.œÐ®}úa”TRYâED3)C0? ±™\… ¢:uh„rx”•_ÑrQ0틈 Õrð‹ Óàn®u…Gp¾EàßÞ G§ïµµÀÅUê ²„¤Ç¢Eþ¥!û°HsÃC‡$fT¨ÌFw0B`Çð†ÂŸ‚CŸü%uƒs@üù#V.……Åñ£¾0(ÿ¬0@¬(É®°øvèÍ`Û €+©=’æ«ò`€ÓÕ ²(âÍÜÂêWŸ"s÷Ü…‡p{UDÝúÕlÃÐ}o4|>Cƒ}pÙA×AЇ ÐêqÕèj%:ppÖ3|—ò!‚a V „µ¢ªÂÐ*ÀæõV¢Û”oþ–o]Œˆ€!îB§‹ª*/:†+Wë65å+tMA@ut MÅðGP½¨Š‚Q¸•y÷èÏ÷–¢ !¸½"šºf…# tdC4óQ # ÆË¨ù8š†añí°¹ÂlžŠ²êz*{YX¡P-ËcÑ‚±„EË#0P)0ÿÂÿàRŸÀh¨k·?÷"˜Y¤ýÌ×ί”oÆ©tµÁ×%æd©ZfoŒýÔŠƒÆÄD%µ^•àíÀ^ùµw3 ‹ã‰ßS˜c!QŠ+ÔæŒŒiVµ[  Ú£×øãÀ­ºÑAÍcÙ¿ï`ÛÞ ºÞú§3œÈÄvìya$K¶72Aj€b«ëâoäÊä7ÿ€!çDsk¾¦{:^·FdDÔÜôÀ7 ¹+ˆ½ø!.ºcxÌðrQOE®äÁè»wa(8ê¹m×7Áܦ’4œ‹^|0Ù‹®H²€§²%¶² Rwnªe!Û„Æ%P˜±3–Ã;Â[aP-VŽE‹Å …j™z*‚“·ƒ;v×—{Q_¨’ÿ¹*̰©ÀO¿Vªû/È™)1.GS³T}ð°›Ú~õõ¤üßöäÉÚâ"8—ÂÇÀЧúB¡Â»Óf k^ñD rÝ·ƒ# ŒÒlvýþjh<áa^äbr—<ôwº íC¨RFÚ¶T‡¯B†®("ÙDDÑg›å18#“ˆ¡h}½ú’4ôT:ôëKî‚·øí¥ÿ£´JEð%fèHíhê¤dݤֽè8ôT: :ïî_˜ÿü¿ÈÉÊA%ÓJÖ|Y ÃABT4ÞìƒxôXÏìGÒ°SqTneÉóÿ$mí:Dåhæ DD[3écáC²Ù‰Ž3y;8¯ÂªÕr‘±„E‹ÅòX´\ï.Æx`å¨úšîÕç©üÛ Ø}Ë.À³W5®Û«jÀrßsþ$òúº‚6µ  +ôÉ¢ù,0üRa²3Dv„G5Äm¢å[¦0óþXzž ‡–}͆ÅÙ´»æQ:öèÈæX'Ùëg°lbb" Òýœô]Yncóó~óHäÈ8ÈüUï½Ëé7܈ Z]eaèæe,øØ B» aàµgñÛ÷Ï0íQ/=ÏìEÑŠX9y±ç~CTˆíw —>#Ëßyô)ßÒÿú‘$÷/–=Á®é_ÐþìËiÛ‘Èâ6µÍê °µjKtûž(ö Û¨ºÆ)|Çö0kf¼ÍosEdÑFâ˜g¹è_â¡Z %ªC¢â#Ñ4èxýË\ª‡±øÓÏXøätDC$lðŒ¼ÿbc$*ö…Õ©+ŽP;†n^4ÑíˆêØ §âK{œDtè·u † ‘Cî`ô›¿½ö2+_ú;2ö¤á\ôÜévJkÔj/!m’ˆ¤=¢àka€3¶=­:y±ÛT)’~׎#ûíØùã;tºè&"]Žã^n@Tj<‘ÔÍ«öRXy- ;Vòv‹ÅRò'Á‰–1Ñ2µµç̮÷؀¿ý‹tÆŸ{}Ïù{c{,ª©› í x Ìó°šÝYX4DÌk… óÚàļN„úž»)®ç©Æý¸¾¦šÒñD@Íú﯆zñnyôŠd#:# ‹F0Lo‚(ƒZ^Ly~!¢3ŠÐØ($Ü¥Åè‚ÅiGA¯ö é Ùm"‡£PÏs/ûÞS‘·w…[x!1á5ã€Ú°"R´$Aua.…%È®hBbceS,ù×?Òêl³§†·¬˜²¼<%ŒØÖØšÚð6‚ŸDЪª0‘ìÊQ~#MŒ`뢗dͤ×VSšë½ ¬6è÷Œ[4>Å,!|õñˆÅÇòXœX&<†Eçdö^ ¦ °ùÁ æÅºØ·M¿÷!X\&g»©ßKa5»³°hWÕ Ž[W$QF’AkFõ[  w5‚ÓId‡NæÿZ€[TÑId‡Hó5Ý,Q«¸"k߯ƒ`·¡øþ§¾¾¡Gè%ê÷*„Ä%âò ‚Àqø÷¸=M[d<ö¨øšqøEÅaë70†àu4¯y¼QÍãÓujEECÇôœ¡ƒèp6 ˆ â¯ve£á0(+ªe!`~Ÿ–Ç¢…b ‹†r/ʹ®å¡v–ÌY (ñ-Â"Ð#áŸj,);0Ÿ"x¬͇@c¬!¡ˆ6¥Y%ó"ˆvÁe7C’´Ö«çµ£1Úÿ ~ÑTëÝFù,þÀ6šÕÛ0ÛkÈv;˜÷¬# +ªe bÚÇ{ KXœ¸ç^>ÜÍÛ/2ü†ynè˜BAÇ¥˜gÿºž‹ú’³ƒEEpy\ ‹æMCâÂôX(ͧ3ž¡w:Wþ°Ãƒn¥~žøˆ É60…E}¢"ðüµh˜Ç{ KXœøøû^W^ª/±ÛŸáÁ<74ßß~aQ°­àªRž€ÿ=!BÆ }²°hyg¡&ºQ'¦æøcˆ !q æìºu¥9ñ1@×4¨m˜¸m‰Š–‰ˆ),ª÷@,þ–°89¬ºä7ôËÒúýbAö½ؼÎ/,Ä uë«4ÜèÎòTXXœX€ªzª›Ô€e³Iœî>ú’q´B•¨ø.vG™'"H˜y%ž£/‡%]ÿ DÅLî6tP›Òs„¤ô£Ý†¤˜¡Jº”Óq 1? 7˜÷KLœX9-«AÞÉC —Âï©ô:ÔWá©"`ý2Ì| ÿ˜¬í_JÔ¶<-úbÔÿÖtM7c÷›ÀŒ“d88ÿK~{yzÓܬDÝËî)¯°|Ê\´£¸B "”¬ŸÆâw>¤´Ü‹pƒ ÐUá¸І—}Ó_çûÎá—w¿A3š°Qf iP"‡ØôÞ¿øæú³YõÛF¤¦˜Â|ù(Þ:Â"hšGKh´,E Ç'õU‰ lŽçOÀö |ë•R·œlp «{¶…ʼnI}CT]Sѽz“Xm’`ppÓL6~ó# `LûÊ66A؆h¥ìýæa–}÷+ªqä¢ AÉæHyûCJKÔÃÇ´ÑE¿Ì̇ïd_ZRP¥Ö:ãihŸ TîXȼ ck:^C8ü¢ZÏ8ŽôYÔ÷>Q‚½ß>ÌÌg^£¼\;òØê¿ä€œŸßeÞë9”]‚§ mzC¯» j›©5$.,Zþ KX´P,aqrÜ8/Ø{!ùÖ-§ñ¦wõ%j[¢ÂÂâÄÂo¨‰€[­®ÆSQÚdwA’‘dÙa½è Ù@–ê®'ÙA–Mï€(â¬kð È604Pì ú oÉ."ŠJíô¶l¾_±~Xº ñ?ËMß~E|+»¹-‡ö#;Ì}о}id{÷/açœ(*°9Ì1úǃf æ{Å>CQQÖñ=ïÿš«ï‹"ú>ÅÜNà±Jvó¢mh†¹Ÿ€üzQö׿{Ÿ}ŸGAv@éúoH]œ‚*I {s»‚n–¯•læ6Ã|Þ0ªl1œÿÎRF\|J“„B €®jTBÝ B–‡¢e#aöÍ©<Þ±øsX9'7þÄnÿ£ôœN­°(Ã~Œ#,'6ª»Rw——ˆÎèÈ&úÑ jä-û… K¥¼ÌCH‡¾ô}±ñ!hHºÊù_³{Õf*KÊC£‰í1’ž‹Ífñîœílþayûsìá„÷;Ÿ~—^N˜\×þíP¾'…í³Q;€—\€ÃÐ(Noq&S‹‰HîYëX³p­ûŸAñŠéäÈFtF“0| ÝÏìKÁÚylX¸PÙ=åÜI¯¡©>¸“õS¾ 7ã †ì ¼û9ô½âj""¤:eaªö¬aÃä¯ñ%k&³©c(Ýz·cÛÔºœ…±{¶æÐ{üc$$†‘µàv,\FU…%*ÄsÇÑ}p7ŠÖþÌÎ=I]£ÙûÛ JJ+p´J$ù²Ûi×)н³¿`×Î*(Oeí‡osʵãˆo^wL"P•ÇÖï?aÿ–4TMĕЗîWÜBB—rçËæ¥¿cèn2¦OÄ)ÞF‡.mÑ› ;»æõPU˜f Å‰€ÙŒ³êH+Z4O,aaÜLêÎö(˜Â"¸¦tp/Š&¸mXXX4S B­ªòºK‹í‚Ø¡Iv"J2ž’L»{>ö˜v8d7…Ó>a۔匞ü%íÚÚØóõ L{úI´ˆv´ë‘DaÚV6|1‘ì‚I\8a,Þ]K™qËUìÉ­ *©Fe.ÅSÞ%kÛ{\ó7š;2Ì™û²Í³øõŸW³;;ŽA¯MÅn¯› .HP˜ò &n¤íˆ+‰8°–¥/N@°»Pìá„D…R²oë~š‚÷£¥Äì_Lú¶=@5Ù+&ÚùBº%0óºKØu œÖ§ÂY–ÇÖŸ>"mÞ\>q 1qrMSŸÍÍ?, CG™U¯?IvŽìT.{”[ÝÍøùÛzao*·¬#7WãŒ'græYí Í…\3u=Ïh‡P›G¶AÖÌY³jnû„¿ý´’køë'¾\´å_&ñÁï¸lü­¸Â¸rj:ç^zZÓT…@ó¸©ÈË(æðܾ ¾æ-+ªEc ‹`‚/ÊNj½]¸-,,N HsWW—Ên0?àÅÐ5ÄÐ.Œ¸ëÂm J.zÜ~7q­dŠ·oÁþÿÌ “>ÂQšÁžß²kÉ\*ŠU›*rؽu ¡}ÎâŒË‡#xðN {ä=νhGÛü&Óý¹2½Ÿø–á#ºÖ1¨ aôûoÚwFÕ ÝÐË u9P³2Ð ì"¢ @e6é›·Úç<Žà/!ô¹ñ^¢B ¤mÁS}xB´ ‰æÍZj®ºªZ÷ÇÙcÏ#<*„⌕—Ëôÿ ñv<ÞãLúŽ…^¾™œ]H’ ¶œ~ë? u®DÐíüË@+À]T„ÈN!8MAÑë&k[ ZX2gÞ|. <ÕÐáÂq$vO¢0ke É"‚`˜‰,Mt—€Êâ|* ót °Õ¬{TËCÀ–Ç¢…b…BY 'fâ¶……ÅÉGàBð„B%PRzp_L` þ±Ý¹¢ô&*¼¾ÊPRHÎÈVxDI†¢‹Xòù{äí?€» /"¢d'I¨¨UU8Ú¸Ìäm â$a¨%…èU%ìAªÂ³m îKO9ºÞ††‡«mÛZŸ. ‚$ã2|Ÿ˜a¨åx+ ¢; ÁŠi: †µ¡µbçÚÀý1j¿ ÃP°;bMOQЕ„N‰5=9t¢"Q MÓ̆mzâŠpÖ„7J‚oÛ†á{c=¯{<¸‹ ?ƒ¨X¥f?šF¼+†Œ"¡æxk7Ö4yÔ‚E{ÓÀ0ü¥ÐÁòVœXÉÛ-Ëcaq$\˜‰Û'Á¹T†›äïß·š&«Ã£ÙT¹ʧzËÐ**…ªƒ;˜wÿìÉôÒí®×¹~zÿX‘Ω]hªŽ`˜‘šºfÔ`ƒ²õ)l>—òr !º—~:n}ÂIû䶦duïÁ_¯†úíXA/oš†ï˜´Êbн*‚,ýÏÑÀ0Ô #¾ˆêêê‡`@EE!ª¢¿”–*˜ßœ\¨ž€ŽWÍÃ㥦ǵ譢Ä]Ž. MŒ…ûRЍü² Š´|ü z­y-KXX4†ˆ%,,,NFêóTÔ y0÷VÔªŠ&iØ&ŠZÙF¶.Y⻲–Ï$'§ çÀÓ 0Õ ÍÕOpɽcéÐ?£h7ééÕ貈½MG\ññ”lMå`F)öpPŒ–ÿ÷V¦=tEå"B—‹è:â\κûÿpJY¬˜x/%å¾·GC#f«¡›ÝýQÆ—€+6œÜMsÙ¿«› ¡ppîGd—{iÝ¥J@ª?‚+±Ùlš5]» ô’}¤Íû"z—Ô -@`5>^É&×=~$§‹ÐÄd<ù+H[•f~'aPºe{¶¥Ö®¡‘`èMoÇ "ä§nSXTP÷­¯Ü¹%.ZNjó:-Z V(”EcȘÂ"ïxä/ƪ޲± ˆcG}Õß·½ù»¶RUR€-Ôuìwn U²þÅëþ“P²YÿÎKmû3øê¡(úÂB!Þ‡,‰*®ç±áë·(vë°n[—žÏ®'óÿ2ï±[)ºòB*¶Ìfãš´¿âyâ[‡°Ó«bhÔ*huöœ9z¿ý0ƒÅßMç’;.¯3»oJ7c|ëšf޳vÐ蚊®ëÈá­݇Hýâ9ÂìÿbèõcØ÷È+Ìý÷8Êo¼¡h'«Þ%r=Î?Y7›šÿó0ŸÁðíÀÐ ¶ßôèÇöIÿd¶˜C×~Ù?ë5¶l(!éö‡i/²[SkÇx<¨è¾mK‘±µ‚Õï¿Å€±·›VS¥ÊcHt?ÿj6|6•ߟº=ón¢Â¼lýüÿ(©jÍÐëÿN¨š¦£7Iƶ‰ ‚»ÔKAÆN€lÌ ²@ ‡‹`‹–“ÚþX-KXX4†_X”ïcŽÐ/÷ˆë[4?3ê3,,CãÈ4æ±ðW†;ä­ª,ËÛ±1,*1í˜Ö‡pD¶%vÈtlíaËGâÕº cÔã_Ð>ÞaœÆÅO=ʼ×?%åJ8ÉW<Æ)­2XýÍ÷lÿqW½ýW–,øhË^š‹h Ûµ/pÖƒb¼„& !Ro‹袃¾wõ#bö±Q0ËN;1oú.ÌkCÐxèŒñv9÷ß/˜¹ÇÁŸ›•‡²qWk8cÛâ kJ˜Š2¸‹ò¨,®D‰ˆÅÕÊ… Aui1¢Šì‘dðSUVމ3*Ñ0»FûC¸jšàù„ý9õ)pýz’¼ëlSAÕðV{DÉìR]}(ò¢ ÁNXB'! »*‚¶¸ŸàqƒÙPOðx)9¸Ûƒ-¼ m¢Ì¤uýð±×{<"èÕ4$»­Þ7Qµ¤˜Ò¼\4ÍÀßW”]­Í×>l?ÇÅ;çÌdÚ½——ºþ,ŽÙË¢ 3ߢ3ù×M­‘ªaýÞ[çßg©Çy,ËcaÑ ¦ñÐR= ‰ÿ"¬SŸ!*=Z4/¿;‚þö™t5ßucyV™† þÜü3Á~#­8ppÃò.ž*Q­1é+NdÎØ6„¾H¤€¹L][xöHߺ^s}{X¤¹ Ã4Ø¥ÐHÂüÏiñ2Aã­iˆ×Я>(M¡¾ã­óœ†(¡¸¤¯†æ[t­ZÅÕì³1QQï8ëÛ¯ †¨Þ¡Ka¯ÎûÖ—b|<:vJûн º"‰îYÿøœÊñ?¡yáàÆºž P{núû,Y“-¿Çâ¿‹æŠ%,,CB1›µ4=bÀc°˜1gb€x uZTY¢¢…á7ª€| 3GÈí{M¦®±ì7#ƒƒx,#äp!æ7Üô ¿w¤ïV¾CŒïÑëˆòŸŒÞð—b¦X~®îM§ii IDAT7ƒ~4Ô'`šj8†ö?nûh„MUf¸1¼•Õìù}6ÀVL#´¾Æ­Á¢Ø¢eŠ%,Z4–°°h ¿°(9ÞùƒÔç™\TÌY‘ÁÀyÀ =<Ò&;CAh®N†êÕ=åoU…Hæ‹ ˜"C¡®!ug7-jiÌkZY˜W–»ccDë^½ŽÓ-NŠ2ÒÈßµCva^Ïý]DêÖï¹eá¼F[9-KXX4†?ª¥x,C—Ä€GÉ·è˜çü¥À-¢b;'á”ArÒ°‹hÝãTÑ(®P„¿¬»ES¡y=xÊK•Eùäl^“¾tÖü´mÿ2 }>ðBíy8ÛY_e™“™@ã,PP~f%@zÆÒYý{]vÃñ§ÅI‚dƒ]‹§£«ž\Ld}礕¼Ýr ÃòX´h,aaÑ BËðXÔ'*üvü¢"x@Rlã:»Xpó?hßÿLd§‚(ùB¬ÛÐ ƒ šaÝF^λž$}Ù¬°õß¾{eæªEçoaŠfó< ŒÏóœ8y“àøt5(yÿ£ظgÙœþe÷Ñ.±¶ŠRSS_–Í ‚àKòÖ½ü¹Ïó'P‹²YIó¿ë¤ BUQ{–Î3a;ŸZaá?'ƒs,,¯EË"”Ú¤{‹ˆ%,,Ã…yA®:Þ9¹¦1Ïo ó†3x9*©k¯³|™äs.G²™I€þåXá¿§º™XÚ¤6—²ÍLØlÊøgAYÕc—d‚’j›²ÓAÏKGÓåœQlýqRè²·¬²0oð° S\øCçü†‰¿„åÉ..òVøCP4`·»´8?õ·©1ƒï¼ýXW‡jMGG@O¬T(Qâ‹Ù¹p ‰WßEÛ„Ð?..4ÄÿÝ+J»ri«÷Òëæ{h&¢‡_„(Cζuän_¯k1ÏÍÀó08ÈòZ´<üÂÂòX´P¬˜‹ÆˆªiÞ±ŽÁåcýá-2µÆâ%À§]λ¢×˜OçÑã¢ËsæíXßrD tÏFö¦¬ su û}˾¿“³+M (gy¬À¨(dÿòŠrJšl?‚ÕÙ»Ø9)e^DªÉY½ŒÜŒœf›ên ºARúß4ž«?˜IÛ~ƒ†Ÿ§ûVS¨=$/O|²R_ŽEð q.°kׂŸq—¹›î÷#€¤°ê»YüÑ{x /‹ÚP·šÿå›<š÷׳ßF»’×óº(CÙæ,{ÿIöï/An¬x}ï׫ØñîíÌ~ýs¼Á‚ä(>!h›’WÿÀŠž¢¨ÌÝ$]ÖC‡] ¦¡yÜY@湨RW\Ôõ" {T¼UÑ þÏKD9$tAÀ]˜AêWï>ç9RzœÂùwÍ4ÒEÑ4,jêÎk5›2 ³É–ï9Co,ÌIÀ[Yæö>…(×HÁµíßë€ù© æö€™NA¬5ŠÌm‰ ¹4 C §óµ÷ öë ºo]Ñ×|L دV7V\”kó.ª»ßD¨npÅÅpÙK_ò˿ǵÏX2óUàvÌ*3JÐêFíèNJ%X\ ŒÀ™âtÍëÙµyêÇ=: ?¿iGT]Äæ/_§¬Èǽ5Jß«Ærè—OÐ;õÃYµ™ËRi3êúH sñOì^±ŒÊÒJ$G±ýFÐmäE8´"¶Oú˜R¹;}¯…ÍçQôÊ`ÓÏ? ÅŸOï«ú#T”²eêçܾM°ãJêGÏËÆÒ*Ɔ^žÏ¶i“1ÚC:¸”Ìõt»å?$Äj¬ýìCrÓ÷¢i®vé;vÑ­œèh¥ûÙ9ç¤fâõh8b:Ðá¬1tìßùˆ‡/˜Ù1å#ömÎÀëÕq¶éAïëï!>ZcëÔ·É:(!+Yþþô=–˜(ý¿~ÎîU[¨.¯B kE\ï‹èqÁpó ¯.fý×’½}*á Ýèqõ$tˆ¨;ŒÂLÖü<…ê*ݯýQ‘!Mš{!Ù`ó´ITÊÇ _ >ÿªu2þf[2á˜Â¢…b ‹Æð{,š£K²¾R²挳ôÞìváµqç=ö¢"ÿ…5× D[wºKl(èˆ6è50™ÆŽæ`ú&T÷(ìvðäd²oírÊ ‹BãhÓoqâ̄͊òö¦ãLìEÕžµägìG‰¤uÿsˆŽw5Ï ˆ"‚Ïb$<%\µ‚â¼DGQ]úߥ=ºê›it—“µv%¥9Ù¨ª„«uñ}OÇj~f¢Þ½dmÜ‚»JÅ•ÐMÓð·„0$]®¸"Û žC{È=XF«(Û±ŠÂCÈá1Ä÷FDŒ]3„’Ôõä¤g€ì"¶Ç„ªCxÔHZ%·5ÅH£«Í¥/LbÚ½WôÈZ¿üu`Qá÷V.•ÀòŒ¥³»e®^.&j†k0<…ìþe*%nôò vΞLç‹."íÃ'ØVâET+"’°ýrg<Ç÷= áˆéÓ÷®Í¬ÿêuÒnû†k¾ŒƒËÞcMJ¡ƒ÷Ñ»k$†¹+?gîkÏÐ÷¡Ó8EÍgñã—“2#…Є.Ø%7߾Ɏ™ó¸âݯˆ·°áãÿ™+@u%!Ñ=Hy )>Êï3ÖÙ÷4ÂÊsØöó§ì^³”+ßþ–¸°">x5«–­!*¹/!a6ýòk¿~K&­â”A ¿D)eÙÜDtêCX”“Ô™“ØúÓZ®ýèqö/ý‘üRØÆŽé?“|Á•ì÷ ?>óblGÚtiKÁâ-¬ÿüMòJ¾å¼›¯À¨Ìcɓױzú"»a=l›ö©)ë¸ö£ï$óš"Ê6(Ídî£cز …ž·ÿgh¹G}ˆ”dæ°cæ×ë1Cï4j«ù—àêP'ÛoõD È>Þƒ°øóX¢1ü‹æ&,‚{Tø½²o žMDrÈÉ,x †a˜ûÔLa bh( fØRÙŽùÌü×xöíÎÀÞ&5{?r|7ú?ö5g_>ª-s˜~ëMtåikðxÌéþ°¤¡Œœ8™äž &K èE»XôŸ›Y·peMÒ¸Öš¾OOåüQC¡"—å/ÜÂï“ç@x 6=O9Äœõ®xébblTï^ü/cûÖ\sJ(‘m£  QËaÁ˜¾7½ÎUþƒÒÕ_ðÝ¿ß$69‰Âí›j¬ñÈÁã¸úOˆ—Éøà1fð Å%æ©Ò®BiJ›;¸ù—7phMåÍkz..|~?N¸äŒ¢}»¦®—NðýïO憓Ç` ® œ[ᯠå7êvzª*ÒW~úRr›S§"I¶cÿ= „uæ²R2z­Îfìgßã Ùì GËÙOßûÞaе7ëdéØ)­Oçš/Ó¹·ït&_&E)+Qcè>ê6¤ü»§/£×c—!¸Ýìýq2Dä”ÑÃÈúð~Rf¬¤çM_ráÿ݈˦±ó‹‡ù驉lüy #oˆÍn]¤Ïã_3ü²ˆW²rÚb\×|Â]¯ß† l{åoüôö²väÝ1Ô›H¸ð®ûàq\v80óc¾¿{<3qꆅ… ‚–¿—Ô K‰<ýnúv22¤ó“yœôqñ; ‘®‰a=·0î»7 Ó ™¿ü„Ø‹÷ë,â[CÕ¾T¦\Ùƒ¬Ô½ @îoï³zú":_÷£ž¸§ÍÃÚ—.fÞÇ3ÈX»YV$£tsž½‘- ¶1àþÏqç-ˆM\QO`Ó”)Ú»«3 Jçð0¨àP(ËcÑ2±cNhZ´P,aaÑÍÝcá÷Tø…ß[qsHTìðÿ~ƒÐØHÔ¿Ü©*€QHAúN—ˆZy€íï¼He•@‡öÉ(F Ÿ»—}ûqæó¿0à¡”®ÿ•Y÷ßÎê—î¥óÀ߉Qlˆ:䧦2ì™9ôÖ›s?`öóϲø¹WhûåDœBC7tÉf°á•X³p-½î|—¡7\…–±’yßÎúï¢û‰NŸÏÊÉsh}Þs\öô]8ŒR¶¿s ¾ÿŽÜ}Íæ÷îaûÖJºßþ gß8’Ÿ1ûÅWèšã•º$™FŸ‚è-¡¤Xç‚×ÔÑÆŠ§¯cã²_Ø·óaE;˜÷î T´É•ï¿FlTkž¸— Y{ˆí¨!þÅf€æ…¸nÉŒxä ¦ß7z´æq¯¾¡6¤.p9Y+D{-ü!(~ãÎãû¿Xœ±tV§½ËæK]Ï¿µ©2´_¬  ˜ÉƆá©ÄyÆm ½íï„ÚÍõÿïtzTU#kûØ3ÿÕy›©¬(CŽ‘Ð5h3ø¢ãžãà†ï(­º WÞj¶mÞEûQoÒ6f®\ áètVÊöì H ï>gè›äìÝŒ»âT4¯N«^×sÎ͗⪠lHvp/{™•Ÿ9h܇¶×½ÁÝ×€Ír(W¿]uP¼q= ‘¿n^UD–Å#Ÿ`¢’BÁöÕüþþ'tîÛ‡VCÿÎ]ËÆƒ+ M«@¶àVLW›ÉéÍ¢w!è9;HßxŠC;¨*6LÁ BîŠé ÷ ÿ ·ãP@Õlô÷FÌJÚu‰å`šQ™ÏôGFáÞ¾“Þ·}Æ9÷Ü‚ä¦I+D‰ Ú¹“ ß¾cË0½þs.ÐcØ$Ï-ŽdÙñˆÅŸÇEóåUÀàŸƒÿþmúœrD¢ŒáÝÀ¬;G`“ AÇS”‹× Q§ÝÉ1WQ6›Œ ;iö« {)¢\#¯çœ;fóýë?°oU1ɪ —?ÏàëF"z!ú¦GÉY:ƒU›ç‘wÐMçD;Þz¼‚$ãÍÚÆÖU¿Þçξ÷nBEÚ]ÁˆÖóÍÃ/²wé6âG aäSî DG:ðz¢»vÒ 5{=›æ¯!fø£\øÀm8$huë“ ß·‚Ù_njàÐÐqrÆŸÐïüèÀ ±7³uÙ³Tç óàBòËEÎzý=z 錮Â9/¿Cö˜3ñ¨Ç§ÜŒ× Éç^B¿ëîRÖ}9ñ~ÌR–þ|‹àÙÏ“-ß"X\'oû<7ælãC×7.ïéíúÅQ'_çØªÖ™bÔH>gT¼Ù¸Æ—ÃT±w KÞ—‚½{©,)EU½‚@|kTÛõ¦Çо,™³–‚}%T¬ŸCi¥ƒSν§X…*KPº›…]€hsš»Ô*p«v<ªˆªi€„bC@«{bÎ~áIf>7‘EOÞ؈î>Ã/eÈ]âÀKæo±qÉJóQ]ZŒ!™s$GúHÑÝöàë,ëÖ¾tkðÎý‰>–áîÃåðåmé: )"¥ëæ³ô«9”yOA^AD!A /E©‡-Ôº¹1îýý è:è^Ü©;€Ìô”—A¸&û%¾Ü¬”Ÿ§²0/XC­—Ì_9È/.ʵ°h980íÒ–Ð;Ë¢,aaÑ‘˜¡PU»«£%0¯Â_VVƬ}oÒàóÂO½öŽ¿<ü©CG0áq"ì"†a†FÙ㺒8x19?fPáH<û¾ÜZÿ¾u":Óµ³™+Èm *Ë©Ìß r­»´Å¨6-[t"QíâÉ®8N_šaZ Cîz‚̵KÚämß0xŒÚð'ÿŒ½¿þÍÉh´øE…¿[¹_Xx‚«Å9[V'§|ø|ø9¾lÎhÿEŸ–¡é¦Q-ž»†ùÏÞGNa§ßó"ÏJT+'󺔼RsÖA7dz\~ +¦ÝÅÞuóP6.EŽ=ƒ®§wDó yªÚdäËŸiWÑ ³²’·ª%®;v¥Ã0ÀPk´Žæ•ˆ=óAþ6s%›Øûû–dç´{ "ïæû2)_ å‚»F5Yª­dƒí3§±cÖw*°³¡¥—Új~OY°¨°<-“ÌûyéñˆÅŸÇ9kÛ\¨Ï[ჸÈ>äŒ;C±7MâèQ`(½é}ÝĹ|9þjNª¹˜ˆ€b¯¹ý "hÕÕ˜M¿ÁpÚj×ô€™ÚÆðÙømÏaÀ•ç Wz00ldE"¶ÿ)ìÿé¦<ð$¶„ô¹êN¶OBË]ÆÜW§ùöïIXÝ7&jÌx(³rTÍgR»¾¡©@;ìNG­bÕ õ/Ë®¯CƒÐø(†Üù3î¿f”®©Ó4ìµ8™Â¢…UpE(¿¡Wé±°™À¢ ß½{y§³.%ièð&ø=š¿ÑB·KÁ}heŠi;òFº `:†(¡Ø¡¼hy$^ÿç¿­ ÖÎdÏ^°¡ 8%âö€”¹ìß´™¤®§£ˆ°ãÙ1ñ'.xk#²,‚-œä1£HÐÚ±cñ2¶¼ÿ8‰g ¦WØcò&)P’u€¥oüC×Ö›0Ï·@Q˜ßã?'{®œ,¿Ï'–Ç¢Åc ‹Æˆ ù%Qv×ö‹ ˆÆ÷¸ô¹ãг›¨ð#ŒjM‹?6tp%´"DÒÉO™‡÷ŠîH²Y/>cî|TÑIX|ö€*ÓÖ¡Š‘l Tæ³7k š3[ˆÒp¤a 8"lBXkzß<Á …’áÐÊElœµ€VEýþ%ªÑ›Ñ“Rè’Šè€=ïÏÌB÷Jd,’ÍËÞ5)¸o¾‡lÎÖXù;µE’ê¡ù|A’q´íê,2¶gxN'4JÓV“›q)Ñöç>ðc„æäs/#ñŒ®½+~»³¬eu½'£·ÂO}åfãÝݾŬñVU&-xþžS®ùxam[»rÏ ;p)!d§®aõ§_Ðÿš‹Ðu ÝWÓØ0ÀÕG”ƒÜM³ô5'N£ÔïŸ!;߃C™Êö¥7ÐûôÞ(‘íétáX¶½ù9Èè5j(‚6z»“M‹obÞã7Pž5žPrÙöõkd¦KôîÚYpc¨*ºfîW0Àƒ÷à&<~êÝ -!ãçÏÐ¥p"Úõ ´d7’QÆÎIOàÚÛ²ôR¾ø ØõËgd| DÐQMoHà¡ëàˆ‰¢2ë'ŽÃá~š¶ñû~ûŠb g¯H†ŒÑ }ñl–½7€ný#…ÜÙï°Ì•…X}u_½I¹•k~fçü³h?êAZý0—•Oý ýÐ}8+w°úã°wE‡ÁÉd~ãÍ‹§² {—þœuÓ½|÷Ò+,ñÚ¿÷:.»p̸õ-–ΚCX¯AtÜã!Û!söÓ¤|1ÂŒ]¬yû~RW¦7èâ[+ †M蚊#±/]‡žMÑú÷YôÆûJO'sÎd挿œ•Ó¦ ;BQœí}do^I~z:;&=Ëì×>ò8´{Bë¡ôÒ…Âß&2ë£o(ÍK'íÓ'ùýûÞîáHh†L›>ƒˆ–U–?;žõ‹×ppå/L{àï•™ ç×gè Ù%†Üõ’b;3gÇž6Ìó/'KGîÀYàúJÎúEEµoñ`&rÏ9”¶%{îSwâ)-?v! ÐåN½v¡J5;¦¾CI©@X‡®Ä´omö†QAn;€³y§÷«^û'KÞz‰¡sá?þ†C¯dãÜE¨:h†Hdzƒ˜Ü…v]MBçx³—‹ an䪷?¥møV¿úw¼ú<…FÎ{k½NO@ÓÂ:t#ªmGÓK¨Ah·s¹êÕ׈P·°ôù;øõ±{ع&ƒþ|BŸaýI:ïμî,*Ö}Íüçþź;þä×ô:w޵߱oÛ”¨¶Ä$u#ôÿÙ;ïð(ʵß3³5uÓ; zé:‚¢XÎÁŠ€°Š¢¨èQlØÅ‚ vA‘"½wéÞ{vwf¾?f—l–¢_d™ûºæJ²;åÙÉÎó{Ÿj­ó¦:Á˜pC§~H’©ŒõÓî`Þ£·³uÙ&:ŒšÎ k.Fͤþ/Ñq¥lÿòC¶Þ â ×±ü…{Yþî{$ {š~wM ¨`3»\L@z®yãk¢œl|ã¿,ÿè¬]GqÅKoešHXóVXÍr$z˜>WuÃyx)‡wk°ÏVp5!\ùÊcì™7« ˜ ä£ÝSîûËíµð, Wƒ:ûÑ=çM塨ó× FK` <ëç±@Ýð'#ÚŒ¨;ì" ø¸ËµÚ ùßtÇ™)Uê ƒ¨°þéþ,þRâ–5¿|ªÇ@0BÉ–9Ì}ä^2Êœ[BpËóèûÌ·´ïÙœ²_ðÅÍ×S.¤à´Ãé*7Þæ2†½ñ1IIxF F°ïû…w¯¸š¶÷ÌaÐPsh+ îõ[Ñž´RX]¦|ÏÀËÎ'oÝw|s÷Jò5ý(Z#hqÑE®M¡ÜŒ«¾ÞM²°…9÷\Íáý9­àmÄ´O£`s.C¿\G‡NN¾ëž‚zã+Œ˜x'sžàƒÉ?påçËHocC*7|Ì»×ßM×É 8®7fLeÉŒWÈÍÌ":^Œœ»©å]ŒúøIŒNÿ}†Z¨ƒo&\Α –w¡åy4îYû¦bÀx–wv{ ÍhÆ@ˆ–çâ±éÀuGN0ée VKƒå? "8+«@4 ™Œµ =>ÑŽ¢<ÊKÊ0E‚(@Ua1¢9ƒYÈîNÜ'·÷܇äòRJs³A $ 2s€tò<ê=nq¥Y™ÈˆnFPD ªìŽ¥S¨ÈÉÀá°FÅa ‘+«¨©rbB2µ±v>î0ÉΊbŠOdâ”,áq„ÄØÀUªYA®¬F$$³ÑÕÙT•Vc²E€àT©*-A0a4%Pª*¨(,ƒkxF#ZYk¡¶‰§vÒµ]Ã¥nï¿‹ h9!?~ƒe/> Ëû|`%P…wï^Êм镮÷Üÿ—nï…Û³¦svÑø¸-Q_ç,D:õ‹VÚïUà-?Å3¯ÂH­Acvý>Òö¿ë?[!Fµn翤mÕG©(‘°¥$"ýOP4BuÎq²vn¦²¸)$‚¸N½ ‹F rË,>¹ù"FýÈ€aÑäÎÄML»ÞÙ„S{X€½œâ㙘Ûa ³ H@y!™;7R’_Š)0‚Ðæ­‰n‡ìЪV–ÝIîþÈ¢…¤6Ä´J¦lÿZŠ‹ „·=à{ÞQNìØNe…“ ÄvD§&P•‘C@b f³JéáýKP¤ ¹,â¼ ‚ša2i@­*¥øD6Ö˜dŒ”P’•…j²¡Væ¡H¡„„ªÌÛƒ‚®/sÇócPüÜwÕ`†Ý?ÿÀÜÿެ’í5w¡…b8©ž ¹šÒì¨wÑ#Z% š°DÁKÐ ¸¼ÃUc>ö&¦`kÃ…Ey¼¾VqÍ‚«*'+T .¥}ÚVÐ:ʃ¶Úì²! IDATÓÙÎm(ŸÜÆËÔD—uïï4ÎÅç˜ßû÷Þß)ÇÇ(<^;ƒ¦¹Ûã±úíçXõædYq:EhBÂs)w½^EÝÿI·ç¢)ýOžK ¾zü<¿‰žc¡ShDcpIzΔzΘšÐ —kÒ‡Žl¢À•L@ :ÅæÈ$š÷Oª­_ª€Ó¡%hj/€h $¦K"Z»ž– ¾ã©€)ˆð´´“F†ê! œ„žƒI¤vöSvuŠ ÁÉí NiÒQiÕ“W©GÙ†°dRú&ŸœAU°¤·@u5 iÕúä1¥ (¢B£´™Y·ñb !"=Ñù?|ËÌ{ï$näS\óødÌ”³ëÛ8v¸˜¶ãÓÿO5:íвÿpÏëm=ºvé0´Ù³*êvõõ j*FŒgž‰»i gž…mÙ3JBËU‘w|ÿá•ù9ÁƒðæÍ‘í à™Rÿü⫪v/×yí¯Þd*§U±é”ãþÁ÷€ªxý4Îå/Ék§×ÿ¯ˆ­B2BMi9¿½<‰­_Nw*²ü+°M0Ty-ÞžC½"Ô¹C Ús½ÈßÑùûèÂB§>Ð úÆ$,<“¶®×:I&s‡¶—^§Í@6|ÎþuUÕ½ 첌â¬GLœ²Ñ©‚æÏÆãË0ñe„yïÃóï:Çô1÷kª ¶Ci?¨Û¿zœ7çMCD¦¦¼Û¥Ð{@Ï¿l¼ý#¨`4‹tq G×.í|¢ÖXv‡œŒ0k*¸+b)Ôv$w ~·°ðôf¸……ìf\>oHñøÃ1ýšF˾—œ¬^¦ÓôDMTdíØÁ²þˑՋ*€ÅÀV4qº¢Â3¿Bçì%Ðõ³±ävêü ta¡Sn…¿ëI{—˜õôXHÀe±m»ã:t?§ŒÕ æä¾ }}¦Ô.~¯rÕP¨2aÍøÜlZ ÿ‰‚㨈mO‹ý °ŠCX  §¤} IH+=qtðš±ì ÖXvØM±ô¬[`¸E–gr»g’»àñû ´àÀ®sî¾²u›Ënº}€ÈV­µø~ç_÷ èœ]¢–¢ªP‘WȆ™/³ã»÷©ÈÏ9,Ž  ˆJ´p'÷âîâÞIÛº·âì'(DûluÎRta¡Sh?……§¨ðŽíd Gê€+°Ø,8ªü6ÎGU@ M¦õðdpà³»öÙŠ*ƒEêecHõˆwwÚ—Q©È`KJ ¥÷`¶3£?ðZƒ.O¯…»^SêÆíöZ¸†[X¸—[XxNHÔ^£<`®³¦úÀŽï>è¹ÿ×ÙQm†$}ð¿‰Lë@pLDm>DS¸šMwΆ @eA…‡wqpù|vÎù˜’ŒÃ%h=*¶ ý¹E…{ñž=,Ü•ÉôœŠF€ªªýÑžÉûsxå•W:tHzùå—ÇÆ¿¬(Y‚ ,ø»Ç×iôämú¸-i»/pÐOcðÌ­p'l›Ð´LÀPS`дÑsvaÍR•QÚLÚÌ£ì¨ÍÙ8×0˜àÀÒE|Ç¥²Ãq°ͨq‡fxVˆjJ¡ž‚ß3üÉLm2w€k ¢¶bT ÇëF h ´“Œ¦˜°”4CXr*¶ä4Â"0†"4D¹!ÿ¡ªÔTWP]\HɉÃÞKÑÑýrMyi °Øä¢ýy w‚¶g¢¶gH”;·G…j(Š2_„Kü<Œu‚ ôôóš<ºÇB§>CŽ…àñÓí±pÏ‚š º^,„Æ%ŸÑê%ç<‚&¹þÜ A„ü¿rl÷!¢:_JB»„?LR=[‘ØõB‚ãš;Ø ØŒfÄH‹Û¨ijáPîŸî¾žÿ§‚Ÿžë+hâ#Í+ºWvØcó÷ïLÌß¿3M€¸s4Ü×UWõÓ¯ç˜Ü" (2h‚ÁÚäí©ð\¼=Þeeu…Ÿ¡ÚßcPUõŠ[8{Ñ……N}4– ϰ ·ç„=Sû^ŠÁ*àôsyÒsA{öN¶.\BôÅבuЏ P°ö#¾¼c<ååzLú™ÄÎ禰PU0Xhyñelúôµ‹€oÐŒhuá<º 4ÜÆœÛWè‡òœðÞU¥<íU¢Å×»KØš¨}F‰^ûÕÑP©õUÒ8šÞzŠNÏ{Ä-,ÜaLî×<+ún/…§¨Ñ=gÎ ¾xëEVŸ¨â²‘÷0¤kB·×ÍûˆY rë¤GèXÏNtÎta¡SAÔ–ô‚×â)0ºZ‚mQñºû¯‘šäª!ïš—Se­QÔÉ÷EmÁý¾rjhðªïú]qjÛ‹’¶ ÖZSŠ ˆµ5õQ½*F Úv‚ëwUœµ%\=;ä ûPdm€°è ²€ŒMk)É/ÆAdë®D6‹:™ ™ dÏ&rE5Õ¶+Be.v9ŒÈ´8jòNPp,ƒðV-(ܹ’Ü,±-iÖ½;5G6“±ëNÅHH«óILOÒJÄ ` †¬ Ë)ÈÌE0šÒ–¸6© ËÜB•©!BYûvQ]­œØ†„Ní‘ rÈþ}7¥û6’ß®!1Qš8•|™sŒÜ£Y@ÑŒ³ÊR¸Ÿüò "Â%27¬ÆÙ–f=Û!V—s|Õ ŽŸ@5…–Öƒ¤Ž©ˆ €BÁŽ-È¡YkÈܲ“ÕDT‡‹ˆN°µvEÙùHÁ±Ätî-Üâ—<EèVIH(9~è|`u“’%j˜›ž¤[0€oCÏÛ°t'áÖP›?å]¢Ö[X4†ùÆ„ûú' åÃ}‡vmý}T¯Å-<=TnéÎYòìYá)(|5¥ÔC Î&T0Yµ¨ÑýËßã¥Wðò=ƒO¾m0Z#â)ß *ö;³Ùï7´Îé£ úÅÂÂsfÒ;·BB ƒjÓö<ÌÁ&g8²S4AÉêïøñ‘Qdž¨$ <˜ÊÂ2‚Ò/gØÓ3hÙ-ûñ,™x#[Öl?¹%.ÎS¾ââ~•£ïNâçw_¢¤TSFI­Q‹ci6Ñ ^¤tí—|óÀ„uL'oÇ^d̤ô¿”ò-³É/t=OCZsñë?Óû‚æ(¬}ö6V~5w>µ` &ý™\:úJLöR¶¼:š»%lŽãæ¹>b)„îÏÍ!Ý¾ŽŸžy €í¯§¢´„+yƒËË`0¾/'³ð¯X2q kŸ>ÚHTŒHÞÞý$™Jb{k•¿­ÅƒT”G¥IDZéwïýZªÙúø ìÌ-%ØRBÞñJÌI]h–båЊÕ'­Ô€‹ïáÆi¯$žqÏ…"CHba)i”?Ôøͨñ¬väÕ”Œ?óÔx—îøxOQaF&êþ¯{ælÀ©sSsžxеTàBàG×kgÒóÑf¨ë©ð%,Ü÷€§€ðôPxö¬ðLÔvW‚Ò³ëÎdb:œOlå>¦?öC.鯴°z×^ñݼöñ|òK«0…ÓóòqLw æ3:j¿ƒ.uê#ÿ'n{‡š¸ÿŽš'v½èÌÏ` 9kØûÓëdæY¹ìË}Œûå #Ÿ}‚ʽ?²qáTQfÓÓw±eÍ>ºÜ?“q+NpóÛŸX~ŒMÏÜCV‘Jõï øeúsTF äÊ/v2îçµ´Š ¢²ÌŽÑ,ƒ ¢A‹YÊ;͙۸í»ùÄÅY8²ø{Œífô/‡¹öɧ°Tìáà×?¡ÁÑo_aÙW³Iø÷F/ÎdÜìe¤5 gÏ´[ؾ-ƒÑ€d¶ dn'¬ßÝŒ]t‚Qo¿M¨©”ÍŸÍ!xÈÝ\ýÜSt›ôÃî¸É#tI¶C››¦1üž!¨3—}´>#/B•P|‚Š(ú¾ø#C&ÞÂÁ²ò·u$Þü·ÌÝÅØy«iw¾íï=ÅïKw!šMX,Tçå!u}”[—â²ÿÞóøö¯ÞMÏ'2nñïô¾ð<*ûŠœÙR}Ì?ˆ ³@\ûnÍ(j_Ñkiª†®·×ÂÓ€ôŽ¡wWú)s-¥hß5%håF½—¢z^¯oý¦¶x6ëôt]³ú®Û?±øú,Š<Þ+ñZ×s)uƒ»ú“g‰YoO…gTSïg7¨å $_ÁÔGF©ÜÀŸ¤Üg´Ê’×bÄ¿ïãLJILL d× ž9·>0».%=ºÇBÇ"'Ê»1ž43Z"â;õðOܽ¬à¨©¹†ÜÌ,Z¦&ÑüÊG¹ÞÖGl{ª÷.cçÆÕDö¾“ o…UqÈ\¼ëG¾c!Y›S•¿”‚ ýN›^ÍQdèÿ›d]{!N§;éBEE Ó/Ó¥oGTgGÚwŽ «º ýŸ~œøfä + ÷eD‡ŒR\ȾÅï`ŒîÏ€%2D‚ä8.~ôQ޽?¬¡ûyƒQL±8i ‘ ¤ÜN—oòÛò8 „§4 ±AaA8=²lÀ‰-:Ì„6o…ÕfÒQô{ákzõI@)Í`ÁÏ¿`i5”a÷Þ-”èôäYö]u û7­àüaé(ŽŒq0ìÁID'@ð «XÿÎt„ÁÑgÌ` *´>Õ+fú/É-ç%éü‹X+ŠIª¢$9Ô …jŠÉÛÞø2ö¼ÃaO‡Nc@:¾Ð„E¶ŸŽï]ªÒ³V¾HJkO@xô™´¨ ­Ä÷½ž°U“YÿÅl'á‚«h=àJZµNCÙ½µÆIáίøüÚu˜U$Òc»€2ªrŽbÈ݆XbRãQ«µ§¥)2[B4¹—FU1 FâÎKA­vśҫŤu)vʨ¢€\]JM^Ž¢ |{Ë ˆÕÅYTªàÌÜ‹C€±]‚ƒ´¦t¢(bŽH€ª2AuðŠâôYnVk\¦€¬¢:e—7CZ˜ÓÎÂ*ÊÄôìG¨ œ5Z¶5± &ªŠTUEqÊ„4O Ȧ着 AŒh‰AYEVñ·sU‘!ºõy˜C«ËŠã¨+*¼K¬6U£Ç»qžû5<^s ÏpwU#ï¼oQ¡‹·SQЮ'&`,Z9×õhafþ¸=“«=……§¸tßÞ?½Ãž¼=MñÿëìGu ˜CøÏ3¯óͺ+yéÁÇÞÿ[Ì.O´(IdÚÀº\•{Ÿ¾“‹š`Lã–±×2kÑÖn?¡ ‹FŽ.,t|a@þ…ª/y;-2½Æ€@¿T„’ehqé½\›~Ç7­%sõB,ûŒ…óßgͶ÷¹ö²'D¶Dç+{㬴ƒª`´"J±ÝRÉüº ALÀb1kç  y(dï0Zëv§¶ž—‰%¸L6KhwºŽ¸j¨‚‚d°`0jv>‚Ý®=‘3‚êÄV]bâÿc¼k•FYÁˆ*Ë ƒ šAÐÆ¦  Ê2Nµ¶•ªªÍ¢Á£Ë² B³qY*˜ƒ‰HmÉ-kZKÐŒ OošnøÖ}‚×ß¾âì Ôvë6rz¢¢©__o| ‡`à>à~`¯k3·°ð¬æîAáYrØ—‡Âó¾Ñs*Μ*ÄuÆ[÷ÝÂ¥Î`êÛßÐ7@«%eyù@ÝÓSëlš„ p*êÉ ã]Xèø¢1 ¨kX¸ 3™Ú£…3ž¸jU>ÛÞ}’²´›¹xÔ´¿öNúæ`Á¥Ùµn=ŽË/×fÝ#›Óiìx̲–ð}|îWì^¿‡ø m$¤¡ÚßåО£$^œŒ BéîµäÉÂØ¢î¤zš_¤‚1)8¥:€´k'HP¶{ 뿚…”Ò£VŽ ás*¢ÁX[Žö/ ª` À"’{`1¥Åw¢ò—Ï#«Z%-6Q]ë«~+|º¨€h2•Ö‰[Ö´äTCX¯XTïª@Þ3×îÆ‚nqæÞ"­¾r³:2ZÏ!_×%x¸-tïL>ï}•ö‰òÞÞ ïÊOü[BçôzÏ£\1çG>|íiöµ@0HZn¡(åä–Ö5?òв(L&$Úéüta¡ã mÆ«ØÇöîÞë=‹+ˆR˜-±¥†¦!‰2W½Å¶Ï%0èÒÚ7£ìÀVŠ««l„´¾ˆ´îÝXûÛ ,;•ó÷¡|û2æO¼‚ðóI¿í:ôÀ&½Áª§n#øñg‰0dòËwS\®#ÊüU½ÿPQƒ-šÔ!cØþÂë,~î\tÓõEYõÐhv+eP¿{ÿäѬjŸ "9›WSÔ¡%Áá¶Ó|¤«œŒŠ I¦Ó…YøÙ<¿þF_ƒ’·%/=lnCê…ý‘Ä?¨\çx`’Rƒ¢Ò;‚–ÀmAK0õÎpÿ®B­S¨5ÝׯmHzçRøÊ«8ÝÞMQtÈh÷a}çÞ ˜ Ü‹– Ýâ÷tîooAé)4¼„û5ÕÇúMýéœCJbÚ3O³vÄ­¬\ ¢%UQˆMë€U¬dÆÌï¸ihG" €3›¯>}‡s+z·Oô÷Ðuþ]XèøÂ-,üÝuÛ׬e¬%$, 4ÞÕ·áL£‚GÇû>âÄ}X8îÖDFPžŸ!¤ —Œ»k`0Ý~‚¼±¬Ÿ:ŠMSµ§¥5©=}¦|L|¤ !â:O:ÂÒ÷_eÞèn€HD§‹°ÅT!‡„# Z¾ênNqòðN0¨î‚ß‚&*YÀ!CêÈéöûn6:™ŸNÖ¶´†ÓvòWtꞈ\]ŒªÊÈ>ÌU6 ª˜Ø…ä¤Hö¼w{WLzød¹ÙZ\Ï|Ww?0#¸\Šb Ã¸éºŽÝÇñoŸ¤¦¬Â[ÐýÞ7Hï…â¨D•'s:´fxŠ+—D¬§5CQýk7 „&4G¥U‘£Ñ„·¯$cºø ‡q_+÷ ~º^ ýúÖEF«žôG×e 0xŽ3÷Ì÷•ÄíþÝ;´É[p(^Ûé¢âÀá»PwÒ¬å ë¹ÔL&¾³ÅnÇ); ku!“¯iϤ¯ÿÇÕ£ =¸+›¾Ï NÐ{Ü z¦„øítNýKZÇh¡.Öác» w'^‹k<®¿/Mj1ùÆ/Ö AQ‘~i˜Zƒ¼ÊC;9±o•5˜‚¢ˆJïIDŠ Õ©½¯”ärbç&ÊŠ*0GžÖ‘ˆ„0d8Šs)ÍÍF5… Vä£B ‘ùaLOŠ{¾Á„©7aÏ/ ,'—€f­1 &g?åö@lññHN;ÅGÌQ„Ä…! Ú+ÈÚ±Ž¢œ$‹fiĦ%£ÊšX©Ì9B a„ņŒ†¯É?Ny‰€-%ƒ*2P’S€5¶9¶X¯$yœ¥9”VšŒÑ5…”@hóD$—§Z€êbNl^GYA>Š@X۞ĥÆ!ØAT*Än$$>Nû2²WPzâ(jh2¶ð@-Ó·,’ì‚›c´J~33$dnÙÌ×·¬©.)zø ­4¦{q—Ç´S;óªSoD}y¾<z(Ô©È@°°y½WìBkš· 8Ì™»~Þ¡Pž¿ûõ%üëœ%¨ª:áóM¹îÄ¦Ûøåñx¾•µž[¯ÿ;ªá•/âÂä0ª‹2xuòm¼÷Ã6쪀,Zè=æaf<8†ð úµ±ªªËDQì×°g¦óWÑ¿¤ÏTUíü›Úη›âââ€Y³fºñûשÏõD¿®§¢¢õ‚VùÉÍf´Ùã7h/öߤ¾oé³øÛ[ç8ÓÍOA¿AGM‚w‘U\EB‹vÄ…Y¼ÞUÉ8¸—ÌÂJZ¤‘ä—1zááYj640:ÎCkT¤ R/KªKv ¢Ö°NA€€ˆ€ 4šÝSq¦Ð RßÔPWT€æ±xhì9ã#ÒiŠÌGó–9þꆇŠ]¾|ù+®¸âÛ°°°Š¿y|#°óon«Ó€è¢ °qÑLFOz‡>#ÆóŇÏìñ^ÙÑUŒ½d$k-øêçÙþõ…@¹7Á¡~`C¢*à<ƒ}8D#ˆ‚&^¢Qû`ù1ä?Tl‘ yÏÌœzŸêÕ¡t м=Ñ……Î@„77ÿææ€~hyAä ¥ówÑ:5.ù×8†$˜™÷ÙL>YâùŒ©á“©Ï²è@&ÃFÜÉ 6‘~£õ%uŠhÂ"ÈõÏÌ] ]ÉÃç" Å{¶ptóþ¿á¾6 ô!¨2;×q|÷ÑÓþ(Qƒàg“Ý¢ +šÇ»²‘]Xèø“`!pµ¿¢£s´2ÐmÒsýCl˜#ZðÌ´©„‰9<÷TŽTj¯ï^ô>/̘µû<óÔ5€Êâ\Ö,û•Ÿ~žÏê Û(¨¬k†fÞÍ/?ýÀ7_â囨øgæ|…BÕz,l ®+ yÙ6óMn:¢UZ:W@¤†=3ïbö}Sæé¯˜½(eGØúñëÚyðÿÿ­!ö|6OÄì—>Fþ|—‚U›Ùðþdgÿ­Žà ªB@X…/›]\èø“ïÑJ†'ø{ ::B+à #ŒJ§ñ¡ ‹&BËÁ7ò̸žßõ/¼û-N{)O>1cfoLy‚–Á²w/áºáèÝoÃ/FŸî¹èš»Ù›UÀŽ%saï ¹ü_Ü8ò:_ÜîšÌ‘Òû.ð®Wï]ãÞX-! _&B0‚’³ŽSïfëšC˜¬ÚEƒ6K/@r‹ ×ë’Q{­Ž—£¯Ç)³íBí>%#u…Œ … ^ÇuïCr½Vß1ÜïKÏÙ}ÕéÄYV†¨ú.sÊþÝÇ•€Ò},zæ^ölÜ„dþƒëàõ"xž§{L‚Œb/Ã×ñ½ P~h9‹Ÿ¿‡Çs1ë^+ßçü ‚Y»ÿÌhB×[Tè¹:…hM‡û{ ::BK´>+z†á9À¹4/«ó‡˜¹åá÷ùvQ/Þù9¬yK˜½æ0Ü=›/IEÉßÇÝcobîFÏNŸË·`õ·/qß“o3ft3–Í¿…icïâPèPÍŽæV;sßy‚ÿ¼ú?b:^Ê»wõü§î)2Œ€ ¸ðƒrI™;v!«P•±ƒŒ½ˆ7yàÁ)­)ß·’¢\…ä~C )ܽŽÜƒÇpTÚ1E%Ó¶¶X+öü 2÷ÅšÔš°¸P4C·¦àù‡ŽÔ²+¶È„š2Ö­¡$¯ÑFx‹D§jÝÄ%yä8@XÛtж¯¡¸*æ}úb–s9¼~5%ÙˆÆ":^H|›DÔjÀrY!;ÖS–›‹,X NlE|Çθú‹õÄ2 "¨U%Ý´‚’ÌldÉBX«ž4뜊Z^ĉ­Û@€Šc»ÈÞ{”ˆ”d ŠLîÖ5?н¬Q DwìIˆÍ‚"»‰½Œã+–YåÇÌ IDATP˜™–PÂÚö"±u3íCÑ’>ÁrÉ12÷Ån‰!±M’X;¶šÜ,röî hÏ: ZDä´“µr)¹ÇŽ `"¤E'’Ï?IôÝ£!0˜ÌhWüdE(_aPº¸Ðñ7GÐ’Y‡3Ð6Ɖ¤Kü=†AMS\;^|æAοêq^žº‰èΗóÖ¤1€µKç2om&£Ÿý™G&  íã¯qèÀ6¦ýú ûŽ_CYY ªL‹ô6Ü÷ÌÄv¼”ð”Íͨ/y[Œ¢d¨×8þ»ˆ(Zÿ1³Ÿ~Y…³ï㛜FOêÌ÷·_%¥=…ÛÖ¢ª¸yõEd͜Ăw_§²ZÀ@UqÁ)1ìo‰±oà‹QWÔï n™þ$×þ÷M¿–ùŸïåÒ¯wa3f²xâMl\²ödH—1$ŽS¾cÈU½(ZñßÝ~¶ó:’·y;Ž–C¹!1†ýO_Íúõ»±Ú‚¨*.GŒhIïÛ?£Ïí=‘s÷³hÒl_¼Â"1Täã´HºöF~$+§ÞʚϿC¶ÅHÅ•4ý*—ýçn‚L ßPOUAÒ„…»Ô¬wï&o::þBEkâøZ½CþŽŽŽO‚ÑÊwõ÷@t=ª‰qÞˆÿòнA äÎISè«•ŸÍ.Í¡ XôÞ] <˜2pÀ0>·{iYU¡üë¾~¬e`·ŽÄ%tæ¶Çg–Þ‰~ýRjx¾:ïzþn ±am6Å ¶Þ¸þ¥ç ùÆõêHˆ˜¨¤`Çï43ë¾ùŠç>VÎ|9f×Í;¸_rÕ““©9ºœ]ö’>.=b(]û-ÙÙÕHPʱ{ÉV ½F’Ò!†­ÏÝφ%›h;~:·.?ÁØ¿#ÆbgËÔ ;¡`2Ìm{hÏ+ÜôÚtÔ=_³~ýnºOùžÛ=ÄmßÌ#Z9ÈúÏ¡Ìy‹¿dûâõ¤Ž|— ö2þçítë×’ã_Τ¸Èñ‡áA¢ JÖÌeó‹I;‹»ÛËmóÖ’žlgÕÿ^žr×~0ƒé·¼Ä¿¦½‹˜¹‘Õ/¼K`‡qŒž»‡ñ 3ü®(Þ:›Œm‡‘L {?žÄÚëHõã–e0öÓψ²Ø<ã1ò œZØ”5¥ä8Kᅵ-›‹i9ñs®º}8’‡ pVAâ¿'1âñ‡èõ¿y\rë(2æ¼ÂÊÏ¿#nØÜòãNÆþ°‘žôàØÇÿaýÂ5ÿXžŒaáF÷Zè46¢åõð÷@ttê!¨òü<B÷X45„šµjÒ~º¶IñxC³äÎ|5=’B°;µ¿_r%æÐHRã"4y>±Ýæ²ný:–/YćÓ&óþ´'¸÷¹¯xyâ5 ­R½“·L¢Áˆ J [J! ˆð”$ f0E·$")Ògµ@ÒµÏ3â± ˜p–dÓóá0Ç]LRjrU aÍZa5¨€` &uèh6­{žß—m%u|O²—Í'#»Š.cFP±kÒñFúÞ5` H¼ŠþÿÝÂçŸçèê½Øâ (@긷òŸ±ˆÀÑmåäfgRSc"¬Ó0†¿³œüÂ* ÕØq(CŸJ&é’§Q%¬e ,-@8i{¹¦ °çn¢¦b0!Í{0xú¯¤o+&84KJ `IÂcÃ.4£çïÜûJbâ"q:jˆHo…€ƒ*BU>[]Š¥å% »ó.ÂÃAL¾ î;¾=e¨55 P}Á‚>`÷šlÒžüž+®ˆàôò4¨`Á–”@P³T%Žþò¢é<ú:ÑÑ"ªNï'^âðÕ}8ðõ|.Ñ £Ðð^ ÉxZ Æ@.0­Œç~‹ŽŽ/ZUèÂâœAMYÖ,7ÙYrl @ÒÏɤۺž|}Í3Xy܉¹lO½4‹~×ÝÆÄKFðध8±ýg.0‚ÕëRÃ5Xÿ¹AŸ4Öþ±Ä\YÑ‹Sv5©S‘å@âZcµžbp,1Í’ø}þs¬~n…ûvSét`AV ¡w"§‘¹ôkªoêÁÁ5 Q¬i´xrÁBäÊjÊÎå‹zcQeD*r!ã :/9^BÂÛôÄ ƒ]†ÐΗ‘Ðz6GÞ¾‹wgü—ˆ.ýi3àjšù 4kEàî ¬|™¿ï¤0ë8Ь zýé©+íÚ“Ö½ZóûOÓxoþ„&õ ýÊkisÉ‚B &Ï•d­8Qœ`´ÅÝ:‘ÞÏÒ-›É;r§½ƒGq.•eÅ„vN 0 d§Ö¯"ýÚGI¨ÎDEÀ™±‰Ý€9’„ÄŒ8|ÕPAq%M(Š‚R™MÞæ|ŒÞMx˜ˆâESd ÂRâÉ*ÎCù‡Ú©¹îA]Dèœ (hÍòÞB›>âÏÁèèø 5P dù{ : ƒ.,tèÑ{ÝãŸæ­GþC×¶3èß&Šõs¦3züdBú=Èè+Ûóúóÿcú¯Gøñ“hg!¿ ‡òrhB¶~ðŽS÷ …’§UQΠ9§ (-¶Þ ¥kf3û–‘gާí•ch{ÃD¬j+_x§ ªÌ-/¢uÛö¬ÞµŽŒ-[É\º€ nŸ"áÜ¥h¡Wñý8oD”*;ª  ­HF‰˜óÛ¡æ-Öíp  uénu1×~¼„ë–’µ}%G~šÍŠ óY=g×Ïø˜òŸþËœç? 4­í®½Ÿð„foÿ’•üÓ3Te0ÄõbÈ+¿‘¶n1»Ör`Ù6¼r'[gÎàª/#Nªý„E®ø€/ÆßŽÓKû‘wÒ1¥%&Ç>~~â TU§ Šª 5·/‚RVJµCÁbT!0žóFßÎᯟaͤ‡Iúz6 ±&œ–fª*(ª€hFp%j €*Ë(v*¢ÏÊW Óa-VåßtÑ¡ÓX ”×/ùy,::Þ¤¥@‘¿¢Ó0è¢ ¢8emúÝÃì JíÍ´¦póƒ/pím $JK‰j?”O^˜¨„PÞyæn~ä3útþ‰Äx™#ʈëП±wßG×iò…8e§Uý§LF—l2úŒÏUÈÚ½Š¼…NÏÍãò«;€…+¿¡ª D³AY±~Ý¿Yu独c G$º?0£²Å†`’Bâh?zQpňd„ܵŸQY°·WÐ¥oK3äÍy E­A%Laá‚l”Í¡² ¬6$™÷gÝ.;ÿþðkDd ÇÐÿ'8lÜÍ·¯~ÅŠ÷_çšÉ üA“h0`Œ"²À‘µó),y€¸pU„²ÃÉÚ“GÐà¶X$ä†ï¥¨ØOºçB§1“l.^Cï ÓxÐú¬àŸi{«ãtaÑä9þYº €¶i¡uÞé}Ãd–u¿Œ•ë¶RZ¥“D‡Þ}H àš¿ EŸqlÚ¶›ªj•ÐØDzõíKZ\ø?9`O£Í¡8¨ò?$,D ¨Ü²’¬ý tż¨®ï;U)À ¨”íÙHÎÞ@ª3·ñË“÷R©BöT”wÁl5~þ5¤&=Éå? ¤ ¢Eçv(°&u%­÷E¬˜÷6Ë^mKÏ+Qu`K¾cj<ÍožŒêšèwÍ "TžËÆ×£Ócø ¨8NAqX̘CÃ0X‘«÷’µm#¦¤P2~~–%/)‚œƒ»ˆ Kw¤zÊL¾d€òŒmlž9R)‰c.Gtæ“è0`Æ€ HH*äý¾…ìCýš«ÈÞ½–¼$ŠÝ¿°ô©)¨¨YE•t]œÇüfÉ›oÒ÷Æa¯™Åš%Ë ê~'¶p+² ¨²ZÜô"i V±ÿ‹©lî;œn}Ó‘kêŽSpuÅËܸ’’ô«I¸äVÖÿï–<ù¼‹ý¿=;‰2ÉJ¯›!) __S@vÔ€¶k÷¥Ô½:÷o€T`·ŸÇ¢£ãÆÄ‹ý=†CMؤTb“|¿—Ъ #[u©gKçõÈy½þccû\¦¶¢8|çª Öf]ILŽa×âI|Q%2æ±óQe“ÖëB†¤>#h×å}~w,ï½ `!é‚K‰p® ëƒÛÙØ, oîƒ1¬%éC/çÀ{ßÒ¬÷`¢b `§d¥ëÓÈÏÃÖׯ³ý5Í:5„%Ñí©Ïižb&oŸ‚ˆFÍVdH¹i:-׌dÓó7²û½ÔâT©1t¾óQZ¶O&àª{زî~ÜÒ Sd*)/áø’,|tQŸ.@’T§EkZG­Ñí”!eÐ(ºüö+[>¼ãs'#ÔäP]f"éö·hžAhÕ!•³§2'ï0W>öñ?,fós7²ù9@´2àjbw|϶Wï °múÞñ6…þͺïæÀÇwÚj0}zŒ ‹€"šQE1<‰‹FßÑɱú¥©´êô!!b­×B…Дó‰Žâ÷WÇàtVsÙmSèµûk¾{žVÍÀL!5Î{ðÚwMsåÉ4<²ý¤°ð…ÒÑi¬¬ŽW¢ ƃ[Xüîïè4úCñAUÕ‰hõÊ#AØ~ëIhMðL€­Lb äZÒ€—¯ÿ|•%©[oZ_Tg 0¯cDs"b)>vKd3Â,¨ŠÖôÍQt˜;~§¦J%0ª9±ÛSµ›ÂÌ9Ã.¸Ž+×5ŽOnã:°ûØîuDƒ×þ\ãÀ ªkûúÙŸíßý>®±þáupoç:—ZwSmGlÁõº§gÁÛRßEwW÷~<öëÿ€Cë$¬šþË_~äð4p-ñ°MTT¹]Xè46º¢õµ¸øÅÏcÑÑ<œ”øy,: „ uŽ Â6`[íîF`Ëĉßn ý5 ¢ª¨ ⟒ĪW²¯/#Õ—PÀG’°ÏõNã=Ô¿pÜ?k×þdû?Û¿÷û§uê98õZŸ2^œÞôûÿG º¨jŃŽÎÙÂf`/Zu¨_á+œ¦£sºtG³[*ü=†Cï¼­ã 4dÙ¿…Gt½66û[^U’ï·Aé4q¨,·Úp§Sô䓎Îé¡¢õ³ Ô“e§£sFq ݳ{¡ úh ’êõÓTTæçøi8:M* r@óX8j_­Ó±ÃcmFÅ" íçqèèDáèÅÎ9ta¡ã æÞxhîß (,Ê8|VdIf-Ù·AÀ`ÖzOèœYj*îP¨"ꆒx‹`ÆHð-péç±è4mÒÑ&gù{ : ‹nžèøÂ×ì«¿ð4Øj€‚²ìcg6®þo (²×üBÆž(‚DƒÖGâôv ry.ÇVüFqQÂY ®рφ…gA€ªÂªK‹T Àõ²î©Ð9Ûxm¦øD§I“†V¶{¿¿¢Ó°èÂBÇÁcáΫðü P\S^JUQqmţƆª=—MÏ\Š9K|SÁYÅ‘9ï°õç5¨§qÅETXÉìÑ}Ù»÷hã=ÿ†BQ.eßìì\´ WÑ)ÿ G‚ê¢jÊŠ@>'ëSr¿‚..t/{ÙÀ]€ÙÏcÑišhe¡Wƒ:çÐ……N}øÛlõ‡rÿ]â(/uVägùýîšÇArͪ»½ ¢¢‚ žôJH^3ï¢ZÂî÷&°dÖ"0Ön+´*ÉõÓ½ª€1²%®¿“èØ0­¿…Q ‹:¹ñôf÷©vL’Ñ£,­àúÛóÚ .¯T»­(¹öa¬-;+\?½ö'zGò¸FîsõÜ—{\ýÖ B)k§?ÈÚ¯>F5âW/(Bea>Õ¥Åj=и<|::§Ã 4Ãn€¿¢Ó$ Ζùy:ÿ À@§¢ õ“8ÓxÏöºgÝ @~UIaUYÖ±àØvmPÞ»83"5åd®ú¼Œã`ÂÖª IÛÕº{ Å{·‘¹c;ÅLxZÚ'#ØkÈý}åe€ñǶì#>- ‹Jm&gÏ!ªË+0„Å‘ÚÈ”TL-éxí,ѨŽ*²·mÁš’†X’AÎÞ½80šÒ•¸¶Éµ­µ½LPu|/G7­§ª¼SD"Ñç]HdL ÎÂl²÷ÀœÐ‰ðØ`­Eu1Ù{v"F´%ºY8e‡·PNÙGöËÆ\HYó¶(Ù))©@4¥2赟è<(ÅË\ÌP´ìkæN¹ƒÌcµ“îæÖWñ¯w>!ôľ5”ãêq!; :s5so½Ó¿¿ãÖÉ#Øùöí¬X_C¸¥ˆ‚Ãt›ø#qöYüøÑ¢b$òöî£Ù gHîÝœ­¯N`á[ŸÔ K4Ǽ@¿»oÆj®fëã7°3§Œ³Ln†»ÚW'Ì¢ßÍ©,{N {>³ï<Àõ³÷&œl²wÆÀ逼ýÛ²Ðò}|…ëé¢Bçl@ž–À9þŽN£ZcÑþˆNã‡BéøBÁÿ¡Pà;~ÝäÜ£¿dƒH¢Êþ/¦²qÞ¯´ø×#Ü:w·|¿’NmS8øÖ86¯Ød¨8º›èÁOpÛ² F½ÿ1‘¶\Ö<~/'ÊñÎ|RãÁÔi7ÿ0›˜ÀBÖòE5¸êë=ܾð7¼õ6¦êìX¹ISV‚ˆ‚‚€Ùb¦hï!⇾ŸüëáG@>Àþµsq(^¡C"8ó²ê¥»È<ÆÅo­á®\þßQÔìùžu?,@-ZØ’ª}üîc ©" P³vPjîÈ%o.¢ûý@1@ñA *"èû ™4žÜÏŸcá[ŸÛk"c~É`ÂÜe´é`eëÛÙ½l/‚Ù„Åj¥:/ CÜXFÿ’ÁÍo}€-ØÎþeoQeMgÄœeDŇ~þ®ûl9!N?¸+@©q’¿o'@šaæyBÝ ÐÅ…Nãfð#0ÿL$é4]»€BD§áÑ……Ž/ª“ŸŽí)$ îŒ°{9–¿o;Ž*?TF@.Ïæð¢0G ÿ#ÏFpZ7.žò 6s5û¾û»dBu*»Žå’'&">Äþ7sñ¸ñ¨Õ[9º/‹°äÖ‡! D¥D!ƒé4áF¼ô)©Ó1šBmÖ–0#Hõè<ÅQƒíÂû¹dÊl± ¤Þt/ÍcB)ÌÉÇQSw]Q„Òƒ+ص;´[ž¡×ОF'Ðfô‹tÿ×µ„F…¡:]å¶|™Ä®×TʼnhmËÐ×¾¡ë°„DÓDÑï…oèóïáD'ˆ¬ÿt&BLO>;…èä‚Ó/fÀCO!‰9ݽÅ)¢8ª1Æ_ÀWž!®eI—ÞL»çSS\@E™ˆ­e+ŒV†`áɱHþúÆÀ^]IÞþÇÑ:y†A¹ïWÝc¡s6ñ2ЭižŽÎ™À‚æ±ØŠæùÕ9ÇÐC¡t|QŽÿ„øîºí^dàHîž­T—b H8£˲(ØQƒõš¡„à ‚ Ö„¶„Æ…a/.@UTb{ "вT“Ï'p:TEFUAp*(N¬ÁD5oÍ®yòÕŒÕìÚAyö½ÛRô­ Y!¡w{-ïÀ Ì„Ìä×8OMD 2û ×­8A‘C4}ŸúÁe«öýùEP$SkbpÔ¸µe ‰‰Ñ8í TQ" ÈyY0¡;s*Jù!þ½óŽ¢Nÿø{fw“MOEé HSÅ‚Š{9{±NÎzçϳa÷ÎrêÙ{/Ø@EDAzï¡„š^w§üþøî°“ex$ÙMxÞ¯×÷5»³³³ß™lžÏ<Í´`G~!–ia 2;w ½¹†’<¤%$£«°‚`›&Ø`Û–škŒÐtرr!UE`+5¯ÉhâB¿cQ¹ãPÍó¡>éd Âð„&ˆ !eĦyRdŽEäpÂOò«J‹Ë·,þ#%£m̱-SO²¿F(–m˜†… !«<ùfµ°kÚžšªWÿθˆ’W™HÇ3¯äð³n$5Íbî“#1ÍZlU[y"v¾jÛØØ*T*•t¡Eé‚-Xi™j^Ø ÚÎjx"Le ì@Džƒ±Lð)1`T“ؾ/]ϺŸÛBOðÓ/ÁCR—!h(ïHÍföÎ9ÄSŸòfOÅ2m¨ŠPÎõè\“ÑJ# Bcà>T2÷µÀc±Š°00´ü)–“ê …¢Q†ªo áé­ˆ6ê®ZþúYS¼—ƒmƒ7-›æ¡túxŠ*ÁçUIÑ fû†RR:ô A7Aƒm³&PZ ^x=°eÙ*€´f)Ê€¶Áöùðz¡hõïä—sàè÷¸ðÉ'8lÄp:ìMÉfÐ}>´ZÿZ÷RYÙØª›—¬ŸªeÍç“à‹çßCKl†fj˜…[Á§æ]¼z Å%®I–L*:v0îg]ÏàGqÄÍ·ÐïÄ”¬_‰mÑv~y»·Ãm4݃«Û šòìäÏ› *q»€]·£U‡„ÆÀà à6 SŒç"4m<¨üŠi¨äm¡ "ÂBˆF„Š…Œµy-œ»ÃEÀ†ü¿¬²öζ ÞÔ\Ú =‡ÀŠ™ôÀClݲ‰í³'0ñ‰1T%fÒûâ!h xt‹ÞcÂ}±eíjV}þ/¦þ÷C|=.¡kߎX¦í{ÅÖÍ]‡íõ¡U+ç²eéj6Íø±·Œ`‡ÛòS´½r¯nã×fÑÚdv?Šn]Yúêÿ1ë›­™Ï/cnacE5m;DJngü)6k¿z–…?/`ãOŸðõ#÷SMä…EÎØê³-°s8äìÓlœÄ¸Gbêõl=™ñ·žÏôW_¦¬JÇS‹(¬1]Ç£y(Û”Gþü1IÜÖu(ÉßJÁÚe Œ° »z,"sƒD\‰—€m¨JQ‚P_4G ‹Ïc=¡þP(!ÛT”°(‹Áç»4•(@%zU£¼)« Ö­8º`õr½E·n˜ ØÏ ƒ.{„ü›™÷éÿ±jâ¿ñT`k™ ú¿èÚ­9fùzlÛ"½svLy×ǪrݾÜ#v÷¤§fsÚö?•ùo}̇—æsñ»/2à„ŽÌüìï¼öÙßœ~§Ñ¾O¼q01¥'\ÚZ…YÙê4ÙNH“«y†mšjƒ b[àkÑ!w½Eé}ÃùñÆ#ø€T:\p}‡"!¹Œn§œÏ¯ïÌWõ eÿ!¤UÍ!¨…¸0° ?ºˆr«Ø´»ð†¬YËÔOþ7'< UœK¿QÏÓû¨îXF¶i`»bª44lÓ¶C6»¿%]ûdÒ§ã{ý0.ùb9­³ôº Ö,¡xýê °"´Úêh: IDATåöV@ŒÚlÂ>Püð>p*0>¶Óš(Ç¢þŸÏŠõD„ú#Ž¢˜…8âRààhTiÍX‘ŽºÃÑ"´ltZyÀ_‡=ôfVß ¯ÀhàÚšìÊ6Íý•’ÍÛ°’ÉîÒ—ÖÝ:(cß2(ß´ ;µ-¯gëÊUXÞ$2»N‹6iXA•logûêU˜ ™´èÖ O`3çÍ¡¢8@Róv´ìÕ½r=[V¬!¹Uw²rR(^·›®$ù¡dõrôæíIÉL ™µ6ë–ðg‘‘Ó2ê¸'*6-c˲5Mðgw"§G¼`Ù +ضd.%E%øR³iÑ£Á‰ÍÉÊN£bË*Ê“È:°z(¢º`=%; «C»žMÝ6Ù<ÿgŠ6mFKÈ õÀž´êÜ¥Ql*òVð&“ž›«´Õ›ÖQVm‘Þ¶>/åÛÙ¶r%$¤Ò¼K/< k¹ë˜úÔ?™ñâƒëP!#;P†X1P T„FJø: Bcãà$TÕží{ØVþ ª@ÀÔÚÂUh숰¢1ø8Ø‹2Aû„†ª¡Þh „DNèµ(/ ÀëÀÏÀõÝN>ÀéÿúÖðVœ¦)áüÙ„*39¯{BžM •/à®n¤éḭ̀Œðs-´¿P®·ÚÆ þ°Mµ/Ý«¶±]¯yÕç’æ ÍßùœÐþÔ‹ÔÈ]±~6XVxŽ5Ž5Ê:÷¾vž#;4_'1<ôZs𛳝ÈsÔè€i˜¼ù—~l_¾`<ª‚N JT” „E9ªDsJTšBã¤%áþ· á|BÝÑ •°=z'4Q$JˆF ¤5Àg¥_=Q¡WIìþºüŸ™ ,Èûuâ€Ê[IiÙ²Á•…m«ŸZ_7÷ü_9RD>w¯wp×Ñ mÛØ‹ÏÝÝÜì]÷[£ë[”9Ö6ïhûÚÓ<"×Õºï†À›çýNѺNTåÊ7¨½*” 4V¶wï Œ@‰…êŠ#d¤Ë{“G’·…h îÀ¶j€Ï*E¹G[ DÆîDÅX࿸úYT¯˜ôÞXvÝš,,›ð FuÕTc<›° pȪPâ +Ÿï¡Â¢:Çx.BÓ@.&[b<¡ža!D£åµhˆ*6ð6°xÛMFÕX¯ lÈm{Ų Ÿ¬ÆUß¡ñ£{ 4«§}°þd¢Â¯E´R³°g§‘ Ä+Êk±xU,Cö…^¨œÍwc=¡þa!D£Xôn ÏÛ¼²›×g·…¡çŽQfmš7ÃÚ²x>ž&اéàó³›Þÿz¨w…°g46Ìù…‚ÕKªÙ(±`DŒÈêPRfVh ì®Aƒÿˆñ\„ÆÏu¨|ÍŸc=¡þa!DÃ@yú4Ðç \VËk«€Qš¢Â–+Êó—ýøYã¶ætðøBBBƒÀ¶Õ,Ÿ8™òâ@yb4Í¢pþl6Ì[ÖàÝÊÿ,šs>bè…²LXøÅض½u÷Ö¹îÜùÑ ¡)ðp+ð7àÌÏEh¼´N>Cšâí4¡{¼B³‰ªÊT_1‘™ÀÝÀÕ¨¸Þ·¡ºs‚êr<XdPST8£ øuáØ7ÎíwÁõ¤·mîíÐHмPµv>K~šNîÑç‘Û-›âEóéosÚ+“9øŒÀ¾~xõJæ=t%‹8ˆK^ù€ÌUå)ÞÐ=P´d «~[ʧ yVf›ëžXûóÖNÿÑDy+*¨yÝ‰Þ OÄ…÷ض}¡mÛ£öbSmæÌ™Éiii¯tÐAËê}bPªiÚ(MÓⳄ†aÐ UÍQØa!ÔÆ”ªô`]s$ª'À(Wû ¨²³ç£ÊÜ7SQIÝŽ). `AùöÍCæöZö1·Ý‡ÑÀÂB÷²3]WC-m+¢ÜkDÙU4UýȲ@O„Àªo™øð?Üz(ôͼ`ŽÇªŠäzO­¸?Ç)*%k{€ÊRL*B öÂó×\5síká°,§\¬î ·eF”¼õ¨ŽÕÎëîykz¨ˆ.­ë>gžØöÇX~|øÎ<êbrZ$4hD5_›yŸ¾J°¢l=Ê…oQóšs‹ ·¸„Æ@;MÓß›  Ê0Ü«í÷‘ ÂeÅ…ÆU¶ø}`cŒç"4",„ÚX‚ªÏ u+,²Qù×?¢šñ­ ½E%xÝŠ*K—È®•xÜw `30kñ×ïœÔçü«IoÝf·=êͬbË¢e$çtÀÚ¾„Í«VbèÉ4?x0­:¶Ä  Œ}«‚­ó§`ýFŒ€MR«Éé9´,ÕÛ7±iÉj4]£dÕlòW·@×uðz±ÅäMûƒ‚õÛð¤6§U¿£Èl‘ýøtбÙ2k…›6`{SIíp0¹Ý; ï4êu4W¤Ç…Kþ`ëªå-)­;Ѫw_ü>°¶/›‹‘Þ‰¤À&6/]‚¥%Ó²×Ñ4k©‘7ûJ¶’˜•KëþG‘œ¬c[¡|Ö¿ð*+ ’²ÛÒ²ÏᤥéX&TlbÇÚ<²»v§hÙlŠ6oAOnANï#Éh™DùšUl]»ÐÙ±à ²“š‘Ö`f»îÍ‹²jÒÓQ•Ëœë-@Íäíh9‚ïÔú Yºc E•Õ¤7kCF²g—×+Ë ÙVPFjf6ÍÒ“ëz^äo¨)qÐUÍQØOa!ÔF%ð=pªötEìóTàqTGí›P% #ï¿ON~Aå9¢Â¹3ìtÂFž üZ´~õ€¹½ÔlÈ­4Œ°ÐÁ.[Éç× Å“Ñ3ïŠCîo˃9òžw9ì”Þhrþxðr&4ËçE÷è+d÷;‡3^ø€àä'øêÙ—˜÷ô…lÝðgž„ÏÜÈ̇ÎaÇÒ;?2ãó8÷?ïТYb Ï…¦f2ï…¿òý ï„Oª?‡¾#çØ›.Ç‘¯ {aõG÷ðõÃOPQî´.O ý©·sêƒcÈÔ¶3íÞsX[˜CBÅ*ÊvÒú0Z+gÌܹ¯gßÊcž$ÉÓßæË»ïbK^þÎ×3û]Â=O»®lýîc>=šf½û°mþ<œ6™]Fpþϳî©Qüüõ7ü|Ç0ªÏqž‚Õ^ M,˜ùÊ#*ÊV¡ÂðY4Qá 1†„&Á܉oqñíÏÐèżóÚ¤»^3Ë×ñÏ Ïæý%¼ó)—Ñ!fóâžtT~ä—À‚=l+4!$y[Ø=€þû¸Ÿ6ÀS¨nÞ P^wˆÞL¬ ˜æz͉Y·¨™moz‡ë§nàÒgŸ!­x1ÓŸúÛ¶Ù7Ì`ê‡cÉr3WLØÀõWpâWR4g,ˬ§Ý9÷qÞÝ· ë rÏ7üågaM(/ ¤ Š“_œÁ S–rä_N¢xö׬œ»v—ÊNš6}ú8ß¿ð­ÿßoຯ&Ó£W"sŸ¿‹¥SV %„ߤ%@ÙôwÿÈ´¬38Ü8i!G ëÇ?ÄÏÿù-) OB siν\ûÓN8ãTÊóg²rÁŽ}f ןA÷ÞY3ñ ¶n1ÑòòÃý£Ù¾=›“žþ…›~^Ïé·Ý@ùœ÷øñÞg©ÖÁëñ [—ê÷Ô ®Ÿ¼„AG  hŬ^°†ƒï}›ã¯½Hdègrø©Ç5XÇmO¬>‰•“¾°€)¨¼ ák-2[ò+„&ÅÀ¡çqx–ÉW¯¿Æë?.ªñÚäןá©ñshÙa§ 806 §]€'c=¡aa!ìŽÙÀ·üï彨äë)ÀÙÀ•¨&9«÷òýnQá¾;쾃 lðý^Q¸}Ãô—Â4¬«(dUWÐüð›8þª‹Èh݆NÅQWžA`õL6/Ù„Þ¬ Ç=ð:Ãnû'-r[àKkN‹¶HHð¢[6žÔ ²hîõ’Ò¦+™­ýض…i%Òÿæ—éò 2Úw£ßy#ðë6åó±ÜǦ,à÷÷ÞCkuCº—–´!½ÇŽ¿ã~ë–üŠe8 :^ÝbÞ'ïPhɱþ‹Î‡v#¹MO¿åqZ´JfÓŠ±T”i`I9ðl†ŒMvûévåt½ö;ãh²û ¢K÷Þ$˜:šf±aÆWä­, ×MOÑÿŒ#HÊnK¯ÐõˆÎl]û1…ÛUî……ÆÁ·?Ç€s‘Ù¾;ýn¾?°-¿¤œl2Zç€iº“šêos]Ó¡º¤’/Á¨ª\Œ*¹l¡®¯€kDóVˆ¨š‰Í:ðè#’ZÈc7?ÌšPŸ’¼™<ðЋ˜Íûðø‹£™Oì,bÙâùÌ›¿ümE»ì+XQÄ’ù0cútæ/[GÝbO&p?ªŒüžzT M …vGåiø%>ØË÷éÀPàï@_Tµ§Ç€MÿÃÜâÂíµpǼ;!*%À¤åßzÉ’q{>çBŒêè;­Kì E³¾íHðƒT“mÞ½/0۬›Չ–¹é,|}?̛Ŏµk¨6 ð¦£; Ö–6ØASUµ² l_šåv %>{R›ãñúÐ*+kZ°Ø•EëæÖßøîºÃð&&akféjL väªÏmO0H‰YÁíürÏPæddcÙ ·³ms¾ì ª+ƒ yð·LªÌ XšŠ¹Îj™†m€¥m™*qݶ¨¨Ø ¬xã Þþá,ËF³+Ù±t%†¿+eÅ™š†/íuǪTûð%f˜UA°Ø9WË îL$¯otüþÖÓäÍœ\‰ É«¤¦ pçõD6ÈQ!4:»’Ç®›«žxŸ‡_:Wn¿€×»—i[*¹æ1œÐ¹«f|Åí£¯áË™[°Z3®yà9î¹ñ"šû`ÛÊi\xÑH&Ï]‹ÏSóqöÈ;y扻ÈM“f:Mœ«PEWžF~÷;DX{b*ð\h”ãv³mpªšÓ!ÀDà$T=ôÿ…HQé±p‡¨T ÀBl{ú´§ÿqtnŸ4ëбÞ+ Ù€ Tµ% lMÝ›Óü>v|÷:ŸÜ0’òÜ~ô;c8½;vÆ»c&S_øOíž,;9 OZb¸‚“Sv)š+ÆÛ¨Æß¾ÝÏŽÇ4À¶Ðñ%xñw‚†¹³J“Ú)­è|Ö¤' D€ÏÃÁç'âÉéAR‚¥N~nºkê]k# Ðiú ´m•†´A×𓌖ҜæÙ^¡Ú±š>;tlZŒWxaݯәùÊ# BñòP×W5CîÜâBã M—Üúï;’wÿý(ݳ·ñÌËÈ9îZî»ã4JÖÌâòÓ/à7½-c^z†ƒ[ë¼÷à½<;z$©¹xèüþÈ{-Ue6z‚/¤lÐ4tŸO•q°l5ö„ ZB2©t;ërZ­AõêÙüòîÇø;…¦»"=R< `i´=r8JÇ0€âmüöêÓþ< >l´àÞÙ˶¦“˜˜ؤµ?™#®;£<†Íü÷e{~5?Ø;]{Ú¯†Ç›Pï!mºJ6meÒ£·(/]Š*à­îP(' *Ò[!¢BhRøsâ‰ûn`àqÛÈQèíxï»iãÇ?Ãôžþr,=ã`NØ…M½úñÒ‹?rßy½Ø²j;dç2hÐ wjÆqGtCKÍÅï“?•&Œ%(6¯Æx.BŒ ao¨®C¹7OAÕ¤žˆ ‡Š£<x œN݈ ¨¿­Ü¬ÛcáÜ].Æ­˜ðiÅäÇo+܃¡î±ÑýÍÙ>þ5&¿ú6y«YúúýLýh©ý"÷ \t¿Ì2 ÍfÛòÕ,ÿäÆŽy€€­±iùBª«lðøÐÌ vÌþ™­yå5îæG?%5ŸZþVrÖé6üȸGaãªõl=™q·žÏŒW_¥¼ZÇã;Ô0ÂÀKïÓÎ&¹j#ß=8š¥³WS°|>ÓÁÏ>ÌÆ&>Ÿ¶Wæ²³‰ij´x*mZiüñÔMÌüæ óÖ1çå¿ñõ½ÿ`ùüíø÷î¨44°lœ9•’’ WºE÷@ ¬‚ ÷^Ãæ¿oE]·å„¯'·ÇÂåˆ\ɯš,ýÏ»›;†+ï°«Çpîàv¬šó3`ðÖ[rõÈ‘\uÕH®¼ãQæAÅo3Ùb&qÄ=ÐVMdØÀžôp ÷¼ðgÞpwÞ4$v$Ô7—¢âÝtÙÞo…°·P•œ>D5Íë‚òl¡]ë Çc¡6肨Ý>ž 'ʇêñÝÜ^8==÷ßá×ܦn-×y§i ¬*üÍý¬ü׿?©> ùÀÃ9â–HOÑé8ô<øâc~¿o¿MÇ¡§ÀœoYñØ¥,è1Ÿ^=Ž¥Uf" _¹œ^Î:ö…í¾]¯YX¦e{w1´mÚ_ôOŽY»Ž©Ÿþƒ7¾UœKÿ[žãàÁ]±‚˜¦ È:þ†Þ¼€^~“Ï~KíÈôqà™÷qÜUgcùضQ#Q\‹¶4m,+ˆmHè|Çßñ0ß<ù®<’’¡²‚f†sÊÝ·’hƒmYظ¶5ULWNš–ÝŽ -õ9fþó4,íkŽ»ð´:/7«yÀ Lù×]¬œôe9*—h3êZ¯BåXD ƒr'mK~…ÐtÑ’0¨?¼9ã†SlÏcë@ Y•RiÙhºÎ©]A³¬žøðpù#ß`ä>ËÄi¿2ÎÆÜñ-cþžÀÝOÎ}7"ÆGÓ£+ªÕ€ob<!†Ä& Yþþ’îG £§\ŠwlaùÜI ¿bÁN§1ã§OÉŽn}ÇkšöG¦Ðp$£ÄD p<â­Ø¯‘›Bc!Z®…SÊñ\T¢®iG€x€ß,Ã0}qÌIåÛò‡Üö8©-ša©³jCš ¶î#³gošwïm«};]·m’Zõ¤SëžhΘ٥™šzlYÒå RQGgÙвK3lÓ5Ï„T²»uö¢{^lLÍK«þChêÉ¡ù yÈ8 K¨jUèXÓhÙ½çÎyÙ6$´nKóÐqØ6˜ÕœÛ•Nmº†×‚Pï@mrèÜ¡^ËÀòiì6ÒShŒ< ôDUƒQ±Ÿ#ÂBhL¸s-tÂÕ¡<(ÃÏCXPøBKÕ#¸à³×Nضl~ú±w<ɇ P£lÕXj,SêÑ‹mîjF&”ÛFÍmvi gGY·ëtv»M´yìnû]¼;Qö_Û±ÕjqG›c”uuÚOS^ Ë€Åã>cÊ¿î¢(oå6TøÓ*ÔõS•DÏ­pwÜ–¼ a¿À0Ô¥o¸~ØzŸx·]3™'_þ=¾}—V^‹•Å´;ö¼å\¼)~¹õ .¾ë}Žì7žÝ’X6g5U ¹Œ¾öv2cw8BÝs#0¸åõösä¾Ð˜ÐPBÁî¨$Â!Q)¨Ú©¡eJh›6ÀÑ )iݺ =›C.ENÏþx|šÆ\Ô@ V±â§oð·ëK›ƒ:¢Õcõ)áO ©¡iJ“ ¿ÿÄœ^då¤/-ËÎf :¶W£ÂÜ£åµpÄF´(ǃ&Š? •·b.?ÿ¾”C?®9©áŒR¦|ú1?-^MÀN uûœvî_8°YrèC*ùñ«Ï˜6s¥©Ù­|òéœ4 Çî>NB¡§¡*A=ã¹q‚ ¡±áäZ8^‰ÚÄEªk¤„ÖûQB£ÐÛ—”šÛnàÑt|"-ºö&15_R š×ó'Ä…Ÿ? Û¨ÆÖsà aïÐÀ  V”SYRÀæE³Y3íòçÍ´Ì``0UlÀÉ™¨@‰‰r” p?N8”’'Ž?#,‡ã€±(aqêwQ$Jht¸s-œ`eøé#tßzçûf˃•eWOù¦Ãê)ß´Ñt=91-Ããõ§hºÇ³wuVãWÛ;4l#h*Ë­êÒb ó»Õ¬)%‚(Ñàx%Ê]ÁáäUDvÚ–0(AögŽ>A…’Þ‚ˆ Á… ¡±ááfeN˜T¤¨Ð"ÞçN¼]¬ü¶eeT¦R\˜;«*:Ä{¿ªðB €øŸo}é=p ‡bÂùFhéäR¸“µêONÿ §)^¤—BD…ÐØñìy“˜€Ü$‰wNަנ~3a'",„ƈ»§…FØsÑ…SEÊݵÛ@ý3QFd꟭#P"÷¯X@Ôñ,¶³ ·G!²‚˜SE¬2b8âÂ-*œ¤mwIY÷¾E\™÷€é4@8ߪU«’Fuqqñ¡çwÞ¨¿þõ¯ kÙÔù-_ZßsþgÎÞ¾@5Í­ˆél„¸$Þ&A¨ Çxv*?yC#%Q9ÎH-ý®álë#\MÊÙŸFãAT‰¿v¨X×P‘ÛýÇÐwŒÿH1éîÔHqᬋæ­0#ö-ÂÞÓ%fÒ€ó屎ð'Ñ€ëQy9¯w"ž ¡Äc!4VÜ^ P†Ÿ†2ݸL玵ӗ RX8=0ÏE¤¨ˆ4ØcmÀ;ÆtªcôfÔ±Äz^õÉ.•m#ž;Æ¿#ïTäwïUDr¼"*aßÈÎF‰‹ŸPw¾å„„½&x¸ø;ð,ò;(ìBc&2$Ê@應¼s ¦¨p GT81ÈñVd­Qw§ *ªDæ‡ìO¸ÃŸqáÎw_1Ü¡OA¢WQ!ûÆvàBàeT§ûË 1‘°'ÏY(OÓ·±ŽÐa!4vÜ!0‘ë#ÅEä]kŸk8½1Ü à°{akÏ@èŽ ëúU*5ž…P]°»Ü†ÈÜ ç{æµp_‘‚"š§Br*aß)†¯£„†«ñ‡¸Õ›b&JT¬ˆéŒ„Fƒ ¡±MX¸ ÁÚÂb¼(…;*š°ˆç 8•ì¸&Æs‰5îî¨d4àçº÷=„›¨&¢Ç%¡z ù¡á­K ?5›¯º£Šö(¯D 08²?_AhèÀ—ÀÈ? Aš6ý€µÀ¨„ãúÄNo#GL$¡r>RQ‚!8xOƒ"Ôx8•xîm›zobh_^Â"£>»}¨~kQý(.C‰AAØ…PÍð.õDA€>À:àT~@])(Pb %ÒB#8xWƒŠ£’<ö'­uûƒVû(¿ÇFUµzÕX.3ôžôÐ>’ {5êS`|ìž²ëxÿ‚ BãJ` õ÷N!^èê×3èTûs‡ºFz(A‘Žò:´†Ÿ{ äØ$¯ýu®nWuƶ»©QÕû‹Öº}l’×öj”¡ªõzo *”kOc_D†¸ uÓéGÂÍSAa·LEu©Õ÷´¡ B¢#J\,ºìÃ~¢y(œÜ GP¤£DÁ™¨^A㓼ö'­tÛê]#F7l« öç­uûø$¯ TŸgÍ]û­kFÂ^Š;PâHAöHw”·âŒXOD!´AÝ\Yôý“ïu aA‘Œ [Ê@yrs€ïüšV>,Ùk—«ÙÁÚEèŠ=¾µnŸ”ìµ5ÊïP%Â[ö`¤QS`¸½÷Öƒq.*Á}:pèŸ<‚ Â~ΨXãf±žˆ BŒÈ~F‰‹>{±½c¤GË¡pò Ò–(Cý+§¤xí/[{vŠ…= Š(Ãîª<''{mê5ôEè3r\ŸëŒDöÞƒáþŽòмdý™(õ‰T•„Ƭ®Žñ\AbI3àMÔ]úóQB#’½)n¢Œòc€+S4mðÉ)žÄQ&G%Ûh:ûÞ}BˆiåÏ{ø¾Ü¬.·í™¨Ö)¨T"3´´BŸì,Ý}0²€ÿ§w¢š BÜ ÂB}€ßPñ¦Æx.‚ ±&ÕAúhà/„ü³h‚Âãz:p*pY‚Æ‘¦z¹.Ãàðäлêº]hŸ3*àÕ/ï–šl{*ª’Ô7¨f§„>Ù-0,׺ö¨‚-€áÀ´:ž¥ ì3",¡qðêÎÜ TÝtA„ýTàeÔ —3Q¹‚ rB‹LT Ö€+3tmÀ)Éï-Í &†¶¨ïþØ!ñ[ÆsAˆœP' Öô.p8ÊðžŽ*k¢’²O®ÊÖµþg¦x´[šìí%Ö&yH`Ì«‚g ½|QnÚ…–=•C2uï n0 ¬&ìѰÙ5CbŠ AˆnGU„êTÆx.‚ ±Bs-ÝáN6Ê#ñ2*\t8°UâõŠt]ë{Yš‡ë3 zÆ‹ ˆ$tdó+5^)öðN©I±eÿ†ò¾¤¡’µW¡*G™¡á$x»EÄßÑ û",!¾I@uRÜ㹂 Ăڅ“;¡£Ê¹f¢îîlÏÖµƒÏOópc¦AÏDbòôg é²jxºÀËGe&…–½xÕo3á#qçaDV‘‚ø?Z¡ "ÂBâ›.¨N³§?Äx.‚  ÍîJÆ:9Ðõ;9²¥WïzYªÎ5™]>ÔÍÄYgK«à•b/o—Zl7­%À[À—ÀÂy‘U¤D`1C„… Ä77£#M1ž‹ BC)(ܳÝ%esPIÙW¶òèÝ/HÕ¹>Ë [c‘¸Æ E^>*³ØjZ‹Pc°1´¥ã±pDFm}0¡^a!ñ‹†JÖ6P¥åƒ M=5µs¶; ’™/mïÕ; O×™aÐ>!´—¦ök²Öòðr‘·KMÖÖjTè×§À:ÂswçÁhjgFˆ3DXBüÒ U:ñ5àÉÏE¡>ˆÌŸ¨­±z|ppNŸ~à5é:—e´ñ…öÒÔÍæÐÙÚ„‹½<_l²Ö°WŸ£JÕ®#|¢5Ú‹MýŒ Œ Aˆ_ŽDÕ.?˜ã¹‚ Ô5ŽˆpGË¡p<]Q¥d/íâÓÛ\ž¦su¦AÎþ"(" µ-Aøo‘—÷J-V­õ¨&zŸ˩݃á„Ix1„:F„… Ä/7wPM’Aš‘Þ‰h²­Ð²ª‡Ï]}z»ë34.Ê0Éñ"æ°ƒùAø ÄËmG`|ê…±N«S3#šCΨ°Ïˆ°„øDÞGý3¸ ÆsA¨ ›Cw-#½Î6¡r(.ïèiyEŒÈ0Éò†^¸&¡³VhÀkÅ^Þ,±Y0·‹‰^¦Ö-0Ü^ AøŸa!ñ‰X„jø$ù‚ 4fÜž ˆòd¢~÷º¡ÜÑͧçÞ’©q~†I3'ËBØ3l7à“/Û, XùÀXT¢÷r ƒ]“¼A<Â> ÂBâ“Ö¨.«ç߯x.‚ ÿ n!Y6Ö݇BFyg/î›èɺ2]cx†Aš³…˜¸ŽÐ™/7áõb/¯–ØÌ¯6 Q9£ú#9B¢¶2µâÁþ4",!>¼Z.ˆñ\Aþ µåO8bÂñP$½ËÓú&zšß’ g¦šdJEÝ¡A_•yxºæU›ÛP¥Ìß@yÆ« 'µuò†°Wˆ°„øäNààX`GŒç"‚°·D–‹ÖØÎ‹òP\ \8 Ñ›~UºÍå&~ yªBÖ^… o{x½Dã÷j£ø•ƒ1»÷`È·#삟¼ ôŽF~ÈAˆo"ó'"Ü»á~ ?08éðDOÖ­Y6'§Z¤ˆ h84"5¾ÌÃÓE3«ŒbK…ܾ̪g‘CÊÔ »E„… Ä'_ OõDA¢­±»d¬#,l Õ—çJ`è Éžäë2lÎLµð8[ ¦ãË4ž/öð}…QLD Œ¨)§ÈîB¤@¾E!„ Aˆ?tà` pUŒç"‚Im 5«<¥«t8þ¿'eT–Í)) ⡈4˜ðu¹Îs…S«Ìr ¾GõÁø¨D}¯în‘"0„",!þHGÝ5ÜÛ©‚ ì$Zc;·wÂi–„ ã¼J‡!'§xý7¥[œœj¡‰‡"~ÑÀ¶à›2ç‹u&TULFy0¦ì¾›·x0‚‡´ ìŽ~&ÆsAˆìCáNc;HF ŠË}'”ìõݘaqRŠŠF…–Êuž-Òù±ÂêÒÀÂŒÈF{REJa!qˆ AâÚÊÆ: ÙʨL†#5íè’<¾¿e›BXrPvŤ çŠt¾©0Õ¶ýaQ‚ªð).D`ìLj°„øC„… ±¤¶îa©ÀqÀðDã†%{õ3L†¦Ú"(š¡ïòÇ2g‹¼L¨ Z›€·Q¡Reá­vñ^D “š0",!þa!B,ˆæ¡p'e;Šl`(peš® :5Ùë¹93ÈI„%G]bоýQ^îÀ€º‡^—T²çcÒQíþbeUY¨"±N*µu®êr>!ÆôJxªÈÇw†QfÙ¿¯¢ÆÔÙw†„H퇈°„ø# øø x8Æs¡éS› pçP€ y:‘¢iÇž–âå†Lƒ£“ëÑCaCßþ°c9̘ Ö9¾§AÇvàóÀ¬É°zQ݉ os8öLHôÔ~|ºJ×Ãôqá­ ‰¤vƒN‚Œ$À†myðÓÇPnսР}דËuþ[¬3®Ü Òf2ðª¢aIhKÇc­ÙžŒ&Š Aˆ?¼¨çåÀµ1ž‹ M“È>ÑÛé(³µ%ppu–®õ;+ÅëÕ,H_ç}}š‡ÀMÏÁ7ÂúIpÖIPb¨™UC®…'Ÿ†æ~¨ØWÓUÞƒ}ÅÒŽ‡)ßCª¾ûm·Ïó†ueuoÈïŽÐã2xøAè{€ë ~ýþv%lݪ¾Íº&ôÝÏ©‚g ½|UnÚ–=•ƒñ-°•è9ލpwò‘ÑDhÈË_„½ÃŠv±žˆ MŽÈ Oî„lgél—œ\”®ký†§y‘iÐ71õmj¨E j¡:TÃFÃCC¶¶,„;¯…_#D…cκÂf­#¥Ü¸_³=ª‹,þ¶W'➬îõ3¡´,ÜNÎÙÄ ,Ój[5;Ch(ë,òÖoäûm ¥<ðôÍÕ¿Ãko€¯Ü|- :xF]­¶¯ë[É¡ï¾_¼á7˜_öz‘÷зK­C -kðÊó^@8áßàíœ-goâÅhˆ°„ø$èM¸¤Ÿ ¾âµ…4;Íâ5ð÷ËaêR%PÊ xb4ô¹9°fKýY|¡¯¥·žnepC<]èí÷i™Õw›i ^¾6ö`8RʹæIåÚ£Ða!ñÉ"T,skÔ± ÂÿJ¤˜p‹ 'Ù 9ppI–®õ¼:ÃÃðtƒ~Çc@ìM¾jN?Í ÜzLw‰ ð§Ãèç`Ø › %0ÿ7øð?ðÕ·0ø¸çf(Yw_y!ÃÛnyŽî?¿ÏÎlÐ<`hPV‰»ÎË Í:öopÓÅ0m´>Nè?ü>ÝwŽ€?‚·+œ|,Ì} n¸]7ý ô‚”ÀìÉðذl•:ç‡_wŒ„eS §žk~€·V*ʪù0k)¤¡É–9ê}~?$$7ÀÃÎk£k"¼ÐÊà¦*´×‹½½Þ(5Ÿ)0íÀÀ'¨) %›œ$oG²ZÄV¾ ûˆ AˆO~:"ÂB„}'²…»ÊS;àL`x[¯Þã‚T² :ú\Šx ªŽ÷?¡DÅê_ážëà—yaQa‰máñÏá”CÕŠÅ‹ »== æY°~3tìɽ Ïk°v¼:ÎôSàì³ u"L~ ´“G³TÈ“å•p£UAu&›w†ƒ{«áàZ½{«á ÙpØðÄ+íƒ-«aùVè{s.tï #N¹› y;èÓ[ ‡ÍI0ïY¸bTæ…+r¥u‡ËnSÏ7¬€mkv ÷ªOB×ËA~xÒop}&<_ä=ø“2ëà †5UEj<°–š×¡‰xé=",!>Y ”¢Â¡¦Åx.‚ 4nœ0G\xBëZ—´ôèÝG¦{™¤S¢Ÿ)µ¹ƒ`ÌI EZ6fÌ«Y‚ÖN»C‰ŠòMpÿmðÝÐìpxôEÔþ:þr'ü²N8Î>ÆWwúÏ-¡r-Œý|CTn‡ž £^‚K+vͱÐðÜhøæw0ðú oÁ›À¦y0àáõ¿¿/¼Já÷”¨˜ýŒ¾Ö¯‡C·^„n}`øupç?Áªcó ÆÂ3/Cþؼò> ³‰]`Ì[pbo¨Ü /<¢ŠÀFh¡!títJ„çܘ o{»¾Tb=¾Õ´®BõÁøúë\“5ÅE<]Â^ ÂBâ“*”×âXàùÏE„ÆK'”Ѷe¬% ².îiëÕ;OÓ¹!Ó u‚+ä)IËQË >Ž¿þò|ð-8Ñ>‰8ÿtõø«·à½ +6L‚ç^‚ƒŸ€îÇBö˜4Y ‹žÇ«îA›Ò`àÑêlý2òMHr\6^èÜ«–‰ÙÐ2µæ}öeãáŸWÁ¦Ð8,ä2Ø2n¿–n€^À®„gyJ$ýü¼r8ç Œ'™°—ˆ°„øå[à)T©Ç­1ž‹ “3¿s€/€_€õ@TiCŽü *³/,üž|î~ºd­OÀÜŰr²h<í!;Mm{ü¥ðãÙª¿…’!ER¡]ü0n»²;ÁÉgÀØ5pd_ ßM„r YMWÉÙO]Ó6(Q³ 4òfÔ4à›†:ËA×ú óUp«h ‰l_ —…ìù¼¹ªÔnR*x¼JXhÀæ°> öëvœ@¯á0òd°ªà?·Â;ŸÅ^TD!Y‡V^›j•ŒŸ†j¸èîè’cÑha!ñË/¨mg/Çx.‚ 4NªQUžÚ§Û‰ÀàÖ¦5äæmÖ°ÿëí®Îи$ͤ…cŒÆ›YW¸î¸þ¨ëYxùÈé ÷ü\wu¨9]›pÕ&_"¤{@EYX¹ ¬J(Õ pL\ç÷ƒ>ƒaI•[‘?fýTÓ(×€5Ë`Ö¬èݿݥl!\Á*wy[3äâ(/¢¦ú@%‹ë€­S#ABÓ¢çK˜@§ƒ•Õžÿ;¼ónô’µ± 4‡m|Tâåùb‹¥s+ðªçÅ ÂÌ ƒr¼âµhdˆ°„øe08xšýfAö†¢ˆçNå§ €ÍÀbàûÅ«ÍèmôûO‘Öüš ç²tƒÜxEy°©ZÝãž~/¼\y5.™/¾ ‹¡²Hƒ/ž‚§_ƒÔt°‚àÏ£†Bp#¬Û¦Ì؉oÁ_úÁaC y¨Æ?šŠ]›ìyT Ù^yj³èCëmT®† ´ìÉ^( ¨×L mw%` ‹Á €­Õ|´Ý–­9¿Á¼ojö¼ˆ¡Ïß„7Š|¼^j²:hlÆ_£r Ô‘VNävx¹ú„½D„… Ä/6ððÐõ#,‚à˜Œ”ùë ÷ã$”Üg7ûi ŠEl_´·ÞµÝH|¡ÈÓìâ4ë3 Ú;mbmâé^ð„nhkÀ¿ïÞ½àÐ6pËc°r1Lœ‹×C8ì$Hø7ämSÛzn=ŠçÂÄÏÁW 3~‡e…Ðcäh@%|4vWëHC w'}ÁÌ_UÐú¸bÜÿ¼úÜÌ\8ç"µÝS•üóì!Vͬšo¡4/¶ßUèü¬ À‹E^Þ/µYo7€É¨jPÕ@ *àËDSÊ%42DXB|óP\Üã¹BcÇÝ .ÚÐ÷ð8rm“N›àZ&ÔòÜY—èzí±#©) œfcN5Èa Œ·½=7EÀ÷À¶<Ã<êÑBŽx§Do1"CgDºAGç~C›|šVs j¶å¿Ãc÷À/Cj+¸óøãxí8ñ8h¼= ¾üz§zGhÀ{Ï@a©ú¶ §Ã¬ß¡Ç‰jŸ«~„Å¿F„Ùª*Ô½ŸÃÁè¢|-Œ¼ s×¹ª5×ëÀæ/`ì÷pã0â_м/,ØÃþ‡Õ[`üçʳ¢9¢¦UN £GBá: Ê«÷æìÖ-š¯ûx½Ø$ß4¶3PÕ 7¢<~Â×§»ô±[¶‰°h¤ˆ°„ø¦x¸xU–O;‘ڃƒºÓžŒ2ˆ“CÏý®õÎcg½Ûàvß¹O@ýóîǵ ŠÝ=ßáa¢Ì¼`hÔöØyîÎúÒˆõÕ¡á~\±¯hŸUú ©í>ûf”˜˜‰2ü’TTÆ¢¦ÕwL5ðõb½ùi:7ftjh¨RݱË*À²ÂG’üö[š)9{a-™P¾ <©àO…@!T»<¾Ðz³Buw½ 8ä"8b€ª0•¿¦ë Âù¾$ðg(R™6yžª¡¬p§vŸ “¥UðZ±—7JLvXö`!JP ¾‰jT!’ŠÐpW†–U¡áü=9ûŽçM„E#B„… 4žN²k2¦ Ôj†ë4CUjŠÃo9(‘àx |®¥;¼Çƒ2Š€âÐ( ²Ð(u-c£6ƒÞFÄc÷ˆ(­#G¡¼>Ô¹ÿø%,ªQߥÛC䋎@t¾ß, 3ЧµGÏ>;Uç–,ƒ.õ-0©U[~Cdçg uE8ïuüUÎ{’3l¸ç8÷pøéyã®±{#õÜ©ÆÑ溻c° KePBÃKÍp¬=ƒ½Ý¦.ç²<]àal¹ÍVÓÚ,BõK)DMJ^mt<=Û2~XähÑÚ3ã  ²6oЯŸ¾fÌ•YQ:R¯åœ:øÓ›—k]µÝM–¡ß[«Ð .Z=@¾°Þú©QÁh¤¿ŒPHƒ¼6ŠD,¡íÐ}1ñZMé ¢ÄÀ`ôDp=°.u»Xž ¬@‹ˆê–®ÂFö`媰>¥V¡òoýŠU½6€AÙ/Ê‹pEa’QmA`øcóh*©„¯Ý'õðh…Í£•µŠMèt§¥èȃeðÅ‚/‚·AQA¿S0RŽt mù BÛâà·ÀáH_‹ý•:mé`à$t}úÜ€Nu™ÌB ßßPŸ± ™Çϸ–Úõ³ø#ͯ¤.kÛÜýpi\"Õ –o]NϱÍë Fg«Ö/0„FR¯Õ‡uºÊÓË5ŽWé©-À@_vÐ(Â)Oµè”§tiOu©Çø[8Ý)ì­ævÂN$b!틟¿@§DÍÉðXM7àetÕ®sÐù͂Ж G/ҽà ? Ñ\³½=U’òÞÐÕÔ5³.Ê3¹²Ð¡ÀN¬½OgS³¶ þ^añh¥b~«EG(¡–ü sžv×àÎ\’¦fìæúPH„Bh‚ Ah_DÑyûÙÀiH ÚL“<žˆŽXB{ X= võaøâ"(2¾L™ZÿÖ?N1ڃѯÄÌýAÉ9ù]"´ß©­›’ðŸJ›û*<–%½j`0ŸÆè„ˮ՛šëAîCLy ›ïƒÑ§°…°‚Ðþ„.]:¸6ÃcÙßù5p 0]‘EÚAFºF{ÍE0v'0ö$8üÇÚ@ ú;oÈ ˆ™}I¾Áw \:FR£jëÓÝÔÙÝ’„¿UXü«R±$éÕ ýKhŒPÍÕaá°§Æváô¦ ¨HWL*< Í" Ahl6¿B_xdt4û/Ç÷ßC7¿„ý…p®}pBšÎƒá—«m®SssÍÖÚ°X³ÍSÎÛu*÷?Uf,é™ôŠ(ò-Úîªkðà›+¶Àk¼ÊmžZ€öP,*ؽ‡"è£z)ŠÝù'©O¡vK[ý¸ ‚°gþ\ƒÎ½ZŽBà`9ðMô…Yöš«"•®Lm°›wЃ±7Q‹æ<%è²ÎC{ÛfÞEùW¸ôˆ¦FÕڧé³·>UØA“·/0úúÛfÑy&W8ô¥F”驲¡Ç°,¡x¢Òc£ë•£ËÅ.Eàpi{Ó‡¢9…ïÅH—n& aŸ ÂBÚ?磴ù˜@/¤RѾÄ^ò“F…‚$½W‘J×l/]Š”ßÅ{Oݼƒ)Rù@?`HËìxN®É÷ †ÄØ9¹oQR³¯õpïŽÏÕ¸lu½2´ XTÒØ%ÛoV—N<={›òÔ\§lñO_‚Ðþ1€‡Ñ•‰ŽE¯‚•wEÀ× É¾â<àŸÀ©èNè‚ 4¥9Fº†ïÁú0|±öa¤ÁFX` -2Íä›\Uà0¬¥F@PÜWnóL•ÇvÏÛJ£‡¢Š]={Úšk`®ò´».Ù’î$ìDXÂþA!ð*ºbÔchÀ t´âhtUáËQ‚.óû1ðmİ-{"ìÁð£a£·/,Ò‰ŒÝy0â¡¿ŸKJ`[f§¯g›|¿ØaD<5²}=½N‰–9õp¹ŽPìp½ÍhA±ÝaÜOUj.B±§”§ ˜ŠŠp„ÂâŸö9",aÿát`úÂâ—š®G7Ò{7SƒjGü¸ 8½ò(ž ÷ÁðF°›wЃMÝwòþ¼ÍöüF.Úƒ1$×0º\”oqyè8û&‚‘zvŸÔÁC垪v¨öÔtðåè2°AÅî¢õìê³G'ÂiOA!!‚BøÊa!ûg¡ÍĤùÛ÷€ûZv8íŽÀ tzÙ‹ ´EÒ Œ ÷"\ª6}W’ú"ejs€>ÀðÓètzŽe|¯Èåиúb#õ˜u÷–Ûü¯ÆQ•žÚŒî’½¡hΔÝ\µ§æšÚíÉC!‚Bh1DXBû¦3ºQÞ4ßóqà+Ÿ}@{X¶ev8‚ЦiN`ìm™ÚÝE0üß…S¤‚ej³ÑŒ¡qÃèú\‹+ Æd³w#µÏµTXü§Ú¥A©è”§èætÁ’±é"éª=ËŦ+éšÚ±Ï@¾",¡}SÜ \@óŸ÷À¤ÏÅeð,p.ð¿ EÚÍõÁØ]£=_XøiS_¤Ùž‰Ž`ô†çFçSr,ë.Gg+ “]§ç(Óká;l^­uÝ:¥6ó€µèŽ×~„"H¥hÎí ŠpÙØæúP€, -ˆ Ahÿ˜À•À-@—4w€QÀÜ–T;¡0ur„}M°ÆÞŒ`#]™Ú½¾P‰“Š`ØÐí¼<Û¸ºÀå¨lÕ8*Sj ®´x¦ÊQl@{(V¡Å‚Ÿª´·‚",*vPì®E0:! ¡Ea!ûÃ?¡û+„¹¸¿e‡Ó.øppzUR„¯Ž½‰`í#á©`ZÔžLÞ&tÌ6ŒÒã²,ë'E®2¸«ÜbrëÔ+µ-(Ö¡#ŠôéN»kl4qû÷ýHÇîúP¤ë”--Ž AØ¿ÈA›µ‰Î%öy¸0##j»\Žcß^ÈðXa"]'ïæF°šTPdø¢¡9aáû0‚&o3u¿jÀ¥'ñËЂbM…ÀîÒžš3f}é"éºd;e BFa!™'Ýç0dß§øÇ; ø#08õóÇèHFE3ãQÀ1ÀËÀ]èj[þ jøõ’ ½ |u¤ëäÝ\šTº©½M –¶µSÿ;†.ް- ÑÿŽT4'.Ò Š Â!½B:e ­™@BË`¤¹ßÜmsÛW(ô…«zR|ZPœ|‚¾ˆ éI SÊ^¦¡á%Гº®Æ"“AØ·|^®$a±ÜÇ|ð3“ÿ Éº¹ŠOá´§p ?ÕÉ"(„6ƒ½ç]Aø¤+ÞšÛÿ«$‚ξ-&~ž0ÏF¾š#< ln@_üc¤ éL”RúQö=áÏ™ž€û"ÃE ²nÓ´ËuýèOú›Á¨…/Z qâïG|±®ÂSºÒ±~šSsí¤l¬Ðfˆ… |5¤Á•´`hÛ¿PÅØuì«þŒ*ô…î #z%^Ø <lG÷Yƒ~í‚(' ©ŸýIHs&KYy„}KpÑÆÿÞµ·é"aF8RVàxÐ8ù ‹pu§æª< ÙNàxaÿ„|O­‚°oi®jIPP( 0ݱ¹+PŒ6Úã´þd8í?ÁÛ©K²iá'¬œú+¦¼Ä¦ùÕÏ÷‹h¬"ž\HôBö=Í5ÚKÁ6Ú §KYýƒ#Añðyˆ¨Ú02•„/Gº ”¿îq)p}÷ƒÇt<ôÛ?¡×á'Ó±Ï/ J.í,Lj·U±zú;Ìxä6ÌQ†u©½ƒ)é&‚ ì;ö6‚î…áÿ=˜â »z"|q¼ nЦ ¡Ú ",á‹ã_XÂ+^6úâÒ¸#^P4aÌÕ·pðyß%šŸç€jGëц©'Ï;/‰(¼Ïó 0-}^”†¥#8žó 8øç¨¡¢†9Ï<Ìôo§¾rÇ èF…«i¬\\Í”Ô(Aøj G0ü(s:‘ü]ð»šFƒ…9‚·áÏuð³-Ÿq¡Ý ÂB¾ÁU® ¨ˆ /À{‹ûyÒ/î¥ßq'á&Ú— 0# j+Y;kµÛ*+¯ˆNMa‡nb‡ÐB¤¡–ªuë‰têAv^œº²uÔÕYä—vÅjGßR† VV¾÷oÜv ÛW.þ"·$µK8UB"‚ðÕéú`#Á4¨p#Sÿó6b‡ûPø‘ϵÐîhG—lAh1ÌÀmØô§€€‡º‚ ¤'xÑ ¦>ùù·€K9zÈ×ï~š¢v**Œ$–}ÈÄkÏeþGŸÑçÔ+øÆÓs¸ìÅ™œrÝå¨õÓyãÆsX8u3Vª(«aêt'ÓÖ·A³ºéÖS»}#õµõ$k+¨Y¿¯½)ŠN”ôëË„;§Ûˆ1#€¿½Ñï!ß0š.åB„¯Ž°Wyò·t½)ü²²ÁŠP²@ ìwˆ°„½'œþŽVt~×yÈÈa_»ë)ò»uß»T ¶† ,|ú÷lY¿•Á?xŒ³î{˜>£¢ãàC9ä§ãô›oÄ+ßÀÇÿ¼›d*ýǩܖy³fê6,\Hmƒé[Ü C+µ”Ú0L òÚõlÚI@Ai&üþß÷2¸(`ײ—a£¨ _-éÌØa¡±»-ø˜°ŸBÚ5öžw!E8î—#Œ¦îÿ(·c—ãO¹ía K»·ËHèHCbã"–¿6‰hד9úⳉºà¸úïÉZè~ÒU~ñVÌÒC0€í<Ë¿»‚U‹Ë‰Ä#$ë“ôÿ§Þý}†–fôùd7 %úqò/ïçÅž3¾®bûà.}‚Õb‚ AhŸ·½ýüÉçTØ/‘ˆ… ìé¢~Ó$€³¬Hô’n¾î#Gµ[Q€NÍ*6CÖã(Ìoªì”SÊ ·ÿ•c¿{‘ÚíL½ûû¬Z’à w½Ä…§1áæ›©[ù?¦¿ô&j?_‡wê¡ï1ÇsÜOïÆ0­k€“Ròk¸* -OºÒ°»Ûa¿D.R‚°g†¾pªJàúa_¿ÄrÚÙí3ý)„aX€²"é.¡JûÜ$xŽ¢ë±gsÄõ2ú›ãé6üŸ~ =Šg?W)Ü ;ã|žrn ø)PJS¿EÐk!ßÛ‚ B«D.P‚°gšëUMm×u8¼÷Ñ?¸M/UíkUJébl#m濉GåÒìX¶7»„áßø1KVñÒ•çðè1ýxpÜh–oÛ”¯ ÐçÓŠØû£ß‘ß½Ï0à #bᨅ˜¹A„V‰\Õa÷„S |_Eî>ÔŠDÏ<òºÛ(èÞå6 vƒ;»€ÜXö6Õõ`¾I TùBþwÉ!Lú¿›¨Y¿’׿u/üüÇl®¶é~êvùõôè ®·¨°½Äs ¤w/ŽºöWç#Ñï»(úý&FnA¡U#ÂBvO¸vyÐ_Q\VzÈ1YN8§!sƒlI”‚hÇþô:ú(ê½ÎGoÌÀÈÓÔ%e#Y°ú…¿²që²Mݺi,Z»®Wý |Š“n¾1ž» ÛjRvvÇu`ЩgÓ}ä‘yè¨E6iw~JTpA„Vƒ\˜¡yÂU ‚)P&0ÆŠDO>âÊ_`ÅÌý" ^4—!ç\On^Ýz./ýéi*¶î ~Û:æÿú^¾ç~̂ьºøb¢F`5$ðIj×,æõ_^Æzرc Íœ¸´æöò ^ÃèˆaY'‡ÒèµðE­ÚA¡U åfa÷„½~TøV¯ÃO°ºz$ž“Áf/‡žÅ„[ïâí;obÅüû£†‡›tÈëw"§ÜüöË!‘7†F3ÿÑ˹÷‰kQŽGNéЫ [Ÿù¯E:r·ºTJyNÊQ¨ýpÞì& ï1ãèuèqñU¾}ð1ºù–KÓf[ûŸòAZ5ûßU[ö5Ø7ÎúFíxêwGX‘èC§ýþñœágœK²>sÍ$VjVÍbÕÌO¨ÜºåYÄ;÷¦×1(霛†ÍsY6å}ªËˆ—ô¤×‘§I,aåG³Éë{¥JXÿá4²‡EÇ.…T,›Á–µz9’È~¸üaÇ`Á ÏòâΫB©Ë€OÐÝ|ëÑ]~ýξ¾ÐA„Œ#ÂBÒôTøMðbha‘ü¬ë‡]ô­Ç?ÀŽDöŸ4¨4˜¶öW(O/¡&(¼€‘ݰÐ]¶SmÞ”«ï›xžÞ׎—Ô÷M,ÜúýsÖlX¨,çÉ‹ŽgË¢9·¢ED:zá‹ ¿Ãï~üAZ â±„æ š¶}ólè œ<üÌˈfíߢt5#'¡Çž£Sy¼Pu,åêß»Ipô~ž«ç9è¾õóHî§¢ôùÊ.)dàØ³Ž ±¯…AóE¯, ‚ ­‚°+éLÛ~äÂŽÌ*êÐ¥çá'Ê:±ð•á&aàØ³±³²ûÃiÚ”1hâ–ò³‚ B«@„… ìJXP£1`lÏÃŽ§¸O?Üý¡o…”ŽÒ÷èÓÆÑ´a^0r!Õ¡A„V AhJ¸”gÐga} ÓØ÷˜Ó°¢†D,öÃÒ¾ aïQ ¢¹ý0èDzQ!ßã‚ B«@.H‚°+¾¨F,üÔ“±¼‚N½?7™Á¶&ö°VnP¿eÛVmؽ“5÷]ð\è4lY…%%ÀP´” ¦CY©]%JAÈ8",¡)á¼õ ¿"8´ë‡y]{èÊFû;ž‡rvc±6Àt*˜þëoðø…g°tÙìHú]•ã‚'!  n: BQ¯Qà tu²°ÏBD… ‚Ð*a!4×iÛŸÈå#{u*V3“ãý Ó‚¥]Ì“?þ%U.Fsß&žƒ'»¤¢fH;&xåKxóûÇðÊß_Á”sÛˆ‚HÌ¢ôÐcA¸‹hEóS¢@¾ÏA„ #YÏ‚°+f`ó'q&ÐÕ0­®h~ÝF0,Ý't.?J§Ý(¯é>vT§2y.x MË¿&Ô®žÌ†Y‡’ttìp¼Á´€œŽºõ%­÷ˆåçw)7€d9[çL£&wÓn§Ç†enwþ¯º£ƒvD×ñ ×k«=1<JGÍL~?(¶Óøž nmñé ‚ í‚Ð?¥Äþ-À°üÎÝ£ùÝzµé4(+õ›×S¶|®±¼B¬XÙ]{“•¥;j×oXÉÒ§Q_›$·Ïhz:ÓÕBÄ´ÁŠ€mÅ!ÃŽØX0<-N<pT¬^Aƒ¦eaÚ9X99DÍÀ ØH ‚H+fV|çq ©Ø±l¨[¿œe3§Q—tÈï7†žÖ/X2IÅÊe˜ûcn›ÉŠÙ«è4æ ºt¶XúÒ$‘Žô=êD"m²ïHI¿!D²rŠ’u5=•4õXH*” ‚Ð*a!„ýÁ’³&0¤ ´¯™ß­ç. àÚ – «þv“Ÿ|˜íeDsòÁK ÌŽøÕsŒ:õ 6¾ü^¿ó6­+ò<«ƒÏ¸““þïR²­$kßþååÛXùi%”-೉°¾ÈÆK:ÄûŒfÀèñ*Ö2óæñ,+óP µdõ>œ¯ÿõ:äèUx p«7°tÒkÔm]IÅðæ¾Ìì'\p“(ϤÃá_§{Ïb V¾poÞyå›¶aX.ž]ÂWü†±×]‰Y·–¿8êß$ùá=¬Z 'ÜİҼóׂQÈøÎdøÚœé^y]Øâ¾ƒØ¼`ÖÀì**‚[”N‚ B{@„… hŒÀm8ÅÄB·»ôìK<Ï&Q›™A~Ì”½u/ßñÔtÁ‘7ÜNùÍ;xã‘×qj] ª>}…Wo¿‘­‘Ñœò—çé3 É´[.àÓ‰7Òõä£sb/VMzˆyó?£jS9¸eÌ~ì6ìHœ…ão¡ßabfubè5wÓ«¾‚Oþr›·¬Û%•Ê-_ͧ÷ßÊ–D‚š$XË'2õJ¡T”ƒ:IïAÅT|ø2¯Üv3nñ Æ>ô½º*Þºí;Ì{à{σNÍ&–þç:~6ýŠç±ü¥;Ø–ß…ÁÎeõKÙ¸z1 cçÿ £ –ŸOai6/˜Õ¦‹`n‚ BFa!WÃþŠ ¤¸÷ ½ÚÞÖ0À¨ÝÀ»ÞMuÖ`N»ûeFÕ ¥ ¿~,o>ò.†a`Z°ü‰?°y+uÿ½Œšp(žÇÝü;œu«fLaä1—sÈÿ=ÉH•`öÿჅ8ý¡¿SRÃs\¬x¦Äò(=át¢–ÇÊ—bó‚š&CR.D»„ÿÌ aÓ§¼pÅ©$ޏ ~q^B§ˆtÄK$YüÊ#TUD8òÎg8lÜN¹þ&þuÁE¬œø£o?^§pu:„Óþü,µÏ}›å³—Ðóò9óÛ#xü¥‰(£mf )ÑlÈïÚ ’ìÚ[å ‚ E„… 4%¥F-Š Ó,)î3¸Mú+ÌTÏŸBÙget?ùû 9¼N ×Ñnje˜^e‹¶°ì_7²é¹”aàU­ÆQPUQ…çA¬¸; ¹¹¨"²;ö ¯ÄÀóôDXyhÃt\³Ïu´ <Œ!»sbjQ ’‘nävé„jÐV ¼Ú**6,,~èzÊž±ð”…S¶šZ ¸z+Ž2P :ŸúCºv†…µµw‡Ÿy^Íâ–8Å_- ŠzöÇ0Ì|¥¼Ž@MߣÚºÞe¯ ‚ÐNa!šðªo0ja¦)(ê5 Iå¤6ƒ^C-^bñB]­ ]e)±­ ptnR¢– §¢E”ôBvV Ïó0̾t>àxr†ÄP© ¿/°Lå¹x®ý…¢9Êåêƒ)ÓE¹Ç6LðÜJ’å«1³r(0˜ü¨ƒ§ÀèÑ—N‡Œ¥pØ¡)q”]œ¯ßžFPÅ«kû™Až =úbggç&kª;«háh…¤C ‚ A„… 4%ÇÂòìhÌÊíеm @ZM(å`˜`ÆÀ)û”é…ÊCeÑ-g©QÊðËïaàÐÉÀÔÆo/‰þÙ?fJI˜–¯Ttµ(/ºÝëþ IDAT™¦ú’a`˜Z,*qÛx …R```¦ªF–.WkÆ:Ýý ¼Åk9ô{wÑ·'cŠha’ؼT.èªwÜ6ûZ…ñ<ÈéØH,++YS]HÓ÷©/~AR¡A„ ÒÆ«ñ Â>#]´"è³(ŽvÀŒF3?²æ2êw·‚H,Že³iî ¬[¼ÚÕ3yçæKùlÑôSôP.ôþæ7ˆ4ÌãÝûoeëÓ¯.ÉÚ÷Þ`ùì%M¾5¬¼Ž°~:Ëç.ÁÊKÕ³yÆbv]0W®‡[Ÿ Q—Ø¥ª–‰cÅl>ú·ºÄr ±u#›g¯ÃËɦ÷áã0«WóÁïn§¬ÌS‚u¯½ÎÒ·VaD¬]þß:ŸŸ÷œ·ʃœâN˜Ñ˜ äÒ4]/Ü}[Ä… ‚$b!„ÍÛþfŹ:7®Ì·${›Ô²›ýT²†gØÁ#˜6m2Ï]4šXÃzj¬¾üsøô?oƒ§3ˆºžr‡½7©/þ†/x…âŽE$«·±uùg ¸î_ô:h *å¥(=é:òžþ6ïÞpˆÄ«ÛJåêΜóÚ+tŽÙøúÁó\ëæ0é¢C°Í‡üø:qnƒž4Û3àˆSY=ñyž9o4ºäS½v9yƒ®á¬Co¤tü5Œ™öS_¼…Ççÿ—âÎE$«·²}þ"z^0‰¾£“¬7éê´/§,}J”Gà9îžÏ§ŸDÔʉ”ÙE°£qÐÂ"ü ‹ AAhqDX®¤‹ZfwÆ´ì–›s@ývæÝ#ë¶yi½Ïž«èsúÕä'æ2÷µ÷1Ò åbw?ã®»˜£îyšè_ΚEÛ1sÆ3î?':íGÌÆ&’S¨ÅE¬ˆ#ý%ÞÍÂ)ÓH$âz3|ìuŒ:ãt|µà¹P4æR¾~Ïvf½ð.5UµDJúrÐé瑟oït+£çÑç¡r‡â%(/Ÿœ¼’Æ4%ŽåàŸ=L¤ôV–~¼‚¤£(:àh†]ø5ÌxÑBޏm2}îdɬY$±Žýqãµ ?ãx”WAŸ¯] #à¹P<òl†FL¢@udè—R4¨'«_}ùoÏH{ž”rÉx’ÇGOý›Ö®³t¥0â%Œ¼ö7tîmÑ aFÄ$« ˆrÈGw»¤ïc!âBAÈ",¡éD,lˆõŽG²s0ŒÎTɺ ê*]L3MzQÂ%™LâÖÔQ¿};F<ÍGÚKb—4аq-åU‡ýüiŽ´ÁÊgóf^üÍ,(ìLрΘ.¸.-`Øe¿bÈEúg;–¥½ AAà)(w=ÆÝ€“лMÜzý7€¤²8èâŸ0ò²Æ!9õ4mTçÙ]ñ½¿r`B?ÖŽƒrô~ VÂA?üÃÀõôÿ²LpàyqFÞø/HꟻœxgÉ:ð"Ý9â/b%á³'_kæ<™à5`Ö6ày1ª¶‘PQ0Bç\)Œ¤…›!ïF4; ˆ š¾OA!£ˆ°M¸ªN0µÄ¢v4†a-±P@¼£~ù £ö°ŸâzM¸®Ù]¬8lù>žûÞÏé|æåôèß›ˆ‘`ÕϳxúBzëWt-„§0RB!©KǦÃMMo #µ_šäÔƒ³ÏשO pêÒü½.5&CĽÀÿrM ½$4$—¬Öúžs}ÏÝÃ0Œ¿wÜî÷ñÈH?3ˆ²k9dh… ‚aDX‚&œFÌ[7¨¡2°.¼·•v©ÂÀm€ìƒÆ1â›3oòD6¼]ÊÂ*,eÔõÿàð‹.%ê¥j/í{»ß¾8Ö—ø_j/ý»;—™Â0ÀŽÅ¡QXìÉbÞ Ÿ… ‚Оa!»F+Âyë&`Û‘†Ñ:'{B¹`—ôcÌ­ÿdtm=uU(!^PL$Kû%2•Þ#ì=V$ÝàÅ[!‚ ´*DXB#árMV=ÏiÛkÀJ§“Ý1žzR!¯ƒÐªQ®ú]Ø\‰Y‚ BÆa!ø“²àÄÍ'é4ÔëhE[Ÿº©T·ëLCø\(Ài¨mñ߉áh[ðVAZ©$"Mi®UZÒM4´É4¨ÝaÅtGm!@kM*Rà&ê`Ï>øÖ8zAa?@„… 4%<)óN: u™)ôU`€éî`þC¿eÁ³2bJo­(O¡¼Ö© ú:hŒX€ø+A„V„L'aÏ( º®|ÊuÛæ .U¶ÉàÝJæÞýsL_×Jó˜ÝoŸŒá c¯·§ÿ“.6UWƧw^»•&ë»C)¨«ØP´…+‚ ´'$ BöŒ*j·mÆs“XD2=L¬ˆ.%ëR ì¨î-¡P¦nnç%ô>fD÷|sP‘®œøïi˜ûb8©ÇFt…(ÃÒÇð÷w|ƒ·¡¯R=ŸÔÿ4€D¸ïD;¦ãÊ3ªûP8 0RãS {h˜Iíï::]Ë@O¬•‹î7jÄç¹z|ž«ÿn˜úœxI}ËÖý8ü6+ªWS”ÑØ‹ÂŠèó£ °¬zÖ¿ùo–ÁØlp«[G0ÄúŠJ’uÕ …… ‚ ´:DXÂÞQY³m žëîyÏÀ° j΋,üd#}O»Ž]rP@Úøø­©t?þº÷+BÕU°ì¥¿±bÖR .ñŽýxæw(íÛ•HPµb¦U@qÏÎÔ.šÂ¼×Òã al™ü_¶nÝA¤°½N¸ˆÞöF¹`y5¬xñIVÏ[„kæPzÔ8Ì+¨÷ú1xüá˜áutlV¼ö$+§½Omm±âôûMúŽBí†Ì}ù5zœp>]{wE)ðªV3ãÑg)9äkô1UÏ>JMN'Š ¶³ð­Oè0öj:W/dsy’nýã,xy2±Ïeôø1¨ŠµÌ{þQ6®Ú ‘\ŠŽbè„óÈŠÉk^}œ2kŒµ,ÿh E^ïpæÅäÚ[˜ûÔƒ”•+ßäý bÄ„qdg›†µÛ·à&jˆ_AhEˆ°„¦§Æ*°U'k«©¯ÜA,?/ãÓ93[§>Àä?LöÁ§Ó¹{Ê„š%¯ñÎïoáˆÒqô–ËÌ[¾Ã[Ÿ%»û` ;å³øÅ¿ñéÄ78û±ÿÒ§o³o¹õí»é}ØPj–¼Í”[o'š›‡içϵ)_·–Ù/½Å9¾N¿^Š™?:7_x…HIâ±$Ÿþû’ U丆~_;œ¸jºÂoYðÙ¿Ì‹·ÿ»°+¹E¹T­[Êœçeüß>¡[âC&ÿáÇŒézÝúuÜm xû÷?fè é?z+þõ[f.YCÌJ âvð9¨7àµWÞ&ž¥pÌBºÅ!eKyýº3™?k¹¥°“”ÿë|4sÞ~;F=Ë'ÝÅŒ)k‰ÅL"…0j6RU^ÃêÍpÖųè‰ì¨oãtæ<Û‡A'Œ%'×Ô‘’ b˜P»unC¢ž]#{ÙöOA¾ZÄc!¤›œù¿«ñ§¦|Í2ÌV’{oG² š‡™ú+À´¢D"YXf¯b3‹>~‹¬!c¹ø­ÏøögpÁŸÿŒ¹ý]–Ο †M$'Ž‚3’…Ž: ^ZÊÕ“—2ö“I¬ù”Í«6S6}"S^y…¢~Æ·ßXÎÕo-aì%çc`ÍŽ`†ÎžaCbÍTÞùó½äpßüï"®ž¼„óï½›XÕZæüûEÜx.–8©¦MÄ;bƒ‚Hv8 úœw —þo!'M8ÃŽŠÇ\Î/ÌãÜï~Åÿþ5óg¯æˆß¾Å5ï-ášw—qâ¹gPñ¯ùðÝÅØQ+ž FÝü W½³”+&M¡OkÞšBCÁpÎúëK (…øÑ7qå³P\ÁkA*Ó‚ëV¨­¬*Ø}gmñ_‚ A„… ¤'‰ZèzäÅŒýÕ_9î`¼Äž*§‚JÖéu,G^w+ºw 1PNFÁ ޏþzô/Ū[ÏšŸ Z2’îÃûP¾xeë7ÓåèãÈ1aË›’´l”ëïq#Ï>…ˆ‚X÷Q ‘µ+¶‰ìÆñ\(4˜( ¬ÿè-\ˆçCÍÂxá²q¼ÿЛÄó»l]º + Ñ,XûÜS8º&í^ °³‹Ééцš%P2ˆ¾ÇBßã#²cïþá‡|6w3‘ˆ~ÑŒÝN¿SƲ±c»O2‚aB]Å6¶­X°2õk¦‚"Ý&‚ -ŠD,AœŒ…'h¾ØXQ¹aµSµq•ß­égæ-„ y}G`©j¦Ýsö¶ó©]ü3Ÿy(F¹+ʧnÇr–Þw)¹êzöŒ³îý‰lJ‡öÄ”ë ü&Ê7-PIǤ×ÑèUrów!fÙéÙÌÔ‡ï¡> ù¨]ƒ I(8`<ýÎÂ'näõH=ûæ°øÉßRCGÆ\4ŽìÂ2JòaÉ?¾Çä¬íÄÊg2ó_éc¥" ®çîRæ×ó—ªBedw`è™ßgÖû?àµÎfÛÙçb•¯â£{n£,GÜ/éêÇ…º§+Ï×ÕDZbØy13&2õŸ1á²2]nÖ€Êõk¨Ùº) ¬E… ‚ÐJ‘ˆ… 4Åc§`窰ÿó†d]Mõö‹38<ç@ÞAçqüw¿K^Õ<¦ÞùC|0Ÿ#oø GH$Ì’ûÛÓ7Ïá£?^Å‹?ý63'½NSÅi—ŒG¹SÚŸÜÂ" fn1ù½{aǬST+»˜¢Ò!ij²ˆ8Œ3ž|—‘Ç aÙã¿aê?'2è7Ñ}P)NçØV(«HëÈ©¿}Š!ÇbÙ³¿å;nfS}ŽýísŒSŠÑaGÿêWçÛ|üç3uâû ¸ò ;xY¹y(eݹ7E=J±ýeeíPJI¯ÞDã–nà×]Ç^É™·Ü޵jïüêJÞ¾ïO8CŽã´^¢oß\YzRЭÃÎé·Ä:ô¢°ç,ŒÜ :åZr³—1ãñg¨®u3nâ6 Ø8÷C€ èŠP ýž ¾7EX‚ §ú¡U`¡Ý  d§¶\ (Š€ï ?û²ã~ó÷]sþ[lêÊ·ÓP— ’߬\[—Gõ;MÛàÖT°}ÝZb…](èÖCñ¹ÆoØP·âS–Nÿ’£¿En9¸¦›çòÄY‡P;î\yë…x iká8ÔlÝ„ãÄ ºϳð-DL’•UÔUUÉ)&«0 å~냡ŸoÃö2ª¶nnjө ±¸ö¥ïõa pjjQV+Éø—¤eÃÄ+¿Îòw_œ < ”£Fº§E ºi^¤ä¬ ‚$J ¦˜¸ÍIÝÖ VM{kD¢ª†HnNfׇ•ö9Gó‹‰蟽ÐÚu€xèeO{>ï°M’«fñæ­W‘wÈ›œúÓ_’eT°à‘ëØ°-ÂQÇÂh&5L¹  ›ìÎ¥;ÇíÊÓzX9yä忥}{M긑üŽ”vÔ¿ò>Ÿ¨-hìœì÷3‰iAņlùl¶–¡„Ú©Q‚ ‚DX‚&è§ðB÷ý”XU[¶±ví'ïe8in"SÃmDíi:©øÒMÞÜ(8êlNùÉ<>øëÝ<}ÞsÑÞô˧9dÌ=þÝFHÔ¾›Ä+Å—î”iAácEaÓü©)ÛX ¬ ±v—×Ì".A„ !ÂBšþæ¯{otäæU¼ÖgÐØqõo·( <+Ÿá—ÿ‘Þ§\IuÙ”a/éIQÏNZHò;Å'ë>žŒç:-4¦yHÄBAhEˆ°M¸@pâܪÏV}øvŸÊÛÈéPò¥£m¹=‘×3ð»ýåù·0†µe[YôÚD€Y4ú'‚ïÇp”MÄ… ‚1¤*” 4â·½ û,üÉ\=›µmÙ‚ÄÆ¹33^1hŸj\çcÅÀŽ6³o*ÍÈsRÛ^Š Ã;¦=­žÐùȦ «§½AÕ¦µÕÀ"ߋ͉ AAÈ(íaZ$ûа·"8yKÒXmg °xáË·‹ôÃS;{r˜f‚ÅßÉŒÇÿ‹cì›q†‰uŸ0íž?²æ³õ;›l·V”רÑ;S†n ^~´i{)yþN…òoA!#ˆ°„¦MÜáêPþŠq50kå{¯z›|ŠÉÔPCìI„Wâ °©fÎ#?åÍ¿ü:¬H’ÕÏßǼ·ŸÃ3Ò<拌Ä䦙zï/Xùñ2ŒðùÚ›ÿþûž³·Ç êÊøôÎKy÷ï/¢2]±"°qÎLÖ2Õf£KÉî)Z!iP‚ BFa!š`×ítÞ µH¤öYX_¹cÓ§Ï>‚™¡´3‘,= µ£z^lEu߃ V4õwC§$Eâ`¦>ù±l›USŸgÑäWQq°,Û¶0#ÌTê’a€Õø˜fÇ;¢÷ß%ʈb6¦iìœË¶N2 öcÇ¿ +ôÜ }kZúØ–•z.±¦Q•)W†Þ7oúw+ ‘¨ÞDzSû§Î‡a‚mÕ³þÍǘ?k9‘ì}±ùÜ:Ålþ‹ÑP]±˜‡~Oú‹°¸ð߯ ÂBAÈ ­<)AZ” ¸¦Dù“¹dà~ðÑ’·žÿúa—ý”Â=[ÔÄlXP5çe–¯¨¢ë ~¬|ý9*¶U+éÃг¾Cç^E¸Žž°ošñ:ËÞ‡êò*Ìh.EƒF3ä´³ÉÕ²ðÑûÙ±±7±†O~ƒÏÿ†iay lxûIV¾?DÒ"¯ûH†{…ynš©«í³^dá[PSUK´°+½O8›^#§ ˜hX3‹y/<Ïö²rìœbºv*ý9‚ˆ •Ëg±dò”u(Þy‰­›«Èê6”Îþµ³ÿÃ’ IªùÃObÄi'1ô9QUk™÷ü£l\µ#’Cá Q Yq ™`Í«Sf ¡“¹–å3gH*r{à€3/&×ÞÂܧ¤¬Xù&ïÿc0#&œJv¶Ù¢ågM¶­XÆ¢—Ÿ˜Žn€ç¿÷üÛp Q!‚ d‚Д°Ç"¸BìOì|÷ìš-Gòä½]NºéÎÆ5ãÀŒÀö©ðú=¯Ë-ÂÈÎ#+fP¾v5óß|‘3x…^óØòÚ½<ûãQçu dDœ5Ÿ1ë±;Y4ç!¾yÛ™,áIvl©ÀUÕ,|þaz?ƒH<‡²™/ðüÌIÄ‹»cÔn¡rûŸY¿¨’3þt‘dS§°õÏÞɤ[JÕ‚N…Ô­[ÎGO<ÄÈÛÿÃÉ_ÓD\˜¨üðEþ{ýE¬/KRPÚwÇ&>úûè}Ã?ùÆ÷¿Iýg“™üûa=”‡aç³’Tl.ãÓ§ï$¹q-‘.=ðvl ö±ûÙVû_»äk?㵜ËüY È-í¬¤ü_ࣙó¸ð¶Û)0êY>é.fLYG,f)ì„Q³‘ªòVo†³.>˜E¯OdGx§3gbp29¹æ—פҳfüý÷ÔUìX̧éû.è±H×$OA2†¤B B#*´†ŸåoI  ˜2ÿÙ¿y›ÎÇj®’ÒW„ÍÁ »„K_[ÁUï.ãœÛAbÙÌ|ú\V<÷÷Å%‘DÙXûÑ4¿úL¶£EDð}—.b!ÂBAÈ8",¡)éDE0%(0\àÓúÊòu3ÿq'nBµl™Rå ŒÎté Y8ŽMÿ3®¦g—6®þŒÚ0ìærñ¿ž§SþV½7ƒe¼EÕŽJlÛÖ~ÓÄ0- à ^¢ŽøÀ#9äܱØØ¥Ãéß NE µU.VPXD üò¹*I×£Î$ÛÞÎÆ9‹Iä÷ Ïˆn$ÆŽµµ; †¡~å4Ö/[LÑÀã)îeËÜETÔCï1Cñª×²uör<ÛÂÀ`Ðå×ѵc3§=úfA—SyþYDmÈî5ìü\ Ó­ØÂšŸ Z2’Òá}(_¼ˆ²õ[èrôq䘰åÍIZ6Êu‰÷8‘gŸBDA¼û(3µ~9µõ`Ú6V Òò¥„ HÖ:|øÈïHÔT-æÒè­HÒTXøâB:n ‚ ­I…„] Š‹°;®ÐÓÄÐùïo/üßãõ<ü${Äyá4´Ð =³°ˆ¼¢Tªø¨Í¥0c›20M¨+›Ã´¿ü‘ÍK–PS^N2Ñ€aZôI•v?+¦¼T¼®>ÖØÂ ‰V‚ ­ ‚žt>‹¥Q\ø‘‹Ï”ç}2õþ[F÷8ähŠûôÆM|õ4åx8JíÌÍ7=‡ÏE™ªbS~s-K;ŒüîÏt̉w.fö½—2çƒÝ«ãó”ºRI`Ô¥÷2ü¤¡8µI0 L•ÄI”ô뀷0˜OäáÔC·Ñ×sòçâV7€ &ŠdC…CFœ1 00­Ð8Üô~¥^²;ŸÉ÷ÝŒÕ@ ôðjˆv臙¨ÓÏ-i4=FK:³›ÁŠÀ¦yóùèo¿¥¦£{W¸4M¿ó#éªA ‚ BÆ‘T(AhJs‹pTp%9¼S¹nÕ†·~ó}Õu{,ͺO0#¨Úµlød:D –s'±zÓ ºuÁ­ÝBÅêõty5'ÝòCŒNÜÚ¶9Ó1¢q̨~ºJ)ÀÄŠÅ^N¶ÿ¿½{‘«¬Ã8þ=sæ>{évw›BwÛn+KѶn¥%Р©I£FþÑxAŠ`ÔÆ5¢\ ”¨D0±RJÀ&F4Ø¢•BJ‹-rQ©h‘"­•Ké½ïÌ9Ç?μ{Þ=;ÛvË.]žOòf.;vgš÷Ùßï}ßÀ‡ü´sIßÝOó9³i[0—éçžÎkëî`Ó½?£»‡°ÕŠ°Ê’nœA¶!MGÇ.rmg3cá\fœ?—[æÉ•+xûí>Üd¢ü2Žáun~"ùÖ™ôví„ÆYÌX0——Ì#up›nû/ÿí-R©r;ÖŸ2=t“ý[Ꮆ„ ]û±ñ–kéØ÷Æ.`3á{­§Ò®">¥ùÆ<ðOüèzölò0°8DôþŠ·BÅnk}…ˆˆT  ‘Áâ‹JU ;XdÊ—»€ Ï­YõÉBÓääǾøMpF³ÓÆ#(Ö2õ£§ðÒ/–ÑÓã‘©9‹o¼…3çL!p.üö]gúóìþ÷ìX{+mW,¦pru“£UC¦©…†©$ÓÎÀßøà'›¹håÃ$—•¿®»WÖ–pòu̺úf.ºvŠÙ¦œA¡v¥>8ëúã9Yþüë_±áO¿ÄÉh^ô%.Xv' 5°/[OÔ™dÒÉþï“4š™SqS  ‘ÌQÛz*Ns~7L^ôe®¼ñ]žºçnžøÁFÉ ùÓæóñå«™1£¯·ƒÜ¤©Ô÷5õOà Ó4úÖ^Ü85­ÌZt ¯¯^˶áC /%_ífDøž¾g;~û@ðð:áûÌ= ^¸mÞ—:m[DDªÆ :3X¤ê•W-à©òHY € ¨+šòýónò’Ëîtç\}m¸x„'¦nvÿäS<ôÓ¿³ømœÒZ¢ýP7Ù‰'‘oÈBù¬‰DŠ‡Þ¡ý@x]MÓDÜ$tï?©<©\†DJø¸$sÙãúOÁqÁ <Úÿ·‡ÞnÌ„f u$¿BÕÆI„Eu¾½—®C¤j)47’v¡t¼ÿVNxšvï}´¿s€D¦žÂ¤Éd²à•†ñ4;»ÀMáfR£òŸ¤S>Ñ|ÛÏWòÔÊïÏÛl#Ü  ½<Þ-_v”ïï& ñv(… 9áT±©, ¦Zá®I*~nzË—f¤Cˆ <í{¥`Ó­7,è:°Ï=ÿ+ß%™Ëâcr{L/Ðq äá—<²“&“j[¯übô¿nmë›Â€ãƒW„Ì„è¿ÏÍHrüÕ•Àƒ—š“§SëDÏ;T'Xà‡;N嚦oŽÜ¡ ÿn©ºf'4Gßg˜ÿîA©B¾ÿúHs“Ð×ÕÃæ»—óÜšU^àyž%¬Lt—GÑ ³®§Ò6³ ""R5,D†f&lv+”K8És*ì`áÏø¥RÏ3÷Þ´ð­—_Ì_vã*&NkÃ+ܺ Ç2Iœ´Kà1ôAnÁ௠z #´»jàïy†ûøczΨF páîOï¼²“7/åµ§ï ÜVöE Z)T˜ÍìEÛ ""R•Ô %rd k$‰ÖTdË#OÔU l‰*”G8XXßÒ6鯯à´+–©ÍPê}“WüŽ}t´—È5M&•r4ŬRŽžQÑ}¨‹<º†­÷ÝBû{Þ6¯ˆkØíO]„a#¾ÎÂ>ÇBDD¤*(Xˆ™iJ0x½EŽh½…u±ÛY ˜œÓ:w~êÌ+¯fÖeKH×Öà¦ÂoøÃŽCÿÎSUpƒ”™Ÿ „­Yݱóëxé7«ÙûÂÖ^àày`aE¢“ÁÂ] ÜÖ¸Ò‰Û"""UAÁBäèÊû÷ôW-L¸È‹|y˜0QÃàp‘Ú€³Á™Zß2=×vÁ"Zæ\Líä– ]Sï¤ó5ýç=ÈØ}]íô¶Úßü¯³gû&^Ûò8íoìév†Š=D­O&@´—¯w2°Ral©Ü %""R54‹9:³C”©Z˜µf›YÓe‡‹‚u;O.ÒÀI@+ÐLrn2[7ÁMj‡sÞµT¡(v¶û݇z~ x‹0Pì)_7[›µ]D s=ÞþdŸaaÖû(TˆˆHÕÑÅ·”U¨‘1CÁBä½1“Kû·Ë>aPˆï U$ ñPaÂo…R¸¨Nöú ?v=¾Ö"¾ˆ?>ì*‡*""2&)Xˆ¿xå¢Òv³ap0—•†½¶ÂÞrV¢ú±K»…É vÀˆW&ìê†NEDDÆM\DFF<$b#"*­«°ŸÃ¾”êgW,ìP_sáá¾øŸS4qYñpñQ©ýIÁbìñc×íp?÷Äñ6* ³4qY•vs²GØ¡Â~œÂÄØ7T`¨T•ˆ_ŠˆˆŒišÄˆ¼?*v§³*Ƨ¡‚ƒ‚„ˆˆŒkšÈˆœC}öô™ÛLhpP‘¨ÿCa‚J¥¥š[IEND®B`‚pyramid-1.6/docs/narr/router.png0000644000076500000240000053157612234375161017517 0ustar michaelstaff00000000000000‰PNG  IHDRþ–©‰H IDATxœìÝy|TÕÝøñϽ³dßH „}eŠ,‚ˆUqEÜ«u_ë£u­­?«Ý¬}|l}j¥Ö§®(¸¢¨UE5$!!û>sïùýqgnf&Lßw_ãdîÜåÌ0MÎwÎùž/!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!´H7@X”R#€ó±@: G¶EB!„B-°X| iZC„ÛÓiI`aJ©Xàv¥Ôýš¦%Fº=B!„Ø|à~MÓ¶Fº!‘¤”r®‹t[„B!Nës5MËtC:™nYçWEºB!„'!ÀƒJ)w¤ÒÙH`!J©àVÀé¶!„Bœ`®†Eº‘“…•¨-„B!Ž-'09Òèl$°ˆœl@†à„B!"£w¤ÐÙH`91‘n€B!Ä LúbG˜B!„BˆÍéˆ#Ã0 ššš©¬ª¤¶¦–&O3ÊT‘n.§“è˜âããHLLÄír¡i²Ò±B!Dg"Åq®´¬Œ?üÀú )(ØEee O3¦i Bâ MÓPJÙ÷-ÛVIEè VøýCƒ²¯eïïtºp¹]¤§¤2hÈ@N1œ¼Ü\œNù !„Btòµq„(¥f‹÷ø††¾X¹Š%K–²sg* ©Ñ€–P¢ Êj¥RŠ„„x~2~<“'ŸNÏÙèºÌÊB!Ä1õ²¦i—Gº‰r¸…RŠ»x}Þ›|½v-¯ÓáTpÏ^Óð ²ïg Phʺ÷ É>½{@Û´Ð|‡ù/ú:LSÑ¥KãÇã¬3§‘––z¨o‡B!Äá’Àâ“y(ÇÃ0ø~ýÞzg!ß÷=è]o™¦äïåÛ„ ‰TÐt¨ ýñ³ ‡ð,*$pQþÿ©ÐyY€®kìÛWÊ{K—RQYÉ•—]JrrÒ¡¼%B!„¢ƒÀâ8²yËV^Ÿ7ŸÍ[·ø:ô ¥Lß³!cv°¡B6›X}3`«.ñ=6Qhh´ì¯üíίüg j²ŽÚÏ:Ô pëëY¾bñqq\qÙl\.)F.„Bq¼‘Àâ8QZZÆkóÞ`æMèþ< ÃA3‚…vöÍ0]|£ì«ìQˆð¤Í„ О#h=!ªå™¦¦&>ø`)¹9=˜túDY5J!„â8#ÅqÀ4M^Ÿ÷k׮á[ù-S‹|=üÐŽ¸ò(ƒ€˜¡%B H„P¡ûœßަ^^ÖžÀ h{JUÀ“: ¼üÊkôÈîAß¾R S!„âx"Kñ¾ûþ{>_ñ%`(e¢”‰©L”2¬{ÓÄ ¼)ÿ~*øFȽR¾¥i­ãB÷5}ç0Më:fà9 9·}¼‰ ü_hBÚï¬iÅÅûxgá"jkë"ü® !„BˆC!#\}}}ü •D¹Ý˜¦²‡†Ò|õ'Tø‰K0z¡‚VŠ x.à”­öñfhšfçSµá€ëÐŽˆX\»v-+W­fê”I‡ö:„B!DÄȈEWZZÊ÷ë7àÔ(…o$Bùî}MìQ ÓÚhM•2ƒG ìÇ!Ûýµ%”¢å9Ów#à>pÂw `#'öñ¦ûZfÀÍß~S)»ýš¦QQQÉú hllŒà;/„B!…ŒXt`J)víÚMiYèZÐ4&ð؉æR(ß„ ¬K »ªlà(CðÞa«Xør·[AÈO­ë[(x6ÿ‘ZÀy[Úo*“ÂÂ=ìß¿ŸîÝ»·ùþ!„BˆŽC‹Ìãñ°uë6 îWC(•:- ƒï[Ù©­Ž«YI˜¾€Ã Ü9ð:¡¥,|Ç(á.šñÑ÷?W\RÂþò ,„B!ŽXt`—‚Ý»1M3hùU;–°{öšo¨€X $0°<ÀöÖñ¾QšNÐ ³þ¦„©yšžÜžZhd ªªš²²²°Ï Ñ™}ûí·|ýõ׆išdee1mÚ4bbb‚öSJ±jÕ*Š‹‹ÑuÌÌL𛛉‰‰aРA|ùå—äåå‘——¡W"„âD#Efš&å•Vž‚¦ZÅf«1ˆÀ%f6‡®/Ûzà€  J‘žϾ ¬\[L£G Zm¶ÍÀ%¤5?‡«Ja†;¬£ ƒªªjLÓD×~*iš444ÐÜÜŒÇãÁëõàr¹ˆ%..A€÷Þ{G}”¦¦&ÆÇرcûwïæùçŸ'66–+¯¼’çž{]×¹å–[¸ï¾û¸ñƹùæ›#ñ2„Bœ€$°èÀ”R476bš†îïÅ·t²ý¹ * 2¶Ú÷MC²â ÐÛ·F 4­ _2µÓ¡sδáŒXÓØÍG_»0M_p Y ÞA‘…?êÐB+x®R¥µœ °YáæZ)«£ßÔÔtÔ‹šš>øà¾ýö[Š‹‹©¨¨ , ÃZ}ËétKbb"999Œ9’ñãÇÓµkW)âwX¶lóæÍcÿþý :”o¼‘ÔÔÔ£zM¥”ýùó?G×u.ºè"¶mÛFRR³fÍâÍ7ßÄãñðÊ+¯Ãá8ªmB!I`Ñ¡Y5ð­Òd…fHؼÀ«?O"hÖ“Z¥‚¶k¾éSþiTÊ#œzr6FçájüšŸŽmfÏ>šºØÓ°¬sj(;(PJkc ÃÖ âá†>LÕ²ŠÔÑRZZÊ?þñV¬XA}}}»®KVV `ÆŒœuÖYäää“qìmß¾Gy„+VàõzYºt)N§“»ï¾û¨wØÛ´†aß”R$$$àv»ùÏþÄ Ð}¹Y !„8¤GÔÁY˱šVH¡Ö"Ã÷Ø_´Îh)dתxžá+Bg@È}P=e‚2ÈížÄÅgŸLFŠÇKÿ—LöÐ=ÝÄkX×% ¨¿}Öò± 3ô¦ZÚlµß Ûþ–|-û-ÍÍÍ”””PWW×¾žíÛ·³xñbî¾ûnîºë.>ùä“£‰ÈùòË/Y¶l™=-®¢¢‚¹sçRW×±Š7¦§§“’’@Ïž=2dǧÿþ$%%E¸uB!N$2bÑÁ)߀€5ýˆ€/öÃufÛ\÷ɲʓæ[IJÓ^äkZ<³f bHŸT›@ `xƒó¢ñ…:5 =ìJNªÕ]Ð Qmep‡};úG±¿®iZÐHƒÃá`À€ää䜜Ltt4uuu”””PPPÀ®]»ì)*MMM¼óÎ;lÙ²…§žzŠéÓ§½ÆŠˆWG¥¾¾Ó{ölbbb˜2e .— ·Û-£B!Ž ,:0kz’¯Ð\àr²A?i´L@ =:ü²Pö„(ßS† ñqnæœ;„)csPªe(Ó‹izq;àŒSLÊ«\üs‰†Ò­«¶uÝÐp#¸^E`BwK[÷h)Þ×®·éˆˆåñÇgÊ”)DEEÙ5ÇC}}=555¬]»–?þñ,_¾Ü¥øá‡¸óÎ;Y´h½{÷>v GݨQ£èÓ§Û¶m³·Í™3‡øøø¶*˜¦iôìÙÓ~Ü«W¯¶F!ĉN‹ŽÌ7Ra*ÝÔPZËR³JShJk¹<0LA Í¿V¬/»ÚÊ™°s¦ôaʘl\ºehJ¡)¯•HÂíPœ3ÖÆ|'_þN‡æ«Op¥ìÁŸÞm_Ø·(U`ûM_»ýmnY•ÊÄ4u‚ƒ¢£KÓ4’““[­þär¹HJJ"))‰ììlÆÇý÷ßÏ‹/¾Hss3›6mâ…^àÑG•|‹NdèСüíocþüùTWW3tèPn¸á;èB!D0ù ÙÁ™¦²V…Ò"…–xp*tà4©ÀYQÁÕêZ¶+ð£†frÁ´<â¢MLo£õ„i L(”Ž¡)’âM~6C±§ÜÉ®}¾Ú*pZˆ|ÑÀÂx*|;íÊàëÙ*”/gãØæ.´gŠKZZ=ö,]ºÔÞþÚk¯qÛm·Ñµk×£ÙDqŒqÆœqÆ‘n†Bq\¯W;8唲†LÒEàv¦é Ìàç¬|ï–cn¦i’žͬ3òHItbÍ€§åfÖ4,¥Ð”äôénpÖH·LCœÛ3Úã¿V`;Úïß®BÚïß§ƒêÒ¥ Ó¦M³ó0JJJغuk[%„BYXth eúWPj¹ZAÉ·Z”õ¿À}WcòßZV\2•‰C‡ŸNêÉiû Ì&+·By@5¢LëgÍTh¦a^p`2c”‡1ƒMßÈB¸UžÂµ±eµªÐç¬6¶_ùVê¸ÁÅØ±cƒê466òÃ?D°EB!„‘%S¡:<…2ü?…>ƒoÆ‘ šî8Û©Õþþç5ÅØ“3¸pz6­Ã4Ñ”5?Jù¦BiÊ@3 +7ÂWÃ04Rã—n°§DgK¡”fRϺžo†‘ É%·Û¾kû÷W˜f˜ÐôèÑ#(ÃëõRZZ´OAAv͇ÃAÿþýí¥AÛÃ0 ¶mÛÆ¾}ûìü¸¸8HTT”½_]]ùùùTUU‘‘Aß¾}ícš››Y¹r%kÖ¬aÿþý$''3{ölzôèöº lذmÛ¶±gϪ««illÄår‘œœLnn.'Ÿ|2yyy‡”sàõzÙ¼y3¤¦¦’——gW•VJ±qãF¾ûî;víÚEUU‡ƒììlFŽÉ!Cp»Ým¾W6là믿¦°°úúzbccéÖ­Æ cøðᇔãëêêÐ4+§(''§Í÷ì@ ظq#ùùùãõzIHH gÏžôïߟÁƒ{ÈçB‹bÙksygÕ†àÜÇšî$9=a£Âäñ£‰sɪqâÈ’À¢ƒ3MÃ4Z/¾Á‹>Ù˹jì+Ê„½¹ñ’^¤$€×Û¾Q Ã.z§L¦éµÝ—®éx”b@¶ÁMg»øóí{.§Ö’RAøæ°ým4¦©cbvØØÂår´“ºråJnºé&;wÃívsÏ=÷pÏ=÷´û:EEE\qÅlÞ¼MÓ0M“‹.ºˆ§Ÿ~:(°Ø¹s'>ø Ë–-`æÌ™<ÿüóÄÆÆòÃ?ððótéRjjj«öÁ¨Q£‚:ÉMMMìØ±ƒ·Þz‹ùóç³cÇ***ÂŽ9233=z4wÝuãÇo×ë©««ãÑGåý÷ß`Ò¤I<ñÄ 8‚‚{ì1.\Ⱦ}ûZ]7--)S¦pûí·sÚi§ÙÛ•R¬^½šgŸ}–÷Þ{¯U€ç?vÆŒ<öØcäää´«­¥¥¥ÜvÛm|ÿý÷v`ñàƒrï½÷¶ëx€ÂÂBž|òIÞzë-JJJÂ.a›ššÊI'Ä}÷ÝÇôéÓe!ÄaÐØøî<žzõÃvííŒI$gêeÌýß'™Ø-æ(·-œFÞãU¾Z_H¿ñ3¸àŒS¥CÚIÈ¿cgOs ìµû¨À`" Âд6‚ C‘Ó-†«ÎëI¯îQx=Öh„²¦"Y¡‡áË•ðXù(0¬ 4¬ ÓÔž×ÌuÓ<9ßÁþ…®Vo -óÎ÷¿µc½Üì¡*++£©©É~ìp8‚¦FŒ3†ôôô Ü‹·ß~››o¾¹ÕêSá(¥øöÛoY³fÝÉNJJbÔ¨Q­Žw:x<ª««íöy½^>ùä|ðA¾üòË ýccc[UÞ¼y3×\s ß|óÍAÛf{öìaÁ‚¬^½šgžy†sÏ=÷ Ç麎av;KKKilldãÆÜpà ,_¾¼Íc÷ïßϼyóX»v-ÿûß™4i‡üãüñdçÎ<ö¥—^¢¨¨ˆ?ÿùÏœtÒI­ï`š&ÕÕÕv[!|m‹¶,[¶Œ;3o¿ýö€û•——³lÙ2¾ÿþ{~÷»ßa†ÔžB²èèhtÀŒëËEçO$%Ú÷'ÜG×ušj÷ó凲¥´ší Ÿåç±I¬~é ’yo°‰×^ù+/¾ý5³~ËyXtòïØÁ)_’u¸`A ª0•H/Cëg(Ej¢‹+ÎéÎiÃ0Íz+€Àë[ÀÉ7bazÁl¶î1Qšæ;§Ã·è”B×aÂ@ƒ¢‰Qüm±†a¶Ý#¨=­6µ?4â°jw˜¾äîÃ~뎪uëÖÙÓyÀú…Þ¯_¿ }rrr˜6mZP`±iÓ&>ÿüsÎ<ó̃^£±±‘E‹}sÚi§1eÊ”VûºÝn{:XÿuëÖñÐCþ‘–¨¨¨Vߌ§¤¤‘‘@TT½{÷æÔSO%''‡ÄÄD\.¥¥¥,_¾œÕ«WÓÐÐXßÊßyçôèу#Fð5éº4å§±±‘µk×2þ|–/_ަi¤¥¥Ñ«W/(++cóæÍAú­[·òÀ°xñbÞ}÷]|ðA*++ÑuôôtˆÓé$??Ÿ={öØË|üñÇ<üðÃüùÏnWÝ‘Ð÷¨½þ+Vpýõ׳eË– c6l}úôA)ÅöíÛÙ´ieee”——óÐC1`À»£B²Üñüñ¹þé’ ÿáÚ ®dñæ=lÿàU>Z7K?¶mÄËmýÍr9]8²·8~H`ÑY_èûj@˜-%(‚öȱ°»è¾ eâ+^á[$ÊTLÂø“qêÍVe<Ì– DØ#Êåµ å²N¬)ß @C:n'L>ÙÃ'ëܬËG˜ß­ÛÜ~{•«Ù¡…ÂëõòÅ_ØÓŠ’““[gusçε;Æ•••|ñÅí ,ÊÊÊøúë¯íÇn·›SN9%ìT‡Ã”ëP[[Ë»ï¾ËÚµk+Pü€o]׃r$ÊÊÊxñÅY»v-š¦1yòd®¿þzz÷îMTTõõõ|òÉ'<ýôÓìÝ»×>îûï¿gîܹ,^¼˜ÊÊJbcc™xëIú¦w-ç=z7¿y})™Ón⽿ßËÖçâÊ'^c×þ2>œû;FΖé7=Ìßø9Vö —/Þz–_üú~ض“êúfÐ\dtͤïè©ÜÿÀ¯8{T®}ú‚o¸îÜKø6º?Ï<÷;6<{ ˜÷åuIüaÁøÅÙX+’%Ø‘)¬%X}µ$ZÝûòZî}·ý¼†Ir¼Îõç§‘ÛMÃ4êÑT#˜hªÌF 0PªT˜Í(ÓƒòzQ¦2 ”ib(Ã7MÊðbz½Œí×Èå=¸&†é[&×¾…¶¹}í?Ös¡B;Úá,Z´ˆ»ï¾;èð””®»îº°«#¥¤¤pþùçm[³f ëׯ?àuYºt©Ý=z4§žzjØýu]š¶S\\ÌîÝ»éÒ¥ ¿úÕ¯xðÁÉÎÎ&..·Û6ù\Ó4ºuëÆðáÃá¯ëÖ[o šN´eË–ƒæg„4†a ”â”SNáÉ'ŸlT€•?rùå—sÉ%—†TUUa×^{-¿üå/ƒ‚ ¿˜˜æÌ™ÃìÙ³ímJ)òóóíÑœ#iÙ²e¼òÊ+Ô××ÛÛfÍšÅ#<vDËÏÿï4kÖ¬#Þ&!Ä FÓвDYA¾µ«ÛIj|KÎ^ÑæÏ8óÜ yvùJ+á”ñÓ¹ø¢ >(—}{v°ô©û8ÿ‚KÙ¶¿Î1Œ’½äïØÉŽÒRšÃ X›ûö²cçN*Ê*ÑgB1±¬/¥4W4ÑQ±D;ý ’xù艻¹pÎí¬ún w§žÉ„áý(-ÞÍï¼Àœ³ÎåÕ»Z^2õì+ÙAþ¦¯ùã òÀs+(­¬ÁðÔãí _RvVXtp¦©P¦å5¬Î½ÿÞ0P¦‰iúï[nÊ4íç ¯—Cñ³sS8ý”(¼ž:0Qf=˜u(U¦/ 0¬mšY2QF³/¨hFy›ÁÛF3¦Ç†‰2<-û^Î[ϙà š‰×k`†Õq4Úîõµ= ýf[í?Ê¿ ÏïOF.**bÿþýTVV²ÿ~Š‹‹Ù°a¯½ö³gÏföìÙlÚ´É>...Ž{ï½—qãÆµy3Ï<3húRMM½*R[¶nÝÊš5kìQ‘˜˜.½ôÒ ¢|4Mk(DGGsÝu×qÓM7••†zöì´T]][·n=è4žÐ¶ÄÇÇsÏ=÷pµ&]×™3g]ºt ÚÞ«W/î¸ãŽ&ÃkšÆ5×\´ŠVYY7n<`;Uss3o¼ñFШŒŒ yä‘vŒ$&&rÿý÷Ûy.BqXLOSÍÍÍA7§‰ý¥E¼÷ï'¸û‰—è7b"ÓGúFÛUºýg|»µ-¥yýc>Zò.¯Ï{“?]Á¿~y!‰ÀÚOrã^Ç?f¯;VŽ„×æÏ¶n/¹®ãõÀ‹îcéòO8gê0¦^uËV|Îooº7°ûó¹î‘§)j„#¯`éò,Y¼÷?þ”å þÊÐT75û¿cοcG•¯š†ÓÔ”òÉêmdÅ?ßþo¾þ„«&ô h÷9ÌÈ‘#Y²dI‡ („èà¶~Ê=7^CJ\ðj]7غþkV¯³þ¶ä;¿üíq²¢¬¿´{¾x™ÿ[n\xÛcÜqñXûØ”Œn\õøßÙôí&~÷Áz¾™ÿ߬»çJ†N{jÇl¥ÀKzLw¢c¬/„¢ãbèÚ5Ãêõ¼ðßÿ¢À‰ySx~Þ³ŒËõ}qäNç´Y7ó‡í[¸àÁ§©ûòmVo¸¼qÁCzß©¼òö<Æ÷iÍ(qäH`ÑÁ™¦5jÑ2Ÿ_Ï (ì]¬Õ¢&žõçÄÓˆ×ëŸZ䥵_ƒh¦Bф š55ÆDGÓt«\ž®¡4_‡ZÓ1MìT×N1(­Œa凿¿m'‘‡kðRºÊT(ÃlUøïH M2®¨¨Zé©-‰‰‰Ìœ9“Ûo¿‘#Gt Utt4gŸ}6Ÿ}öùùÖðóæÍ›Y¾|9gŸ}v«ýËÊÊøì³Ï¨««³·]xáúîe IDAT…ÌÉ—0=dÈCêÔ†ÓÜÜLCC{öìaÛ¶mPZZJMM uuu|÷ÝwAû×ÖÖÒ’¬š¦1xð`’’’ºo||þ.ŸK‚‹h.»å^ *"H‹ŽÎ—Äl{¸:k«y-?^E¿ž..œEÏ.&†Ñ¼S;rLe‚QÄР»|×ÖÁðÝãÍ‹ÒuL¥Ó#µóGklØCuVpnÉÙ6Úoú«Cþ¹ÿF˜)XN§—ËEtt4C† á’K.aÆŒLR5fÌ dMMM|úé§a‹Ý»w³fÍûqbb"#GŽ<äpÏž=Û•7ŽÇã!??ŸÅ‹³råJ )//§²²’úúzšššì©k¼^o«mۮηÃáh5ÌŸˆÞžc‚“÷<†aRÇÿ@ŠŠŠ‚‚ª¤¤¤° ý¢iñññG¤=BˆTò@î¹íÒãÍ€ï u`?{ ËXùöBVîãËWŸâú´Á¼û?×â*êÍ@Ò”qôéþ÷Pvz.ÙÀŽŠZvìÝC7°¯¹±‚¦Zë÷èªEÿËõ…ËQ†§eÍÖ´‡}¾¤rOÀÔ[JNÿöÿmGžœå$e,-Ø÷×·ÐZzéÊTÄÇê\~V #‡èhúá›Óìk5XÓ¡”T³µIw ü+Ok:šæ²–‰Å‰®+~2ÀäœS]üß2º¿mvl´„•ïí7­éSÇ0ÇÂívsÎ9çàv»Ù¿?MMM8N’““ÉÌÌ$77—áÇ3hÐ 2229g!++‹éÓ§³lÙ2{™Ú/¾ø‚½{÷¶ZöuݺulÞ¼Ù~|Ê)§pÒI'ÒõüûçÛø½{÷òÏþ“ÿû¿ÿcÏž=A‰ÈGƒ®ëíj§¦i­ökïëÓu½UPbÆ!A:µªK—.í‰ ÕÞ÷C!ÂÊÇ-ÜÓf‹Šë?âœéç²¼¨žu¾@Aýµô‰mb[£õ·©Wzqm¬ß¡aZɹ†Âô¶ïo´~Sªöí§¤ÔúyûÚ•nXÛº/ éÄÄ&«ð†ùÒô¹ßëâÐI`ÑÁ™( Ó@Óµðßúû|ûïñšÌœËÌ Ñ8G¨®¼-—Ó40š±?>šŽÒ|yšeêD;š¹bœbÝŽÖîÔq~q~ö+eM…2B*xi·¨¨(n»í6&L˜pT®¥ë:3gÎdîܹv%æõë׳jÕª •€šššX¼x1õ ÓédÊ”)œÕ–Cý&Þ4MV¬XÁ#<Â'Ÿ|ôœ¦iäåå‘——GFF èºÎ–-[ZíÛQíÎz`…n°rP‹ !Ä1azXÇ"å¤)\v^–?û8ñQô޲ЗÖ44ám£o^ãi¢pÄ»IKk{ÑŒ€ÆPm¶Zg|r©ñ@¹‹Ùw<ΗME÷z‚';h:§ÃkÔ5·Ýçdž˜LÃÄ0|…®³í]hö*F Žâ†YqD» k·ƒ¦®kèºF´\NE´^Ó—ëáD°#•€`ƒfßàƒ…ÃÔÉJ¬å¿f˜ü×Ë)ì©Òì|‹ðí·ÿƒ©º©ûëõ3G»(Y=¸è¢‹ìÀ¢¦¦†¥K—rÚi§Ù Èùùù|þùçöhJ÷îÝ[-W{´¼þúë<üðÃAIãþð‰'Ò«W/RSS‰·§X½øâ‹|úé§’h ­ÞyO„“FZö`à; ¼^knul48€ï}Éö} ¤g·þbä‡UK)Ü n²3­ÅGLÃ7!Æ…Ã2šoìç›=¾¥mÛѲ¨¨bârƒ¸”.Œ>¬õNF)¾ÿ9• &C²û´ûU‹cC–›íÈüS TÀ”(e%r›Ê Y¦ÕªW‘ÝÅÉ­—$’™®0¨Ð|uît tݪ˜íñš—6ð͆JÞú°„yŸT²xµƒïvh”TXµòœºuŒÿø _ ß*SM 0¼^†õ(áºÓë‰s+L3 ýao ÓT`ú;ek8Óét2sæÌ åD?þøã š~ø¡]»B×uÆŽKÿþýzÛÖ­[ÇO<TL:• ðÈ#0eÊòòòHJJ²ƒ ¯×+ ÆBs8jkkk™$Bˆ£NYÝ¿ªê&ŠK¬…B†Ÿ5œx—†·ñSþ9÷ãV‡•[ø÷¿ß »ÿh†÷°æZ%wuá(^Oiu]Ð1;V½Ç¾ ¿´·ÿ7®;ío¹£»ö£o¿€É‚—ßäÛ¢¦VÇ­˜ûfͺ€‹/ú_n);¤—-Ž>±èÐ «Ó­Ž€„æÐÌmkÁ鄋ψeìÐ(¿|·‚Í—Æ ì¼ÇkRº¿™{𨏣‰ý•ΘLô¨~4’IT×X¾ªÝOtÑnŒÍ[ñÔì$;Õ¤·Zr2© VPâNüíP÷œyRßôdÉ:'Í^- (wø”¬n Á÷3òzõêÅĉyóÍ7عs'«V­büøñ¸Ýn–/_nOƒŠ‰‰aæÌ™G¥E¨ åu¤¥¥ñØc1xðà6‘p°Ðú%%%TUUÒ9”R455É{+„8ªÒ|K…+†ïoÌ 3oäÂ~/0wC!s½ô®ð‹ÙIˆr°ßf¹÷nÞú¡ ô~vûݤúzÆÆí˜OSéWÜuÍcüío&+VcÛ×rû]÷³³š0½M ‡¯ŸòÞg+yÿ«IŒÈëNfzn¹þ2Þúø!Ê~XÈUÝÊߟ˜“rºà­-eÙ[ÿæö_ü•: 9óVÎ>%_¢ý$°èèVõme†¬Õ²«RàУÇrÞ¤8ºf×P¦¢ÙcÐÐhPßhPQ㥬Rgo™ƒ]%^4g*qI}ð$fÑ¥[ ={ô¤wŸ>äæä妪ªšÂÂ=ì,ØÁÎÔÔì㛺m|þÍ&œF%Y©º'VÐ%¾šÄXˆuC”ÜNßõ¤Ä{¸dTûª’øb›£¥é­†;4ßëñUå>†oó±’ÀäÉ“yë­·0 ÇçŸ~Ê5×\T|¯_¿~Œ9ò¨·©©©‰¯¿þšææf{Û¸qã5jÔA•p‹=zàv»í÷±¦¦†íÛ··ë}ô3M“]»vѤr!ĉÁcÖ8¿×8øßO_^sÑN–~ºŒ©ýfàpwã¾çŸâ?üœ{wòøM?僗ΠwF_/_Äö2§ÜôG®?k¨}ª>æ0éÔ¹¼»jk>ù£FüƒÌâârpÆ0hH6åoÃë œnËèÌæµŸ¿ÂÙ£^áü_þ‰Ww7'_x ¿½z5¿xá]¾[ñ<“'.eâØ‘4lÿŒeë­ÌîøÄÓyâ¡ÛégEJ)<^†ü]Š( ,:8S™V]Ý HnöÝ”¶3$†{®J¦GO UÍ—6²w_#eªë£(ªNfûžbâÓé7àdzöÀ™“óHMK#5%™„øðI¦II‰$%%2xð@ÿ¥¨ªª£¼¢’ÒÒ}ì+ÞÅ÷›×±~ÕJb¨%7¹Œ÷^’\Ud¥B÷4ÈH‚ÑýÊp»åó’ù¾P³’¹M»ùþÂ~AÊš7u¤ßÎaúôéôîÝÛ®y°zõj¶lÙBMM½Íáp0kÖ¬V£>RJJJ(,, Ú6f̘ƒ&;{<*++%¸ðÉËË£_¿~ASÛæÏŸÏùçŸßî"…ûöíãË/¿”ÀBqȺtÏe`ÿþxûõ$ªUür»ftÿþT*“OÞÿœ¢K§‘à Ï˜ ùtiøÕÌÿ¼ïWÊ:4bã3Èí•Äì›Å/o½Œä€òA®Ôþüëå׸þÖûùjÃñòzý„›}”3RÖsí]ÿCÜL\ö¼ƒËï{„ »÷òö÷å(M§[†¯°ª–ÌõϽJî„ÿáÞ?>Ç®½%|¼ø]œÑ±de÷dÄÄsyà¾ß0nHË¢&Ž˜XzêKYzéé²hF$I`ÑÁJYùªuGC)0 En¦ÎÔQö”ÔñÑŠ8ôØ>Ôx2Ñ]Iäåå1eêÉü¼woR’T{4 9)Žä¤8zåv†ÃOÏ ¸¤ŒmÛ¶²uëf¾Ù¶ ½¼Œ¸¢M4Wl"9¦šÜŒ&¦©dWY<ÕM{ÀBÃJ oy] ÝaóZÇJ¯^½˜0a‚DÔÕÕ±hÑ"¢¢¢hj²æ“fggsúé§·Z"õhhnn­€ö­ TQQÁgŸ}v´šuÜÉÉÉaäÈ‘AÅ|ÀŠ+˜WæH^üdK«íâØ“À¢ƒS†•ca¦=µ(0ïÀ4M¶î¨ä©«HJÏ¥O¿Ÿpò°“9õÔôïÛ›.]ZÿðhÉìšNf×tÆŸ6S) ÷ðæ­|µæV|¿Ž××m`_áÔ6›è1ÉV ßk \)J)kÄÂ4;g`0gÎ^zé%šššðx<¼úꫤ§§ÛÏ;–¡C‡à GNBBB«¢l_}õÕinnæ¹çžã?ÿùOÐö bŽ'˜èèh.ºè"V¬XaµµµÜÿýÌ;—!C†ðø>úˆ'Ÿ|²Uw"¿§BˆHqÒïäQô;ùPŽÑIËêIZVσïjsð·/”;.Ñã&J#D„I`ÑÑùVQÒuç"ð›d…®k˜®DŠ* Jªö°·¨‚åË?ÁÓÜLNnýúõ#//ÙÙ¤¥¥ÑµkWÒÓ3HNN"))éG­ío555”——S\\Lii)û÷裡¨ˆ­[·±aãJŠKˆŽ‰¦¡¾‰ÆæfɸN0ƒG$‚~öÒtf§žz*ÇgåÊ•ÚÖÒÒÒ8ãŒ3«¸ÚáÈÈÈ wïÞAÁÄÇÌG}ÄÔ©S[í_UUÅóÏ?Ï3Ïûl;°ìÎ〘4éØ}C£ë:Ó¦McÉ’%ö*FÕÕÕÜ{ï½ü×ýÇ'!!úúz¶nÝÊÂ… yã7¨¨¨ --††{YÕæææz Úèèh®ºê*¾ûî;,X`oÿè£Ø´iS§Neܸqdffâñxصk_}õ~ø!¥¥¥¤¦¦rÝu×±páB;°ðz½G½ÆŠBq$H`ÑáYßì+2µø§H„N•Ðu=h4ÂårcÛów°=ºC§¹©‰˜˜h²³{Ð+/ÔÔbccéÖ­ƒ ¢K—.äç瓟ŸOIÉ>êêë(..fÇŽìÝ[„á5pE¹P†µ•Ëå¶–‚BÃíÒ­*)vÔà“h£ý!ÛÌ`úǸqãHOO§¬¬enMÓ?~<={ÊPò7}út&NœÈâÅ‹íNìÚµk¹ï¾û4hñññ444°mÛ6{šÏ AƒøéOÊ‚ غu+­¦òœhrssùÅ/~ÁæÍ›Ù°aƒ½½°°ýë_¼õÖ[tíÚÇCQQö>]tçœsNÐ3©"„âx!E‡¦P¾šÊ0[úè¾eg•¦­@ëÿ©eÊTðX€®ë¸Ý-K98ã\˜¦ÁŽ;ÉÏÏÇ0L<^n—‹øøxâã⨬ª¢¾¡Ãkàt¹Ñu ‡îÀårãŽÒ¬Kø8”ÍhX«>ÙíTÕµ}­ÕÂÖàöÕÙ09š)þz~ Ç|ºÉ)§œÂ¸qãx÷Ýwím\vÙeaGD)Ô¡7 ã:£Ýºuã÷¿ÿ=III¼öÚkv-Ý»w³{÷îVûŸ~úéü¿ÿ÷ÿèÖ­Ÿþ¹XS[[{ÀkùÏíowàã }¡ç:˜Ðc…~Fåz§v/¼ð÷Ýw_«<”ªªªVõ-¹úê«yì±Ç(++ ª]R]]|!„•œiš˜†a>÷¶54”f „Ù#t¢QëíN§¥N¸•”õÍscCh:ÑQÑ\fÛT&þ`¡å ;œQÁW yUAû[?höަÓ4Fi޼¸¸8~ò“Ÿ••e?û~´¹\.\®à6fÍšuX•¶£¢¢1bû÷ïGÓ4½zõ:¤s 0€¿þõ¯Œ?ž×_o¾ùÆžŽVîÇI'ÄYgÅW\Aff&UUUœqÆÄÄXËûåääpiU]×2dS¦LñmÔ4hP»)·ÛͰaÃ(..¬Ž{ß+]×6l{ö챦ß)ŰaÃX|0&&†‰'’mo;”›‘#G2þ|þõ¯ñÞ{ï±iÓ&öîÝkÅÅÅѯ_?†Êyçǹ瞋¦i4440qâD{E°¬¬¬c²:˜Bñc~æ®øQ”R3EÚ§²²’KfÏfåÊÕ8NÀWõÎǰW‹ οhÅ^VJ *°4 ú‘JW-ç Sý»ådþk´zÕáÛoŸ¥¥âŸËíâ7¿þ ?ÿùµG¥ò´RŠÆÆF{”BÓ4¢££I•k¿%K–pùå—S^^@jj*‹/f̘1‡|.ÿ·ù£QQQ¾ÏÍ¡«©©¡  €òòr¼^/111$%%‘Mbbð’ÅþåjýBTTÔ…ÀýÁ œNçA}J)¢¢¢ZgºnàˆƒÓé<`dš&MMMA#Yn·»Ý× T__OQQåååÔÖÖât:ILL$33“®]»íë)ñ º®uL?›Bq‚xYÓ´Ë#݈ÎDF,:0Ýá 5-L’KR)|}ö HÁ¿9äQÀ½ß¼Wëî]è!çÑÏÓBSí±cÕ²_íWJ¡i:ié©G­3¥išý-{$ÔÔÔðÜsÏÙAÀÌ™31bÄaÏß¡oo!¶ƒIHH8èò¨~n·;hŠÝ‘ÞßïǾÆC½®®ëGì3KïÞ½éÝ»÷A÷õ¹B!ÄñF‹Ìåt’Ó£'†2Ñ £eÊSÿ´#+}!ð{Í×á×úï!C­"ˆ€ýµ–ó $ýlWø~­˜®­A›í·ð:¦I\\\P]‡ÎæwÞáý÷ß·§¦¦r饗V‡[!„¢#À¢s»Ý 0|ëB¡TðÌ$2UÉÚG éµ[OX³¡4ßl¦À™£ÿFv@‡ÒìhF)_~G«ÿ­–©RÊ7ý*\û±¯oíkšYY™6°Ø¶mÏ<óŒ«i&L`„ n™B!Äá“À¢s8œz꺤gP\R0-È×Y“Ó6ß;x"èØ¶R&y®Õ¢S#vÚG@Ë4‚f=°ýx½ƒ$''‡ÎfãÆÜqǬZµÊÞÖ·o_~ùË_¶ª~-„Bq<‘lÀ.//)S§ÐÐPR&J™˜¾:¦ibø—f5LëÞ41•ïæ{ì?κYù¦¡|˺ZU®•ï˜À}­sXU­ê¿¾› ¸ŽiZíLö¹†i`*ëf˜¡çmi¿¿ †¯­ÍÍÍté’Á¤I§“Љ:Ú•••¼ñÆ\uÕU|ôÑGöö¬¬,î¿ÿ~FÁÖ !„Büx2bÑÁÅÅÅqÙesxçÝw¨¬¬Ä¡;ZÆ| Ïá_ Ìph½Þ“æK¶ò)¬s(ßž¡K¼jh˜€Žé?—2ýOÙ{´ŒR¶Ó”ïüV+B“»­—àËñ X^/S¦LaÆŒ‡õ~u4555¼ñƼüòˬZµŠºº:û¹””üq.½ôÒC®[!„BÑÑH`q7n^x!Ïÿãy ÓhYýIAøêÔ\ÃÉ~¾e¥©6êEh(¥i  ûÈÀÜn+¨0­óp¨€tÐÂA kÉ 1¼úöíË 7\ORRÒÞ‘ãÇÖ­[y衇(** ÚÞ¥Kzè!fÏž}ÄVrB!„ˆ$ ,Žn·›[o½•o¾þš5kÖ éz; „ 8ÚÞ3l‹ÖI{ž ¿ñ Í1M…ËíæÆodäÈ‘Þù8Ò£Gâââ‚¶åååñÀpõÕWv !„BˆŽFr,Ž àöÛo'/¯†×À4­iF73ðž6îÃÜì:uªeD¿úk¸›©Ú8ÁÏ·¹_èùL+/cÆŒ³¸ì²Ë:Õ´ ¤¤$;"..ŽiÓ¦ñä“OrÕUWIP!„BˆNEz6Ç MÓ˜={6‰‰‰üáàóÏ?÷U:Ö kðçUø3+Tàö€ÂÛíÐP-K:VEö$´lž}LÈTà9B¦n™¦‰Ûíæ¼ófñç??IFFF»Þã…ÛíæÊ+¯$//I“&1bÄRRR"Ý,!„Bˆ#îà3jÄQ¡”š ,:œc·lÙƒ>ÈÛo¿×ë|U«íÜ‹öOƒòÛ’Ìxlà©öž³­TËù¯Ñ§O_.¿ü2n»í6RSS©ÝB!„?ÂËš¦]éFt&XDÈ ,êëëY°`Ï<ó 6l ¶¶ö¶îèr8tíÚ•9sæpõÕW3hР–„t!„BˆcC‹#Lzsòc °¦°dÉ>üðC¶mÛFEEõõõx½Þ6VŒ:öQQQÄÄÄ––ÆØ±c™={6Ço•Ø,„BqŒH`q„I`!G"°ðóx<Ô×׳gÏ6nÜHaa!555ö4©H‹ŽŽ&##ƒììl @—.]ˆt³„Bqb“Àâ“äíNÀår‘””DRRƒ Šts„B!Ä H–›B!„BühXDNÇH€B!„81I_ì“À"r*#Ý!„Bˆ˜ôÅŽ0 ,"gòB!„ˆ”õ‘n@g#EäìÞŽt#„B!N@û€"݈ÎF‹Ñ4Íü ¨Št[„B!N0 "݈ÎF‹Ò4í+à6¥Tu¤Û"„Bq‚xøoMÓ$yû“y€Rê àa`àˆps„B!:£mÀ?°‚ЦH7¦3’À¢ƒPJŃ€¡@2š$„Bq$Ô›€ š¦ítc„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!„B!ŽZ¤ :œö~&ä³süQGx?!„B›t…rn›|N:/p¯ÚØ.„BqPÒa<1þ»ëÛ4‚ƒ‰À›¢õçE>?ÇÐÁÿïi< 2mB!„A¤cxâ ÂÝtŒ8þ… þmრEøD!„ÂæŒtÄ1nB¸×/V‡1Hr, ˆ 9‡8¾ùƒƒj ØìöU€ë3aúnm A†B!|¤“Øùùÿ –Σ¿cؘ LNu¸£ºF'§Ÿˆ3*æ7Y žÆ:šk«©//Ã4¼…Àj`ð1PàÛÍ?]Ê eä"ð^ !„BXtf¡£þ‘ @V§p0p 0-!«GfîØ©ô3•ŒþCqÇ&àŠŠFwºŽuÛÅ1`xšñ67Ò\SIÉÆµìøb)»WJ]Yñn`!ðw Ÿ–Ô`Žd„ËÃB!Ä H‹Î+4 ð¬N`p9p{ZïAYƒÏ½ŠA3/%¹gTÈ$%]ÆNIó}BüsœÊ¶maã³qáËTîÈžæ ¾Ý ‚ƒ‹ÐC!„'( ,:ŸpSŸü…+—b ð„;>aÊ©Wý‚á—ÞBR÷ Œf0ˆ´YtºN(ßYÈW/ü‰uóžÃÛÔ°ø °ësd`}Žü†LB!ŽH7@q¡y΀› Lþ–ÞwȨ³žx‘a—\ƒ;&£YF&(L/Ä$'Òëô3IËDÑúU›ª+G€b¬ÏRà—á–"B!Ä F‹Î#tµ§À Â…õíò àï½'Ÿ×óœ'_#kèP (DXÊ´n™ƒ’3f¥[Öu«Þ»k"ðPHppÑV!„Bœ@$°è< PáÀ *p&“ÖAC IDATð—~Ó.ê2óñŸ‘Žá‰X[ÅqÂôB|—trÆN£dýšäê½cu@Á¿?S5„Bq’À¢s *ü…?¨<Û÷Œ ºý»‰JL\ ÑnÊ´¦FåM8›’õ_¥TíÙy °¨¤åwˆB!Ä N‹ãŸú‰ÙÀ‘ ÐøK÷acÎüý¿‰IM– B2eBTb݆gÇò2+÷gcM»µñ³B!NXÿkSø —ï>ø]b·œ©ç<õ:©y¹˜2ýI&eBBF*}‡±í“wûxœÀ Z‚ÛÀU¡$ßB!„8ÁH`q| —Wáܾí³.÷mSøGïÓ'ãmjûDB´‡i@j^ènv.ÿ ÖJQ´ÔG ¬‚â¿I!„Bœ$°8~ùƒŠÀ)P.Z¦@õž<ùâëSÇÜxŸ$j‹#F™Ðuð)”nù>ª|Ǧ~ÀÇ@=ÁAE«ÃŽY…BX¿«j‡.-ëîOîÙû´¿ýщ (3bí+ÆAjî`6ðFoc}°ë³è¥ŠÜB!Ä FtÄa *§AiÀhÐ~:êš{Hé™…é\CEçd4AÖI'1|έƒiY4Àäú?›þÚ*2%J!„èÄ$°8~iO‡òwæ«» 7伫"ŸW¡¦ƒî¾i!]LÍ·ßQi‚ÞúzâÇ3¼0|öÍ$÷ìÓ¸+·ÇXøƒ („Bˆ„L…:þ(aÛŒ×tÇ/¦?ò¿zÆÀ]ZVs€Sƒºâ]Tl¡ª°šý冋¨Äœ:˜¾Ô^oM%µ 8¢bÚ4p¸¬ ¡ÝÕÂM/M••(Í…îìø–Äa?eúoŸ§÷€.`€æ8ŸÁgÏwŸÍ¦/^æ?óþ?{çE™ÿñ÷”­éHBèлˆ ¢(¢¨ˆbïíwvïôN=ûYñô,Ø ˆ¨(" (½÷Nèém³mfžß³K6!`9%AæýzÍ+›ÙÙgvfw¾ŸçÛÎçÂÛ.EARUdè•xÊ«°E'áŒU!:Ê@9ž¢|¼šqÄ—óCñ•” 4äèx\±6D„¶Œž\47IR!Ž„éQ! QURŽlwãŒu"ôc‡XÕ¹ŸB;ÎkBAkоÒR ʼn=ʉ¬‚ðú¨8Tˆl8“Rq8©. <Á²R¼åHŽ¢Râ‘1=’ zù!¼%>4ä?,7å—b¡ý¹ãYñÞ iU%}˜!O cž=šùVH”……………ÅŸ KXœ<ÔέˆôV¨@0¬íÈq8ãcê¯o… ¢lKfLE¶·âô{^§U»F¼¡ç5p¶èË¿½ÈÁË'°û“÷(¼üdEEøòYûŸ›Ù:ó ¼UìñYt˜ðwúŒIÑ—Ï2íá§È{k“vÝÏ¥OߌoíW,xù^ò¶!„„°E‘Ñcƒî½“¸D'Áƒkøú–kI½é%z íMþÌ'™ùÑJڞٗݽLayjT<ϼ…3n¿‰(E¢¶£GV¡bÝl¾|7{6a ê&£ûœ~ßÿïˆ|‘Š^Åê‰W±¦¸­ÒKYûÙg¤ ¼Ÿsž¼‰¢o&1祧(:TBFMê@·[¥ËОHH¾26~ð ?~ò€!Õù"†Ü}/YMãYñôxÎØÁ·×÷#ïÚ7ve4ß :ǵ0Ä6ΤÍY±ò£WξÊ1¯Mjqa˜ŸŽ%*,,,,,,þŒX¡ '‘• j Œ®6wTÓæ§¬W³MRÀp3eÅ%ÄvëFV§´£¼'FbÚŽ¤MÿôÒŸ¨( ¢Øœøv.cñëïÝf ­Ÿ‰âÝÊ¢ÇÆòã§+p¦e’’“…8ÓÚÑ,©r/sîžÀºÅy¤ö:›cÆ‘’ê`Ëç°`Ú,SäøË)Z·Œ’â$‚•Åä/ùŒù݉/¥-NŒÍ“îaÉì (ŽZ$ž<æÞskÜCjÏt¼`)©.¶Lÿ+ó¦ÌDÔñ-’xìâà§3ÿÅWSÛÔ¼‹?bÚµW³{ŸB³s®¤ËÙç"/bÎg°bæì.ƒm¯Ý̗ϾBUT:¿‘Ö]ÛRðÝ¿øâ¦K9| H|N;â“¢Ar’в3I‰±¿<™ý@˜áY9ÏF±Ù[0¯ÑºÂ¡¬*QR,ÅÉÃñ< 0²q—~$7oS¿• d”í!èä„8\@aY’âÀ™Ü‚«ðVxP%’.w½Åë/‡¾ÄG7\Æ)ÏÓmÊŒ¸½„7&ÜHÚrÎÿõ¡|Õ|*e7íÆ<Áy/]…KϦñ|2²#åÓØ–$Ó¢ e„K¡„…fãžæÜûï&6ö}ö$ÝöE¶¡_Ðá¨ãÑ ÷P)»isÁ?=ñj\ Tm¹‚ÏnOe¡çØ¡P² ’‹fw}Ä9—ŸC´ÝÇ׺QlËæôg¦ÓwX+ ÚéÃG7L`ͧÏѲý5üðÞ‡¨í.e̤÷ÈÎP0´{I}î fMüŒµ 1솇Ð6Îbêg^z?ømÚ8ñWýn§ð7¡ I—¾$5oëÈß¼f(°óÚ R̦SÝDÏò\XXXXXXüɰ„ÅÉEíÄm[èqI’Údöˆ#ÞNÀSŸCt?B€$ÛéB ‚U ÈØÝ.´ 5£Îb€·’{GF“‡ØSY„¯ìz(I à[ãÓ¸ðãexö`Ç”ÿR±o7¥{V’_lÇÚql´y .ø* ¡M?ܱ.ôƒ{Ñ0¿áÈ&¡’ÑŸ1.£rÿþêýä­"¿RŽSÂVÙÑ“cÏÃí‚`Ñ.öå !w4ú´B÷˜wrdde°«x‡×-¢ J&wÌ4NVðW‚¤ª4q ®‰qh÷>‚U``€ºÏ‹ts ' a@TrMºFþæ5íFÀ!ªÅ…FÍ\ ‹?–°89j-‘UwÐÁ“šÓwh½7ÄØZc‹‚ÒÂeø*!¦V¿Iݘ’m«¤ ìÍ0°%Äátªf5+†ê"Eu’§m‡J2•{YúÐu¬[º”€P‘~$‡ÍÁ±ç ¢ÓqÆÅ BžêâvÕ@Q¶e]ÏÚ¥KÊ/ÛÏ‘wŽF•Ì<£ª ݧ›Ø Wò2Îh’í1äy%‚e%@2©MšEŒlîT’Âcm è4?}$+?|9ÈÁìiYrVÃüˆ#Å…åµ°°°°°°ø“`åXœ<„ãÓ#ãÕÌÞâ2›)©íºÖkï À4‚Sš’˜G麅ämÙ‡lx^ÙÅË>gË:1m.&5EA‚Eåøª‚f•# dÍK©æG jXŸB’Q7ã?,=ÑñF¼ò WÎÙË_Σi5“©k®Uô ltÕ{ÿû:K¾þ Ñþ|Îze¶¹Ÿ¯ÿ3ûôðaƒ$!É>|¾Ý‚¡ªQ2HU¥yË@•Ql PAEyá‘ñIøK÷S¨öZ_Ù¤3„izÕ(ôçè.ܵs-,,,,,,,þDXÂâä l„EŠ‹ðLpеi¿áÈÇ ÿ9”ävt3<;XðàìØ¸Åv7¨6¨Øüßüë)’‹Ük.ÂåIv¢ZÆ–y߃ œ±P¼l2»÷ì'¶I61 `èz(ÄJEU ¢ô.:]ù8Ý†ç’Ø8–ÃË¿eg>‡ÅÉÿ<.éPVz '–=rÚáëHŒõ³yòóx‚™t»ä^Ü \qØdÈŸþ/æÜGtF*^6½u/îÝÝ ÞÂ’÷^E,øM½:““¥ 0s:sZ]×k ÐuŒºÜD§§¢âgó[÷•׃`þ–¾÷Zh?±¡GgÚ hkzB"ßÕÐ1tÓèÖiô{ûz™9Œ§jüe؇Xûî?ðjÍé>öZµŠ¥ß™ø~ös|qo€î#úS¶öK–|8wóñ´ìÓtPÒÀ3—5ÿyqñM´ì™…Q_%†0Øœéz²~ú;­€hÀKµ¨_·‘IÜ'"ÊÄnjzPDh|^ÌÀ´zN·°°°°°8¹±„ÅÉAíüŠÈ¥…#&.*¡iËz^M„RBsNšC҇ϰèÃ÷YóöÃ(6îôöœö؃tu6Õ '²'¦“Þ/ƒ&McXÿÞ? j¶¤ |z"=樄è6ÃèØ»ë6.`Ù´>LxövlXÄÒYS™·ôG&]oz µøVñ5›¿ü¦wô%¶IQQÑJ\2±9MQʳV¶E›Õšè”ô£ÌÜ`rκ•Ó6üÄ’™Ó˜·|2Š£ ]nx[é·¬žþ[¾üž–g´EÕª“¾‘$œ©Y$d5Åêœm ñ˜g9?à`îoóÃ?a%£+ƒ&þ›ý3 èòÐ,´èñ,ÿæ¾ùñ´ À=äVιãQ%©hÈ8ý6šOYÏ…ïãh5œ6}€°‘˜ÓÕáŒÓü¾f@!5s‚~o¯E4…)r€æ@S h‚éÑ‹6}×ó”…²zð»íÀj`)”ÀQíM,,,,,,,jÑ@æ¸-ŽCØ ²av×v`†˜¸Cÿ/­}·ñ½9Wb|½–š­$ƒ,C ´€’ý0P°G%•šŽÓ%aDvº–Ì\ ðÀWÀ‘˜†;Þ ÁU'l<>d‡Ù!# Ïá½üWR:®8t¼ÈÎhlŽv®•<^»D¬$…6©kþ\ õ¥8œGÀ'p%Gî§ÅƒêT~mèxjìK2;“û SQTˆ!Tb2šáŽ3»…áÏM@Å]x=•H¶xâ³2QmTçÐÈ@ €æ×‘].äú‚Ìc+ÎÛÍä+‹²½;ÿ ¼ ¦GÀú?@uó¼_J# ;bi´ZCïŸE˜‚ (Å8åT—¿ ‹Â'q@Zh0…I*°øX,6ÿÊÄÂÂÂÂÂâ”ÁòXœDζ†ÃIÂqë­â³ZàJjX¢BQG(1)4jŸZZ_;É\˜¸œ)¸$s»³ð IÆã6mx2QiÙDGn/)8ãâÍ—Ô!~éºcC:Æ~ŽýzQ‡N Áž˜JrR*HfþEd§ta˜ÖvT“¢CÝ š‰ù`³c³ýÌØO0†qÙÄ4ÊÊöîl†y½ÖÕ(/\ª6 Zš]n@GL`§º„í&`%ð>¦(•¡å·–2ˆÅôtd½€áÀ³˜¢h¦Xšìÿïoaaaaañ§Ä ŸÈÐÈæx2ælkJlz&6øë»űüªjUÂ8~à}mCº®íÿcû÷ÜÏÏ#˜‚ãgªÙ6¸Z­B€Í%›ž‚éY+?)‰4̶‰2f¸RLDг¢TûÐóy¡e¦xXlvòÇ…'•‡–]˜žŠgB㜠< < L¦?üAã°°°°°°8©°„EÃæX‰ÛáÀ—4I–£²Z68o…Å©‹0 ±Y[€DÌYÿL@fÈR3Ì0¦¦˜á|á0?/fnà àï˜ù…˜¡MõÜ¡…=À«¡¥0¸¸ X <…)B¬p ‹SKXœD&oG ‹›ÓŸÕ a@BN+@ÊñwªC‹4L¡p8ˆ9Ó¿3¤i¦ñ~2°xø7p!p50ø˜|ɯ˱°°°°°øS` ‹†MdõœHaq¤Ô¬ÍåvÅ6Î6»U7@d( hþc‡ É6%Ð'vl’¶P÷ì ¯aå*€9>Õ†ô6¶ã! HhÒY‘†®oV`Š†Ã˜Þ‹üÐRYÃü=Ÿ}€»0ó=6`z\æb ‹SˆRKÆâgˆLÜŽ,×-Ûì’;1µÁÅ`V7*[¿Íß/Âuš®…,4Š7,b×êí¿k¾€¬‚¬gl øó·°rÒDVL‰Ï_÷øN$’ Š-TKß¾­lŸ9‡2^ïcû5î¤Td Ó+1 XŽé™Ø ”a^¿NL‘|]€ùÀ9Àx̰­/·Üz—……………Å Å ›°˜ˆ‘ ܱvw4Žèèé±UØóñM|ùôcx¼uîÈFëÿ=žO„&ý¤²VÊ–©ÿaÝì9ÅÑû•d0 ·2Ü ¾zäV¾{ç-Ê«@©Çoƒ,CÑš¯YöÎ—éØœP´ècfÞ>†ƒEUõ:¶ß‚Ý#&Ì< fE¸¢SdO‹“ìÈ~–ÿbV‘º³¢Õw˜¥RësP'‚?ÛMýÏN¤Àp îäÔúï•LÏ€¬V{ " yEM÷бZ½Ôбo×£$û=»Š(á§Wîæ§ßBSê¸Èe0¼…Ê;Hôà{÷âHŠ µƒ–#Þ7üŸ9Æp¿ŽÚã!äy8²î8ß6Õû~ÀwßAQyY6{v ü É5ÞGªã}¤ãŒï„#@VT܉©` ‹Ú êNvÅϦb†G=\ |)6ŽåG‹zŸÑYXXXXXüAX9 —ºr+"“¸í@´3.¹~FGÈÖu ×-£hÿntœÄf·'­mË#3ìB’À¦¢Us`ÛJ‹Êp&åÖ±3n'è²›ì‘w‘a¸Q„Y?T±A (ü-Ûñz8ÒIjÙžè-ÔïAu@å®-Ø´‘ &p¥6#¥]g܃ÒÛÐ5Ã[FÉ®}Øš4A ›soåûö ‰M2p¹T@à=°“*9…X—ÃëÖ 'µ ½}Š¡S´a)…»óÐ$'1ÙíIoÛU¡xöoǯ¤ãR°e-Þ*ƒ˜ÌN¤µjDùîMïÎCW\Ä5mObF¢VÔ½$AÕÝ”ƒ,Q¾{#ž&í@–‘eEx)X¿Žâ…¨Q‰$¶Ì%&ÙÐÌãQ(ßµ‰¢=»Ñ%Qé9$5ÏÁ&›]ÍëYQq™=>¢9º‡EmqêÖñ§£x øxx˜z¼²Ö¶×÷£0ñýÔ%àþì¢îTCÔúkaaaQ¯X¢a#ÕZ"2°«gýŒLÉ_ÄòWîaÁë“8¢± /AM¥íõ9ㆫ‰µIHн`#ßß=ˆÝË×yqÓQ÷pæƒÒ(ÊâG³¸p4·½ûÊÖÍaîײe]Á‘Ýeœ~%}ÿö<-2ãØÿÙ Ì~é¯>Pݼ#áìpþÕ§ñÕèaúŠFwâü/öÑ:Û¦ƒâ€’ßæ“«o73‡ß½7ÏæŠI“Ùöâ…,Ü”Bvr);-%kØ“Œxž¾‰oÞý¿P±é:A)vàŒ;ÿB¬ìaÍócùqc"YÉeì^ºÕK÷ˇ³ñã)/óàÊê̹ÿþ’¦-3jTñRì^Ü<’Åk7ðÝuÝ(z6mmN„¤±æ™ ÈûnéßâRF½þiÝ@€¼ÿ>ÇŒG¡2´ÔZ_þCï¸W}5Г$BצêYúÚ×qøïŸ˜•£¾Àì1xøf)ÝÆÀ-˜¿ÿ œ‹Ùˆï×"ãïñ[œœKPˆZë,ÁaaaqB±B¡Nê*7«v›ÃY/f‚ªÂ®)O3çõIÄ÷ËÅ“—sÅä¹tjßœM¯ÞÈ¢߃’j'px'wÙöÂl.ÿørÏìÎî/ždÁK¢«.$l Ìpž@þz¾½æ|¶ì°Ñçáϸ|ÚR†N¸’ƒóÞæ›'Â#À»ñK¾úûö¶aÐ ßqŧ è5¢3%³fåêr†Oú”„ä(bÛâ¼×çÐ8Õ…š¹7Ý~,#ž~žX å•OpáSÏH.ô-ß°s“‡ž÷¼Îi7^ÌÞ÷dÖ[ ö¹™‹?]Å„©sÉ”ÃÆ·ïbÞ‹3ÁåB±¹1vÎ¥Ô›ÄüÄ97ÝŒê_ÍâמÄ=à.úàμî |ykX>ó;ŒZçKºèúØ»t?gÈnú?1‹žgö=HPó±ýûýt{h*W|ö½‡v¥tûÇlZô#’Jæ½Î´{ïÇp æì׿qùG_Ò¹_c¶Lº‘ùÏÏDª'Ý)É2²Ý¦°_¯áëNMãv0Ó‹q/fbûHàvÌ.ã]0GôϼW]¿ Õù+jh±ÕZìÇXg- w©}¾Â‹Zëqík öd”………ÅŽå±høÔöZ„o `SÎG"^¾m_¿ììÉÇߥi¦Ak?ó*Å dãG30ö $4°'3àé·évFG„)©.òVžÉ¾5_PVr!’,!fhÕᯰ½ØK÷‡§3øšÁMssñìúE?þ@Á®r|?þ—ŸJ·[^¤ÏýHHùû÷\ƒ/h'£WœÑv‚ édöîŠK¯ (‰©döìM4Û¶'Ms[#**††dK£ßs³xz6xó™|ÏgØ‘=E³æQ ÿm"{מÎî•“(+ BG²å0èé)´mƒÞ6‰•_~@¡Üa C¯8žÂó°wÝ$<ž‘¸•ïµ’„âp@µ°€£ÃûNEƒ§³™Þ7À#À§˜†a$#€`–¯­ëÌÕõ›p¬Ï´öσuò#¨ûœÉÏ‹Ðÿán÷‘ë-/†……ņ%,.uáÿÃ3”ª¢žøS(É,ÚKÁÚbθŒŒtA¿9:Gz²z4çð’‚@Ї#§Ù:¢ûMßž™Kv£t6‚š~$ëZ’ lób@°mÎDüë?BYغ<Êóvá=¸œñäŒè†ð€f€-µ'cÞœ‹áŒGìCBÇ‚¨} `hšùP¢k¡;°Ð‘Õ´ÏÍF €^Gii%)¹ƒÉÈŽ2{qÎ&íÈJm̆ò*üU~$É@‰:ŒÌ^U†¤žLl‚‚æC×Í©ÃcdU M`„Ô®ù1 ;°á ÉnH>ó8ÕèF$¾J á)f_° )XÂÚW¯eû‡1!!öSVèCs•PY&ˆI–ÐN`7È’„¢¨P[žA­müžŠâ`0ÓkÑ£ŽçïÀ,ÍûBèÿºDY]!eAÌÏ:³1¡ ht¤ÚÀŒ|?‹“‹°(0ÏupÈ =.Â<Ï6ªE…ú+êX,,,,~W,aÑð©m„…hzàw• a}øý §'"Kw;IErºkl+Ér +FHN ¤:nk’¡vÜ>/UÅEü&t:äαÄ%9ñxfŽGä¼$áJKDÅo½_J fþƒ"¨#‚ NG&ª¤ªÐZR±ËJ¨Ú•„0FËtlGæ Íý Ã0˧ŸÆq (2ѱ®£Ê +!"ÙáB ðb @²“3ôbÔ&]°Û%Œl>H€=³Ûv˜Úa§ºq{&¦Ñ_2ðfïÙëj ŠðcˆÁ¬DÕè´Å¬ÊŸÕGT,¢!Ö¥¶ø H††¿´„ªÒBCø½˜bõGÌÆŒK1û©˜•)Ìë#r c‰ ‹ßKX4lŽex……E0è÷ø;ƒ5&žøT¨Z¿ ãpK`( îáÐ[PbûcSE!°?å%¤$Å`è yòÙ_Y 1I•kÞÚTpÑù¶×é: ‹€T'-ŸOA¡NZv‡\1 â-ñ!55CÁŒÂ ̼ñJÔ1ÿdø¨Öæ‡$ËÕÁ¿æàB—Ù¡Qšÿ•žÔöS IDAT›‰WMï‡Q¼‡}Ұ%t@q˜Q,Rð÷; ²¡D¦'£®x7YAøQÛ3ä©é¤&BPUøØóゎt¢¢ê§2”0 ´€LañsBâTñ˜áNQÇÙ&x³ñÞZjæS„EhŒÆÚ\îö1éYŽ&]Ñ©î”4ÜñÉĤ5Fu¸ ±“¦ÅoBÍ[e½Êì‰Ê[2·ÿ®…_÷/ÞµåFC .ŬBö-àÃô`è¡%ükRÛ‹aaaañ?c ‹“‡Ú!$¦Ç"è?ñ·¶ø ’r³ÙöÝ묘v.ê ²æ“ÙQ ÕUƒˆ±ƒPœåËYñÖk¤þínb¢l›ú4óJH¿t q‰ÎÐ,ª@Ð¨ï¥ØÞ¹ƒU=GN‡çHL‘©\9—Y·]@ҙ˦Í&µcWÔ÷g±äÍ×Èyî>bUk?z–uk—‘; A z}h>œ¿!ÇÀ51“Æ-ÓX½è;VÏZÄicúa7ülœò4‡÷W1ðlâãm¿›±&„¡ã+÷€ìþ™m5p¥Ò¡sò¦-eéäY »ùlªÁîÏ_fú}÷;ø>.éÕ I¯‡ FÀÕ‹°1SWˆß©H¸ÞÏ‘…™Ì}>f¨Kd•-fyÚ;\ ÉÝrœEûsÆ‘ÝkŠC1p¡OWÇÌ·8‰‘% µmG‹3Î"èyŒ-ßMÙøù»ƒw-úf f©ã‰˜ž/¦ Õ©öZXÞ ‹ßKXœÔ¶ÂFZ Xå©có?!W*®~‘kDZè¾Ál™5„m+»~ÚN\—Qô}6 ù+Ø=íïLúé \vƒâ;peCßkîÀ¦U1z ’{_AŸ‹¾å‡O_äµ³HsR°k3)‘ÿ©pæ5të5“%_ßϤõïâ²û)ܹ‹¸.ç“;ôtdgiÉé¬Yþ9“/éϰW¿%'ÝU#Ï@/ Y)Ðýôª€9¥'@Sâéó£Z3Ÿî;“=ŸöD ”qpÝj\Ùcé{ÝxTÍ‹æ«D÷G†¤ ‚U•}Þê5ºPƒÁº?SE"!%ô*f_ÓÏSŸÒAÀ¨ªÊ·=@סõ-°}ý9¬{~4ysºa7<mZ‹-µ?ýn¾ ×ƒ¹ tƒ · ÌfqPwnœºÆÌ—@? Ð>´´ÄôdD‘ʲ7fÚk0=³DíCŠÍ~aëaª=¯½Ô¶ÐÔ(gÉ©úaŸ*(NG_J»á±uÎê—_´cã ̰ºéT{»"dx”u‰XXXüOüÖèo‹?žp uíÒ‘‘%;¸âÛwsõ ¿ÜmÈêÚ e9Tz0¤xšôÅ™›HjzºFU1®öçÐý¢s £¸ãiÔm4§?òM›ºZ€¼9ﲯ²½FB¶9iÒwIJ€®¡Ûc‰oÓ“·=K¿³ €=Žì3‡¥–¢ìñiÒç*ÎxàŸ¤¥»Ð…“„ìt‚ºƒØŒl²úžE”K9"`$É Õ1*ý¤öNrZ ’ø=¸2úÓ|@gTÅœåu6éH³>¹hAŸÉMêiã8ãŸÏ’áÄÐ!è÷`ÏéI³Î­Íø!ð{ƒ4êЗÌÜnf©¢ÂGJß3Éh–yte(ÑéYÈ"ˆ+1´îƒINŠ{Ù§ Ê©˜/:z…ŸÔþÃHoÚ5>“æƒF@°ÃëCu'‘Öó<>ô2-Z'¢kœp$ 4Ÿåï=¯¬xæli3æ;Z´Ðž==Õ‚ÿË0“³×s€€W1;vÏÅl · 8 x\ÌŠ…@gàÕ˜ôÌaƒÿúŠÜÿÖ‡‰nÔC£ηEÃÂÐ@RdµiG«!PU\[°eÍ0ÌïØZÌïUjÞÂÂâÁòŒ7\dªÅ„pbÎbFaÎh:€K㳚_}ÝWÛ®|t¢i3¬Ò0$ {”‚ЪgL‡ÙÚ™Z'`€®ƒäÙÃ7×´aƒz/·¼ÿwÔP¼†êÃ+ÐuPœ²dÎÄVïTè3ïAu˜=*Âû•mæ~¡õµÍVìn>†Œ1Õi¾&è©iåʶça aw+H:G< ŠT üÕ ìn@@ ¼N[€à±òíe°ÙCš#†l—V1~ ìQ "ÞGR@QAóêIÁæ4…_}ˆ 0«†y‹‹y}hþÊò—0 ç* ð„/fìw€j‘aq4Ñ@2fó< <›Ùý´Ì3þ7©íÚ›ß kžÙ¢d Í`ÙÛϱðå‡Ð|ÞW1›3z1æÂß=ƒj‘oå\XXXüf¬P¨“›*ÍïÃ[^Š+>¾^:,A0$*²Ý_óyÝoÞ­3æ[=È`mfñ«eÇ&F?3 Z š×¬¸dÞ9º²‘šßœ—BÛ5®º£ŽŽ¼>PYs•æ«Ûº5‚æØe›Š,™b¦Æ1ú"Ž1D ªÖ ÁZû«kLA_äc–ì­õ>µÇ-B"GR”PøÙÏìçà¯('è«SP€âÿsD–’–¨nr&cö¾(Á z¶Ù€á™g?ùÑ©©ì¹–LÁús½áþ'åHÎJèjVTÓ³y¬Ð³úÆÐ@’dz_®¸$¾{ü¶U•f/•ð¯fí.ÞVX”……ÅoÆ'µ+w„´*Ýï7<dwR<¢¾æ|Å/LeieÞ|ò–.ÂÖö,FÜq%6‰Zù¿à-OÔ­/<ö|«m($* ö# #þæXUhÈÈëȪOaanž— ¼Þ|йG<þî䄚¼ßIÿÞÕ¬øø}|†j xYA–e³Ø‚®c ¡ë2Ïþ í:5>¡½RêI€ )*²]F”ïå‡WEi{%}F÷9þDF="„99Òyì•|ûØÍ7i>of”ÚÍ+ÃXâÂÂÂâ7a ‹“ÈIeAŸÇ[ºoWT£vÇ*‡ß0APŸÆÅÓö!dEm¸³~¿I‚’=Û†QÉÑ ËP9šÚMïªE… dO¥wêÕø¬GÿCTr‚éõû#$ƒQ²›Ý_DI@A–!XY€¯2€¤*¸S£š0pv½º4®vÛ¯×÷ÿröÉÕó[öý ÊH*v/燞ÀÑãN€î/a÷÷ŸôAß ûT»;7Æzúa†Fv{%U¥…Ò‚çî»CÆàsªÅkm±o}W-,,~5–°hØDÎîJuü_¤ù}U¥{·EÉÊÑá8'¡ðKTüyd(Ù½ÌåòÐêpÝüH,Ã¥nAY°! x8&#»ëðÇ&šö‡‹ 0g¸-†2òퟒŒâ(øæ5>ôq÷"çÝ1 Ãg`ΔfH*ÈFÈ]”Àæ…2ê¦q®(é`¯:Í×|Þ6©z½î5/Åò衜. ‚þšÛ‹P·êÔ`Dæ]f®,™9^a¹Izзb«5–d(þ}ìœ= [Êå ‰Yu"+*6—9Y"„™WV;¯K¶c†Š†ŽA’ê!dQ˜ç¡çU¡x×纩oý؃Y( Ü©;´eǧH ›……Åï%,>µã_Ã% !I‚¢jaQIÍ9a+ êh"»h‡E…-´~ŒÍuÞàû^$µ}»£r˜þ0H7qÙM³@A )ÕLþHlFBV3JÏŽ5¬øt!™ƒpð»Ï9\™Hîe·íaó´O9´u>O5:™´®ChqZT [>|Ÿ2šÑ~ÌH\vÓz•´ v¼ó…–t;W®üž­ßK•LJ-¶©ýΧe×ÖHˆ@1›>øg·Ó°üÄÖ%»H=ëN:´aÇŒIì^µŸ7€#.ƒ¦ç\MÓ6ÍZáÖϘBÁÁB°Åت+­‡ŸgŽ%”–dðí]Ǫ)Ó𠯞Ƣ’é”o6álýüö,[f؉ÊÉ¥ýÈ‹ˆ‰V0 S°”¬žÅ¦¹?RUéÅÛˆŒ>#hÞ£’vb­va˜CÝý …[×5:¸né]À͘‚£¿£Ö”……ů ›HO…±„ø@AÙþ=Mƒ•I–Lœý‰E¬ð ëŠÝê´×¹•¬@Ѫ¯YöÞ(ØÄ®g1øî‡qH¿C~‚$PáÅl®dõÔÌS–dð•û¨8´ ó _¿‘FË©> ™¬^"ËJË@GàöNc®¦ÍðQG øÃÕåk% ²1 íˆB±AåšÙ|÷Ľ8ÞJ *¿¥Õ0Z%›ß¿šé?EIlJ\cž-ÛYúæ3´ÿ¿©œ{Moò¾~’åËKptÍ£sûàÛ1—éÿ¸ w¿Ƚr8{g<Â=N¥GÆM ¸í­—èÏÇô½l ”`Å?oçpb2RE!ššD§.c`Ö‹|õÁGÈ1©Ä§'R¸q+¦ÍbÄSŸÐá̦T­ŸÇ¬ÛDZ}ûœ )¨Äçñ²tÙ£\ò׿m¯þMðnÿžŸOÅ'À¿ñ3~š”Fë]Ž¢ ¿¼Ÿ)S˰¹c0¼Å|:{TqþWc³Áþ©O3ãÑ{)ó:q'Ä(.@óÚþm2#/rÂë¡ ÜÉ ºïy¦\7üŒ€§âJàeÌ2浿§aÑ{ŠO—XXXüRNñyîM]žŠÈçÂëw—äm£²¸°ažÍZ-ѤÚö¶d£’\Çs‘›k; ”à~<<ŽÉW aÞ”oëîÎ"eÛ˜õ׫Yóåt¬Y¾Mk ¨:26©®1ÖG­m$¼»f3íša|vÃùlXwÕVÇëŽwœu}Nµ{T×Þ¾"+P¶o7•ù°ê\u͆ÖåÁ8•8V^… ³´ôMÉ-;¤÷¹þo Ú &«vl@ÀocÀ“S¹fÒǤºö²bâ§8[^Î36s÷ríäé$ÅäÏ_HÐCËQW¡àcû—‹63\èà¬O E‡KÎCÙ·‚ïŸ}ÍÕƒQï­çöeù\óÑ'¤'V°è‘ûÉ?¬!É ŽhâB²ÆÜÏøkØÙÍêù3°µÈóqÍÌ\þÊ¿PŠ–°aÙ„ágùS÷²}‡žNçÆŸò¹å» ôèÛƒ’ÏþÆÒkTÃ2‚ßÿ6.õ5âl2æunšòÑN ¡ÐýzÜ?™Ìçº)_“–;¿™O¥‚ù«øþáñÇwgÔ‡ë¹mY>×~0Ôƒm¯_ˆ¦Ôº9J`r›ùŽ(ì@A„–—Òkü¹Ø%ˆm9„ŽƒºAÞn¼(Zð2ûü-.¾Ÿœv©T.ÅÕé ºìNðÀ6­Ú¨a!Bù}¯{äí“1Å…‹j¯Yd¾O¸²………ÅÏb…B–(>U‰ôTÔUb6¸¦iÿ¡j³Ó‡díÿ ˆËly¤I¥äˆÆf¯`Í¿ïdߺUìÜq$ö­qKGóÎ4íÞŽ%óVqh[±ƒ;òˆ?ûo¤¥ÂFo°ãõq¼;³)BHxó·PZP$Ù†Àݤ vÙl¢©$äÐvôµ”¿ý*‹îÁ"›‹Œ¾çÒ|À¹´» £¬ü:åÛ¦ðáØ¥81@’ðWQ©»l'AÃì@‰fÍiIG&G„ìÀžÞ ‡  d› F1²Q °þÛØ7=YH2”ïßè* ë-&PàJŠ¥×Õ÷òåƒF ÃøøSÜF†Þ†C¡¬ªn?‹%,.µ«rÔá¥Ø•·xNçöç^\ã¬É½ XòÚcT©1¬zWÇç­B² ¢ÿcpèë˜üÀTU˜=š$ ĺ%ìœó9×þ›³‡S¯dÙ}3oæl‚`; ƒ«ç³}ÞL==Ž™%,ñ™#¥‡*–ÆedE\T¸q”®œÊo½ÚªŠ­Sž¤øð~2Zg0ïŠÁlÎ+4Ç!›7\€k7pþÿ"F­déý—0ï¿_× ‡ž9‰­ó7ÐëŒ(–Mžf®Ó½äM_|;:öiΪû.eîôYGÛÜœþÄTzï@ÅþE,}õ1*ÕV¾kà÷zé=46.Û‰½hÔc$-Zº(ßú5ß¼üÐü/§ÑEn8-«% ´*¼¥óÁôV1“?Ã}à Ô§’±)*"=á(¬:\]û\û 6—ãÄçVüFDÐt«H*xÖ|Èg7]F¥OçñwÑã¶ŽÄ%*Ì{ä&´PËxM8é2ê"–}u{×~¤/¤Üï¦ç%#P4Ì~@ó+§E³$´€I 8ÝȲBB—æèúsŸš~Ĩׄ‹î7†µSÞpì]ñÃÕ˜¢ îœ¾Sñûjaañ+ihÁ3GS[`Ô%,¶æ-[@ Ò{Üü€Švl@¯Àç­"©Ûp:_t¶Ê,záiª*¼Hqèqçœÿâ뤵ÈQΦ©ÿdçÖ2JÎOsLQ‘;‚¡/}ÁYW]M\,mä§WþFYT†=ûñ©1$tºŽsïÿ+q.ÐC–Ð ù´õàýÄÇ*@]nx‘¡·ÝgÕSTÈvÚ]û$—½3“N[¿u#ž”-›ÎÒ9¦¨Hèrƒ}»P°ü5òì}8ëÖ Ì)Q´ó<Ã.Jù©ü4Û †1ôÅ/qÍuÄÅA°`+?½ú7 ÊAq:°¹ÌÏÉïõÔu¹—§Õ¹cqH@ñò7oü4Û,ÕÕ®#š¦{CAOQûW.ØŠ™¸]Û@‰§r(T¤¨ˆLÚŽ.l>è¥I÷~'¨ˆDR hÕ4**môyèKF<ô GŸK“œDôÒb$Ùe ø Ät¿æéÛ§OdåôOq5@ë–è8qH€ÝKçq—‘{ÉxºŽ»œä¨ö/_„ûYóò‘Tðå-eîßobga2Ç^çsõÌÅ´r€oÍZ‚j,²SÆpF‘sÎÅt7ž®—_A‹ÜV\½ˆ¢ Û1îŒÀn«†(iǸ„¨ €†­iW:]r ¹—Œ§û—#J9°zAá@©Çßl¡ƒ#ÎEç‹®G’•@7BÕ€9:ª¡Ü],,,0–°hØDz)j =bÙé9¼ß¿oùÕ¹ Ùcãâ7gröwblùŽCû ny†!wŽ£Ýù×rîƒ!6J‚ŠMXõ»W/ Òr\gNø}z:›îƒž |Ývòi>ê*¢“H8„¶ƒúátVWuMÚÐzèhœ1*KFßÑ4ëÝ–„Nç2zâg èrGŒ2+Þ¦HQ;vÃ`Ϫy”{Ar·¥ÿoÓïšë9óáWérÖXZMjvkšgÞum2Ͻ„–í3سjŽùºØŽœöðûô:ïlº=ôz_< ¨Ü°‹Ã;ªùÈç”9ú!ƾ1“aÿ¸›†“”ž”²ó§5ˆª ¶ÿ¸Ö<Î~—‘™æ@k@µ•dò–}¿¢´È£æ5jp´çâT ‰:^nE?ÅfïÑùÂëÞwùWÌWÛÜÙ@­ )؞ǡ%s˜}÷hV¨ZBÙáJ³ÏEl3Z9²Õß²g]1í®8èhÓÓØ¸×H§+¬}åV~üb)e‡²}Ê¿˜zûu¬üvªC1ËKד<À–)¯3ïñëÙºb7Þ‚|Š6¯¢,rœ›˜–}iѳ/ekßàûç&R¸'Ë¿åÛëÎaÉ»ñ«uW•3BIlµ‹Ù¿¥øg*ÉèAÔA7‘Åê‰W²lú|*öç±ã½ç˜yïu¬š7ÙíFªço€æ‡VCÇШu§à\ÌÞ)6ªodÈž%.,,,Ž‹ Õ°©]ï?,."5 È úªŠó–|—ÞòÌ¡'~”?‹›¦G« ëô–†ÊX¦’Ùª-Âñ-úâNŠ£ÜSJáÞCDù Ì—7Î!:=Í 8$š´ê“)øªà/,"è—¡ÂóÂïG ]>Vè ùý¡õ†ß‡5>•’ o±é«w9´»¢ÎÑK!73§%2Sñ—ƒ-£'Ã^úäÈ6%+íH û|è> _¹™b gd“ž?4nÕ'“ñúâ/,wXß»hzÚâT4ØçÒªcQ0gù—erøà^ †ÖCb³™¸ ’ ;ø àf~…àhqqªŠ‰0u…A…p~ã®ý”Ìn}ÐJŒ[¡h@ Ö»a˜ëÃY!å´ du˜ÁÖwîaë;÷ØiÙí`Ïš/øü_¯3áÿ‡lƒ¬ž«|Nyl{rz÷F i`oÖ›Áw=Ê—Ï>ÍÜ›z1×åo¶Ì^Œ|ô9R’T´ba6½“ÍKJhÕrݯ¿E¯¼Æäó;Ÿì¤´°[Lgκ~,Nw=îùùW²þÕ[Ùðê­ækÕ(rîšL÷m̆{5Ž\;“‘•κeóΕEÜøÖÍš­Öa Ch¨º0ø‰g™}ï_øö–Ó™‡édtev¦ï#oÑ8ÍV£i_½ Àí wì ÌþûõÃwm˜âBǼ6ÃßݰçâTþ[XXKX4|Ž¥c†×˜È¶í^ü]º§ G\,¢AU‘I Ê•€0@H Ër(dK#¨™ÉŒ0‚^Œ€i)Û2R(}R P޼‚º7ÔµIEV¤ß˜þ(«°ûÕëøþ­™@4{]M§‹ÏÆ¿òsæ½ÿþ‘™P#äÐ}A ÍÌ%0>ò7mA2®ôVHµ:J’Œäp™û PDu YM÷Õ5±¸]‰fއC8h>êÌ^E@ÌgÅ'n*yP2’Õ¥+RòV(6(Ù½?µð`^›uçYÔŽß>ˆ,"\Û[!mܶg_Š#ÆUÝ]º` ¡ß8ÆÜ%£ºQŸ€„>ã¸ôã~D5oŽ®™¢^MíÄèW¿%oùb¼UœIMÈè~:ŽàVv¯ØŽ½I'd@@tî¹\øÙb‚J)Y1Grô¤Ÿw?u:‡}kV⯠àHL'¡cRÇ¡@v5gè'?Ar 澃¸èqËKd÷>ý;w챩¤¶?ÔVÉ>°gödÔksØ»òGJòKP\ Ä5mC“Î푌:ZÄ E7çôg¾¡ÍþƒÈ 9¸5aø‹sÐcr0Ÿ‡!Ñr«4:K"9Á¦AãÁ70öãÞìÝ´ŽªJ gB*Iz’š™\ÿ¢"„3à,b7-ß¿{°óºŒüþ†RÔ-,,,Ž%,Nêaq¡Ë 6¯éŸ¿yœÝ{@k—*ª3¢ p$7Aq«PžÏ¦/Þ£U‡‰‹ÑØ8û=Êò«i-Û¿o= ܾ‚Ís’:¾?úá=¬Ÿ5Ãì?‘Ö”¸ÌF`¨Þ•¬¢Ø1½ÇC’‘DƒËÌŠ¨î¬«ýÞ‹$9 ~Xþ–y÷”E®:‘€ÀŽ5l]¸ŒŒñ=(˜ÿ&“®½¤(:?¹„­Ã¬/+¹eââ2Ìñï\ͦ9óI›0 ö±~æt€Ü(‹Ø¬Fp0|¶">'̇ / YÚl÷–°^îaî IDATúƒ™Æ­úÐ(ÛŽh@Þ Y†ý+P¾·XZ]Wòö©Zf6RTDV‚Š=£’S“Z :·Áœa„jb)iƒ#åo…¶ÐzC¯¾|…öF9´™cVf“ÌuBjK«am«ßC€P$wì…$@¯•4­û!:«m³;„s¥Í׆< BqÜ­7DŒ šd#¹÷0’{Õ<Ž#¯Ó@ŠK§é È]‰æ{+ÄIàjÙæ­:€]‡äv]ÍI€pï@ ªqb2MÏ!@‚»E.mZäÖ8††TíK蟕If÷ÿgï¼ã¤(ï?þž²íz?÷¦"DD X"ÖØb-&&êϨ1Q“hc%Æ–˜ØKŒ¥¨ˆ ½w888®÷½-S~Ì77·D){ð¼_¯ym›}fvvçûy¾m«w”Œ^êhëQÓi›kq¤ý†Á> „EbÓ‘˜°V¯ÅVC×¶¬ýä­>EcN=¯[Æ·›vÏ™ì±2ÒÞzxÄ}ï^¶ {ïÑh3icÄ W‹·ßñÖK$ìBVO¿ˆµSßìohÑÀW´z-ìóÕþÍ&ÖÜ•@ HDòvç!^Ž…ÓcQ,Ù8ëê¶oG:ÄÍÓ$Ù›y £­¤N £þø,#Æ C"Jí’YìXô-¡NZ¿“ïcäåÉȽŽã´ß=H^Q!FÓ.¶Ïý–k·` ëi7pò7âÑAä3|ô‰¨@¤b3Å ÑÒr»4C=¢è†‰aÈôÿù då¦bÖ—°øï¿añWëÉ9 ÐK°uÑ #&qÒµ¿"#; ½ª˜­_þ—ÊŠZüY#8éOoRT âë:’az©Ñ¸e›—oBé},§ýî!ò{tÅh*§tÞ·ìX]Œ‰Ÿ‚S¯åä;ŽÏCòÅf/#í Ýã¡Ç¨³H X?U5{ÆõK¸ÜŠÊ +Ùòõ§&°°r{A›£>¢ËV:Å…í©P€ž²¢ ê;arâ%m ŽÌ>™´Â`­]¸í Q’k‚vˆ?‡ÄÇ6Bì:÷>ÀU¹#HÒb·½¤ÛÇÜxOú„;ïß{8ÐD£¹†êâ hºŸŒ>ƒIJõî3U XIé‚9T勤êfP8j9]RÑb†³â…¦íë)]²–†f¼º¦è? à‰ÍfJ@¸žŠK†tä´\ Àëu„5H@¸‘ªÍk‰FT2z%)Í‹¤@ÃÆÅì\»Íð’Z4’‚þéÔmÚDT7tHFNŠêÖ.`Çò•„#:r “üá'Ñ¥_FÔ2®#U%”¯ß„f@ ëò{ x!¸m#Û—- Xß’—¤nƒèöƒãHñ‚f€Ù\GUñ:4ÝKF¯!$¥ûv[MsÇr^¹ôdvn­¡Û/qɽ?AJ PÅ ŸÜu+Þ}q'ð,–Ç¢hÀ¼MX9-@« ­3LêH Þï8@k·ãŸgöè÷ó‹ÿ5‹ônÝ~†[px"É0ã·°äõ§–75X¿[çoמ08Ò 1‚8a‘øØ3œ*Ö ’KXh©Xõï“€+³z÷ãW甓}h“¸%PTëÖˆ¶]–d«D©ýüîøiטe%ָαž¡¹¶gXñÏñ 3çx´ÖxpYm-s"IÖç˱ù9Sk §r®gã4%%VZŸ¶ûwüŽxôÇ%”Íú€õ3þÅÒ÷§ñ0ñÅÙŒ<¾oÂÄg+¨Ú¸‘·®OSeÙ'ÀT,#¤KX4`‰Šf,Ã$L[a‘€A?û{†×žýuþ†X¿Û§úO¼`Ìù·ý¹}a8ØçP0)ö¿‘¨Au‰>>ªÖNûˆ~qN¸Xõ» bý®Ý“`¯ÁÁD„B%>ΰwX‰FkÈI$öÜ×5Åë‚ë>y³M§CB,qQSþZ ÍZôhüxjCo¿^»íÙŸí@T¸Çã0\ -–4joÛh]Ïè`={i³y½u Îýˆ;~§áÔÁ¸dO «û%óß›BÄ„ü'Óÿ˜¾ ÔjÛºYõþ¿hª,«a§v(”}nî-$êp§£³19IOzwr¡}Ä!Ö\OK]]ǹQn"aBuèÑÄÔ§f¸…`u FÔìÓx†¹ý†’Ú¥»Mû&Ž¢až@ Ø#BXtL×âLŠuŠ «‡Àü…¯ü•úÒ²Cžk!øŽH`h &_dz¯dÄ¥÷qúÝ’æ=A OYò5«Yöö³ßµ´=ÝÂÂ:‘ {qÐè¨"”tW}¾œn#Ç%Œ'êP z5Ö>~=oÝr%Uµ:ê^þ»$—½ÂÛWŸËÚ‘ã÷µ;dH*4.}‰W¯:—kËQ;AîŒi@Zaw²{F«§Í)(„°"ªBu:JܶFk–؇嶞S¿½ø¨ÿ|(sÒž@;R¢Ø'L0t/CyÃc—r=Lâ|—±Ð®yÏý‘PC]–·"Šu.ÚçcG ÜGRE(ÉqkgΞ^ wJ~W5³G¿}Ÿ©?˜ìŽ´BýLGUdIn aŒçi‘”Xx“Ñq…6»Œ$›´l-¡z³N4j 9fEâ}Ž$Þ\Eùº¹4•×#íiš,’„ÙG(öZ¼P¬ÝŸÝ¶tûíË­%kwo3ÔHýŽ´´hmB?±êXãò§zÈê3˜’¹3z™@5mÏY¶çõ‘ð;ûˆðXtœÆ˜-0¢ŽÅûZÌ[óñk”.Z(ªÌtbô0DC î½ìæÁDõÀ¦™SØü嘋•äiŸ‡ÎP([\©%f¡íÌ®[X$ƒ» Çë;cÛ+’dåɆFKM‘ì±òkŒ– -5uhz¬ ƒýTÂÕU4ì(#ØFñÄ w{ÙÊ-Š66iŽ€êAõxPd¹ÕjU,c_m›ðX‚¸Eç 6_¸; ÊnVäαÇ/ðMK]u¿/¾­ßžŸ†/59¡ SAçEV ¾t_>r;Ñ–æUÀ2¬óÑN̶g¸ž»E‚ÎÙœ!PÎP(+y»áˆ {«iš¬BÝœçùðÅÏé?i›ÞyІšF|é=9êê»ñ×Á¼7ß"1ñçtå¸Û_`ÈñC1M0*Ö3ï™;XõÕ SÁô(œô+NùÅU¤¼˜èÕ›øüÛØºl%š!“7|"Þò*$%ÉáM‹˜÷כذt'º¾œBŽúùã Ÿ8ï>˜µŠ uóßeÖSP±£ |ô;çj‚«¾@λ˜Ó¼œÆ/þÃç>N¿Ë/¡äç(‹öç‡O½GWÏj>ýãl_½ÝÐä$òÇ\ÀØ_ÜK—B?¡M_0ýÏw‘6þzç¾Æ¶U1U?y#.à¤{ï#·K¼åß¾Ê˾Lí®j”@:yã®`â¯î"Å/'\N¡A—á£ñ’2#ÍM]•´nq‘`{ %ÂcÑy°±x!Q×ÅŠwŸVºxNhþ Xab~Ið=‘d0t¯Ÿº—š-ëëYXcÜ!Pî²”î¨#ílt‹ ÛPKr2‹ú%^>”Fs-åóÞcö}7¢§õ¦ÇQà møš™¿=©=MRïÐ}p?ê×Îã«Gn£¶ ÔH9³ožÌ×oMÃÛïDŽºäò³%Öýó>¾û ÂHáJ>ûíE,›ò1jÁzŒ:–º¥o±~ãð{‘UÑí+˜úÓóXðÙJÒ§×ØqèÛV2ã׳ðËå–³bOÃW!¸ú>¸ö6­«"{øxò{d±ü‰ÛY=c •›ÖcÊ ×ï¤lå·|þë[Ù¾3JN¿ø¥ZÜ}1+§Í%uð© :ïò ’ÙüÎL{üet/˜-UT.YÀ¼?]Ïö­QŠÆF^·L6úÿæ4‡A’=xšŠYúô•Bz8‘€ÒÀÆ×îæëæ$¤7Ù0 £k/¼)2GkŽ…ðX‚½"<{VÈ6ÒdZáT,#ÎöX„°ò-¶³¾üø™9Žbè9îî!|æ¿ò «?ø·ÌÄ »‹ÒzÞÙçàž’·¤p¨Ž*BÙFZ‘79Õ›”Ÿ˜q÷²‚t=ïnÎûãŸÈJ†¹¿9“éo}JÑu/pñ—áWB|~Ñho­'„²e¯³dÅFºœó(=ò+Ò“ rå5|~ÛYúÉÃlùÅÏÈßþ2«¾\Jóïeò}÷“™ Õó>æý[/¤BÓ‘¥(›>ú+›Š+}ÿT&^*>/TΚÂ;WMæÛWþÃ1ãGtN$*Yüü#ì2²9áþ÷9ñ‚‘(‘«ŸºƒŸ~IµöMŠícúˆ‰œýØ«tÏÇlØÄœµuôøñ_¸è›HË€è¶M¼|î KJÐLbI^ã9ë…Oé38@¤¢†YwL`ÁçϰfÙ¯è¯Êèt=ëvÎùãÃäJTÍx7®¹’ò¥kÑ®‡L‚¹ïL<²zö£©¼´;–WÍö”»åÉY¿g@°„Ç¢sá4ÊlapÎcÛÈ‹ßháТYü’m æ¡&f·  xaýçSøú¯¿ÅеyX!PnQáôš9ó+œ·# g’«Ûk!EI9ùrRVN‚Y—&™ôw ©)Õ!¹?@aÄ™§ãÑA3=$û“%ŒU[– éiuùå¤Jj%³;&L5[·Q6ûP 0ù§¤¥@¨2GžN¯C‘"F¨–ŠEo€T€'Ecó§3Xùþ *ê£$çAô›…44‚Ô‘²ÀlªbÇö’úað)#¡"¦—¾\A~¾Ša—¨5ML Ç¥¢GŸ|ë{HîËäÖ0éò ìøâ3æ=ó³Ÿ}œšŠJ¯w÷—j£N÷¾µ ¥f1ô‡× ÒLuY5’d`˜iô>áRÒ3%BõÒãRóÓ1J·IÀï@’%²z+ÇÂOüp>á±í‹Î…SPØ giO[Xx°ò,m®.Ïœrçe}Î}â?=-|(†/è¬xü°qæçL»ç;¯âKZÏ7»3¯-0Ü¡PîüŠ#E\ìITØ‹Âä¬\’2ó²"”dèœ/7ÖlÓC·jZ¬ñ›K0 ½¾N~aÎîž2† )ÉùxÙ Ó²#Œ’]HFÏ<¤¨µ]]ö]”ŠRÒŒi˜a˜;Yøà ¬°“¡1дBÒ»)è{8^’zc zc=œÁ|ì^_ö$ãIJ%ìHn¿ÏkN(ë ¬øÇÍ,zo*MBjÈ•hD%u÷tœ‰¤@R÷l¤¨u’K&¨j/ Jfr>þü\$ûì—@R”„Ž%’ÈìÕ  –°ˆ %&&A;„°è\8“¸y¶!§bwvw_Ol©>¬/ÝrÁ‡¿º´ÇY½F÷Q£­°¨#ÅÄ|'ìj9¦OãÓß^M°¶r=ðV^…;üÉí±p'mÑyƹkÿ;{dyRÒñ¦3 iU—\«"ÉPJSS RN è ›ÐT_F¼^YAo¨£¹¢³(4PŒ(5;›Ñuë/Î0B Ïù/¾Bº_Æ0 $IÆV1“ÈI‡¦=d>K¾d_ šÞŒn—’•A×m Bv[»Ø4¬mI¨žý8_üã5Ò‡LâÔ[î¦ëÀ¡¤&ùïUãiŒØ*%Lš›"Ö7J¬Âl´+ßܽž¤HêÌ—$HÉ)«Ü¬—ö=,µ± 1ãÐùˆ e'p;à‚X³È¶X¼[·uã–÷o:›U½cÕUÒRЊLÓdñ«Ïòñ¯.¦¹º|-0¨Ã:§‚´žg!Ú†CÅ«u$y+lœ†˜{¶×¤ûS3P} Þq{Ç&©^’{E¢„%ïŠá_2ÛX÷ñ‹j? ûö'oTm¦xÖ»D%ð§CpÓ"J6¯ÁTUdoiýÆ‚¹Œ²²&ò†ÑeDOR•r¾¼ëb>}ñÝ=6Š4 PÓ²IÎI§a׬]¼ O2¨¬™òAd5þåO–¡yÛf ÓÏП<ÌÈ Ç‘Û+‹¦5ŸP»i+²×‹ )• h^ðoª«ÁŸ„jØøù߈’LN<Œ˜Xélg½$C 3¬rÈÚzÙœ"Yx.A„YÙùp g8”Lk¼»‚e´´ÐÖ…]¼×\]>qê—¯X»Œ\s;I9YZâ6m\ìþõ;JùæÙ?³ü­çt`)0h ½¨°—޼Gj~…M¼( HÉYy‡j\{Ç4@×ÛˆÓ´æ4œÏºŽaHèºLÏΡgÁ³lyé&>1Kè;¬'Û?y‚e «ÈûÑt+ôÂäßÐõÍé¬}þFÔhÝŠÒÙøÑ£ìØÑŒÚ]%™~“oå›.aî=“Ñ‹ï$3)ʺ=¦­Õuåqü4­ ܺ[•™ y29Ø2ïOÌüõ%/º³|ß¾þ2Vn…išÖÉi‡s™Úm8ðkÞþžÆ3 v3kÞx˜ÚHÚþ&k¾¸>I ŠWþ—On»‰A“FP·à?,üd 'ÜFŸÁÙh_j페˜†Ža‰ûƒ0Á—”‚¬zC‹fÒ^'r$—@ 8„aÑ9±Å…ŽõçîÎùî 42VÕ)F4Z1ÿ…¿ŒÙ4ë£äã®ÿ5ýO=FŠUƒ^ˆŒ#I¶=¬ªgíÔ×Xð¯Ç¨/ÝÒÌÁªeo{&:v~…[XØTÖŽ:Hqwòk2àdå&æïÍ)9“´¢"¼u÷·çMÍ#½ I¾X­TS%9§;iAYÒP ãŒ'ß⋇o`ýË÷²Aõa˜ºÿä)&ÿìj<&w4ç<ù&ŸüávÖ½~« ‰Ìác{í(6­ #I:iǜ˽Èì?ü†oÿz ²)#gä2ê÷opü¹§X!VI©¤wHrrz»³Ëˆ@— ÿÈeÙCùü±ß³ä…ß#{û0úæûÙòþßÐ ŠP%™d‘š™Œd‚…ÌodÂu«YðÎû|µd*²/…AW=Ã0móÞýš…Ÿ~CÑy:¤NB«œÆ¬?¼ˆìK¦ëè[™ô§{HñB] ´¢"|ÉžÝâÂT¼$ôDIëF¢U¶1Mð’ð%§ÒR_“IÛó×}^ƒèg!bˆ‡Î‰ÓP±…ƒOáÇr]°fD“cKŠã6[¯p<ЧÛ1cÕg^JѨñdõ€7%v!—ŠÃ›X×áP}ˆšÍ«)™?‹µS^§bÝò°XˆFg'i7Ç–&Çýfâ{.ây-ŽìߨÕÚ*ÿlÿ.íßdàÁ ¿~¬hÌOoC ª¡îdŒP ¥%DÂ<é…¤wÍE2['-$ŒæFêËJ1ådR ‹ðúÚv˜WTКë©/+ÃÀORnIé>L}ï¡c² •_}ÄΙÞãÏ É«#¼DWÆËWþï?àš;Ί_ÈBY‚æ]ÛµDðg”™Œdê«ëPÒ²07¿ÇëWüÏEsÅ­¨+݉äK'¥K>ªLB&ãï+Š*6¬ç«O¢©²ìZóªšhþhO$ÉI@à@x,:'Ξ`0v¥(ÛkážarwJ5mXáQE¥K¾Rºä›^ÉÙyIÉy]¥ì>ƒIÍ+Ä›šŽÇŸt°ö+8r®Ñ–f õÔ—m£¶d=»JP}M3° Xì¤5oÇÎ× b §Ç™_açU´­ՉͬïÅž¼^@Q½¾Ãî´34À £Ï k¯ K08wÓÔ@ò§’ÕgõØh+*t $:Y}Ò[×ÑØ'd/”½ýÓfÌfèMO1öÂó0ë‹Yôð Ô6˜pÂ0ëL‡iU²JêRD²d}®©ƒ‰B ;YµN|L FNI!³w̘pêÌ¢,Ѧ( ’ª‚%Š;Ê1A)aÑy‰W!*žã|ìÌϰßc›R ³¹º¢¨¹º"¯bíÒ ,ÃÇ©ŠÇáxA9ÌL¼=bç焱²Ëí@#­´C´ ·°Ò6aÛž½t–˜…#ë˜Æ#^XÄ~[ªç0m.c¶ l8è!èûl¬½–UÏÜ̪gn¶^Póè}Ë }L=–¬…˜ ˆ÷œ :Z$YF2AÿcLd$EEV=ж*”3ôÉy+€ûšç¼¤ÅñÞgçh´Î2G°< VȆ^eçh89\“÷g#ØtÝw~ï!Z§³‹»³OEеØÞ g%(g¥²#­Ë¶g±x±éŠ$ËŠâ9ü<‰€i€¯÷XÎyne«–¬mBò$‘R0ˆ.C{[aYßñ¸øŠNåÜWçBî`ôÈþ{" +*Š¢‚u ØÓ„•sâJ áaѹi-”ÞÖsu«³fwí„AÕftïc3 öòô0Aî}µÎŠ¡G1¢Q°~ßñ&–í¬ €‡î˧ýÇ/·ÂiHú° E[Txh+,ö%¼JÐ9pž  [\8;k;;l‡hÛ³"^ß ¢ÚÿVœM@×# VJÙhfëô)T•Ö‘âEeÒ„dI¶DÎnOC´‘í_ÎEé9‚¼^]:eiYµö #ñ„›˜šŽ©EÁúmïIPˆë@ ؇¶±»LÅ}Í*å4$mÒKkNE¶t£´†?9…½¸zÅ ƒ–ÄÇ´íÎð8§×Â-0ìÇÎ0:g(á©Øw$bÇ5ÔP—P¿"YÒ©Þ°ÝÛ›S}‡¡c£é±Ùu$«|¬ÕE¦U™­U“Àz]–­ç$ûÄÙËÂõ\¼¼Å 5_=ÆŒg×pú€ÓÈï•‚Hz$Íú|ûJ§‚”$¹>Çñùñ”Øþ˜±Ù9V*7¶¿²ÛžcÛåXìe=I Qúñý,^ØB—ÑIuåiVS¡‡[ˆ¶Mnd ‘Ââð"žç"^\½F[#2B[AáîØÝ‘·"Ì!Á^p‹ §ÈÔiïÉr Û³¥¹§—Bx*þ7db9+ÁªòVô#™&-»J†B¨™d棨-;w•¼¤d¨”¯\‰žÚ‹Â=ðÈP»n)U[·5Rº Ð@|*h:èÔÖ„H)($¸muå•xR É2U«£bÝ*‚M!|¹Eäöí*·áš*ê+šAÒhÞYL}u&I²Êjy<„Ê·S±~-ZT%µÛ²ûvEŠ…ÉÐj©\·Š`s_Fi½“šæ‰ÛC2 êW/¦¡1H §>¥‚-s¾%kôdºíŠÙPͶo¦±mÅZt¼Y=è5a2]úvÁŒ…iI*¬fÛÜil[Þº^ÏñgQЯÂQê·¬¤q«õ¥k¡bªVm#·Gw%1þR%ÂÁf¢-A«ub L $f<ø0A%€GÕˆ†¢ô½ò1N¹ùf²²Tjg¼ÀKw?KNdë´·1L€ýϹ oã!„Åá‰Ósá®Ðã±ED”ø‚ÂÝeÕ¦£>a\ZÜÉÁñîÛÝçC<ï…-,œ¯ÅóRˆï½c::ö2Ði®'ܬ¡zÕ=†èpL@ÍfÔ}Ÿºç&¶6§0áî'(Õ“ï(4m_ÇêÊtFÞú½'œGÅôç™úÐä ›Èù¿ù+ÙÉ ,ýË­,zåv¤œnœÿË‹ÕróN¶Lÿ˜1÷¼FÏ®2ó{#>|r†rò£SÉõlçË?ßΊ×ÿÆÉ—Ñ-OÙ2d ý¯yŠ–ÊŸ0çó­{Çß~êŒð.dJÙÜ™äMü5ÿå"k>âó‡défðùï‘,á“{.gÍüŽþé‹ ™8„ê9/3óÉçùè:?—OýiIr[‰RŠ †AcÙ"šPHÊîIÀ§³úé[˜?s HÙc.¤O— Ÿ¼A]e1³î:‡´7—2¸o:+þö3æÏ°×û} üløäuê*·ðÅg‘öâÇ 8ë v½ð¡@^_úž~%ÉÉê÷+»1 h®ÚV³Lç5$Ad@ HT„°8¼qz.ÜáP¶¡h‹§§B¢m5(h%f¯:/no–3$Ê]%Ê~ììÒOP$ˆ9Ô)p÷ø¨ÖU®¯Á“—wȤ))䌚H~V;• zŒGvŠÆM_ãþ1“O@ ùò™'ÐÞœü§74$Ý„“}‰ºó†²ñÍ÷©¹ò" tCfðÍÿdÜO.Á+AóÆé?öoFüúߌ¹p$²åïý‹9kuôˆKâ°0€´>Ã)”_Ö?j"…2 ——ahaü½®á¼$;ÔÑC(žþkvî$„й³qþz_þ:§ßûczƒÙ¸iÿü7;ŠÿDæðB´v=(dˆQ’sö³ÇsÞÙxš–ñÖ=Ÿuü \òÌsd¤ÃÀºñú ×ogþ´Åô¿4›%Ï óøë¹ä™çë=Œ^WÂü/·rÍmOаð5æÍm ³Ï•œzÿÿá óèzLv”Ô`M,´yÙu?AF-!,oÜøNãÐ鉰=ñ<î¤m!.:/n£Ú‹·Ã-$ Ú‹AÇìÉè’€]Áª]fKu…”Ú%¯}šC€¡G0cÎz ÝÄÐ#x²úÑý¨± A´q•ó·áý[º÷Î&±ò Ô¬ôw›Þ-!:¦T@×>#‘%Ð4“R‰ƒŠ0B`x£Èf,a:Î¿Š©c5j3LŒp¤Õ›5É›øRS ÓŸD®?3ÆhÖ¨©ÛB¨]úw¦ýj6º®#Ë&Õ«&»¶V0ô¨Âyjß1ÿ“+È@Óòrju Pèrú d¤B$]ÆÝ@QÚÓl©i†+i8­õ†åîrÚ ¤Ûë¿¢´§bë­ dœ‚,™öŽh=Mª¶¬Ø…U„€û€G¶Q(aƒö­ìx¯W…;*ARLß·Àp†88Ńû¾û=‚}#Þ±³»ZêjŒ`m¥"Ù‰ Šdš˜º¨zˆP#è…Ù(mþddÅ”°¦àSÒPsR‘ôØŽÇâ}ôö®‚ÿ Ó€´Œ€•øÛ” H’„$9fF<wíÀÐMLt¼ùãPä'» kÒ89é¼~«w†jB2¬è5ͯ`kÉë%9E‚"-h¡ v”›áWíõ<^’Sdkþ?Ä:8‰‹aÔX¢+¯£p>@ hƒG΋}9v{!œ¡O¸^w’ØWFA<ân¡¯ŠCâ»Ð‘gÈéª1 ½©¡l[ú!ͯø‘}édô„]k—Ó¢ƒ×Û¹è.vÍZ‚䟈Ç[Ù0­%VÂõ;#ÉHN#Y°}¿íºVHUŸ?ÉÄ ‡‚â–­ËØ¾v;ùý ÷Ú‘[6-OŒ7§*A'úÅýðÈúî| IñâM)ú=Ï~B’¡¹ª’ •cQ… µ§0Hñß v#„Å‘‹}Iu ˆxÏ»¢¢óoæ±£ç;zM°o¸_<¡vV¬]š®kWäá}7$”@.¹£‡²òí×Xôö%œ|õx%ØøÖßXµ³‰ÂËÇ“‘ û)Ù0u …h£ÕRao[5PèÒoiXÿâS ÿ,Ý <4mYÉÌ_ŸÇêÕ.ž2žô´´½&L›øòG2|P¾]¾‘-_ÜÇô¿´Ð«¿U{„æPÈeÀÄ1$åd0|pæ-³Öûü/!z ð±êéGcëå0ð´Q4S±âŸËæ³øY žp¯tȸe*Ö.# ¶µ´^Ä$ƒ@ Ø+BX:2E¿Š#w"¦`ÿ/¤Ìy? l)]6wÑ‘d÷;š×?Ê„k.Å'®ƒ?}Žk¿Þ€ëaµ?¶ÖC&½ßHS'µûò‡Œ¡÷ø ü‡ø’,Y'í¢Wž¤¦xÝ`–W-„%‚#±Åîqã@ f¢à!Ñ*&¼@ ¶$)@ZìþIQuöãï$úáyè‘C5ÜV”(hA0M“¬ÇÑÚý² ² Ñæ(:àMö ëì.á*©àõj}Nö€ÇÑ e°K±í«V•×ø3ö2¨~vGûkQ𤀶îCÇÛQ|`†MtÍ@ö)¨ªUEª#T?(ª%ž¢-m_“TPˆ4[ÞÉ£Zûn›¾/ëI2¨^kœ’Z¸ƒ}?ˆÈ 4UTòÎ5'S¹qÕGÀ›@P»m‚XB#„%2ìòÔ@ B¡à ¯Œ/À.S×*·};3yÈÙç%„…¦·8,EÉõØ¡!ìóX%æ¢m{1˜„]áFFÂŽêWæ¶ßú&Kä8À£MmWéh;zت%{0ö,*´PûÆ Îý‰j {c—N3þööe=Ó€hhÏc9ØÈ ÔnÛ@ÍÖ&°ëh;CŸÜç®@ ´A”‚G¼^ î&„ @qÙª+‘;[€ªiÉæÁˆùŽÛ7í1î§ñÙû»/‰ßûós4¦;ÏA„+€RZ›cºÃžÜ‚C !,à@ã4ĜƘ3„dMMñ:³z˺Î',‡ šų§‚å­h¦õUÍ(JüñímüûDÌSÒ¡gÄùºÓ£¢B¤t>Óo™Ä[×ÃêoÖ"û€h=þù(‹^xˆ­[*•½lÿ#)P¶|•ëW˜ÀbÚž›y,@ˆ @àB @ 8°8· «Ñ˜íµ¨–5•ŠõëNhV“'à É‘­çv{N$«¿ƒ3|e¨±ñiQ•£ï{ƒ ‘Õ*7«ú¬×UŸµ )Ö‡¢ÍgĶ©x¬Eõ[ë{ñ lÕ¡âÏXÿÙÔ‡e<~@²>HV½¨>ë}²bm§ÍÅMŠE±Æ`÷ªèÉz]Q­>ŠFDC×Ùý96Š×:.Z(Œ± Õzü±±¨ ‡+¨\ò-å+¾!ØÂ$ UQŸ7€¢€£EC¥Gaù;/ G#-´=7ãy+Dc<@—u Áa…3lÄí±° 8XÔ\µkÜúÏÿ“™ÛÿÞ>(Y†š5_S²x=Î¼š¬Œ€e9Ö—°pÊT²9îƒû"G(þì¶./&ŠâÍèJï³~BQÿ|0 T¹Æ°‡ìCš+XóÑÇHEcI©]FÉŠ„u™´#xÆ$'[µ ¶O{‹­«Ö¡I>rLfZ%5Õ½&L&ào-Ó*IP>ok”aŠ?ü;õjrTT™HõF¾ð2Õ[w"ù3É;î,´utePô [§¾LÉÊ¢†Lr^OzŸv ¹…éî¦èu˜ÿö[覇nÇŽ¥æ›Ù^²%)—n'žÁÐÓÏD‘,DÍÊÙ¬úèu*·”‚7•¤ü^äsÃNŸ€¼m‹_—¦€(Ûf¾Àò¤ké?6{÷‡ÕÏaáS/±eÙ¤”òŽ™ÄQç^LR€ƒ’È/«P¶| ›gO1€ùXð4¬ó²#q!…@ ˆ‹@p`q–›u‡@E]·5À·Ëß~îŒ\OJ—.˜°cžª@Ù7/3ý‘ræ1ç“›@"å‹™þÇ_0è¶WéyTOVÜw=Óß~9%›ô.YT­ßÈâw§rÖ£o2tRW6>s'ËjR¹lü$üÁ,xò—”5¦ãÖaxÁZ4JJ_æÜ[¯Ä+,î&>â94OŸOF ?„Wn$œ~4—Ž8ƒÝT¢1£_Á`çôgX¹d`°ñÝÑ’G1qŒ5R¼ߟM°¡²ŸP}¼úw‚›Íèqý0š«™wÿµ|óß1éx=ÐÒPÏ‚>bÒ£/3¸OÎîÏË»¢×¬aοÇ$ZZ‚»__ýþsTì|‡So<¦…¯ðî/AMecÛûêÓ”•Má„þ‹˜ýÎk1+Ü`ëg/ÐRCïq?y=t6üýF6h­ býÇÿ¢&g\wñ†‹…_-~í)´PK°ë•sX5:º&á $ãIJ¶ aYÅã÷C4ĈÛßàçój¸îÍÿ››gÌ¡% +ßà³§ŸÃ?à\.ù`37ÏÝÆ™7_‚¢ƒêKÁm³j¦Ì°;>dòãA)ि/åGwNB kj´¾œÿú*n^XÁ÷ÞŽ7¸‹µ³# ìó0ï¿’=ñf~2«‚[ç—sî/nB[ý s^}“–xÂMöàIò Šä¥ï÷pâ —”Scéc±«2Èæþm‰ ÿŽýý¹ð™×(ìêš©þêK¼G_Ĥ[o&àðÑÿÂß1áŽ+PµHìƒLTÇüßóœyïý¤f&&«§Í¦©ùÀç\(*”.žÏæ/>Ë[QG«pŠ gž…5p!.A„°‚‹STÄóXDVe( Ø Ì[õÞKT¬Yƒâ=$cÞiš´”oeÍ3©)-'gÂ-\ýö|N<÷ ̨æ².Mô¨NÖð«ó“óð©5â|†Ÿ<¶n¢%•³ßD7suÇÃôR€äËbÄõR8¬7Z4w’"HWAõà ¤XÇÅ4ÐL?î}”ÇõÉCï“/!'/ ­d á–(%³Ÿ B/Fÿì.Ò}‚MQz]qý¨T}ø5 F|Þ™”ËŸäÂ?ÿ‘ ÷¼Éé×\‚¥õ²cc)ÝÏ€sy…Sîzá# ¾ˆµ1E2ñå1ä´ÓÈHðÓeÔùô;ÙÐw‡yåú{&þß {ý=uì1Ö¾†õƒb²ëÌÿǃ„ê¶‹hõV„iåÃBT‚¸ˆP(@ 8°˜Xµ‚ây,œâÂé¹ø6XSyÌÂ=šwú/’A˜††’Õƒç^CÃË/ðõ-§2ÇŸL×1çÒgÂ9 N¯…¨¦¯úð•K{wª<ìG?F‹?‘ÿýf€„$µ*ÙŽÁ2ut<üàOÒwâ•”­YÌŽYSØ4ï}¾úâu¬~–›¼±l"!$swþt,[²^UA ø$ùw×Hèÿ›Åjšà÷!§yAg±š·Rì³Ì(H#8áª[xÀ0 ¼)>0ÓÈHóî1IZÓL L­ d…O=_þåW,üdJR:ý/º“cGSöñý,š±bw@BöxbÂÃÄl¯n² uÛ¶3ç©{ѵè`-Ö7Ž-¶¸p‡B9½B\‚vˆP(@ 8x8Å…[TØÂÂCYiúò¯ÿö;jJ¶!ˆò³ødÁÚ«ƒ¶ê¾ù U/á’|ñÇ_²­¡G]ö3&ý}*×~<—¾*´,_NØ™ùìĈgwš˜f€ìaôšbv¬]^«¤«¾k-›·cì±Î®²ŒìQ[× ôø6®${P3 € 9ã.aäUWsÌ×0ò¢ ‰n^JYyUíè2(eÝÔWiˆ€­cÅ;/˜HJééÕÛvÐëÊG8ÿ‘‡vÒXšvÇÞï‰ØÜmKª¯}á¸Çé"Yž”yÏ?HMñºZ`:ÐB{QáÎü @ ˆ‹@ppˆWnÖí­pw!`zýöÍU3¸­9Œ´Ÿÿ±MR³»¢`°ü…»Ø±¹”Ÿ=Í´§³+{ÂÛXýæ³|ñÀÏØ´¼”Pm5µ›WÓ ’•Ž7ªÃOÄÔ gÌåø}õÌûóM¬œ¹ˆª_2ã7?d{IŠªÆ K²Çë­šÊ% ¨« #í1»ÙŸ‡^§Ý„ln`Öï®fëêí4nÛÌÂß^δgfÇÎòŽû…HÖ²fÊý¼zþþuþXæN]„ (#.£kŸäØ@˦¼ÆÜgã&±®¸ €¨Q†®êõ`9ƒ‚,}ö*¦<þš'þ‡šíîìT/,{ó9Vüçy˜•ÓcçUt$.D~…@ Ø'„°‚mˆ¹ó,: ‡ ÅîWŸlšùAèëgþ°_Å……¬Qã4ªU߼ο'uç•nCï3–TôP¤§qìõ×ÂÚxsòP^þá`þ}ÝeÔ¦Žàô."Ùz$„±s# ´hÝ¥LÝj0®ëiG]Åä»Gº±„¯Å?Î;‰u%ÙtÉÍCQAõ)íÄ…©Aö1g“ª7°à¡³øð¯Ó‘|Dôd2Ð#-h]‡®§ÞÀ W_Jó¼·yõÌ"^˜Ð—éï½OÎÉ7rò×âëàxš€WRÈè݆uß²sÍt’ûÆÿïv2»öfèÄ Í¥³™ýàíl-“ß»?Õ¿bç† î£é>z Ó°u¥3çcH*Z¸Эð' $¤ÝÇÐD‹ßÁü{¢ú ä›ÙÌyê^ ]Ÿ,Ã:ïBìYT¸»m a!ârRÄ@€õ+aMèx°rÜ|±%$)@jlI‹Ý€÷Ì w>.ºê& gõÿçA©©(¡tÙ2ÂÍAÔ¬" ItëbŒìdvÉA5Bìš?ƒÅÅDÂ&¾Œ.ä=™.sÁ€ÆõËhÖUrEÕš©Ø°)£¹Ý21 +Ý¡¹t µå7b0ªÞHKC½iµååøÈПٷɖºl.}{&™¾ö â$=LõÒo¨kŠÒg4Ù-”m)'³ÏpRRT+K>ÚBåúÞîä (@Õ#ìZ8ƒ²­Û1LÉùÝ)=žÔTÝÉ%).žÊsçŸK8$3ê¡ é¢r[Š?ƒ¼‘'“]‚…( fP¾s¦7•Ü¡'ž¦Sµy &Òû %=ËG¤z;W¯B×M|Ù½)Ô‹Ú‹hÑ RûMzfHÜ´ššÚZH+¤KŸÞ(ûQD*^صb9þߨݺið.V.O3Ð4Æn›cKVÁёȂ6a![X¨±Åƒ%,üXÂ"9¶¤a‰ û6˜¤¨žãO¹ûiéèË~jU[ÚO™%ÅêÂmcê€`èÖ¨e•6æ¤$±»Â‘¬¶}¬x@2@sôˆKÕ~ù8/^}ozs︈€ »¾z‡w~q%Ê;¹â¥ûñÆO±qHÄ*.™V/#Úv]Åcu·hp_’¬}Œ—´ÝFX´ÀèG—0éGÃÐbûfÚÇÄ9žX^º›Ë·óàíqI²ãócã’½±ýÐZÇ!©±ïÁ àù>(^¨X³†oýÕÅkKÿeX⡉VaÑD«¨hÁ¶÷ÂÝËB Ú!ªB ÁÁÅ %ÓÚ$/B«àhÁ2í=±[˜©kQiæ_n9>ÜÔÀ¨k~…â‘1öƒjê »Å9ÍGÓ2’;Â==κ»?C†”APðëž¹Š§=€ÏcP·m=º'›Ó~v-~©ƒ|ì8ãÐãT§m÷ù{¼ÁFZ¬Òµ(z4þ>u¸]×±4öã4âŒÛÔÚ½õ{!I–È*™ûÓڭ·b…ØÙy<-´Šg8”»1žðT‚½r€ûº ÀäXÜå=,Jl¦®K[çMïÚT±SîzôXü©IûÍsqP0AMͧ×ig E2AMÉ"oøDÆÝóÝc¿ˆ¥ïŠ`ê„[4rޤ׸’•›¶ßBÏvÅàÿ}…i÷^Gã®Ò` ° K<iõN8=îr³Na!{D„B ÁÁŶ'ÂηðÇ;ß"+Ç"ű$Å–¡Àɹ†§žð‹ûè{ÊÙxü2Zxÿå^h$ÙJ&Ö#Ö4¸ê±<û3è;#ƒ/ɺ«…dLûˆâµ<•6òÍßþÀúÏþcZt ð5Pƒ%š\‹3¯Â.=k{-Dn…@ Øg„°‚ƒÛá¼´&r°„…Ðm/ös~ ;p‚,+½M¾LvîÕt;v<ž$+<ÊtÖðIdœW¡Dk¢;v’«&AÕúul˜ù‹_{šæŠÕÀB`9–pÅn‚¢‰ö^ Ûc!ÊÌ ‚ÿ !,ààã RhMäv‹ [X$»–¤ØzÉ@àXÕëÏí2l”ÒkÜô}2E}Q<dÕ‹$‹Ê⇦‰¡E1´(Áº*v.Çæ¯?aÇ¢Ù4U–5«¥@–Hh¡Õ3á^œž Û[a—Cù`ŸÂB >ö¯Óká®å ‹rVŒJv<ç,WÛ䀔‘œW ¥ævÁŸ™ƒêKBØ…‡†®®¯¦¹¦‚¦²íh‘p+Ôi+°«”¬-B´æT8ÅD3­^ g~…;q[x+Á>!„…@ ÜIÛ¶¸ðb Œ–hHr-É´ [€øbï÷X¹YŽ÷¸+îË¿mLvÅ*{[‚e|ŠëÆÁ'žQoÐ*êK– p6[l¡5Ü)^ÂvlQ`{¢,N…SPO…@ Øoa!‰…S`8Ÿs'ì:+BÙ »d­STÄ˱°±½"…ÀÏ€íÀÃ@]l[‚ÄÀå‡rŸÎsÃyë¬üäÎÏ‚ï„@8¸C¢ìÛxqõ¶‘h ‰­9*m{d¸·mq¡aõ½¸3öøn¬2µâÚX¸ÏhëÁrŸî[§˜Ð\ïáO`¿!.@x8«EAû¤n·!©8–xž ;¤É),L¬kÀ-XMðnÂêØì9p»%øŽtTÊ. ë ‹r‡8Å{Î-(„¨û!, 1qÎRK®[¶""ž p‹ w¾„üøöî;NªêlàøïÞ;ewg{/ÀÒ{GDEP¬€{¬Ñ˜Øbò&Æ5¶˜hŒF£1,±£Q;¨ ‚"}évÙ¾l™zËûÇa‡aLwvy¾ŸÏe–Ù™;gvîÌœçžó’Ú¹-B!„Bˆ(X¬ Û¹-B!„BˆÈ¼‹=Z1¨Û"„B!„耜ØeeëIíÜ!„B!Dõ'ìïÎiï†!„B.Šlbñë€\ßÞ Bqdr´wD§ D]ZìßíŒJç´ã²Úø9úÿ±×‹ÆOûß¶s[„B¡¤“'þWJ+—­ý{ûØŸE|k+€ˆþ¿s½ÙÆmÅáq10; øòwBÑN¤ƒ'þ­‘ÿ«Q—‘ë öÁã­ãŠ‘h-x4Ù?Ȉ0¤³{x\ü#¼Ý‚_!„B´ éè‰o#6€hkþ}ôÙk (²Ø¿SÙÚ1'Çaü9ØH…޽šs}xƒý_C‹ýƒŒØM|?~<<\‡B!Ú™tèÄÁÄŽP¨´‘KpyÀ``ÐÈòU‡3UQT“ÅvDE|‰¼>±¯bY–aêÁf (¾±Gøþ‘+3ê2²OyÍ¿›+G°ƒŠ_ÁömŽB!…h[ôHDt0Ù,ì€b,pp¶3!)7«÷@R ŠqyRHÎ+$§ï04§‹Îß*õè+Mï¦áò8°L0#ç¤E§ 0Ì”~³€Eÿ¸‡í ?ÿþÔ…o ,"ÁEt!.¸{„â6ìÒ²B!D\‘ÀBD´5Já eJK/à^wrêÔ±×ÞÆÐs~‚';=ˆt š BÍV¿ù ù¾ºêϰ«­§%''`D¦IÉÈÅÁåOS°“´ÿÕ¾ÍB!Z§ú&â©Ðh¥pвˆâHà‰ÌýÆŸrÏL†ûcî ©C#¢X¨NE#FQ4lm¶Á¾ìÅC´S5 ÌŽá a™ñw®ÀAz·.œ|Ç“x²òÆ¿Ç>¾bÙèUÝ4…ÀÝØkÔÇÏ A…BˆÄq蛈Nê`A…\¤¹ÜLºíït=j4¡@û54–e‚šÔ•cîŸÏˆ‰æP©|ÿNþóÀ3àNcäm¯2ú¸„!Bµ5ìúzO>i¹.J¿˜‹•1˜Þ'ŒÂ©©Xþ%»Ö­Á‰;£ˆœ#É*.D ¨]½ ¯®âHÉ&«g/*XzˆêÕËñë&®Ôl²ûõFó5±kñ|ölØŠ‡Ô>#):O²†i€oÏêvW¢¦æ“ž­²ûËOfEÿ1CPõ•+°kÅJB†JBv7ò‡Ž%§gª– :Õ+¾¤tm‰ÝÎô"rŒ «¸È`Åî‰kñ{ž@@þðLúýß™ó« Î6õÐàZ¦ÜEg‡9ùnà"ì`+ø5vÕ§æöl”Bñ¿ÀâÈ9#]R6:¨\?ôì+•~'O³ËÉÆKÕH.ìfŸÍw‚/»Àþ…â"¥Ë2ºbª°öÿ.æ­·–âNêOnW‹Ò’zœöWú׋÷\Î'¯¼E@oÙ¯š˜Êè[þÍÄ3ŽeåŸÎañ²rœ½NæügÞ¡Kƒ¦íóyãü“©7Uº_xÓs1Ÿýße,ý蓨ž±BÞñWrڽѥÀÅÊÿÜÅGþO~oÒ‹4v-YOáÏ2è˜î,yäzæ=ýÜ~9JJ1Ão…Égo-%]Í'/¿?jÄHu§0ê×Ï0骳;Ìé}3ý§žÍî¥?s,ý÷#¿À®µûØ‹übWåî¬Á…˜ ÜLÄNÒ¾ØÜžB!¾‹ø=Å)—ص*¢G+@*psnÿáy㯿ËîáÅi×Î4ÀÔÛ™1ba!,ÝîÈZI:˜&†U”–”àJÍ"!=•½KþÃû/ØAEÆã{åõtó`úXñòÔkÙô8õ{šyßEU±, _ù&v-Ù‚+%Wz2uŸ¾Á™Ïa‘JßóÿÀiw=HNa:Vã–ßõª qåk¼÷¼T¤žÀØ«n °83ÐÈÊWÿAu=¨q“>SÎÁéÉdÔÍP”¼§o å/½‡ª80ƒP4n"™9S]µ‹íkV1ê„bvÌû GÏséSÃÛKzò-œó—[ñ˜M¤ßuŸ¿ú»—,¤j_Çß ‘.ão`ÒmבQ”Gåk÷4G*žÜ\rŽ™ÌÔÜ–}ü š+ËÓPé3ùIéuóß(Loäkÿ6Êv¼ƒª¸:Ü |S‡”üÆ]w³o>w’eš±¿È‚yjø2Çihû_éL.Ã^›bp>ðãù !„XaÚZYÛ¾Ì~ÖcÂT¥çÄ)˜z›ûép´Ô|ÆÜx/]ŠC9á§ŒOìÂê·þÁ'×Ç[SACùnûÆŠ‚¥CbŸIô,îCuÕjÊß^HóŒÞlÛV(ô:w:Ëž'Xò4¯ž= ,oåôݵ4×6¢„3¹•´ÁŒ½õºôMÄT ¹8Ÿ$ ô],ÿÛµ¬y¡ˆô¢b²Žã¨kî¡ Œc¯f‚»ˆ’·þÁ'7L·³,ÜÎú¯øýÐCÐ÷¤iô:ñL׿¹o] ,ö²ÿ*Ü‘ôôŽ<%*»ÊÓUÀìÑ™7±×¤èˆÏI!„h“G¦è¤íÈ48Åœ:ø˜+æÒ:ÁhE —:Š´L'zT¼s/Ïÿê÷˜¦…â( hÌHRRÓ)]»Ú¾ƒ–’L¿§°xÉjõ³Ø8ßCÓžJTÏP:Õ±U³ßB–Ó‰ærciÝGÙWÃÔz“èÖh Ï%K(Ì#%#3º YãÂ)wV²ìý)_ô%͵»©¬ÝMeÉB6°’sgÏ&qÕ#ýÎøù…h˜4”n ©ºÓ™JVA f$­ÛÚ×EÖ0©X6¥}ÏƸÅ[¹š oßêy ú¾b÷æm¸^ðp§ŸÅ´gŸ ÷ˆÖ>~=;×”tè¬zºs,½Ž?C]÷ÞËgcO ªÁþLЬi¡r`2wªAK¤h•$»ÀÊïOnAUåÛÙþìC,Éw’›PË73ogÇújœOå’çßBmåêp(”~ñ?þ2$÷fôõ0lâòûbÕ¼€‚â°4‡ýK1Àj`Ç›ï²â•Ãí´â²·ým)ŠÂðóʆg 1 } ð>-£fÔv°\‹BìŽý'Ø íN©@ {ø1ÇÇb—ŒÝ lÞÞ¶´Ñ^!„¢Ó’ÀâÈT´6 Êœ_åœØSÆ€Lw®»ƒê_\OÅޥ̿íGûWM.àèË~IA–ÆÎ€€ß·¯ÂV…ÞÇ_Ȫ·? b÷f¾ùãt–?’„îõšw=‡ôÂwò1¨K–,ÃkçÑí\_Bź22M€ß‡Ùº²†E#ÆÑ}ü)Ž­óçœ ,À^¿!\hì¿wô³sç·cœTÍr @"öê×CaØ9yØkM$ À|à& ØT|/„BtXXYZ+/ 0Tu8 <ãB´D ÝßníûŸX&$öËàiõàN¡ k¦ ¦Ùc®bpó78“!Q ÷Ru(¾äÏLµ²Ø°x%†®Üíd†žÞMï¼×šc/fxBNÎÑÓ}å&öÖøqô?‹Â<'¦i?nÚ„Ë8ïµ,ü×ß©+¯ÄÐÉ… ¼ðW ?3YCOað47î¢a$&€a‚Ôá§3ã±·XöôcTÕ×b8=döͨ+MV² ó¢û8UOgÃWËÑuO×) ;£;›ç¼E³¥Oc1í2‚i}HÏIÚ©¹xfÓ£1xÚelÿ⃣MCï¬¤åØŒL‰ŠŒZDŒ~œþöT¾ÿ&°H ß'Èò±G#ú„·¾á6ì oeÀ×Àrìõ7¶qøGH„Bˆ¥“Ozì?ZáŒÚ"geÝÀõ™=û_séëËp$&vÈ ªœ €†ôpVÕN—½†‚ÞÕçV@sé·0Qp&Ù¥P.{­Ó!½å¶®DPT;( Æ^Šè~S5IESØ·° æ²÷k™òí¿.ˆê°7½I·OÉk\‰`Á w§áv(8íÜèvêØÏ]±ìý›%°À~îM•¼üã‰Ôn[?¸ûeòìÑöÓ쉽2õù@ZÔnLìiI_…ÿïÂ>¾ÝØÉÓ=Â÷í ô‰H¢å=€ýþ؆½`ßz` PŠ÷Q…]µJ!„!#GŽØ³‘ÜŠ<à¤~'Ÿ‹+9±Ã–˜5u4µr}­¥ÍZ`@QT@÷ÙWC­ß6èmû±-B(ªŠêtУ‚#H›Éð‘þ—ÃŽò-ö1²ìDç}íô·ÞÎ`+Ͻ#0uH-Ê¥Û1'P»mý (Ç>>CØE&pð  k+»Qÿ‹=ÒPŒQŒ@ÔµQ—ØÄöðe)vÓÃj!„">H`ÑùEçV(ì_ J9Ü ½úœxV§OÚnÍ÷¹ª¸õjY‡eˆ×ÕÏ¿fž~+_ûg_Ë4úaOm2°Ñs°óFb7?Ƕ`¯`½;¡º {´!zB!Äa Å‘!¶”#j—ÓwÅ}±:Ø*΢s°LÈí?œŒî}µ[×À^0o(p)pÚ·ÜÍ{À4$ïA!„h7j{7@V­•˜vëðüÁ£ñä¤t¨¹ù¢ó°—ÇCÏ SÁžÎÔ ˜Ž\ìåÛ­ÑÉB!Ú•ŒXt~­-ŒyÝûjNWQ·£Oè0•„TgxAƒÐÛdEùa¦#í÷8áÄmô–dôNÉG"t5¥ÿþ[_Ë4±“¸Øù½~ØÁC7ì܉ü˜½„¯+ýá.„BˆhXtn ‘Ñ 虘ž•Üuôñû*Å3M °é¹Ç©Ò³zÞ%$%:7áû X`¨*ÊaLDQLÓ^‡BUQ50j7ñéS’~Ì5 ›4³µÄòNÂÔ!«÷`’s ÷ì޽rµìÄ®ÐÔ„=¸‘‰]"6„½¾Ä(ì ¢X!„íF¦Bu^JÔ¥³iØ‹âõÉèÞ—¤ììøOVGˆ³fÑk/àõƒ¦ÄüþP}þCݦ•ß«Nðmþ”¹·ü˜e­Bsµq¿ïò¸€¦Y”Îû;ïÞúSöTP@S9æ<φ•»ìÿt2©Cz×bRò»*@ZF×\ؕҰKÃÖc—ƒ<ü8 ;¸XÕMB!D˜Wk£Ñ[пpı£”…=5HÓÐ4 …ðT(nP•ðu†§EQ´ðºØSnP£Ž|ÕŽû:UµÖœáß¹†õ¬}÷%v”6âö´ÜOsÃißGs·ò¸û±¥õÇæLR¨ßò5ëÞy¿éÄá´w :P5'ÎP5{‹ì3Ú¡Úï, ÜÉ.²{»Tl:û·Ñ¥’µ˜»›Ø%j¿•·…Bñ?’©P[l©ÙèŽZ:ЫpȘ{–TÅOéG³ØúÍ üþîìt=þwx?šj—ÎfÃÂå¼A\éùtw:]õèPÓ§, †êןŽäKÔÑT(­lñ>Î&„BQ$°èÜÚJÜÖ€‡;Á“ÖµGÇŒ+p*^–þîbæ¾ù&fR¹] ©Zÿ(‹žyŒI÷¾ÄèÓŽ!´})\{6k6îÀ™” †—P@gõ÷qáC¿E+[Ê{—ŸÂÆÕ¤`éAšªjH~åÎxæcV¼Á—³ÞĪ¿|œÏý™ôŸ8œ³nåí{ÿ‚·Ùœ@ ©žú;Sîy•§ !´k^~*ë¶U’ZT€é÷ÒTó ëçÜnjނGkI>W~6<ó¯Ó¤äÉ[p'÷èTœj€]³n`ûSh® o#¦å¤üÖ7˜zéhØñòÝÌùóí46;÷µÅñÌßy÷«œxÒHŒ\˜á²³ŠªfY¦™Í£lцB!âŒL…êœbÏìÆ–›UbON )éò´¯æ‚ª/²àí7qº˜ ælåÒwVqÑOáÜÍç·ßKM£µOßšåt»ê~º¨Žë?]ɰñ=¨zÿnÖ­ÛKýÒ³qG5Ã~ù WRÆõ ¶sÚWáß¾ˆÊ(š~—þõ^4èqõ,~úÒí˜%ñÑ_„¤ Ìxe#7.màÒÇ!CÙÈg·þŽz/Ô¯x‡uÛ*8êÎÿðÓù»ùÙÇ%{ÊhÊÝÆömuhQ“yô`"㟚Ëñ—Ï-™Ó^ÞÊñg ‚îÃðw×®ûºž+žô¤ëÞû ¿¾mó™wÇí„òŽcú뛸qY—?öI¾RVýí*k-±‡â”eAjQwœ‰'ÅÓ "Hp!„BÄ ,:·èÎXlòv·”¼"R3ÊJß+EA3 v,ý”f#™1¿ú#ÅÝR1BÐõäŸ2úœS 6|ÆÎÅ‹Y»x9‰½O`ÊO.Ó¨âÎÈÑ7ýƒ£.ºf6úr¦ÿýcÆM?³©žÆÚ:jªêÄ¡( )$¤%£:@MÌ )¶|9›Ú‹þ×üž^Ãò 6zÉ›pÃÏ<`ó\Ê·5Ympë²/ÙU² ?YŒ¾íM.þ÷§å$¡GÿÍ-Ð<¸<P\©iáÀÃB@2ú‚)¸*9#ÎbИ^˜Û¶âÓ¡bþ_©ÄÉÐ+ï¢Wÿ|‚ ^²N¼€£§ À¿a •ʱ:H`®Ä$Ò»õ»¬¬“–ã5¶B!Dœ‘©PWk#Ñ£]<9…¸S=/°@ÁðU—¢%¦xPŒ€ýC…œü¸ÌOðVì!àmÄÓ7›„40 {Æà)L:ËËkžøäÕû©\·‚Úª{Gîì}=W˲ÀÅ´°tð±ÐYû·i”Îê†eš(„hÚ½Œ$j÷ÔÒ{ô zŒ|‹m³ä¥Ù’Ô}½Ž›N÷)PåÆŠYZ±,ûqkß baiÉ$ŽÂv)Z4´´lð{QLðWT!?z[žó XŠjQ·}-˜ xjÂÃùb|¯4§›Œâ¾T®[Ñ»"T€ýƒ‰è ɳB!âˆSôÙÜÖJÍ:€Œ¤Œlœ hn‡~W– ¡¸_…$ÅÝa*€a„£ P£³ü>š÷6àÎMcîàÝ·ßÁ9èF_þ²»÷‚íï2ïñµpY&à¢÷ÅwÐ%׃¡[ X8=àt“Û?W~1g?5—Ýß|Aź%l{ëÖ>%³ž§ü‘™rboB‡Z;Äœ*äzP-0"WZÖ¾²PŠeÉ Ÿz5ùݳ0B†½’uR òbêm>B\±Õ¥‘’× ›– ­B!: ,:¯Öò,"“bR€„ÄŒœzº×Bu%àÊ-ÆðÏaKI)»b_¥ËÞGwf’3 /[Óh(­¢i/x²ír´›_ÊÙðÔ IDAT¸7ïŠãž|Ÿík¿Aíz<çüc=»j8aãó 4Y¨.Çþ]X§Õ .`‘\8‰Ñ—A¯©±ã½'ذjE'Î`׬?³¾4£r=§Ìàèîcç“·ñʟ梁d+ÖäÞ­?5T‡£%X²@ÑÛx•,p¦ç%¤ ;™£ÎŽ´ËÎn~éq¶nõ“?.±ãœÖ·ì*^I™¹`W-spàhEô&„Bˆ8"9Wt©Ùس½)€;)3§ÃMƒ², SsPþ 2SC,}èz–/Ø@sM _û#oÜü3–´G‚ŠwëXòø|ñܳÔU5¨.¥¾ªHÈLC‰éé[‘nr(Èž•KinÖ\¬b?&¦“~F†ÓÇW^Îê«ñUW²ù™û™ýÛŸ³bþ'àêP]pUX¤ÑRÁ,öø|nu g&„Bt~2bÑ9ÅžÑ=Ó›¢9]®ÄŒŽX„ü> +ˆ¡Cþø+˜øãÅ|ö⋼rÖ›$edá­«ÆÝgÜü3\šÆÐkž¤zÃ9,}ážzá´¤ÞsÏ_(,.fô3(»÷qÞ¿bïC×qЗÍeÃÓXY¼Š¡Ž%/'‘mÜÄ¿Ê*¹þõ{™|Ã/xÿ±Ç™sAð¤@s#®nã9íþ?‘æVp_øÅŸžÃª‡/gÃÌ;p‡öÐà ‘wÌL8mz`ÿçe© }FãRŸç‹›'à½ï#N<6=èCYÝÔ}˜þ4L<½§0éwññ·óÖECpc'%$uñw<²b7 ˜º«­:Ã3“L0ÂÉЪþn_.µÕò;Å^8›È€ƒ¢ÚUh*öÝ!ü\»z•iæÅØÿù¨{¿f0j‘½ðß%ÒË<ÈãÄ1glþt.³®žê· ýf ðM´XtÀg)„BtN2ªsj-Ñ5:×Â¥ªªª¹Ü$«·…{Þâ+K[&mVxjeŸ¸ÏÖns¨jK–a¦úÖZy.­}¦~àiúÖÚ×Y¨šMÓÝÐ]ìŸW!ÉÛB!D“äíÎ'6·¢5EQU‡+¡Ã¢óS‡CÁžÆmÇ`!„qD‹Î'jRp°u¤K&⑲ß?±Õ dÄB!„ˆSX9¢;e†e™¦ôK÷ìH¢¢‚mœ0C!,=d! $:À3B!ŽLXt>‡êxÙ…išz Ý´(Š78\‡¡®€æ¶÷¯¶Ç»NBA‚ FüÎS0õ¦¡[ØÉÙ{%äèB!âˆ[kgyÀ´LÓ4CVîrdRðïÞÂÎ…_²kå:B¡ï1¸PÀl®¦üë¯)ýz1u|ä@sCõçÏðêÅǰzånœq\¶ÁÐuLÓˆ ,"£P!„qI‹Î¯µ…ò‚¦®ocüO —ƒÕœv)ÖVÛ«ØåV#£ ±¥eQìR¯‘ûFö};§ÃdËœ?1ëêSxóÞ;Ùë-êÝ¡¨á68qPÔý÷á²·áëU¼Û>fÖÕ'óÚuç°v]Å!;ö‘¹Ê!Þ¡Šj´´õ:F·A¯.§rãšýñûº+`ü`a~ ŽãµåB!Ä/ŽÏ[ŠÃD|† új«ÙqmOŠ šeÒ°cþ ŽæN!¹¨—£e½EÕ´¨ÛXBscÐHîÒ—´¼d,=¼†„a`„t‡ Í­Ð\¶_sWZ©¹ÉX ªý}MàmƲÂ%];˜ÖVQ¿g(n’òº“œá²×¸0LŒ€=¼¡h‡j¯CÒ1M L5É¥ûñ6í' ë uŽNнü>?Šæ&!5ÕjYË":1|A‚þ O2N×ë{¾ zÈÄ‘”€æv¡¡¡ÆmTXh¨;¨ð!„BÑaH`qäQf,+ØÇ…â€`ùæÞûKÊ×®#à¢&¦à)êÃÑ×ÿ•þ£û*]ÊÂG®cý×[iÞÛ 7ž¼® ¼äÆœ7„(}çŸ|òÔL¬¬>äåúÙ¹èüAp¥dQ|Ö/9éÚóYôÛËXòÙGö>w|ÁÛ?̈ß<ÏØ1y¬î,|õEª*0I$)§€žÓnbüå¡T¯çƒ›®¢& £¤à„ûž¢8¥œwo½†ª²j9Ýé7þ$Ö½øgû‰éM¬¼÷Lê·ÝÇ©?>%zA ”P ß<~7%Ï%ÔQPIí9†£o¼Ÿ}sQC>Jžø9%5ýXÔÌò7g™$fveÐO`äI#° p˜>Vùù9¬X¿€Ä.}Qwo¤~K- ﾿ñ"SvfÍ.*V/AS–P– ž”žx+·Ò\¹‹ºÇn"càpÔæj|uµX>¾Š= Xì~îÞøÃ=X€æê†Ç³“êõ»©~àjLW S.?•^'ôcíƒÏbñ _¿z"¾Ôõ¬ýðCt4úÝt ùU,Þ³Í~n–I ´Œ ×{À©xÕ ›ÿ~#s{‘Ì'ÓmÒ@B[—±mÞ3¼½ÛÍU¯=AºjѰc5;g?î„TºŒ™BšQÁ–ÏçQõÇ_‘?ðºwSYúèͼÿרu0] £iÓg,újš'-~‡Â+Žûj+hY0’[!„Bˆ8§ç«Åwé€E:cVÔu‘Ÿ `¯·¶Ý%HU*¾|Æ*Ýf<È¥³¿ä´K/é@`çç”®_NÙ/±fÓvÀAŸKþÈE/Îç™/Ó½ ŒfV¾ô(»öX8œNT äpÐgú¹äåœqÛ¯íƒ?ègwIGßÿcÎ;w÷ œñÄû ëÑÌç/<ƒ$Œ¸‚ þó —¾¶ˆ!áeíOPQë`ðe`øð~lù×/ùàÉ¿£é#Ïá„KΡð¤sæ}wÛzW*#n{“Ó/>mÿå³Pð±{ñÒ†žÉY3?àÜ?=ÄÏ|ʘ³GÚ¼™`8×^Õœ@£o{ƒ fÎâGÏÍã¸3‡¢ïÞI}U¡²•|óü¿ðô˜È´-àGO¿Ê…Ï~İc{cÿ›¥ÀX `† ÊwÔb§­ÇÑ—âð‰]í\6Ùb7!„ØGF,:¯Cuº*šªvhhƕ≫.š¢Bõòw±Wú@ÆÞx#¹ÅR¯ýVÏÑø¼Ð­G.å³W2@+ÇèËI^W'jÏó¾þkvþéaŒeÔï¬!=œ¥m¦fÄ…7Õ- Ïä³)º÷Ï”Z jîœ|’s P=ɤuï‚Vó Íö(Fb²IÅò7©ÒÁPð¾¬ž¼ÁÝûûØyíyÔTÖà ‚–9œ±·þüYTgnO ––€bX˜d žÂ ±YðöR´§1`D!zÐìµ"L]Çlåï­jPþù“|úÄ“4VÖ ‡B¦…¢˜8\QßÝ–‰UÜŸDö~,@q ªb¬iÀD#sÜ T¿½Êœa§ Ÿ¤3®^ëý(òy©ßµ`ûXó³øßıg[û¿‡ÒÖ{´µÿ !:1 ,:…Ö;`±´roU9†zºþí;$E@ã £Œ&¯Ef®B°rË_ýТëÄÓqfଯGñ…ÀáDS`oõZ2ÎÄ"O[µ@ùV_k Š–Þ„©{èzÕCœpÚqX! EQ±t?& d%ƒ5 ÿIÉ»Ë÷íA_ö8ËæœÏØ3ï«`µoïªzÀô3Åzé§|òÀÔŠ{ó£ô3Œ¼lV=qóžõîû€³¢ƒµï§*LL6V`Mé!{Σ¯¦_@‰ß„}öîÚŠîóé@]øj“– 8BŒÿMô¨DìTµ•Û´v)DDkCk'b§2ÆÞWщH`ÑùÄ~¨GwÌ¢G,ªõ`ÀWWº%1oо•aYÖc°„@ÃB?:“¬ßÌ`Û+0ç‘§7“{ŸLAá\@p÷W|1óq¦\w!æ¶E|=ó9 ÀUÜœžù„Ö~»5VøOg}4UÕ’žž‹Û“ ð~½åÂËÈN¬cé“·°eUVÁ(&ßùIµ›˜÷—»¨ÓMÔªB÷Ö±ðáßR8ú5º$¶ìÝ2ñÖVàó j®}­¨à«*ÅWé%ÿ”ëãÅ8šÁ»k%{¾üÍu"ª›CäÃXX†ƒ´Þ]ð8-j>}ŠºK%3̽ l[ô®¸í!*T®_‰eõ´T…Š>žce 0­µÑ;ˆ 0Œð¦Îð¦q`P¯‡øáľ/uìÁÑÈP°û‹Í“jí$¢‘À¢sj-¹5v:”ØUVòuŸ~'Oû›wp–“®¦ëÓ¯RZVÏÆ—®¢üÓ[i,¯ aä%tÔ—œ¡Ñç홬ùz Û^¸‰gßþ%FÐ@ç. >óçä(”ê¦ýÄ£çY†`bY8 1! €àæyõìœve´âÛkmÊ“µi´tèò€À™(JÏ„”´Â”ü®xr ðdåáLJþ›.:EÁ …ðÖVá­)goy)úšF=Ø,žÖb—8wÐR•0`(ìªCÞÓBtprªó‰t"ØSÜ@ö™Çäð– $S“sò¯¸ô?«ðääÄ]R¯êÓÛHÝ®í„üZBéÝ{àrí¿ò¶bè4–nÅÛÔ Š“¤üî¤ä´¬¼­˜¦i€¢¢hQ ºn‹iÅîÜZ¡&¨Z¢§KEÕ T_KCuzÐÂY@j^Š ¦n¢&šhªj¿¥,ÃÄ2MLLTʼnâPP‹àÞLÔ„$œnçÏY_å.k÷âHÉ"5/§ š+*0ÕDÒRQ-ËRQ´–·¯bY˜† šf?(u»¶ XxòzâÉLÄ Xªw%†5'TmÜȬ+&Y »žÞš±×³h ÿì%¼ ö´ ¸i7ÑA…u©E]@p.pyRFvïnc§Ð}Ì t3‰´ÂîXŠ‹g‡‰ˆS–ešÙ½|!;¾ú˜_}BåúMÀØÆRZŠÈ´»Ø‘t ,„èàä{£sŠ,\ØADRxKÁ,’ašÓuó©z!qð´sÑýíÕ܃Pìu-"_GféŠFT9V¾S´o?Ñ_q‘v„¯ÿ_÷¿oßûþiå6ª}»èÇQTûöÿÕèR8XRÿMâø+Û™kÞþ³o>×o™æÝÀ&ì b/vPÑ ø°‹`x3À"ZlÎDl@¹îàöÄŒœaCξ‚Ágý˜ÜþQ5Ѓßí½#ŽlŠbŸÒœÐP^ÖOç°ìÅÇØ³fI ððOì ‘éQ‘#¶jaZ !FkïˆÃ"ºcáÀ~# ™®ðe [¦12)3;­Ï¤Ó0õ¶v×¾"ìƒvŒ­¨€â0}%Y‘ŽýáþÊkíqþ×Çüoƒ‘vb°òµ')[ùÕvà#ì "6ˆŒRDwD:À³ûADWwŠäPD¶H"mð+àþÇÖåô??ÏéáÉÎÁбßÿò×ß‘e‚—'‰ü!Ã0õbT‡3©jêãô€(°¨¤e*¶œà¢‘À¢sŠ>kXD6wø÷Ý€}OºW’»CtBEç¢(lnfþ_~¯¾æs`5v@Ù"AE;°ˆL’së¶Øäìè€Â¾.xÐåI¹ò„ß<è<ñÿþLja!FHF(ÄáaYö Íå¤Çøãé2r"U›Jz6Uìž„\lÅ>F£ËK!D§íÅw "‰qÑÃÌ‘¡çHçluõ¦5ÔïX/GƒhŠë–Q·c“l&¼¦-#Ñó±AΫG‹Í©ˆ *T øgrNÁô3ÿò£/»Õ寵º?!¾W–zºŽÃŒÇÞ¢çÄÓ‹±§DMÇ>V]´œ‹®\&„耤+ÙyÅ&ŵÖQ3€zÀW¶qÞ[ö“\s+ îön‰ø_¨¬}çE,ÓÜ ìâÀcô`•¡ŽdmU|Š*Š€Ç3{ô›8ýÑ·è}Ò)’G!~x–¿“œ_ÀY½Ì ³.˦b¿Çì?ºÙ$È¢ƒ‘©PSke&Q[ôt( ÈðÖUõ|Ö•h®+Å3Õ2¨\>­‹ÑT£Z”'ßBˆªAcy_œ÷ jr“g®aô '+¾€Ïß]Aòˆ³8ÿÉÿéÓÂŒ&Jž¸ˆÏgo&¥( Í×D]u½ýPj*ƒ~ü §üv žáí[ÿºÊÚ}ÏOÑÜdOýgß}+™jïÝr&ekw¢eõ Ñ»…½ÍvÃ2&3tr%¯½‚Ͼ'©}&pÖs(ê™BÃ7¯òþ=¿a[ÉŽ¨}ç0è’¿1å7çã$>{àŠ–i±âÕ'0 };ö‚xÑ#iÑ£Ñ‰Û h™{’ ¸µË¨ ™ã®ý=¦ñïc¢º¡ôõÛ˜7g#ã÷}fž T*„ÊñæM·Rtñí;m’}Ôt"ŠJífÞ»æ$¶o*gÀu¯rò53ìP»ÐCÐõ˜±ý“[Xð—[.Þù1öq=ÚþäÝ·r·"NuŽS¢5±S Z˳E]Ö 7|8‹ÒÅ Ñ\íÒæ} 4ºsÉ©N,³–MËK°,ðíYÇÖ/·c™ÝúN#¯Àƒ¡kèÁ¦aà°`íÌß°pög 9érüô™0‡bR¹êQÞxè5Ò†'57 Ë4(]_þz6•×a™FC#¥«6j® j~ –©ße Y`F÷Д,S§¡´ƒa ;ó < –ÙÀÚWÇî=:4meá=WÚAEZ__ú+z=Å P5ç1¶®ØâtbY¦i ù6£ôLQqwÀ_÷1‹_{…Ä“é2   ›°bîçXõÛùøŽëÙV²GJý¦_MNb0ªXýìuÌÿ`M»¿ŽmÑ\°óë/ØúÙ XŒ½Ê¶NK’vt.Plpq¤v,bG*¢G!]áß]áÉÎ;zÒo&!=¹]FôÊÅT|³”æF}ߊõmÞþS¸Mdª¢†¯ší«P·gçþe5xÑÚï۸Ͼ)ÿÅ€HdJã!§•F¦8ÅÞ.êzË a:FHE7öî÷šÆNÁ<Ø~•ƒ]×^ƒ=A8úÒ_ÒgòÙ)Àÿ´Ê‘ã;zåøÎ94%D'!EçÝ‹tÎbG+¢G-Všö–.žùf@o×ù¼f<ÃŽ¥°W7Êç~BSỌ̈ jR }~tŽPK™EQ Tldý¬7Èÿ¦?þç<õ.ÇŽÀÞwŸd¯³'½ºPõõjšªJñ6UÙ;q6ã]õ9MµÛÙÕBq'SpúɸŒ¶ZêÃ]8)ϽÍé¼ÍIWœj·ÅUE DHìq:}'ŸÏ ¿{Óï¼›q3ÎÆã°PC-§r-ÀQtç=µ‚óž~‰îöõiC/àì§?âGÿ‹ÜÍ õT1‡]ë«7#ýo¦=ø$—<ý*=RjØ3ç%|V¾Éy ¾žù!¿w° ûéGŽÅèà":°8RŠh­U{‹äU Î~ÁÏ(9£Ïj+ªŽCNÇÓœ`ù}4WVá­kBq`ì»MUø6jÔmÍîqF¢—HnÕÁr£4Ð1iž.r-’O;³ó IDAT³äÓçÃW׈ 81yXQíû^/º½ýš»íŽ»ÒÑ}t_0jtÐ t€Ps3¡€£nxŒ©÷½ÀÈI'£„ ÍÿÏÞyÇIQÞüýLÙÝÛë…ãîà€£÷^¤Ø°`욨I¬‰Æ£¨‰¿[4ÖØPcÐ(vD©Ò;Ò;\ƒãúm™™ç÷Çìr˲L"wȼ_¯aٽݙgg§|?Ï·Yuز’Í[¨)¯C å`YFx½ÁÃ'Ú¯ùHìƒÇ 0ê|ǼÀýàH šWãŒÛ#.%­p]hx‘â"|¼;¢ÂÁ¡‰ã„Býø‘؆™BìP¨püz¨æîZ0ýÚíßÎÇœÑXå)%XZ}ÎÅÆUÛϤpoõ_ÌÀÓy<íº¥b«>#¦¯ ¿‘Ä¡µ_2ùÊ ~÷‚¾5TÔxÈ9„ó7`O§hc.þ²C¤´Ž¿n!5‡ÖP¶1H5àJèGÛîm0ŽÊ‘Ùí,ZuLÆ4 µ]_à *`ánÞ…=ÃúÉo²õÃ_±ñÍrª÷ì¢.±2\“ÏFrª‚ÐsHÍ…]‡ #w0)Í@wåãÎÈ‚¢ Xªª±Í‹­oÝÏž' ¬µöºj«7àóƒ[¥I™äš Ö9…ó>7€Ù@- ÇbdYÙ°¸ˆÕÇâtãX¹aaánÊìÜ+£ßµwžüdíï‹ULù ß¾ý/êêü t¼].dÄoî¥e› ,4ÅdÇϱðŸïP[ïC”ÎCrÏÓä¶I:j. 6¼ÿ8˧N'sÀyœu×øhS5ØûÍKÌg2ƒþ0…6yéHÀ(^Êäû ÕrÁæ=~Õ¾.äÆmfåüy I\«> üÍÓtìÙ2fÞŠP!¸o-Ë^¹—­«÷bšB§Y÷Ë8óî;IÉð"#ìwÅEï<ÊÌ)_c¡Óù®0xX>R…µÏÝÅÊEKîtÆ<ú8{¾y‹Â‚jZ“@Vûh*~ú ó'þò’ý˜¦N\F69£ndä]·³ç¥ûY4g>¨nF<û­³tV=tßí®ÄÓª7c•t±‡9¿¿‰=;ÊHqçÞò3tåä‡ÎíµÈêÖžWÞÆ’¿ÿß5ØáPë°ïȉ…ÚˆÓó:ààÐäir“™ÿS"+èD–ôXhH5MfÀ¿nÁߦºø ¢Óû¥î$+Ìúj6}ó›öí4:žÞ-7„"Ï’ ±Loþ@Z N«gãV©}ÎÅ µÛYùÏ÷¨ziqͯÉÉÊ¡rÛ*V}8ïóHK3æ-Ì~Q×ó'v[FD·¢(ùŽÏn;“™/>Éž 1ôt²úôÇãmí«–}c—–y8äAZXá×â „"BÓw"ô!‰D'­ïY´ê&-:÷B• •¦SQFÕ¡r_ ^˜Rn¶`‘IÚá%ZXÀékLÄ ƒŠôVŒÐ\îsß<oFrÓL¬‰@ÕaϤ˜öÛ ”TyhwÎxZ·Í¦ø«'øøÖKØ¿Ç‡Ë ;Þˆ)ßË¡È{9¹r)œþ:ïÝ~-E%à ÛBè¨ŠÅÆwäÓG¡²*H§K~Ki( 5û7³gÙ<ªk}‡ ,˜u¥ì^6²Â½( tÓÖx7_O›Ž·M2Û´ bÑ¿øäÖkص»úèð.ªuˆE®gáäY¸²:ÒvøH½6Oùß¼õöQׄäž=©Ø°‚âõ‹Ù<ý[ Dõ¶,ý˜âuË©ðt#11žâï²wù<îEx üóøð·¿¢`ýZjhèÊn]Åw/ÿН_yÄütJÖ¯ è»…ìÝ~é+dÝœù¯_Îî… )+óaìßNÁ¬¯_…דˆîiQÆ2 ÿOï&=¿s3àl±|,¯E¸š988ÄÂñXœÄʳÏ"?PÌ.ݼ&oÁ ’Æ>þ²mÔ6 GZײF÷§xÚ* ^¿ÓU ½h7 LU,%XÀEöy3þ÷×¢ø *÷l¡®º©(¤§k¸ã{б³ÂêÂ*v~û-JB3ò!8/“mëÖ³m-.:ŸÑ]ʯ8ŠØÓÊ¢RùÝLvl(Añ$3øñ|Ñp‚ë&óîõ ñÅòìŸà~R`!í·k­4a*=†æcÖÔP¾{3Á€…–˜‚[ÿÁbü~‰êñâINn¼;²°ËL.zù÷Ú½µø»òS#…Ed(T´·âtäX¢"¼$?k5p”Úqô¸&Qú‰³dó_{ž@ËQ\2q »$a‚ä>ÿK>ûÛ$ÖÍšEVR>KÞ|5»ã^ŸA‡niH6OÜ̧¯¿Å¢çsÁ8û&d=›ßý_üåiÒz\ÆùÏ¿N‹–É1=B }&ª†µ (¡UWj:ïú€á7 F Xl|éWL{úV¼ø9­Ÿ½ Å×p@ Œ{)­(!{Øï¸òýÇIö‚_›Cii…Ý¿!Â# Mˆo?šîCRYºð5›>¥¢îFâv®¢r{!OÏs.&)уÐìd)Ew¡ú«YöÞkJ»q\ü§—Éɪ`É“·°ì‹…lûèoôxöZ¦yØ[îcïŠíøóL†G[[LÅŽíTº¶SY¤t¦U¿³Ð¤}ò5Ò‚øÌTß<Ïï¿fRN–Âá‘9ƒN"·ƒCÅñXüø9–Ç"2*,*ü¡× yë§½%·|9 ¥±šqKÀ›D›3ÆáÑLüťͻ #»]2VtX°´ðdä‘=8 P8÷–¿ÿßþ‹n=‡I× fòïÀg€+¹™Ã.²eàÒrhž“Eú°ìj§ŒïEËö}þ C\`뾌©Q³m ³Þ|òzÛf¶þCÅfš*©ÝºŸìcË&þ‘-3¿aý‡äŸWÁ{× eî??Bá ïlÅ«£[3ùÁÛñj#) U‡ Ÿ¾Ïw¾f3ý)*b…B…ÆéÚÃBÄXÂɬ.ìkøP¡ª½úþôT·Þ襢ÿB…ºý+),3Èùí’ðWƒaèt¸äzUƒ’=»©Ø¼šŠâZZ¹›6]ÒTCv—^CF’NåÒ¥øÐÑ= ë&=ÊÇOþÃìĨ§ß9¦¨8Q¬@O›.t¹h0²ü†BÛ o$+ÛMõž £ö±4A¤t㢉«÷Àu”Îû–Õo¼ËÒw^ ¬ØÂ¥ÆžÃq©äý9*P½w7âàöU¬‘hYÉØ5B) ¡b”—QYU @\’Nåæél™³C±ÅG ä‡êÒi?0ÿÆÙܶ 鳈Okp¤bÏ*ÊömÄ$·ìLf§tÌ&PQË2¡ÝðsÉîÖÏ \ƒ}Œ‡tXX;^ ‡&Œã±øñs¬yao…Nƒ°pE<.7ü¾–3ÿ|GŸ¤¹´ìݯQâ¶Í dKJúŸ).©TÒ‡_K¢‚~P˜»kÐW‰éô¾õvm»…ûÖ0o‡ץ¸ÚÐûú'IŽS×iÑešœ†ȶ瓒®nçvTB§|šµO=F ˈíú ,ì;ž F}-–i’ØnºÊ_ ;’eq’`xïSFÁÆUô<Ãg #h„Ö. ç¥fÈ #1Cï ÖÕßù' ¹ñ:¾~~"å Þæ“o7ó|ú^÷ÔàAê+ƒXøj«—Ë=Ùh(\¶”9¾i™«Õ4ˆŠp¿ ?GV*‹Õït$ºÃvd—íàÊ6Cƺòú¯Œé“†€`Å!$Éd·êt¸J‘´@ñf‘©@õQg@³¾ЂöÁ!LÐ³ð¤Æ´LûP íCOrc˜{X71yíF~Ï19Ç&%èq:.7‡…š¢' Å%b(òè9ŠQÍ–Wbù×_Q]]‡¬©ÂÒtPLjC4%´è{™Y£¨x;Eë¾À»é#, ³Ýù´ÊwaEΠHͪ{¨]=•Y«§ÚCP]¸âAxÐdÉg\ˆ˜þ µg³õk=‡^?ÿ5ë_ºŸâ ËPŒe$ö¼˜Ô°+Ÿ.iB|z ÝÇÿ‚âõ+Î’Rö–Ð .¢¯Ž×ÂÁ¡‰á‹Óƒèzà±B¡|Ø†Š‹£efMqa³Üœ;þ•ÏIn‘}Ògµ¤Jf_†\v7› PãSèsNÃo¤â¢ùàKè¢u'¥ëPTÒ\ÍÏeåkÏQRU‰i€–œE—k¦ËàNHþ¡§õG¿k7SU¤Ù¨sÐø6g2ôÂk)—‚æ½o Á 2† ’B§ù Kè¢t'³W/Ó6@¼í†Ðeܵ¨Þd’âUâòF3³dêGÔù|¨‰¹t¿ò&,¡ý¥èB%(h?槤Ó¼wg;7DK¢õ×bmdöì‡bjíÆ^MÊÞ22zuÀ´4ºÝòé9ƒXñå'ÔÕÕ!qߦýozˆì–^dAÞù¿Áêx€”Žƒp‰“O¤êppû.füþêØICTø¸ ‹Šð±_qº‹ 8² TXX(À@ÍåÜûêÛÑ/Ù´ê²Ïî½2cÜS“HÎÉ9é1Ü–¡Òñžÿ£K(ß9Xßó` ¯y”®×ÛÉAŸíåHè:Œ/ è5±,‰§¡ ÏèJ”f9ûÂ.o €šÞ3_|×.;ZßQw/io·ÃÕÒågG¾/¹÷ÅŒ|1H0êìíežw޾3h¡xìîáÊÈ!(Š=Ž@= »ÿ)µaèéô¸ã]z«öúz0”t†Ý÷×#Þg-.þŽû†Ï@¢¢{…-ž‚€žD÷;ž¥—Ú0Γ™/£y |çn>¿ÿjJ7­)¾Àî™í©ˆNÞŽ.5{:V‰.-«Ô%Y=¨yCF5‰P–#P%HÓY<œ¹,1¥B\n7ÜZuÓ?¦ïÅ£HO±›?{‘C&´mÛ…”üzâãaßô—)¿âL2R [æN¡¬Â Ë°!xd5frÎD«Ñ1ìÖkøøO¯óÕsÏð³ç'.*y[ ˆ÷¤£ )Ù¶›Þý›£X[ßÿ§]a-äYPU•º‚ýìÝRBïÍÁ„­ó>£t_-YÃ:ãŽ肚ò=ÔÝoxšaWt"XV|̶ž>.T7G'1H0´º{«–,çàâ…(-FÓq`Ç£®µÒ²ÐÓГR€rêVnº¾%Ù­ÖÿãA6,Ú†LjËG_'¿s_Ró³)ßZ@RJ2Íò:Ѽ«Îæù¦]^ÉuyZq ÝÓ(H žd/}®¹ƒ¯»é\àÀìcÞàÈó!rÂÌÁÁ¡ à‹Ó‹höXD&‚êQÏ÷Ÿ®øvüg÷\•zÁSï‘Ú:÷¤ÏŒõÇnªkúí%˰¡©vN Fô­Ç‚@íѯ…&ÿ-±¶+ ðG}Þòc‡Jè „¼%ÑC°þèõñűßg†×ï²OçèYÚãí»Õ ¶ngúC7°Í’2à û˜«Çõ¡%Ò[Ý»ât7"s+"+AuDˆ¾Ç^Ž+Ñ…ákÌ!‰*øö°æ¹ÛÙ››‚ Y®Ò2©|óœ9®ßL{‰Ï~§3è’QÔmâW?Ä•qùg&!7@þ˜á,™úÓhNÏ+ÇÜ:ùϽŒ–Û‡¾—ôêþÆ.ë÷ôA‡Ë¥Ç'³X3óO,}o,#®z¤ÇQ@bv <*¬{þzâë„}3XòÁ”ÐÀC‡šæÁ¬ÞÄœ ?¥îÊkÑ«6²ô¹'Æw ß5£QŒ#……àiÖ ]@ÁGO°È8YUÀò7Ÿ¦¨Y3ƒM³ÇÐ嬮•f mäedüõ JÚƒÍs9ii®†9+¼ÿ d|ƒ.º’‚å¢f÷4&_5‡]¥âÐA2Gw£YšŽŸOnëŽìXg wÖù$¦h$ óÿ‰¤]0šÔ O£VƒŠ…”wÆh³sSª‹öŽ^¡áø{3< ‡&H#uhD¢gB£gE#“ä4ì>%UE{rv-œŸÖ¦+éíò@6ny¦©Œñ‡G1ÁÕ Ù5o&ŸÝ{ØSñ °‡1Q‡}\ÕE¼®…28}ÅEä9)ô]€;ôxQ|Fó³G=üš'¾Éì!E…Ê ÓÙ½a?u¥Û9°m#å»6S¾s3å»¶PS'èxÁ/hÖyˆËØ6c[fLeËü%Èîã9ïñ—ÉaºÉíwFÅn¶Íø[¾žÊÎ…ó‰ï0”sŸšJë¶©•ûYÿÅ—d»ˆ¶=º"\Id´O¦pñJ÷U’7úbâ=¢¡“q-ZãîaÏŠUìøæCön©¦óõ÷\½¸^Ó¾g;6Ly•*¿F¶+ÈÚ©¯²mñbÔV=8ãïÓ£_›£ GH ’[´Ç]¿‚³¾dËW±sñjÚŒ{ˆN²)]<ƒª’D:‰nFýTtoÊÁÕì+8ˆ–Üž>¿|€Ü¼4¤2pˆó>§®Z G^·î$w:“mšq`ç&üÕ5‚º7ü‹✇~O²Wà ns?;W®Cõ$ÒöúûèØµ%æÁ,^ŠˆkFŸÜE›ž­MÌÛ%-ˆÏH¡lÓ:J7¯IÄ.öàãÈêPÑåÔšŽÚ?½‹ˆÈø.ÀÄ…/vBhbè1¼x€Àظ䴼Á·?FÏKoÀœ€Äo¹IwNph„€Vý‡£{¼­‚¾ºn@) %—ÃKx‚ÌÁÁ¡ à‹ÓÈd·°P0°/ÌŽôNOXxB[° Ê´êÒý-«K÷g®œŸHC? §»{ÓFbÿF‰Ø¿muèõãݨ-šÛUÅØÕÃÑ€.')&Â#2Yûx}+NGQGö®P£tTT-¯ÝÈ‹š^‰ÙY½‡ GW4&Ò„ô¶ÉèØƒ¢ï–ŒæÒP"=R|‡½§ë5ÃÁ¡Éà‹Ó“HQ&– ˆ6.#…ÃV\Ø¡(Á6~<4T®9Öºœ¦¦Ek ¶8XAƒØ )H-šÛ…+µ„Ë9R8„+@Ez*ÂÞŠ°Ç",*oEÃ>öV„s-Ü@¯äܶž–=cFÂB(v³E°Ë5KËÎïQTÀâ„óL„j'>*û+dÓ ‚¢î±·óßä°((Ê·Žh„šË.-ý}´N5, â3Èé3”¢ï–ôÀöÖÛcáÜSšŽ°8}‰6#/ÎÑeä{¢ÈpÛ' .ê öÌtäŒRqŒÿ;4.%Øë†Ý€Y4‘¦W87'ò˜0±ópHSX8D ‹zެüåx+Ž$Z䇅…Š]P¡o›3Æ {ãN›<&¡@ ¼Ò­[‘Z"{ãMҨܲ†Š²r´äÖ4ïØ.\êØëP_´ƒò‚"ˆ'#¿7n÷ÑâBªv¬æ`Q%îø4ëÖý?èZ¯5»×RQã¥y‡|ÔÿЫhöÁ`†ä~°|7»7ï¥Y—ħ¸tIÛ‘Xä ͪI/f›_;l©NC®EX€;^ ‡&€#,N_båZD¦Æa#2,*Â!/„Edükdž†3£ÔôY…Útöoü ž‹ðq‘¢"Üh12*RXD'jGvÙŽNØ>ÝËFF÷˜‰…J:¶ì3 Õ†ÿ˜ëùQ¡èP¹òc>¹÷üžÎŒ}õ+úÍbëK0oÖ\ô.7sãÛÏ“C$D¢j°îk|ö—QZdü+3ÈËÑ Fy~Åb×äùúݹ¤u?ñoM%ÕÍ÷VŠb±ãÝ[ùjA'nþèM’½ß³¨…ÕòQ0÷Kª} ´1oT­û€î}“Ñ/Í¢ï¹ÄÇa@‹ƒˆKNõÖ”µÃ. a»¸ˆ#,GXœÞ„sv­¨×ÂD7#ŠœÏP»± ۰Ð9RX„ ²;ââÔ`öïyYèñcŽœ‘ápaQšaDX¡2G-3lø 0VÐOЬ:¢­úáï Íc‹)!¹ÃPÚZ¡‰†ý¾òãqJÙjJ +_=‡šsÝ™cˆ*–)1-PlƱÖ#”£÷Å)…wRY=°}Ö'=€O±«j4œÑ¡›„#,"ÝÇa2:&ÒcaD,a#2\g_'¶Ç"2 <úÑ¡é±[ü û>%âoÇáã!Z\8ZP«´¬ã­8¶ÇB ½Þ;¥u¾HÎmwJåW¨nð•ì¦pÙªkjQ])dt@fû\Âv¶¢J*6-gïºï0-WJ{“Öª…m” a_4åpS7R!‡U ¾šý«çRº«\ $d·#³S/’2¼ÈЇUÅ<´Ÿ‚«9XtOv{rûŸA|¸,…­S4¬[ X3ŸÂ ›0¥›”6ÝÉîÞo²ó÷0¥Bθ d«ãò€UWAM•o³LŒÒ”ƒž@J^'âSÜ1Ö!©+-"àóƒá£¶¸”ä¸LÂêIU$Õ{·q`ç~O2)m;ŸìÆ2C9)@eÁ*KKÁHRË|’2 xj%¥ Z ‰ØU碅·zîLN884"ްp¾›1^‹ôVDÇÔ닱«l­#,š>˜‰}£¾(¦ÑpÓŽ Œø êõ`Äg¢E…Ñ¢"R\lƒ*?½}w¼éñ§L™YE‡ò9ï1㉟S°Í×ðº+ƒŽWÿsî¿·¬aË‹¿á›·Þ¦¦ÎŒx—^wþQ·^÷ï;D+`ØÂ¬Çnæ»oæExTÔÜ!Œúó$ú É=üvÿžõ|y}ª«Ú:¡’3æFÎÿ㋤'ºŽXµP€Úý¬|æ6æ¾÷F¸«¸PHm{5ã^y‰yIGUéÒ‹5»˜¯çsËGG.ƒ7~÷ÈÜßÝ@|ú,ºhåã”À² 1;‡ÄìV¢bïŽNØù`±<ã± N888œDaá&º+7-,"Ã_4ŒÉp¨FØŠ|ŒÎ­p„Å©ƒÞâÛ±ß-‘Â"–÷"õ<ÒK)&N•IÓšÈÞÑ^‹ÃñÝ•–Ù©Ç)Óm[Ñ pÑ'ìÝS $Ðý®·ûËK©Zú.Ýu?µ¦EÑ–]të ïì‹@óÒ÷îÉk¡°Ús€}EèöÑt<„@˜&>µ#ù#/FË˨ûnF«ÚBpÍ4Öm«G øŽ0úƒ"…^7¾Î¨.¥nËL¦Þ~Å{+)Zÿ—£( ?‰USÊ–÷þ‰¤vÏø×&“žd±òÎ1Ìøl5³ß¡´ðvòZ'+Îb“†)hãËœsÿ͸]в…‹‚EçQ³cAì éá“BñÒzÔù$½õ*eÒnìy¤xá 41 …7ý1÷þ—-³ÜÌžMÍŽ=ÔUíeų¯ å^Î%ïL¤E. Œ¦Y†àµ~îÕ_ÓùÌ_þ7?íIEZàMÏ"¹E+*öîèÀñ'°œ{ŒƒC#â ‡HÂ""òV-.L’å ŽŽ¾Ð‡'Fy§áJaOÀcÀϱ»k‡íŸhaîq6gpd8]ôqå`s¼Æx*ÐÒŸäJkÓù”IÀTW—´¬ntqªÉ=¯â'À“’‹7ó Fܞ͆i/³ð¡qÌ*/¡²° ´þ½©(%†¡Òã¦ÇHž=™5Ÿ~Ä´Ÿ½I]Y!•Ev̘ŒŠ¥Ò›w£û¹— +‚äΣéuö ¾z{þš:jù"„…@UøÊígµ»—ðÉõƒA pƒ}››©¬¨DÉK<¾R–V\6­†ŒDW X®”\âÒ“¨-+ÆGߔ̀K‚ÄÄ€ô˜X"ƒí£©özôä–x2R¨-+Å`…HßJV>kT „‚Y¾ (*(&è—ú=«T5ÒO²‹¤œ6°tNkì¢!‘÷Ÿè[‡F±ˆô^D Œ°àˆô\D{(¢ó*‚ÂiwjQ‡à~à àÿ°ó.öÑ0-0¢s0¢CžQql¢Kg†Ï%Ð.=¿3z\ü©³ãèŠÇ>Ù½nt ÒHíØñpŽtù¼×xçö;ð×ùT²úõ!Ë›À¾õߨwU °öÕ›øú¥÷0,‰»y>ÍÛ÷D÷(”îÚÔGt¯Ý£‚R€š”€eI¤]ZI (¡×,‰eš`˜¸rº“ÛF¡ç&úcG SJÐP=^Dè½RJ¤E9ñ‹¢%‘ ÉèÍ“‡½Wö íõHû« îØ¦}ê E§Ýðq$tírJ^€3ò»‚éH™ ì öýÆ);ëàЈ8ÂÂáX„'Þ"«lDŠð<ÿ}qŽ{úÔ§¸ øø3pvÏ‹Hq™{ÝHÏÇ'Ö9é­ˆò2;õAÑÕSb– Á`½}0ìÛOÙ–ý´é”Kpû>¸ïnȹö/4›ý/üu~÷H.|ý5: kGÁ{øà¡ï@؆ÿñЇ Ù8k†%Éw7cïŸ@VfsoiOé.QNÓú]{Ù¿y-Ç´Ä,?Èåóp{ãˆKöp(¼“` ,aÇc¥»+ÿv'zÀ zÿj+«–$9'åĺ`›Ò^¾'Bˆp¡*›ðYóÍ*øƒ$õ½žKŸ|Í´°5PJÁŠUĵꅦZ¥g-šuè‰ær§~_°“£'´ÀŠ#,þár´pdH”õ÷ÈÙ"8Ú-}¬ÿ;4}Â7è5À­À»ÀƒÀ}ØIÚ‘¡s‘‰ÿ‘‚Qq¢ÄòX¸€ém;¡è`sx'Ž¥@v»¾$y Ê·Å¯Þ‡j^Oõ¬عhíïHÂWú@’Dóš”̪̚w'Úëò„Ë ¤½c¤åëÞÿKWìµÿŽydÃßV>}fñ•ÖMcÅ’B¼í/ -MPv?H 5¡ÙCÛ²õóÍ”,%ïä’×¾ýëï(Þº™Ú…k§,&Þ«Ÿ˜¸ø^H¤´0üF#ÅEÌ·¸2»Ò1?ƒ5_~Ȇ˯¡ÿˆ<„¿žï^½Ÿ/žÿÞLæìvç#O‘ãì~!im:¢y⼆ߗzùX ‡FÂ'B´Ç"ºÇí™8ž°p8uù¸;,ªø#v2w˜Xb"òÑáøÄJÚV°Kͦ&fåü{ò a uÈ%ô:‘y3¿¥rå|±òƒÐ_òÆßO9TÐmÞŒ²9L½¶?ÂW…ÐT[l¡díZ IÀ_xÂ?è·ó'L¿‘Ò’ì–íÙµ©ˆ}_ü‰Î u%JœX”Φ¢ôvMX¨Þý)ß<ðéáñê #tË è†Û`o£©{è~Ó³l[ûwï`ñ/eñá5£ÇµO‘Ù<.f/ +X~¿}R˜A¨¯ÃŠŒ™’†¯Ž`}ðè“D‚t'Ò,µ9Û—Ïgê5ƒõâ 21±žZºŸJ¿{&Pp×|}ˬ2¥t;ûÖ®#µëÕôš”D@i)VJ6ÍÎKûþ}hÝo êÁ2”ô,š·¡ËOÿ@¿‹/@¨ñ¤¶ìDF·Áxã ü-†Œ£ïE—¢)*‰ÙmÈè1Š¡ÿ÷"]º¤c™¨>ˆéÊ §ÏPòú %>+ŸöCÇ WBÍÈ");´ö}pï+ ¹lJ O…@à¯.!˜Ñƒz£ÕT™ ´:Š”4Š$MjU“Úa$yƒ:¢XQ'ÐIÍiN]Uº7‘Üa— Òð†Ö긤¶¢’Ô£hÝ¿#‰m’׳+ue{”—£è)dŽú%c'ü‰ÌL7æ)t …ŠÂöYÓ¨Ú_°û:YÆ:º7ŽsíqphUïð¿äxŠÈpªc}Æ¡iðïnÈÛøýv‹‹¹?ð˜~Ì„s•½`ÜØ9^ [\\’˜Ýêž+&Î$£]û£±5yh:˜u‚†…¢ºÑ–?T•H€ªƒå·°,/&(.P˜~;-A÷€v$i‚Z¨ðµßg‡ ).0k ¤¦¡»i÷¸ ô9{=ÒÂhA‰â¨‚¡}«º@sÙÛ1êCªZµ_7jM,C¢x44Œã”ÃU]®Ü$UpyÀô6ìèq㋵û4PÕµ´sN޵!!Z¢ƒb¿Î‡¢yн`ùµNBÀô‡~κ©çc’(j: » ˆŸ#«Ñ988œDœP(‡ÿ%ÿÉLµ#,š'òÛ€|à`,¶÷Âá?#2¤0:¤CYÞÔtâ’ÓOÄíH$.z¨©µé;òïfÀž‘VT0B3£ ö@MÄa¯#râ]ZökÂeWŸ2BÍæŒÈ\ üQëªÀ28¢ª“8:—%,2„ª¢†ZEOT^Ïá'à¯zƒ„`Ýñ×! 0 ŽB=‘õXA°hq{¬~Ni„)¹mšc ð©@èààp9…¢u~¤Ä ©r–Æ]N”jàg@10ÈýŸuh ZDD†F…¦4OR*®¤”Sª’O4ÒjXbþ]ûoß{;'z$ËÐ{¿Ç‘çIyÿÉök¬?B¤¬V©4‹ÈîôÑÕ¡N2ްpppøo(‰À{@zãç”%²3}´À@’—€+Nqb;N_xR2ÀÔˆíÝGT884ްpppøoÙ \ ´þ¤ÿí1ˆÑ&]@¼'1Eå?›µvpø wBØÞŠxŽ>ob‰ ‡“ˆ#,þ¬Æ=IØ¡ ߟhÏØIÜž¸”ŒÓCSÐ Ûý‘€î‰Cs{ÂÑž½&þë;8üøq„…ƒƒÃÿŠ%Øâ¢/v‡î¬ÆÎ)At%µXaP^À—š§p~ʼn Õ{˜ûð•|ð󱬜±q¢âÂ41ê}¾@“Í'P…dÿÌ¿3gâüÆ+‚¤ÅáßBÀÞ/žå«gßÁlúâë˜HÐ<^\ñ‰H&¶ 8U¿ƒÃGX888ü/Y\t¦­w8§Ñ^Šèàñ$§5Mƒ9FaiñïæŽõw¨?HÁÜ©ì˜ý5{·¯CF™Œ±Ö­jP<ç%&ÿ| S¼ƒ ´X]šNöœvÔ¶„Ï}‰%¯|„ïìz­HÛ^»•/Ÿz Ÿ­šÊêi3ñÿ›*VM)AõÄ¡{ÀÎë‚£CÏ…ƒC#â”›uppø_³8;™{p0¿QGÔô‰Õ÷%¼èBEsÇ5~~…ˆ O’`ú-¤¢ ¨n°` „¢¡z‹#ún¨:È Ä4%Š® j ‹¤Ë“NçËï"«,Hv¯>‡Û2ª.°|†i¡Õ£ °KÒjn¨;¸ƒ½«ç£äšU»¿„‰mT‡ÇnÁ4%šKžµ—Á#H€¢†Â©¬ˆþ¨nÛr5ý Î#E³¿“éõ‡ ý/,ËîA§e)´ûék¤œŸH¼ »§†–a¿O†JÊ Úá¡+ö÷—¦ý¤ýVT)Ýðo¤ Êæÿ n`ø}7£© T;¶LÕíñ™†Ý—#ú÷ªýh_¨öwk¢V‚ªi¨ºvîQ¸Ôñ–¦0r‡ÓGX888ü¬.Þ¦¿ÞjÔ5}Že©¡jº«q-$fÅ6VMF@ºÉê3€šÓ((ñ0ð¶GÈÎÒ(ùöS6Ìý’ƒûJмÍÈèÜç]M³ìLÃ6X,ý”µ_L£²:HBË.´í;êÍ+¨#•ü ®£Yª‡„æyHWoRª¬cï—ÿdÓ¼o©(¯A÷$‘Ú¾ù£BnçöÍþ’­‹W kŠØôÁËpáu$Ícãêõ¨îVdwkÎöé“Q’»Ò6žºýzz^5·¨/cÓôITðáÊA³¢FÈ=;g~A}0Žv#ÎÚ¯Ú±œÝ+דsöU¤¦zf€]3¦²oý:|u\©¹´}%-ó3‘X†¿ßP!°w5kWï"»g?ªWÍ ¸ph‰4ë3†üÝÁ ÿ5ÙúñÊŠ z3Éé&VÙj©½hÝ­cƒ* Uì˜9™â" |[Y7y*]FEQp)TlYζå3©©ªÃ“Ú’Ög_NVë´qSµ—_}Ìýe=‘”vÝÉyn­i4ÖІ°Õm8S%²Ä,8Þ ‡FÅ?»€‹€G€W€~ÀC@EcªÑB‹/êõèæ^Ñ‹&„Pýddü¡@°ì;f=õ€Ý&<»þ¢ýÖ‹>7=Êž)á㇦&1Õÿé묛;ñO¿IVއ¢eÚ¿¥¢²!gµ;KÖ€‘‡wÀ¥dy‹YùÒo(, ÐùîDÚõo϶ÿ»›Ï_{? 4iwù\öÁl Þþëæ-@ÚŪçoÇj;‚®Û^fÞKÓAI&ÁkPSSKVÿ[ÉÙ¼–³Íȱví“©Û4Ÿ…ßÅÁzhûô5ÐîÝ ¶ÀªdÙ ¿f_uK~6ô<Ò=¶‡£hÁÛLÿãËœÛa ™-ØøöÝ|öä+ñ$é*ÕåE¬üàMƼø%=º·d×û¿å›ùÜòÕ$Ø:ƒ¯î{ov.¢}àÖ1ü~ˆoÉÈf3ð¬íe΄Ÿ²rú\„îF˜A\‰™X•Å$\ð~úä\Âö(¨+dÕk³³È±àOHÌë‹æöBñ—|zëTj+êÐT #`4c?yñ=2›ë÷¬dÆ=ײ~õfWÂôaš’f×<ÅåÜK¼Þøž EÕPma¡;G)ò¹ƒƒÃIÆÉ±pppø!©în~‚uN£Ž¨ñè LÅÞg`—Ë”Øæëᨗ‹BQuwãu(:.·Šø‹ö“”7ˆüÑ@ùJüáQj&Þ¶ç2æ÷“8ë† ‰‹×©XúK?ûY]Æ·¯¼tºl}®¼Š$½ÆÎ‡Ð½(Pt´¸$4W<òÀVÍû?8öN®ýb ?x;n ~ß:}±Ž®wþž^£ºØCŒoÇ€_¿Aï¾9È8—íu°*©© 5àZN÷+.BSˆƒl[º U‡²MßQYJ|':yzTކD yÐ=Þ#-VÝcoWU1kv³á¹—0:ÜÈÏgìãÖÅ…\óôûײøí¯±tÐ܉ {P$ ÇãRÁñŒ™¸Š;–äânE¯ÝǦoV¡è°cê_X9}.­Æ=Æ/¿=Àí³–Ñ©{ i®#Ž iI]7q }»êÐê ®ûf9Ý´"0¡¦˜Ä—rì2~³x}ÎìJÕêyìÙº=.ÈÊ'îeýêÝô¸ãn[^ͳ7Òw@Ê&ÝÏÒyë¿w•®¡¨M[X@ìò²Ž¨pph$aáààp2x ì>ÞZ5êˆN>pð$ð0ø0 »t¦ê±…F¤¡¤"„¢º×c‰Ò»ÝÆeÿ˜Áe~œ¸âyì©€Ði}Åít3‚nW$·dÏ”•Æ5¤´\„Ç"Vï G`884"M`þÁÁÁá4av9Úë ØÕ£þ†ä½½ñ†uÒ¨ª°ËÇ&`‡†õ~…66XlìŠP¡ Ô¦d#idŽ»ŠìÖIƒ€ÐPKÙôç Øôç#ß(ªÀ_s dX§3ðL\ ,A«g“ôÚ$ªbyc,5=—ÜgRXð%Õ»Ö²éµlš¨¡§dÓvüŒì;`üËÄô±L{B[B˦ùçÃ-¹#.ÎÎ7¿äÐÞynßÌ®9ëÈ<ë’âÀð¿="¤…šC·Û.aϳӘñ«Ì´>ûBò‡¥Ó¹ƒÐå‘N'a™X -Hoßa†JÄj^”¸8„¯Ë€ƒkkÑ»ö$!^Å´ì÷¸²:“« £þ°)˜Ò^'ØYêñýHIŽÃ0íq¨ÉÍlK<À¨®ÀªPSþ“®@¼°B¨9@•ô[Xàiôth®YMÿwpphaáààp2‘ØIܳ€ë€ß¿Ä.Mû:°øž&Ý)ƒŸØßMÁîZ~[h)À«€µ€%¥´Â†sc#‘ 4R³Ó0C3ðR†þ£è´¾ânò²0- ÍåAQ ””¾¸•Ý!#× èÚù  1 0…Vl`(ñ ¾ç5š÷ÿœ]³?gÛâj« –ïeË÷aJåòg~\“RÈöÄ{ÝX¦½M4ANß‹HzëKªŠö°~êZÐ:Ó}Ì@ˆé­ÈP"CdEQÃ;S¸éø‹©$÷Ké¦U.ýš-óÞ¥`æD¶/ý=—>ýÊQãÔ@FÄ]É/ ÒÄŸ‘še}ÿ–&–„¬D4]ipt„T‡‰4 )k4¯‹YÅBQÝènwF'4³qs,„Ë4± ì_)ºTs,¯…ƒƒÃIÄÁàØ!R·—‡—`7×[lÄžµ?ˆ’°›v%‡þŸ„Ý<h´2N`]­CK' ØÒ4ƒþ&e.I3dÚJPtVÈÎî2ža¿€UYÉæÏ?¢ºÒYx’ýè^ ê«Xûѳä|™4W)+>z—Z“†ˆù„¦Q·i>Ë^{|:í¯бOµdÿâOøâ×7rȤ¦° ¤dè_Õ££FââH‹Ø2 ½ÿÒ:·¤jÃÖ¿ù ÄCüñd·L ÷•‹ø< 4¼ŠŽYÀô R¹x!©j˜Åß1ïÍIä]t+}~1œ×ÜÍè}˘ü˱”nú†Zÿ#ˆèît’Ø¥…âÉ £›`Û¢™”W¶ „#ƺþW¨¡E‹XR±;‰‡—ìˆ%;dÉzôD<·€’Ðw9„í±ˆ?ζMìï<ÛcQôRZf°ij,Ë„¤žŸ;­µ¬øãO¨øn ÊîÕl^± ȹýmúÜyù=P2{UË'1åÆ ÄÉ2Jwí³÷v,„ ]¯¥`æ$Ê|°cÑLº^ú âëÖb„„îMGU@(¶21JÖñõ]pÆÃq«±mKi‚šÞŽNzR°aŸ=q_@×Q£ðzÁŠvY€'™fÉ©¬[±†Õ½ËñcÙÿÙý,œ¾Èn¤@±êí§X»r?ãŸxŠì–ñÔ”íÃWY…š€K y|N‰¢ÅÑüŒ+`ÉûÌzì!ÆÜÿ¬¢o˜óÈ]éæÁ‹¦ÂÞ”lÙI|—֯ݶ4‚Äåõ¥MŸ,ž9‘o_èÏW‡ƒÛYtßu¬^¿…þÏ\Ú$:v[¦‰eš`Ÿ+±Â¡GX8884J€/BK08{ÖþàYl£|WhÙìÆ6Öë°…H=v)×c‰ {^\Ç®ï ØÉÓi¡%=4† ¼ 5@mè±&âùnlá¹”EØ¥uMlÓÔ‹6 ÆØê±ÔGØ"JbwNÆŽÁ±¬` IXOA¿øf®%h)íñû·<ñ »7í`óGo†þè!wÜmœsíUÅÅ€G^¡\ÞÉÖys©Þ¹†jw&Ù»s hm(+†ßNÖ6ý5ÄuÏ7ßÉœw^£rïF–?w÷á±$u9›Aw]‰„ìNÃhî}ž’º Š—}Áîíé¤ylg@0`{T"0 …ç_ËÂI3¨˜¨Yh9 ?ŠÛ`˜í®8Ÿ5Ë—±ê©Ÿ²ê)À“Aó.]l܈e¸ssî¯ÎåË'ñþÅŸ‘œ£R±óZz[zßüg’Ý`|‰RÀ_µ=‰é÷ôI í¯ü#ƒwbÕ´'™øÍ“€ ­S_’ÕU¨q;¤, ¡'Ò¼çù°`*S®ɹç¢iGÈ)ƒXø0L‰)\ ¸ïiÊŠoøöÎ;ÌŠòìÃ÷Ìœ¶½°Ë²”¥‹ T¨Ø±÷^Qc蓮|‰šh£1F¦˜£&öQ±`A‘¢€tÞû²½23ï÷Ç{†=œ¥$Ê.ðÜ×5×is¦2ïo~OaÖc×2÷±k1‹Î7>͈S†â~—r~'q]G;{pqAØ{iç)A„Ö°ÐÕ£º%§>è|„¾èp¡ô <‘œü¥[S1iv#LšÝˆ8Z T@¹oÚ”œji)&¼û»b#XÀ§hÁäQ‰¯³“Ûž;Ùhw£pï¨{ÿ\zð•7írRñ·†ª©‚Õ3¾F)ƒüþGPÐ!sk„‘„XùJVNù‚ºªj\;@V×Áô:úp2CàÄb4VoÁ‰Ö±î›¥DrsÈìÜwö«¼~×OhÌØŸSþ9ž¡CrX;c*Mõqr{¦°K', *çObíܹ45Æ0\“ŒN½è|èñt(Žè.Ò†¢zÞ$6­Ý†EÁàãÉg#ë—,G™Et>xá°±u{Í0˜3øb‡ IDATkxë´þ|³º’?á’g$ìlç €Ê¹_°aá6dô:”î}ŠØ²p6¹KNNɳfÊ{lüfñ˜K(¿9nô@% nåtÊ«2)<jW²fáZ: 8”ܼîE‘hb㜩8Áî”îjj$QWGݺ9Tm®ÅÊ.¡¤,È‹§§ô¦¿sÚm×b¤ ± ì-kY3}*1#‹ÒC&P»€M["t<€@²–’]³Šµs–SÐ÷òK³õûÊW±bÒ'T–WaEòÉíÙŸ^Ã'РÌT­ZÉËWGíÚÞ¦åïÑ»ÀcÇÿ‚ |ˆ°aOÄzðOsnƒ?ªÜÿC;ÉÛtŸÍ‘î­E¼[|  |ŒAç˜,OîKfr_2’÷sÐ⢠¸÷È;~]6òŽŸc§¶×ÛHV½uÛ4 ,Kç«äc×K±Ý2‹W¯Åš•õX}OeÔm7QÈF&=z=Ë5èzþësʺ†uiCçB¸É€3¬Ž•ÉÜfýz3”4= ½^e°µ÷‚÷½×€êo>eч/2ùŸÏˆ+{tÇŸ1˜Eœ™Á–_.×ÕÛ¶uù†YÊû6™z>'9ø7`š`Çõ>XÁ”c™|¿`ìÇÏæÃ§çsÜó8ü¸RˆÁÜ¿ÜÎ~û‡ÞÿÇ^~nšï„a%™ÇÖÛa%×»uoývóq4,ÝøO¹Éè)ïX¶uô¶n^ô />šÆÊò‡Ñ¿'¿ƒ(ÂBÚ …aOÄIN ´H¨hÛÍÙi& ó'ÞÖ%Ÿó³’u’ZV$%â­®hûwµý2¬ÊÛûï tØŸÇÆú§ß#>ÿMÆÞøæÖ×Ìp.\q]º„·ÀS×»3}%Üø¶£H;M.‚e8,{ñ.>ýkýxðå >j öά#Í<~qƒbkµ,€Ô’W®Ý¼ÊMs<}ï7ƒPÚ9ÆÛ||ý¡,ì× «fýü9äÅÇÇh%ñ&Îyª#_½ð µ Ùtõ% }3ÅE!íFìC$¢Mر(èðÄíuÕ±!m€ A„ÝÇö;Š–Ž…wë±ú=ðmóeÿ% \"”{5]O¸Zˆ“¡J®­C…v×n9ʤDž¿¡÷Åɪ„nœ×q]“’#/àœ£. Ó¡J v5Üöê,|‡ÄêkA‡:5¥y9õ7$ÂnF„… BÛ²#±Po7Õc7*Œ€±GšÜ„/ï‚ qú.P6ØíD¤y!n­å1¸ >e€ ‰t9^GÕ>ò!¾4Um•€&B{`6ÖAöR¯²º)kšjªˆÖUïÑáP-Ø…ëÊfP·Š°¾íKa*¹ìN n ‰h;¶cËDµ" 寭¡©º†D´Z/ßÊ…ÚMkjÐ)#ª• ö\oOöhö–S” ÂÞˆ‹ •7UWÐT³eŸ«ågbS³pk¦~Åæ¾Õ‘¢¡µKæ±æë©lZ²µ›­aõËùè®Sxç7Oáÿ‹“²D—óáyùó¨^ŒýÇXáo[ÛÊ…š5ËAkH°mè`‹6»wëA‚ íÔÁQêT­ÞâF«+0[ëR½»I–€µ‚´ºM†V(9Ó46ôàÚ ãÙº<ïÌd‚¥*™ôȵ¼v݉Œû×›¸FË嘖~HY¾·l³åó†ïySÕ1÷wgñÚu'òéþÚñIÑ0õ~m×9ò­c‡¯ÛMÔ,úšÊEkpÓÃØvRŸWÊ%ÞXj¬¥qKC dxÇ'˜²=i¶ÑØÞsí@к.T¯YºÇLœÖÝ ‚ÐFHŽ… BÛ’z•5u2€òDS£ÝT]jŽ…#£zõeÈî@NI>†“ìÇLÊŽmYOÕÚ (VN zuÅÄ—tì8¸Ž‹ b¡¨^³Ç6ˆ•‘•kâ:Zd45T«¯%Kv¬&¹Ž4nXGCU f(‡ìÎÝG’=#×Qzl/*a£”~`(›ø†uÄjcÄê«ôë­Ð&¯o"·1ÃÙ„³ ”Üg³¹o„áB¬¾7A$ËjÑÂè×í¦FF˜P(‚aZéUˆOà*¦…hVpnÂÆu] #€îÈÁ÷=Í€¦Ù}××ò“ǧiãzê+«1CÙdu.#âŸDÛq1L#Ô}Câ ”rQ¦…ÐÃǶQŽf+hµíݱ©Y·` :Û#¨q!m€ A„¶ÇŸS‘*,\tS¿ÚºkŠTW2P9s,ÿþWT­_mC(·ýäÈ?DÇ’,L×á›—æËž£fýz\GÈ/¡pØ…ÿÃ{(éÁ´ÌzðæNŸ‰Ñûp:Æf²lÆR\#H¤°oú=Ù™÷o¼˜µß¬ êÓÇy©n)'Üý$] køúþ1kò*«0Bùä”ö`ð¿cè‰C©˜øþþalCaö=‡sîû1Öâq¼õëû‰'lrºvÃR¬Z¬s*ç?ÇkWqâcOPTj‘Ë`X¨\Ä'ÜËÚù ± Ì`6%ÃÏcø ?¤¸(@Å—oóÙŸž ÏW°æÝ§Ù°ª33’—1òG·‘“ XÐ0û->{ò÷l^_áº}<јƒin«M§‘™÷ŸÏÜ¥UKr콦´c§a=ŸÝ6šõu ä8–ﺓ•oÿ“uSL÷!Ý0ª™õðO™ùÅxê++1Byät*càurÐI‡²üÍ{˜ðêgÛ—÷<Å=m>úíͬš³#0Š3þü+ò27òÅ-£Y]SKþ—sò/o%`·Mr¸iBÍ–ÄuU¨*šÞoG Ahˆ°Ah[Ò ŠÔÇ `sÅò…EmÙ·À BÂsç¥lÞP @83‹úMk¨\2›êºN\þÔÝlzå^޹ﰊÈ+ P³v) ëà­µÕœ÷Ì“”v0ˆ.žÅºY_\ð%k2!;Ò™úk¨]·œ/ú;?ItÓR⺲¨S±™† kÁn`ÖýW3îå·È((Ãݲ” –²ùç—.ø†Ž °`33Ç/…™3˜~ø‘ßý +¦NJ9üŒ[©þè×Ô'•šÝBÕÚMØŽJ†ùö;QË×÷^Ä×-¢ÓgÑe¿Lª§MfÞ?JU}—?z nÕJVMù˜ÅS>%·ì0JöïGÅÄ™;w™ýrì•Ç]ø1c®º„5õ:8šˆ»…ÙOÞO‹Î]·u,Œp&™=JÙðÆû`¬dÍ?¡KYoçgΔOˆ;9îdZõTLüUM ¢}¯Ã2mf?p-¼ô‘ü2¨\Ɔ ËØtÏ¥dvü˜œÂR6Ïù ‡%T.YG¢6Ly›õKë€-TVýŒ¬†Ìúêc¢1Eä¸{ à´Ñݰ rùBìX4ŠîaéËËŠ¨„6Dr,AÚŽÔAߥð_ë6/œ²Ý6Kà6‡¥ï>Éæ µÁºë?\ùÆ:€-_<Ú™“™öÂ?p\t;Óÿ>Ëÿ=Ã.<Àê¹Ï³ðÓé`C(À fqÈíorå+_0üüS1€øúMTÕpÊ?ÇÐíÀžs>ö9ÕŸ3ùµ·ƒ’³âÊ1Ó¸âÿ¤c—|œò…Ló:±p ÇüäQ:ggq¦Ýs>“§|@ïKîfÄ%—qü¯_£ÿ€LŠÞÀ…/?EQ~X‡6y`7mdÍÔ唞ùK.}öeÎä®zþ-Êzv$¾d) ¦ia…ÜÄ…/}ÎÅϽÅ«E[` r8.VHglÍ ¢+'òÕÿÑûuÚ¯=f—?óo:•àV,cê[oRÐï(ºtͪ¨\;—ºu«hÜ\—\ór6¬Ù@ò™¸®º2è˜ChK·Ì @ù’9Øñh-:y;Y|w›)]Î… » ‚ í‡taP.Ú±X[±lñÆú¶Ù2Üx%[f¾@Áà9ôê³)î?Œá÷=Éq?ùÇýì^BÕ‹¨Y¿ 2èÊïsàñýÉ-ëË‘×ÝFçÒ8õ¬_4«EC:³ßÕvá9ôêÅÇF€ibZarºõ#œ@¸S)=:a¯›K€a‘‘ÙÄêIï²~E $×ëæ,£©2úžÎQ?û1HÔ­'Ñ#2èBޏýBƒìÒèmD:QXVB(5]‘Õ—3Æ.áŒ[Î¥bú$¾~þu¾|éj*+±‚­:ÏúÞp%¥A¢Õwè(º† ª"F¢|KçÏ!Üû}Aâ„è}ñU””š¨Ä¶c`79ûE×ý.«ÆÏ"V^ò™‹qdŽ:ŸÒ.™:Ãà иrõJ'~ddÅY;ù]Ö/«„Pëg.Á-èN‡^ûŠÕË–³iù"ªYÜ X1!•3¦c' °ÿ©têZÜæý1¶,JÕ s,¼ß‡äVB;BB¡AÚÿ )5vÜE7+ÕÕ&*W, vv¨î½›1Œ¡ìÀ²²’»²É‘Ä Ã–qÿ ‘È¢  +8ºKt°°áâΰ~% U -\‚¾½ ™ÉîÛ ÿŽ)\;òfvÜ‘\]¹È±YùÒ/Yù’~Ù EfdbÆM” Ž =Ž»˜’gŸdåâJÀ¤øðKèÚ9L¢L'ò¶CÙ(;ý¨ÔTM,}ãWL{m õu Äêëu9*²’I×Jé+u ·6ýS ,  @96ŽYyýÉÈMî« œŽ„;t#žîUà†;pà±G1û«94Í{µs¢rõ<0ò:ò23Áú¶Z¹˜‘< S_Ð_ýê¯Xýjòøõñ!n°ý÷d΄¯©ž?¥H4…9ì–Xô×ûˆ~ñ!³Ã+qÞÇLVŽn*Ø&ÄjbÔ¬]°-´¡åïûï5Av3",AÚ­9•®¨)_8«¨Û!m#,”Š“¨×‰Ô±øz m4.ú”ÙSC8‡âbE ÄâÔ×Wa:7îÝ@¼²€HN¸eÉØ]¿a`˜àƪ´ °B ¹ïuïWŒã$—âÆ!PD$¤~—¿õÖ/­J.Á¥âý‡Xyö(º÷ÉJ9†FÚ’ªFª?ÿ#>þ'²zÎQwÝC×þ’›“à­ëN >–Ú:ÜM³P*R1â±â1ÈH–Çuj±k¶`th­*t<éR ÿð7¶V3õ™¿P±.F°Ë zŽ8#U &n´ \X úÅk = Ç6uÉ*7BÂ(9ú\¬§ßÂYð³–‚™1’ÃOcÓ{aÅ×O²%CA¨”.ûI(@ —iwb˜P_±‰Ú «V¡wÙIN~AÛ Av# %‚ж¤s(¼«°þç+”RÕæMkîó°›·Ò d“U6€Š¹¯2÷$ê61íáÛùà—7ñÁÿ=L¢ ?ÙÅ@#sŸ¾ŸÅ3V«ZǬgbãÚ ƒ.}‡ôwºÞAŒ÷ªÝXKSMâžX&àÚÔÏ\C^¿aÆ™÷ï_0á±»™8i:Fêg¿ÉGO=MÜUX‘, Ó¤qÕTÞ}â4ØzÀê&œD µMÛŒHMj—ÏÃqà ¾ñ/ ¿üd:÷n2µËWc„";lH§\‡Pn ™Å%4,Âòk fC8åÓÆ±iC V0ýu>åB¨ä 7 «?ÿŒЇžMI·Û$ó»á’^úøàÒ0s59û £CG—/ÞÇßÍ„ _á¸é=‚²Ì,@¡\…ÚzîG¸¨ \E¨sJ‡öÅhË¢&Ôo\KíúUŠfa‘šO‘*0AhDX‚ ´R ¿¸hÖnY2†ÊFŒ6h”§Bô>ýF2ÂNýz&üd8OŸ>„)ãçE—ÜF—aGpÈIck>åÍÑCxü¸ùø¹·±„:ŸÇ®“Ê',”«ôàÞuq• áL² ­B¶Œ½g/;›hÇS8üØ! \–¾q Ϝڞy<³Æ~Àêi騭¡è:&ÞÿS*«ºsâÓ“9ä¤!€KíØ{ùêåÉ¡L"EElœþOŸv1›«m,Ÿs¡d•b,ó;¦>û2ÓþòoÞv9 6 _gÉ䥸†™vDë*-,ìP‡{.ÁØ*ÆýòJ¦¼ø2³Ÿ÷~üâ 0¶3VO9ŸÐÖÎ ™ô9ã"Bªyí&cºÜDŒŒýG1ü˜ƒŲ·ï䟧íÇ3§ÃŒwÞgÕÔ tìÔ‰H™eô>e¨^€ eCz*Σwç"ýÄ ¿Û‰””°Û0qÛuaüi¸¶]l¢Yx§ŠoÎ… m€ A„öAj5(¿sá ëU­Z¢ªW-m“ÜNЇ_Æ™¿y˜NXõåÕ„² èwõ£\ð£«Ä¡ç­qÖÏî¤CÇ\v 3%˜™CÙ‰7rîC¤¸ÈÂI³òÉÈ+$3';.V(›¬¼B"E…B&6tÙ ”vè@8;…E\åsÈoÞá¸k¯$37—è–4A"ùC9ò·c9úì£Y7þ–®ÜDF^!}.ù1Äðk~DIÇŽdäå°èÍ?Q^aÿ‹ c÷R"99ëpm§Eçj7Žº‰—žGÕ—¯òþ.áƒßÞGÎ pô¥£°7/ã‹WßÇ1³ÉÈ+$ô}0FH~2ó2pPzÚ½œú£;È/ŸÌøŸ^»¿ºuÒ-ôzáH^«'d×…ÒÁ£è1¨/y…D]È~Cz6»FP^y…„J²€ †Þ÷N¸þ{dæåß²‰&„ssÄýï0òÜSÀ×4)=ì<òò ÉÈëJ×>C± (9ær:’‘×.§œLÄl›ÞÊU_XŠîé’ÎᓊP‚Ðh=\Aöi ôE %§ dÙ@ ”¿<ã÷/gpÎE8±ô ü®·Ö² V±ªuëp\ƒp^' ºwÁ4’]µ“óD7¯¥zÃF\VvG º— ²u@¬ \×+ˆÐÃjÃuq ”abuGh羆X£ ÊÊÁ êV¿i-õUÕ(×"£´¹2t7ìxÇuõÁ„õ Ý7OVQRÁVìº:±B„²sÙ¦W–²©^³Œ¦Æ8…É)逥š¨ÝP™ÕŒ¬n"Žj™?¢â1\3¹ÉãÒ°~µ[ªô1éÖÓup]3¸k}Fòx9.Ê -†Î*žÜ¯`Ë40,}|6ëããÚ2KËÈ)ÊÔIê^'p¥pâqV0´õr£é„ô”ýÙÝÄjkùÇéûS_¾áà]´{W Ô%ï7MènÜq´oÃà-AØwa!‚ÐöxÂ"„ÑÂ"‚9hQ‘ w »ìÖ~'ÜýD›^Eöü¸´ìýàÍcê ÐŽÒ…Ól{mÙ;3©–ÏÉy[tÅ6Ù:ðUnÊ•õV–³Õü\ºe§ÝŸ¤³âU“2LßûZÛ—”ç¶ÿqK÷Þm6 Ͷ§¾–n]­Ÿí½·•åín¬,ûtoÞzV̉G¾êÑ¢žfa¥¥°hãõ‚°o"U¡AÚéJÍzS°xÅÄû% df¶ÙÀO©—Unó¼õ™vþ¹tƒbånçüËÞ™uµØ¿\_Úã²3ŸãöæiåµíŸí½·™¬œ2'­Ö%ŸöçVøïK· ´1’c!‚Ðö¤«l“NXÄ¥5kWÄ×Íš‚%—†„½ÂƊ:Ö}=`9PCËßC:Q",¡Ía!‚Ð>H6Í'›æ˜ñµ®coZ:~ ¦ a/Æ´ bÙBÊϘIóïÂûmøûXˆ[!í‚ íƒt5ùS ¨–¯›9‰†òš6);+»VMO"ÚXŽî_‘ú[ðORJÚ",AÚžÔ®ÁþÎÂþ«´žk1»rÙ7°DX{)vÔañ¸7æ¡“µS éÊÌ ‚Іˆ°Ah?ø‹Ô>þÁÔªD´iýâÞH_iIöp!X=u<å‹fÙÀtÕ§ÔAIÜ„v† A„öP”NTx· týþé‹>|úM››Kº -0L§ï•wµ‚z2-¶éÍ`Xº´©JÎcnûº·,ÓJ.+\Žf ù¾À¶ËÞúz¨yýRì};$Kï.û"N<¾ÝOÑüýO'0ĵ„v€˜è‚ íÃ7™É[ ]<€îqáM õCò:ww;äP\iÖĆuË©^»š¸ƨ[ÃòÏ?dýÜyDcA²Š‹°’GÙ´ nÙt–}òëæÍ£zí&摟¥ÇÿvÕK—P_^Žk†©[4…S&S¹¾’pQw"f=g|Áª)_RW#£c'BAC7˜ €[_ÁÚ/DZ|â§l^²ÇÈ"³0_‹oƒ„Ê+™üä=Äêk?æ¢+¢yý*ü=+üBÃä‚ ´RSD¡ý°½äíÍ)…®é¿tᇯ|Áu"á´MêöU¡ów“>ù£lyjå+š0ú|ï÷œöƒ[ˆ¸õ,xî~ÞÿÃãÄ£ñä» ¬¬NìëÓœ|Í©Ø+?çßW\„Š*¬¢2Ô–E4ÆÁ0-Š\M·’9Ìüd*.`XºücÎ|à²3 ~åW|üý«XüÍ­ŸˆÐïšG8ñ¶›É°Àq± +'£fÚ`úwHNþªPþ0(AÚb  ‚ ´R«BùËÎÚ)÷=‘1uÜ©îŠ)ŸHéÙ”ap;A`ã4jÍ28ë"™a\;Ê’ŸdÅâz—}Äøß=L<'Ðë(†Ž¾•¢®Å8 ˜ÿøïÙ\¦eáØqœDNãrBanÊuØ<ïi¾þd%#FQPœƒrlV}’E³WcÙL¹ýRÎ_ˆ2;Òë˜Ñô8°åDùæé2ý½QÁ¶>Rí ÀXM#3^x`°…faZj¶µ>‚ ´",AÚ­%p{+øÇ'[<ëÅ'°£rÑ6.ÐÜQŒãìG_æÔ;n&¨¸"ÞØ@4¤dÄiô9þÎùýœx÷o8lä`LÀLÄ1’v‚ABt<ò.áKÎýõ¯É Xô¸ä.~îCÎýùOZ&ÐHÍ–zê¿ù€yK—&=nþ;<óüísº÷ï N”9Ÿ~FS4MNÆ>L óß}žŠeó£ÀTtØSœæï}:ÇÂC„… ´1rK¡ý°½®Ûž¸ðÜ h&¯œüQß•“?²zwN<ír÷i2º˜ÞËp (î¶ Þ0pm—âÃNçø0ûç˜úàyL¨ÞLõÒ¥¸€eúFü À¢øøcÈ›Ø=ËÈʆڪ º9œHÄ€ÁŠX$†a’¨®@úÍïý’&>ʦ|e9u‹Ö‹B$k·’v‰iAcE-s^ÿJ©o€•4wœ÷œ:q,¡#ÂB¡ýá/é¯åq ,rgåŒ=Þ»ÇáÇbX!” ¯ZS\€ (®co}Z•³^ãõF³esB9” 8ˆ’AÖΞ—vY–›½:Nò8¨ä}×±[ mÍpd«‘ˆ7‘htp•I‡¾‡`Z Šúb‰[±Âeãßeã¼é `"-EkâB*A B;B„… BûB¡+B¥s,üà i&®üêÓž‹>zÇ<àÌó°cm²Ýí']³C%XûÅ»lÙ%R6˜Q¾Â€ýX÷ìõ¼<{®›fÔ¿³CXÃÀÕâ: Ì Cïz…#Ž;•h¢fõrbMQTF ‘"IVñÚ\Éä§~ 0Xþ¾û«?¥âVB;Br,AÚ©!Qþ+µ~Ç"ž|}Íùò©_ÑP^#}-v¥\ÜxBÁ[¦¾Ã'o|€ ¦ûßúíYûI~¶n‚oþøkù+ßz”ÿ\?’/;’1ÿ~Ç ¯äïÔç¡bÙ7uÀ´,)›*,ü9þ®õ‚ ´1rúAh_´V*5Ç›¢@0aóÂÙõÓÿõ(V¨-6»}av, €Hl}ÎMØÄb¸F€}#4­þš·FÌsWœÅÆå@“˜ÂÚ%+1 E¢Ii][ç ;Ê!h$a'Ç´ÊÙ:ŸˆîvÇ_w+‘ IÕâ×ó½xý÷²eC-fÉŽ=ÿ|2âXXAØ8.s^þ+À—Àšs+üSªc!n… ´3¤Až Bûû†mún½†yVòq€æzAtHTv劅=»=†¼®Qé"€ö Ó$ºynnGÊ=‰núb ëˆÕÇÉ?à0zŒ<‘î €(±@DÆâ IDAT™EéxØùÿÃ{1•KAÏý‰t:ˆ®ûu¤®¼šÂ^Òã˜3).̓Dõ5dwNÏ£N§¨K*§®¢–Â^Rvôé—P0ìz êC´>JfQ)9{Q|ÀÙŒúÕßès@Wœ}¼±¡a€óѯnaó73×c€z´Xn ¹)^Œ–aQé\ AÚ˜}Ý€Ah¯˜4 ˆ"É)Èr’S^ò¹"àŠ.ÃŽèqþßÞ'œƒÚ‡«ÐZXàÚHæ†õÉ/Ñ®«çsl× ˜ib(°BžÃ‰„2=D?§,FÀ4Z>ÊhžÏ f,ñ¦®kÌ `8ìó¢tyÙ‰O>Èþ_ x˜µ@]rªONž;cÛ(AÚâX‚ ´_Œ”ÉôMž{ðÝw€ªº k؉x°÷ÈÛd£Û Ê'®ß¹qõsN¢9IÙÚá0Êå›Ç{ïÖ÷¸èOBi1‘ú\‹ù¼ípÁU`,Ì ¹uû:¬úr"ŸüúVìXt"0-<§Â›R AÇBÚ ",AÚ'FÊ}¿°H ‹òî{!Qnù7³úæ•ö4: Ør`-´Îw=<•¨[±‚P½z5ïþärjׯZ Œe[Aшv(üMòÒåX‚ÐNa!‚Ð~1|·é\ ¿¨ðÜ‹Pî:vÎ꩟v.îw0z÷Ú§ó-„ö…€hu ïüè6Ìþj ð`Z@x‚¢!ùØ›ZË­©&í‚ {Û©"ÃÖÙ±hñê¯>-ê:t$ùÝ»àJL¿ÐƘ°c¼ϵ,ûìÝ:àM`Z84ø¦&¶u+HcÞP×yÕWãóKúD~Y™ Å„6à @¬¦šî½…ï½Þ¢s'<—Âå%jÇh)*DXB;E„… žCª¸ð‹ŒtbÃÖDk*;.ûì‚‚²ý)ê×O/I†dÂîÂÐÕŸjÖ®aì]£YòÉ[õÀ»ÀšE…?*¨HÍ­o° ´CDXÂwCk@ÿ$;‹‘r Íb"°ðß·U‰¦ÆÜ¥ãß.2¬Ñiÿ¡3‚R™HøÎ1L°°jâÆþì*Ö͘TEK§Âþäw+üU ü• ü $Ahgˆ°„ÿÖ®§ÞO­Ý„í±½Ð¨T×Â{- Ô¸v¢ïªÉE6ÌJ‡žÈ)íŒÔ½áÛÄ èÊOMÕLzò~ÆÿîûÔ®_µxS‘êT¤VJu+¼ô^GzAÚ!2„ÿí‰oRè¢W"šOŽÍ'I¹'l¿pð*@…hnœ—d&o½zYÉLJg&ß3èÎÎërà9WqÀYWP:è Ì@²É[òÛ(_Bag1|wLK‹Šê5ëYòñf½üÊÏm¦_£ßyîê}“ßµð*AyŽ…× Ï a!킰뤆¥´vÕØ»ÂÖ8X‹¶ÿ]t¿•2¹l+.d|'øIíaLNáä䉋¬äãà"àT`ð)z`WôGr »<’#ާûðQä•vǰ,L+€aÊ)BØ>Jrl”ë­¯eÝŒI¬œ<Ž5Ó>§rÅÂú?o°í>4ÑœKQï»ïM¤¯%}+¡#g AØ5v&qÖ@Ÿ ‹ÐºkÃlEð,ð*úª±_€ø¯Ä¥Š !hüß9ß O\xÎEèÜ CÇ´O@»9¾ùs€~@o À …s³KºÕ¡„ÌÂŽ32wã® {†kÛ4U•ÓX¹™º k‰7ÔE•r«€ À< -­9~Aá•õì„v(¾W`ƒÎȲø~Í[iÕ6ü½:Àsu.óãnðÚÁ˜MóIÛoù§†Gùó1„}ÿwÑ/.EkNEjX¨ í9QBKv”?á ¸\  ppU‘e8+Ëâ ´&(R×Ô9ðLµÅ3µ0'îÔ¯/3i®$å…H‰ƒ!øñœ…w?ž@g²iv%R'ïù0ÍIàž[á¹",„t¤^äðþ£<§"‘œ<Áà …ߥˆùÞ㉓Ô|3AöäD!éŠTA ùäÖ¸¸$×0ú_àºüD’¯îê)Ѐ ÆÔøCµËü¸[ |€v0f¡Oºþ$oq0?€'€ó€Ÿ£Ù…ðr/Âi¦HÊ|;#,$j߯ÿ?ãÏsh),<·Â˯hmòW~j-§BþÓaBN ¾Îö ÿàÊEWÙ9 ¸ª“eö=;Ûä6}½tÙÿåô—ü%6:ð\ÅÓµ0#æÔo¢Œ©ˆƒ!lK?àï@_à6àušÃ›üåhý"#DË(ï5¿¨ðr,$JH%]‰ìTÇÂs-â´t.â¾û Zº©N…$k ˆœ$„}tW\S /Æ\%Ÿë\\Xhšû}/×äÚ|›~á仿í!¼Õ¼Sgñ‡j˜sj€qÀ?Ð ¦¢ˆƒ!h‘ûtYÏkÐî–?éÚ_ŽÖk¦ç~—"[á…ýµæXû&©®…_Xxy~qá‹Jк ð'AØ “„°/±£†v©EOtRö讳ç¹Y&wØôü6ŠÜÒ¨ /Ô˜..Î/6Þ×åY\gÓ'LÛœî äý^½Å#UÓcv½‚§iè +&-Ë>Šƒ±w2xÝ5ûçÀŸÑƒ3í•Eö ¿“á.°ð/ š]A;ãÑÃ`é.AŒo›2à àltƒ»(0Ý]ý-ôgÒ–È9Aø_‘ÿ A؇‘“ˆÐÞÙžC‘ZÕÆÂèØôë€Sö š%·ä›\”kS`ß<å%C¤>i4øcu€NB1 xø¨¦¹ŠT:!qÑÿ=A #pp°?ÐÝàðe`ß½;!‚ »B{¦µŠt… ..2óGç˜\—o“·¯8;"ðqƒÁcÕ>nL¸1ÅàYt¹ZO`´&.R3…ôt@ç󂇛%èãü`)Z ‚ Â^ƒ ¡½±½*OéŠ0íPœ< dÝžçe;:)[†¿Û’ ‘ú¬Ñà‰j‹wmWLþ ŒªhŽ‹n-DJJE6“. {o€Çª|Øè¨&¥&£Œ€JÄÁHG.º @?à´˜è¬V¢›¾Ì@çP‚ Â> ¡­iÍ¡H'(<‡bppÂàUx{¾Á996ûjÅÿJR`Lj„?Wy£Á&¦ÔT´Àx-0öUÃD;ý€£‘è>(ÐM즣]ž¯ÐŽÄÊ6ÙJAAhˆ°Ú’q(,ô`5€®õpöÁa+ëš<¸2×!CŠo‡¤t˜Ò¨òA£­ê]5 -0>ÊÙ±ƒ±§ è‰G&§\`Ú•˜‰®ªõ%ZtI®„ ‚ ÂBØý´VåÉ/*<‡ÂE_-ÜhÀq‡­¼Ûòᬇ\Orß.Iñeüµ:À+õQ¥fÏo[hþ½RµéÅE{þ„ ô÷« :¤éH´˜è˜œÑË?f¡ÝˆUm±¡‚ ‚°' ÂBØlÏ¡ðWzRè2ÃÑÅÃ#Œës].Ís {YíyȺ7Ó£ðhU± 65®š‰Nò~Ø„þ¼ü]¼¿mÃ@'B¯ù_v%‰‰v#z þ‡£Eô¾¬–¡ˆ‰À7è ù¦ ‚ ÂN ÂBø®Ù‘Cá¯ôä™è§ 8vD$}[¾ËÙ.YâP´ IñužªðBC£Rs€¡K§–ûæÞ‘ƒ;ÿ)¿IÞ^ Äva«-t>NàðäÔíD£»\{b&Ú‰X 4íÂ:AAð!ÂBø.ñ„„w? ðäB=ø œvD$º)ßå— 8탤À˜ƒG+Œip©rݹ茱è+þ¾ãTà×ÀP´[q °|;óg EDwtÇõhG¢Øl Ù˜B³á¦Y– ‚ ÿ",„o›]u(²ÐÀL8jdÄʼ­@qZ–KDŠöIR`ÌŽü½ÚâùZ‡:¥æ/¯£ÃŠTrÎ]u0J€»kÐbÀŽE»  ÅK&:œétº^h7¢P= - æ«“Ó®8‚ ‚ ì"",„o“u(¼§#€«€“ΰ·äÁ¹9–IËaçö¾¥þyvfþ=Ô}úo÷Ñ?tßÑ{wvÉ׿‰Â£UÞjp)wÜE茷ÑNƒ'ý†¿†¿ÆéÀhÇ!•ß_ »YŠþ>mHN Ð%_§ Cšâˆ,A„ÝÊÞ4Ú†T‡Â©‚Âúg£Å y\f rkžâÔl‡€'(¹`À®†x+Ñ*&.lˆ7@0ˆAt/ “7€p>`C¬^?ç¼+Ãæ`„2!^ ‰íTGõ¯3Z¿óÛ ,ˆÂßk‚<[kSíª…À+ÀËÀz¶u0<Ã:÷W¢ó"ÒK.cÚ˜ ,¢ÙA¡1w<‹ ´Jju'¿+ôM:äéTàŸ¼tB¦uÂ¥fäÃ.6gæ:üŽC¬üúxgüáOé»4#î„1sáí/`ÄMðÂ×ðü»Ð=?r>t9žŸ ¯¾Ý ¡dTóãžÝõ<;C¸ñ)ødÜtCëÇFÙÇÂK3àÅ÷t`ÑÎǤÿ0 vL0­Lqs®µ©eþx¸ûà¢Ã˜¼ï„œ‡î‘q­‹ €ZtÙáAè0© C£DT‚ B;!ÐÖ ìq¤s(@½Á¢wßË¡8 ¸>`pøIÐ-ù.'e9˜©!O[—ºâ ºv‚Sa@1Ì/oþ¶* «Nººw†ŠJH¸p`/ˆeéVf›¿“}ß½dæB÷cA$ ÌœæÇY;¿èX sIë>¥¬èÝTDÇ »°½ÉϱOþTêp{ž© ôùGó« W^Cça¬z?.ÙÉ¥g¡s.â»°E‚ ‚ ìFDX»Bj…'$R»e»@p4ð½=*3`Ý–ïrJ¶ÝÕZ(¼ü&œ{8du†QçÁŒ¿¶yàÄýôãÉã`æ8øÛÓ`¯×©Ã~/Ρùê¾^Ž×­Û Èñž÷°“ëñöÎ[¯fÞt$’ózë ±­?èyÇÁJ.×;®«çqœd&‚ÿ±ïà)ß²<‚¾}$ùÿ2½õšÉmÛ*6T2ÍÚnùù¤®Ã¤ÙwH‡‚~x(bss>‚0â8ì  ¾ >}ÎÝ6Œ,ÑûŸgœX=Þø+ÔÙ­F:@ ÃGуÀµaéløø5¨khý_$ù9w£ln‰Á³µÁžO×:?Ýd» ÐaRï ÷8-4J€2tèT䔋–1í‘AAÚ þü /¼)€î7‘MÉò“·eèäÛ !pÎÌ ¨ºJí‡Rýз»2•¡Ô/žSJ)¥W)uZ_¥z&_ëj)5nº~mÕdýÜáç(Õ˜Pªü¥F¡T/”꟯ԃÏ*UW-¨/WêÁk•ê†R½¦”ã(µq¦RÃPª;J}‘R ýü›?WªJuA©[RÊv”ZôžR‡£Tï”mî‹RÆ)õò¥œ–«Tµ«•ºç<¥z T¿|¥žxY©ÆXËy\[©ù_(ur?½m§ž¯Ô¥Tl…R§uSꄳ›ŸÝ_oëˆJM˜£”í¶\V<ªÔTê@S©®(õâ{úùõ«õqò³h‚R ï¡g(Õ ”ª_­Ô)Éã1¤¯Rï~¢”²O«f)uÝ1ÍŸËŽ¦~zZÛ uWA@õ ]Áé>àÀä÷+’üne£ÅFÐ8 8‘æ0;AAÚ’¼-¤b°­¨0iNºõn=—"Ýù•a#``7˜[G\ _Ÿû,ŸÙ2Iô€¸Ë‰Ð5M›áýO äpè¡ ¶ÞN}éêRïO‡ýO€ý 4 ŽN”G¡¤=Ê‹ {T/÷ß×C]?6Ð÷0È3¡n¼þªv2€cే  Tm‚yë§AÍz÷…….½àôƒ“Gygj¼Z®€ß\ =»Â¢iÐo8ô=„¾EÉe¥|“^‚ñ“šåá˜?Âå—AÿBèÒÜe-÷éädZÃÚéðùRx8(꾄ÏÁ…C¡×zÈïuªØY’›Ö)¿êèp}>¼Pèô×çG«lu1:ÿâ`Yrî Éôuš³mvÔÉ[A„Ý„‹}—ÔêNéÜ ï¾£cܯË2zFVÀ¸-ßåðÌÄ·/(<,àãWàûW@A œp,þNî¸ðåºTFÊûзDßÃããÀôç—'ÉÈU óÞuôï=χî°~¼½n9º †£:éíY1¶D[ñú’mÊWCãšæy‚ðâOõ}ÈÊ…žGÀeçAiÓ-lzY±})<ðÈÉÒ¡Z­Íðõ´–O«JXU©…EQ¸‹½@åBVý°ç10i&Éå(G‡žd†µÃã¿÷t ÁO‹l®Íƒ¿Tº¾PçÞ´(á^€.Qû*0fÍ^оˆ AAhcDXì{ìªCQœ \™iC.α¸>Ïæ°ŒïÀ¡H·¥_ÁûsàÒa0xœØòƒP· Þ{i[çÀ#3©6âM0e²îâm$w+‘Ç…Øh¬†¥‹`c:÷…Q—ë«ï_|sVgÃ~‡BϤðñÛ­ï¯ç(E‹ø,ˆdCÀ„¨ ×ÿ n=P°~9,]sgC¨\wæÎ' ã¯ÁàÎ`GaéX±¦Î‡ÓGðîþÓ7ÅÅÛŠ"O,ÔU5ßG9HŠ¥ÊU0w|5ã`†`ÎÌô²v•ä&àžb›kòáåZ«èÏ5êŽe ÷BtÆ?€%h1á9­ ‚ ‚°a±ï°«E)ºÏu¹¦1ðì,‹ÛòmÞ‚¿ÅMÕ0áU¸h : :'‡Ã7›õ785rÈæ¯Ó÷cðð°x# H((9Î8jAmTOEK¡´\”†‚‰`¡£Çÿ³wÞaRSû~3™ÙÞ—¶ôÞÅŠ °ƒ^P±bÅŽŠŠzÅr-?õÚ°\»" ¨\ìŠ]QD¥#½HÝÞw’œß'¹s6Ì.‹R¶œ÷yòdJ&mN’ó9ßÖ«/kÌýCž¿û€áÆ'¤7˜&àl•ûfô€Ç¦ÀaMà¾àäãärŸ> ·Þ(Gú €‹§Ôü܄ˡç!Ы9ˆb¸g(¼ûžüOŠ;ÂI—Eÿ]›rŸ¼Ô¿6н©|S¸%"¾€± Š·Ê·ÛÂ5ƒÁ4Üš޼é fË“¿bx© ÍC0:ÓæòTx>/˜õZ¡¶¤Â>˜‚¬…ñÚ‚¡Ñh4M­Aoת^0¶Zê,¼“0&\“:à«–‚W³,Kp—Ø—]´X`îO°6RÚBûV€ “ßݹFƒ‡lùòHi×Þ °T@R[¸÷Is+ ì¤Ë9 ‘ÁÎÉ`ï€%`å|XµNV½Ž‹…Uóac”˜äOYû Èè ç_.÷ÝN¼Ž=Ò \©±y=d#»Â=/‚á§ÈÏ-j–eɳ5Qk6Èú"Îe¹ë2* ¯~ÃÉ'ÉÏpñ-2SUéføì3ˆ‰­¼þ_–É×N€ÓÎ’ç°B@Ç¡ðÈD¸i œÔwïÔÃveAº c •R IDATY|ÕÒæ©ÆÁô.¡ÀuÈ*ÞOISkP9Á€ä­½5F£ÙGh‹Eý¥¦.Ož…¢ð`XzÀèvV’Ɉt‹ãÜQø}a¡ˆFØñ ÌùUvn²Á’_vî"HWØ8 ^ž£Î‚Óî€F½`îp6tmNLš9Ènè¬YpËyîo—@ö0lX¼ú¸™—ü*Ÿ&FÙÏX`ÞaÆpq_¸ê_2à¼0úŸ c~ý~þ –m‡)0ä6Hê1-àÄã!ѽ{ž#®…/vDŽÉŒÁ ¬Ï†l2³àÿ^„/‚î}àˆ‘å‡Ü [Š ì*•¸&ðøtøò µ€Ÿ¿ÿ",Ù îy5äyùàA8c0ôm ¾ Gž Å)pú`H–yðÚÃÿ /Ùkh‚2,†¦ÀKùÁ&¯ˆË~¯°ÏBZ/¦ó©lÁpÜÉ+—¨-F£ÑìE¢½jê6Ñ,&•­AeÙ¦ÀÀý)ãâ«SC'4±–îÐ,Híè~ÀŸ™0èh°*àÓ7aúÌÊÕ:ÃYƒ x3L›(]‹–| f{èÖ Úw‡#Ž€F)¿ ÆŽ‚©Óe‡ØÂ¹Ðû H5áëðÎgrý%maàP–ï<‹VV-ÇåR%w„ní C7èÒ^¦¨ýñ}¸{Œ´,¬ÚGô…¬ÆÐý èÒAf›šò&dv…¬&2€üƒ ÿ@éR5íu°›ÀÀÓÀÙÿ „ÒÆÐû hÚ>Z6‚ß>OB·Î2ÓSéfÈŽ‡í`ö/иô<Ú·”¿gN„»o•]ñ¸pöéP± ¦> Û‹à«ßeæ§öí ç¡pPOˆ ÀÊ9p÷ ðÛ¦}:D€£†$ :„ÌØµáÀ¡Ûl1Yc°)$¼bzª¥Â³ù…·F£Ñh4š¿‰~¨Ö/üqª•"He EkdÚØaÍ@ÇsÜnÑÝy® ‚BÅÓäëpžL媶^À¸T eÅn€;u'1l_ ?~«×K+‚JL’ òBØŽŒÜǧÊ•ìÏm?ÐûèáÖ½øý'øýkéÞä…'uƒgCã8ز¾ [¶CçS¤(Y÷;,ùâÒ"Çî{e_Â@Ç“á¤cdQ¾å?ÃìÀŠ…ÞC 3Fn{ëzˆI„Ò|hy"wŒì¡/û¾ÿ<7íµJƒj1R“5ù÷E=>o[^•rGY·WgÂ$Â]ÞtmžÀðSm¹ƒoÁ{…&ò`~… ÌžCy— îV¼U©ÚÜú5F£©õÔ–næïã·Rx¾æÞhm{`00,Ë ´’lr}Z˜N1D²zŸ AÈ^ó¾ßß{µ3†@ódXñlÜ(ÿñ ¹ô>âÜü´9aÞײ‹-­n}ÀÝî÷@\bâåûpó9Byð­Û+MTÞ{ãâñ!0ƒà”Êuš¾ßWq¦\Æ.‡p18î ùýñgIQ‘½n ŸÎ#Ƽ7' „öO›w®®]×p»q¼”oòR,¬°Ë‘–‰ß€õHKD*l«í^½6¢¹<éÌP £šÉ»*+€$à`°ŽHRo^›Rïº=i4š…õÇIl"AªÞä½¹¯C@>Ò•dåÛéörÓ}j‘Ñhh’ÉÕé×' Fèz2 >OvÂ8þÈø>ùHž±#.…¡—CÖ²3¿nLyf}èO7‚‹GCë x÷IXºT~n§Ü ‡wY“á‡oàËàÔ~ðÃ4ˆí§Ÿ ÍS!w3|ñ:L{32¶n›pô¸ðrh’[ä>™n?Xyå¶=\¾_ø=|5’Ò\øy:8§CJ*$¥ƒØ¼oÏížÆ€ma˜^àÉçZ0)/È«…‚%ÒB±i¥ØB¤={Ùþ©œH<…*¦Õ¢x:ÍlÃ$še»'zsh .?*.˜y[º`VI ãÄüð pðÈ{¥'D¼{¯j£TÑmL£ÑÔk´°¨_x¢B}À©Ö ïá§ õw–ò›d±ß€U¹ŽèôFa¸Ë;ÅF“ ’L®Mµé/ê®Àˆf?÷c†CÅvxêÿ`Þ7Ðã<5  øþxäQ0šÃðQpr6~üf—Ay–]yý¥îÜrçårž– ~ƒI“ ;·Ü íšJËÉS¡ãP¸ËK¿ƒ‰/BB[q#de ÿä•»xºœ@þ[„‹Ÿ‡«Ï|2Ön¯{W¹›ÃðvÉ„<ÁË*FÆN,v±D” Â.Uæjö'5µ¬'.üÂêf+Öì>þ ULx‰™ðb ðÏÁ@÷©nJ·ˆ3áôd›Ó ãÞsÀeÖQÀ À$`#òjó*º{“_¸ê¶¦Ñhê-u­Ë¡ÙÕËßï eQYL@å€DÕ-Ħò¨n¬;yßÍþ(uD»— ¬Ó‹Œ&ÿH2*Ýæˆº(0‚ÀÆ0ã¸e8„óá£Ç¤÷ô˜ñjÀº_à–óa³%ÏàÈeðöçÐ+ †œ?´ûÇœ·®:¯”çÌj GCjd] ‰ÀÖßàÚ!°f‹ü‡þÈ…7ž¬\cC•†eÀÃÝWÈÏ?}ý÷αµ·EîÃsy!Þ(´Y¶ËÖ‰åÀv"–7O(”)órß¼Ô÷™ê¨ ‡¯.µbMÍñîyeÍJzÿ4áä¡ÉÁà™bJ­£² O¢Å yfòùbÔ&Ë<‰¬™RD$˳Zx÷Y¡Ñhê5ZXÔ/üÂÂ@v¤üÎ0‚HçÊï&å/ 業q³XUàˆŽoYÝfMÏI q}ªÅQ uL`€7pÄÈ£Li ={ÉÏ~ø 6Z²£P¼~ýz ÍZH÷¨ªpª8 kçÀª•r6°¥@~Œ…¸Цµ|ÿý»°a‹´\`É4XxÖ,ʶ€'Á]×ÊîÌ7“àöÒ¡­.\ál¨€· ƒ<•ë°Á!c(9T.dçY(¼©:¡Z4T‹…o¡;yõŸªâ(Ler€,àRàšCcÍô»3`p²ýž&¤—ä6§'ÁØÁÎS‹¬§Ëÿ& Ý£üÖ Od¨‰t»Óh4õŠºÐíÐìuO8D[NµfxV ¿˜ˆõMžÀð&×Eª\ˆ¶“ Ã=fMON £ÒlúÖ5¡"°Ý8Ìík+K² °)ß} ‹ÖE]‡ )U|gÉõxçÆtPm ’Ú@’û»…¿ùä Û‹‰0a}/Æ!غî¹M†á׿«Ûm[,x67È”"‡•a«X ü´PøÛ§_H”W1U(ß{ŽšˆŠºØb5U-ŽÂ³Nˆ\‰±H·§Q™£ç-é&×¥[¤˜T/9ÝÏÛ†àÕæÆÝÙSç”ÛG¯ÏR9žÍï~§…­F£©WÔæ®‡f÷Q-ѾSýÊýÂBåì,.<«…7÷Æb`M±íß-²ºPL³Á‰AnH³9¦. ¸}€˜ÄÊgÒ:ºƒò"™)v&$&íÞvA©›15«qe ý·Hu-/›ÀŸ9µûÊ6`m¼‘âÙ|›Í¶UˆtB[ŒŒëñD€'ª²PD~«›êâÍï]n×Oü‚Bu}R“;Ü48óŒÄ y_¦E—87.jwZ„îQýlžÌ f<•çŒÜd9€Ç€mÖ˧&£®*í±F£ÑÔIjs÷C³{Dó¯.ýlù éˆÅ(óªÄ…ú¹'0,`°ªBÐvZ‘Õãýb#«BÈ™fsB‚ ‰¬íX6¹×(3ׇqÇ5»A7·Âõ†u`ؑte›÷„–Y»¹aÄF(w]£ú‚Ç_ˆä0jt4Ôœ¨š1ÌzÖ~›~¯1®À܆gò‚¼Ud³6."b¡È¡rJdÕ©*A¡VÏ®*ͬMeQ-”õ‡hV OPxsŒhº¸üÀ³éøL‡Ó«r{ª)â0&Óâ¬$x ;ØýõBk¢ Ÿ ­¼jÌ[´ÂzÊÚ4¦î¡…EýCMsèÏ¡î‰ Ïï×^ ¿ËSU ÿ{où0°X[&D»÷‹ÃÝ>.¡ùÀÓ¸)Mp|‚ƒQ›†o…Ï?„ÓºÀçÂesá½i@:\pôj ¢¦~Û7@n®üîâ‘°4ÊRàü» CÚnn<Áøð[8©'ôcî€ç^…øæp×=â¥Ë÷a=„-aéüè6«ý…«Êáõ‚ Ïæ;l·­`%²­äQÙB¡¶9¿E"š…B òh{m\MªZ)@‹Šú‚šœBí¼«qò>7ÝØ <<5À-éÉžSÔžh :ÅÂËYg&™æ½9 œWnL^E¦Jö,¾^%o=J£ÑÔ ´°¨Ÿx'¿_¯?׺gš÷æžõBµ`x""š¸ˆA¦eŒfÁX ¬±­gÛ=>)¡Å‰ñAãÆt‹“¨=ÃÊ=7â䙊¾~¾8 Nê·?C®3Ú6—¿™1 æÏ•Ë~ÿ#Ýzœ“!Q)sr/± ëÆ£|'€X÷û¸8Ùåù` :Nê—ßÇ] Í¡i²û#‚¾ qè{ œr4µ…GîÞÿ•´k*àé¼ ÓŠ6XV²]¬DZ(<7%5~¢ªØ ÿ2þdÑ,j!³~‘ëÿö#Xµ:rõ ½ 8E0ï;øæ‹ýÛ1`y9<šâšm‚/K¼G,¾Cf{*$âÚT‚ì`©sÿTeR‹àE+€ç ;jõªb(‚î䩸ÆÀ0àÑbÌÞO66‚÷6²ibŸ´„¸ôMp8=¶ÚÁ¦+ÂΩ´6[•ýV£ª ß{F£©èWÃB}Xù3¤¨sïárç1îë«D´€î8ßw±ÊoMdòÔV@wZ4nL³”$¤ÀØÝ=ìºîÞ©>l ýž V¶-‚MÙòˆ å÷¥@VWÈL…²XÿG¤[ë1µæyœ²}Çý½º}Ç]¾KHŽƒ¢­ðÇ"ù™C䌪x]ë•+vï+ÜñÕ?*à©Ü ï;üi9D,yD,ѬÕ¹?ù­žU-Zü„j¡ð»;iAQ?¨*ŽÂ›ÔlO'£“Æ¡£ÒLF¤[4R³²íë½ð~¡ÁÝÙ~«°·//" ?zÝj,:P£Û°F£©õhaÑððÊS+Ðú3§xyØUáÍÕÉ/2¢e“ŠQÖ ´z˜ÐâØø 9:Í¡’CLmq‘òð·!rV¢áuÔR[{jÛò¬ÕÆ«Õݧ%eðR¾É‹yŽÈV ‹ÛP¹°]UñeDw‰R…„—å)ZP¶'&¢ÅOÔ¦¥ù{T•>Ö_9»;0Ü€ó%†Bã3Ãì îýÝ (°à±\/æÈ™<Ž ò¶ˆ´eÕê-6H£Ñhjµ±«¢ÙwÔÔ‚áÞ8¼'BDOKëY0üâÃ[OÐèi@ëcãƒëÓÎLrdyý謽(‚bB^ˆw‹l¶ÛN2;Øj*[(j”-ŽBübÂ_A[e×_ª³Rx¢BéÀ%À°®¡@Ë{3áœGf®m­Á€Åe0~GéÅV¹"“@D`T—ÍL£ÑhjZXhvׂ¡Š uRFU–‹h Ï9¨%Ð3­ŽŽ7͛ӧ$:ÄyÝMíÀm-¿—Á¤¼ ¯Ú8"iXÁΊª¬Ñ„Äß±PhAQ?ñg{RE…w/Â}}<0:-`9,ÕdLºEæ>Š£øË¸‚çí‚÷ç,”îQÿ&#‹DzÉ6Të…N•¬Ñhj-ZXhTüâbW1žõ±+Í‚á ŒH7†¶}â‚æõ©‚s’mBZ`ì_Ü;Å‚2C1£È&G ŠEÀ¤…‹Ù• ˆ&*¢¥ŒU3=Ug¡Ðî!õ¶§hƒÞw‘À§&Ç6²8,žºÕ ØaÉkëñ<›BGü‚tš…¼N "I3Ôø ÐC£ÑÔ"´°Ðø‰fÁˆ–ÂQu‘ò Od¨Ö‰Ý±H FZkoNw˜$HÔcß⎨þZÏ燘\hQìˆD,Åì\½}W²½×Ñ‚²=Ë„7©ʪrÑ-¢~QÛ“wÏ@ppC—P åmé—¤Úû/ÄߎóÎ/…{²C|X xiÁXB$µƒ?ŸvÒh4µ-,4ÕQ•Ã0YÃ/2ªª ¸¯["óÏ·;".ž"8/Å–.R £{ WPÌ-ƒ ¹!f[HAáY( ‘UÔDPDËôd)sa;-(ÕYLÕlOAàX`t¼aô¹>Õdt†EVmw{ª)8L+0—#Xv6“€×€\ä¹P¯¿K f¿¡……¦&ü]áOS[¡áMž# 7Èûà3ææ Á©‰Ž,F­¡{WPÌ)…çò‚¼]lQê° ™6v5ÒBá 5ulM‚²£µSÝžü5T´ h8D³RøÝ/ p pQÿ„`ü½,Žª-Ùžö4d[ðPNIù6yŽø x øŠÊqFª› Î¥Ñhö+ZXhv‡hÃïå,znMþTµ~á°+ †77Ýys +ÐáX3tm*\”b“ -WPÌ.§ò‚¼_b‹bG ÈQÒ¯õDÜ•ªª?-u¬wá·Pø‹-jAѰØU\—'(Róámƒvã2 .Hµk_z꽿”½;B|P.ÞCVïþƒH…îhµ\´õB£ÑìståmÍîâH©/¿ëŠ?ÐP­ä­v&ýºÕUó.@vpÿÜl óýb‘ô~‘L @› ^·èÝÃZø¾ÄàöAnËvø­ÂÙÌEVÊ^4ŽD Ã5@6R4D«’­Zv9ÑëTT—BV ŠúMuƒêÀDè<”hW_“L9˦_¢hP¯æ!8?Ù¡]Ð -«0ze;âÈëdòÚò*ݨ¾×F³×Ñ7ÍßÁÿ«.ÈÛŸ¦6ZŒhU»£½Wëid!-Š5CÃR .I±H º{¤»¤ÑqÇ9¿.† yA>)±E©[‘1k‘.O^]p€À‰îûwÙÈg½ˆ&´…BêÜž¼²@Ñ 91ÞL—áÐ7QD¾mˆðgÏ òLžE©à¤{Ô7ÈkË‹¿ð»zgÌÙ{­ÑhZXhöÕ¹Hù‹ìysOXTƒ±+)Õͪ ÐhÛ-ˆ•àÌd‹Fõ% sOñ?AaðtžÉû%–¨lAÆP¬AZü…íÊÜÏ3€3€ƒ€Ï7€r;¹;U•6VÇP4\jr@p60¢]ÈèðÏ´W¥Ù„‚ÛSMpŸÚ³KŒËðy‰U†¼'"Ý£¼Ú:{”F£Ùçha¡Ù“ü FM²HÕÄ‚Ñ è tîcÆ^‘W¤Ú¤6t †›¨òóbƒ§ò|Zb‹ Áf¤…b= E´¢uÞû¤@8¸ ™êßÈø ¯MPh …& Ì£Õ¤À Ü28aXJ[3,ÚÄ [F4 (w`JÉøÁš°³™šö-¤Ë¨šžÖ+²ç½ÐgV£ÑìQ’›ªfßP] †‡á½öú§hñÑ>ó ‘£è·ÙÂø´D$M/ „¤MÐ!©¡µx×Bñy‘Á¨&÷æ:β ñ§ ??›‰Ä?”‰ðÇKxób`!Òõ¢ p%ò>ò3²HžZq»&1ºcS¿‰V1Û?À €¶Àhàþ¾qf—›Üa“¦ëÖTKЀƒâCå"”¾¸BôK‹âfä}P=ÿê@bU¯5æo¡o(š½ÉîX0Ô Rž{“jŨÎr-M­·ž¦H F—n1fÜeÉW¦YdÖw †Š<•`V©åØð'Rl@ ÏB¡N»Jë½/Eþ‡ç!;„KÛ‚ÅsZ©®E}=óIM²= 8 Ñ<è|—[äNÂük|Wl0>'À¬R»˜<…Œ™Šæ¥¯IF³Çihã·š}Ï_±`øçj5æ]Y.ü>üEÀ&`Ã[ˆY¥NÒÔÂ@Œ#´ RêÛ`€#à“B¸q{ûslûËÙ$d‡.°i¡ÉÚTâ~VLÄ*-Ó·œW“¢øø8ŽLM;Ï]Ƴ"i Eá¦Ùž ààXƒ—¥„½Ò̦’h)d÷mbá‚dA33»´B’çÐy-®B^³Þ¹fÁÐæo£o$š}‰? bU ÕM"Z€wUY¤¢Y/¼×Þº#-];‡±CS«S-š„ܽª«·ZïÌ"“§óàËRÛÒ2±9/eg EUÅíªËòä¹:©b¯Hþ‰Ìâó>0»áýßuõÌjjŽ_Tø­ždh\ \Ô'.˜16C ŠíiOcÀº x8'Ȥ‹°à àqäƒí.åÑÙ£4ÍßF Íþ &Y¤ªž°P½w'‹T)0þ$µ ×§\bÓº®Švàã"ƒ y&_—Z¶--4^P¶—VÍÚT•«S4A¡fyòÒÆªÖ"/8Ûûü,d@·Œfîå3 ÙÿìÊí)ˆl ÀiÀÈ–Á@·›R\ŸnÉÚ3uéš«+¸O÷/ŠÜ›mð] +ð€ IDAT™]¼äNkÑîQf/PßA4uá{ÍEJu¥©ÊEª:רª‚¼ÃÀÈ<ùß8"{V©HŸYe[&]BÉ^6ýÚŠ–Ó MFl ðPžã¬±œµBÖ—øØFD$”±s@ö®ŠÚ•q{R+h«– õ¼z,AVîÜŒqùÉ]—¦þ±«Àlï»Ã€MyYr°É‹ÍlNMqÕæk¬žÐ>V0$YРÆüVÁ…Ž8y=®B^ËþâzDy¯Ñh45Bß44µ]Y0Ô‘O5U­çµ«:ê{8¸ø˜ã~žŽì wln’¯M pqŠE»Xjטå6|PàÉ<ƒÊì°#- ‘ó]Y(ª Ê®®…*Öüq1ÑF8È”´ã-Hר¯÷Æ)Ñ쪺^Uk£ ´.†õŽ ¦ß“isªv{Ú÷¸Oú5pvˆ7 ¢>žDftƒÈ@Nu׸F£ÑT‹šÚDMb0¼ê¼j0¨¿†*2TaZ!]t6"+H«¨L èôh4ÏO6¸.Í’.R°ÿ­TØ0µÈä?y³Ë,YÐn 2ÛS9‘ø‡05‹ŸP-~Aá¹6ù;U¹KTufºÇ/#…Föž8%šýBUnOªë“@º= F62=oM puºEªv{Ú¿(é§Ççø¾ÔÎ&Ï!&<ɧݣ4Í_B»Bijþ‡V´Q³ªja¨… ü.PHñ0)2^@‘ò§C-Eް¯Éw„õC™“øf¡W`it"ij÷;ðNA€áÛMžÎ³Ã,g2ÓïÀv""AuyªÊõIý¾Ì7©Ö ÏR¡ Œ¿’6v;0Y_äà|dfÅß9-šýBU‚Bµ$ à`\þy^r°é«Íl§8ÄélOµ†±pA’ 5Œ_ZÁŽ8™Ao òº÷úÚ=J£Ñìú¡©Íøk`D òŽfÁP­j°÷dÀöÀJdýª‚¼cÜu¦ãZ0ZÉC’L®O Ó!ÖÝ«½ÑQr¸Ô†· Lž+€ŸËl X¬ñ'•34ín–'ÕMÊ_%[M©®Å_9ò.À½ÀÀ‡ÀXd¹¦vSÛ“j¥h\\zh¬Ùtlœ–lk·§ÚŒ”Ã9A^+°>ž@¦Œöb§ü…LuúhFS%ZXhêÑ\¤ÔÌ3þ ¿›ÀPàà1à3 ‰ªã0ü…öB@*ÐèœiiW¦˜\žbÑ5Î]ûžz¼P`ÁŒB“ ùðk¹]ŽL»iI)'bñÇPTWà.š ˆV»¦1•p6R`´Búx?Ì`…"8žHjÒÚ„¬Ýñ•a %§w­A¤‚*èA^''£š™ƒ‡§•a‘¬Ýžê®ðû°ÐàÞÜsËì<`"ð*ÒºèÅËXT]›FÿÓÐÂBS÷ðçÉ–/_И„¬Dû8çNÕyû†ƒ‘tz43©g'‘fÑ%–¿>2ë^…Å6¼‘o2©~-·ÃȬ-KÍD, þt°5IMPøÅÄÞ°PTGc¤Ø»é’ö,ðœ¢9°|ok!„ØlF;Ã0Ê÷÷¾ìeî :¶‹¯_ s¾‚ߒ˨½”![}üþÞ‘`žIaøéCy…ÿMq°¬þµ#ÄÔ¢°cÁ Àä?ç^om½Ðh4ZXhê<~ †÷ÚAv~f Âd6¢8v®æíÅ`TUÍÛÿ™÷[YqºÐ3Ó4ÒÏN rCz˜ª³`¸W] ¯ä›¼\ Êí dÜÇR¤Ë“óà»C¡ÖˆVÛc_Z(vÅÀ;w¶hÑ¢ÄPhg]!˶1A‚fôž©\ÆÁ˜U.ów¨Ç¢º”ÏžP Û`tZÀ8dTšÉué¼o1áâ 0ü hWy %ÛáÍ ðØ}²Ö¶§Ì–pÍ­»ž½ÊÚ·Ÿ*f¦m…î…pä!“½gD›{ïšYh0>'À¯åöV¤kԋȃ]Ç^h¡Ñ4@jó-S£©)~ H1ð<2XûLdѸxª®äí‰ ÏB±+W)5#ˆŒÙètK %›\fqß‚aÀ ¦˜<K*ìR`ÒBáexòêPøCÑ]žü¢B-bçÈöw¼ÍZÑxüñÇ»ñÆg2Ö•m[Æ £oæÏ’Tî¸ÿQúv˪ô}Ŗ߸uäXæ•¥rÏCrR—F{|ÿê©°¨®j¶ßúwpÞàÄ`pl#‹ƒÕö]\õÜs9漟|å pät ˜ên¯<£Þž \ïLûh• «~€Š°\O°të ›—Aö&h{ØœƒÏ…Òå0wdµ‡óàˆ3¡C2|6þÌ…ä0p4I…¿Áœ™ƒ¼BšB‡ƒaÇpZ@Ÿ>r¨á·ï`ÁЦ3l°Ðõ0Xú )âBi½ y&¬ú¾ò~wí[ÿ€œ ò|¤u„ãÏ€P¶¾|ÖäDZvèx,Õ‚aX4~û6"P’[A«Ž°âW8|0tJ‡Ïß„-;|W‡Û†¤öpä±Ð"Öþß½'“J‡w„”VpĩЦ1ØEðË×°dž"„BÐû\8´;˜~ÿ¾ûœdÆ5¶87îË<µÈzÑ™Èø‹ÅD¨MeK†'ïjÍýE£Ñh4š¿Â%ÈGûh÷}HÌD"2>" Y ¯)Ðh‹L+Û é–s(pp08 8Y±û d=Œ›€[»€ÿü9¢7 ø Ø–0ÄÙIAÑ5ÆÈ\ñ¿#‹RMp—¸)†nn®†¹Ç2™¢õÀ‰@?àpà ä(rg¤;VKdÊÏÆÈÎ`Š{¼ñDZ̬ÖY,…„¢ ìÜâ‚ÎÍ¡dñè´%ÿû<{þ»¢{ ‚–НWåG~`Uˆâ’áT±>'\& òòDaqyU›¬¼¼ã¬BÄîúHj=ª›“g­K@ZÞR‘×GcdÛû1=`ˆ[Ó‚"»BtEˆÎ¾© BÜð¤&DY¹ÿ½[ˆ®=„øf­[× ±t•\÷ê/…¸ï]!ÊJ„˜;Gˆ°Bä qÉ¡Bœv¡K6©ÿ–+âÌÄÈBˆs΢¨\ˆuË„øSi#åyBÜs¡gœ.D‰úo[BÜpº­Ôc5…˜ž™^Í|ä@ô:à^ÞvÏ5Ês7Šæ"U¡|æ¹Qy“é~Ÿü¦le[žÈñfG{-Ó:©>ÎþÀlï<Ô91á§ëqW1ꂸù•©<øúE\ÕjSf. ý)×rÏÇ€SÂóc/æšûÞ#«Ý!ôêܘMËæðÐ5g±cÕ&>t&ß<};—Žx–Ö‡ÅA={’¿ô&>|ÌXfÞfýëúÔ$Û“çöt.pc—P ÅíépiªU³ª"ŽÛ´E8ú²"™– @DkŽî{!"ËaÃ+ãàß®kÕÐãäwK¾„ë/„U[aÄ›Ð>¾{®;€ÏfÃëŸÃ¡‡ÂǦ ¹ùÀçÀì¥ù ôú5c1¼ü"  qÛaÒx(.Ò‘vpÞ~Û“i€rX4¾ž ŸO…Š)Ð)…s/³ OÎC‹VÁ›/@ßÐõ!»ÁË¿…ë.„õ›"1&•poc®ƒ'^“ëêÜÎ9ÒR 6Šò`þgpË…ðk4Ÿ‡ ‚~™oA«T¹ª•?ÁüðÎ[°!.‰™;j£ Œmlqz2ŒÛaò~±ýª€©À3H÷(Ï•Îs‘ògRNšF£©Oha¡©/4ÞDtº9Šï¡>Èü#fjx¤é›‘x5ÃQUa=oÔÎ+*f(ÛSEŒ*¢ êX/†B ÊV…j•¨W‚"B×?xŸ|û9/Œɬä|Š:ðÌ¿ÆÓ46ÍšÁøGgpÀ±#™ùîc´Í0¨Øº€¡gŸÆ‹ÿ¾“‘wœÀÜ·¿$œÜ•GßüŒÁ]b!¼õá¥)?²ùž+iWo”…Z¹Þ›GËöŽnN4Œ£®O3™n‘¢æ­Çp7e‹ª—K3 å5<¿† äÁgKÛ]PÙÎÜO`ÅV0’eG ³+<>!¨Ð,ˆÖ™°¥Dþ~Áøy©9ÖO°=š&@Z6ºDÆÁnÇè |=¬[G4ƒ1OÃÀ+`årøn<ú.¤œíšËÅO=‡Ê".‚hÔšw”€_>…囤ƒQô“">™!/ض^~•l…‡®‚³®„k'Bf4i/-¢Œ¬ùSÞ1Ü=ÁâŰôg¸g¬[º³ÕÆýŽƒÍm¦šÁûr¸pQ…},ðð2 Ÿ'0Ôø =J£©Çha¡©$"3@µN¶U³¬ú0S-^ ª*,¼ oÓ}í‰Õ‚¡t«¢Bª±ßB¢ŠOP„‰n¡ðêY¨õ' ˆÛì0n¿4;ýÃýÞ‚ÌpÝÝZ×΄yóá”+ «lÛ+ÿ€Ž@ïà£÷d:†Kî„Kχ ³aâýUÄrì.ĤÈ>g+|2U¦•8v,ôL8å28¯7ÄÁÃÏ@øKh|¸»k-àÐxøos‹×òñäqÑò çxàI`‘ä¿Ú=J£©§ha¡©Ëœ<<¼þ7×å·`øË`©V />«}¡ÆbxÂBíЩVOX¨V 5[ ÌÖŠê!8lj -·ËhN›Õ¿rÀeýÝ…-^¹÷Jžµç^~œ{î¸ÜôÃxm⃠îÐÁ^ËÄ;r탟²­¨™yugrssÓºtér²àâ"ÿÕž"8 Ù +Ûßù-ªë“Z*­2•ñþñÁä±™}­¿ß‚@ùZxðnhõh{¼8–-–1]ÚÉå¾ Ï?.÷lÁà88éZ˜z$4ï™ntwÈÝå˜x9(Û`Œ;wÅd ðÎ8õ$èu"Lþ þGœ©±ðå‹0g.tŽ›³È7ôF<ApVAA¹/üîß~ñ˜²½ý>N¸ ¦ö†f¡‘kš‰‡µ `ö|8ý`xêc¸x4é ‡fAî øf*,K…>Á…÷BÏP}Â0íYØäÈŒPêqVELÄZ•ûÿÞ9 °ml¯€N}à­!?Ž>È]0|~^å½aô¿¡÷@§À‘G~ü¤zW,?î¥xIºCÿdx2;Øüé|û¡b!Î@¦×þŽˆ•"@¤»Z_G]›F£©CìÎíB£©M†Ls8Éž} ßkµ#¯¦kU'µ*¢½óRÜú à•±s ¶¿rv´Ñ½zøqã2‘ß]ß›ì|^yá6–g0ìŠKiš${VÍ›5bþ'/óîûßN¸dï>;Ž|ƒØNçpçµøäµû™üö7ld$”²à§yå…7Yí´åŸ£¯§yRt¿íÛ·{ì±~–eF§‡ kˆd ‰Cåä»CWàCd •µÀú],-Ë“7y"dí–ó'Ú‡'<Ð(ûXc›6±ì¹¶,†o¾…¸LHLæ- %Ö.ÉÿŽûÂnF®F dÁìÏaö×°µÚt”Sª _¿ ÏL‡–­Üµ;`i´h =„6Yðç xíxù…HKÛM’M8)Éáø8Ø`[­ ;ƒ&Àj*wûSÑÖ›@'¦¡¡/^ÍEa ;ýÙKy«V­Jyøá‡ÿYQQ‘pÏ=÷Œk×®]a”Å d­‡¯ Ã(þ›‹æ¯®Ž{¸ •;ußäá&ªQ+d7H …¢2]euÝëOóJ:ðÕgÐ-3r;ÛøëûÜtóm¼ûÍÁxÖõ¼ùôxºg¥’·ò† »é_/&)-âüRZ÷à®çßbô€žUø ‡ÃëºvízÎêÕ«í‘Åz" 0f+Ó:`‘2í ââVoi¿¯ (nð-·;ÙžŽn‰38 ÿ̰hí¹=í ¤dnÓ 2Ò䆶-ƒÍÛ#•<ʬNœ Ù«`{®Ü/i%(Cž­xåH-÷w^rçJÛ.‡@Bò7†5‘³â «½xå2=üÛ(ÒšCL9fG?OåHKEJ䬂m¾ýöþáŽý )¬RX=[þ£^Ë®’šÉjßTÀ–åòøcÝsTÕqú)s-^9·^”–ç Ydv†¬¦P’ ë–ÈÏw’Ç™»^¦òmq ´È”+ܲÖo¬¼Þ¿Š–oä›ÜŸ+XvÖÏSõzü©¿µ{”FSGÑÂB³GB˜ÀDàröÒƒÀ¶mÀ4Íê:h²cw¢aköÀfw%0ü¾ìÞ{ØYX¨âÂÞÅkÿCµ^?\wKX ().Æ&@bBB%o€pi+VüAA‰Czãf´n׆Sý>Ÿe‹³#·„Pbí»v¡yFò®öo•a= £RR¯ruK ²zO 9².D:²’õ`¡{| ‘Öˆ-î”ã®ë~àßf× +³¿…´„Ô4ÛS[à*àò~qfâ¸LÁ IŽwêö>žÊ^EÃkéj–ÙÂDméYmh›•VùÛp k–/ÇJÊ¢sÛf·®ŽßÃÂÂ#šÀ€ÈØŸ*,¢¡ŠŠhq JPx!ºËö÷~T…b‹amk(,ªÂ«Ò†ˆU µûº:QUЬÙ2Ù K ²ŸúáÀMñ†qòI¦1¶‘ëö ¬%ij=†¬8)ßä‘\Ájéõð_d9DϦ¢Ý£4š:„š=Ê®…E˜—¼ž>ò.­Ž<—ß~†Éÿ€Â5?ræQƒØtìæ½=†ø¿·;{SXx¨Öˆ€ï³ªUÑà›h‚ÂC‘œÌÎN#µ…"àSÃ0öFüP/àsdpë®X <ƒé-@‡\Ô7ÎL—)81Q×ÓÔ X_ÿÎùžõ IDAT 2)ߢLð 2®h‘Œk~ר=£ÑÔftºYÍ>Æ ¸¬œÜlr>~Ž& â™Ñ§F¾vÊÉۚ˦»“is¿¢¦©õò²ã›«52ð½ÖGÃ0Š€wö÷~ì'#3KÕtÙË€TàOàÆVÁ@×›Ò nH³‰5©û­IÝïjò­Md¨üÞÂÛŽÁž ^öPï (‡ƒÊ÷&‘ìÌû­c`B‹3Ü›mûm™}(ð*Ò:·iåSk UUÇG£Ñìg´°øöÎ3<ŠªmÀ÷lI„ЛÒ{U¬HÁÞ{Ç‚½*¾úÙ{CÅ ŠH³"EA^¤Azo „ôl²;;çûqfÜÙaSÔ„Ï}]çšÙÙ)gÊî<Ïyšâ˜ãÒ¤K¸äÝ×G3dÀ)œÓ.I~©i²²œ;|ÀÚŸ}˜ ·éó“R¿íÛ‡·ji;_nåµ­^ˆ €¶ýŸ@æëÙ†¬*¿ YÅ8·q½ú][Ëë~49@K+}lMx¢¢ÀåÃEpµ€»†võaû¯ðþËòê”§€,ZÀý£ ]*äl‡±#Á§ÿóã¸Åê4ÀŸ#ûž:¾b\°ò;øàý’3@•.·¬(ª ¼}óyí—`Ð'Æg¹žËwîÑs±ß ËZ5Yìu/,P¹G)U€ª%š)þxkµäÁ;n%öÀJ}è2ů¿métÎ;¡;ÝO9Áƒ/ k§¶ ¾î!–ïÿ'Yd+§pIM¡ÐŠÂ:`ÒÍéऋÔùÈz-¯K³€7Oˆöœ=­‘Ëýa›RQ(íé.©ŸðvÏWÁ¼íðÚÛ¦8™g ‡CaÈÙ¡„º¥íë¯üÒàNÓ†À¹aØ…_† K9žè|ÌÞó¶ÂeC¥b‘؆ –Ç:ýÔðR‹§ï¥­g7~ ÓWÁ9½.íXÙÿHBêX·'ùµ©Á µ<­ã4í5¤{íÉH™ÅJÀëE>ÅÕÂP(•€²X(Ž=B0²x×ãøÌäµ™oñÁŒ!<:¬WØj. gÇb®z9ËrbxòÍÏè×!•y½Â3Ÿ¾È¾€‹Ÿ>O²zŠÕ+ÐðpÀ\n¹³Æ°ã€aÀ½õ]Z‡’ÜÜš¤“h¹=U¥ÂrP‰‹–Öa€¿(TSÁÂÊPew´×© š5‡:^HJ•ߘ1óE…R0ŽÍŸ<†=ì=ÄxÁkFjE_žÖÕAýÈ*ÕÑù°}Ôk‡VC 0|]'ÖuÑnðFÉó…êfè@l#hPW®— ¯S~:¬ß&‹ÖmÞê—ßì{”éeÀWzRì®Ló¼£cå ŧÊÕæà¸öP/EÖɰú‡Ù å²ÊÈÍf^Ϧ^ø¨Î•‰.mT†ûü_ õÞÀçÈú{ eâsZ/ÔÀBQ‰(‘LQ9uÞF<üÖøºÿU¼tçÓ\Ð*í´Ph‚øuÊk,Ýä‘w¾àéÛÏàŒ>ÉßÙW¦NeÑï0¸[ÝJ; …âba݃c ÂZâv7àî(!Ãâ½®§êhmJƒUEt2€¸0ø&¸lÄ{¡¨Öý 3>ƒ_ƒØ¦ðà³Z6,‚q/J!zÀH8·/ŽÀ÷ŸA· Þ”ŒuOÀ'sC®;ÞÚpÍ8v:¸üþ3Œ{UV¨ŽBîó”kàÆ»¡cS¹M^:|öLyGVÿnÜz ¸ƒ°p.œ8º$Ã=7ÀƵ{ÒÖB\g¸c$4ðBÐq±ÝL~毂3®ƒknƒ6õ¥¢³;|ÿ|÷)t½.;7´]ß«!KÀ¬½°f$FÁ¶Ý¡H­îWÂwCÇfrYþa˜þŒò Hî÷ÜñÖÌ…vÁ‰m! ¦Áo@vNø^ÄAß‹¡uªüÜkœ– ¿-‚aøíprwy=7,€ÉŸÁï+6–¥ ôK08%Öàí,wÒkYâ.Ó=êeà;d]7¡¸ {¡=kªÎ¯D¡P(!„[ñ±(–€xcÔ¥‚ØdñãNŸ¢PLzb¨M ¾ïe‘±w…è "ùœ±"(bìÙÇ wÝů[ Âö2÷Ë$‰ñ³6(!v !Ž«ìk¢PDÀ^̓‡cx YP¯ÐxHïåß7r Ñ!Ú"§U¥µFˆö-…øzqä_bá!!n?Yˆg‘gÈeë¾¢+B4FˆñS¬…øþ=! ç|B\r†?ï)þ×¾m¡§×“û»ïQ!rý‘×›õš¼~×=áË!.ë'Äâƒòcî!.&Äáâ+Æ âŽÇ„ÈFþþÝÛ…x÷«£—oýAˆ¡7 Qh~^ð‰õ↱Bäè‘÷õãB´Cˆ³ï¢˜ÓB1ã%¹^kÛýiÝIˆ_×oÎKBôë.Äêò³^$D¡¹ã´-B ï-D‹*ð|™Ïü–ˆky…G*³€þÈ„µÍ–€´ìE#í-VñHå"¥P#TŒ…¢1€h†Ý÷ êžÀŒ÷ßcò/IˆCh€Ÿ´ƒZÝ$bj‡'žKj¸Ô¬¨n8kŸX…í<„ÜžÜHÁh0¹ÛõÀÓÉžÔŸ›x +gÿ@›n0ødù9c |ò&üºAö5º.œ|2üPè—ëè~›‘¹,hÀ®ðß)à7K@Ú _¼ iþðßûΕ0yä˜îQÇŸm;@j7¸óQHð:,š “¾†ls½sï€óΓ.Ov s`ËFÈÒÁoõG‡Ü4˜5 æÍ‚™3`ûaÛF~È®Cn€8PÓ&À§_H—$€“Ï…USaáÊÐfkÃ7S!Ï%]¼ô"Hi OÞôoÓaÉL˜82Lw¬³o‚ËBA!Yƒñ:,ú~œri:k0ÔJq¤’øó6ËeŸ<¿׿ ]›ÃÖEpa8£Ì] õZÁ3C-WÕxÞ´Š†˜ÖHsí>˜Œ¬Tߨ\Ëkk«Ò+åB¡8(±LQ鸒[óÂØÇI-ØÃè‡G³K@m‚hš·r¡¥¥‘›“¶Íº–qÄx+ÙN¯P” »2aW(¬8 /¡ÿãNÀû.wE‚§Ë¼&O¦ê$Tå². g|3 fφ‰ŸÂ¯« ;7´Ž,}?n/ì[cž€lS0ßñ<þìóË” ÷ÃÝWÀ—Â„ÅæÆè^h{ ´L”‹–w^#.Þ2×óÂygAœ-€àà¸eÜxlÏ·y—2WÁ#Cáòóáõ „ɦ³Ÿ‡ùsaéL˜ùÌœßΆ¡{årü/á­OCÛ}÷¼<ô¨ÐîôB8éZhVK~^õ=\?F ƒW?—côhpÚ úCûZ=n wÜ +ÓÌkáAÑ€ý«!ͼ'»×AVôëF6Œ¹Ö®‡ôµðØHÈðA»S E½*—=ê‚DÁܦA^LñÔIui#¯K %ìµvÛƒ»íJ†B¡¨”b¡¨´;ßr÷leg4ÀM—3»È\Í»ïNÀg¾T2~ÿ‘—æ®$±C;ºwnT¹W(J§8¥Â²RXn*p?0¥K”{ÈW \ÞÏê´¡ê*nààZØx:ô„ƒqÀ“Ê&ÂÙa— bRÁŠ·ŠòHç0Ͷ£ƒ`Ç&éïéös3Á¨T]¦P®ª]$\@ÛÓBg5ú*8«/,Ûü 0O¬Y{huN…N]#ìÐB—i[ìÅÈ‚B‡ìÝ!áü¼ë¥+? ú\ ¶Qþ"_h;W1ÿ$Fbއ—Þ‚S[Èe‡6Á˯@Q=hÑ:¶„ãÌŸ«¾‚sÚÀŸB´ùÿ&ŒbcôÉêÞvÜ.Ø™ºïÏ’•MüÀµƒ –©\¤ï‡#îÐî\sp%€ý›¡ uÛCã2VFê] -Re–®ô`Õ½Íë5 Ñàç¦ϤxRë¹]"Ý£† ݣ܄j_8ƒºUü…BQލt³ŠcŒ‹AWÞÏ +#{ÒÐû˜1ô¾°e ;ñþ§ÓðååQ¤ëÄ&Ö!Z*UÍÑœ££Ö“[¸¸±C”«ÑÓ)pQ¢.½}ª“Ba!Õ{p+´¾  Àѵ@_ûó¡n Ôï/·`}èÜØ±Ã¦œw|xý]xá³âc¬™hdúÕ5iн>´> ÆÍ„]…pzs½\˜9 û–~^z]{‡–E'ÃCoAŒ™×vó2(RiiÚyºŸ)¦¯SL‚t{6EæÂ‡!ª!̰°{b`Ù'°þnèÒ:Ÿ_üöÁ©§›+ÂãÁmëÏ_Ƽ`Ý΂YŸÀ²-p~ø¿·á¾;!/^x=ðË·p0½z A HpÃuu†%ÀØ o—‰yÌ^Ö®DXUW"¥¤­Ž¿B…¢JPþ. b¨SG)Š*I$ …=–²Rxó€/“ÝÚ“&y-jj0¬–!Žê*ÎhÀoŸËÌM}Î…k/…DÛ pÔ À'C¢\‡ÞÐù8(´»= ¹!°Ví‹j5†7Bý¨Pì[|D¬-^× 9›àµ§aO¶\ܪôï#¯¾Qo> ß-†¨¸Ð>¼ö? bÌï¢cÁc‹¨• íÚ@‹Т ø³á—eò»Ô¶põ­Ð©šçÞædèÔ v/43mÓñ½`à„8SÐ÷Dƒo?<öì53Oµé gž.ŸQŸþ¾_ Q^ûr^ ÎãMe&ÎÀa¢ XµNÎ}ž¹Þzv†ŽçÀwká§åpr;Ø·^}GVЍNcùÚÇÀçLjàŠêí|ƒŒ_2}Ùˆ"ô›´~§*{”BQ¨¢\B¸‘„¯¯ì¾»€35MÛQÙQÔhгRX‹õ?Û¸ra¼'vTŠN÷Xª¯2áÄt _ Í’aë*øá{¨ÛTVtöçÀ²éà‹ƒÞƒáÔ~‘Aß©) °î'ØsêŸç_õS!û0L~Ž?êÆAî^X:Wºî4뚬š÷JÅ¥Ù)0ü*èÑEÞCÛàû‰ðãLù9µ ôê.û¾ulÛ&çµ:ÐólH‰ßØtºu(þ¼w,4\p ôí¹aÞ48„úµåÝ_³öì‚nCà´¾2°{Û*˜û3œt*x4›±z±tE:þ¸ðèÕFnŸ¹¦| ¿Î”çœÐN>An·g%ü¾ܵ Ç9P7ôX8C¦ìµ¿å ¾ ¿¤ÀÒ0g´8.½ºµ’®U«¾ƒ)aó¶ðŠéÕ Žáí#^Î ’-Ä*à5d Œ"B– «Y ëWYUòa)Õ¥X(Ê!„œt$”U=" ,8þË/¿¼¿[·nÓo»í¶9åÜ|§iZni++ËêI¡p#“dàràÎv^W£Ç’5®ªDsQs” ‹¦]Æ-…Ú¡dP2P?¡»ÂR¬Jº¹ÌcNíëj„*Bëærû¶V_\„¬Á"×`…ÌHÑB9ƒ,ŠlÇñšý-+×PˆsC0(·ÎÉOhL\˜óV¬(€âúî3¿·¶µ<Ï,;˜½ß˜×'ÒÞªKm=žÇ+*’ëx#l_Ý0¯ÁÚB•ááÛ<]7`ð.ð;áÕºí †p4…BQ”b¡¨L>z›-¯”uŠª„ÓJát)– ŒŒÓè=¢Ž‡{’tx©ùbŠ¥H”×zÿ´/ÇêXåuœcÙwûñj¢d É8ú)¹.FeÀ†€±ùúÈ ¤`èHåÂR •r¡Püjâ߇¢zp2¨î2¤ÿ«BQpV϶+A -ppÅyqž˜§ê91Ö”K”x¢PTÑá¥#ÞË6È4Œ¥ÀËÀO„l_‘\£”[”BQ”b¡¨ bH+ÅBÎ EuÀi©°»>ÅWw´ô¸ZŒIѸ¬VOMt{R(ª3¬ôÁØ ÓòuŸ€iÀ‹ÀVä¯5’r¡~Å E)(ÅBQ\ Œ.æWr_Š¿J¤ú2–â= ¸¥¶‡±uuRª[=ŠŠ 2ÜkŽÕ1+Ëu¨&»,KÌx•/sÜÜuH4",4×°Ç](ÅB¡(*ݬâXŒ¾C)Šê‡f›ÚëSX¡¿{5ìv[üÑ4YU;êçˆŽŽ…èèÒ×û+XÁÞVJ ˆ­„sˆŠ…˜Ø÷³UNäaS‘f]Ê@>¡|ûï\¥¡U(Ê€R,ÇšsžÀ«•Ý…âob.슅x.·Ì(®>s¯Æénéü;Å‘ Ðüøq<ñX(›QE3åT˜°¾š JÉMWF ±œ2Zv™Úœs÷À3ÏËŒMÇhíá…ß`êOÐ<5 (;æ³8%ÛEß=nÆdê釃Æ+ÀuÀBy¼,…B¡P”¥X(Ž%^àA¤¥bE%÷E¡ø'DÊ e%-]Ü›cˆ×ŸÍ fôÛãfb–+´Õ¿‰è8¨[j%† K¹"…â m}{JY®#U¹€m¹b Q3hØâÂ+8Ão s{=Â1íøN—Á„²:µi5¨[ ’’Bû±ÒáFÂJ‹[š¢S\˜°åõïò@r $'ƒÇþ}¤>Dºv%-«É˜¿Öõ…pñ~4ŒÕþàH+úGH•1šp …½)ŠR𔾊BQnœŒ¬oq%g†W(ªÎX +;é43 øãwðò«Òè39ßã•¢Ó5†‡§¶Øò%\±Ò×H¡8* ä¡i{¨] 2·ÃÁCR-‹rƒÇ…¶|Se«%‹ì‘W·yWH®zlYª! ` LY0€¬F­¹¡0O®W$·€† ÀŸ »×K¡?Ò[1D×–Õ­ccÌu„ì›^$÷×ê$ðè°oä¡Âìo³. 9»aß³¢£ ;?|HHß:}%Ä`ç~¹pÅ@«.àppd"Ed7Uô\Ѓr? ¼IP˜ B˜õ:À5,j¢­A¦ïfyx%+HFPßLÙH7ÝBU=¬¤f^…¢BP űdpø¾²;¢Pü죗‘jYX®QÑ@:ð‘ïOÉÓwžµ×Åén²ÿ ¢Š¤œ7ß çõokxl¼6žþ>ŸgÁøYpÛ5àj| χsÎ Y"‚MáÞo`üèqêÁÝã`ÂL˜4&΄qß@Ÿî¬†T Œ„ñóáãYЭ\ïâ§á¿³àË9ðÅLøàKèØýèu~àò1ðȵòs0a<ÄGËïšv…w–ûùüøxtë²$´ƒ'¾‚OgÂW?Á„ÙÒ}*>6ÜZ²êõ#ßÀÇ?ÁeÙú¢ÁCæ5x1œwÜpÔM’}è0Þž_̆/„ñ àšëeÞ½ö#áãÙ0r´#Òîž(¯ÅåCL%× £¿†ñ?@Ë”£ûU1gÓs\œµÇÍãz~FPL^V"U/ËJaoZø”ÕB¡( Êb¡8V¤"kV¼²V(ª?¥ –â6§ë€´ÃA£ï³™ôþ.ß8*EpQ¢qlb*Ä·³Ï×ïðéjh{ôª+¿ß³ÒuhßÚ½kz'zž Ëaî|érÔ¾‡²ÒÁŸ÷ w_züüÔï }/‚öáš³!'ê@a ¼ž{5øâIXµ.žºÜAX±RZÁ—BûNpý ؼ#TæP qe–ÅvEÉÏfÅ’S ­«VBÃгüß³pñ¹ÝÆ~ ºBA:¬Z]N…«:CƒT¸ïF©øXC| dÇC÷Þà¿&M‘Ǩ;.¿2àÅ,¸d ỗw<ÐøBxûch–;‡C…pÂéÐí$äÀÖ èy"4 ‡£@´‚à©¢.ƒ)ÓÀ;Î?¼Û Û¨9â³› aì/_æ þæ SÊúÕUw*%Í+ŠP űâl 60£²;¢P”v¯~ç¼aÞdæ×øƒë/>`ˆ‹÷{X_HÍ º¼þ€tQò›c 3þ.éW †û@K®=áó©r¤¿ëÙ2ðÚ: €Dà—ïÀßnE‡áæápÝ>~Y©máÒËÁkÞŽB z ^xüðá}0z,Ô=nºÈ„1×à¾0ô øq-Ôï7\>ô ü÷!û±ü¼t\z)äÊ7hà0ŸåÒu˜Ž,ƺÐ]Ï•cQU…¢BPŠ…âXq° 9R¤PTgD„f8šNx¸®=l÷0Ó€ï&çéé§ïÕxî°‡¬ƒ{”Ë Á4øbìÑ!}-Ì^#¿kÔ 6½ ë÷Als8o€\Èy@~ü Ú÷‡ V~‹æÊhßjøôyúœ$c)~hØ F< u¼0y ¼ðš´´ï -¢aß68ç\í[ÃúU²Í»Ho{»(i¾|s¾Hzâ[±›~ƒ‰äçí`ïKÒ°ôï-ïéê…×O>ìÝZmhÑ,ü-ì6-ƒ•» ñxèØ\u᤾ á«é“/Óø ¢ÚÂÙ?,[ ]ùƒa×:ÈõC£nÜ«V+ š´€ãÎqYyÐz¶•ç¯ ˜µr¨¾Ï¡Ù.úíõðx†îÛ§K‘ ÅzBvV˜»nköbxöÁgS(% \¡Ç‚ ðÊ JQ³p*–á%$¼ø‘ÿµVúJ7ÒÛýà@FPty,CïöMž;æ‰dÁ…5Ù=Êå†Ü}P&ô(²ú]àðåOÐíZéRÔ<Ý›BÖzøa>Üx£\õ¿È«íA^éûÌ´«6‰Øå ííO0•3@³^ðþ¬ðþüà‰’«äî ã²2{iæ~5¹üpº¬| <˜÷O‹–ŸÞ'›…@‚'\±py›`þ/pÊÕ26eNlëaÃàie툓JQððÇáç¢ à…¤]°l%\~:œÞ¼=¡(Þ›%ãFιêvÿØ»!äV0ïÓÖ"YIû‹<€0¶˽„”ˆ"¤zéw4»‚aptÅm¨™¿H…¢ÜQŠ…âXШ…ômU(j R¤±)–YÊE‘ùÙR(ì¡Öèg2=í®EÁ^ÃpÜy×ã):m£©™¢ŒÐ@ؤW§'ûê)u´íw5†8 ¦}‡B=ȶÖ.  q²ül˜Þ+šKfúxœtºF>¾(¿غ þ;¢âåuŽŠ‚fmaËÏR)˨½z1iZ]¡cMùVm€è!¹!¤&ÃÒG'øn2Üy9t9N®µ]0kl(‚X»ä¯I…GäÃ+c!?hZ…thÒ´™.c÷Ï}7œ4Ⴓäÿ}¥Œ=p{!ç˜ø ¬×á͇aØ=0}Ù$¯|íhX6–eHaþ†×àœóÁµSŠ ±ÎÎÜ”à IDAT™½0³&i‘4 ´ƒpè üX»&¾.“š&5…W¿‚“Žƒ9Ÿ…ž pd,üw„;ƒðÁôÿ†¿±5»àP>4NÂÍðÖyy»ƒwï¶ÂÛñ; ~ß§´”ÛΞ7ÂæÃн™\6g©tƒ:꼫(¦Uov®ÆÓGÜ,.ÔýÈ$ ¿G·PøW*")VUËjI±¨Q¿D…¢"P1ŠŠ& èü£9*Õ gI1«l™Õ,×§"BBMR±Î³µ\ÇgŸ9ý˜±_7V<ž¡ûúïõ03§š×Ó ßšA·} ÒaöBÀ 1Q°ñ7ذXÆ=Ìÿ¶fA£á½I0àxò¸¸ÁäiP¤K“®P1Iðýðóßžz.‚E[!µ ¼>†‡Q`Ì=P7V.Žì$rä´s?¸ïVðºÃûýç阊SŒ¦L‚,ú€—ÇÉT±ïO‡ N†ÀØUŒë‘˜ú5—¬}±g!,YZWÓL«Ì^øb*à‘ã`Ľpåøè#hÙ6.‡‚|ÁìeæÆ,Ù;×Áæ?Ìe…ðãôêc­Ð`‡n;èeðÁâB}21ȯHÛ–ù›³ÿάù|[+DþN-¥Â*Ù¨\¡Š¿‰R,MÐX\ÙQ(Ê‘HÁÛv‹…}¤Ôrò"4»àc :GE»¾_R¨orÀຶZI1«“‚!‚àó?0 È'Ý mŒ!X ­ ¾BynQÀ‚q°ã°\¾`¤!Gë ~ƒGïƒ-éÐk¼÷Üv‘,:7ö:Xf ê¾<ÈËÃÁýðü –m·s;Àè«á·Mж¼ÿ5Üv5è™ðÜ}0ù‡Pü‡…Øýl= õÚÀÝwA”ò}PX`;_äùúò P6?ó`ÈM0~œÙv¯…ûî€m" ó`óO°!]^ƒ _J§9—y"ø @+‚éwÁ‡ßÈàì'^…W^‡& 0o"Œ~*Õ¶n:äø sìÛ*­4«VÈþî]«¶Wýø ü^ÍpÓo‹÷s™~Á\à`7ò·f)òÖo+‡£wùm¹°ÇZ9:³›B¡(Õ饨ž´Gfã8XXÉ}Q(Ê ga<{`¶‡PXq”96çcÌùXscÎÇØ¾³¦ä8}k G#·+ùá$êI¨.îQ®hˆ«Á,ðA\*¸t(È Å%DÕ‚˜X(:Ef ħ‚Ǿ430Û$$¶ƒó‡B½$YÏbödض٠‘× ¶hþÐqt V}ùQYYÕN½:6„ì4Xþ#lØ(÷QÜ›1¾+to»`Ë ˆ© Á<©,YÛĥʧ ÿPhì»ÎÉ2;9 öm‚%³à@ÆÑ ŒÄ¥€×¹i¡ˆž?a@AF¨ ÃqÁY=€"Øð+,›'ÅeË}ÊÄÕl×Å ±u¤«UAN•¦äÃÓG<Ì÷éEÀF`²Æ¸Ž¼–k“ÓÊZæ#dM´oÛ³·YƒN«E¿: EÕ@)ŠŠæjàEà`[%÷E¡(/ìÿöj½nGóR¼‚Q\³+–‚‘t:÷‰ñDJ rn¼¨Ù£¬þiŽyç÷e]n}°­ã%Üþ^–ãØØ¬R†¥Ø[ÛXë—tç2«¿v5´4"í«¸å–½ BçR–íŠ;FUAƒ=~øÏ7ÿÍ1ð ±Xì!ÜõЮP×ì E€p¥Âº‚ÎÔÑJ©P(þUÝð©¨þ\4ÞÇLö¨PÔ"‰mÅ5§°b :šs]Ñ~àÀ݈ÿ*:ût/¢ êTõv÷­H®\Ú_\n}g· ·mIû³lMöDÀ¥amã²}.ë±­þFøK:^qçï\n‹‡ðüceé[UT*4(ðI–›ëÓ`®ÏÈÖeÅÈ8 ?!W¦B.‡ÎV@(#”¥\8 {ªY¥P(ÿ¥X(*š[‘Ò¡þ¤5g€giŠE¤fOq)—¾@úï4 wE‘‘4-O‹Ñ„›Ñ·Š˜SÔLEç—<¸9ÍÃÙA=Û`ð °p·&+žÂR* lÍ®pØ »RaY*œ¿5K¡Wï,…â/¢ EEó2•æäÊîˆBQDr.±OíÊAqJE$ F$¥#ˆ±Ým Í*I‹|O ·A‹(ªæè³BQ4Øç‡Ñ‡½Ü{Ø`kÀØ ,@¦‘Í&d¥°+N…ÂR&ìÙv…ÂY¯BÕ­P(Ê¥X(*’Xà`)ðS%÷E¡¨ ìBŠ}D4’ËSqÊD$夠´Hß©q“ò´:{u7]£µÕ?{ÙHQÓªJbaçJä$H(ëRq£²¬S\߬¨–è[Ö~U74 ø(ËÍ-é?sM·§ÿé„»%¢¸I¡p*Nå]) E9¡^?ŠŠ¤p#0UOñïÃYóÂr¯ˆd½pZ2œ kêTJ@¦ÓÜ¥Cþò"‘ôM®+¦båU<ˆm ½ûArÿDƒ{'ÀØÿƒ¡ÃaÐp¸ðJh×Ì„€^ýï©i:Œ:ìå¾CB¬÷:R)ØìC*ÌVÊØ’”Šâb)ìEïìîNÅUÕV(å@UÏ'¢¨Þ$"Ÿ±C•Ý…¢’„’›¶yû27áö‚¶y«l-Æ1µZ4ò·ö °sžOïõë~­ñµ½ŒL Ð8Šš)B„»y º ÂS½Ú¿÷»äwn·,¦ç7åM{$|IÛÛõD)Öj„§¿-2Wö8^·ÖƱ~8ï)Y´/p>ørbáâk¡ß0r<þ¬t4­Žh ðu¶‹ÑG`K àG^?RQèôæ Eæ#¯J¤Ô±ÎÊÙvë„=}¬31‚ª¦­PTJ±PT$ñH¡)»²;¢PT"N—(+Á§N¸‚áL;k/ÚUÖcn³ 8X$D»W³=¾Ëw%>˜¤qCí ”™k‚8eÅ 4éç ƒ87ìZó¿–⦠y뵇¾ v,¤ï‚ùSdUnqôÇŽ´ý‚)›~ ó<Ðú\8ódfüé°k»ÙO>µb%N¸:´‚ü Xò#ìÝ!—û58ïùÏùúxùùtlÕà½{ Ó rÈ&@õ²Z˜}]êÓsØÍÌÝ@Zb5)æ#S*ÇÝAH«÷Ç„Šà9SÆ®PØ3=Eʬ¦²=)ŒR,I4òU™_ÙQ(ªvK„,V¸°Ó-ÊCñÖ »›‡•:ÓnɈ6·[ìÞ0zÞ’Në©yïè“âl=ª®`гðøÝP?Ñ\„µ·ÀÝWÂŽt¸þM¸÷zHŠm—ö$üç^˜ñCÉ;7€+Þ€ûnß>ýIxö~˜ú*!WŒ{C¼ùJ½ã.xö˜üsønƒ@|Gxúur&xM3EÎøè)÷>º`÷6ø=–®’OÌ2Ýš“«‹b¡Az^ËôðFV|¡ç#­ÒMög ù|Ö+ë‘ @žvRœu(Š+rg¹ *·'…âQ“BÀUË»» ²;¢PT"ÅVØ-vEÁrõ°Ró<[Ë1[®­åÙ¦–ÿy:0˜9³@ßwÎ^‘iÒ«Ûˆ·Ðéjxþa¨ß¾ >;r ËYpë-Ðþ\¸ÿ.HІ OÃ7ìP¿5Üy›´§'fŠ åY0ònHбm¿굆»n5·7‹Íáà xân˜»Rއ1ã c+XþNæÁ{†÷‡Üíðôý0n$4„¯Â©ý@ ÂûwµgÃêÿIËÅi7ÃC×ÈíÏ“ÿ¨Õáím>_Ÿe¹é·ÇÅs™z~¾‹€iÀ7À$à[¤ ŸùÜZ±óÇŠòh i¹°k;+j;SÉ:c* E£,ŠŠ$ ùúS·ŠpœîQ–¿]ñ°»E9Sd)ö"ÆÜf+p GˆŽ¯dé]¿Ís%ŽIKj2{Tu»Ü\p“tš÷!܇5·yೱг;ÌX‡·Á´Ipßh)änÎ…¾ŸC”§t¥*ZÈí§O–ÛlʆÓ&†o¯ûÂÕCawü°>œ=ZÉ'B"Ðà2¸èTÈß×÷€E¹`¼ ¾©0âBz>,œ…Yò_ÓÚ]/¼õâà»7áÝ·äMUGƒÕ>x&Ãô|]7d–§e„ªfÛ]™œñV[ Œ^žnv ŸVûo ¤Àìêôd+Õ¥X(*7!W…Bq4v¡Çr²æ]-ÊîîQÖø k½hsË][t£Û•i´û<×ãSWç+¸ª‹aðÆBû€‹¦K15Xõ!ܸ |{`ýopomz|ô5ÔJ…Öí¥24J<ÞhØ<î Cû‡æö­ÚC¼Ò¬í5yÇf~éÒŠ‘¶–-à–[ŠÇ 39Ð]]ûbáæñp£#ÛËu:t†XäéòážÑÐ8~ú!¨ªüæÖ K‡—¸y'ÇàHPOC>s;)–RQœBQh[÷wàààuà*d,†õnqº<Ù …BqŒ©ÊOŠêe¬–¸–B¡°g©±{Ï—dÁp6»+•Ý¥ÊgíÙ£"ëËìø¡@ïõË­Áˆ:nîIÒ©ç¥ê‹d.$š¯/‘Í-( ~œ,EË®Ãà•—¡u=ùUÖ!wQ?µtkE MÁ+¯½}ƒºGoŸŸºfn `z:¯£fºNyjÁ©gG\P”;wÀŽ]`è¡m£úÀÉm!xF?)€¼e»DÇMz†MËq1*Cãw0Y1{Ò=O'<èÚWÌÔ™2VGÆ Ý |¼\t´BôíJTý'X¡¨±(ÅBQ‘X¯ûR† G»GAxÖ(Ë‚áÌeW,ì F¬ãs G»Gmä Ñá?™z·éùîøG“4®¬­‡‡™W5 CŽê£AíÚfÊX öyðÚý¾ ûH¥`étxé 8´rO…ùÿ-}ÿ®Xx»Ü~Ù·ðÒk¾rœÛ›¨u;y½,›RJãðïÿì·KÞÅKàÎGÀÐ@÷AãÓ``X:YŠ×–âÜË—‚k#䩚ol³¯ë aL†‡iyº0äsµ ˆmÕ·+ öš…ŽÏÎàlËâýp R¹x¸ yçA¹=)U†êþ¥¨ÞTeñD¡¨ŠØ³F9¼-%ÂÜm/"–O(È;Rp·Øm5…ô}Ÿ¾ÞÜ|Mš®Ûçaµ]À­Jh€¿–®‘ú_õ=Œƒ;FÀgC÷VР¶\Ïr˜?Á—@¢ pË«Wn4H m?ÏÜþ®K –c{ô½N:M.ë{#œÑô,X·‚fÕ<Vÿ¹4î ÇŪ¥°3† …‹.‚Ž=Ây¼A˜ü|5ô*èQªA¦Ïvsú)yú!f#kPìC^{Ò\ŸIû3j=»ö*ÚÖ3nÕŸ ÜŠt‡z œ­PT9ªâø‡¢æ`JU(e'Rí kÞ*ªçÌ,É-ª,qVzÚƒÀ7%_ïõs¡«þmµ4HRÇJyZU0€ioÃ…}¡Ç0i¤¹¡OgfÂûãÀs! íƒFBê©Ð°#´n"·?îtxàQ˜’#Ï<ʬzDî‡%ËáÚÞ0ð~ø¦OøöÍûÂÃ.CÞ™„æ0~:üot>’c`Îañ¯pÙÃr›¨ØóL˜ w^¯}þ º@Ǧpx3LþFÞ±(äm<Þy8'-‚¢Œª¡ì™ÃESr\Œ=¢±ª(X¬áh·'»U¢¤X ¿£9ÓÇZÁÙn¤Å¢92í&à‹Š?a…BQVÜ•ÝE¦ p>ð*3”BñO±»z8³ÞØ €9›ÓuÊÞœë dJÏ>!ô……"éûuS è ü²7„µ¿Â¢åФ9äo…¹ÓaÅbˆjMœÛg˜Û§Áo« v,ZîdèÔüG`ög0j$äëÚ êañlXÿ,_¾y¬Ž] 6(-ÏßËׄb(4@Ô…¦õ`ó ˜ó­Œý¨ì{ ÁÆ"¸/ÝË茠±?(v ãuþ@Z,W'+ErA S§KT¤´±öŒhÖóþ+ÐiµXì®ðóV(e¢²ÿ¢5›Ë7öȃ …⟡ٚË6ïAŠÛnsÞƒ÷öbŽÉ›Í²NÄ”Ð¢Íæê=\ÐæÂxötªNG+ÕiU°`èÈ^¶è ® ì[&k¢‘ýó-O‚Ä(8¸ö”uëw„ü}%EYòÌ ‘êUŒ¹¬8þ$¨UÌö™Y¡ Ø)ÇAãÆ göu².BŽkÖÁÜo“®š E9°}…\f}o! ÉÄR¹olMêIodyx#Ëà`Ð8 ¬@¦1¶g{²„Ò‚³ g ŠHU³íÊu"08è¬6¯P(*¥X(*’ËÖŠŽ@Z%÷E¡¨ hŽy»‚á6ç=Žf)%)±Žeּǜ¶z&¹´Ô‘Iî¨ ©ª¸GY tìX I-µËʯ劰n$ʺ½¸m©y¥½]­ý–uýÊÂt{š•çâÉ åEA°X d9æ§$·'»U"RQ;K¡(K¶§HkÉf`82ŽC¡PT"ÊJQ‘´"­ÊJ¡("Ú+É=ÊútLóÎBcG€…‚À<ŸQwvÛSOÓh-*_ ¶T*«jŽ3ÖúO%À®Š•…²n_Z?ŠÛoY×?Ö˜jë¶"‘îáÉ#{tcð3°žÛS¡9oov7({0¶]¹°§“-®bviÙž²EôRÊï(Š¿ƒR,åÉñÀÉ@3 )pp*°é@P¨C(°O¡Pü3œYלʅ]±p*ÅÅ`DR4@þf{Eì”/ ‚ÝÈú&û)e‰£°êNØ›=íq$ë˜Óåéï<=u€o‘.Q§£âúŠJA¹B)Ê“| е„u@Y+Šc…]P3󥥨´ÜÞ@ û€ûƒ"æë<’7ù½t2H®ÊÉŠø x3ÓÍméó ƒ™ÆÑnOö"Œ%µ’Üž"ÅQDÊöôw)DºÝÞtBfŒ þÃ}*Š¿ˆR,åŽL3‰uÀ3¨@n…¢¢±Ô‹TÿÂàèŒHÁÜ¥Õ¾°ö› ìBö:¿QçË\-Ö#ÜtŽD©ò˜Ušù×¥¹—,Ê6ÄïÈàìÝ„+ Å)ÎJÙN…Âîþ)0»¸lO— dѼQæ±–Ó~ EQŠ…¢¼9 ’#|÷!ðý±íŽB¡àh“HÁÝ¥µHAßN!ñ0°+_`Ì.uçå{ÜMÜÐ*ª dR„Ð`oî?äåÑ ƒmº± ©P¬C¦lµg{ú»ÁÙ‘jS”5ÛÓ?a 2è `)ª¾…BqLQŠ…¢¼ñ!s‹ŸìXžÜ‹< űÅõ`蜖 û¼=°Ö9_œ«HÁr?°_Ј›”'jï xµNQ†¬}¡¨<4(ðQ¦‡Òó}Áì,A¶C„WÍv*V³/wÆUX…òœ …UýÃn¡¨È(œ_ÀH—¨¬ <–B¡°¡ EEà.&üùš |P9ÝQ(6J«}ñW-Îï¬}å; ÈYYd$OÍsÅ \ôŒ¸•{ԱŴ-È׸)ÍÍÛÙÁ@¶ÁïÀ`!Kƒe(©…ÓJQ\MŠâ\çŽEX¿ažÛUÈ”çSÍ~)Š F§A]äˆQós™ ê§Jë‘B¡°ÐSgæ({zZ¯­ÙSÓÚÓÓ:³FÅÚÖ±ª~'Ý€ö§Åº£ŸNrF|…ž£ÂBƒ½~x鈇r‚ø„Ø‹Ìö´‡Ë’¥DÊðä#<ŬÓÕ)RålA¸’ùO²=…BnGf",–Ù³gwÿí·ßõíÛ÷›3Î8c}yÛì>Ô4Me:T(PŠ…¢âÜdÎ/ÎAx*Šªƒfk.ÛÔ®\¸ )ÖÔRJJIkŸZÛ6zÅiZó+=ŒN Ð$ •š¶"ÐÀ0àÃ,7/f ¶Œ\`2+_R°,öii5)„gyrÖ¤(ïlOG!„p !þÐ4­MékW,Bˆuš¦u×4Me R(/ÊžµDQóø©XHW¥TT?Ôoôß}TYãh·(·mÞ.HZÍ4í©EcͶ,;CB´û0'Ð}NVûÑ$7×Öщq¡ž´òBƒÿåkŒ>âfN6#•Š#„ß/§uбpÆMØ]ì.OöØ›HÙyG‹*pßeFÓ´*Ñ…¢ªp,,N³{iÇVV”ꤋ١z!ÍÅêÞV]Jœ>ù¥­¯¨žØ]£ì ËráBZì.R‘Ü£"¹E9­Vq½>@ÛÓcÜŒI18#Á|¬ÔÓõ÷Ð =c3<ü77HŽ!öË‘éc-¥ÏîödM#ev²‹á…î"Y)œŠ ½ƒ¦Åbµ¦i#¯`Á7_°t÷zö»„þ]‡}›·o-}>—6§žË¹}:ðC~–½•ÅB¡T¤Å©PØ_ZÎå‘N%„V_2 Çl¤ð°)L(ª&N! ¸Ô¤öyc @(Ž)Ö}5Ý_ëØ@¾/,ßy{Ó‰lÁ°,1„×3ˆF*%‰ÈˆŒŸ ƒökMo©íá$]¹GýU4ÐLÌt36S°9 çk€ È z{ÕìÒªg[Ö g@¶e¡Ð Ô·¦PeþüÌ/c¿Ïâøk˜ýóG´Jå9¼q?|?Žz›³ÿ¹b¡P(lT„báT슃5¢Q’’¡¨þhÈÒ dFkäSQ5qº<9S“Ú×s^M)5 §uÊþßl ‘nŽV*¬yg@¯}´;Ö\惑ÏÒ·€ß'Dû׳ôßç»j=XÇÅMut\Ê=ªdÌ»ó›Æö0«@" Ä­D¦ ÙBጡ°g¿g–bQ\‘D»¥¢JáuË.m_ù%Ï0„îú§ÀáryåKI½•Šr§¢,v3º5ïÚŽé\”ðYÓ8Œ|át¨ìŽ(ÊŒÓ?Ú®4d!둸‰,PT9áBñ·±[¥ Ǽsj^;-‘¬–Rq ò9ú ù\Å«=[F[m¾É÷xŸJÒ;^¨'+¤àµL/ogëäúAd¶§]„êIØ §»“3pÛ®L8³=EJ3\uˆh˜†©Ô.Ìbü#cvΜß)éÏUŽ6rùåûïXºzyAhÔ¼g]0ˆãëzeÏŠjOy*Nÿ\Kq°F8‡^Û@) EÕÅi­X Ü µ[9Œb¶QTo"Y/œÖçHnQÅY/|@SàV¤"ñ$˜ûŠ6?϶þX ÷ZR¨5¾6ÑÃ):õ¼¨§ þTñ>Író|&lðòUíöTšRaÏödWK²RØû¡ªÞ ´¢Ù•[p!ëd.`Ú]§5gL@þ¿[S»õÂ@Ù£|ÀqÀƒ@-àydB‡Zæú±„»UmÒr ÑáÍl½ë¬WâÉWÔâù7?m¬*„§{ø6_ò:­@*d–WZ …3}lq5)*+ÛS¹ ¹4в®º›5?Nâí©_ðÖ×72ê²l#šn`á'ÿቯæÐxЃL{ûZ×6˜òÎÜöØûÜwû,šÿ,ñ*C¡(å­XWdIÔOÔ¸¹NLÕàoI¡Pà#(øl9:¡Œ@N”KTÍÆScW(­fÅ_X©¥Xøîÿ‡|~BºëÔB¦¡¶[Bn´¹ÏåÀÎ-£ûµi´›˜ëqNÑéçèUMGƒ,žÏðða®AFP?ˆT(vÙí©$¥ÂOx,…]¡pZ)Ži¶§r'è'اž}š‰¿^Ã+Iªjƒ IDAT<Îþ³©ï¶;X?e>…É-÷Ê(º5MàêŸæÛÙs˜ºq k÷æä&*ÿˆBQÊ[±pº@y̦n!À¯CŒNuû{R(þÕè†òwl¥ufв[05—HîQ–ði¯wa)ÈtÓo™ëݬeaW*ìY£"Õ¿8̶Ï*ÐOøµPkpWm÷$¨_ÓÝ£Ì]_ç¸yæ¬óëùÀ:³åª+áty²”‹"WÍvZ)ŠS(ì.OÕòJE‚”WðÖ3¹zôgÜñò¾¾0 —šË…Q”Iú‘ š4=›î-BºS9­áq|»j¾*±¡BQ6ÊK±(®j«U±ÕZ¦P(ª/.B”íB‡ÝßÞ…R.þ ”–=Ê@ ¬gã€=À½À~ ™-βRؽ¥,ë…læ¢ýs™nÓóÝ &ÁUµ‚!Ǭš‚y5×ÊlOÓóu!d‘»•@á …e…()ŽÂž>Öy‹ËöT=­Å0ìÎ'ùtê~zoïÄ]‰Ûc^f=ˆèx]‰D…yh ta 4M¹n+e¦"¼-åÂr™°Š'Uf•o…BQ>X…Ð,Ë…5hP\=EÍÆnµ²[*|ÀùÀH…àZàBVŒ¤r‘yω0µæóÌæCÆ÷,¦oð7^› ^¸ßÃ*5çéÓàˆÏòÐo¯Æ´|ý€™ÀOÈš@…Èë–gkÖµ²·SBï˜ÞŒãÞäÞdËêZm›òý1;Öh½’ È–V~îëšKÚÙ™9egvÏï<åüüÅC¸ó–+)ޝææÿ»ƒ:PlÔ¬<¬/ÿ˜ùåž5îWññâ…h¹ÅÚ­Þ‚i´¥°ðº@¹ƒ W\ˆ°„ÌGÅy–]«…N“¨ð®UÒY†xÂö‘gaçऒ}8'¦B¡ù š|ÝA²w`ìý¿GDqfí?°àWÃÆ†Ã×)üy£NIæÞ}Éz¿X«rÈ몌H•i…³Ð"œ>вu?¥Û¼¢¢‘æ¢Â®U¨5kEf“Ò‚¡“.æš³'cׯ±mÀ¶@ÉbÊC‰m˜ÇŸ®ý+«"Äc5ÝÜŒ3uWên){TjjZwóf‹JÐ<°;HSp· ÌVÍ›{œZÎçêuåº.&c‚É[±£Þ‘ „M¸£Jç?µL£'ŽbMBÂK±=éc½.NéÖ¤H·È]Gí¡ŸHˆ3þþ££ýé•Õü¬Ò}¸Úû|»rFwÜ6Іs÷+o2ñÅÿ1»lQæÛÀÝ9öÔ“’/AÛ‚ðchËÁ~êÂxÞt³>Äb!™Ž›J§iÀ˜úÜ›-ž-tfrp¬gàiߛܟÌk´ÍìQ鬚†×‚á4»ë_øqÖŨ´aùËacüÇQµëïó5.-4(Ú’ô¼ƒì7ëUn¨R˜5"À|`NIj¶§»&…תÓ)³=µŽÎÈýObd ïÚû íÝ|Ÿš[Ê”s/gʯ› tnv¤Å"Õr!ËËBf㮨ðºB¸CíŠtÎ^ò¼×Zö(h.4Ò­â.[”wÍ ¯q­ õÕ¦µÛuUÖ藴еÅ6Sr­ö·§%[½8ÿ¨ô1­>áX'¾Çɘå¶Ë»r¶ëò”N`ÄÙÚe¬%Aáµe¼ P¥£LVÊØF<ìè•·½– yø!óñ>Ï^k…Š]“þÀ“À@àxàÃŽK]óBIy/ÀðŠˆT‘á®yáÎì‡h Dö'¯3X9;nŽŸZ΀Sêuõ¯ÅôÏZ:©ÑøWµM¹™¨ÂÉpU†#¼¢"UH¤ÆRx­^‘å]½<5ŽÂý .*’Ôµ;âÂuuuy¶m+ùùù­^ß¶mEQ”RAÈTv„°ðÎH‰°„ÎCºgZDÅ®Ë8`Π÷`Þ6ŽOµ^x-îk¯{”wœNX¤Æ^xÿºŒu@¥a3ðÙc܇µëÅù*WähìœáuRN½U§ò÷*…13ÌÆé¯O{¶g=ŠT+Eª¨h)ŽÂëú”ñ(ŠbÙ¶}!Κ(mÞ¦^xaÀ£>úPÏž=ßzñÅïj¥*N.Y»G’ìÈ•·½3™nJJA2›T÷§ÔŒp®ÁÁ8kT,~‰³šööâµ^xgн‡ÑR€wº,R©û‚ÉóëÊMkôõUÖno„õÐõÅGçì`÷(–ÅáÖJOÔ'ì„Í*`&°G(¤£HµX¤ÆQxc)Ò‰ ¯…"ãÝžÒ¡(ʶ„ìÏá¿›EygåwA¶ƒé •o!ƒAÈl¼Ïtê$‚°ë0'•ìgÀ¯€Í?áî@75¸Û;ÌO îN',Z[±Û+.ÜÕ»?–ÏŒãNXÏÀSs}\[œ`¨»þY[ ¿ˆ™p_µÎ=µ«D5ÍÝž\aÐRìDº•³Ó¥õŠ‰Î¹Eûñ`"ð8NüЪö¬Œ d ;#¬u BçA\wmÎîÃYüîrœØ~-w{E†wÀüc׿±µÀXl6`Ð3õ‰qï7ªÅW*œ_`’÷sÝ£’5ž^¯rCµÂg#†LþP•¬»7}ì¶´Sƒ³½‹Úy­é,"*~1à·8úVàLœ>¡ÚBX¤s‡!ŸtnPòìw^þü¸¸gàÕVxÁîúÙjr¿;ÜO- ŒÔìH©ëa¸Ö‹ùÀºM¦µû63ê…zÍ]±ÍQ?Õ=JUq¸©ÒÇSõQÛZ‰³ÈÝÚdù®åa[kRl+ŽÂm»Wl‰ Ø1¬.^Çù,ooßêBǧ--© lB礥ÉyÎ;'>œï®®nÛeµ”=Ê7.À»5 Fjp·+8‚@%ð%°rFÌÂúžëSþ\”` ›¼v[(±àÉj›ªmÖ‰`°Ç አ×"áþ¿=Á٩٩¢¢3f{êh¼‹#¦ÿ†c½˜Ñ®µ„ެ†-‚ ´„üppŽßùަ¥ìQîßÖ¼[é¶@òÜÕ@EÂfð#u‰±ï5ªE—(\Z`lÉ=*)y> «ÜP©ðIÄLàdzšƒãöôc¹kÉJaÒäú”.Ž¢Se{êÀü<Š#HAHƒ A!!œ´OÎʼn«Ø™¤ ænÉ‚±=ÁÝ-­àíÍ5X³Ú°öøÃf†¿Ú ®/²84Çn^ÖÄá_U:Õ™Älk N¶§µ4YZZÂk±H·jv:+…×åI‚³w>aàRà œx‹‹p>ARa!‚ ¤NÎ^h§z¤ îöþUÙ:kTkÁÝ®Á+:Üךܣ>Ê>‹šãŽÞ@ÿ³r}ü¥8A??–³ÈÝíÕ6ËF-Žïýb ‘&A‘š*6ÂÖ.O^+E"¥^^—'ÉöÔ1Xüx'ýì³í[A蘈°A¼ûqÒÊþx¹}«´ž=Êk½hiõn¯ÈðZ+ܼ½Â#€³z÷J j„·8Î@ó[ š¦lO©¢¢µ8Š[[NÒÅQH¶§ŽÃ“À¾À]8™¾´ou¡ã!ÂBApÑpb*N£ãˆ /­ej)¸;ÝÊÝ­mqaHþÿ°|aîqÕfÆà,n÷5κMV­ ‹Ôàl¯ÈI'($ÛSÇäj`œx‹É@CûVG:",Ap~nÁ‰§8ø_ûV§UZË•.¸Û»æCK wÀï®wÇɈåÊ÷p2E’›»Æ†{^K.PÞ8ŠÍŒd{Ê<ªqâ-Þþ€“-J„$",A€«p|È/$3üÇ[Ë•j¹ðn-­ØíAAáKnZrkð”g¤œçŠŠTq‘œí-S²=e&Ÿã¬ërŽ;Üí[Aè8ˆ°AÎÁY«â*œàÔL¢µìQéD†ÖWðvE@:aá^×{¾{Žw¼–¬^Aáf{j)–BèØü'õì¿qâ-Ö¶ou¡c ÂBa׿pà>œÒ=í\—ŸJKÙ£R…EKÁÝ^A£)€Û+,Tš W ÄÙÚ*]úØ–¬©)d…ÌÀ.>Á‰K:糄]‚ ».cpÖ§xǵÃhßêülÒ¹G¥ îNéD†+,\kEª°ðZ,Rc5R³OI¶§ÎÉàbàà78CviDX‚ ìšô‰¥X\€3ÓÞYðÒS]£RFêÊÝ®¨ˆÑÜZ¡¥\?ÕÊëê”ú7 +Eçà=àNàÀWÉMvYDX‚ ìzèÀ?|àX ª}«³ÃðfUJ înÉ‚¡{þz­*é]¬\áNL¸[júX Îî<Ø8¢bo—Â#ÍíZ#AhGDX‚ ìz\œ œ,kçºìhZr‚­×0p„D‚&aáé‚ÂÝk¤ºU¥‹£AÑ9‰—à¬Øþ/œ”Í‚°K"ÂBa×b"N¨Û€·Ú·*;•t‹–„‚+TÒ ‹T±šÖV²=íz,‰Szøx&Í1Þìe‚Ð)a!‚°ëÐxgàsc;×¥=H—=Êýß *Ž8H'*¼®Pé²N¥‹¡ð.t'tnžÆws€¹Éý!œ5b>Bb0„NŽºíCA„N€Šc©ÈÇät¦`í‹W¤. ×R ZïOÙZŠ«Q±ëñwœ5-ÄYh±ðÖ¬‚ïëüL'? ê6Á™w+{f.׎¯Ûz&ü§ Býf8ïnøÁR „tž½(À¡¥õN{#pÁS*ßTØydÓŽ¨Ý¾~PañxwN(¨3u|”ÜtÎv¼õ üîk ]µ1-çóÚ‚eÓ}H.3]K # ¸X_= *• ›Ú¨M<žr!ðy6ïÛÌø~û‚Á_^W©±[öS3PÑ`²¡Î¢Þ€„iw7¶Ý¤e ì–™zY·Ý©mNí ïq®ë’žæzžö ;çX6l ›”‡m©ÙȇÑá&b1{µÁ›²·”“¨†ÖAM£E°4ÀÈ|›Òb•>……AròÖIOi÷³m©}-ívUÆ“{€Úv®‹Ð¹PdÛæöÎó÷W¶¶гhžô¥l;rk2ÞbaÛЭD篓c¬¦µT58üE8í]ƒé³uÎÎßöD#¬¨S…ùP3ûl@]¦EmÔi8㼆(ýŽÛŽKcB~Ç]©Ñ„œTTAeÂqã‰% 7fVWCÔ†^…$½Å€ºFg]U±MP§!XQXZ úÛä…hšéÖÀh„Õ`(Ð= Ývµ0ëïO63Ìíã ¹zT5DAKÞ’î_·O7l€–©Ô&4ü~…ý‡X íb€f*69‡›–ÅÆ:•^y&jš»š¼nAn€§.„ÞZ¬)O£ þ`AÐ!^-†åõ>P`X©ÊAýb¨Élóá0”7€æS(Í·Ñ“1)k6C܆‚(Άh*êAõ+”mf­µ :]ò5öê# 'ûKbðõXߨӳ›J7%Ž‚s(©j8lw¸ó[¨‰$øjmˆ£û8õ_´VÕ8xîX•â¸æx‹†„΀žá¦2£ðí XRë#;¤0ºÔ¤_± TÖ@MÂù¨6l¬„†CÐ+7ýç.ì2LªçÚ»"BF’:0ÙÖkakÂ8 ã½\•Ü¿ÐXN¦Oð ™tkî´ô^›‘ñÂÀ¶ šPš‚kt×Ç™ˆšÉÑ· 3¿ƒ?¼Ÿmr-*ÐøÝñÙ\5¶Ž[ŸÌˆsÌ?M.>©˜?õ«dÔ¿U®9#›+F×;ƒÀ(z‡Bv¯ ï]áóÏà‚÷tNÚËäémª»‡x{J”«þc1d‚Ÿòe Þ^c£i =‹üÜwf€ãyÜšÀ€ž…üà”ü¿L.8±˜»GU¢³æD8âM˜¾Þ4¥ÅA8ËÇÑëø~\ù*|ºÉ©bažÎåÇeqÕÞudmGš¦ÅcoÇ8{ žjšà Þú.}V‡-,,P 0WçO'æñDZuüóy¸a‘sxM}”oÔyì’<¦­k1Îůëì?(Bæ·y‰+çÃ¥¯Àë!n9õ úUŽÛ;›;Ò3Ëäÿ}W¼§’Û+Ș Ë‹Q¿¦ÞóИ:9Ÿ«bæ—pÊK*vŽÆÐ|“+,t‹=Gç0í´Fº-j+àÒG^Þ1Ã@÷iìUª¡(­ø+™°çèW¬òC¥Å›óà†½[òóµPaB°ÐÏÄv=\û¢Âœ›KOÊæú}ê¨Z —¾¨ðÚ2h4¨ªBN@ãâ# ¸õèž{®™«’•¥òúuÆkQ®¾¦Õ«ôïäí MºÕǘú€Â÷Õ Ç‘ËÓGÖJ`ø®Kp.p+PßÎu2ï”’’²¿µQéù8'Þé÷8ÁÝ+pìð‚ðshI$¤.Öé}­´rÞÏ¢s ,KÙ’±Çê6Âý_ADS;ÌËfÁñC´ÀÇ­S5JÕ8÷½nrÝ3 åäqÒþuÌ_/lÔùÝä§n ¡ªÂ†çûÒ†ªF›xlVTÜô2:ÈÑczø,Œ0ü÷ã8»÷Íá©_E™?ËäÖY1n›®sä ¸kr ¦÷ƒ…ëaZ¹Æå“‚œ:´¸é Nÿ7#ΨÙWŽlŇ_W?ÒÏæ²(eË£üçû|þ>¡ºù1üð œþŠãO‘§sd?…Ùs,«3øósaúæV%TˆEmtMaÜ%!£Õ[·>ã¶÷üôÕ §Š6˜šÆ) Š£&ç=ÖªÂþ»ÐVÇø¤ÖâùOÃz/Ú@„º¨E(no¹¡UÀŽBaEtÂQ Ÿa1#®pÐn~殈³¡Áâ“™aþ5¬ˆÛ÷®ä®—à™ÕN¥³ºèôˆ|º|7¡ z1ÝËâ‡J([aa£Ÿ‘jœ™ œCÆöö3¾¤³ªW°ˆDàÒgàÿ­´Qu•CGúX²*ÁšzƒÛ^k ¸O1ÇíQÉe3-ê ˜QžÅÈÂ(¯V8í›»ÞdEص>Ʋ6u–ÂÁ=“ DXtXlÛîCú\÷?›iÓ¦íõÅ_äyæ™]'L˜pÉO¸„¬Tåõ¶®›Ð¡IÞÍLyß{œwŸ°5Y@¸øÇzqðb±Ú†TkDº‘—{¯Y)ǵ©ÀÈxaáóÁ¦µ1޹K'ËçÃ6l6l6©Â1›ûårŨjÓ3a“¦ó¿óƒ7Ü T>¢t·Å_ª\z)Ó^¨Õ8mÛ5ÆüdÖ÷T÷Uirç'ÒQûe1mj#9>¨]ãìëÓ7‡·.‹RZ`Àpøh%”7šÔÆtJ|ži|vá”?­Fgê>JbÔ®r¾ÍûöÉåíóÃô̵`wxo!¬$¨©ƒg¿ƒõ¶Î‹çøÅ§]“ûÀÄ;-îúBá·#Zù M˜02‹q(¿žaqÿ;1N~ïW÷~”~^¼Hçˆ>,]‡>ëMþ2#—egGÙ´Þæ¢/  'ÄãçGè³Z LŽD ný_Š9# 2fDs~#ŸÖªÊÕ'gó·ýë1êà’'á‰Åð¿Y fæÃ—´°()?k[~í<ƒk ¨²Uþ89‡›¨cî8ä~¨Êë ¼šë»ÛnYüï¬8ÝbpÆ£ o¬Úö³wÊx¸i„ Þ]S@¿Ü8ï®T…ñ£}TÇ=KQœk|°t1L_éÔø'æpëÁuT¯ƒÿÇ Þú2Á'Â^˜³ùj™Âу¡")L­p‚U›|D«aƒ¹Ý}LèmJ@xÇgðïqáSO=•SO=œì4? Û¶ßDXì:¤Š5åÿ^@€ô¢CDÅönöÆI=EúOhR…‚W@4›hn½H=·MÈxa VP¡/…bÍÆ¶Uv+UX¼"Á› ¶9Ϊp€¡VŒ%ëõ׿,žïÃT 5©³-ÂËTÄœ ],ˆÅÙî™mC@Q9jB½̦¥ ŽÝW§4Ûp¾J²aÏ<Q HªÔHz ™‹+[Ê·€ƒöÒé°œë`|žŽ)TÔ9iN5 ^ŸkR¶ÀiW|YkóÕ|س+¬B!?g ‹luO©ŠEU½ó±¼?3ÊÂ~l˦ÌtÚUŽc‡à°!0c®Mey”9AÀVß[åÛ ‹*l*7;ý5ºÄÇÐÂÂÏBQR¥ºÐ‰ñŠ Õó×M~¾p'ÎÊÑ6[½Ê½òãpWˆú-ÒwBÛÑ’XP€rœ…RËh¾*—•æøŸEÆ ‹„%%~î=5Aå+/Ú¿}ZãËy íC³Á4,fÌ6˜mƒ…J¯Rîù mo—¦/«6èi>“®YÖVúPSh–w[Ø@ÿÜ­G‰>’I‹l°L‹oæ$˜‹{—¨ôè¥1&×Þ¶Õ² ô€+…3^±xþó0ºêÔOQœ NVòÖ3½á* ÉIÖ1f“0•æ–íhbAvGÏ‹ÓO7±·< tÕ šÀ¶'~¦{¶cWŽØ6F¢©E5i °mˆ¥)Ïò³ NP´é™ØW¨MæÎÑt…9Ö–¶–tݾoÿP>œ<¾ü”5p÷°è·[ˆ1ù5[¹&Ù(ÉûÁyµ¦Ò¤*™Ý7§ÄÇhl«0j èsaÑú8Ol;ßǹMÊž†wæ˜×97æˆá~BFbÅêYºl ¦žÅàÁýð¥¹ù*V•QѧKéJòƒm^Û¶ÛäGFȼ–WT¸ß¼ Bʘ߬ѫ,Sn AÈ4 æ¬Syð £4fØ=pÒë4!¸nŽ",\4wPïùñ ÃQƒá‘6Õ1gÀ³!?/ÄÛW„éš3ª6”m€ˆŠÔdO{Ýœ’ÝlÙJÓN  ¶iÚöÖ¿þÖOù˜<d3͵ÝêÅmÈÉ ñæt·m§]À²õÐè·èÑ’µ¢YEaÊ>ðôðÚ&tì$ IDAT›xòÛ- üÉÌNñ¥ dù0|PéÜ‹Z‘Jnoi¯¢@@··}«* > ´Ôàm òÜ+-*küPèóí'Õ •ü,“ªäyÕ¨1|à‹³±´P¤j‘ Ži¾ß¶¡°Àùߌ[|¹Rçôd6±9K·ã‰³<Š>‡…«,tJä‚Ñ6š7¹€Û| ;yßi>n?+Ä”u˜qØÔ`6 0 j°oo( ÁÊÊ+cÐg€ÊÄ~1öÓàÍy1üØä4¦ ÿ¸E!…‡¯àžKå…ŹœÿŸ—¹é¸Ýš½_½ð}Ž>á\Ö5äé_ß!ÂBØ%ñZ+´ä¦jŽ_1Κ`èýK‘ïAÈtøhŽÍã_[%Ùw­Î h‰‹N4”n蜧‚eÛ,®T!Fõªú(Ï/Ì#PèÌ./˜GÝ ü2T0mÀ¶¼ÅAè |°09ØÖàåÏau4u{æ°¦•,ßÒ[^_!‰mCvŒìµ Qž_K Ài×’N»~÷Y.öö|Ê&¨¹pÝdÈkV† A8³‡s¯Í. sñk¹|µ nz>*wöO­‘¥;©nb “w„¨ØïQ;ͦÀÁÝ¡X…Æpœß¿jóÎ|—>„›>p>ï>ÝýìÑ3J©îÜÈÑŠ(÷L‡éß9–·£ÙͪaÛt-„ÑY€eóÌÇõ<ôUˆ×>ƒ+>Ú΋X0²? /jjt¨ÄÇ>}iׄ¥2°t ¦Áƒ_™|¿^gú8àn•CîU¹æ“llJ{CßeËŠæsTu…‘ƒN4€ì¢Š$h;ÃÑrûsÞE—®Xʯ¿ŠÅ›=“KF˜ÿájf.)ç€__ÈþƒòZ¾ l?é¬>À“ᚸ‰#*d“M¶ŒÙÎχ‚çy¦iâÀKÕ&d¼°h4 bØÍWnNâZk×­3A‡s„Ò€É_o`ÊÝ~ο[ãèÿÂê,?WìoPR 4ƹè±ÿù¦€.%0¡>ýªž‰÷ø9ö•ó>pÒ;¦Sj‚FìfÖ ×ǰš×«Ñ€X õņ®]€HœKþço èîuÒ·ÛÔ}œ³?ôË2ùë œxŸ îјô(¬ ù¹â€DÚÙ²I¼mbnM7.Ñt\"™mëìàˆž€mñä;õìûOøë—°apï Wï úusΫmˆqö^_ŸV œ㤨ac¦ë †+öu^Ξfò]&'ý?(K€?¨rÍ>ŠB0v Êv:ý‰éa{Þ-· jnûܶ8QrQ£iñ=ˆp2¸#b(ø»Ã?v\­j6ǹð‘Ç?Kê’õMØŽõª%LЊàø>M7Äž~Æ76mˆîõ k?øý~N‚€/¿ sàí'> åõÓÇÔ=UÈ…#ºÚ[DCaÏ þ,èÛ'Ù`â^…’¼° 0ö¤Ë¹æ¤1løa:·=ØC=ïíG¸ããïè=æþö»“ñ)`4ÖR¶pó.¡¢º!Íõ¬Y¾ˆ™3fòýì…„c¢<…-x°½– wIR?’U:ÞçÙÞeuÛD\d¼+ÔØ¡ jdiÑæo˜0|Læ#¤T×Cé ˜~üý …™ë,tUaðˆíãØ^N°ëA£áü¥sb6Õ ½þy˜o謬3‰è:M Pº©ž%!Õ‚®ÅpÂpÃòã[|òýA˜8Lc·BÏLµ C$ ’£¥‰°àÀQpÁb•91›CÁp¯c4»Î˜Áö«ˆÑ¥?¼üýu˜¹ÎBUpßQ¿èÞÚ×Þ†P&Sé±Ýšb ÂoŽƒŠNµ©²oLÈ+/„Ë_Ñ™WqSAÓ ×À ÿ:²‘!ù&°ßX¸a“ÂGkœ¥£{‡âiÉú–ÂÃüägëä()Ÿ]ò4øË(.‚'fë„“3õ9:—OÒ8uP D ¤?<<þþ‰ˆ ¾Î9{Y,ŸcF}€ =-0 K=ÌGn‰Fžæ!¿¦R‚Îøî$àØ#à‰Üõ­¨iÌ÷qöŸ~kSí×èî7Z7Ú0eø ÑŠÂ)û+M¿Ê6¨8p¨FÏ:1Åq.?Á]ßéTÆÁB¡{7«'Á½ê¶,îxÂþðIÜœ6*ÔŽæ#‚ÆYƒÒ² ˆ–Íon¼‹Ç?=‚§oû'œ2‰ãJ×sÓWQÓØ“›n»›aù>–}õg^ü'fÎYa+„ºà×¼•[®<‰l7-çæ+Nçöi3Ñü:‘˜Íðƒ§rÏ-wrèøâön¥ÐqðlëÉÍçy-BfãÇÉìæÎ,Yž­Í,mq!÷ËH§©Ò!œIßl 7ù÷Ü }ô“>øA^m—ØÊ±¥O¹µ²=ï'ýÜÂÎBsÙ¡ä~ËsŽ›C¡iUd Œèî×­ë‰fÐ4¿“ð´Ë5:4wKi­¾éÊ··ó:©írû¸¥‰I·~$ËòŠïψ÷½¤¦„!n¦BNvJ9 Më'¸ý—î³vû óOK$¯gE¡>î¼Î%ÏõÖ9Ù~ÛÅGÓ]é¶Á IŸ{úAbŽe(+H“®‡æŸsK´Ô‡©ez?WÝy‰9‡ç¤Þ›n;µ”sÓíkKtxðc‹¦™€p²J„†äߎ1(ŽÓ;;há΄mÛ“€··uܧ÷_ɤKÿg^Ɇnæ´¿>Á¤_ÝÃ+\FÃü×8ð°Y¸y¹å†•X¼ðÀ­¼6c#—ÿí]î¾~"Ón8Óþöç]s;'ì3•ß½Ï5×ßOÑøsùöëÇ(jÁåÒ¶í÷UU=¢›-t<¼– WLø“[(ùÞ/zå«7M¿ÌÒ†öÂyÂAèøèðÞ\•“µêbö%À×8CŒXr‹Óä8•:Zù©Ef8­MÐZl=hMØs²=Ǥžã{ÎѽƒÁÔk¦± ¤0okB9]ùÛsdù[Úµ­[£¥ú¹uH÷^²>¡,ç×&m9®ØòÖ?Û{û&¯§ú ßïÙ—znòµÒR6$¥…2[ꇤÉrƒÊìiK}¸­2¥.éÚÐ&_BGåÀsÿÈio¼ÏcOßÁÙ äõ;’ÿ~%Áý½ÕÅ<ðò+\tìN>| ûî}0¯¿y7\s ÕåØªƒ¦žÉÑ#KàèðÕ'x§2@m#å¶s…Ž€×Ê_áµ\‚¹¸Sš®Ò;-žºøåÏšÌ|añSp„·uLk¯w4?µ¼á:½=e´umÏgÖÖåno™mI{”)tlBݹíæëønú©Ì6ý\wÝŒë•æzÞÚ\>‹>|ˆ[ç8)› ln´)¯¨`CM×ÞÁúˆs'Äÿ›0’½;š“s3ôíÒÞ-:éÄ…+*vͱ‚ t.\kd‚¸ òe!‚Ðáé2r"ïa1gÖ¾übÊxg§m£+ šáóÞã[MM&°è1h<ƒŽÅV4NþýèνO¾Î÷Ÿ¼Ã›/¿ÈWå³×ù7òÂMÓ-kéç„]ÔîTË…Ü$‚ù¸®ujà¶o ‚ 옶°mìD2’0cQ´£yüéŒ,²1lðie æV P`P6{»w%Ÿ_z«—.bÁ×_ðà7òÊ¿ïá½ÓOáÌ %íÚ4¡C‘.;”ë%BæâFÀz·6Ð ÒÍ ‚ ì’è%œ0|ñó™öêL‚Y!r²Cl˜õ.güâ.¹þ~¢‰zþ}Á>ì3åw¬hÐé3dw&u‡ì +!ig…f¾Õ©®PÞˆ ™Í¶D…X,Avb1°qOg\ûg¦M?†Û¯<‰YŸO¢Èå›wßdUUîè÷suôq4Üð“ÝÄ/Cýòïyéãoé7î`öQØŽ-: ÞEò¼3œ",!óñ>ÏÞMb,Av)ôÆ6•û ¢0Ðô7èH^xï]þxÍ|3ë+V)>ºív8×þùœwDÎþÓ£Ä*¯ä¡¿á¯-G±`È~—pë½W3¼ Ð^-:Jšm‡ >AØé¸–ÈtAÛmö|‹°Aèèh9üúæçøuš·JFÂ/BC] –â#/7»Ùûz¨ —Üû¿j¨¥.š@ÓJŽY¡ERÅ…X,!ñ›& ÒM ˆÅBAhNN^A«ïsò æì¤Ê6p‚ÐîìðgZf!Aa×$5p;õA:­Y+Ú,€[,‚ íÃ,`ÊϽÈå—_~Y,Ë»÷Þ{oñûým•æIU¥¼®%d&"4¡ó²Ãži‚ 퀢(ÿýÜËÞ衇^üùµA„ŸŽ¸B ‚ d.yÀH`^{WDADX‚ d.C XÙÎõA‚ Ì( xA¡Ýa!‚¹Œª€í]AAa!‚¹ *úö®ˆ ‚ ˆ°AÈLü@ _!‚ tDX‚ d&]q²B-i ˆ°AÈT\a±´½+"‚  ÂB!S)Æeí]AA‚ ™Jw@Ö´wEADX‚ d*ýÕ@´½+"‚  ÂB!Q€À2Àl纂  ÂB!Ñp,Ëa!‚ tDX‚ d>‹Åòö®ˆ ‚ ¸ˆ°AÈ<‚@O`U{WDA\DX‚ d¥@ØÜÞA‚ ™G? ¨nçz‚ ÂDX‚ dý€: ¦ë!‚ [a!‚yôê‹… ‚Ða!‚yôñX4´s=Aa z{W@AøQ(@W °Û¹.‚ðóÐhyŠÓ"óWiQq’C'pÚÓ–èÉë§ûPpúîçöŸš,ÇŒÊró'ßo­­Þë%Z)×m›•,wGâÃé¯tmÔ’É÷Üz;¡^Š A„Ì"'¹IªY!³Q`Ábøhƒ naÙÊ–·4Ua`7˜Ð'A–í”»—h/Éí-_…ê xòKûïãŠmS¯ä ø½oaQ¿¶uç$*ûhLEù©e*P¾^œïÃÎR9~´AŸlsë6hP»núXg£âã’‰°WÈÖ¢Fõëàù>²³`Ê„E>¶¾ž_}3*|tëà´ÝÚ^”%Ë!ÏÎPY]§Ìñsθ0dX» žçÇ ÀaÃMÊf™¼S`!~~7¡~ÇÔ+ÃiKa‘zkØž}2«&ﳺ_ØñÙˆ°2f.„ß¼— ?¤âÓšÞªo4H¨ £zgóÜy †Æ·=ˆSaÙ ø÷·~N>HcŸ®‘û­¤ÀòUpßL?'¨°oÏU›á¦÷Ü0ùÞâ¡(0¼o6G–†·‹·>?}GÍÒ9@ç/Lž­ñi}¿ÛkÖ+ƒi a‘* Zt‚йH}î½û„GŽÅbu{WDÚ{Îö12/‚mƒ¢@u<ô<¿"ÌÝßäóÀ䤰Pp\SÜo×åÇtXµîü8ΨñìÓ=✣zŽÁs ‹æ×tÿ‡¦óRñ–—ŠÖnrÊßml>ûj14€êº™žë¤ºÒx„Ч~- ¸uÍÒÙ¿—JPmªT€ݻڎµB¡É­È=ÄíC÷úî1.îqÉ}šY:ÍûÉí ròáœñ:µªÆ¸"Ô=åj4‰G¥©«›Õ/ùYøÔ”6ºuqûÅ{ž‘ò¾· IËQ³¶zß÷Ãé#à™E¶¼µ*‡#{‡ëDáéEΡ} Ð;Ìšñ J­Î€Vó¾Äs}oyÞ~uûÁ[OOßl!ÃÝÿv†+” 8¡ó ó3íO!‹X,„NƒÊÈÞ*c»Ðl@:*hóÖ]0¿Ê&j@Pðé_­Qc¨ô,R8|hœl7N © l\Ÿ&åþË ©Ë+›_Ï…Yu4]eX)Ø/Þl¾h|³V¡ÁÒ隯°Ïà¥Ùö6š» ñâéi&v²N¶ ª–@µ>˜ ¯ÎÕ¨ˆj¨šÂƒN£W¶ Tl†·—è(A•={ÄùdžBEN6£u'?„bÂÒ2ƒ²9 ³+}ä穜8Ö`t‰6„²à áQúäĶ  ç·—ëT%T†÷öQ”;×óô›Ý¯þŸ®ÑÁ§søp‹p,Þü8 ̼5>\á#l*ô-Q9p ÂËÔâåðÍF@ÁZ„×ëlŽªô)Q9fwƒaEÆÖ¿`ì3 ú¿óàÃÙ&Æþ k°~ ,Ž:‡°·Ÿ_˜álr#6]º$œki°y=¼´@ev¹ŽêSYj3i·ý -Ö¯÷Vé(šÆ¾Ã-ç'˜±VêØj€F7R²ùö˜ö‘R8~t_ëy‡fG W'ZžÍV]¥å`#A:*¨Î·»÷™ö>ãò4ï\а¦½+"m…e+Mß*Ég€ìÓ@SÁj€«ž†»æ‚¹%2YaÒä^<¡†ß‚ë¿w®wÏ+aêŒ\N,ªç¸'-þ}>¿T @´޹Ž9"›—¦Ô³i-÷“=t­1“á}Tn}Á⣈M·¬jl*ïŒcóyôˆZünÀ/€^|®ýÖyyß«aªŒ\n\Üùb”åë!šœ^×}>¿ _ެÞþNÿÔ$š¢šôÍbÚ9ãK¢­NéèªB–ÏÂç;n?šðÏWáO‚7šû¹ïà‘¯sxþƒÑÅQæ,‡³Ÿ5ÈSòºi¬-7˜2XÁ„Ÿ†;æÂIs¸é°0Ù›?= ϼ]Ã=ýó¹âŒZ²KáW¯Á?ÏÍå’Ñõ|–º'‚ÙV ¤C0é~¢¨¯Ø,Zoqüa¹\5.ʸ| ¿¢ëì51‡÷ Ûhsêc6Omð·ü ÌjH‡_O…Ü^pö«pË99üfz6—9ÃåEUn9'›_¨çÓÏ`Êë ^Yä—£aÃl8çæfñÖ96{vðʧð›·¹ä¹<¾ø]´Õì¹³#œñôÙ`ƒeA~ŽÆíSãl\`qã‡Îq¥ÝBüლ<þƒÅ’U \ò^!ŸŸEOöE£mSWnÒ¿ÄÇØžª;©DP·É)P9tˆŸÀ¦/—ÙlÜœàê²8ü*ð«Tª A$6À-ï;©ë²³5í÷)ÆËeM#f]µ˜1ÇЫÈÇQ#>Ÿga§ ¼õ¥#*4]á¸=CìÝ5ÆcÚ,®Opñ뮑ç3Ñ€<Å¢k‰Ÿ¿O²yá+“/ÖZ”•ÅysE¿÷ŽÿT8lÜ0*ëL>Z•Çî¹q¦¯u$Ìî£ìYâXEòt·?HÀ¿ž…9õ6Á—íï#7ÒÈ=3Ê–G¹é³\îÞ'ÎÞ9ðuƒÅº‰0TmY}(μÚ,öÒ`m²[Μ  +d4;Âbá>½^k… Äo²8î! MU~z–Av*Š•$ŸcR,‘ˆ ØÙô–“ñž¸‚àúÐ\øˆ‰_WQ€êˆ;ø´Ø­_ˆ³ÆÖcn„g–B÷î!î9!JŸ ]àŽ©6ŸßÓ¾P¹z,ô)pÎìQ¤Åv5¢L:$——NªuÜÝk8…nÝCÜ{b„Ý)ïò±pñ ƒŠ¨ÊÀlšM­²¡O¡[>ä„`3ް8öà<®Ú§ L8ñ Øû}(«Û€¾€M¦Ê3géLVœ7 ÞYoWDXZdDnËV #‘àÍùÍ÷ué ×0}–³’fa§.Ö™Ø+{‚/ /‚å ìjÐñ«Îì¾ÂQãrùÏ) ôΩåãï’ý£h\sL.ݯà‡á¡°º¶‘5!º«‘¦­‡oMå’røçÁµP</Iv™eðÍçÿü|ÿ½0ÈáƒëÙ<F=" ðæ ç¸Ò.þy\”Ò,‹A ðËé]ßÈÌò,T¥¨òûynª#ú‡9ºìwlRL«…» »÷ ùðE­ÉÜÕ&µ%°p¥Sþä*EÙ&xr¨ªBÕZx#)€&î—Íß&Õà‹Á¢M6Ï.„×YÜrŒè_/€o7Û,ÝKÙSòî•Cà Á ‡önÃÿvbG»B%½û°£ ;±d“™…ËäÃÉz솹٬SÃoAر¤>¯†gKàˆ‰8°¨$½Õ"ÿ 3GX¬D„…ÐiйâXÃsâX€®ê,)·yì‹%k<¿ _v­! #Α÷i”àÃR .b±*aâ¯40M°’ßDÆüVJØ*û–8Aʮ玥@I¿%ˆó ¨BA°1Mmë‹ØØR¾²¥| Øw¨Ù䉤Aæ%쬰T0-nšÖÈ£9~,lËæ»•a¬kð3"/ÚbÝsº¸roÈWœ ËVektUTÔ;Çô,²WI=D7ÂU1›UõABªK¡ú5¦¤Ò»Ðjö “£rØÀ¸ó‹„c†:Â"‡ªˆFÏ”Q[U2k(Gã˜!¦ÓA˜:Þ ¶e²zS²~ùAèSaè2Îìÿô8{&C.X³1Æèëœó-ÃÆé‹Š¨J!N™={SäøKÊ&ZXÚê{ö…/æÀôµ6t…o£ÈÑ8løÖq. `Z '?ã>¬¥ëG Ž;;ë+lU‡÷HðÔøjƒÅ§ë–7Úœ0ÆÏܹqV,3y¯j,1LgDa8ãMw„+Tª¥Â¨”oáf%·M"ÃG“Èaçâ>¯®˜ˆáüE€F·›pò=÷¹Nµ\@Æ%vx|@àu¤¯…N€ã­1e¼Â˜"³)˜Y‡ >8êuƒuÕ*V‰#ºfëœ8LlltMå´q*]ò 4;}Þ{hž]HWÓ 2mŒ”mPUµÙE]á¢üˆÇO ýV³ë˜$E  X6ø5ößÍG/ŸAÜRP…#Gúð5çF[}Ú‡öösõa tOØ 0=nçÞŒXJS;T%YäkH¡wNb«ŒUª’Òg­LÿÚ@<)Tšß>_óÃuÍÏs¯ʧY™[\QžŸÓÆ Ÿši)ìQeY²ùÅAð©ŽyDz·#\!™êÔaðè|X±¸‘¿nÐ01Ð3À¾=ëÓNã(žŠ àð.&q[!ä]µ±t…Ýä Ý øs…òµþïÛ†Sö´P6Áû‹ÃÜ–¥bbsh.YöÖ®ZF[ ‹tq©³Ÿ–çÿ¤þßr®…3hñ$#a'ÎZáZ)ÉÍkÁpÿwÅ…÷™v,®ÅbE;×CÚ›xBiúFqv1rP¨ŽZNö²r|üù¸‚Ét°f-<6S#»@sÌÉÓõäè"„3¨¨‹)ÎÓ“ÌöÛÎU“•Ÿø­æS›+ÓnaH£B6( 'ïåã°~‘-«?ø=”EUŠ‚­óZ–BÌpfiS3U%]³ÖÖDùtuGi€jx}޳?߯Ò;/ÎúfâiëºÖW›¼² ›½o„x{¶³¿( Ð;/é Ð5Yn¸ÎäõÅ:ûöêàÅoÜ£lMcp*aC]”ës™8 žsáñ•µcƒîwþ-(ñsó”09‹²2˜½IÅ´†ä›,u¯lÝR·„ãFÁÀ·f5˜,Š8»ç'[ §íE¤q‚‡¸cr-˜ðÍX]¯aæ@¶f“Óú† ¼Ædé& /‹ÑÝâ|T¬`­3ب9:£*­ ÈLaGY,¼– w`ÙmÓRŽMÐÜZ!®P‚°óqÒ½ÏmG\DqžßM‚Ã+,Äb±ãØ8(á|ÙÀºö¬” ì lÓ± TÆL²ºÀ!}á_‹Âücz×ЀÙ7? 7.4¹üt?'yÆ¢e›Á=rœæñO Nì§à«·¹âUǫ†k!(«P0lãX ®*œ³»Ícó ~ûŠÍKgùJðî 8í9(èãô½Ó,"·=¨à[Û IDAT0i Üöl¬Mpþc ‡Œ R±2ÎÛË‘òÞ#³è›UÍÊm(Ŷø¿×X´$€U›àeÎ ÝgÑ?»†õžóM†ö‚±yð}ÅÝo6°di£6ÁkK›TTv7ÑgAMM‚óV9 ŸŸæ'XÕàùIñÁ±{Ãc¯BÙ²Žúwˆz'xê+ƒ5µdù™5TE£¥Å· ô8c°Í¬YÉ}!?'Nß÷¦iS\ “ºÂã«á‘×Ã$ªƒ6Äyø‹ª„Éî{*>Xl›_´™ñÓœý5út3Ø#þ ƒ’xÝÝçÖ1œßa÷µkµh)ÎBÄEÛ‘þ€Ó§œ~×€«Ã€Å8ñ³pâ_!£H˜6ñ43Ì¿ó¼3Çbý¡*Woñõf…›Ÿoà¡WUTÓb“cöËãš=«Á„Á] H¯c]u.wíSÏä¾0mn˜W;V‘ÂBBL ¶¸ۉ™Íý}ãPo6ÿ:3 §¾ÿŸ½óŽ“¢¼ÿøû™Ùv½sÀqpÇqô.E±bObFšŸiÆ“¨±ÅÄ$š¢‰šØ£¢Ò, ¡*½wîŽë½l™òûcvعe¢wGñy¿^s»7;å™ÙÙ™çó|›«›" 02Üÿ|êùIÏFÇ\8¨÷C³fÒ`Ô8øÕN¸kEƒ­çVi¬×ÉÈôòÔ,A¢3­­ƒ–°ËLK¨[®À}Sà—‹ay/Š|…n)&ê 1dû̉pÛ>Á³_˜,ÞØÌâp°zR²›»/ŠgxV-ËÖ¼&ÇùÐ ¨0 ümoÛ(pæiXwS`èC3Zûºð>ÂîS\{^Vø¸Lãé"禠—¿Ÿ"Ýe¹À „áGeA¢ÎÈ×ë¦Þ­·ü¸ÚS"r®½:ðvmA»¶¤3áÁ²ÎùˆÄSøÂ¯Çr2x["9¾ØÁ),œÖF;æÂŽ»pŠ {rZ0$íC"°èu˜ej3ÂËI$Ç‚]?Ù…õLö`=«ã±úÃïÏÏIQn[x›¡ôË¡ýFUì.‚ÏŠã8˜ŸtŸ#F@øï•¦›Ãýd&@S¼¶F°¥ÞÇ-èÝ. ÑÍÁ;Øü °µÖÅÀ<Áä‚Õ¥0{ƒJy‹B|‚`ê–bª›‰=C´Ô››Ý ËSÔ-pÐi{áð'&0#¯é`]ˆ}ÅðiQŒöÚmÖaáØ\ëbP>ŒNÓ˜·IaØ@(H2Á,þÓ¼LÍ †+ƒ¿¿V¹ð›*Ù)&S†™ IµÙÙ\ºvÕ¨deº8'?p0f#ú#`ýV˜»C¥:¨¢(‚ün‚«FøIŸ·Šjøp»ŠË£0¥ˆ¬8kõÊjø`»ŠÇ'”®±|·BY“‹¸…i# ¤XnZ-õðÁf…&—‹1}Mú&‡@À†m0w§›&]¡{W•)9ͬޣ¢Æ ΨYß[殆ϫݘ.…ñ} r•+¨¤gû8?/\ ;Ÿ¬…e•.š‚o¼ÂYM&æZµ)vï„ÏÊU22\œÛ;€K@|¸IP¯¸U(èŸv·2Z|¸jP韫0*'ÔÊ=oùzØîWIëîeFn3¨Ð\³× v×¹‚ÌT•‹†‡ÈKÑìÒ'aîF…&T÷ ï$Ts¶ š4…~ù*£»ÛXÎóÖ+ÌzÆli˜÷+‰ÄM6b=Ï[ˆx#8¿!,l‘ànZ §¨p9Öµ'‰DÒù8…NkÑ`[.œ.QÎx Û5JÆZ´/*ð:pÉa–ùð3¤¥Hrì_a… ‡^Á"Ü"ŸÛCá c®cûbØw2{Ãñª‰úT°z#ö]ÌÆãXæhÚkÿfxÛ!Zß½D†qœëÙû·Ûw¸l·ãxŽää&rwáí;Ï›}Ìð¶bͷ׵ϣ³ j/GÔ±º¢¶e‡&‘ eûØííÛ¨zŽÜDž4voÑþÜþnœç£­vµ…óšsîÛÆÃAyð3»çkß."6}ËØí°¯³XóÚ›ã ,ÚÓÊnˆýS·öNœ­ã*T"79b¬+‘H:û–í+V*äøÌp¼:Ý¡$íƒ|NÛÂb'ð{äy—œ¬ØÃ±0±î8N"Å£#ÿG¯ÝÉv®ý ÖÝ+z?;;ÏáÚÛÖþcm;z^ôzGÓµ;–ç²±†Ú:mÍ>ïm-çÑÉ¢kÑD[0œ“,”×ñ쪌¨ùË€—;¿9‰D"‘Ħ#+oëQÿ;Å…ÓBagв½å¤¨HŽÑuh¢­ÑBCЉÎaPDka¡ Ç¥E‰D"‘Ä =……ÓÂgá¹’– ‰äÄ"–¸ˆ¶b˜Ä¶THÑ1TcY-†9æÍæŸæH$‰D›Ž´XDÇ÷ÛVŒX±RTH$'ÑnQö{ÛÍÑ),b-+éÖ†ß×bY+dö-‰D"‘œPtTð¶ó½tØVñ,)*$’‹èß°óµ³…¼7Xçx5«ïóX™¢N…s#©D"‘œBt”ÅíœØÁhKÆ©ðp”HNEb tTuíè4Óѯ±²"}]0€uXqÀ9¹kþD6Ùódåv‰D"9ÉéHaas8K…D"9ñ鈎^´pˆNÞp¤×¯&VœÅ>ài¬x ïqmÑ—§-±-,¢—“bC"‘HN:CX´…|PH$_?¢…„ó5:æ*ÚíêpÛ;Õ1?ïɤw2ý½9ÿwÆÚE»Úµ•@>7$‰äæx ‰Dòõ!:û[t;Ù5Ÿd9@BÔv¾Ž¬â„gqòº@AÄ ÊTa¹v©èî˜Î”Ɔc}™,@"‘HNP¤°H$‰ÓB!¢^"ÆÀX`"0ÈR\n—'!ÑëòøNæŽt;"L0O aeèšò7‡BÍM!,q± øX ìÂz6©Dê¦ØV g&,).$‰äC ‰DÒD §˜P‰t Çߦø’ÓrsFM¢Çˆ‰¤öì7!_rª/¾“›.éP˜¡Á¦zO ¡ŽÊ]›S÷¯Z\X´ò“o›ê·óÛ±®•h!ë§H$É Š‰¤#ˆv{²…ÝIì\ÜÕohÚ€W3àükHÌê†;ÁêÓO²Ûxê!@ êpZó©/Þˆ·ž+Üòþk…µûv\<¼ Ôa¹Èéá ¤õB"‘HNH¤°H$íI¬8 ë^c»³LJÈì:hì?gÈEן™‚²>ÕƒÖ$ùúàr»Iï݇É?û-#¯¹…Õ/ý%çóþüp¨¥ið `#–¸°¯)gÌ…ó½D"‘HŽ#ÒwY"‘´'ÑV yð=àùÞgÍtÕó1æ»·ãKMA HëÄ×ÓS‡’»åpöäò¼O—ÃÏ^Î /ê&r=©D®3YhU"‘HN¤°H$íA¬ÀlÛRáÂêÞ‰»á®Ô‹ÿø™}ú£ÁÐÛÜæ!{P\ tr²U¡€ê¶\w:t?*¨ÇɆ,P=ÖÔÑÇy$tÍšzŽ;ƒYŸC¿éWöž®À²J¸‰ ).$‰ä„AºBI$’öàH–ŠªnÏO&Ýù°2æ»ÿ€¡ÃÆ-MT—ãJìJbfr§8¾¡Ú ê+›HÌí…ÇÝ~ýVÅm0=dýßT´æ@<é½z tf÷X@¨º˜òÝÛA$’Þ$qqÊq·éAHîžÃŒÿ'>)uýÿ|ë[ŸuMÙØW’¬w!‘H$Çi±H$_•hQa&Û>ñßQTõÎ3ôˆ2æ»ÿ‡iƒ•ÂÞZ¶¾Ç;×Å‚?ÿÃÓ97/Õ{Þ{”W¯;—ýêqµ“µD+æ³óë1Lp‰Ûž½—îþ½s­Š Vý›—¯=›—®=“=ûjPO'ƒwœ—©÷>ÁÐ+nJÎ ì¡õuf»FI«…D"‘'NLJD"9I‰v²}ßÝáyç!Ä/ÆÞx·{ôw~x0ÓÓ—Ù‹0ëi.?@syM¤ãíxªå&å씋¶œc¢æ ÛÍÊe¹Ù(@ ¹†¦ª„Bz«õéü·Ñ†˜Ç¢W³þw3yó‘0@ 0‚!‚M«‚œÞNwhûsEmãø¢–JT›Â™„ÝV”n9$õè‰êOa)—÷Pw°ƒë+ml³­yLja€+ÎÇ9?œ‚É¥÷aÕ=Q°Ä…Œ·H$’é %‘H¾,Ñ ì˜ wøóÀCý¦]ž0ñ滯œ-Jx8Ý4 43".—ÖBýbBºBb—\|ÉnŒ–Á`Õâå sŒÐ ´@ j‚U…@e)µEEh¸HìÞ‡ä¬DLÝÞ­¥“쎱¢‚Þ$ ¼>\n7ŠŒÆzê” ™nâ³zŸêÅÔ=fhþB 4µ™`³†‘hZ½o—“Æ¢]´4øñd䜕 fx;²¢ø+Ji¨ªAñ&“Cœ´h+‚Ô•WX ]HHOÀ4A©/¯À0ž”®$¾©¿†ñd椡–«V¨¢˜ýëVÓØ">3®ƒ†—â j4£&ª/™Ä¬t„ßOCE)º®Ô,’"ûAàMÍ!.Ñý¥®CwBSïý ³ìíS¶yÍÀX©h˜DÌ¢SÒJ$‰¤ƒ‘ÂB"‘|¢-vlE<ðãÌÂ!=ÎùŨ>ß1ÅTŸ3ûŽo¡^Hâöwؾq+¨>sG2ù÷ÿ!»r¯ÿüǤN¼‹‹»w„¼ýoþöAò/}‰÷œÅ¾—aÞ“Q]íG5f|/†Ýôk&~ûÊVŽüŠš¶~Æ[?½‘êÊ&=øÃÎEå’ÿòÉ#·°ss†)ð¦f3ô¶¿0ñ)q!T0Ë×0ûÆéìÝ ðOžú93Ÿ~—׋mã£{.eóÜwÑtð&gÑÿæ?qÎuW  Phbûs¿â£¿?EMMÂå%µïXÆÝû/ÈÃtœc! X¶ŒÙW^IƒaÒ}â\øç_áªæ?Ê«÷>‰†Ê€ß.c|Ü»|x×ïÐÝñLyz9ƒúePºè/|ððƒ”ï/G i¸|‰ø’'0ù‘2`”›%·œÅ¶Òz2GMçÒ§_Ä\9‡·ï¸‰jÝ$ÿ;O2ó¶k¨]ü^ùÙý„p1öwÿcâ¤B_ò:04HËÍáœ_þ7nš~º¿¡öàöÇDD„†,œ'‘H$Çé %‘H¾ JÔm­¸Ö—0ãìŸ>Fr·.í+* TSDñPåÄä_=ÃØKΡ~çæýùßÄõEœ§…}«ß¢²DC¸@ ´°çÓ—©¯ª£×%c©þð fß}åžÓ8ë¾¹ðá¿ÛÓÏÊG®bñß?†8Û§FàòÆÓ´åcþ{óåì[¿ƒ‘?x‚!gŽ¢qå\Þºå›ì(Iâ´ÿû+çÿæOtK€|‹¥ï/F8†oLD|ÿõtK2Ç1þ¶ѵk¦©bîxŸ-Ÿ­eôeÚÝ¿$ÞUϺ'ïc÷ö:|)x°®½h—(s!‘H$ˆ´XH$’/K[qýï¹ä;¢÷YSÑB×SÓˆ/<™¿LNO7Zõ¹4oZÈÚÍ›ñ§ÿœa#Ç1÷ÝÕ”¬ÙLÎÌ!ølbÛÜø&ü”>y ýæiš}ùL{ð_Œ:£è> ——¾u>[ü‰ñ7…P¨ •kÞbÕ³?¡¬ÌàÌG>`Üeg£„‚lû8 >Îyæ&Î,DQ ÿ¸á¼zÙé¬yõ&L;ƒ85<œn€™ÅÀKn¤øÕŸQ,Æ1òº+H2t¶é!ÉLzt.ãÎéâ‚ÄÊU¼ñÔJê«êÑ+°ì—pLç’¿¾AÏ> tèšÏ«¿¸UsVRøÃÉ­jS‹„Tz\|êʇìÞMé¶br’BìÙ¾€—]E—L…ZÓ Çb¨¨nƒ=/„@à§¡¸ÁúW×ÙøôÙöœ‚Q»—  ÐDeùnN;wé]ã(9°›š]KÛ>ч.MÊ÷~NÅ–õ”7ì¼ô=Œ@;|íx“|œþƒû™}óŒ ¦† €W°¬¶pŠ »â»D"‘H:),$ɱàÑEð`„Ê•c¾û#’ºf¡;¸5ª%³+Ѝ7 LÓJE¤8àl†Kbý›oSwçÕl{ûS”®é9fø7cUââ‰Z¬Mšª—D—×RO'Ö•øôJ×ÎfÓ²ï0btºßDè.0›(ÿ2U Y›¨9cÈ-ì‰GÙ§5 èÛ¯Çê »KŒ@¦@(ªÚÎη_DèaÕ&2‡Œ!³09b­cj×{yƒûSR¶šm«VÐUk¤Æ‰ýF“7tð!ë(ªuà†Þ@Ŧå(¦ .I¦ABB"¾ƒHïQHÉuìþèĦ½¸ÇÜÁà3t>zì/lzëyj‹ÊP’=xªÙ~‘ÔZzŽ=ƒÞ“/›ß}ñÛÀ§@1ÖumµâB"‘H: ),$ɱ+”ÓbqC×A§% œñƒY•:DÀŒéEoš&jbw Ï»Ž þƒMïÿ‡]ûŠÈî{7¹ùnÌáÒhj\ƒ¿Üᔨ¢±’Òúr𤡸ULÓ\Œ¿o¾wxîÖ»XúÀ/éõâ ¤©ÃÔ®±\øì2¼:ºi¢¨ »ÖÒ¢dš ¢·Õ¥Jë4¬‡éúš„5á\ùÔO¡!ªÀô×S½{;î.}qéQw4‘@ÿ™ÓX¶h5Ú’gX¹#ˆé¤»šÌ Ðc|Ozx¦š}³^}šÔx-ÐHKm †¦ãé’‡áN¢ÿðîl\¿ŽýŸ¼!èuñò{é¬ÀÃöÙÏâR‡õ¤{Az{ÆÙ„ÏÓøÁ®çô 4Ôžü;ü©%—láL?+Å…D"‘t 2x["‘+m¥—b°Yß#>#ñ˜‹àuÐ}ÂdÒ2 –Þwú]õ ¼¸R»“‘›EíºOÙ¸x#jø xѳìßZBJÁ™¤e€©€BúÐ\ºO½3fŽ¢~ãKÌû÷(‰]É:3ô[—,'®[<É9 6Îáí¦2÷é׬ží!ÂÇÄ0Á i €r„RÛ¦®Ÿ;’ÔîÉTíz‡{HîOrš—}ÿù-¯ß4•‹w º]×Ð }ÜÕôÊMEÔí,GY ž15ú;2M 3ìqyÖºÕ ùü­÷h¬,bã+¿åµë§òŸ›.bçºMÙS.Ãcˆ°ÕÃM÷œ^¤ô/ 9[‚Ô¼+II¥Ý¿:dõëÏ  ¿p9IDàÚ4g ·D"‘H:i±H$GKt\…3Ŭ˜™QÐ?µÿŒ«ÑÚ×> ÃÔ ™ kºåÛbè¡zTª!C ‚&¬!ë$˜L^~?*Wn@-¼„~ã{¡w§_û—ÝÆ§wÍ jѸÍ:v͉€ë4ÎúÞ­øèšÑü W#ozŒÍ+Îc÷ßî`ýØ ¸æn>_p +îAÅG’ê °kÁ;ÔxzpÖ5³ðy­ Ò­N¤š€¯k.Ì{·n19û§÷ „Nt.VÓЀZ(€š9q×ÝÂë÷?ÌœÏ`ÓðaÕEl_ò Á¡³uþ(ŒXò¸ÒúRxÁ$vÿëk^ÿ+ÉïÛMá²ö£@ÐÔɽì7ô_¸”-+v±þOßbýŸUlTJÿïÓsähÌ ÄœI4…e:(=Hé1ˆ„\o—nP¶¹—ž„ŽˆáWtÑulz÷Å~þúÚsW±„…FÄjaFM‰D"é Ô#/"‘H$@$•§ÓýÉžr{Æÿžø^ã'µ»µB(`4—Q¹«˜ŒÁ—Qpz!´4R¶k)ÃΣ÷À ÃR>Å[hÑû0`æ¼.0UÉñU•- ¸æ ‡öÆ4­ àñùcè5°+uÛ×S¶mÕû‹ðLaÒCÏ2ì´n˜:4•í¥¡F§ß×’çÁÝ%ŒÄJꪈÌB Ï:‡‚à l]OéîÍÔ”Vàí5˜Óï}–cÇ0—x5‰Ú{hiq‘{Öùx»©À 3Çà WÊÕï§|¯FþäKHïžJêà³éÑ%Žª-ë¨ØµÆ¦Y/áüûÿF÷ì¸6Ï»âVñ))Ô—’˜“ÇoÞHïA½Á°Î­VWDUQ%Éy})œv%©]»Ñ{ÒÅÄוÓ"T|Éé$téIÞÌrÞoî#+áƒêIA´„âIyC.º”Ä”xÌ=»ñ«q¤åÏdô5WçQ:¤GoÔµ;Åk—S½kK"°KTÄ â–¢âPçÖhÿ¶Ý@a²OŒ½n¬)2“‘e%’“v– ^û-¨ó)P‚5΂X÷K{ ÆN·ò•î•Ò<,‘Hކh÷'àŲT¸€ï&fu»ûê“QPpÈ}» €+œ÷G Z-ry¬t§ÿ¾âUÝéÿ¸\–ûLtÛ7˜þ- õ˜¦À›šŽÛ٦Ⲫ]kH¡;{†f-§¸ü5µ˜Â'9—Êaã „ „á(žçU±j5\Æ*Æâ hP= ·ð74£zð&YÑÞÆ:{ö¶ ÒîXŸÙÇ)ë8µ?¡@\>|ÉÐ[ïË>V¶+k]ÕcYœÛë(\ØüþÛ¼}Çeͦ¡ß ,ÅzP¶¬‡gˆÖN‰…-,ìß´ˆÃ*p™$†ßŸŸ“¢Ü¶ð6Cé—ƒuv%ɉ æ­W˜õŒÙÒ0ïVÍ@ЈuŸlÁºO‰Xz¿Ê.%‰äˆ8ƒ¶£Ý srN;ƒôÞR °…ßñ¿IL—+#Ä!.A¦vˆ—Q«å…ËM|F†µ¬ÑºÓmhrLÑû04@€7-ÕJ?d^TØm2!Åfèл¹©C(Ê ¡A¸¼Ä‡ëBíù޵­Ã}f @ñùðÆùÀ<ô¼Bìó­cj:=yÎ%¥G~|í¾€Ï?­¯Sûa© k[H$I‡!ƒ·%É‘ˆå.a»L QT×À¾ç^†¢vìètGaš–EÀпBûÃ¥(Ži_Òèl·×ì—Ó°Ž«3öõe0Mð&Å3à‚k¦iD\y\D®WÄ-‘H$Œ‰ähˆN3k×®pÃ⳺&ö>czÇ×­Hb ¨?a*.7HÄšf [T8SÏJ$‰¤‘ÂB"‘‰X »Ó– ŒÏŸp.Þ¤¤“ÒZ!9ù14È,HFÁ@˜žm _gÒ).Zã¨ÈûXŸI$’“û÷ìLlAŒ×/‰äpµV8F0,oÂ4+€ù$Bõ€;\Þð .¸ã­`í£ÞŽܾHðsLìmÇÛ¶Û¡ZmPsçªu<‡[Ɖ걖?ž:$f§ÑeÀ€!@*­E°}4RPH$’¯+2@ ……D"9±*mÛnP*pZBz—Ä.ý‡¿~ TSgÿÂçùäO²öõùh ÐXÂ/>ÎâßÿÝk÷ Ž”Œ[€Ð›ÙýÖ3,}ú Öü9¦+ÆMU€ÙRʺçgÉ“`Çç{Ž‹*4oÿ„Oþü¥(1ŽO¨Ð¼g5«þù7**[bö‹Z^˜Í–%ÏÏ!¤Ç*ØyäMœ†¢ª=ÞD\öœqA²`ÞÑ#`| IDATëH$§Ñ¿åù]ŸdcŒ‰ä8Жµ`XZ~?Rº÷:!*m!JüÏ^_FúèKéuѹ$5ígÝ?ï¡d_3ÃRòè5<ïÈ©´:¶?w ŸoÒcÊ#N‰•¥I0š°î‘Ÿ°?hÿýB Çæu\ö¬6 ´ì˜Ïg}äѳÈîqhutÅ õŸÏcþƒw3mÔ¥dgÅ¡æ¨è”~ô7>]9‚Á_€ç8>Q rFœŽÛ—hªÏÖ;x[ŠŠ¶‰v‘0ìÿX߯ì5H$'.p«ÖøQ¿éðí>p o‰äHD§˜µ;i @Az~|iq„Z:±AаkêÖQ„ Êš£Ö„žgDÖ±—PÂþ;.{7ŠâšÂ..®Qa×›–‹i× z¬ž·‚Šê%d-«„ë\˜Šßlm]n£Û±m"Çt¤šσËj¡E²6©ëÕEÚ¬º¬ãŠT7ŠêBu[û0­ô¸F’O»€é÷eѽ[ vQs¡†Û>-ÂJ ¡Æƒ¢¢¨´Úæ±G{`š˜•MZ^!¥W÷ŪÇÐÌ¡×­Ó 'Gâ#8;΃@³†òÞF='Ï ‚Dò5GUaõ^…¡«´mÅX|e¤°H$ma[)¢ÿ·ƒ`s…ÙÙƒFvj÷L¨`6ײ}ÑlJ6¬'d¨ø²òè6êLz ‚"¬wó¾l[ø{ ¼I$v/${ÄYôÒ»u/³ílU5¨XùÛFCc žø4R{¡ÇØÉtéž®;¡:!{>|›²ÕKñU2ŸCßs'ï ÷Ì¢ü„TÔmXÊöÅRY\†+¡ YƒÇÑûŒ©$&»ŽØy.¨ÿbUµA²%1Ó ÁåJƒH Ûè øÜ@ Ž¢å+ ©>¡‚KÅl©d÷¼%Tì+AÄ¥“5ìLr »càJÊ"³ïâ¼.K ¹!P¶“ÝK>¦¾ª5>´¡“è5¤Õn£W¥¹d;û×~FsC Þô\rÆKjº·ó:¡&—J÷áã)ݸzVa·­…Œµ8”X®Ñ#›•5MFù³ Ÿ"„0[[~¢­@òÜJ$GôÓ·•%B€i˜ºÒÍZ ë7]a»]Å…‰äHØbÂé%€—/>³Û ÑG,×^̺½|ô›ï°ê½ZæKeì¯ÞæÌ+&ѸöMÞ¸óVJ÷h½„|ÆÝÿç\8è½ê†íøüã4FYcûMââ¿Ï%73¢»ÊVÿ‘ÿ~ö;ÇR³~Öã\~ÿ÷q‰Öý.— v¿÷WÞûù©ih½ñnçý‹îÿ=)ÉÊa;åŠ Ê>xˆ7Ÿ{ŸI®bâCñ—¬âµ¦ÒH ½³‹ACÒiØ<Ÿ·¿w9ñ—¾Ä…çÄ¡jûYöÛYToßtp[înÃ9ÿ¯ï3ø´®”¿û<¯þägLy«”C³©_õ!ïÿòzvn-vœ€\ÆÜõ*ç\=¡¸¡ìf_ÿ&µE%érθô‘?“’ :Ír¡º¡ë ÑXµzbÇX„íMÒbáÀi­°'»²Á„‡C:É`ÆaYƒ¼á):û–t7“H:§õA OA „U(Ô®¬]ì¯cÿ¶£Ý¢Ú),$É‘ˆc¡Yî¸xWZ^_ÌN•VUØñÞ_âÂCÆà (Ù•-¯=C­¿–•¿½!ç¿Ïž²D…èÆ€ïÿŒÜ¤–?uuu»)zoÁK†¾ë£º1«÷°ì§ilø^CûÝ;Ð6¿Ê’ÿ, qëR¶o.¦×Yiö þ$Rz#wL6ûß™º:?ųïbíE3Ó/"@„P Vlç?ÿ5 ~<ÙãuÅ,‚»^eÝüÕøà¬w6S®½ð°1†ÝÏ» ÷sïP²ÿ tc([Ò¬f3»¾ØÊ°ã©Ú´–挼à,\ͯ@K-õU©œûÄbúŽÈeÛó¿`áÓ/±æýå ŠêÂ@*4U³ò·²skýïšÍ” ÆÑ´éMfß~+Ÿ?û+†Ÿ7Õ傪¸Æ\Æ5ù=©¾2þä2¶.x‡}Ûîfø˜n1*¤wB@rN/„¢&š†ž ”É å´ZÈŽok¢­vÇCÇꨀÝXb-KXÄ>¬B„¶°°$Iça  KP“-*±ÜBCD~×NqÑ®qRXH$’¶ˆîŒ9G~}@Nr÷žxâ;§~…£©’½‹þ@BÁéLú¿ôî)ÈïžÌÇs>B‰óÒTÛDúé·sV^Fâ0†ÍGÓö¥ìô*Ô¡£Å8µièf<Ãoz€ÞÕÍdŒžInA¥Ê^„X€©xqÎþ“NJŸI\üÌkä¸);} /ßñ3šôzv½ó?Fý|ÐAO(¡º¨_7—Ý~P¼Þü ã/Yy.e»¦±ó6ºŠÓg]ˆ÷0•ÌMâ{O¤ ;ìX¼ÀíPºàc˳K„¨ùì3üßOùÆÑÅxz÷RC7ÝŒºíŒšy:¦C.½‘µ¯½A`×.¦£W¨ªøK–³féN²Î¾Ÿé×]‚GÔsnaâ­{رGG01€<μÿßä$`ºz1nÖ%l]÷ÍU¢Û—ýÆÓ€ø´LâÓ³Ô¦ÊÒžÀFÚŽ±Pœ1&{ô3ˆeб:'vP<Žåœƒ‰¤ópþ^mqa[-œbž§ÑÚª]³¿Ia!‘HÚ"ú{ˆÀP°„EÏôü tÞ¥i†Ð››Èè=•ìn‚@=ôøÖ\5+BA‰óÁ„‰Ô¼òw¾xñ6Vü¡Œ@cã1Ý1…¡¡¦w!{à*^ûýäÏ4×Ôlñ[ ĸs¦åŸE—|7ÁFH{Y‰wÓT¨ØKÈêØ¸À–Ð0‚l}d&Ûahj yO%šÞÃ¥¼5Á”Gî”ÉlyiµŠØº½Š¬q?$Ïü„-{PUt%‹—:ýwd¤B¡šCf·B+]5!W¢³¡. n¦¢ÒRº‹Ð}b!n—Ü4`Èwb€ae Ú BÜ822ÐB Lpgå:¦}¾: Ó€øôlâ3ºˆ¦ÊÒÜðìXׯUÓJ£µkE€ˆh0‰tTÜ´®r.Ï­DÒ¹8­ŒNaáÇúÝúÓ-4laá´X´RXH$’XDÆsf†²…E×´^…Vö£N ÐUª˲kU¤3ˆ†®ŠYÍÂß\ɪ÷W Ýϼ’‚Ó†PöÉ3l[½ó¨v#Ü^‚Û?ãÝ›fP^ÕŒPzÑ÷’kÉîêfýSQÓŒàul€HåÐe…b§pr‘qæåôȈÃ0U\Þ8EG$Å%Ž|·7=*]OÅgü‚íÏ£©¡Œôé—3Ð×ÈŠ'W²ké{ì*JbØ÷Gáöb¥iŠO@M÷!la` Ìñ ؇IoÝ¡(–Ï‹§´ÊKÃí|š˜Vƒè䦾´ Ò»P݈ÍLÆŠAÄ‚íVá<&­-"um¤h“H޶8p Û…Ñ~ÇÿNqጳh«…‰¤-¢³¾(ŽÉ¤%dvCXI‘:±AVg·zï<ªJî¤WoØøðÕ¼ÿâg¨ñœÿÇ?Pºm}où+þßMxC•¼=ï·á­ù¶§¨&µVÓPÕ "Ÿs~ÿ>£¯éGó²7Xö”ußjëþSõÞٔシ>.ŠæÏempã4o«ž¹Õ73 n7ý¦ÿ„I³`44³{ÉBš=—J$…kˆ˜òºYCÇ“ÖÍÍŠ‡îÀ4} ;TÏtâêŸgÉ}w¢¥õ§ë á}c˜ ųÃ0ñeYnL5{«0…eœrÇé,»õ|VïS¹ôo³QUBæ áPdšà‰x’RÁª¾­DM±2-?!pŠ »ƒ}þìϬ‹»µ¨nPÉñÁθ(§å"à˜œÂ"ZT´ RXH$’¶ˆ®¸í %!3Û JbzLº–U+ž¢qÛ">øá¥äèÎæWçÓT߈šûm22“ì" T/ÿŒíó3ÙûÁßÙ°³ èÇ4?¬j p[wZQÍþõsH|n!ëŸy4T4ûA¤Z+Aý¶ù̹é|rúw¡dá;„ /§ŒPq$ŸŸ¦‘<ü|r³îb_E3+~}Í[.„ÿcí‚…„ èvÓ?é;nåseùüõôºøv†œ>ú yS_þ²{õåÀu(]§‘Ó3¯8‚®°a_3éýsèÖ'5RSãhO·¡áËOa¡Êöÿ>ΪÓ'2fÊPŠæü¥/À]p%ñÉ>LÓ8òÆ:¡XqXéf㱢ѮPÎŽò×ÛåÑΔ¥ûçag‰Îl[RXH$SXD[.œñ¶°Θ*c!‘H:§kƒ³ó„^_RJ§6F7}.¹ƒAËV°qÉ”®|“Ò•Ögq='3ùç·“–“Iá°‰ïz—ÊÕ/ðöêñ$$§ÒÔRKé¶©(¾S ù[ÂEâ BÍVŒƒÖbvÖôÖ­kKØòìÙÄ¥÷Àí`°™¢·çÐ2ýF4„‘‚Ó|ÖÙY\ƒð$}¦£Uì%°LASÓ=ˆ)wý÷~ÿå%ëXõ·uöJdMú&ç]{5nj׿ΦwW!úOeð¤Ñ‡Z†L0¼iŽíÚeëH7š”xPEÙ§bÃËKIíu©Iá‚yzš›0œ®\¦N¨¹£YL-HC×p¥õâÌ;Ÿ¤ö7óñ-§ñ¿¤ õ¸Ó0ñŽ{I‡¿ Z­Ó  ‰P¨ó¦q]À‰X)g£-Òª5¶¸pújGŸ'§µ"º¢¹cy‰DÒñ8o²Naa‹ ;ÊiÁˆåÕnHa!‘HbÝAˆv'Iuy}Š;.¡s2B…1uP20ýñ9¾ó—`àMÍ¡`Æ·è–ŸN £ïù'iC_á@Y“L÷ 3Io xÝFL5 K!ÿòû˜>lqÙyø’{sÆ=ÏÓTßBÖÈÓpuÉeêïÞ#gþ\šë[ð&çÑ{Ê4ôQQ¬áÍê¡%1ôÎçéZªßk8žº-ïØ…n*¤õ9~SÎE5ÀˆÏ㌇ÿE&H1ŠPd_xW™Ä–E h¬­ÃÔ\$ôIÿ™3INh~Ƚä>ú.ý!q ÙV5ëçÄBKdzÖ¤=U€.\ô¹öq<7’5a¦ßZ7qØåœ÷è(zä¥Gª”'ç3éW/@ÂpD3$˜Éyt¥[·TB~È<çûÌêžÏ–OVâo âNêNîÄ‹é1°+ÁIþå÷3mJ:^Wx{:$¾„éô¥ÛèBŒPç]É …ôÏ¡AÛ±Š»Iw¨È¨§Óraãt‘rZ)œÖ ç9•H$‡³-,œéd®QNKE¬‹¯Œ¼H$’X(D _ùÂS…DÒùD ‹X#ÖätƒÂñú• ‰D‹XY¡œêr ¡Ÿ[ˆ©ƒæð·ÆÁÏp'Y`u‰±\0êBÎ"ØÂ †f½?Xä-ZDùÛˆ]w.g‚Ö|è"†cû†cÿBÆý(øñƒ —±°¯0g ·ý¿ÝI‰.0ëÜJ$’ãƒéxu §È0£^ëµ RXH$’hÚòCwNnUu¡ªªìðv$ºL[t̘ ºÝ D¬Ú±®mI{ÔÓY•\8^íËÑ™ýIžK‰äøcÆx]›"º^…cݯŒ‰äp´åe˜¦‰a²7!9á0tÌVã¶·%±qZ,œûüµ•’VžS‰¤ó‰% b‰†h E‡ Ja!‘HÚ"º“àì˜PÐ44MÈ® ¬˜ é²t¼´€Ó4lb)$¾Ñ‚‚ï9Šù‰¤s8œÀ8Ò¼vC ‰Dr8Úê,膦u~ÊŸ-HK]=j| nŸûx·ækôcZ•ûl‹…ç<Ù>:b¹YH$É!È*™‰ähˆv‡ ZC×NÙ®™ÖtÄy Š>æ¿7œÃŠ‹¿ôpMôv­™±÷yð3%l)9†ï@ˆc_ç¤B€òƒiÚ¢ÚНH$I;#……D"9VÐji65Ë)ÕKS½VÕÂ415kpV(Ö|t]7Q½ º@˜ < ˜Mز–†úzÜqÖòî8k™ƒknoøÆþß®7aj¦iÕŒp{­y. Í@¨Ör6Š \.ÐýA´–UgB=ü± —µ?SÓ1‚ºUçÂÛÎ'ð@ð×UƒU§"ˆ‰DÒiHW(‰Dr¬ Ñ4Í¿±þ”éš*n-þ5¥f ùýMV¾ºßø›9ïú‹¡v«þy;Ö— ›*qY½~ý¯è="—Ú¥ïóɰ÷¿2¿©ˆÓfNbÉ#÷¢0äô1˜¨z Kû5Mj.nù1¾†]|úÔcˆ!³pï|‡=+·1ôæßá+~ƒurT`²á­ÿÒÜÄ›Òþßø%Æ÷E˜P·a!Kžúu•µ˜¦ ¾ûXÆýßÝt둊£¨†¢Býú9¬xþ_T•VaOj/ú^|ƒ¦Œ<¥RO š*ËÀ*‚.}(‘H$’Î@ ‰Dr´8ƒ9C@cSu¹÷T VVTÊ%ï°lõzV PÓsÈĨÚÍÂÛ/fíÊu¤ 'ÞkP¼l;Öl`Æßߢks55û‹Tî§¶d?ÁšÝlxç0!“ÆXUµÍ&vÌ{j×0FÝòcâZªÙùþs”>ÿh Ý T®_ÈæW>c»×MBN?¼J#û–/dïŽrRŸŸ.åÌýéÕì/VÉ5žøêìœó;ÊŠ¶séßÞ kºb´Q!Xü?æ|ïJö7©ä ‰[ R´äEv.™ Ïΰ¡9­×9Éi®®€ˆ°°9E®T‰D"9q‘®P‰äpDçößë@}såÌSh´Û—@Ï‹nç³W3ëæ+ØýæïY¿rƒn}Ž>ø‚ï}¸–kyßþå|üÌ+$y-³žx€7þ‘Ë}„x·Ô®:ý—¨¾Dܾ8k]¨¸Ýnp§3ä×oñí·–2xÂ`„p:ý®’ïÎYÃ÷æ~Áç!¸k+U*©_¿œâäÜú*7½1›k?XËÌï_IÝÚU”lØ‹5\¤(аq.û«aÜ]opÝ[sÝÛŸqÕï•S¾¿åy!?jh;½¢‰DÒH‹…D"i‹Ãu¾B@UÝþÝù§’°0CDÖÆÝöÝzúÐ+)Yú4†k½Î˜@Ó®Ô7ø ºçÁŽ¹Ë¨ÿ%ĩឹbbêu·U šdº’ÉßœN‚¦Â4B?Ñß¾ž845‰~çÎdñÜ¿¨¨†x*P;ç–wû)™yýèù§¸õ&®¸ô`ë}¤Œ»ï¾{ no2+×Ð\WCÙºM„tש¢*ð×ÖÓ\S Pž{¸oC ‰D"iG¤°H$GCôHo(­Þ½t”SÃÝ4 ÜqÉx¼ôz½Ñm ïß<‰UX'À ÑT­àI(#_ØÌ<¬_X¬„Dn<¾®¸MÕt †“šÂÓmYRŒ€FÆÙrÆ KYôÏ—øàGK€$ºŒG¯‰3wýѺ–† 5l{ö^¶®ù‚†²üÍ-Ö‡ªï” @Ph®*£©ªÌÊ8Ô*/…„D"‘t RXH$’#Ë$”ÖîÛ‰  ú|§N—Í01È¡††pMâ¢GÀ§Å…ªê IM-úØM+`AˆÖ©šÄ!æÌPë¢z ˉGµ× /`„0]=vó?(˜u75ÛV±ó£÷ØýÙ|V>±wÎΘ5 ÓaµP=:_üö–¼ÿ!Ig^Ïä]NV~Äîwyógw¢ŸB'¡@Km%ÍÕp <Û®gË­O"‘H$íÈ)d—H$íˆÙÆd8^kM FmñîSÆG?Å•ˆ/§Sß‹Ñe…SϤðœ³é5(‡MÏ>Ȳy«- A¸Ÿª¸Ü(.p{SHª÷W€O<Ô¯YBÝÞR„û(ÆsÚèöª*ì™ó¯ß< R?ú]ô-f<ñ®þýý€I}õ>Œ(‘"ZªÙ_²%{3ú'£/™FÏ¡4(¦± ”8Ï)õý5”• üA ‚Ö×­‰DÒÁœB‰DÒNÄêlEwÐP­ù›*6­A9ElŸæÁ?V â’)œþT±—~~ë>\Lѧ²ðæY¬ùx>în]p{@Q=¸’Ïæ±sù&HÍ#3ö½þK–½úÛçü‹îÿµM®(·13ÖéŽÙÝ5ªBuì_1—ﻗݫ׳õ'lüàCÀEbz×C¼ÒLoŠ£bÛÞyŸ,aõ“wñÖC¡c²gù"jªZ¬¢y'9FÊ·|–”*–@)*$‰¤Ý9Eº‰¤‰¶PDO(5t½æÀúå)C¯¼Ç­­íƒi•¤V½.Ôp0¶„nS®ç‚»Jù䡇xû†EÀD¥ïÍeòµ— tˆËÍàqƒùbÁߘW½—«_x—ñwÝJùãÓ{f:á Œ_B…0¬JÚ„ª‚H‰TÖ€*ÀãnŽ¡º‡nxè=ãFÎúâs>~åA^ùäO(4¡ùùßüæN¶jM<&ÐÍ$&Þú3ªö]Ϫ‡¯d}uÓ½ì›÷4¥ÿºuƒF2á²ÑèÎä¬'ôAÉš¥»±Üõ¢-±®k‰D"‘´§RÜžD"i?¬7à|@<$)@pgÞéS'Ìzê}E᤮i! T]NÐP‰KψŒü p+P·{•E%ÂKBv/2 òp 0 Ë·?TWN]y%®„t’»Y–ƒ†=[i¬ª_2)ùñê5øƒ—–bhV½W*qiq»¸¡† !/ éÉá´´`ú›hªmÄ›’‰'Q…@€Ê­«©))á%!§Y}ûáq»@ž ZJ·Q±«/‰9ýÈÌˤ±h µ-¤ôè‹/É{RÊZjkùÇôüµÕÿ>š€:¬Ô³M@3Ђ%ƒƒX2̉D"ùŠHa!‘Hb!ˆ ‡ ‹äðëÅÉ=ò¿qÕs‹H®µµ¹“Û(V ]¡±,˜Ö2­²/)ÌÈd¯/ÔÈMÖ´ÈÛµ?ۚѪ "<ß ïSX8L{bÛAw'Ó Û|„uOT7ìúxÿ½ù‚€¡…þ|4b ‹Æðd ‹ ­……D"‘HÚé %‘Hކè {”wkCñ­z×VWF~Þñk];q¸š¦~„‚Æ¡Ÿ²NÔ±ögÆrÐ1£:þflËÄá0C÷«Í'+Š v/¡…*€Rb_¯§ÊáJ$É É)®'‘H:ˆèl:zø½žL Ü4ͽ{—-@?ÆŽ®DÒ^üu-”¬ýÀ>¬âxö5뜢…†D"‘HÚ),$ÉáˆôjO–ßúæ=Ÿ}€î?Éý $'-Š jvo§z×f€-XתFÄÕ)V·D"‘HÚ),$I[8ÓrFøÚÿíÕ{¶‡J¾ø Õ}\Ú)ùºc¾U‹i©­n¶Óö5ëÒj!‘H$팉$m¥›v+Ø­¥[>| Eµ)‰¤ µèl™û2À žÖשFÄuϾf¥ H$’@ ‰Dr$Œ¨ÉÂïk {—/¢¶¨LŠ I§¢ªPºa%¥ViÀz¬Âx¶”SÛâÂ~•.Q‰DÒÎHa!‘HÚ"Ú'ÝéJâì´™ÀçÕ»·†Š>_b¥A=IJ$¬ zhÕ~¡Zó\b &a¥9uyÃËDû!Ûw[SÌ*×áý»|Ö¤ÆÈÙw0e¬ˆ´Mu;Òà:P\‘vÅÚÖQßI€P`ãœÑCÁr`­-kÎ {)($‰¤Céf%Éáˆv‰rŽkŽ©Ó\¿æå'Fö;çR"ÕåN\„0h)/%áNÏÁ£•Q¼v=qãÈP¿{¥[·c)ùCÈêÝU ÅSÁh¬bÿšå4Ö6"|)dôAzn„Â4hª(%äáÍêŽê¯¤lÓF‚†‡ô£HËŠÍ:‘ŠŒÆZö/YDÕz )…£é1t.5\sBÒ\Q?>]; IDATf@Bzò-”íÜ ž2&9Õƒ¡Ù¢E§zã Êöâ&±G²ûöÇã §©ÖHýž ”nÛŠazIÎBVA¯ƒû;PÜP¹s;½ °+”ûc[ÔÚÊ%Å…D"‘´3RXH$’¶po;¶í‘`ÛÊε¼xÍÿ†íùß|µÏä©hãÐâ£E5X¼_\Jцݤ˜Œ§a%û6Ô1ú±åä䪬üÝøßë¯ÓÔP‹azð&%“3åνûAÒSu›1ç®[8°}'¡ Ї¸” ºÌºŸ x=Éf) î¹”}ëv’5n bÍŠªªAQñ¤f1ñÞç~Îé(š×ÍgÑ#ßeûºr‚þ p'¦’7ãLþñ¯IKUÐ+ÖóÁíWP²?D×a½©^·–úÚfT_êHÎ~ðeÙ­¼˜^ËŠE«hilÀ“”FjÿqLøEºæ¦C¨5ú)K_}•ÿoïÎã$© »ªº{î½O`Yv9äpå\ˆ*Š€âMˆMƼŒQ£Ñäyr=QcŒG4ž(Dae÷_ùy¶¬\î§fÛÆ¢R̼׽‹àûŸà{Ÿÿ#ÃÃôÌ9“y‡ ²uÕ&¶?z;;†Ú9rÉ ¹ã]¯âÑGVÐ>óN|Û¥´l`óÊ•ì¼ï6:_Ăû¸ï›ÿÎæ§×0ôÔÃl†ž9!CÛú)õï`ÕÏïaÎéoez÷Z~òÎóxä¡õTʳN»€i¥lß¶…mÿœ­}!G½âLÂ]¹ÿÊÿdëú"ÛW® {ò‰Ì8x˜íë·2Òÿ ÛÖ9ú^Ãêoý5×ù*JùÉ,üƒ+X´¨— ÿ‰c]r&a!OÜÅ·üQoáœO}˜Þ.(?sÃÃýïâ¶¿}3A°sÅ3”6l§¯¯Ì¤ù9xºÂÆ›>É7–~…ÙÇœÄä9‡rê‡?ÄÂã†Áµ»}ò™ïá¸Ó_D>Ç_ò¿yôG×°fuÛW¯aËäå”bçžÈq¯};Ý]mÐ{Çÿ¿xäçÉÈÖô¯XG||Ž Z>ö‹™;¿‹pä8æ{,½€aÚ¦ÀÐÓ÷ò“Ë^Ì=ÇœÄôƒçsðÅÃK^wÁÚû)ï Üü+~ññäøv­\‘ßÆìØ¸óçÈÿ‚ßH®[Ÿ\Îýßù<À½ÀZFûV¤ý+Ò&Qµý+l%Iσ…¤}‰IšCÕö³ÈvÜ.e–àÖ5Kqø×|¥í¤·½sb÷µ¨šqòqtæ Ròm„ù6ŠŸ`mßâ8"lëbÚaGçæÓ9ýXÎøÄçúçÿ`ãcÒ¿}kîXÇà‰ëÆ÷?œ~îÌÝŸ?÷ÐY„D%ÈOžAaÚ,XÝGy¨H)Ü@ÜÕAÔž‡(éÞ5u !ŒDýDƒƒ$ÿ  —îžYCUˆÊÑîíT8úmǺՃ¬Zº”më6°eÙmlY_ÿ?l|ð_8ïg‘ËwTïIÖö¯{|Áa´ï®É™˜¢ ÜûßÿÂÀ¦uÛ€»­¥Èþ,Örv"–$5=ƒ…¤g“†ŠÚY·³µÅêÒÀ“¿^óOçåï|׿÷r*trÐ[þ•×\þfæ3‹õ¿^ÊÆÕ}ÁØ‚ÿÀÏÿ[¾t(ǽh.ù#lÞ:¹.¦ö"æ·Lʇôõ=ÂmŸþùà2ò›–qÇ¿è8øæ¾à âò–gù ‚h„GþõB~½<ÇÔÅgqÎ'¾ÈI/YÂÐ}wsïÖûˆ+eòÓæÓ;s6¬ØÅÆk¯fÅ’S8hÆ÷ñ<~ß‚¹'rfQ/”ÊÏãwý[Èåa×ÚõÜñÙ•K¿f´¶"»¤µµCÍJ’žG IÏ&;I^¶EZ +06T´›ë»þÊ7Í:æø¶Óþä/©”˜`á"&*'U)Q%$(Ž î˜Çï½ãÏYûgïe×–[¸îòS IK¥!ǽõ|ž|6 ŽžÇcKW°ò‹È]{ayƒ;ú€™Ì?úrˆãêpZ#;XöOIJÌÖ§w9Gžº˜i‹Xòæoró×®gû]_âîúÒèJ¹vN¾üCÌ™ •g*TJI»²J4úEÆ•¤j!* {&sÈ+.྇¯bó¯®ã[¿,Szslß°€Ù¯ø=zçÌ—½—5÷]ÁŽm·ñãw½$s|pô%ãà9m”'ظIA•‘2·üŸ÷³åɇw72:êS‘±5{«µ°9”$=&ø¸’&˜€ä÷F@ý!hÓah ÀN cݯï\0óˆ˜uÔDè xTØôøÃ„…^æ-y •44Š ûˆSYtê l^OPè¦}ò &Í;–þ×çø½·]D{ç$œñ*r[ÖQ$GB®s2“ç¿€%øN¹à4 •ÁÌi9Ê´qÐ’ èé¬Ð¿£¶ž©tN™IïaKxñ_•%¯= Êô±ùÉGÉu/dÁY¯gæü©AÿÚGéï˜y̽ôæžp.3gرu'…öqØÎ¤¹ 8äµäÕùçôtäé\x ‹^rƒ[6ºhŸ<ƒžyGs»?ÇË.{#y˜Pîƒ êÎÏ}Šû¾ñé2pð$I¨.ƒ$ÃÌ36`Ô6‡š@G&I­eâO+i" …ê’ÖNtU—n`Ð[½M￾göÁÇ¿öŸ¯äЗœ>¡:sçÚ’ùârµãvF‡\Ã}}TʹžÉ´w&°ã8y_˜‡‘}”ËbÚz'S($¯ó#kùö¥/çéûç ·7øTúˆÉÓÖÓq²^²±¤™O¹o€áÁ!BÚz§ÑÖ•RfB²Ý¨4:;v˜O"(Œ~V4\dx`âˆ\G/í“r»÷}Ìñõ÷Q)E„Ý“éèbÌ:Bõ¸—]ùnúäÿ¦44pð ’0ÑW]vUoû«Ï‘„‹aöj"$µ›BIz.êÍÂ]&©¡¨m5ÌhøÈ?í߸¦÷ǺtákÿùJ:édÊÃz÷ëÛW§ò¸ å “&Ñ$5ÙõÓÇùžIäv×vTJì1CPP*U ßÝ Œ†‚ÑKÞvuÓ]]g÷geש³¿Q™±5AÕÏ òítNmOžªóÞÝÇ×3‰BDûþ>!­©xèêorÓ'¯ 44xp7£?gÙ¥ÈØ¡Ò>6’¤Ä ò$ý&j;p§¸ÓöíÙÓ«Æ[kw¬Z¾üûþžºùFòm4C}iœð£ÊÞ¯âÇÄ•šub  ÂA¶…Õpï£ qº­}mï9ïzœù¬½m3Ýÿ UKAR3„°ôkŸç†ÿ ¥¡Á¥À-ì9³vús–.ÙŽÛÙPa3(Izž5ßvIGÈØ¾is¨v “±M¢Ò¥»úülà5퓦yæþ‘]|9AŽ ÕïbÿŠÚ¶‰òH‰|÷4:'u7z‡šF¾ †w pûg>ÊÒ¯ý[UÊK[Iš< ’4yê¯>Nï’„Œ´#;—…ý+$é°ó¶¤ßFYj‡uî‡$½g*#ù·ýxÞögžfñBzfNß}5¿µ´u÷ÐÞ;™B[[£wf ª})ÂVÞy+?ùðå<öã+‡â8úðKFûN 0¶Ãv(ö6"”µ’t€,$ý¦jÃì=LÔXC÷m~âÁßpM'Q@÷ŒytϘB˜¯6Éi•"`+Ëó$ÌA¾#éȾñчøåþŸýß÷³cÕSk€›€GHj  ƒ™¥v$(;kKRƒØJÒo£v¨Ùt”¨’&QÙfQÝ@Ou骾ÖLNŽëwh×a§¿Š§ŸÃÁ'¿Œîé3’­d£‹ZG5pÅ1ì\½’U÷ÜÌÓ·ßÀÊÛ¯§Ø¿k;p?I è# iˆ`´éS6d¤}-j'dzã¶$@þÉ–ôÛH~È‹l¸HF£" ÙpÑÌŽÏÚ&uNžÖÞ=û &Í9„I³&Whãw£\ø»pŒ02<ÈÎõ«è[¿š¡-Ú±m0Ž£ÀãÀr`IHH;i§¡"Û* i¿ ›AIRƒ,$ý6²Ížj'ÆK;sw0:ÇE&z i¸HCI7IȘL®>î þèu­ò»+f‘”w5x_¤ £ý%¶“Œ¶•$ ¤3º[#ÑϞ͠jûV¤Í ÒQË ’t9…¤ßFv`Õì¬ A%]/}OvŒIxh«>îV16 Œ‹Ú†Q­.òÀkIf‘~Ö<®¹Ÿ]²sN¤?%ÆÎ‡’R6&jk)jûUD™mJ’ƒ…¤ßV¶˜íн¯u³¡" #$5mŒ†‰I-H¶xöó[!TÄ$Ç’ô%ØA먑 –éÏAJÓŸ‡4 dç¤HD6`d;k§µµ¡Âš Ijƒ…¤ñHCET½­P¿Ð_;±^*²á",²ÃƸëuénÆ ‘¬óŒöh•ßɵ…ú¨æ~í$‹ÙŸƒl3¨lÍE½@QÊ|Fv(ƒ…$5@«ü“Ôi.d´IT¥Î:µ…É´ YbÏÚŠÉï¦l°ÈY ­Ñ$*f´Ø`'ɱ·‚ÚB}¶\ú3“ Ù`‘ÖZdCDöñc;jgŠ¡B’È`!iH rµ¡"ûz6X”2K‘±Á"Ïo,šULÒI’`±ƒÖ Yµý,êÕXÔÖ^¥µµA¢ÞÈOiMEí¶$I˜ÁBÒþ-ÐU2Ïe¯T§K9³Œ0¶¦¢¶¶"dl¨¨íÞÌ"’ѱÊ$µ­,"öüy¨m • éÏGvnŠtl¨°¦B’&ƒ…¤ýeoá"½­×Ï"[C‘Þ¯|¯^ h…`Qf“–·ÑúÁÆ6…Ê‹làÌŒRÍsÙ âèO’4Á,$íOµá"dl!0[k‘6Ik(²·ÙšŠôöª™¥ÝK$¡b­,`Ï…ÚŸ‡líC¶F¢LýfOvÔ–¤ È`!iÛÛêì’³“ë¥5ém6Tdçwh…PI I:%o'r¶•ƒEö~ÄØ¬zµ•š¥6Œ*$i‚1XHz>dç·Øsî‚£59ÆÎ[±·ÚŠV› /¿£HRc1Hë‹zäeïg&ê…ŒÚײï©ý|IRƒ,$=Ÿj –04ÒàŒÚ0Q;1^+…  k„¤óöIÀjEµA 6XÔC[[›Q[;a ¤ È`!éù–­½H;Û¦êÕ ûê¬Ý*#BAÒª³z»³zÛª¿“÷ jCC´—çmö$IM UÿˆIšxêMš–µ»¶†Æö­ æµf7 t“ÔXôUo÷6H+¨÷ÿ_¯yT½çê½_’4Á,$Hµ…ÄÚ‰¨æ1{yÜ "’`1L,J´v°ÈJCe½àP¯ùœ$© ,$5Ò³Úç[MIMÅ£íßµýmjŸ—$5!ƒ…¤‰doÍ^ZµÀÙK,àw³ÁïÚñJRK«×~Y’t`t4…’$©é,$©q À@£wB’¤ýÁ`!I‘Î:n°$µƒ…$5F¤ŸÛ`£wD’¤ýÁ`!IÑFRca°$µƒ…$5F¾ºØy[’Ô ’Ô9’ßÁ¥Fïˆ$IûƒÁB’#ÁB’ÔB ’ÔiÅȳ­(IR30XHRc¤Á¢Øè‘$i0XHRc¤Á¢Üè‘$i0XHRcÕ%jôŽH’´?,$©1‚ê­ÁB’Ô ’ÔiEÜè‘$i0XH’$I7ƒ…$5F\]‚g[Q’¤f`°$I’4n IjŒ´ÆÂßÃ’¤–à4IjŒ´Ó¶¿‡%I-Á?h’ÔÖXH’ZˆÐ$©1Ò¦PùFïˆ$IûƒÁB’£BRk‘kôŽH’´?,$©1¬±$µƒ…$5FZcQhôŽH’´?,$©1*Xc!Ij! IjŒˆ$\Xc!Ij  IjŒ2IE[£wD’¤ýÁ`!IQ©. IRK0XHRcØy[’ÔR ’Ôe’`ÑÞè‘$i0XHRc¤Á¦P’¤–`°¤ÆˆIšCYc!Ij  Ij›BI’Z†ÁB’Ç IRË0XHRã”pæmIR‹0XHRã1XH’Z„ÁB’gÈ5z'$IÚ ’Ô8i°½#’$—ÁB’g˜¤)”µ’¤¦g°¤ÆIƒE¡Ñ;"IÒx,$©qÒ¦Pvà–$5=ƒ…$5NZca°$5=ƒ…$5Ž5’¤–a°¤ÆÂ>’¤a°¤Æ±ÆB’Ô2 ’Ô8ö±$µ ƒ…$5ŽÃÍJ’Z†ÁB’'íca…$©é,$©qìc!Ij IjG…’$µ ƒ…$5Ž5’¤–a°¤Æ±ÆB’Ô2 ’Ô87+Ij Ijk,$I-Ã`!Ic IRË0XHRã  IR ð™$5N^ “ÑfQe`W÷K’¤ßXÐè¤ß!³¿g4DtçŽêòYà+ ÚGI’$I\p?Ërv£vP’$IRsøCö*î&5jç$IúmÙy[’¬›€ûxýç@ßÚI’$IMì3Ô¯­(K¸_’$I’šÈ)$CÍÖ‹{p²#f´!—y=¨>Ÿm¢•½_ûY»†û“íÿiw'ç ¹ i ìÙS­¶°Öl'»u¤•Q²øÐ Æù\Àôî€Îî䣻¦ÂÛÄ\ó$„aŽÎ< o‡·|)䚣U?%æÎnã¿þ¨ƒW/`ËÓðG_†n&³twåøâåݼiñ¿º.¼¥ ݰ¨°|{z<%æ,èæ‡—U8yö0Û×ÁE_ ¹eUŒìþ¬º%Ï_3…O»ƒ`'|ðJ¸}sÄÁSvìˆé߽ݼvÿ÷¼®¹BIIDATm0Ÿ¸>ò‹±û/åT~xáv‚ðö¯„\õÔØã›3«/^ÖÅù‡ö.$iq¸YIû]@¥ +7¬Þ «6Áã«àîà£7A_[ž×¼(†Xö#ÈÍëäÇïïä©Ãû øÅ¯úù»Û{xá pÝ¥Éç¾ùåÝüèÒa¦W›M僱m—ò¹€B®Úò(€®\̺ÕEºíæ›ïêà’#’æKý%¶µµñ“÷uòð_ÀÙ³áÎû‡ùåúαM²Jð¢ÁªÛãÙ=üäG˜Y½šþèš ÇžÜË# ¸åm0«qÕ²a¶Tò°®ø.ܽ)à“—ôòÔÇáš‹!Ø\ä¾°n(7¶‰TâpÌOEÜñÜþxÀ÷oƒß—džY½í;m€¯]C* !¯^ÜÃßœ óº`ýÆ>ôÈ2ܰ, “ üýe=\ýX؃>p[H¥ €þˆåý!œPà¸!9`ÃÊþööâþõû$¡"9ëØ.>u~Žù“)ón䇫z òPÈ'ÿkv¼ì…Ý|èt˜šˆøÞ²!¶Wr¬{¾p;@È[ÏžÄÏ®€ó$íœn¾±Ÿwµqåu$¡"rÎqÝ|ô,8¸ 6lá¯PfK9Üçw(I:°¬±´ßår°k[‘‹?[¢#G°c(Ú}qyÑiÝ\qô€kî‡mùW]šçìƒú †¼4æñ€oÿ¢Â§_pÂAI€˜73Ç¢Y°¾ÿ¹íÇH æÒÅïáð®”¡RÂ<ÿyiž—Ïí‡üíKá–«"6ô…{ô‡hë‚ãNÎpø¬˜ò6(U`Á‘“øîÅ»èÎÃѳá÷ï€+"v~¶.]/zåv(âWÀæð®¸sc/-ص×+îÛFøã/fv€ºrüíÛr̆o®M â‡ÛÃ×/ÝÅôf·Á7ÀC«#~µ¾“íC´Å1Ë7ļxqž}C…gŠ9 ““š¥8†8‚0ŒùÔ¥½|pɆ6ÂË?pׯ˜[‹xäiøÑòd/^vj×½e]mpÞ!pÁW`Õà7=ÖÉë–¤_aħöòÝ·î¢+€®"|ø^ó•8`ë ìªöÁذµÄÚ¨ƒ¿n˜³×èj˜58‡«Mà;¦›¯ýa³r0¯ÞõxdM…»7ôpÞ!»ÒÊ IRƒ,$íwq¹®<ïxe£ÚG¨Ä¹0`Åò _}0`ã#ƒÜ¸~çNïcàRáÏ®*qd®@D@9Џi[™RPbÃH©QÒì¦üvÚ-G‡M)px÷`Ò’¦ZˆÎÍíà„ýI4†i“b*#u>$†Ru›ÙíW"XòèΑ|NÍCTJ)dÍ0›ïêãV¨Da±|]R ~x[ž‹Ûû¾‡ù€Órtç“P±uGĺÁа|k ³!ˆ“×Ö<ÚÏ ?B ŤÃIigĶ¡Ž<$ »v•ùÒOÊ\ykŽÙ“r¿¸“ÏœÒO>7[ÚºÛ¹è0³á ócîÚáŽÏì‚b ä„tå,^GM‡UkaÙÖ€b¦Îá]…d½½£ßg% ˜7Žì‚{#nZ6ÌϘӛãˆ9!u^Ý#T*IpZÿøÇ,9¾Á‘äøÊ}›ú÷]ë#I:° ’ö»J½“rüÉiGM®Œ†3aþÕ1﹩ÌÃÛ œ;3iyærœ1+¢ƒ Ú#Çñ¯éÈLÏUöÚŒ>;M„uJ™•j˜óJ! ŸŽXÄhßòà7(¤FÀÌŽxçBb‚ÌÖ’gao…R 1!GÌnãÂBÌKç í3$õÎhã;W„œ<% +Ÿ„ ¾ðÐΈ¯Þ Ê·wfžW²çñ½ wßß¡$éÀ2XHz~ÄP®ÄcgÚnƒ%s ƒ˜µýAR Í'ÁâÏ/8ªstŽŠ›ï…í]ÌÈWX_}._Æ4tý#Écrï€]ƒAçsØ·Êow™;Ÿ:6*ñ^>'€öêK/]ÜÅßœ¾-©¾ÈÁúUpê6Ÿ\Üç|A… Ø=Ôê‚E°`<´úKI-JZ“râ¾üÆ]„!¬]mÉå+Ìëá[7¬ ¼ôDø»‹*<±>ruÈm#ž^SbC±@HR€/îâ–•“xûñ;a |{e@LLnvž…S ߌDÜòdÄŸž…NxòQX±)Ù£¦G2}Tâz@>ˆxâq¸ê©6fÌ øú_T†Ë|ÿnøÈÏ¡”ydkQœì׋tðÅ7í"Ÿ‡õkáÍÉñ– ­’¤†3XH: ’ñ„žÙCœy,|nù0ýþòz˜”ƒüÞðc8éô—,}ïš1Åèí†c€ïß=Â'8®«Ä¿\˶à f=û¾vgÀpéY~qÆ%òœ¹ ÈÂø÷ë†8{A§ÏégåSðÆÿ‚»‡–[àö1¹^ qŒÎ6^rœ4 €IÓà•ÁáúŸp),î(òí{bîßVaʼ<·]Qà×ËJüãÓ#ôNÊsÅ9Ýœ1c€ÉÕNïAÒ”wO7Q®DüÙ qçÉí¬_UâÆµI©ýÜ£áÈÃàÕsaYüàö~.édÑä2wÜ_by®ç‘ÌWòló¶å"^ ¾n‚sNìæOOb˜r’Û*1ÓfåyÅÁpý¸ñöäøŽï)òÝ{"îÝZaòì7¿·9Á`ãfc—$a°´ßõÀÎ"Tê4M*ä’‹Ì=Q¢o8ä÷Oxý£!WßÚÏ÷…Ì`ÙΈéó;øø9erLé…;àÊ÷³n}×¾} O€Ü?Ì©Ÿ é vÅ“óƒ#Õëä1ôc ˆƒÑ–;Ũ”Æ^K¢ä ŨN D S&ÁÉðÝëûX·¡“+/b®Œ-ÑöÀ®‘˜b¦,L:…¿ë¦!Îü‡³§ع±DãïÞØÉâÉ;êvÜ.WŸÛ9Rór;¼€ˆëþ§‡øåÖn>pþ÷­ùÁú ߸mh÷ª“zò|ìü6Ïè㢳àšM°¼¯Ì§®N;@ĹüÜ {†x2ÃöáþóÖÑͱ°ƒ¿:µ!¼ÿBXúÕ€›6EüøW£Û+´‡¼éÕÝœ¿hl"@Äp¦f¨T=˜þ" G^õÂ~iÀµ+"nXÚÇ K3Ûœ×Ãëbþ ¸sUÈ5ë*|ëö!¾U}½§;ÏG.hã¤Þ~‡›•¤ Ä`!iÿŠà¯†cJ‡tÔ\M.Ã Ž‚o\0ÐQ¢43i |çòˆ«î‚»¶æ¨!0 .:¹ÌÑÓ†¡ ½ð…ËáÆgrÌœ^¡§ >øF8xQÀÛCÂBȋ˱pxˆGŠ&S†.øÄE=e »÷ã¼Ó`Ú`DW0º¿ó…Ï^ÜÆ™óëÌ-C{/üçåpÃÊ3§GLî„¿¾¦1;'Ï´ÉS(Ñ9¾ýÇ1×Þ÷m-P$€\À‘³.>±ŸY@\õ¾ˆïÞË6å*…tu‡¼fq̇ö;”$M0ާ!5^HÒm ­ºt]@70 èÞxôœü;n¹¢Ìœ©LüvåmŒ¶yªm¦V_3¯WûZö™(³Ç™¯®—Î5—~kéä{ToƒÌçd·“*d֩ݧ{¿žÝ~h¯³~½ã.0ÚÏ$Ïî§÷*W}OöûÉ~~:ûÐioñä=ÙmÀØBwºÝê(XIùê~àúÂù?‚B{~ªÂáÝ¥äóòìùÿn/=†ìgÁè÷žgúýä«Kvn½°z¬#™÷VG›Êް;f?êßD–‡ïÜò–¯EÛ+Ÿ>` z¸º”Øó—¤¦a…¤ý¯Þ°­©ˆ¤Uû\ööþÚ‚äè$ÝõÅP· C½B}½}z¶í×[¿Þ~g ÜÏ¥0\aïá¦Þçg¿‡½m£œy=Øsåjû¸ƒ¥p4€ìk{éÿUíþîí{/×Ù·¨ºn¦öhmfŸ{®ß¡$©! ’&ŽVî„{ ŽíÙ¶QïõfLƒ“Γ/䘔{¶„õ·õ›x®ŸÕÊ?’Ô ’ô»¬ §¾î=½ qywŸI’~S Iú]÷lÍÊ$IzÂg_E’$I’öÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ ’$I’ÆÍ`!I’$iÜ RãÅ™¥Þsq½7Ij*Ùs¹öüö—Ô ’$I-Í`!M,µW5£êbDj~é9mm…¤–”oôHÚ­^S¨ÝƒÚó$gmt@÷KÒo+…ÜîGéyegŸ—¤¦f°&–Ú+™Pèޏþ±™=1Q4p%=W¹\̽«Bâ8 [Y©YÕ“ZRÓ3XHO6T¤Ë¶µ;¢?þvq £‘lĉÔ8{Ô:ŒT*q³$ i¨Èl)IjJ iâ¨(*$aá®(f{1žt@ÐHÎåIjZD¤‰'jƒCXçyêÜJ:°ê5gª½@Þ7THjIB¤‰koÁ¡^ˆð\–&†Ú`Q{k Ô²,ŒHÍṄÏg©±öjŸ7PH’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$I’$IÒ¾üѼT× .ÄIEND®B`‚pyramid-1.6/docs/narr/router.rst0000644000076500000240000001454712606630333017533 0ustar michaelstaff00000000000000.. index:: single: request processing single: request single: router single: request lifecycle .. _router_chapter: 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 :app:`Pyramid` hands off a response back to WSGI for upstream processing? #. A user initiates a request from their browser to the hostname and port number of the WSGI server used by the :app:`Pyramid` application. #. The WSGI server used by the :app:`Pyramid` application passes the WSGI environment to the ``__call__`` method of the :app:`Pyramid` :term:`router` object. #. A :term:`request` object is created based on the WSGI environment. #. The :term:`application registry` and the :term:`request` object created in the last step are pushed on to the :term:`thread local` stack that :app:`Pyramid` uses to allow the functions named :func:`~pyramid.threadlocal.get_current_request` and :func:`~pyramid.threadlocal.get_current_registry` to work. #. A :class:`~pyramid.events.NewRequest` :term:`event` is sent to any subscribers. #. If any :term:`route` has been defined within application configuration, the :app:`Pyramid` :term:`router` calls a :term:`URL dispatch` "route mapper." The job of the mapper is to examine the request to determine whether any user-defined :term:`route` matches the current WSGI environment. The :term:`router` passes the request as an argument to the mapper. #. If any route matches, the route mapper adds attributes to the request: ``matchdict`` and ``matched_route`` attributes are added to the request object. The former contains a dictionary representing the matched dynamic elements of the request's ``PATH_INFO`` value, and the latter contains the :class:`~pyramid.interfaces.IRoute` object representing the route which matched. The root object associated with the route found is also generated: if the :term:`route configuration` which matched has an associated ``factory`` argument, this factory is used to generate the root object, otherwise a default :term:`root factory` is used. #. If a route match was *not* found, and a ``root_factory`` argument was passed to the :term:`Configurator` constructor, that callable is used to generate the root object. If the ``root_factory`` argument passed to the Configurator constructor was ``None``, a default root factory is used to generate a root object. #. The :app:`Pyramid` router calls a "traverser" function with the root object and the request. The traverser function attempts to traverse the root object (using any existing ``__getitem__`` on the root object and subobjects) to find a :term:`context`. If the root object has no ``__getitem__`` method, the root itself is assumed to be the context. The exact traversal algorithm is described in :ref:`traversal_chapter`. The traverser function returns a dictionary, which contains a :term:`context` and a :term:`view name` as well as other ancillary information. #. The request is decorated with various names returned from the traverser (such as ``context``, ``view_name``, and so forth), so they can be accessed via, for example, ``request.context`` within :term:`view` code. #. A :class:`~pyramid.events.ContextFound` :term:`event` is sent to any subscribers. #. :app:`Pyramid` looks up a :term:`view` callable using the context, the request, and the view name. If a view callable doesn't exist for this combination of objects (based on the type of the context, the type of the request, and the value of the view name, and any :term:`predicate` attributes applied to the view configuration), :app:`Pyramid` raises a :class:`~pyramid.httpexceptions.HTTPNotFound` exception, which is meant to be caught by a surrounding :term:`exception view`. #. If a view callable was found, :app:`Pyramid` attempts to call it. If an :term:`authorization policy` is in use, and the view configuration is protected by a :term:`permission`, :app:`Pyramid` determines whether the view callable being asked for can be executed by the requesting user based on credential information in the request and security information attached to the context. If the view execution is allowed, :app:`Pyramid` calls the view callable to obtain a response. If view execution is forbidden, :app:`Pyramid` raises a :class:`~pyramid.httpexceptions.HTTPForbidden` exception. #. If any exception is raised within a :term:`root factory`, by :term:`traversal`, by a :term:`view callable`, or by :app:`Pyramid` itself (such as when it raises :class:`~pyramid.httpexceptions.HTTPNotFound` or :class:`~pyramid.httpexceptions.HTTPForbidden`), the router catches the exception, and attaches it to the request as the ``exception`` attribute. It then attempts to find a :term:`exception view` for the exception that was caught. If it finds an exception view callable, that callable is called, and is presumed to generate a response. If an :term:`exception view` that matches the exception cannot be found, the exception is reraised. #. The following steps occur only when a :term:`response` could be successfully generated by a normal :term:`view callable` or an :term:`exception view` callable. :app:`Pyramid` will attempt to execute any :term:`response callback` functions attached via :meth:`~pyramid.request.Request.add_response_callback`. A :class:`~pyramid.events.NewResponse` :term:`event` is then sent to any subscribers. The response object's ``__call__`` method is then used to generate a WSGI response. The response is sent back to the upstream WSGI server. #. :app:`Pyramid` will attempt to execute any :term:`finished callback` functions attached via :meth:`~pyramid.request.Request.add_finished_callback`. #. The :term:`thread local` stack is popped. .. 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 traversal, URL dispatch, views, and event processing, see :ref:`urldispatch_chapter`, :ref:`views_chapter`, and :ref:`events_chapter`. pyramid-1.6/docs/narr/scaffolding.rst0000644000076500000240000001660312634676134020477 0ustar michaelstaff00000000000000.. _scaffolding_chapter: Creating Pyramid Scaffolds ========================== You can extend Pyramid by creating a :term:`scaffold` template. A scaffold template is useful if you'd like to distribute a customizable configuration of Pyramid to other users. Once you've created a scaffold, and someone has installed the distribution that houses the scaffold, they can use the ``pcreate`` script to create a custom version of your scaffold's template. Pyramid itself uses scaffolds to allow people to bootstrap new projects. For example, ``pcreate -s alchemy MyStuff`` causes Pyramid to render the ``alchemy`` scaffold template to the ``MyStuff`` directory. Basics ------ A scaffold template is just a bunch of source files and directories on disk. A small definition class points at this directory. It is in turn pointed at by a :term:`setuptools` "entry point" which registers the scaffold so it can be found by the ``pcreate`` command. To create a scaffold template, create a Python :term:`distribution` to house the scaffold which includes a ``setup.py`` that relies on the ``setuptools`` package. See `Packaging and Distributing Projects `_ for more information about how to do this. For example, we'll pretend the distribution you create is named ``CoolExtension``, and it has a package directory within it named ``coolextension``. Once you've created the distribution, put a "scaffolds" directory within your distribution's package directory, and create a file within that directory named ``__init__.py`` with something like the following: .. code-block:: python :linenos: # CoolExtension/coolextension/scaffolds/__init__.py from pyramid.scaffolds import PyramidTemplate class CoolExtensionTemplate(PyramidTemplate): _template_dir = 'coolextension_scaffold' summary = 'My cool extension' Once this is done, within the ``scaffolds`` directory, create a template directory. Our example used a template directory named ``coolextension_scaffold``. As you create files and directories within the template directory, note that: - Files which have a name which are suffixed with the value ``_tmpl`` will be rendered, and replacing any instance of the literal string ``{{var}}`` with the string value of the variable named ``var`` provided to the scaffold. - Files and directories with filenames that contain the string ``+var+`` will have that string replaced with the value of the ``var`` variable provided to the scaffold. - Files that start with a dot (e.g., ``.env``) are ignored and will not be copied over to the destination directory. If you want to include a file with a leading dot, then you must replace the dot with ``+dot+`` (e.g., ``+dot+env``). Otherwise, files and directories which live in the template directory will be copied directly without modification to the ``pcreate`` output location. The variables provided by the default ``PyramidTemplate`` include ``project`` (the project name provided by the user as an argument to ``pcreate``), ``package`` (a lowercasing and normalizing of the project name provided by the user), ``random_string`` (a long random string), and ``package_logger`` (the name of the package's logger). See Pyramid's "scaffolds" package (https://github.com/Pylons/pyramid/tree/master/pyramid/scaffolds) for concrete examples of scaffold directories (``zodb``, ``alchemy``, and ``starter``, for example). After you've created the template directory, add the following to the ``entry_points`` value of your distribution's ``setup.py``: .. code-block:: ini [pyramid.scaffold] coolextension=coolextension.scaffolds:CoolExtensionTemplate For example: .. code-block:: python def setup( ..., entry_points = """\ [pyramid.scaffold] coolextension=coolextension.scaffolds:CoolExtensionTemplate """ ) Run your distribution's ``setup.py develop`` or ``setup.py install`` command. After that, you should be able to see your scaffolding template listed when you run ``pcreate -l``. It will be named ``coolextension`` because that's the name we gave it in the entry point setup. Running ``pcreate -s coolextension MyStuff`` will then render your scaffold to an output directory named ``MyStuff``. See the module documentation for :mod:`pyramid.scaffolds` for information about the API of the :class:`pyramid.scaffolds.Template` class and related classes. You can override methods of this class to get special behavior. Supporting Older Pyramid Versions --------------------------------- Because different versions of Pyramid handled scaffolding differently, if you want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X, 1.2.X and 1.3.X, you'll need to use something like this bit of horror while defining your scaffold template: .. code-block:: python :linenos: try: # pyramid 1.0.X # "pyramid.paster.paste_script..." doesn't exist past 1.0.X from pyramid.paster import paste_script_template_renderer from pyramid.paster import PyramidTemplate except ImportError: try: # pyramid 1.1.X, 1.2.X # trying to import "paste_script_template_renderer" fails on 1.3.X from pyramid.scaffolds import paste_script_template_renderer from pyramid.scaffolds import PyramidTemplate except ImportError: # pyramid >=1.3a2 paste_script_template_renderer = None from pyramid.scaffolds import PyramidTemplate class CoolExtensionTemplate(PyramidTemplate): _template_dir = 'coolextension_scaffold' summary = 'My cool extension' template_renderer = staticmethod(paste_script_template_renderer) And then in the setup.py of the package that contains your scaffold, define the template as a target of both ``paste.paster_create_template`` (for ``paster create``) and ``pyramid.scaffold`` (for ``pcreate``). .. code-block:: ini [paste.paster_create_template] coolextension=coolextension.scaffolds:CoolExtensionTemplate [pyramid.scaffold] coolextension=coolextension.scaffolds:CoolExtensionTemplate Doing this hideousness will allow your scaffold to work as a ``paster create`` target (under 1.0, 1.1, or 1.2) or as a ``pcreate`` target (under 1.3). If an invoker tries to run ``paster create`` against a scaffold defined this way under 1.3, an error is raised instructing them to use ``pcreate`` instead. If you want to support Pyramid 1.3 only, it's much cleaner, and the API is stable: .. code-block:: python :linenos: from pyramid.scaffolds import PyramidTemplate class CoolExtensionTemplate(PyramidTemplate): _template_dir = 'coolextension_scaffold' summary = 'My cool_extension' You only need to specify a ``paste.paster_create_template`` entry point target in your ``setup.py`` if you want your scaffold to be consumable by users of Pyramid 1.0, 1.1, or 1.2. To support only 1.3, specifying only the ``pyramid.scaffold`` entry point is good enough. If you want to support both ``paster create`` and ``pcreate`` (meaning you want to support Pyramid 1.2 and some older version), you'll need to define both. Examples -------- Existing third-party distributions which house scaffolding are available via :term:`PyPI`. The ``pyramid_jqm``, ``pyramid_zcml``, and ``pyramid_jinja2`` packages house scaffolds. You can install and examine these packages to see how they work in the quest to develop your own scaffolding. pyramid-1.6/docs/narr/security.rst0000644000076500000240000007067612621241570020065 0ustar michaelstaff00000000000000.. index:: single: security .. _security_chapter: Security ======== :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 :term:`resource location`. A context is located differently depending on whether the application uses :term:`traversal` or :term:`URL dispatch`, but a context is ultimately found in either case. See the :ref:`urldispatch_chapter` chapter for more information. - A :term:`view callable` is located by :term:`view lookup` using the context as well as other attributes of the request. - If an :term:`authentication policy` is in effect, it is passed the 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 a :term:`permission` associated with it, the authorization policy is passed the :term:`context`, some number of :term:`principal` identifiers returned by the authentication policy, and the :term:`permission` associated with the view; it will allow or deny access. - If the authorization policy allows access, the view callable is invoked. - If the authorization policy denies access, the view callable is not invoked. Instead the :term:`forbidden view` is invoked. 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 policies. To provide maximal flexibility, :app:`Pyramid` also allows you to create custom authentication policies and authorization policies. .. index:: single: authorization policy .. _enabling_authorization_policy: Enabling an Authorization Policy -------------------------------- :app:`Pyramid` does not enable any authorization policy by default. All views are accessible by completely anonymous users. In order to begin protecting views from execution based on security settings, you need to enable an authorization policy. Enabling an Authorization Policy Imperatively ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Use the :meth:`~pyramid.config.Configurator.set_authorization_policy` method of the :class:`~pyramid.config.Configurator` to enable an authorization policy. You must also enable an :term:`authentication policy` in order to enable the authorization policy. This is because authorization, in general, depends upon authentication. Use the :meth:`~pyramid.config.Configurator.set_authentication_policy` method during application setup to specify the authentication policy. For example: .. code-block:: python :linenos: from pyramid.config import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy authn_policy = AuthTktAuthenticationPolicy('seekrit', hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) .. note:: The ``authentication_policy`` and ``authorization_policy`` arguments may also be passed to their respective methods mentioned above as :term:`dotted Python name` values, each representing the dotted name path to a suitable implementation global defined at Python module scope. 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:`userid`, and matches that userid's :term:`principals ` against the principals present in any :term:`ACL` found in the resource tree when attempting to call some :term:`view`. While it is possible to mix and match different authentication and authorization policies, it is an error to configure a Pyramid application with an authentication policy but without the authorization policy or vice versa. If you do this, you'll receive an error at application startup time. .. seealso:: See also the :mod:`pyramid.authorization` and :mod:`pyramid.authentication` modules for alternative implementations of authorization and authentication policies. .. index:: single: permissions single: protecting views .. _protecting_views: Protecting Views with Permissions --------------------------------- To protect a :term:`view callable` from invocation based on a user's security settings when a particular type of resource becomes the :term:`context`, you must pass a :term:`permission` to :term:`view configuration`. Permissions are usually just strings, and they have no required composition: you can name permissions whatever you like. For example, the following view declaration protects the view named ``add_entry.html`` when the context resource is of type ``Blog`` with the ``add`` permission using the :meth:`pyramid.config.Configurator.add_view` API: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_view('mypackage.views.blog_entry_add_view', name='add_entry.html', context='mypackage.resources.Blog', permission='add') The equivalent view registration including the ``add`` permission name may be performed via the ``@view_config`` decorator: .. code-block:: python :linenos: from pyramid.view import view_config from resources import Blog @view_config(context=Blog, name='add_entry.html', permission='add') def blog_entry_add_view(request): """ Add blog entry code goes here """ pass As a result of any of these various view configuration statements, if an authorization policy is in place when the view callable is found during normal application operations, the requesting user will need to possess the ``add`` permission against the :term:`context` resource in order to be able to invoke the ``blog_entry_add_view`` view. If they do not, the :term:`Forbidden view` will be invoked. .. index:: pair: permission; default .. _setting_a_default_permission: Setting a Default Permission ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If a permission is not supplied to a view configuration, the registered view will always be executable by entirely anonymous users: any authorization policy in effect is ignored. In support of making it easier to configure applications which are "secure by default", :app:`Pyramid` allows you to configure a *default* permission. If supplied, the default permission is used as the permission string to all view registrations which don't otherwise name a ``permission`` argument. The :meth:`pyramid.config.Configurator.set_default_permission` method supports configuring a default permission for an application. When a default permission is registered: - If a view configuration names an explicit ``permission``, the default permission is ignored for that view registration, and the view-configuration-named permission is used. - If a view configuration names the permission :data:`pyramid.security.NO_PERMISSION_REQUIRED`, the default permission is ignored, and the view is registered *without* a permission (making it available to all callers regardless of their credentials). .. warning:: When you register a default permission, *all* views (even :term:`exception view` views) are protected by a permission. For all views which are truly meant to be anonymously accessible, you will need to associate the view's configuration with the :data:`pyramid.security.NO_PERMISSION_REQUIRED` permission. .. index:: single: ACL single: access control list pair: resource; ACL .. _assigning_acls: Assigning ACLs to Your Resource Objects --------------------------------------- When the default :app:`Pyramid` :term:`authorization policy` determines whether a user possesses a particular permission with respect to a resource, it examines the :term:`ACL` associated with the resource. An ACL is associated with a resource by adding an ``__acl__`` attribute to the resource object. This attribute can be defined on the resource *instance* if you need instance-level security, or it can be defined on the resource *class* if you just need type-level security. For example, an ACL might be attached to the resource for a blog via its class: .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Everyone class Blog(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'add'), (Allow, 'group:editors', 'edit'), ] Or, if your resources are persistent, an ACL might be specified via the ``__acl__`` attribute of an *instance* of a resource: .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Everyone class Blog(object): pass blog = Blog() blog.__acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'add'), (Allow, 'group:editors', 'edit'), ] Whether an ACL is attached to a resource's class or an instance of the resource itself, the effect is the same. It is useful to decorate individual resource instances with an ACL (as opposed to just decorating their class) in applications such as content management systems where fine-grained access is required on an object-by-object basis. Dynamic ACLs are also possible by turning the ACL into a callable on the resource. This may allow the ACL to dynamically generate rules based on properties of the instance. .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Everyone class Blog(object): def __acl__(self): return [ (Allow, Everyone, 'view'), (Allow, self.owner, 'edit'), (Allow, 'group:editors', 'edit'), ] def __init__(self, owner): self.owner = owner .. index:: single: ACE single: access control entry Elements of an ACL ------------------ Here's an example ACL: .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Everyone __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'add'), (Allow, 'group:editors', 'edit'), ] The example ACL indicates that the :data:`pyramid.security.Everyone` principal—a special system-defined principal indicating, literally, everyone—is allowed to view the blog, and the ``group:editors`` principal is allowed to add to and edit the blog. Each element of an ACL is an :term:`ACE`, or access control entry. For example, in the above code block, there are three ACEs: ``(Allow, Everyone, 'view')``, ``(Allow, 'group:editors', 'add')``, and ``(Allow, 'group:editors', 'edit')``. The first element of any ACE is either :data:`pyramid.security.Allow`, or :data:`pyramid.security.Deny`, representing the action to take when the ACE matches. The second element is a :term:`principal`. The third argument is a permission or sequence of permission names. A principal is usually a user id, however it also may be a group id if your authentication system provides group information and the effective :term:`authentication policy` policy is written to respect group information. See :ref:`extending_default_authentication_policies`. Each ACE in an ACL is processed by an authorization policy *in the order dictated by the ACL*. So if you have an ACL like this: .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Deny from pyramid.security import Everyone __acl__ = [ (Allow, Everyone, 'view'), (Deny, Everyone, 'view'), ] The default authorization policy will *allow* everyone the view permission, even though later in the ACL you have an ACE that denies everyone the view permission. On the other hand, if you have an ACL like this: .. code-block:: python :linenos: from pyramid.security import Everyone from pyramid.security import Allow from pyramid.security import Deny __acl__ = [ (Deny, Everyone, 'view'), (Allow, Everyone, 'view'), ] The authorization policy will deny everyone the view permission, even though later in the ACL, there is an ACE that allows everyone. The third argument in an ACE can also be a sequence of permission names instead of a single permission name. So instead of creating multiple ACEs representing a number of different permission grants to a single ``group:editors`` group, we can collapse this into a single ACE, as below. .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import Everyone __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', ('add', 'edit')), ] .. index:: single: principal single: principal names Special Principal Names ----------------------- Special principal names exist in the :mod:`pyramid.security` module. They can be imported for use in your own code to populate ACLs, e.g., :data:`pyramid.security.Everyone`. :data:`pyramid.security.Everyone` Literally, everyone, no matter what. This object is actually a string under the hood (``system.Everyone``). Every user *is* the principal named "Everyone" during every request, even if a security policy is not in use. :data:`pyramid.security.Authenticated` Any user with credentials as determined by the current security policy. You might think of it as any user that is "logged in". This object is actually a string under the hood (``system.Authenticated``). .. index:: single: permission names single: special permission names Special Permissions ------------------- Special permission names exist in the :mod:`pyramid.security` module. These can be imported for use in ACLs. .. _all_permissions: :data:`pyramid.security.ALL_PERMISSIONS` An object representing, literally, *all* permissions. Useful in an ACL like so: ``(Allow, 'fred', ALL_PERMISSIONS)``. The ``ALL_PERMISSIONS`` object is actually a stand-in object that has a ``__contains__`` method that always returns ``True``, which, for all known authorization policies, has the effect of indicating that a given principal has any permission asked for by the system. .. index:: single: special ACE single: ACE (special) Special ACEs ------------ A convenience :term:`ACE` is defined representing a deny to everyone of all permissions in :data:`pyramid.security.DENY_ALL`. This ACE is often used as the *last* ACE of an ACL to explicitly cause inheriting authorization policies to "stop looking up the traversal tree" (effectively breaking any inheritance). For example, an ACL which allows *only* ``fred`` the view permission for a particular resource, despite what inherited ACLs may say when the default authorization policy is in effect, might look like so: .. code-block:: python :linenos: from pyramid.security import Allow from pyramid.security import DENY_ALL __acl__ = [ (Allow, 'fred', 'view'), DENY_ALL ] Under the hood, the :data:`pyramid.security.DENY_ALL` ACE equals the following: .. code-block:: python :linenos: from pyramid.security import ALL_PERMISSIONS __acl__ = [ (Deny, Everyone, ALL_PERMISSIONS) ] .. index:: single: ACL inheritance pair: location-aware; security ACL Inheritance and Location-Awareness -------------------------------------- While the default :term:`authorization policy` is in place, if a resource object does not have an ACL when it is the context, its *parent* is consulted for an ACL. If that object does not have an ACL, *its* parent is consulted for an ACL, ad infinitum, until we've reached the root and there are no more parents left. In order to allow the security machinery to perform ACL inheritance, resource objects must provide *location-awareness*. Providing *location-awareness* means two things: the root object in the resource tree must have a ``__name__`` attribute and a ``__parent__`` attribute. .. code-block:: python :linenos: class Blog(object): __name__ = '' __parent__ = None An object with a ``__parent__`` attribute and a ``__name__`` attribute is said to be *location-aware*. Location-aware objects define a ``__parent__`` attribute which points at their parent object. The root object's ``__parent__`` is ``None``. .. seealso:: See also :ref:`location_module` for documentations of functions which use location-awareness. .. seealso:: See also :ref:`location_aware`. .. index:: single: forbidden view Changing the Forbidden View --------------------------- When :app:`Pyramid` denies a view invocation due to an authorization denial, the special ``forbidden`` view is invoked. Out of the box, this forbidden view is very plain. See :ref:`changing_the_forbidden_view` within :ref:`hooks_chapter` for instructions on how to create a custom forbidden view and arrange for it to be called when view authorization is denied. .. index:: single: debugging authorization failures .. _debug_authorization_section: Debugging View Authorization Failures ------------------------------------- If your application in your judgment is allowing or denying view access inappropriately, start your application under a shell using the ``PYRAMID_DEBUG_AUTHORIZATION`` environment variable set to ``1``. For example: .. code-block:: text $ PYRAMID_DEBUG_AUTHORIZATION=1 $VENV/bin/pserve myproject.ini When any authorization takes place during a top-level view rendering, a message will be logged to the console (to stderr) about what ACE in which ACL permitted or denied the authorization based on authentication information. This behavior can also be turned on in the application ``.ini`` file by setting the ``pyramid.debug_authorization`` key to ``true`` within the application's configuration section, e.g.: .. code-block:: ini :linenos: [app:main] use = egg:MyProject pyramid.debug_authorization = true With this debug flag turned on, the response sent to the browser will also contain security debugging information in its body. Debugging Imperative Authorization Failures ------------------------------------------- The :meth:`pyramid.request.Request.has_permission` API is used to check security within view functions imperatively. It returns instances of objects that are effectively booleans. But these objects are not raw ``True`` or ``False`` objects, and have information attached to them about why the permission was allowed or denied. The object will be one of :data:`pyramid.security.ACLAllowed`, :data:`pyramid.security.ACLDenied`, :data:`pyramid.security.Allowed`, or :data:`pyramid.security.Denied`, as documented in :ref:`security_module`. At the very minimum, these objects will have a ``msg`` attribute, which is a string indicating why the permission was denied or allowed. Introspecting this information in the debugger or via print statements when a call to :meth:`~pyramid.request.Request.has_permission` fails is often useful. .. index:: single: authentication policy (extending) .. _extending_default_authentication_policies: Extending Default Authentication Policies ----------------------------------------- Pyramid ships with some built in authentication policies for use in your applications. See :mod:`pyramid.authentication` for the available policies. They differ on their mechanisms for tracking authentication credentials between requests, however they all interface with your application in mostly the same way. Above you learned about :ref:`assigning_acls`. Each :term:`principal` used in the :term:`ACL` is matched against the list returned from :meth:`pyramid.interfaces.IAuthenticationPolicy.effective_principals`. Similarly, :meth:`pyramid.request.Request.authenticated_userid` maps to :meth:`pyramid.interfaces.IAuthenticationPolicy.authenticated_userid`. You may control these values by subclassing the default authentication policies. For example, below we subclass the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` and define extra functionality to query our database before confirming that the :term:`userid` is valid in order to avoid blindly trusting the value in the cookie (what if the cookie is still valid, but the user has deleted their account?). We then use that :term:`userid` to augment the ``effective_principals`` with information about groups and other state for that user. .. code-block:: python :linenos: from pyramid.authentication import AuthTktAuthenticationPolicy class MyAuthenticationPolicy(AuthTktAuthenticationPolicy): def authenticated_userid(self, request): userid = self.unauthenticated_userid(request) if userid: if request.verify_userid_is_still_valid(userid): return userid def effective_principals(self, request): principals = [Everyone] userid = self.authenticated_userid(request) if userid: principals += [Authenticated, str(userid)] return principals In most instances ``authenticated_userid`` and ``effective_principals`` are application-specific, whereas ``unauthenticated_userid``, ``remember``, and ``forget`` are generic and focused on transport and serialization of data between consecutive requests. .. index:: single: authentication policy (creating) .. _creating_an_authentication_policy: Creating Your Own Authentication Policy --------------------------------------- :app:`Pyramid` ships with a number of useful out-of-the-box security policies (see :mod:`pyramid.authentication`). However, creating your own authentication policy is often necessary when you want to control the "horizontal and vertical" of how your users authenticate. Doing so is a matter of creating an instance of something that implements the following interface: .. code-block:: python :linenos: class IAuthenticationPolicy(object): """ An object representing a Pyramid authentication policy. """ def authenticated_userid(self, request): """ 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. 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 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, userid, **kw): """ Return a set of headers suitable for 'remembering' the :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. """ After you do so, you can pass an instance of such a class into the :class:`~pyramid.config.Configurator.set_authentication_policy` method at configuration time to use it. .. index:: single: authorization policy (creating) .. _creating_an_authorization_policy: Creating Your Own Authorization Policy -------------------------------------- An authorization policy is a policy that allows or denies access after a user has been authenticated. Most :app:`Pyramid` applications will use the default :class:`pyramid.authorization.ACLAuthorizationPolicy`. However, in some cases, it's useful to be able to use a different authorization policy than the default :class:`~pyramid.authorization.ACLAuthorizationPolicy`. For example, it might be desirable to construct an alternate authorization policy which allows the application to use an authorization mechanism that does not involve :term:`ACL` objects. :app:`Pyramid` ships with only a single default authorization policy, so you'll need to create your own if you'd like to use a different one. Creating and using your own authorization policy is a matter of creating an instance of an object that implements the following interface: .. code-block:: python :linenos: class IAuthorizationPolicy(object): """ An object representing a Pyramid authorization policy. """ def permits(self, context, principals, permission): """ Return ``True`` if any of the ``principals`` is allowed the ``permission`` in the current ``context``, else return ``False`` """ def principals_allowed_by_permission(self, context, permission): """ Return a set of principal identifiers allowed by the ``permission`` in ``context``. This behavior is optional; if you choose to not implement it you should define this method as something which raises a ``NotImplementedError``. This method will only be called when the ``pyramid.security.principals_allowed_by_permission`` API is used.""" After you do so, you can pass an instance of such a class into the :class:`~pyramid.config.Configurator.set_authorization_policy` method at configuration time to use it. .. _admonishment_against_secret_sharing: Admonishment Against Secret-Sharing ----------------------------------- A "secret" is required by various components of Pyramid. For example, the :term:`authentication policy` below uses a secret value ``seekrit``:: authn_policy = AuthTktAuthenticationPolicy('seekrit', hashalg='sha512') A :term:`session factory` also requires a secret:: my_session_factory = SignedCookieSessionFactory('itsaseekreet') It is tempting to use the same secret for multiple Pyramid subsystems. For example, you might be tempted to use the value ``seekrit`` as the secret for both the authentication policy and the session factory defined above. This is a bad idea, because in both cases, these secrets are used to sign the payload of the data. If you use the same secret for two different parts of your application for signing purposes, it may allow an attacker to get his chosen plaintext signed, which would allow the attacker to control the content of the payload. Re-using a secret across two different subsystems might drop the security of signing to zero. Keys should not be re-used across different contexts where an attacker has the possibility of providing a chosen plaintext. pyramid-1.6/docs/narr/sessions.rst0000644000076500000240000003557712612323772020072 0ustar michaelstaff00000000000000.. index:: single: session .. _sessions_chapter: Sessions ======== A :term:`session` is a namespace which is valid for some period of continual activity that can be used to represent a user's interaction with a web application. This chapter describes how to configure sessions, what session implementations :app:`Pyramid` provides out of the box, how to store and retrieve data from sessions, and two session-specific features: flash messages, and cross-site request forgery attack prevention. .. index:: single: session factory (default) .. _using_the_default_session_factory: Using the Default Session Factory --------------------------------- In order to use sessions, you must set up a :term:`session factory` during your :app:`Pyramid` configuration. A very basic, insecure sample session factory implementation is provided in the :app:`Pyramid` core. It uses a cookie to store session information. This implementation has the following limitations: - The session information in the cookies used by this implementation is *not* encrypted, so it can be viewed by anyone with access to the cookie storage of the user's browser or anyone with access to the network along which the cookie travels. - The maximum number of bytes that are storable in a serialized representation of the session is fewer than 4000. This is suitable only for very small data sets. It is digitally signed, however, and thus its data cannot easily be tampered with. You can configure this session factory in your :app:`Pyramid` application by using the :meth:`pyramid.config.Configurator.set_session_factory` method. .. code-block:: python :linenos: from pyramid.session import SignedCookieSessionFactory my_session_factory = SignedCookieSessionFactory('itsaseekreet') from pyramid.config import Configurator config = Configurator() config.set_session_factory(my_session_factory) .. warning:: By default the :func:`~pyramid.session.SignedCookieSessionFactory` implementation is *unencrypted*. You should not use it when you keep sensitive information in the session object, as the information can be easily read by both users of your application and third parties who have access to your users' network traffic. And, if you use this sessioning implementation, and you inadvertently create a cross-site scripting vulnerability in your application, because the session data is stored unencrypted in a cookie, it will also be easier for evildoers to obtain the current user's cross-site scripting token. In short, use a different session factory implementation (preferably one which keeps session data on the server) for anything but the most basic of applications where "session security doesn't matter", and you are sure your application has no cross-site scripting vulnerabilities. .. index:: single: session object Using a Session Object ---------------------- Once a session factory has been configured for your application, you can access session objects provided by the session factory via the ``session`` attribute of any :term:`request` object. For example: .. code-block:: python :linenos: from pyramid.response import Response def myview(request): session = request.session if 'abc' in session: session['fred'] = 'yes' session['abc'] = '123' if 'fred' in session: return Response('Fred was in the session') else: return Response('Fred was not in the session') The first time this view is invoked produces ``Fred was not in the session``. Subsequent invocations produce ``Fred was in the session``, assuming of course that the client side maintains the session's identity across multiple requests. You can use a session much like a Python dictionary. It supports all dictionary methods, along with some extra attributes and methods. Extra attributes: ``created`` An integer timestamp indicating the time that this session was created. ``new`` A boolean. If ``new`` is True, this session is new. Otherwise, it has been constituted from data that was already serialized. Extra methods: ``changed()`` Call this when you mutate a mutable value in the session namespace. See the gotchas below for details on when and why you should call this. ``invalidate()`` Call this when you want to invalidate the session (dump all data, and perhaps set a clearing cookie). The formal definition of the methods and attributes supported by the session object are in the :class:`pyramid.interfaces.ISession` documentation. Some gotchas: - Keys and values of session data must be *pickleable*. This means, typically, that they are instances of basic types of objects, such as strings, lists, dictionaries, tuples, integers, etc. If you place an object in a session data key or value that is not pickleable, an error will be raised when the session is serialized. - If you place a mutable value (for example, a list or a dictionary) in a session object, and you subsequently mutate that value, you must call the ``changed()`` method of the session object. In this case, the session has no way to know that it was modified. However, when you modify a session object directly, such as setting a value (i.e., ``__setitem__``), or removing a key (e.g., ``del`` or ``pop``), the session will automatically know that it needs to re-serialize its data, thus calling ``changed()`` is unnecessary. There is no harm in calling ``changed()`` in either case, so when in doubt, call it after you've changed sessioning data. .. index:: single: pyramid_redis_sessions single: session factory (alternates) .. _using_alternate_session_factories: Using Alternate Session Factories --------------------------------- The following session factories exist at the time of this writing. ======================= ======= ============================= Session Factory Backend Description ======================= ======= ============================= pyramid_redis_sessions_ Redis_ Server-side session library for Pyramid, using Redis for storage. pyramid_beaker_ Beaker_ Session factory for Pyramid backed by the Beaker sessioning system. ======================= ======= ============================= .. _pyramid_redis_sessions: https://pypi.python.org/pypi/pyramid_redis_sessions .. _Redis: http://redis.io/ .. _pyramid_beaker: https://pypi.python.org/pypi/pyramid_beaker .. _Beaker: http://beaker.readthedocs.org/en/latest/ .. index:: single: session factory (custom) Creating Your Own Session Factory --------------------------------- If none of the default or otherwise available sessioning implementations for :app:`Pyramid` suit you, you may create your own session object by implementing a :term:`session factory`. Your session factory should return a :term:`session`. The interfaces for both types are available in :class:`pyramid.interfaces.ISessionFactory` and :class:`pyramid.interfaces.ISession`. You might use the cookie implementation in the :mod:`pyramid.session` module as inspiration. .. index:: single: flash messages .. _flash_messages: Flash Messages -------------- "Flash messages" are simply a queue of message strings stored in the :term:`session`. To use flash messaging, you must enable a :term:`session factory` as described in :ref:`using_the_default_session_factory` or :ref:`using_alternate_session_factories`. Flash messaging has two main uses: to display a status message only once to the user after performing an internal redirect, and to allow generic code to log messages for single-time display without having direct access to an HTML template. The user interface consists of a number of methods of the :term:`session` object. .. index:: single: session.flash Using the ``session.flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To add a message to a flash message queue, use a session object's ``flash()`` method: .. code-block:: python request.session.flash('mymessage') The ``flash()`` method appends a message to a flash queue, creating the queue if necessary. ``flash()`` accepts three arguments: .. method:: flash(message, queue='', allow_duplicate=True) The ``message`` argument is required. It represents a message you wish to later display to a user. It is usually a string but the ``message`` you provide is not modified in any way. The ``queue`` argument allows you to choose a queue to which to append the message you provide. This can be used to push different kinds of messages into flash storage for later display in different places on a page. You can pass any name for your queue, but it must be a string. Each queue is independent, and can be popped by ``pop_flash()`` or examined via ``peek_flash()`` separately. ``queue`` defaults to the empty string. The empty string represents the default flash message queue. .. code-block:: python request.session.flash(msg, 'myappsqueue') The ``allow_duplicate`` argument defaults to ``True``. If this is ``False``, and you attempt to add a message value which is already present in the queue, it will not be added. .. index:: single: session.pop_flash Using the ``session.pop_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once one or more messages have been added to a flash queue by the ``session.flash()`` API, the ``session.pop_flash()`` API can be used to pop an entire queue and return it for use. To pop a particular queue of messages from the flash object, use the session object's ``pop_flash()`` method. This returns a list of the messages that were added to the flash queue, and empties the queue. .. method:: pop_flash(queue='') >>> request.session.flash('info message') >>> request.session.pop_flash() ['info message'] Calling ``session.pop_flash()`` again like above without a corresponding call to ``session.flash()`` will return an empty list, because the queue has already been popped. >>> request.session.flash('info message') >>> request.session.pop_flash() ['info message'] >>> request.session.pop_flash() [] .. index:: single: session.peek_flash Using the ``session.peek_flash`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once one or more messages have been added to a flash queue by the ``session.flash()`` API, the ``session.peek_flash()`` API can be used to "peek" at that queue. Unlike ``session.pop_flash()``, the queue is not popped from flash storage. .. method:: peek_flash(queue='') >>> request.session.flash('info message') >>> request.session.peek_flash() ['info message'] >>> request.session.peek_flash() ['info message'] >>> request.session.pop_flash() ['info message'] >>> request.session.peek_flash() [] .. index:: single: preventing cross-site request forgery attacks single: cross-site request forgery attacks, prevention Preventing Cross-Site Request Forgery Attacks --------------------------------------------- `Cross-site request forgery `_ attacks are a phenomenon whereby a user who is logged in to your website might inadvertantly load a URL because it is linked from, or embedded in, an attacker's website. If the URL is one that may modify or delete data, the consequences can be dire. You can avoid most of these attacks by issuing a unique token to the browser and then requiring that it be present in all potentially unsafe requests. :app:`Pyramid` sessions provide facilities to create and check CSRF tokens. To use CSRF tokens, you must first enable a :term:`session factory` as described in :ref:`using_the_default_session_factory` or :ref:`using_alternate_session_factories`. .. index:: single: session.get_csrf_token Using the ``session.get_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To get the current CSRF token from the session, use the ``session.get_csrf_token()`` method. .. code-block:: python token = request.session.get_csrf_token() The ``session.get_csrf_token()`` method accepts no arguments. It returns a CSRF *token* string. If ``session.get_csrf_token()`` or ``session.new_csrf_token()`` was invoked previously for this session, then the existing token will be returned. If no CSRF token previously existed for this session, then a new token will be set into the session and returned. The newly created token will be opaque and randomized. You can use the returned token as the value of a hidden field in a form that posts to a method that requires elevated privileges, or supply it as a request header in AJAX requests. For example, include the CSRF token as a hidden field: .. code-block:: html
Or include it as a header in a jQuery AJAX request: .. code-block:: javascript var csrfToken = ${request.session.get_csrf_token()}; $.ajax({ type: "POST", url: "/myview", headers: { 'X-CSRF-Token': csrfToken } }).done(function() { alert("Deleted"); }); The handler for the URL that receives the request should then require that the correct CSRF token is supplied. Checking CSRF Tokens Manually ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In request handling code, you can check the presence and validity of a CSRF token with :func:`pyramid.session.check_csrf_token`. If the token is valid, it will return ``True``, otherwise it will raise ``HTTPBadRequest``. Optionally, you can specify ``raises=False`` to have the check return ``False`` instead of raising an exception. By default, it checks for a GET or POST parameter named ``csrf_token`` or a header named ``X-CSRF-Token``. .. code-block:: python from pyramid.session import check_csrf_token def myview(request): # Require CSRF Token check_csrf_token(request) # ... .. index:: single: session.new_csrf_token Checking CSRF Tokens with a View Predicate ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A convenient way to require a valid CSRF token for a particular view is to include ``check_csrf=True`` as a view predicate. See :meth:`pyramid.config.Configurator.add_view`. .. code-block:: python @view_config(request_method='POST', check_csrf=True, ...) def myview(request): ... .. note:: A mismatch of a CSRF token is treated like any other predicate miss, and the predicate system, when it doesn't find a view, raises ``HTTPNotFound`` instead of ``HTTPBadRequest``, so ``check_csrf=True`` behavior is different from calling :func:`pyramid.session.check_csrf_token`. Using the ``session.new_csrf_token`` Method ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To explicitly create a new CSRF token, use the ``session.new_csrf_token()`` method. This differs only from ``session.get_csrf_token()`` inasmuch as it clears any existing CSRF token, creates a new CSRF token, sets the token into the session, and returns the token. .. code-block:: python token = request.session.new_csrf_token() pyramid-1.6/docs/narr/startup.rst0000644000076500000240000001674012642137120017706 0ustar michaelstaff00000000000000.. _startup_chapter: Startup ======= When you cause a :app:`Pyramid` application to start up in a console window, you'll see something much like this show up on the console: .. code-block:: text $ pserve development.ini Starting server in PID 16601. serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 This chapter explains what happens between the time you press the "Return" key on your keyboard after typing ``pserve development.ini`` and the time the line ``serving on 0.0.0.0:6543 ...`` is output to your console. .. index:: single: startup process pair: settings; .ini The Startup Process ------------------- The easiest and best-documented way to start and serve a :app:`Pyramid` application is to use the ``pserve`` command against a :term:`PasteDeploy` ``.ini`` file. This uses the ``.ini`` file to infer settings and starts a server listening on a port. For the purposes of this discussion, we'll assume that you are using this command to run your :app:`Pyramid` application. Here's a high-level time-ordered overview of what happens when you press ``return`` after running ``pserve development.ini``. #. The ``pserve`` command is invoked under your shell with the argument ``development.ini``. As a result, Pyramid recognizes that it is meant to begin to run and serve an application using the information contained within the ``development.ini`` file. #. The framework finds a section named either ``[app:main]``, ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This section represents the configuration of a :term:`WSGI` application that will be served. If you're using a simple application (e.g., ``[app:main]``), the application's ``paste.app_factory`` :term:`entry point` will be named on the ``use=`` line within the section's configuration. If instead of a simple application, you're using a WSGI :term:`pipeline` (e.g., a ``[pipeline:main]`` section), the application named on the "last" element will refer to your :app:`Pyramid` application. If instead of a simple application or a pipeline, you're using a "composite" (e.g., ``[composite:main]``), refer to the documentation for that particular composite to understand how to make it refer to your :app:`Pyramid` application. In most cases, a Pyramid application built from a scaffold will have a single ``[app:main]`` section in it, and this will be the application served. #. The framework finds all :mod:`logging` related configuration in the ``.ini`` file and uses it to configure the Python standard library logging system for this application. See :ref:`logging_config` for more information. #. The application's *constructor* named by the entry point referenced on the ``use=`` line of the section representing your :app:`Pyramid` application is passed the key/value parameters mentioned within the section in which it's defined. The constructor is meant to return a :term:`router` instance, which is a :term:`WSGI` application. For :app:`Pyramid` applications, the constructor will be a function named ``main`` in the ``__init__.py`` file within the :term:`package` in which your application lives. If this function succeeds, it will return a :app:`Pyramid` :term:`router` instance. Here's the contents of an example ``__init__.py`` module: .. literalinclude:: MyProject/myproject/__init__.py :language: python :linenos: Note that the constructor function accepts a ``global_config`` argument, which is a dictionary of key/value pairs mentioned in the ``[DEFAULT]`` section of an ``.ini`` file (if :ref:`[DEFAULT] ` is present). It also accepts a ``**settings`` argument, which collects another set of arbitrary key/value pairs. The arbitrary key/value pairs received by this function in ``**settings`` will be composed of all the key/value pairs that are present in the ``[app:main]`` section (except for the ``use=`` setting) when this function is called when you run ``pserve``. Our generated ``development.ini`` file looks like so: .. literalinclude:: MyProject/development.ini :language: ini :linenos: In this case, the ``myproject.__init__:main`` function referred to by the entry point URI ``egg:MyProject`` (see :ref:`MyProject_ini` for more information about entry point URIs, and how they relate to callables) will receive the key/value pairs ``{'pyramid.reload_templates':'true', 'pyramid.debug_authorization':'false', 'pyramid.debug_notfound':'false', 'pyramid.debug_routematch':'false', 'pyramid.debug_templates':'true', 'pyramid.default_locale_name':'en'}``. See :ref:`environment_chapter` for the meanings of these keys. #. The ``main`` function first constructs a :class:`~pyramid.config.Configurator` instance, passing the ``settings`` dictionary captured via the ``**settings`` kwarg as its ``settings`` argument. The ``settings`` dictionary contains all the options in the ``[app:main]`` section of our .ini file except the ``use`` option (which is internal to PasteDeploy) such as ``pyramid.reload_templates``, ``pyramid.debug_authorization``, etc. #. The ``main`` function then calls various methods on the instance of the class :class:`~pyramid.config.Configurator` created in the previous step. The intent of calling these methods is to populate an :term:`application registry`, which represents the :app:`Pyramid` configuration related to the application. #. The :meth:`~pyramid.config.Configurator.make_wsgi_app` method is called. The result is a :term:`router` instance. The router is associated with the :term:`application registry` implied by the configurator previously populated by other methods run against the Configurator. The router is a WSGI application. #. An :class:`~pyramid.events.ApplicationCreated` event is emitted (see :ref:`events_chapter` for more information about events). #. Assuming there were no errors, the ``main`` function in ``myproject`` returns the router instance created by :meth:`pyramid.config.Configurator.make_wsgi_app` back to ``pserve``. As far as ``pserve`` is concerned, it is "just another WSGI application". #. ``pserve`` starts the WSGI *server* defined within the ``[server:main]`` section. In our case, this is the Waitress server (``use = egg:waitress#main``), and it will listen on all interfaces (``host = 0.0.0.0``), on port number 6543 (``port = 6543``). The server code itself is what prints ``serving on 0.0.0.0:6543 view at http://127.0.0.1:6543``. The server serves the application, and the application is running, waiting 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 .. _deployment_settings: Deployment Settings ------------------- Note that an augmented version of the values passed as ``**settings`` to the :class:`~pyramid.config.Configurator` constructor will be available in :app:`Pyramid` :term:`view callable` code as ``request.registry.settings``. You can create objects you wish to access later from view code, and put them into the dictionary you pass to the configurator as ``settings``. They will then be present in the ``request.registry.settings`` dictionary at application runtime. pyramid-1.6/docs/narr/subrequest.rst0000644000076500000240000002700412621241570020403 0ustar michaelstaff00000000000000.. index:: single: subrequest .. _subrequest_chapter: Invoking a Subrequest ===================== .. versionadded:: 1.4 :app:`Pyramid` allows you to invoke a subrequest at any point during the processing of a request. Invoking a subrequest allows you to obtain a :term:`response` object from a view callable within your :app:`Pyramid` application while you're executing a different view callable within the same application. Here's an example application which uses a subrequest: .. code-block:: python :linenos: from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') response = request.invoke_subrequest(subreq) return response def view_two(request): request.response.body = 'This came from view_two' return request.response if __name__ == '__main__': config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() When ``/view_one`` is visted in a browser, the text printed in the browser pane will be ``This came from view_two``. The ``view_one`` view used the :meth:`pyramid.request.Request.invoke_subrequest` API to obtain a response from another view (``view_two``) within the same application when it executed. It did so by constructing a new request that had a URL that it knew would match the ``view_two`` view registration, and passed that new request along to :meth:`pyramid.request.Request.invoke_subrequest`. The ``view_two`` view callable was invoked, and it returned a response. The ``view_one`` view callable then simply returned the response it obtained from the ``view_two`` view callable. Note that it doesn't matter if the view callable invoked via a subrequest actually returns a *literal* Response object. Any view callable that uses a renderer or which returns an object that can be interpreted by a response adapter when found and invoked via :meth:`pyramid.request.Request.invoke_subrequest` will return a Response object: .. code-block:: python :linenos: :emphasize-lines: 11 from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') response = request.invoke_subrequest(subreq) return response def view_two(request): return 'This came from view_two' if __name__ == '__main__': config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two', renderer='string') app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() Even though the ``view_two`` view callable returned a string, it was invoked in such a way that the ``string`` renderer associated with the view registration that was found turned it into a "real" response object for consumption by ``view_one``. Being able to unconditionally obtain a response object by invoking a view callable indirectly is the main advantage to using :meth:`pyramid.request.Request.invoke_subrequest` instead of simply importing a view callable and executing it directly. Note that there's not much advantage to invoking a view using a subrequest if you *can* invoke a view callable directly. Subrequests are slower and are less convenient if you actually do want just the literal information returned by a function that happens to be a view callable. Note that, by default, if a view callable invoked by a subrequest raises an exception, the exception will be raised to the caller of :meth:`~pyramid.request.Request.invoke_subrequest` even if you have a :term:`exception view` configured: .. code-block:: python :linenos: :emphasize-lines: 11-16 from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') response = request.invoke_subrequest(subreq) return response def view_two(request): raise ValueError('foo') def excview(request): request.response.body = b'An exception was raised' request.response.status_int = 500 return request.response if __name__ == '__main__': config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two', renderer='string') config.add_view(excview, context=Exception) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() When we run the above code and visit ``/view_one`` in a browser, the ``excview`` :term:`exception view` will *not* be executed. Instead, the call to :meth:`~pyramid.request.Request.invoke_subrequest` will cause a :exc:`ValueError` exception to be raised and a response will never be generated. We can change this behavior; how to do so is described below in our discussion of the ``use_tweens`` argument. .. index:: pair: subrequest; use_tweens Subrequests with Tweens ----------------------- The :meth:`pyramid.request.Request.invoke_subrequest` API accepts two arguments: a required positional argument ``request``, and an optional keyword argument ``use_tweens`` which defaults to ``False``. The ``request`` object passed to the API must be an object that implements the Pyramid request interface (such as a :class:`pyramid.request.Request` instance). If ``use_tweens`` is ``True``, the request will be sent to the :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main router handler, and no tweens will be invoked. In the example above, the call to :meth:`~pyramid.request.Request.invoke_subrequest` will always raise an exception. This is because it's using the default value for ``use_tweens``, which is ``False``. Alternatively, you can pass ``use_tweens=True`` to ensure that it will convert an exception to a Response if an :term:`exception view` is configured, instead of raising the exception. This is because exception views are called by the exception view :term:`tween` as described in :ref:`exception_views` when any view raises an exception. We can cause the subrequest to be run through the tween stack by passing ``use_tweens=True`` to the call to :meth:`~pyramid.request.Request.invoke_subrequest`, like this: .. code-block:: python :linenos: :emphasize-lines: 7 from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') response = request.invoke_subrequest(subreq, use_tweens=True) return response def view_two(request): raise ValueError('foo') def excview(request): request.response.body = b'An exception was raised' request.response.status_int = 500 return request.response if __name__ == '__main__': config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two', renderer='string') config.add_view(excview, context=Exception) app = config.make_wsgi_app() server = make_server('0.0.0.0', 8080, app) server.serve_forever() In the above case, the call to ``request.invoke_subrequest(subreq)`` will not raise an exception. Instead, it will retrieve a "500" response from the attempted invocation of ``view_two``, because the tween which invokes an exception view to generate a response is run, and therefore ``excview`` is executed. This is one of the major differences between specifying the ``use_tweens=True`` and ``use_tweens=False`` arguments to :meth:`~pyramid.request.Request.invoke_subrequest`. ``use_tweens=True`` may also imply invoking a transaction commit or abort for the logic executed in the subrequest if you've got ``pyramid_tm`` in the tween list, injecting debug HTML if you've got ``pyramid_debugtoolbar`` in the tween list, and other tween-related side effects as defined by your particular tween list. The :meth:`~pyramid.request.Request.invoke_subrequest` function also unconditionally does the following: - It manages the threadlocal stack so that :func:`~pyramid.threadlocal.get_current_request` and :func:`~pyramid.threadlocal.get_current_registry` work during a request (they will return the subrequest instead of the original request). - It adds a ``registry`` attribute and an ``invoke_subrequest`` attribute (a callable) to the request object to which it is handed. - It sets request extensions (such as those added via :meth:`~pyramid.config.Configurator.add_request_method` or :meth:`~pyramid.config.Configurator.set_request_property`) on the subrequest object passed as ``request``. - It causes a :class:`~pyramid.events.NewRequest` event to be sent at the beginning of request processing. - It causes a :class:`~pyramid.events.ContextFound` event to be sent when a context resource is found. - It ensures that the user implied by the request passed in has the necessary authorization to invoke the view callable before calling it. - It calls any :term:`response callback` functions defined within the subrequest's lifetime if a response is obtained from the Pyramid application. - It causes a :class:`~pyramid.events.NewResponse` event to be sent if a response is obtained. - It calls any :term:`finished callback` functions defined within the subrequest's lifetime. The invocation of a subrequest has more or less exactly the same effect as the invocation of a request received by the :app:`Pyramid` router from a web client when ``use_tweens=True``. When ``use_tweens=False``, the tweens are skipped but all the other steps take place. It's a poor idea to use the original ``request`` object as an argument to :meth:`~pyramid.request.Request.invoke_subrequest`. You should construct a new request instead as demonstrated in the above example, using :meth:`pyramid.request.Request.blank`. Once you've constructed a request object, you'll need to massage it to match the view callable that you'd like to be executed during the subrequest. This can be done by adjusting the subrequest's URL, its headers, its request method, and other attributes. The documentation for :class:`pyramid.request.Request` exposes the methods you should call and attributes you should set on the request that you create, then massage it into something that will actually match the view you'd like to call via a subrequest. We've demonstrated use of a subrequest from within a view callable, but you can use the :meth:`~pyramid.request.Request.invoke_subrequest` API from within a tween or an event handler as well. Even though you can do it, it's usually a poor idea to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from within a tween, because tweens already, by definition, have access to a function that will cause a subrequest (they are passed a ``handle`` function). It's fine to invoke :meth:`~pyramid.request.Request.invoke_subrequest` from within an event handler, however. pyramid-1.6/docs/narr/tb_introspector.png0000644000076500000240000054175112621241570021407 0ustar michaelstaff00000000000000‰PNG  IHDR SÅ^ää ÉiCCPICC ProfileH ­–wTSÙ‡÷½éˆ€”Ð;R¤K¯¤ 6BH(!„"ƒ#0¢¨ˆ`EGD cA,ØÁÞdPQŸƒ*ï̬·Þü÷ÎZûžïîó»ûž}ÊZ€ÞÊ•H2P€L±LìÇž“È&ýdPÁ F\^ŽÄ722þ±}¸ ˆbð†"Ö?Êþ÷€*_Ã@"±ád~/㣘mçI¤2\ æ7^$“(8cu)6AŒËœ:Î;œ<ÎØ·˜&&ÊÓ\ Ó¹\i*í&ægçòR±8´÷Û‰ù"1Ýc/žËÇ3°ÎÌÌRð:ŒÍ“ÿ'õoÌå&OÆärS'y<ìKìÇ¢IwñØËÿó‘™!ÇÖk¬éaOzNztÖ›bk–ÇãFO°PÀQìÙ˜_"ó‹š`‘Œ3ÁByHìËÓc}'8=+lR/Nž1áçåøck?3_?Á|A@àK³¢&õ9¹Ñ“þ|¡ÿ¬ M7T±ßcsãJ1ú 2‚'ÿ+‘ENÎSœ1k2—iФFóW¾2aLÈDv&8EÄ™`¡4dÒ/É;ÓcsÊ£&×A Ž\C>7`rm!„ 1ðARH†,Ȱ!Dì ØvËyØðÏ’,–ŠR…2¶/v+ÖlŽ˜gkÍv°³wÅShÞ±Æîºü—/»À­ÛOÅñf+T\#€ãO˜þò½?§'»yriè@eP-Ð#0pgð„PˆÀ2I€ÀÃòÉÄ2YKa%C)¬ƒMP ;`7샃pZàœ pºá<€^€—0`AÂ@˜ˆ¢˜ VˆâŠx!H8…$ IH*"FäÈRdRŠT ÕÈ.¤ù9ŽœA.!=È=¤DÞ"_PJGÕQ]Ô†º¢¾hƒÎGSÑl4-B×¢Uh-zmFÏ WÐ[h/úÆކcá p68Wœ?.—ˆKÁIqËq%¸J\-®׆ëÄÝÀõâ^á>ã‰x&ž·Á{àCð±x>¿_†¯ÆïÃ7ãÏáoàûðCøïA‡`Ep'ps©„E„bB%a/áá<áa€ðH$²ˆfDb1˜F\B,#n#6Û‰=Ä~â0‰DÒ"Y‘‘id}²9ˆœH“ É•äýäSäëägäŠ Å„âN‰ ð)‹)å”=”6Ê5Êe„ªJ5£zRc¨iÔ•Ô*j#õ<õ!õF3¤¹ÑfÓD´Zíí"­ö™®F·¤ûÓçÑåôµô:z;ýýƒÁ0eø02ÆZF=ã,ã1ã“SÉV‰£ÄWZ¡T£Ô¬t]éµ2EÙDÙWyr¾r¥òåkʯT(*¦*þ*\•å*5*ÇUî¨ «2UíU#T3UËT÷«^R}®FR3U Tã«©íV;«ÖÏÄ1˜þLss󤡦1]#N#O£Fã¤F/ Ç2eqX¬rÖaÖmÖ—)ºS|§¦¬™Ò8åú”šS5}4š%šMš·4¿h±µµÒµÖkµh=ÒÆk[jÏÖ^¤½]û¼ö«©êS=¦ò¦–L=<õ¾ªc©¥³Dg·ÎUa]=Ý`]‰îݳº¯ôXz>zizõNé ê3õ½ôEúõOë¿`k°}Ùì*ö9öŽAˆÜ`—A—Áˆ¡™a¬a¡a“á##ª‘«QŠÑF££!c}ã™ÆKŒï›PL\M„&›M:M>šš™Æ›®6m1}n¦iÆ1Ë7k0{hÎ0÷6Ï6¯5¿iA´pµH·ØfÑm‰Z:Y -k,¯Y¡VÎV"«mV=Ök7k±u­õº¯M®MƒMŸ-Ë6ܶжÅöõ4ãi‰ÓÖOëœöÝÎÉ.ÃnÝ{5ûPûBû6û·–<‡‡›Ž Ç ÇŽ­Žo¦[MLß>ý®Ói¦Ój§§oÎ.ÎRçFçAc—$—­.w\Õ]#]Ë\/ºÜüÜV¸pûìîì.s?ìþ§‡GºÇ~ç3Ìffì™ÑïièÉõÜåÙëÅöJòÚéÕëmàÍõ®õ~âcäÃ÷ÙëóÌ×Â7Í÷€ïk?;?©ß1¿þîþËüÛpÁ%]j±Õƒ ƒRƒ‚†‚‚—·‡BÂBÖ‡ÜáèrxœzÎP¨Kè²Ðsaô°è°ê°'á–áÒð¶™èÌЙf>œe2K<«%"8"EšEfGþ:›8;rvÍì§QöQK£:£™Ñ £÷Gˆñ‹)yk+íˆSŽ›W÷1> ¾"¾wδ9Ëæ\IÐN%´&’ã÷&Ï œ»iîÀ<§yÅónÏ7›Ÿ7ÿÒí N.T^È]x$‰Ÿ´?é+7‚[ËNæ$oMâùó6ó^ò}øùƒOA…àYŠgJEÊóTÏÔ ©ƒBoa¥ð•È_T-z“’¶#íczDz]úhF|FS&93)ó¸XMœ.>—¥—•—Õ#±’Kz³Ý³7eIä{sœù9­2u¬˜¹*7—ÿ ïËõÊ­Éý´(nÑ‘<Õv¸ãˆë‘Æ£&G·c+iFš7µ[z[Z{އïhóh;ö«í¯u' NÔœÔ8Y~ŠzªèÔèéüÓÃí’öWgRÏôw,ìxpvÎÙ›çfŸë:vþâ…  g;};O_ô¼xâ’û¥ã—]/·\q¾Ò|Õéê±ßœ~;ÖåÜÕ|ÍåZk·[w[ÏŒžS×½¯Ÿ¹pãÂMÎÍ+·fÝê¹{ûîywzïòï>¿—qïÍýÜû# ––|Òú´ï³ëçÎ/ñ_ž,úJúZõÍâ[Û÷°ïG3GG%\)w¬ÀaO4%àm#«º¨Jã5ð˜¯Û1VÔï S´ÿâñ:ylÄ Î ¶ ¼`;f&Ó±^QÎÅøêè8i˜GÑrRÆ¡K±ÒäÓèè;]RÀ7éèèȶÑÑo{°Zý@{öxí­PU*ÌXÚ–WýQ ðü½ýuEþªcÜ®‹ pHYs  šœ@IDATxìÀ¦Eu¨Ïß¶÷,ìÒ¤,M¬ôH±¡F v£ÌM4;ÆÜèÍõzM,¹b JT4Q ÆKGÊÒ˲K]úöÝ¿ÝóÌ÷?ûŸÿ.»jôÝ÷?3gN›3gæûfÞy߯#"ö>í´Óö{ùË_~äüùóÿ`ìØ±;$n“©££#¡^üHby¬ÿ›@Àé—•£ÞMAÕ[oysPZ!´æ…òSØØñ›‚´³³³sc{¥Sn;´~Sz’þÔà”EþÉÒ¦hÅ‘©>ëŸLõÚ é—•£ÞMAõYoysPZ!´æ…òSnúØ?úåÉ`ÓÿOœk«vød~„ž¤?Gà”EþÉÒ¦hÅ‘©>ëŸLõÚ é—•£ÞMAõYoysPZ!´æ…òSnÆÿ°ôË“Á¦ÿ›ñÏØá"5ã¿õýÛùEødã¨ö_;O{ys²6E+~$ø«ô[3þ›ñOLqý*q4R\[Î)È—®({’?Ò kþ'þéúù¿fÍš»–.]zÁ÷¿ÿý~àXÔñ¾÷½ïozÓ›þbÒ¤Iûã<F"ßÕÕµñ‹Î朠3€,tëÔÎ×^GÙNƒ¶Nàå¯!4Òª[>eIc¹æo— ­t5Ÿ:Àm*É'-PÊceu›—(45l§S¯xü®/j>pô#IëÑG¾½,mÓÿOŒÇâĶ?úÔ¾6ý?ì$cl3œÓgí>”¸l‡ÔËkÌ×<ækÝæG‚Ðkùš†òæ’|ð€Mÿ{L_c†sú¬Ý‡R´÷»eêåmú¿å­fþoùÁX2F,‡#Ah¥«ùŒ1p›JòI lÆÿ°·ô÷0f8§ÏÚ}(xùkH½¼Íøoy«ÿ-?Kƈå:~Úã Zéj>c ܦ’|Ò›ñ?ì-ý>ŒÎé³vJÑÞO–©—·ÿ-oýwŽd³\¹råÕgœqÆ'»>ö±ýåœ9s^L‡Øä7Ußžè@;QyèÉ‹Öxò觎˺j8óÊ’GzõQ6Õ4ò)Gz 8é…àI”Å)[~ëuGÖt5|@ñÊ'/Pzh_ã¬j«4–©“^ðÐÈcò©k/k[Q<ô.’:óâÅ kåH'½¼rÄDþ‘ßz`3þ›ño,$â„dü75¾®+ÄÕé¡ql‹«¡±*eh¤C,øfüûߨgöƒPRg×Ô>µ®ÿÍø7–ê8!oŒ7@ñu]AV¤‡Æ±-®†Æª4–¡‘±àŸlüCÓßß===ÛLŸ>}UÇwÞ¹tôèÑÛk¡*A *©AòXgkã¨c·BY5­üò ÛùÁƒã")£¦«óجS ·MäMÐKÓ.ùÈJ£­@óÈR¯Ð:¡4ê¿©„ ý$M-§ÎS=öMÐh»xùj(;½¾BFm tòÔºÐaøG]ÓÿOìýÔñ[ísêñ_{?ʾéÿá¸4›ñßš»ÚãÆXÒOÔ“¯ç‹fü·Æœ>Áæõ—~dŠZ'”Æ1 ~S Íøö)~Ò_ø†«W×›oÆ3þ·Ä‹sZ ž¡?Ä’4íqE9æ¥Ð<¢Ä ­J3¤vc<[®!2šñ?ìS|ƒIø†‹TãFÊ7ã¿ÿŽ[â¥ÿÃs—ó~1ï\GY_«ç¢õë×/ë¸÷Þ{%@©}"DÁ ¬q-®aåÔ‘êC&—†ÕyhÁ“ÔÝž§¬ Ш¨y¥³ÈUËl×_Ë”N9”•.òØK¾Ö)};­4@.x¡©í¦LGZeR6I«,xL5½tÔ‹¯!<–å"—d°Î+O\!n£×ÞíÖyh›þoúŸ80öÚó”7hÈ‹3Îä•XÇ*ôÒ‰¹º¬L锣žZ^3þ‡çüCÒ_úÑ~¡?×>¯óÔ7ã¿ÿÆ äØl•†ã ¼1昬Çr‡Íøo}×>i÷_»Ÿ-ׯWúW6ý?¼¡¯ð ~ªc•:ðÂú3CúR9DWûž|-^d+ÏzåÔ´Ò¹žÎã¿Ø¾lÙ²lG«!:H£Á›(“jeéÀ›O’ØØ-Š-ÿ[Ë®óJ gí3€Ä·óX®œÉ6R¯ÄY†¶ÆÕüÔ©ƒ¼I}”¥×VÊú®Ö+o]OÞ²´Úž¼eøÍ¡”¾ ‡Êä¡­“tµ,ë©#mƒåR±Ô#ÿ¦ìI¤íÓ§ÒÔ2k¹ö‡m’Þ²¾/hªqúÀºvàÕG^zm¥¬ïj½Ð’êzò–¥Õðä-Ãk½8 ô9T&m¤«eYO h,—Š­ø£ù7eÇH"mŸ>•¦–Y˵?l“ô–õ-xq@SÓÖµë¯>òÒk+e}Wë…–Tד·,­¶€'o^ó@èÅ¥/È¡2yhë$]-ËzêH@Û`¹TlÅõÈ¿);Fiûô©4µÌZ®ýa›¤·¬oÁ‹šjœ>°®]'xõ‘—^[)ë»Z/´¤ºž¼eiµ}*M-³–kØ&é-ë[ð…§¬k× ^}ä¥×VÊú®Ö -©®'oYZmOÞ2¼æÐ‹J_CeòÐÖIºZ–õÔ‘€¶Ár©ØŠ?ê‘SvŒ$ÒöéSij™µ\ûÃ6IoYß‚4Õ8}`]»Nðê#/½¶RÖwµ^hIu=yËÒj xò–á5„^Pú‚*“‡¶NÒÕ²¬§Ž´ –KÅVüQü›²c$‘¶OŸJSˬåÚ¶IzËú¼8 ©ÆéëÚu‚Wy鵕²¾«õBKªëÉ[–V[À“· ¯y ôâ€ÒäP™|áaÓ`caH° í@§1uCä’à#AãËô bè28ò ë”-?š´E©s¥Û„OÊWV áW¯ÎTP…ʼn•ßÔ_ãÅÕåQ}Ê\êZò$ÛZ ùGýkråQ”õòAÇ¥54â “¿–¯Ÿ¡³Þ¾NûÔ#z.êå¯ë ²ú£\PÐ5ýß/úWÿëæÿ‡?õEí#òøŠË¼õíåv},Þ0¯>c^¹Ò¥m×¥¬*O9ÔÉ×ôÿð#sø_9/éïvÿ‰·ô¥þm/×ôÖ¡KéÛÛK½u5-yR-³…¦§Î>ÖvyÐcí†FH'Ÿ6µ³n?<µ iÁKG^»šþâ—c¢ö•þÄ&óÖ×ôú™¾²­oú¿“Æ;¾ª}ÛŒÿÖ85njÿà§fürLá+Ç£þrœÖ±E^Ro8`jYÒZ¯¾šGz s¼ýøfü7ãߨ0¦ˆçCãĸ©c úfü7ãŸ8pÞ!>Hu 3à¥#žØk¾ÿ5ßÿˆ’ó±A /¾øâ8øàƒKì?Æ”4…9ÿˆw^lçµ|­—2rHص¥ë?x¸.\x`áמRÈ?µlòµnëÄëTË’ÖzêH5ôÀ®w¾ó¤ÒËJË8Ò<°N:|Ð’¤WÐzêÌ á!_×)Ç LÒžöei3°–2 cþ¡ž¤½µ,ð”•Gžd™¼í¯e+ Xã¡'Á£]@hÀÕí'-ñäk^dËW·S:êIuYzdbKÓÿÃ}¯jÿ'á+.RÓÿÍø'.¸sÄy`/33Íøo>ÿý FšùøsœqãøpÌÔ㈱…¿œƒkÿ9î¨k—SËP.zHÐÂ[ËOYYäI–É7ó3ÿ\ÄÉXÖøR™÷BhÈ×q NþZžñ­Üšùòi 8òàMuYzt4ßÿZý§Ï€µô!¾â"ý¶Æ?}h?;Ø£½Ô™¤©,ýªU«â‹_übôööþÂ{V¯^§vZ‘,ô™l?þ@VÙ4 ¡ |+eêëëÛè@h¥Óx¹äʃ:ù¯nê¤ÓHdÔº ‡ß¤ù@êÄ+C^tA£=Ê£¬LpÒ×6‚C®uòB#Ï#<ùS–AGÐ<èà"Õº¤ÙTŸ6+zquÙ¶¿–YÓÖøMµ í6?RûkéÐÕnG;]M«mȇ¯æ5?IZ I`­gsöÖtÈWyEèÐé„uùv¼¶ÔtÒ(Sºjóð’àUŽuâêú‘ô×õò ‘9’Ÿ¨§Žc“&MŠÙ³gÇÌ™3 ›vÍÛ¦ßÇño¿âž›cÞaž!Qçœcé+êñŸP95Îyı! 2ÁÉk=xç½R9ôGœ²‘ƒ”­'Ÿ}IZé´^.ù€òü>Íÿ´ÛÔôë;CÓÿÍøw~b|0O8o8Ÿ8wHçÆRÏ5ÐCkRŽ|Îy@êÄ+C^ì€F{”GY™à¤§@äZ'oÍ®ÿÍø'îˆ3RsÆ]3Æ“qOk>ÿ‡×Wø†¤ÿç[:þñ«¼úy¿ëãŸïï'Ÿ|rüð‡?ŒQ£F•ïôÆâ£>Š 6ÎsøÇäw9Êà»ô€'o‚ÏBž­üxpò¶ë–GÒë¤ p5퀗K{¡å2Y/ßm·ÝãÇC=´À®ü>?0mM†A¤ø€r~@uä„’uTâŠÁ”K>ÿ B‡.êóO)´ê;:óƒyüK ²n(2 (z ¤"y‘—u…vÀC¹6*‰4mHM«›AaZ}ôƒ¥Ð%MÊ-*²)-ÞìspCÆ8¤c0u" )":Ò9øúÆGø;ýöõÑÀ@G¬Zµ:n¼ñú¸é¦›âÏxFÆPÆ^^Œ Çãá÷qüã“~©ç ýdþrN¸‘x‘‹|¿8Ô<äá*£Îà ^:íT—ºfþ¢¯¶fþׯú[ŸãwR ›þÿÝûüoú¿çÌ'Íüßú\dÌ×ãÞy ÿÍøgœ0g~vÛ?;ˆh›ÏÿÖwœ_eý‡ß¹êDˆÓ÷”Ÿ ŸÿÎ!¿Žþ×op@\vÙeeÓ€vÒfÒ–~ÿë6h0뼺ŒÁ8õÔ­|衸é ⎫¯Žû︣,G·Ùi§˜¿ï¾±ç¡‡Åäü¹Œ4ÕòÔ㇠åǾ?n½æÂXzÛUñà]w[fì°cì°ëþñŒý‰ISgml¶ >..îl€{èáåqÅu ã¦Å‹â®û–ò­-vØv^ì¶ã^ñü}iSg”6)ÇÑ^ 6ëTò8µÖm› EÎ}÷Ý3¦Ïˆ}÷Ý/Ö¬õë×ç"0ï–¥rÚÚŸ?òe-])—…t.Ä:s‘ˆ— éÈ•ybb0ëù.ú[ëòèÊM6£wh£!}ЕwsáÞÑGÇS—¾èJ:|¾+Ç`&aÀ¾ÈE:{Ñ eö5ZÒW){ØGì$°("aw_OÚ—µiS±o°+ú’¦[Sþ`¶a ØÖ•êR@Ê(›):óŶ|9%r±0éÙìÈ]ØîôM–ÑO}3*žÿÜçÆU×\÷ÜsOl·Ývi÷ð]å š³Œbhr¬8ž€õø—u5N>põÜÔø‡žB‹ÈFG-çWÿêB‡zÐ]·›²uè&Õí¢N^ê±y­ÝFëᣎ²òÉSW÷òÀ«GÒÚŽºŒ,åÉ>y™ÍÇ>aQ4ÔhàjsÍ }Óÿ­¸Å‡ø ?5ýߊâƒX$Fˆ®:^üü‡ÎXB‹Oñ'<µpÒÁKÒÿòmîó_^m‚WàêzêÐMO=I(­ú›þ¢Ÿê~ÃgMÿ7ó?c„ÔŒÿÖwÆó‡ã£8'ÿ0·87Õs´õÜ/ed)O~üí|õtûü×'õ<òTžÿëvý| =ÔQö²¿l«ýÿP®ÛÏ9çœØ)×ê~†RiKú¿¼Á*3³S¤ ý¡~Ù 7ÄÿúŸé{çÅ´¾ÞØ{ÛmcÞ”I±*7^rI>êÏ-/N„l·íœ¸:7fägœg\Ž!ó6G™$4ïØ"«Nõ„†±)ãT^ —z˜k]à凖r;¿rkÝÊ«iµS9Ê/N}@e•#P™Ø t5²­W¶<”iƒee•#/tíxi°‰KšB8$Cä´û¸¶Ëäá3)ƒ24¤v?P¶ˆ-ÒW×[VO»mà凶ÝpÊ­u+¯ÖU Î?ÊQ.xqê*¨y„ʴ狼y” 4O½<äiƒee•#/tíxi°‰KšB8$Cä´ûX›€$Ëäá3)ƒ²´í~ l[¤!®®·¬žvÛÀËm» à”[ëV^­Ëv(G¹¶õ™§Ž¼rä*Ó¾“®æQÐ<õò§ –•Yë–ºv¼º°‰KåP/ rÚ}¬M@’eòð™”AYÚv?P¶ˆ-ÒW×[VO»mà凶ÝpÊ­u+¯Öe;”£\ÛúÌSG^9ò•ißIWó¨hžzyÈÓËʬuË ];^]ØÄ%r¨—9í>Ö& É2yøLÊ ,m»([Äiȃ«ë-«§Ý6ðòCÛn8åÖº•Wë²ÊQ®íG}æ©#¯y„ʴ狼yÔ4O½<äiƒeeֺ兮¯.lâ’F9ÔKƒœvkd™<|&eP–¶Ý”­b‹4äÁÕõ–ÕÓnx^„¸Ã;¾vÔä¸>=z£Ýµ.Û9Úe{À‰7OyåÈ#T¦}ÝÒ¥KcÞ¼yyÔ4LyÈãËÊT÷7¿ùÍØ6×ë<ž@û¸± ϺuëbçwÞh6qÔÿåñ;Á*†ÝÉxì⊯=¿öê8ù%/ŠíAŒ9è€ìùˆu]wÿøÇñ…ó?OY¦O)ù ´Ö ¹+]Wÿô¬¼íòxû‰GǬ_£¦šd±áÑ bù¥ß‹¯žq^\›‹ÛIÓfÆ”é­xIµí”~ôÁøÞ…߈Û¾9ÞôgGîñ¢˜9qoªâ•×Äù7~/þýKçÅàÂþ˜6eFÌœ>»Ôi‹ŽD:ü¢¨Çe=;8»ï¾{)o`;€;éù¿y>!ºú£—yŠÇ ¹hf_ 7utvu·^.‘xîÜwt§\6 Ò©©9ºÓ†,u'}wJK›zûsɹ@çŽú‹Ó¹Ñ˜rg¿ì½¦Ìn6#ús3»“וE;<}©6&>÷³©3Ë]¹s1› œH…É›rÑØY¶ʦ@_æS@tæÉ…žœðQÚÐÇ\ÔÚÁÈ͇l_Ê*~JtgnD¶—Ó ]¹¹Ðø('¿§“r“p°³'vÛm·xðÁc›m¶)ã„ñA“ÌIõ8)ˆ¡?Œ/'%dzôððÇxÇ#Púöñ ÔÁç¦ïZ¯]êþ²ã^’m'¯?Ôk}m3¶hwIÌc›I{¥–1UÑÁƒþZ>2¤ê ýSÓƒ«}G¾zî«éÁÃÃe{ÍIÈh·¼v™’ çjú¿åüd?à{ýÖîShðyÝ?HÐþ©éÁ!G=êmúóŸÿ­žjÆ?~ †ˆ3ã‹jUèŒKó@ô\Íøoù?9—ÀvŸBƒÏëñŒýì¸Ú?5=8ä¨G½ÍøoÆ?±@ªãåéöùïçmá¢L²müÄáç>÷¹rjüñÇ/wäO:é¤8è ƒJ»¿üå/Ç 'œ°qnjƒŒe •ïØõëÿ===Åöµk×–Mƒ#<2~ö³Ÿ•<º¶düw·7Ú f‚ƒC£3n[xa<~Õ•qü^{Ä6ùÓ±øŽX{ëmÅñùrÛÜÅ9~÷gÄ7¯úy,¾ø¢xö+ÿp£‘áäc'-^ta Þ~Eóâ1=݃y`Ý#_nÉëî‰é»í‘uKâœó¯h÷=丠áØI²C´ûç×_·>tcrô3c·y»Ä=½wÅâå-ûÆtŠó÷ˆC¾;.<ꆋ㨃)öa¾ÚQØk¾¦)Êó޾ÿþûcßýö‹¾þ|Z.˜sYŸ‹l=ù2èòêÊèƒùeº3± »r#…{_.Ì»y, wYøçâ»?¬—;ö‰N‹r±Æ¢»µÈ䑃œÚË]~62´Ò~îô'_.âÙpèàƒ%Û5˜w‡“;™Ç?hX¼§>¶º¸ûŸÂÙ0à] H¦šw ôa_ê᱈þnÎ- óEGâ{œŸLùØDò¢§lväIˆò‡lKVC`gd 79r[!}ÄæC㣧£zsl‡y;Ä-·ÜÓsc/w$ç &$^Ê"˜Äx"4þ[˜k¸HŒ)òÐ;æ›”ygׄ ~aüKÆ­ò)#Ó±¬.iäJ·%ã_]ê@IYè§ úÄraú#_ Écôø¥NÊ—:tô ºj^èMúÕr;]ûü.“õ”ѧläÚF!õíó?²ä“YзMÈÀ6®ÍÍÿÒ#ZøCB>8hÔ%|Béšþâç?þá25ýßòñB"®ô±TãŒAÇ„eý ”¯†äѽñ,ò¥×èšñߌçNch¢Ž¸1Õ1ã›X’¶¦·~h” -IH}3ÿ·>ƒô þr¼gåÇq É×}#-ÔK¯¼füÿâçíG|‡¯è ñ¼(ðŸÿéŸã¥/}i‰ý¯|å+ñïü‹¸è’‹ÊÝùÓO?=Ž;î¸ÇðÃGÒçö8û£ÆÙß[ÚÿÊÑ>øé×:)_}lvLœ8±ÐµÏÿÏþóãòË/©S§–6bÇ _øÂ8÷Üs‹È:ÆëøF§~*ßöE@DÒµCmäË®‰ù…}v éË¢uç;ÇŒiñ凾+bv.*ö7.YtmDnÐA£0=êxôîE±`û‰1iü˜è[rktMHy£Zòú{×Gÿª¥š;“¶³óË®ˆ“v!ç cñ½7ǬgL‹ñ“FÇÍÜÆLŠQ¹É@Úл!V®]ã&mv›Vh_Øql©«;Û(ÛÔ6†¡?ЮY³&¦LžczFÇúÞuå”Ëó\÷Gg7wÓs@—£þƒñÝsϫٟÙË_ƒy›¾““y"¡#e³æfÓa€ ¸<;’3ÏIpìŸÍˆ\½çÉ€ü‚‡ÝÉßÙŸK÷Ü`èÍ»Ø=¹9ѧú’x ïîw%MîGDêêëɉ>ð¹ŠÏ ôeŠbk ,ü“®;3èÍÍ€žÄô橃²ñ‘a2Aä¶AÚD¼$$ìƒpMŒäéÌM 6RažtH›{[ïo`ç!E§WFöQwn, ô°©€R.ÿ²mœÇ`S¢ñþþmú(bÔè±1yÒä`ƒ`\ŽurŒ‡åË—ÇsçÅèq£3Br’ËS,)ye”eLfÏ—<ÜdhfOç— Lâ[/ %®†B¾°Ÿù>Œ¯U«×Æ£<+W®,¿æP'6ç+'Rç ç8yj:lwœoéøW~^æ Ê&ò^ÈU§z¨#Ï\†äå'_'ðÈp®V4ศ‡Î6©Hªå׺•YÓI¬Z¦z…Ò1ÿ‚ã2Õ|è ŒÐ#yu@xꂺÚ>è}3¹òõ«tâ±ÃûìSÚC[Ñ‹ì£>ºÈ¦ÆÐzýnãã $FˆIbpÔ÷Þµ,v;&Ư[£ó¹Îõëbptk‘ß•ù®5kc !4 Ó16RåÈ­qVÝÛΣÇäþX“ Ø\læûH}ks!±ºÔAsK¾3p^m«¶[ó@l·`ftŽÍ/—ëbM.h{{Gy½y `}âºÒÜ9;ÌŒ®|`££ :‡œXËä²ÎüùÈÁÒ—ÿr­œß0٠ʥ7äº|eË£ü‰ÇÆW¿úÕªÛÏ8ãŒøÎ9߉—} «ùÄóØAN<©/Wÿ©7—ñÙ-¼lÓ Lëyp m/S|nN”…X¶·ðåÉ\¬÷¤AèäpCž[(‹nê{sÖ‹7ÞÀ¯! äé„T’¸V?gM–ó.`Öwå&P‚²2ЗòÒ¤nN%h}fxyb_2Ÿ:9)‘9*²=©?5·³È/)z³>JZ³h½Ì1E°“Ñ“ícã%û¾¬7ýV}4Ø—ƒ{îWå‹PyÊE!Qâ-clÕêσŒFnbå~Vn¢1Žò‹N_ë”K+Drc+ÇJ1˜"}¹)Åc8%زÉú>C`ìèQ1~ûâî»—•ÉI—1‰^Æ$ɲÎÔ‘„àù ¾TæÊÐ8Æ•)tÌC/­y`ÍÔ9éMʃN½ÀÚnià!¯^Û(¨ÖÉ+T®<ÌÅà ¯m§žIÛ(ÃC™Oý%uÎñÔÃÃ%uÊ’°Cû䃧槬½ðPæ‚^Zð–•GI¾éÿV_êO|Vûˆ¼}£í3ýOÓÿÃ~ª}ˆšñߌÇãÌù¶ ´¡ñårŒëy ~ãŠN¥‡Æ¼P¹òhòÕŸ >’ú(Ã#¶·þ;ÇS—<Ö) HÂ6퓞šŸ24µ­à(K‹,ËÊ£Ž$ßÌÿ¿Ÿó?10RÿO›6->èàxËŸ¾%^ò’—Äû⯈իWÇÇ?þñxÎsžS_Õ3*þæÔ¿‰¾\«~û[ߎE×-Š·½ímeÎcêßýîwãðÃ/ó?ù#?¢Ü\þã?þã²ùpÌ+Ž)òN;í´øÔ§>Uè°«=n-IõX°N<ü,ú·¾õ­¢ŸŸWœ3gNÌ;7®ÎïêÏ|æ3ËMi‘¡/Âv¹Ð¢?ßa×Úý…€Á¬!5ž:ؘQ=yç\Œ01FÝcÇFO^£òncw^£&Œ/u“'Mˆ±C8É8YXFǘ±Ý1zê„è™Â5)z&ÏÓ )7/ò7eb¡V[j;iˆ²G'ͤ´aüØq1!í™0flLÊüÄ„ÒΉ‰?n|Lš8>Fm@(Bór¸‹UÛ;\7ô@™—fÌœ9# ÈŽ…ºäÈ…þ@9ÞŸ¼,¼SwåIëÖ­ßxDþyEÒ9ßùVå¢"ìèå]¼¬¥;­Û²ÙÑYÏB,O ° àEYÏÝ}!ð"Å´ fi{.ôYÈ÷±x˽œ<È\âØ0Àæ|N%—ri/?ð’Bv$òå‰e ³<¾À )÷”Å{hÊçÔ9 ãá¾ß¹ôîäåþqæÂ/×yñ¡´£´mó>bÀÇ,xdã¡k¿ßJ™é[^™==Êy ú¨÷ÞøþW¿÷¯Æ/}qÍ9_Ë—­Ý裎î|n_ÿ}Tz‚˜û%}TÞÆ¹•qÔ?ØÓfL/wûùÂÀ8`ÜÇæ8[ñØãeƒ€¸íÊØ)>¹ÐËþA>û’¯Âȉ2c;ã»/7¬Ø Ì£*üzGo^lµõgÜ–_ÝH~v½z3VÙ˜[“ïUè]³.ß§°mÙ4`œ8þÉ3Wqa e c×ä‰Gå­ÿÊÖº´Åù[ê9›ô™vIÒ!z8êI@il#°Î»=¶X¯=êªuÛhÅÃO^µ<òòh“6€çR–¥CI»À“j:êÄ“W'8ejõ5 ´Ô™šþoÅ£þÅW$ʵ_ÅSG^ÿR®óòÔüà¼Ô„ÏrÓÿ9Ÿ Ų¾Ö7øŽÞ¸G= (~Öùfü7ó¿±ãx¥LL?ÆZ{Ä”q$žx#¯ Êu^cÒÏ¥`3þ[c´ùüž«ˆ'’qiÌ7ƬxèŒ9pÆ”1I}M-u¦M}þ#ëË_ùr¼ÿýï;î¸#Þõ®wÅ ^ð‚øÄ'>QÆÄ’8‰°jõª²1ð‘Ó?“§LÞ °_>ž~ÁÄK^ü’àÝô1§Ð/½ôÒò'øÕ‚7½ñMÑ'©9eÀûØÀ^’mÆæºLÞ±$^ì&äâåÛç«~ô£•ÜTC®/€Deué+ʵmByý›ßÆóûx*’@àì ¹1içbEž,LÇuäB¹cîöÑ‘¸È«#ïþuäÕó¼E¬È…Äv,2øð ¡†î 3æÇ㣣sÜ„Ö5qJtLšY®ÎÌwæs˹I±"òîâôy°9%“l$eò³§í«"ÆtЉù˜Ã´1Sc昙1k쬒Ÿ0jlŒÎºÕ³§Ì-bàó¢Í\Ú¨#©Ç7úŠ€ ñ2¸ äB=;,ùrqÓŸÇø²¨ç±„þ̳¦Ïö…¿0nâϱÇÿyηs1•AŽ]¹ä)¿cÝUñÖÉù+Ó§ÄÔIÓbæAß¼vy¹ãÌó Ùå)?'iöÒß©=ÿåB-î}ÙŸ)E»§ :6°á´‰îÏ#8¹ˆcSE;¤ƒå×R §%òLÁ‹þÏ‚˜ÿñ+K›xdb°+ßKÀs ©é¾¿7N^øŒ¹Y’‚Óýd@«^ 4–ÍÛNð$ç}¡¶Ë¯mò¦ü]­ÛàAŽ©n‡´ÊEží¥N{È{ƒÆ .mT&õê:ÿSOªýÖôË'úÛ~ê[û©éÿfü3¾šñÏG+œ—+ø†2y.æÇu&æ"ù€õÜå“^™ðB‹LR=a‹2Ї hköòÌ;'BGrÜ µzx´Mþ” «õc<È1ÕíV¹È³½Ôiy/pÐxÁåʤ^ý@l&QOªý†}ÈÕd‘¤E–8 úL测zè)“çBPy‘/XÛ®‘ER&yh‘IªÛ-Ê@ßïCÿ¯\±²ÕÿÆÙßÈïÀKâ#þH|úÓŸŽ+®¸¢üÒ>âîºë®â7AàØ?'®¼òÊX‘ço7w»\ï\I¸øâ‹ãyÏ}^Þ÷Þ{oÜ¿<¸ï~û–+zØ¡E<¦_GÿÓŸlìºë®1&_À/#°yÁȯ£ÿ;¹³fR e“À<ÀÉ{í÷çÏ5¬Ÿ>3¦ÏŠÈMƒ˜¿KëÚnn ̘ëfÌŠûsa>%i Hƒ´ÖA~Òœñàú|l _²yJ côÄ„SÊÕ1fBÄèîR·|moLÞf¯A ¯2Œö3vÍ)®Ñ=1®c\LFOo]™ß™›ù ÿº‡ÖÅüÙÏØØ>d1(h#yeê|Až‹I]ÐñüöŽówŒ ù¬5‹û\åâˆÅV>‹Ï$ËË ¹ÓšË&†+‡qò§¾ÔËBqMÒ½÷œkâžÅ—Åé{ýWœxÌWcy™CÓ)é]lVð¸BÞæ/‹ç4¡+et)íêÎ;àÙ€|Œ÷d;òå‹]¹)7}3å„É?ä‚÷$ð+ ƒØ‹¾_ý8÷„SQêËr_ú™ XÇM™Û°¡”eÞ­0~êè͉?íÞp__L=õìXœ?ÇyâKâKG­‰?Åsâã?^ö >â¨/kÌç(:G¥Ì<ÝR~"íJtêfRÎ>Ê2ím÷4¼÷I”Ó[œxHƒŸà£]_ÿùXrÇ]±ìþ¥ñŸï]{¾÷?ãÎ{–ÄâÛÇ[÷ÎØûe|4ª;é:¤,ÂQ:z÷®|²bâFu¤/;yAæ/úûúrÁ¾%qôåâ£ý[ë£þcuÞw¿^rQÆ ñØ /X_Í_H¹{ÙÒ|eÓqƒbÇw ^J\;n˜°'kr²\Ÿq[êx|%?œ2ær3)wt†è3(óº:7rƒ iû3óc27žúcCÙEÉMÆWÖ vöæXÆGÆñ†|ÈÔÜàá¤OéÝB|»ŒO’vQÆ&ËäKŒ§LáÖŽd˜KR {ô‹s†xiü ÖuȳN»)“ ç\%ž2õ–ÉÓF’mǵµóÿHí‡, ¶(œm¦’¤µo´…~ N]Ð#Û•E2k9ä¡kú¿éâÆ81Æ,7Ža3þ[s~Ã?$ü…W”IŽåvèXocÖr¤µošñßÌÿÄF3ÿ~;vœ·(“KàGâ)So™¯O˜¯x”÷S\Ýõqçw–5ÃMùŽ¿7¼á eSáØãŽ-§ ~ò“ŸÄ1ÇSbzÊ”)å1èïX|Gá¹ñ†cö¬ÙÅ&úuÿ‘Ç^¯-éÿº}¶óðç[7¶<×/Óÿ,€qšqe…‚·1àæ<ûÙ±aþŽqõšõ±27 6äÙ|TkÃÔ<®„Ewë¹6¤³À]wÇòXÛ5/¶Ýùy°”„ÈÓV»ûügÆäŽmbÙMËsÁ’NÊÅ{g.Œ¹ÊB>qÔMîØ6vŸ¿i'öð…m§½àìÛä%p¼Á}ì¸1¹àáØ|^¼¬0øwðûˆ¹èŵõç®<6àÑҟüÉ[¢¾Zt¹Jú²$O^Ö½çbjòÔi1fú.ñG¯{s¶ñ¦ø—?:(>ñ£¥©+õeÿÝöõ“ãÿÓXuë×âµoý\œóo'ÇÔÙÇÆUk7ÄEÿtRÞIŸS¦NŽ—ò¥¸›¾5ñõwŸ_øÁ÷âC/Ÿ“óÎû©_¿*nþÁ?Äô¤1ó5ñ_KV¤ݱüòÏ/ÌE"ÿò½Wœñ®˜žöÌ8èåqÊ)Å´±é˜§rhæ~G놹®½&NiÇæ/lì/þóÏÄ¿¿}v|â´sãñ¬[ÿÀ%qÊÁÛÆ´éSãS¾¥›¸]6 ÖßgýíÁ1%êì×þC\÷H.F³þö?%Nü×›‡|ôx|ãÄcãßn\UújÃÒŸÄ»œ“'&ÇË_ûú8ô Sãæ•L-qŠ¡§'_™<1ÆŽš32Ž;FO‹iã'å/L‰qù³Ëâ+§¾>ý‘'rqú7g]X6@rJŽ \N_MŸ15fL;.κ4w³Íyž&ksÒM=”z¦vÅÊ%ß÷Ѷ<Ép‡¿$¾3ÖÅŸz[LMÜôSâ¸SÎŒûòÎy×ÀÊ8÷¯^Ÿ>çÜxwþÄèÁÿtõˆq”]^ ˆ2¾2¸2€3ærSçtæÆ>â1–üû qDb…lŽp=ž¿ Ð™+§Tˆ…Ž<90˜¹uòÒ®|9b7ñ§[ûsçž ÉÍ(^WÁi›!lºñ ÚÒŸ.iL©ÏªqãÇ–þ^µªÕïŽSÆ._>üBZã‹àüãø¦Ž´5ã^’ráeÌ:/‘çªé°œ<ê/‚ò¼^ÐpQ&ÉSCðʪ:ç2ê¸%^h·vþG®W»~m ^ùàÐ)­í‡{¨GâCØöÖ¶ÖòêzÛ?yí²Üôÿð—}ÕôkܶÇ#åfü·6ÛcŒ!Ç¥1CÙ1æ˜$®ô¥yë‡ÍøoÍßÍüßšë!⌘1Κù¿ùü¯?ßwêù¤®÷3ÍyƸ²<Ò翲œ³ ÇKo¿íö¸ð ®/¿ˆ^›/ó¿ýöÛcÁž Ê<Ç÷I¾kòÞ~nüóÿòùXš7×®¸òŠò¾6°ë¨#ŠŸþô§ÁIƒÃ;¬È{îsŸ\xA\zÙ¥q×Ò»â3ŸýL¼èÅ/*7ÉNÛ¼l‹í¥¬­BÛPC꼜±ùì³Ï.›!úè~™ï݃ÄÕä¡— ĹG÷_´0.ºý¶Ø6égäKþ¸YþÐýËãÞÅ·Eo>û1÷àÃòÀÀÔ²‰ÁÈarÀ`ä0=¶]ðÒxàÖóã’ËoÉŸq|4fäÎ é¡åÄ}Ë–Çú¾íbî‚#cÌøi…YÈ!ڼɦÆsv;"Ý™‹ŸKoŠùÛ­‰™³òDD¦å)ïλ—ƨÁ)±ïî‡ZÛ $a²)c'2)ƒ7Ésß}÷Å3÷ÍŸYäj.lòqu–F¹Pb¡Å²®¼%€³ùí>ùsѤe9îR'îÔ¹ÔBFwÊsÆãùPHvþ£±î®âãÇ~:Æ¿ñKñ’}å?âÄ¥'Çä%ñ链G|ÿƒ¹ªûV|ï쿉¥Û)®½éocf>’sûsÞ7Þóù˜µîÊ8n—Å9o=.Þ¶G.lsüž×.ÿû£Ë⪇¿ûdœqôßÇÕ·\¿ïÐxÿ¹·ÆÂ“Ÿ«îûY\±á5y '–ÿä£ñÒ÷>çܰ4ž;~i|þõ/Žóóîz?·õó¸|7 ¿<õkd“ÿ³5yò å£Á8àÕ§Dÿç΋%«^gïulŒ=ó’xìÅSãÌ7<+ÞùõgÅ—OØ-_ °MÜð±“âž/þ$n¿eBœù§/ˆ—žºGÜñù—Ū·ÇÖù¨+¾ñêü…‡\u.‹<óø¸ãýߌe?=0îÿÙ§ã¹ô_±6¹e ×òc…w9¤k[}”w¶YË ¹ˆýÁiGÄÉ׿=®\ö…˜ùÈåñgûï™zUüã‹:ãôÇÆï?;î>ï xô¢/ÄÞ/Ù'f\û`¼hv.†²…¬uñјIñ¿ßzv|éüËâ/'Üo~ÞÅ{÷¾*>÷ò91#ûâæ{ÿ%¶ÙpE¼lÇÅ·þôøxëñÈýÅÞ|WüËO/¿ÝiÛ´“ £Œ)‚d(ŽØŠéÈ»ïÄþŧücáÝ—ã¸'Á# ¾iH{±¸M‚üŸOÄóŸ÷üŒñ®¸,'3ž*9ìCâYÏ~NþȆ'øˆv•w1d¿¶|ÄNå`ì³÷>erå]Žžÿâ8ÖŒ™Û$mú$Â#Ýlªå ÌêÇßL”¼O1—æw¦Íyn"û¡7ÛЛˆÙRÞu¼¥­i7§X¸úóÝk×mˆY9_ðˆúIŽaç sóãŠËùzq5/¸Í&sæ†šß ¼uú¸K‚Öye 4&òê‘—6·N¼2Ä+Ëv"Çùšz^³ì%ìµ ÜLæ=³rݹÝvÛ5ÄÕn»í'žxb9½À‹ Y÷}+úŸŽY킇äwê·düó¸$À\|üªÂ 'œPntÿ*ã¿l(´vfû,J4~Rž"˜˜' ¹ùÆXzûâ¸ñ꫊afçݽ÷Ži»ïù\3wâIÎÅϲÕrhŽ˜2c§˜8éuñȽWÇ’%×Ç ×åÝÕLã'mŸwÈŠí·{fÞÅŸPŽR@OÒ8xtº„G@IDAT€Ÿ3}û¼Ã|\…¿.î¹oqÜpãâäÌ;½sb×9Ï‹·]ï5W¾p*^óN´ø<©Îƒ£ÌBeö6³¢o= ù%7W=¼€­—®\%&zl°(J Òô–ø¤ˆñuÐuç]žÛ.7ðRÁ\8MLy;zßøXr¾éƒÿWüÙKbÛbàݯˆï/{g}÷WãÌ1;9¯_—ï„85Îyÿ119}Ý™‹µ=Ÿ³c\ö£³ã{wÜ_duæsÿY±¤?N9÷œxÝþ3¢sÅ bÏ®…ñ©ÿ÷¶ØaTþ¤äþÙŸës£'Û×ÓÓ8‚y]ÜpþY±Çßž‡Íž½={Å Û/¾¶2íÌÅ Ï ÷&Ä ¼'–rôž¶mô¸‰ÑuÿÕñÙ|÷ÀÁ?<#>|嘸é{+ã²}W$y.—{ï‹:?þÇqÏLŸõÇëßõú8ý-÷¦ö\¨0ï³Åœ¤ZÖ§6Üyy|fðñ_o>8O DìrÈQqpühhs u€>æL@é\0ÓgƒôYö#w»;bIœ÷ù‡ãÔóß»NaÇ©=0Žüòñ‘½GÅ?î?xË!1!»glnüã‚Ä^¾8^x4-Í/¹‘ÄVĆ»úâ=ßýL³ÏäôѼx׉3ãK–§¶ÏýðkñÝ»î-›A†žU[š}ñíÿˆã÷Qú†G1Úãˆãýå1|Dì•¥9'[rqœkYÞ@«:ð}¶ë‰q”ËrÐi*Gþ{±5ýÆ B‘Ô››ý¹“>OðQv^ož  ŽŠò¥œ¼¿cÖœYñèÏ-;¯ÙèÔWz»üãã=’'R.}ŸNæ=„46¤Þì†,¤L6’f OuäFý³!wz9iÂã´£?7:Ê)¤Ö/‘tg?õå#“ó´ÈwÞ‘'D¦>ú´ž0±‡2syê¹ÐN›Á‘žlüS/¿sX³ýȲž<‰²ÐºÚë‘QÛF™‹yÇy”²"@é‹‚tÕújÈÃlÛ¾©ù¼¼ú ö«öS§?µ]»¡‡Ž }ÔCË—46¤«ë”¡Le@£.hH”‘mH¹¶xÁ‘ Q6xR=ç×yu‘«,¡z‘a=ye¡uµÖ#£¶2WÓÿ­1Úôk.c<3@ã¥Ø±VÇ[ƒäáoÆ3ÿ;÷ÌgÎiÄxc§®cn¢ìü%/´Î]ÎëŽ]hÑ'o3ÿ>àdzþ6óÿ¯>ÿ×ó`íc¾{°)pËÍ·Äm·ÝVNÐòBA~Úûá|GþÿÒ—¾ëó%ö>öhÙH¸éÆ›â–[o‰¹ÛÍ-õ¯hýŒ!ýÄOòë <ê@b!ÿŽw¼#þì¤?+?¹ÈFÄ#y—ÍípüØÿŽc`Kúÿío{ч,øH@E@Ÿ‰zõmÍüŸ7Z[B8*ßU ‘@êÔõ”ÐÈîÜ…ÙîÀƒcçÃÜ8‘Ð ê8¾±./&Ú0t€ƒŽ„|êÙ\èîÛïþÂØyŸ—•/àÑ GŸ×¤Û‘«ÌXñ`_Lß!ßa¥®ÔQ^Œ˜…ò yÇ¿‹/ÕÙöÖ)‰$Jÿò~}tÉ·>=ümì>'½ì¯Žƒ¦ $<:ÆÍÙ5•yÄ<¥Žâ­¥iWª]Ï&A¦¾ÔWB 750m¸»ÒߣÇç‚:O¹ŒÊu¶­¿w}>À—ûl_îÞôñèHöGä»óNý`ÆyÚN=ætômˆµ)w§<Êò,ŒI+Öæø¤íœ=IÇKó}y aÕ:ì);S.ÞSJ¬I#§äÎErMÜfF<º:W]o›wx¬~ïgâýÇžö÷±>_*90Øë’‡_Én2ޏÓ>ˆ9Y‘í(§;Z>âW;hw_¾4°;Ïõó"Î'ÆQWö)ï³È|vð%ydêÚëÅ!œ:#.^xqÞÁ_xH.ÜYÌ ù(ã¯ÄQ>VÐòQRg\ñ¨Àn»=£LH,ÜIŒ%vnïÏ sÚŒ½bÝšÕÙ39ÐÿÙ…ååšlÒ$]š6æF@ÕáÝy¨:ågߥž¾¤ïÊbû©+73x¡('vºr<õçû ú³ùYQt1¹O:µèwþr>c3¦œ)“ç¥-ÿÊ„^^äPb:HÐhOA ý¨3Qv¾ÕæBçü‰.õi ztÖ|ÒÁ_˧LbÎÛÒù_¹ð¡„|!8®-ÿ¡…_i«v*ùèÆGàÔ½ø‚Ì?ÊB«Íðâ?éáµ¥{²ù_™ÐË«ÍBì@©éÿV_áב>ÿñ#~³ÏôaÓÿ­9ËØ5žšñߌæƉcŹÈ9¬™ÿ[ëçjÇP3ÿÿ~þ3>6÷ù¿rÕÊX±rE¾}jYð/¹kIùwîåô¬Ÿç¬sY»ò~¾{ò8tè -[¶¬@é©cÑÞ•7ɦç/qRݸ,„ù§ýûŸ²çÚðdó?ï_@§¼ò)èü Þ­ùþ×ɳÖ,øè…Rò$ e.èyãc=V~=€—r<ƒ_`AŽLv•#?_ÀÙˆ"8ÿ £áE/V{衇ÊÂ~‰óË¢¶À,.dàLti /HÃ6db†<ꅼʤmæ­¶O>È¿c¾1ïŠäË»ò6j~ħ-y墰‹…aæ»rÍÏÍå^B–[wÅ÷d©ØÆóݹ$å§ Y‹RZ™ ªÑ£¸‡œrsA˜K«ÄŒ‰£þüÝqã'ßþÁAñÖí–‹Ä\,¯Ëþʸ-?¿˜ »Õ·__O{ßüúãc×ñ«ãZî(¯ËÅXú”^Äy÷6} "¥/§¸ Ì‚­l÷à󼯿Ë{ÄE:+=¶*_üÓ8ý//Žé£r1—ôürDz2}“¶çé>¾25aèÞ¸ðŸOŠWÿïûãc§¿8¦î¸_¼»sTüì–5±{>î±Ï>{Ŭ1,Ös/¯¼õË߉[ÎÙÿX\üµ¯Æø×íÓr3ƒ´ø‚ãá¾µqùçNŠ÷ö¯Íu}¾G`Ê.qüÀ¥qÊGÎŒE·\ÿë„—Ç“ò5Ò'øŒ¾ÉFåæCvSÚ‹´lWÚ9ÀFC×ÎqÜ«¦Äéÿt~<’×µú¦øÊÿ¸,Ž}Ãóbz¾ ã ƒ—ÆgÏ»¡øè±Û΋w.|<Ž=pÇâ#¦ ôøsÌ´®8?ÆÃé¹þG®ŽO}ðæxÓžÛÇãw^g§­o~ÃÆ.ã×Ä5Ùƒë‡âGn\m&ŽX¨wð‹IÉÏd²!•âÒGiñÌbÑy¿GéË|L¤#+º~ÑÕqõµWÅÏ? 2æ™ñ¬ý÷çø¼¸úÊŸçã ·fœå—ù!G}”6ð«!½œ;í´k·x‘ ‰ñÏ›[«_‘›!¹a[;Œ¸Ž éßÙf~Ùsãã8½l¶Ðe󅱓QTÆ<´ ‘ŒÃ4‚:î{ô®ëÍ—ÉÌ(ãÜñï˜vÜúAžô«Œe¨ƒ±ÊE»Î!Nî–àà“{-SÏ\·)Ûœç”8Êè&i8å ­‡4è"gkæÛa{‘A9ö?e.õnnþ‡O:l"³ä·fþ‡OÛÈ7ýߊ{û¤éÿ_þóß‘Œ1}ÚŒÿfü;ç›ù¿™ÿ™'ülú¹éÜÑ|þ·æÑ§Òç?ýÄ©6œëÁ‘ÇNò$ÊÒöæÏ€oéü\¾kÁûtìÿò"D‡øåœøâ¡ü£SÀ“h4_rÙ@`Ç…<ƒÂ ³¦$ñ}”‘-ü\ÈâtŽuò¼äI” >pÐsÈâB>º¼àñÒ^ |àI”m;r‘ÇóÓ“'åï_nHî¶æÂ¦3Û3˜‹Òþž\Ôä"¢7˹QЗõ,íò‹½:ŠàÍüŽ»º,ËJÞ,ñNƒî|£}®3¨s†ïÚ¹p›¸÷+ã=£cò»NŒgŽÏ;¶ùº1¹ž›æ± K;Æ-xQ|ðà qü^ùs¯<3æ½j·øðÑoˆ«Wtƨ\Ü"ŸÓݹàÏá£Ëb.ï§ ƒ™g[¤'ï¶—Ÿ:Ìßq\pü?Ä_?ûÌ8t—¹±ãÿÎ_R`s$×}åçÙ@É錵mwÜð÷ÇÄv3çļy{Æ«¾;+þã’Åñ'{NM;Ä)W}5föŠ˜9=_ª8}v¼ñk·åô´¬ycQüÉγcêÌãí7œßÿÀ‘)3bç£þ$vúÞ;c—9sãc÷'-]ÚÙ5z§ø‡+¿‡,ýlüÁ¡ÿ3&¾åÆAƒ+òäïÞÈXK_öç3óÅGéK¢gTé ¼‚éÇÄ¿è¿ïºï”Øy攘2ï¸îäÏÇß¿j—䟽ô qïI‡ÅôÜeœÀ‰qòÆ«æ->š˜ ÚN~WߺÌôÈ5ÿ;çO‘ÎØõˆXyʿŻ^8'&ívt|(ûâÕ{NÙ¯üRlÿªÝãC/y}\³º?FÓɺ¹8Bî`žhùˆ ¼ˆ•ì;â6Ò”Œââ(7Aò•œ6ØcÁžù²/ýó§}r™§(žõìýãØW¾2vyÆÎ)3éC>2ކ}”q•¹y1iJnÊä&scæÀ>P6¾èK†jFXn²1fØ Êq‘V⮜ òDMk óè §úi#m¥MùÞoG,Ê©ÞóÁ¯3ŒËG+ÛŒq6Ôós 㻼û@p$à¦Æ='¯å¡=Êvñ¯æë•C>.u“‡VyâÑ%ÝHP=Ú$ó(õà° ˆäÔa zÕ'¾ òs øvpÒ#K C—8 ½è£¬-ò GòàIBòÖ¹HÈB&ÉzËêªuÔöÂK™¤Nm¥lÛkùZ:Ñ=WÓÿ-ÚøJÿ5ýßÄ þá2öÈ7ã¿5g8ñM3þ›ù¿ ’üãœRæç{ë-Ûçkb œe’1Þr3ÿÿn|þÓ§ô¯—}/Þþ®?« mú¸ÿ;òíŽùXsë. ñÎÇU­Ã‰8ÎÁGZêI~A*…ü h'p· Ù@ð~@Âüƒ\qBêì8x ÑhÐ]ÓB z€\&鑇,ƒÛƒmÊ«iÈsñûì·Ol»ÍÜ¢£‹‚¼sË"ŸõL®àò¤AÊà5¶åBŒg¸SAüÛWΊ“N:±,p´©â÷Ïæ 2^ÿº×ç‘íôm~ŸÈ›°¬ªrñ•™TÒÅíÞ”Ù—‹Ä.žù^wm¼vû£â¥—ݯمÅsöe2uåÝàr‡8\,&y6|åÊÞ˜/ª‹®|”äñü­û‰œ áNp.Ú’¯/< 1'àáÎ|.»So.ê:9²Ÿýž¿Ð—¶ ÄÊk¢37Pr¢¼å¾ŸŸiLy¼Ë`có%¦¶ÄG½±je¾£gLLÇKH²‰Ýë²/ƤÿÖÆÊÇ×ÇØ‰S¢'w1Ҍ҃ë×ÅŠ¤›œ¿ƒZûh]žjŸÏÒ㣇®û|ìzÄ…qÑÝgÆî£ð[š”mÚ´-»qþºÇ×ä7Â3%ã‰>ˆëòÑš||b\êÑG¹«£ò× ×Ä#é‹)ig†KždÀGkÖ®Îw LNƒzs³¬7Æ·EqÄJº#9µ|Ä)Œ\¼§ œléÏ<,lôQvèæâ¨«§3Ö'q‰£Œ7N‡”„o2Žžè£ž<)r_‹ºúškb‡v(qÏXañS4ûî·_61¿¦„bë…ˆ,3fø©Çìï Z’<ôŒ9ëɃã‚?óâ 4αò€#o¢Ì½²(# »›ù¿µpÓoø…|Óÿ­82îŒ)ýÒ_”ñÛïÊç¿í4€øÀq£_h7´Œ/긨kÆÿÓûû_ÓÿÍøw¼ûyi™ØhÆ?3_kÎÇ?æŸ*óÿE]tP™±Ï¾³ß(oný÷›ÿ—^zið«؇muÌÓîßä÷¿ò"D;—Ö †èp!xŒ§RçFxêå¥:Ê$è OâƒTÝ@hÅù!+]í0yÄñW~ ü\$䊧ì7x.¾°Kk› ·=ò¢zsØföÜ"§Ü ÍEû‹—\™•câ¹*â'ýXU÷åB{0ßæÆ›âYä´ôä÷Q¹°ßL‚žò\CÒñÞ\if&3ç"‰áñ|‹]’tÄ⯽?~0ñôø¿ósñœ‹.žÏ?y´<}çâ‹—¦AéôÎ|{&Çàó‹N»Ÿ0žþLó­õ<ôPÔdwõäb¹+¹ü¬^_²uå‘òÁ\\âQÞ5À¯û•SÙ—£'OÊÓéƒäã凜DèÌö牋Î\!o¹:ó×4&ç:?wóH<þëÈgüYŽŽ‰ò<@¶¿? êJ{ûÓ~Þ¿0>740lØGkãü¿Þ/þøì‡ÓÚV:ù_/ŽÝò%ë̶ äêsÓ>*­Lo¤ÞìÛ1ãó'5SÌ/ú(ß1nbú¨ÕM#û(m£¿r3`jñYÒßè£ü¹G6 ðQWŒÍG2ò|L¶ãÉ㈓¼Ä°å£ìçÔú>¸<ÑGé«ÍÅ/L†G¼G€Èê[·u>êëXÛn»MüèÇ?.‹vÆŽãÓw-Yóçïœý™ñ›c‚*ò®H©³/}ÂFX_nÖ`ï<7˜A•Öe\ Œã,³’qÇ#3œ^è#ó”ĬmfÇÒ+–¦Û¦+ˆúñ×7þ™7ÿÌÎ=à˜hk³Cs z¡qñNó ó!<È’ S®s*<Щw¥LÒÊ\ðÉK=òå©å£S[”=I}@øIÈ‚‡G?¨ùÊ”—öÔ6àIØlûõ‡8 r¤ÓNëÁ‹C·ü@.è¸HØ ž26k#u[:ÿÃg»Õ ÙMÿ7ýO¼\$b„Ø46(w\Ä t@R=>SȃH=y’qm Ræ‚O^è/O-Ú¢ èIêÚd’€àᡎq¬>ä+SÞfü7ý_Ç`3ÿ7ãŸ9â·ùùÏg='Ù½ÁB|>U?ÿ99‹m¤§Òüß‘;/­oViZÀñáâ‡y?¬h´â„$àÄ#ƒ8ò–y—ÁäÉ“ãyÏÉŸ¤Ë»Üœ#gq_ža/v¤Žü—ÓCy ·ErñÎÁ^H÷å;«6u³ù7¾ñ ¥ý,†yQß@.,»ÒösÕÞ—'°—Ÿ§ë_¿&Öt‰ø,µóRB–_ƒl2äB‹å~gÀsáÏÑî>_H{ókI.ðYtò&>ÜÁÅG¹îîâ´D~9(?‰—‹ÔÄwf¹#ë<@Ï òrÞúÚÃ"6uæi‹\Ñe}ÚÆ¢.áÖúˆÍ 6<:rEÈ/¤aåuz Œ¹„LôѾ´¡´~m÷QnZ¬\™ï°ÈSã§oÓÆÓx&a.DG¿ŠÆäFØ?¿¢¼KdF>ŽÁxsüp:‡17w4qlžxÉ8Ì͂ܚÈ~d|QÌyƒwðX §ZøŽÌæNÙà*½ŸtülcÆEnjWŒåÁ”Å£3ë׬ëo\{ç/¸ ÷×=þ¬ÊD/“º8êÑ[ÆdÖÑ~æ5çë$ø˜W 'ï¼å\#N:x<õëcêƒ>óÈ©Ëð(xëD½6H«®šëí_ÛM]=—k¯¶`#8ñ¶±æQ>ºkýÈ€µ=àêdÀ+Ã<|Ø®øÄÙ òÍS&)\MÿûŠ~!Õ}i?ã?ðMÿoüºUb ¿àâʸ5öÄ?ýiüßàHÈÁÇæõ¹ex” Þ:Q¯ ÒÖãO=ðXN™Mÿ7ó?±ÑŒÿÖ÷ƾÖã‘×ÍøÿÍ­ÿxù /±ßyçËã´ô}Ã|Æ\F_Ôsšyû>sN„ç¿ëóŸGJo¹å–˜3gNùU0õcO­Ÿ<8ÒojþïX¸paêlÝSq± ÿÔ §ƒ\:‘ú=ðц œéù"KÞÉÁN)ãÉ1ÏøáçÇWÆãŽk¤ñï˜tž@8Çñ¦Æ?zùP ýºÇ?:IØÂ‡ sƒyí«m„Ö¹ 6ÁG›I¶“<þ!Q uòI”W{ê9JÌ_ÚºMÊÑ>ëÔkŸ€‡†>„‡¤~ xyÔ!xiAûŒäR§”¡7™§=&m…G›ù›šÿÑI¦¦ÿ[qÐôÿð{“ˆK.bH2fŽMÇõÒ‘oÆ3þ‰æEb„Ø0nŒ õ$ç#h3c¬™ÿ›Ïâ‚x f¸˜—LÆ‘qcqGŒã‚æwéóŸì³v¾ø»}ý‡h³cL¿è·º^ÿ@‹ŸIúØ^VzijÞš¹¬7±×¶ý6ÇÇÏ~ö³/B¤S;£ë2Æ×N°Q5?3 :'2„^9ÒÅI££ÐC^Gå£ÎçX¥©ùO½©ÖÞA¢^pä9.r衇ÆösçÆúþüI<^@HåçÛ<‹Ï& |Äwçâžµugy„!µåB:5³.OyÉ‘Ïu÷¥Œž\ЗG²–uû`>_ÞÅËS6 î|ö¡<‚ÐÏø—u}ò±1žJ˜0¹¨ç ·ÿy<€gÔó—Ò»ÇóàI˜uðaSÚ‡™Ï½‰ì§’M›SbR/»}¹øGFIBØ{y!‹å§ò°ÆäÛóûóHDëç%±;qi› ~¿|ÔÝ5*–.[\pAì±Ç%–\Ø90ž¸ž.ãŸh69÷0F˜o(ëy¨nŸ|Î1e¾šã ##ϼÃÜ¥äÊG=yêÐEž‹¼ôðêcä*Ÿ|=§)Çů2l ôµüö²¶ˆ’j~ýa»žÎó«u­¿úÿ4ýߊýÓô~@f"湌pÄ‹Ðñ 8ÇI3þŸzßÿJ' ý±O›ñ?Ïú§ÿÍøç3‘Ô|þÿîÎÿ]o~ó›?è—F¿9!òAæ—PêÌCO¢Z?)s‘œ@êºR1ô‡/±(è(×2•‡nå@‹lÊn@/8ƒV9â)ç]Ê„G~äQ†‡<6’8N}Çw”¼=ëYûÇÚÞõ¹Eƒã¹hO±™òÔ@fºs€'¬³¢õ^|÷@g®¶Yd³ÀîÌŸáàlû¹9Àÿ²XGoþƒ“£þHá7êËO&=¸V빈ç݃)·;õ—“ YæYo÷,ìY´§w“6ÿæ.úy|"…%]ú ùZ/˜KBæ8O ù/w­Ñ8^¨×™:øh(–åËù𠿪Е§Øt ýl?ð˜CG¾sGºò”B9ûÌ~¿|DLŸ>+}쑸óÎ;ˬJÓÓiüc4ó󂉼s 8ç$æcÅù’òÿgïK«ªÎ­×ͽ™C æAQ©HETP°b+´ÕJ-Žu >¥hõ鯨Ôb¥­´â€Š¢ˆ¢”QT&„@€0…„y¾¹ÿZûÜ“\B†E_ûÞÙ;œ³Ï¾=ܽÖþ¾oëºý§ïvZv|ûšÒTZ ú¬ûv:zFé*Øó˜ùÂ;m½Ûi(޾ÛqõYÁþ®w¥xMyTÏ[ßˡ甞™üeÒ÷êyØåµ¯ë9§ý«~C-‰Xí`·®Uo§ý-I©¯ª_ýX¿ÿÊÕÿÎø·çlõ}Öø´ƒÝ'íyNýÓž/ÇžíûvZöwűӴçegü[¿'’ƒý;"9Ù²vÆUÿ ì_’—d¤?]׻ݗlùÙqô]ýÎŽ£wÉÚŽo÷a}·?ö]ÅµÛÆ.C`÷tÝÎïÿîï¿kÞ¼yÖJÒnMçý 4oÑÝ»uGûöí©šZ \F;€ÈŸÇÈ ëx81òÜÆ5½1Gò¦màº(-*û•',0zϹwÉY¢Y¿3žQ5`T¦WaÔØ<¼©.BbÀ˸&*<͸‰ÀhÊ^Z å$&t ¤ŽG$œ÷û!à3øò’¯Xdz‘$\ø³L̉±¼ÔZ¡ ås\#5ä8Pl„¨Uð‰d'&S_æÇ‚¨ÎÖ¥\¼!hÁ´Qvj“ÿC2’kŒÐðPm°{Ï·/'8p$àHÀ‘€#GŽ 8p$ðŸ)WAA>Ô!lÃÕ>? €Ú„…Íw2JB®•2Àça!ë…÷¨-O€¬{öóº§Äø¦äô€ž¥ê»±•çyÈWšŠ¬uãYQ_l™˜UFSV%¥DuCy›7ŵR1¯&k¾pçSl—X0«.¼¦ÿzHI˜ŠË€ ݪ“âkWO;ç|ޱ¼$Jé‘ßEЭ<½º.msñf‹e%Š¡3Cm H²@™óq„„òåMìoÕ™Z:—^€Un ´óú‘4ˆÇ™ŸâÎ>ÁºdÈÿäôBB€Õq«mDdè>SÉ zê„Ã8ò»9rPµÖuÉ“ñDTX2bM]D"ð¿UH¦Í/!T}–SGÚAÈÛ=ÙÖ™¥ã‰ å" xIå¢rË¥ÎàÈèÿ²ŒBØ!<žcfÃ^aB0cMãÀ™êž±GÉ\ªŽ9Û‘‘#£³ñÛïô#§9ýˆëÊaíŒ5g¬9cí߬yŠ &¥~.,(ª^QÝÃíhÂd„ê;¥›@Qì€ÿ:rϨÌkg\»å|ΓÆkfešðp- )@l€­Á̊ôtÌŸÐ)36ý bW:ÜÇf^ní„k×[*3,ƒÊ©2Ê!Ÿ/ñ¶¾üfÒñp!ÉG˜.,‹ÅÿLƒ*æ%?& ÞÁ €Úù÷+kZüZÇRžPº-Ôξ¢ëeDÊÚÁ×N>042â5ÉHyË">¸ëîc:ò@u £@•Od‚‹èܘHFÒà5=hdÄ+ø›:RFBòÌ«Œ•Wò¬vù½ÆÌÁÀ~VQÂ`:LOÚæB¿ŒX6ñBlAjØ2¢,Lù(ïKF"}$#©EHò©`ÉHϪœ,¼é'”%®Dy„n˜.ÊØ' ŽŒØÿÇeÄ>Qâ-Oüdß°ûQÃÆšÆº3Õ=92ªÎvdäÈèlüö;ýÈéGN?â’ëÅzíŒ5g¬9cíßs¬¹ï¼÷ÞYû„ê ~S]9\ì[6ö„é\ø e œûz¢_µ@´6¹e?/PË)ÅŸ¼CUx]aÊ&ªÎë»Á¸Ì•€ŽŽxv3Mzà5|Þ€pzüçU‹ `Q’üꕺ? óçïF¥^~L9Ðõ.0nŽ4î x7À˜uñð¾U`Öi³]ÓN9wJ­²SÌÏ‘‘##§9cÍ™œ9Ûù]ão¤óÛï¬ÌÚÎY9kHgí`¯ý_Á´®ŒÔŽ·ÔÌ+Ê “嵟 Y;éÚF®0:õÚéænc»¥EÀîr.<2G ÈÖ1|Š»ÌóRÅ'æ6»òÿÕô™®6¦µ“íå.´4ÄJ”“0¥³Aù¨à™…!Œç¥vƒTè©ÜnŽ&,WY¸Si @íÎ3ÿq×eP~JNW¸ÑM^eRvâ6XiG0WÎëŒËg|†(`~" ¨Å@½’ T¹×ü3Z $Ü|V¦ÊBægKFòOÂÝy™g]~•DŒ|±r,H E2š9K¤¶B‰ŒÆ« ‚‘Ë©²‰t±eä×` L&…dÄÔŒÌi "Pü2’ÒFˆÌxÁÈ@2’ºå(bHªâ¦uÙöjÉH$36ò¦”Œ/å/ÒÅhP°Œ.Ö‰Çrð¦Å6—)ƒ´-ôUd”T‹MÛQƬ‚IVHƒå°ÔÓ‘×, GFŽŒœ~äŒ5g>ÒÏ”3gó§Äù]s~ûõѺ>Ò:Ï˵`ÍdÿÓÖÙa×pðµ¼L{¶1­+#=x”à;úÆ~ž+’ és'Þ@IÙÇš b8í'“Á½`¤m¿.0"-"½r‚D™nj×¾œi¹HFbà×'@É4=¦ò Ôï%8v›w*ü—“°pó$…4´Ë+Õƒ>#Ìb DpJuyŸÌˆg K™'s-—4¯1½‹<ð0‚e›/PÊçY~iOˆ„0 LÇ-ÖAé(O£íàßYåų!#`ÉhÇŽopàðQxK‹™2ëE™Ë胰dÄbȬAjÝ,š8ŒÆ(ÂÚ–Ö 'ÕÏ"At“éXÎ mñ»|/H¼ËØ LWí*“##É€Ù)[Æ|Väãi¡êc:¶Œ¼ÒðP©•'ó6ߨÎê:V¿à3:Êå±û‘r¶J¨"¨}ØD$ñje?Ò”È ËKÄœ:aêïÈÈ‘‰,ö[§9cÍ™œ9Ûù]ão§óۯ傳>:‹kÈp:2nÓ& çôîÅ%œ„ë_Cj}Ìßßçu¶ƒE¸¾|ÐBÉÁkÄÄ"¦åšYüßÓz‰Ü”E0Èùh°d<ç ìk§ßt@íhËÏ=Á˜gú#W-:¦O»Ô4+ «C¸iÙÍ3©rW)IjÈùŸ +Ÿ•Aí$‡ÈÖŸBr¡\»ã¾2³û,BÁUÆéPZþpµ0ò3G‚¥&Î4x]Š'b‚<€!Ê•ócÒ žå(“‰vùYz—›DC)óðx0f¦Bú,0šL[¶ûœ,•ÄÙ’Q˜;»wïB~^.8ÿ|DE7öd•Ó Ž 8p$àHÀ‘€#GŽþg$ uy~~öìÙ}û kÇN(å¿ÿ”u¶ƒED8xÍÁ´¢÷Î>¦%¢'.×1yýDÙRU)#À箳O»Á¡ôŽOíQîK»ßÅd©¾02;¨î’h`Z" ²+¸[Ímum–Sµž ]ùðÝÊëŠÍc¥ p/G|¤&ÞyGVÈR„ˆa´‹È঵‡NÕÊù"èo9FԤʴµÓÍjű2¢v‰ Kõà ã¿@eÑnºH Æ­ a@j€Äƒµ£®:ð@s ~Ð>?œ%ùHRäæ [Ïnh›Ô¡<úÐ Ž 8p$àHÀ‘€#GŽþ$PVVF­ÒP’{¹HïÄÅðÎ:ÛÁ"^s0í‡i¹H©wÉH;÷Bórø%msô TøùY^ùyE¡“4B*B ðY@l>N-‚P)+Äó>g?o9á;UÜòW@Â@dƒTÚ=L“XœŠ$‚@_ñ¤¢ÏRÉÞ+VyVÐÇ€ËKr‚û˜®¶ý²Ó06÷,¸Êå£ïK…Šß™¿Tßù‰ec¹In”“€0¶øôÝ> ¯u"ãÁÂHeŸ*ó"=ø˜*dˆ³%#}8ÄÆÄ9„EëGŽ 8p$àHÀ‘€# hC+6.†kg®·µ®ÿ[g;XÄÁk¦ýa0­GöäSÈG mÀ6wᮥ- ]y}¨àP‚õRi}Åñò(°2$BÊøWø_ ¼ø™Ý|ŽÜ]§†€O ƒ‘̃_ô¹ÏÏ£ åHQ ÞÍûÂí$ÜÒRPZ2AÐI,ƒŒ˜;ÿ³|Dôš%è¨GùØsQ+BÉ“s0÷e^ :UøË¢te‡¥³bÊËIˆ˜Ôä’ið¦—dy’,ë&[IùP}Ï–Œ|dk•‰›õu‚#GŽ 8p$àHÀ‘€#7 hª5°1ëå¹!ël7ãdFl¯³KŠKPPt £ÃZ¹Î®Ð&%ígsýï†EÌôÄ"^ñ&ìã£,åì‡Ä"‡RRñÁйiJÀA-r™™Ïc„.â*6AS®˜€VmZ|o¼¶rÕgP¡¤¤eeå(--A¿OýÅ ˆŠ©Äkå¥BZÄq$¤~ ¼fð"™¯ô¬µü˜‹ÄøŸ0wÂ~DL[¾Úá¦õx妟,€LÈ)²'ÑŸµ8¬Áv§ƒD’ ìne<òÀMó HuÄ îÌΓÐÎ?µ h" HoP¿À¼vëé#¿ÎöiÇ‘Éû ¤§+•äU«WáwÞÅõ×_ /AðVÊ“Ód ̈\— œe,RŸO>ù[¶nAFFl)¨AѬy œwÞ¹~ñ(DEEüpXdÛÎ-hÕâ(ÊKCQZ^h´—GÅ ýXsôèÓ;(,bð› Œø#:(ÂàÜ}ç݈­µ‘³³³ñôœ9¸jüUHlÓò{á5v1!#nÞ9Qžå|—–ŠNÇû`ùG,ƒ‹¤B)Z·j‰Q—^båΑˆ"Nó„}o¼V—ŒDÔä§¢°ä5Ü#QV’‡ÐðÆÄ´ÄŒ? ¦Í.;uG?ǾÜ]È*Ê4³IXStŽí†‰ÃÐ,¼ÕŠië’QCp¿ÇA(<, OóƒíÙ9Ëø¸ßé1ß oÚßS@žø5‡ÈGËGpn ;Á0ç íúÏé8Ei„¤{E 0M“8OQ`ÿ!‘Àïä4a¨£ñ…“ Ó•.£0SF#'¡AÌÏüøª„É1 y-ЄVÆ|\2}`•™DˆÈ ]c2ÊßG­ÁzŸ4drÁzº ø^ó2©0iÁ(ß F#‚q¿Ý³+?ýßîþyt nݺcôèQèÔ¥›UovƼø7üê׿4š:¢2PFæhD ¤ZH–°ìŒ¯ª™úò½z(§ÖÅ¢E‹ª_®ü~ÅW &&¦ò»óÁ‘€#GŽ 8p$àHà?SÇŽÃ7ß|ƒ£G¢¸¸HLLD¯^½Ìû÷­ÕÁƒñõ×_ãöÛoÃ믿]»v¡wïÞõ&«uª0—Ö(‘¿3nòµÎæñéÂÿ¡rfNDPZR†5kÖ"33Ÿ¯ùÆ£o3®ÉµÂç\–Å.®ëµñ¨rC±Èël®ý?ûŒ$Å[ïpÝNÍ×fÍšVÖWGÊoØðV¬X‰«&LÀÈáþ‘µNó²Üä<¨é|¦ŒrNÂAËHV D˜»-qQëÛ ß¦d§ÐÇ[XDû”’ þÌIsAÈhÿ¡Ãøðýp×wcý¾Oð÷ 3 KØ R÷:Þ=ÔÛ¿üî¸óNOãŠ+¯D«ÄÖ߯Ì)Å©ì"œ<™‹Rúà ð"ó’4 !TqŠ­< xìÈáCÈÌ:‰bi üüF^£;5 ‰õðZ}2RG¦Ë/N¡¶ƒù9¹8U´­ÂûKª¼Çr‰¸b׫©}WLûUæ|xèö \HS¥YØ”±_e~ÑIW`HâP£‰__? Ó¬ËñÇ,µA.ÈìÓ®·¾»ëôAîÿ뱪:˜ð°ÚˆCý¸>Ü/I­€‰œ³qK9)„²r:þÐÃ/·€ij³Js€¯ÌŒ¾èñPdâ3O?ŽU+Waò¤É¸á†)dÌCø bŽTd§!Œ7…a`võÙaYZe«RòźαcµŽGt³,b d‚ Œ ™Á¦ÉÄîömÛq<ýY«6èÙ«û'ãK‹@àŸï"3¼"8”';¥‘*W9Ó ¢g ÿÁ|÷9€ÙwNñdƒçž}7nDãÆI¡º‘››‹Õ«WáƒÞÇÅ í¿7ßzo-~¿üÕ¯XÞÓe¤ãuJi VLegþÌPe¨-H­Gu,ƒT|þùÏbÉ’÷0~|Cˆƒr|ýÞ?°¦´/nš8‰þŸ‹~Žù/§aüŒëÐ.Â…ê߀,œ¤· Y…Ô& §o‰(3¸œH=xK SPbbEÅÇ#¼žøßçvÆöåXy$WŽéD>^ìZ½ÛÊ»àÊQ½ƒˆÿ}Jö?ð¬÷>]ü)ʺŽÂ˜~­8°«}ÿ1‹ä-AnAM¡8Û…Ç"&Êš¸Z„`úR ûtA‰ÉñìÓ5…`âÀ[€¬œB>Þññ5¤d 8W–°âµÕ;˜:©ÖX-AxL"¼Ôê¨à†¯)ûSPWˆ¥ËÞÇèQ#…"o!ÂÙ ¼V—Œä/D„Û¥ à ŠË²à&!ç )CaÙ—ôFXñ˜:´‹Úé5à5«£sÓº˜vSÆ:,;ø¶éïµ½èÐH*øØ¾´¹°Þ~,¦u‘%øÇß_ÄG-7${Œè`]¾é¢ÈšI“'aÂøñÔÄá÷:p¿-#ZpFPgÀ%#!`/‚ Œ.ÐK ë¥5[0*/ªAžøï' Þ·&tUò*L¹ñç¤Ú¸[/S¥IòAL‡VYd4*¤…ÀÎjìXÈ:é¼u7¼ÕìdŠk|0Oíú‹ÝIÃ[Lƒj.dH8^ŒÏ„ìœ|vî$dgç`Ç7;ГgɆ°LåÚÉ7¶wýiCä1ƒŒÅáórœÈb‘iSg¡N iRH€·QPX‚?Üw³SèСƒ¨Xߨ¨(v¼ÃK`ó–-øÝm·òXš$$$° b`«dBC3°EȰ†,Ë#yh –œÕxg¥-U2ÙÙÁ¾&2ᥗâÊ+Ç©qP„MÏ܇»OΠ?iPv|3n»ÿt‹HÖ¡Úw»>ÿ3ï%Øüös˜¿"% û$Ìxò~ôˆR{`é£wcYZÀm~>ãqLêQ“vG.ãßËøi©Kŧ%á7ÞƒIƒbk%©Kqûã_aÆSùËQC¤:.eîúɫ㲠HƒbìZ¾ÉüÁ¸ì#iP”O’×ò¢¿ETÿ^‡¿Ï­êmXÀ6½û‰e§%™0à:ü~ÚE¨ŠÃ—ñþøà`ô ̺ºGåsõ÷%öÙYì³é•q=1ý÷ÓÑ;^§B0q€c_¼Ž'$£ØzŠ™·_Žæþd‚©SɱuxlöB'aÀDÜ9mìT¬ìZþgÌ]²Ó_`è 31eH’ÿ{puòæ¦bŇ«°c×^¤¤gs,?ɱ\ÕÁÔI–¤­ÃS.DÕÔÑ7ÍÄÕýš›ò['á¿G{Û)XïiŸ¾€GoÁØ;ŸÄ¸.v½‚“M}uªÊéþ|Ëèÿ§9”ón[†ÇŸžTÙžUñœOŽ 4D6a M©¾}û¢k×®f­)Ò`ïÞ½F;@& _|qC’>-îÞ½)8çœ^æš´Ö®]KPǵ1C]AëT{Ó˜ÕhúêÔ2™T_g»øµ¶åBúÕW^ÁæÍ›±qüÚd‘ÙpÓ1Ž :•á™gž5r§N2×ÂÂ<¸;ÞZ„û´ŽæÆZ0X¤¦u¶46}¹Éh_}ÕôëÛ‡›¡~LSR OXÚuêˆ:(oÇÛo½…M_lB«–­qѰÁp‹E¤-]Nœ!X&,cvR ÚeäãnhXX$×$¹8pðuth?‘爇"¨‹Ókb³Ìô´*-jC؈¼©¯J;Še$@d’ ƒEßÌBX·lY®ÌƒE;…˜®Ÿn[ˆ„îQÄx,nDþ±és é=sž~ W]qZ%µn0^Û{À‹H¶c—¶ÍÍnö¡ÃYÈál1Oâ“߸¢\D†— Eó 4‰+c\`5 ³PžÎ'R§5¹+h²â‘#}V±>¼V—ŒÔCå×^ÎðKËòQ¸ƒâåEnúÔˆ!qÄD+Š)S‚3›5á5`r ´9e'±üà{¦_óòÉ‘%èß ñamêìG¬ “«Ó†Rx+V|læ‘`|çi<~ú)5n¨iâb_­ ÷Ž5qrH¶C6EÏÆ_û¿œ –K¥žU Â#X|Ìäɧž";ºíÛ·3“Ï…µ€3‡À¾K@]€vóÕ¨û ,_ÁrØWðš&‡ ’ Æ¡!YŸÉK‡Ÿ+'«"Õù#  €TDi¸(¸öíÛ ¯ ­Z5DZ#'X–]èÖ½»X^¶­ óôRÛAcŒm¯ªX²gÞ!L—Tqr(„„ºñ—çþB"€é¶lŶx\1î ôíw.U©ÂQQZŠ/·|¥ÜíoÔ¨|”’ÉlF§¥–ÌT6ÖË¥ÞÊIJRåMPÁIü›‘‘HšÚHÑž\ujEMáç?¿ZÿÂÕW_q£¼#ض?B œ\®¦êÚV¨þݾ~ÖÞ‹±ñÕ¹X’y>œ1²NMŠ]¯?†ùÉéxûíľ(O߇O—o>­$4y#èJBßöMÙ¾üÌ—–ìD5„Ô峟"„Ж0þ\6°;¾ /Ì]Œä/ O_’á"'Nû·¬"û¢µ!.N¿Ì7O('¾™ Ø$ ÁÇ.Õ†Ä*Áöåob]NWÜ8iàÙÕv`ÇóWÏ*Põï )fâVoÃˆÆ \ŽÅEc#!"É‹æcŦ×ðþýO­V¹xç/ ÐN0“T@ÆAô¥˜¶0qÂôk‹#ëÞļ%[0ïÍxþæ!• ÕÇ{l5f“0@ÒO0óÖQÈ^ý/Ì[öý{ <}ó@“N0u*ÏNCzD_LþStnZÆ~?—õ^ŒuêŠß]ìüAÔ)kóˆ0ˆ0¿ŸÔkÿ:Ë>ŠæIÏàÒ$Ko§¾:©ÐÅGÖbIò&ªûš*ð…Û!˜:Yxs ƒˆž˜zçõè\¾ æ.ÄŠù‹0à¹ß!I/ˆ:UeûýÚÛNÇÇÝŒ9$  ù{ê—Muò'èËHÅN_,.wáÄÛ€6#j$¾ów>;p$P·dŠ B@ ûqãÆq-[µ© ;ôóÏ?mÚ´ÁÒ¥KM¼Î;£uëÖu'ZÃ]Ù­=z×\3ÑÜMHhaÖ¯"%zô¨"¨kxÔ¬SuÒ˜œŒ{¹&'$0+pi{¹> \gk=/'èRö>¯ÿùغu«YËžçZ9,RÓ:;/¯˸{-Sâ+®sÎí‰2:Ý#v¤*üT*š…'}ÿüç‹Ä÷.ôîÕ‡ïx`ûý÷ßã­b#E´ñ"‡é¬¡Tïi }†ŒB#¡¤´ˆ˜ÈÎ+#&Öèðóˆø`MXÄl¦ò¦ˆÊÄ­ÝSi+ó÷K›œ¡5àµwß}wßu—! ^ßñ0Bãˆ3([µ›4 „ÃîžòÖÞó1Šó©éÆÆ”ƒùðBü}ãlƒî¼ã.<ù䓸å–[Œ×Z#ãHü@IDAT6k /Ð:Ä "<§²N¡°€K[þø»Ð´U"Áz1RCzF!šs}вY–´‚˜+c.c9È$S^«CFòe硌r à3²¿BIE&ÅXŽ˜FmÓ8š$Ö·¬¯ˆj£ûñZ¹äD†ÀÃgI¼&myJ¼A˜vÝ‘d¶u9ºÇ÷B÷æ– Nè[º÷-3®è:‘$Š…ÆvžØ†=§vÒïÁ\Ññš:ûQàX« ÓrTb„‰Ô”ß`ao“kí/¢—]6†âÞd?á÷ê¸ß–‘i=!Üm/g‡‘½—Î8õÈW vqÄÖxµcÎÎ-ûŒ9OÍ1öWr¨¢ –hâ„k90 Y\Ò¾‘»F*Ë!ò|f’!D7¨Ý ˆMäwª™®W“1&Ü‘'öçμž§tôI´F9ÚPËàÈÑcÈ¥F@Ë–Í‘~"“¾v¡‰ -9'TzthÈ*Ëðs('%®Êe‹ ÉÏÔ‹Y/9'Ü›²×8LÑÄM¿·Ýv;âhžP,€,™ds^ßóp`ª1]hÒ¤ #Kà ÐÔ^¨àäÊ ˜Ž§ÉÖ$#Œ ëØ0nv˜ºH[«ÀL(FÂÖ‹HŠgi6a‡·ÞzÛ˜1ÔÇÛñÿGßý,B0ìvC˹åïaþÛµ>ëMÃ2H]µÛÓSºô;ý‘r®ÁÍWw9ýzµo^.Øç. ÔX¨ŠÐaÌ4t°¿ÆÂ/FŽ'VD×Bhdá«Ï²‘0ì¼îÜéGœÝ– ¯ãF(aŸ«A«Û”ÎL[&¶½1ì]½[¨’?J ›­P«Šú÷*Ä™mèn>7ßl'Úã/‚;—©öÅÊw‘O+ì­yÿ8±oÖß—¢pñ´ivtĹC?Ú‚µ»ÓÀËß§ê³ý*ÆŽÃM·]‰$’WIã¦cò[±hË'Ø[2]HvS§¨ñüÓU}éêßLæ»çáèñüÊ2Ö_§|ñáZÆï‹Ó.F“Tš!ÛûMÌÝtÙÅ>#¯úë¤Ü¬ðýÚÛN% oȰµB¸i‚Mu²Ì;¼h2ØÌi«¿NGç¡=õKæG޾‡äÃ@A„ÁóÏ?o@›îµlÙýúõæM›Ìš»>Ò`9ÎIûWë,{óIétïÞƒ6ýÍôÑ„aÃ.$P^JS[ý&Y»ÿ2•К^Ú¬ò fʵ¿@§”Se˜© ˵¸›àB«÷Àu¶‹(ÒöÃ.Šwßy›öí'MZ*‹Ö©28qâ×.bÜTãõ¦M›b0 ’ ‚acZùÖ‹Ej[g¯]û™!DD¶ôíÓr 0Ôdŵ¾®‰LÈËË3¿ÂÂ"„‰è{î¹øŠþ§º5ëpé%—E|T[7›,»—šÎ);¿äÎv1Ášü¨QFÜ©.?•‚&=S«YZÚÉ-G^Nå šL€õÕcóïüÝ‹Xˆ±™º!<äà]'ºUp#b¦&¼¦Öˆ‹‹Ã¿6>Œ¨Dù™91I““‡‹0rôO™"W d/Šó½(Ê/GT³Pj^0oW!æ}öÆ^peAà.R¨!x­0›õV»öPeÅå(Ìã ”ýæ`Z&~h²²ò1w^:Â=MpðP ó3Ñ*¡1U&ãØÑ š|ÿ†Ü$¦êÂk–ûšeäeÙ…iOÃÑìuTRÏe+A³èΈŽH@aé b?OòȤ¿®A ö­`»•š êFõ’;eNІÏi÷‹`api×ËÍg½Ø¤ÁeÝ®¬¼¦" öæî6cLêêGò;PßX« ÓVpƒ~òutÀÍeS 桞.}%+ßêk†2%!I@”…%…FæÕqMcÍcœ ðas"³á&=s!PgŽbªBÄt15Ñÿï)N^ÛÍä"Àr>Å ×^cìcd7cгº<±&€ Æ¡Â*JÈzY›#¹Ó^Áíæ€VÇ6â$¤s Äܹ4)q¢’cSQ2ll3«qUI‘ìI÷‡ØI‹KŠè µ9½£¦c÷î½èÞ­ ;/ãˆä`‡º†ÒÓBS ¢ n)BBƒU&2©HNN6ƒNYütìXD7Š$ÂŽÄïræ({ŽýëŸt°òÅ ›2‘ à}ÉÆt4æ£cfÄ)O;°Kj[¤fYTÊSåRµ{²·ßS Ý5×\{ÚC~ø¡aMNœÂò¹÷á²Ûæ›ôܽiòð·Çñ³A‰•éüüU„þhW©íâFGÚ¾r‹ _ï/D—•~»45×I( p—A£œŠµ†3êT|_§ ¤gY+D$¢+­ÀR6mFIƒ3[ûÌ:™ÝQô%A ¿üíäj{¯±NîŽ^* ÞG"¦‹‘kžrÿZµƒÎ¨“?óÕÞ{ßÿ+’Ù]Gß0».Bim2×kM½u*À®u_âLéë·p‹ X·ù3|ÉvÉÜ·ë€.¬4]©3{ç¦#GgH@š ݺu;ã^à™,ˆ4³ÄºBfæI¬_¿<ðª|WcŸ«=8pà@è¯z˜5ëÓÄ:TÞÒú+W®Ë S8øÉl¸iå[!oè\W®³©\VQ‚-_o5& zRëØ®]»áš/'%pÍ~8 _~ɘ_è¾4!6~ù%DönóY1›qµ¬³¿Ý½Û`Šƒ«t\j3’ÇÂ3U9·×ÔÒ¶±ˆü¶ 0o±\»ùüOÆŒ> ‹3jb9~ Õ¶Þ2<&á5¾ë>Óét|.Zu"9pŠ‹}© ®úâG $á¿Èñ·‘QAïý¾ÌåXq/lŒîC¬#Ó ëw3â“UXJà%Ò@ K˜Âh/3ŽÊ Lc:ÎdªËHÅP(q塱+Ò”Qdϱ]ùøù÷¡o·8râZĶATAwthÓË6¼‰ˆ.hÜ< nªøƒ‹& Ák’QY)ëLÀ›Ÿ[L¼BðK€È¢BRn£.ìˆØÆØ·‡¾¸ö•„2jcI/&±Rˆ–M!”Ú²L>úð…GÔ‰×Ø¬5ÊHXÌÍ:§çíD¶o5 rÙ*Ä€¡±h×E%ÙȤ6¤óÅ¥y,kžîÌ)Lê¡Ì½ù¥hÓÈà5·N4í[Õ*ØÇÕZµÉHŽòiºâ£ìÜÌߌ0öïzÇãÔˆiÙGËHvxKE9±œV'2}O„Eµè:Ç-ï‹àѵšpmcÍ#G!‰%²p2¶Ê÷€Ž:¤7Ôgç<[I4¢s@5ÐÇ´xõÕWÍéö€ ˜ö>rÄØYI{àz–#(€¯>BRá7ߪU«Ð¢E vt2l ]d9Ä\ÉgƒL)dnaNq`:^’'†5âDà"‰â£,Í$Áüj F6’Ä"##è± Ê9£â‰QnX(Æ{wôÂøgŽaÊã‹që¡XòÐõ¸~ðËÈÜ…ƒš cåÃh?êAz\½]r3Ú•Ħ£-9´ÊÓ°ä὘òÊ»8¿e>^ä׸å’0$ç%ô¦ãÚ‚¯ôe\Ôùe„¸š‘øûmÿkf]ŒŒ…?#Ö­¿<å?ÆàëĈûàÓËZâPòg•*í1/@oü ;©fvå¤ Þ´“)gy½³f‡WõÜ´#¦h¡yÛñÆSË±Šžkã’úâ*²ê“,`l—=ûó7ñrYotís>ú÷heú}Oïö‚}콿BOß’À[•Ÿ ²²ÈÜå`ïú÷±pm6:ý¹Ù­­ŒàÿùíF~ê‰^mkѨö€Ô¶ŸÐOŠ©—uÄžßÄ mƒPY ÷ÏÀâÔš8ms6ãµ ñll+Ì×ÁŽD´Š„s:MÒ¹3²³ç5Ås¿»˜õ-ÆÑâl´ S;dîÙIo·T÷ö_ÈøâE–ƒdIÂÜ0þ^ù–‰ýˆëPÃ^ºŠõÞ¸uW¶{ Š#š£ï€îmÍi*³n³ç³ní7L‚ÜMK±äãxàðäÍY.Õí^:÷Qõ'b\/Ö½±Kæ=ˆœ[èw¢m5z~|ikc ’0|t_¤|žŒ-KæâÅ„?aZ¿x§ïÄî’DŒ¿æ>'¿ÆÂe[0wÁ47äFÝuªÊ­žOõÕ)¢ ®ÝsW,Á½³öbxÛ<$oÀØc„zu2‘ÎN{‹x˜óAÉ«é¸ú‚ƘµÐ.Aà{=²©¯N^jÏ$¯Â!ž³Fî1<á$>žÿɼ8$ÚŽ¥«ŽbÚùü혯óÙ‘€#`$ çÖ r¶­ ;ØŸ¥&®Ýq;¾§ú»‡ÖµßÐÇW¿~}«ß®÷ûž={”èí>€pЦž±ÞµÆ³VÎfÍl|p=RÆï\g ®Ù°‘~ ÖcÛvKÛA™*½_Üx#ºvëÄõ± ]{öà.èxxö,ãß@¦ sŸû3>¤ã¶!\€áÇÓö½Q­XDyÕ¶Î>Åu—Ê×6©Ùõvs3RÚÑÂ0×Nºÿ\ð¢%@,Ò¶m[S–Sôê/R$‹h÷œ{Üᧉ´Ð*¥ Ðhvû‰ ÌF e¤WäE« nò{ ÿöq [5•÷3U¶sT|§V‘TX†ÜÆí©PWX!*°ËùîÊÌ¢D…(iª×óh;â!–U‰·‹ˆµ¨ ¯Éo€´‘u2‚´ Dä/F|IgÜ8îV¥€Ö-ÚâŇªÖCûŽÂï^‹X:G$6Ïk‡Ö8¢' ”‘äZ^“Œ¤Þ®¿mÛŽ €ÝÆ]PHS¾lìg7ŸpÍ8ìÝs-ßNðA@[†¢<®­ Ý4a8…ðî>J=€w¿…«¯¸’ ÚÂPµã5(ÒA”‘Ì[\™ù©8’¿‘mPìA×–#QBG‹ÇO}k°š¸MN XÆ2~f/1ÚáÚî†Ðqc#ú°^cuLŸ ìGL€’ ¢”o¿ddáÚšw€¼¤n£þbšÄÿÂ1ÄÍñ`ÇZ]˜–§WbÁ?þNY\I’æTý³‡ -2¸â jE°âþÚÆšÇ§Â²£©£p3žŽ¥ ÃÎÊê8Ö¼Áû_oÞ‚ÖTñ`Õ@Ø–Ý¿œj²©D"è4€dzHrÃõìTÍm)ÍÓè, X56žT2<.yÁB¯–'Žg¢EbK‚nu Ö‡lœ‚8&YƒÄF‘ 'TNšQÕé$'ŽÎ]˜—üð^…êÅÑ&þN•QGŸ#Çò`)F²‚ˆ,©GÅ‰Ž»Óœ Å`HCešt­¥ò¡dÄJ©>¾¬ŽEDTš’ýdzê-ÏÀÉ&½˜vý1rXo`Xei8x2î7¦åŽÂM7ßXY„.—ÏÄóvÈ<~Ü|Ü´x’Œ¥SHÚ-^îFùì'1Äïí­i—Îè|*k“—™¿Icñ§™ã*Á7íS³`O}'Ƶ'ŒÝëÏÁ“ÿKÞøt6h燑C»Ú_޽ضž¸ç –]tÀš?`í‚ꈘ=sŠÙmØoºþã,H±žð[cê±÷`ʨ¼Ø9ÛnŲu›‘KÒ  ²&aêƒwa`+‘—£@.Ù± { .BÓêb¥KÂÕëV.>‡š`÷ÁYÓ¬Ýà~ÑÞ_ý Öš÷þ .MX†%ô^{åÕãüP>xƒu#rÏÝŒJ¤wODÜMùw°-wúäXu‹è;3§X»$½{$â)ª­'¿½—÷ ‘PU³ K§~ºër«/éŠûïM”å¤~|¦Ç$<ý˜ÿ\ì_v;Ör×F¼»äeU=ãï|c*ÍÙñkoÂýïàÁ¹ª“ú¿ÈÒäðßõö_©e‰3Ŷ´É¿ê¢/ìÁÃΫ$tÐör ökˆØy£ž8ºw¶ìÄÇÉ{qó¨.ÜšOÅ»/U•ÝN§¾:Ùñô^±ò¨˜–¸¡øí8¦Yê®SAÚ.£i‘mËÄz°q#‹X«”P=uªÌ®žÁÔ©ë\„Ë* &Y9©ïŸßC¦ v¨»NŠuvÚ;Ã"š(ÓßOá\èõO@žj¤c²©³Nnžå>sÓß…;n} ÓgÍDâæp÷’v˜yWÕük×Þyw$àH aÐ:ZD€lk\B y‚¾ËÇ–‚â×d.;yòum»N*Ð)[õ=£ô´Ž”#´µk×áòË/7jüv>f*°ª 1Ù p½kÌ—¹š6êë\ÏkNëì“´“_´èÕJ_v2‹hÙ²×ÚR/£À´¡ópùmÐŒ ÂÚå×÷óûS³0ŠƒÓMX$Ä`>TÃ:[€L¡QUÌíÐøOyÿmþóL·1Í"ÏÀ"áÂ*–Ò8 ‹xð–±0ÄLÆZ÷‹¨|\éS6üÀ`×G0Jì@{ªd‘äöeó;×ñ!áÜ]ç©sGßæõ·L+š—™ñKE!wæw§Â݈äœRž.ÖQ¿s+17:Ín®…Ed¯_^®Ñq òë&•ú´¯ò1ï®G˜ª!¤ð·6*Ž& lG7ÿô¼ð´p‹<^£ŒÄhhóôH:ONâ{ow:…• £"×ïÅÖ퇑y2Ÿ~%\ÈâÉ I 1è×+›xüçΔãã¦ãÒeËi6P‚ëv]xM𒠌䣬¸™…ûÑØœl’0¬K»¦ýXÿ0?¹•æ„™Ô §ÃE>kNMÀº–ËÍÈEÒHÔæ˜Fâµf¢ êÀ~¤z‘µÉ¨YdN¥s™$œ×r®=gJ¥ôoýàæó=Æ£­þoˆ7!>)Ò‡Ô3Öê´ê¤¼¢AŽW­ü㯺’ NÒ‰…°qmcM]’*úÐË¥ Ýrµ ”BÁ°®Ž3†‘`Y &¸¬aáëÒ<Ð_$ˆÈI þD&h¸vÒµæyáhÔk8È4Q È—jбaÜêpl@n µuúN:™ªLÄjjàk`xÙ¹}¼/ÓRk§iBœ’´äù±-ý˜‚ñªŠ(¿ÊK½AÇ[è}õª^LPZ|NeÖ$)u©B²¦RÒn?ïò_9Šh«”ÇSrssKǹtĘ_ƒ¼Ü|”•°ã1®:®Ç ÊÉ˲œ<̓Á²ˆy¢<Ë™_p¤ê[óS4÷ôÞP”sÒDÿåÄ~UE´ÇqÑ(KMC^ñ^|˜œ‡Ðs¦a”Ÿ0¨Šh}Ê=ø5^;“Ú##ÑyêË}ýÀªzLë»Ë=Ý?Á¨)S•‘4àë-ãD÷›¨N´ä7}6’N Wî«LCéq*æ÷’€”nÛ9zŽ¿3§ÃQWã‘'M‡¥+÷øcÉø.Üu×,þp?‰©CÙÒ–aÁòTÿ}.Øç-æç "²NMMÅÚeiÿýàÎd°c™…IO>GÁcæô‰ÜßÎÆü?Üuö}´’Cؘ ¸°»ÿB=oÅG±]€¥ybø§¹q“ØJ€âì“HÚ²'Ì"ä–[Hè™â*¹›q½q®! ô- Ýz³®Á†ü#ØÆßB$µ«$Sôh`9êNªÜßVe•P™gsb7ÓŒè;È" Lnô>˜Ÿ²±‡$“œ÷( ¾(@^~µuäÐ>ÎÜ­ù%¡C·ª²Æt€8Æã¸Õ"^Þ¼/¿ðfÍš…¯üIpî® qÃ0ì €·ëhè×±<ƒ?=x'F÷ŒÃ–…³ñÔ§i&MíÏ]ÁÏ  -!uïN`ý‹NíÃÞÔc†°°2¢/EõÆ“ÌçÉÇgã¦ñØgãîYKÒ`JõÄiuñõÛ™¼Áâ9V¿¹û lª$½¬’èµ®:UÅ¢Væ¶·qû£‹QLp;sÖõÕÌ ê®SD³–†ü‹Ò ø;£À_%+ÔS';Z}ïõÕÉ—µÌ渧“È;gNÇ€¥¸³oy©ÚM2¡î:­öÞõöߌ9S硽Èë¤bïÎ}Ш8¼{+Ò2ªÔKëkïúêT’u iǨµ·æ3În͸ v Ÿ¯Ü´ŠÄ1~ÎÈånš 8øÎHLL4Ïj‡¿®`ßô{P[ü´‡ÿÝïn5kÿüe8P[Ts=‡¶õ/¾¸€$ÃnÎû7cÐ ‹Œ·Ò:U6ûÒкÕ^gk•îÖ¶³ÒþuvL£(L¸újžÒpÎi§2ˆ¯ÖÙ¡o²qO£i†ÖÙvãÜ~Þyý0‰¶Øã›M·Ú°H]ël9:TÈ;•‹Rí¸±ShÝÏå9wÒËp-—«ŽE ò,bFÏŸExfŸ4Š à¨‹è¨yX³ÖG&×d$ÌwWɦüQ|ŸÌñ_X¸´Vå}’>:Q/ÞsÐ ®HjÆRœÂ[’‘Ö&¶:º°:©¯ñ‘P" äÔO˜¤Qy·A†˜9ø'-å\ž.å¡Iˆ[~ˆùŒ6 Ë ¼W]Fuâ5ÊHXÉEÂÅËøÄ𠜒G'âÄ^<&á¯/­Ãž½ÇÝ8 ±Ñ¸j\?\wý…èܵI¤hjrúqR*ú>¼·ôýúñZ 2r³s‹ÓgA±01]hS4n‡™[‘™w˜¼e®6¤l½ìÒâ •:}„B~!%ƒHOD^#~«Þ¼$”@m2ê݃©[ᢶ£p]Ÿ±æà*û¢Ã¢qÇ ûé3_Û`®·îd0­ÝêkõaÚ‰&pC¿©!æDÎÕõ'?#ãyܢȓ긿¶±FGˆ–µã/"@,—ñúÏwŽ7ùýÁ S¦àš"|³ãªäó²RàüµìÝìtœúÇgä/è$ºq‹°u§•ϸyôŠªË黆…×]C•û9ÈÌñ ¥à8v˜ß¹b,žûÄiÏ,™?G§?Ži½ý{ùìOáî$õ…Û¦gâîyÉ\„sQß¼j¯¿àÐfªnÇa\§&§¥U×ó“Nf8 Ràgûr=÷cYHì[!2±Ò¤Ãº`v{Ò›¼!qWu\+xBØ,âžVýÝãúþ@¢ødé‘þÄÝ´s *!ÇWk(ÑNÁA×J°î…û°pK1­"Fcì„aØüâBÂàBÝmÈ>ÀäðV]põôiØ}ë¤lÙ’QI(Ï>ne@3‘9®ªÊlëÌÙú¦?9½mÿAô%7;QáÍÑN8oÊÜ‹ùk¾Âþ‚Ë«Ò`uljǸ»žÃù$/R3òФÃ9ˆK} ³Ò]%U¡ö:Ùm–»‹ϯ@\ßÉxàæ‹kÔþàä_ëøpÇ4©aLzq”¤&\IˆèðuשªÔuª»N‡¿XIÚŠN"o½]b\è2ëœóúSX¼_ìÏC‡õùâ³ÒÞÀ¶Ï)†”æãѪj•²l͘Žç§õ®¼X—lê«SŸmô c©T˜ôæ>8ÛŸî"ÌæàH{'͵G*³u>8p$„®÷íÛgŽ&l×®q¨Çl}–ÓÀ¯é¤OAñƒ 2wøwjõœL‡ï½÷^³Ö®éÙ—^z Êû²Ë.;Í,!0®ÖõÜZ3ªß>cÇÆu5Êi®  ¤Œ_ø3ÉS.½ôŒ= ›ÖmÀ_hn!Óý-ü×BL¹~ Z%¶ÁÑcðòÂW+Mke¾pë­¿Å€Átȵ·þi··6,R×:[Ž# HÙ¿$„|ð'CÎYDi‡qgþ__ÀŸ~ø4,²wß~CtèymBÙë{a-Šx  h;¦§ÍAþ~…¼h“ÒZ× QðD:“C ט‘¬€Ð("iÂÓ2þ ßñ¥\Ëåì§éo&Õçyâ‰<¸Â# O\ ažfS–}#Œ²–ÜîP’VѬ‡Õ®Ò§QÊiýÈC²Œ"â_Í2œp!¾ÎÜ@ l ª4,ÙùÖXã1íùiô÷ä•YØ©wž¤A9å—nŒñ'QÇX« ӊЛ<ég˜2åIjáL©1¥r«O £ “ª&ÄÃåì#ò›gðwî¯m¬yŒöoÂqœ~ Ôý9j¥QPF¦QÚ5^®È˘ÉÏ~6¯½ú2¶“8cÿè£~<Ó¦M5%Ö1RÙw3¾&·Õì슄 æ%9 9Çœù„2¿ šTðÝ+Bù–ÐÙT©ªy”ÓÑqbˆäá %e/~¢ Ù¦Ä4‰F"ÕžJXVõ÷ ²gÚì—z‰Ì+˜$=?ë>'1ƒòrªàc¾ B‡3_ ‰·^_ŒÞ=ÎAÓfÍ9Q0¾Î,¯4UŽIÇÃ>lT˜ýÓŸÆIZ2b1jS/©ìð@xHù²>•A…©%˜Få½@Ÿ5EµãÕt¯¶k[¶£Vn:ŒçùÁª/ ß¾_€Ð‹¸sL›ôˆGã½ÏVaKÆÑ<°œÅø|Ñ#¤ÑíØ³òi´c&»÷úÌ”çw õ–Çd 'Ìx¦ßŒ;ÂÏÅó¯¯Á\’ª»:_묱ŒD°¸ [’| ¢)Ú2Í´<ª¶ñ« @÷HF¬\æikz×.Ö×jtMw”_×1š*’ä·€`w$^*ñkJtnŠpªqLÂoÀ,sD¡é±$ êîN•é6¸ ù©"Fu½’G]^™ŽÇ“‹wï› ý§â‘ëú#ŠÚTu†j})0nQëSÏã5Çq£U—ÞüSjYxù ¢C À`{¼føÙ_'Ófù»ðÜÜÙUFàþÚƒÀg?Ûu gÿb?JÞðr'õ°´jÊáËŒÜy:Öpl©’©¹NV~ûšÐêjüÖí3_ꔟc¹q¬âNÂqnÏ–, ÆD=Á_§³ÓÞ1¸ò‘'iLdöéÂÔ˜šsnz“ûT¢v û½ºlê«SIÔ¶šÄ>pËèMö\Ÿ°Óg®ÂÏ>T£o;çÝ‘€#à$ MƒîÝ»µ|™è”9=´=ü§¤¤à«¯¾2àZkq{3)¸Ôy2*M‰µF¬m­©ôòó ̱‹~ Ó׳ڭ'ì0kZ­»+¨)ìãN¯Ô¹µý¸Îö‘œ§guô¥¿“ؘW©Žžip„Öê³þ£9¢ü$¯)o»Nq±qèÓ´—&¯ø.{­\ ©kÝ­kwC´¬þüs–á|ƒE´J7&øj¹(3ç@,òÙêÏ iÒ­{bÖ1‹xX&³«,,B\#P-{w"; SðÇVk~ß·6â(:¬#H<0ψfñ8ðÍIlqõ¦63×cÚ8$t—F"|U2~Csê®2I>”3ŸDjj x L ô‡È ChßSˆL¢m,R¯©­D„–Ó˜Îáµ±ªÊJ£@š2 Å%ÅÔàÜŠÄŽtöÇ´Ã+b,Ÿ汆á5ÉH‰¨[ÄSC}hh.ºÑ4cgI,!Ë©ÎÜÈàš;7§‹__‹7–­G„›š\H{踱C!5%"Ãxl»´[TçZðšúaM2ò„h½ÊßE>ì!aC@œš¾Ý˜xh""®2/Þpb¶–—ØN}NOÑ$>’Ž4S<ì’¯ÌÐ}6€ÝÊ™¦Á´Ô.© ÓF“ŒÛnÞM}k¯¹ù,¢àÍ]/W^Oä1íb:²ŒV?ò Ö7ÖjôÚe/væf½:¡ÈÇ€Ýä`QÍB|¬ yŸˆ3Ö4NNÇýµ5ó±—“1¤*Œ—€8 xÕ }2‰ò^qY‰a1_yí5lݲGF#yu2Óöâ׿üµ¢ô$8ò8 ˆl`¡Má™ ;g¹˜2µÉ±"xÐ ™(^ä Òè0j¬³i(Fö2=U¾”#)œÈ}÷¾=ÈÎÉBU+ò‹òƒ6<´„,Aë¡ÉÌÅ37¼„±Gb#s±3°A¸iꓚƒÈþ¹9«´å$«ão¶ë>rÿî'cs-†^8Œš tÙW^ŠÕ'ãU:_“¶„ãƽà‚!œ4È–©¾ddëå¡ü4aëXGM2ÊCöIšk|ÒNЈ¨%Ø?ö{-ј¦&—úƒª­x®Äþ¸¾wî›>sÛ-Àøxút<Æ#In¿} Mš`Ü=·ðD‚Ç0ú’ßàíw +ŽbÉÂ/1vöïIÐ4GEñJ¼þÞF\Òx úÿf ûN’-‡p^]'(³8·ªÜõ—‡¤ÀîŸÿÍ|ÜpÍÄîA&åB@ FcÀȾ(¹í6šŒÀø>mÐ.± ÒÖ½Œ¬Å/o½IîVô~Ÿ°Ë–ÌÃÒ„éÙ Xù¯¹ƒL¾¨£¿t¹ØþÅA4ëÜQ–.xÍh"ôîJ€àá4¹±­.5‰ã7yØ„ÞVõ{¤öÅrì í„>²Ó¿ÁK/&›ë-ãü€Þ|ËÂ&:HLÛ¯æX§Ú‹; ÇÎMÜ~1†'.„bÍK‹±“ÑÆóáIÃ1±ó»X¼vž ›LGiQž »S€‘ãN!È^ÿ÷B#üâŠþÈß¹ÔøAˆp™üŠ#[–½‹/B»có a šâqÑ•=‘¼h ž¸}&:w''äÎ(ëJ ¯Q\¨ÄaEò*¼¿®.ê’€VÍãqñå±v±êÖ¿×ÙÛ–b>e„¤ñ8—>'\èŽþ$Ö®˜‡×[Þ„‹:D`ë{óV@Ï1Ô*G•:=y^n9 —ölŒ ‹^‚J;úŠÁÜí>j1‡¿Âú]MPºíËœ#"‡²Jx‚‚ `j µ·a I”%[=¸`Pgĺs°ú•‘Æd:ëk•ÓMÍ€¨Ó…Å)¡Ñ$ ü‰ñëíK<éâõ7wС%O>hŽƒ›Þ5ZˆðìTœcX·!I=;"><‡ccŽQƒ:u\¥iA½uby÷~üŽ©'JNaÝò¥–¯“²BÄv¸£úµ2‚¬·N4$yÕ$/X‹¿½Ý S/iƒí‹ÿvaÓfF:ÁÔ‰ñ½dô·íËFQª¥¼mãtÉA“N}І,S0ujÖ¡- ‚MxõÅ÷qóõ<’sÃòùÁÐL㟡Þ:¥ö®>ÿˆ¹T <1œüý&ÙS'䤩N¦u é _r²ë¶µ6F΋#G ’ÀE]dÆíŽ;Ì :%¡z°Áõòåˉ°}ÔyõxÕ¿oÞ¼™Ú ½ 8¯~Oß•î@j ¯[·]º¦øŒhZ§Rש ùçâÒM`¸‚b:½œ+ÜÓÖÙ!f‡‘¿ÑŸ†¬SÜ a°×©zÏÈ8Q™‡}ý$~þÙ*\ò“Ÿ˜~!šú°Hmëìô‡°fíç8•u Ÿ~ü FÍu¾…E¤ñ à,¬bðë!,²rå*úaÈ2æÉƒÎØ ,b eAiø0dìx&jà”Á0r¶ž÷ÙF¬ÝÀ0@Ô›ÖŒâ}XBvf5í¡²ß´Ji—׸ Z¶I4,™ž‘1…uáðãü£<Äà°’æ–éÈ;©‚lXÄ“PÆÈàºë®Ã Nlòuà)òàï‘GêýÕL(………T7Žàé ÍFš„ œtÏ'I(åEiŒÈÜ+„̤Ì0\"C”Ë®èáÄQi )øßÌçš^ìx5Ý ¼­nd”æ¸wí&DNŸ„Û.ëÛüïå+̾¢ù–8æQl{3}®yãûÏ7פ]0ö1ºÿºæ¿0þ¡É¸wü`Ö©ž}e6Mù5~3ü ôÍÞB7v !ÕþOûŠVç’Üj’åKýå‘Zü3NåŸõŒ§÷íX9o +tùéûg¬OÜû82²ïAññ¤ï¥!6«Uõ(Œûý=Èxä ,›ÿ(–™G#0ú¦Ù¸Ø¶í/8ˆ8”ã‚yÀÄ;éa¿¹?§3ßþE=¯û2vC˃‰}+có¡ß/îÅðãOÃ,Á|&~îxLn²Îí§Ã1ê®?/ðÈÅU‹ðÄ*ÿu:t9ÎŽÃwzØ;¶OÌ&éÃÑs,þ‹Gٙи#ÆMÂö“ʤópŒî² +¾ Óæ­.þî }¯}L[±L‚¹©w¢å7ó°p»í'ÀŽYó{Û!c¼É éÄCñøóS4êVL/œyËãÑM–ôÌQ·ÚG8ÆcÊ#÷ ôÙ¹|Žþ¤u|â¯.¶©[³X"pP©ã†w,š‡µþk&Þƒ«Í:=ã퉹Ëv⥹Üʎ닱££±lOPøÃ›,×U0ƒ5xí¨« Ëi[·êƒü³KAîcÀDLžTu¡Ú'΀g„zû»Ôáµ ™•§OLŸq]•‡`â>[ºèC¢YéD`øbÒÀª¾ß :q.X²¤²S"nxŸJÒ Þ:±Íþӟ¼óñ‡V‰Lžéo3~ªN$¬ÃüùÉV|M_»óØ †ßù$&ñ,Æ`ꤲÜt¸óW,Ãã°fvŽé{0Ê?wS§ÊBø?|§ö®žHåwýÔûC² ¦NGö4l4;öï;Œ„!WWþŠØY9ïŽ |w ˆèq mV;F6´=—?0ù0I‚vEh“JÇlË·˜¼ý×È·nÝŠI“&™hZ+~ÎÝ÷5kÖò¸ô6¸š¾”‡Ž]\·î‚ùŒJóˆÀtµNÕ:TpÆÍÚ2­qù«\®¯O_gkd׺۶m«,ÍxªY}- ´ÛAš ±q±È¢9¢Êºu¿tô¥L“kgÝG¦¢>,RÓ:;,< 㯷Þ~‹Îà>2¨aä%—,òÔSO³ìd,{QY‘Á"+““±‚ÚÒ²ù¾’Ï…P#Ëô ±ˆ0‹lÁ¥]Íýk®ÿyÜ]5åé¾÷8JS›·eý„Q ÈÄ!!Äg`>/\B #\"ù{HøèMZÚµáµk&^§Ÿ}wÝq‡Ñ,xçÛyhÞ9{ Váë—£Œ;ùS‰bņ¢ÓÜ|+h‚m¦â’>WbΜ§qí„«O‰†âµSé9ˆóá’ÖyèH7çû2K‘•]ˆS¥üAb="XpõùHhƒËžËoT–Ë-ÀþyØs,¦æ2U°úðZuɉ¾‡GDodÓWƒŽ¬à†…Á–ÌG³”í–„øÐ¶$xR7¢‡#"R> 謒mˆ×*¤!Á1ú]0m¯øóжQglÊZƒ=9»U|’éT…Á-/B»ÆíM«êGlŸò3ûQ°˜V8óÓO>áøÖF¦A¦&C-kˆV}Ò€+£s@9Bgüˆ´:÷Ÿ1Ö¦öùB)îä“ö¡¶€˜vÒuzC7ê‰ToØ¡#Ùù¿ó6¾úòK :£x¾©«˜¥`§IÊÙEGqHíÂŒCM@"8<$Œš‡ªaf%Æ%U%[i>HMB–Ýœ×ü,exM„/OŸÇŽŸ@>'¢èøXzálÃRÂ|˜7ÿ*(´ vL£VÄK&-VM_9;:£<Áú¨o*¢|Ez7±x¼âÛ‹±yËfCJˆ™ÔÄ&fV(Œ^¿þýqÍ„«LÑK©9P“Œ˜¨ªÀIå[ÅNA¶ñÛm»h¿5„ ä%U+ÈöhÁ‚â¶ÛfØ—j}–àiêÔÍ„Wk¤:nçåqS¡M@ß$g†âõ“TÀ [6T1:̾Z,JH|ÕtOóCÕ¾4<5ýQ¤P+à™™c‚%t*Êy‡DÜiåãx’[9¦Ún¼a V<œ•sD Ÿµ¯Öüîåî‚ì»ÁzçæÀÅ_ª=cɪª®¹<Å{z¹ª%__zKr‘c*Âr6ŠGüwêLõ÷%»^FýmÝ<>¦ZIùÕ_÷ºâ˜òÒÁ] §Q-ZYæÕRúÑêäÏ·$+¹êj5É/ˆ:U+~_ƒ®Û3Cý•©„sLŸÞœA´S¹W¿x–Ò V6uÖ©zÙœïŽ üOHàСC†8Ð)\÷M­³rž(‚aÆŒƹÝâÅ‹‘–v˜Z±rBëÙÉt(bbÉ’÷øSbNN¨ž¨~/¾Ø°=ûœƒâÜBNÄZ#3Բζ°Aö¥îÇ;#>çôÁÆaû–-xŽÇ*Šøðx<¸eútô=÷_M'æÜÙæóÅÃGàÂ!Ô&nÐZ=X,Â-Rb$*”§›n?F ”ÑÛŸ®Áãeá=ÅãyŒphb+„6kÛŽ®ÂUª‹ÈŸS6»þÂ,æ?kë™Í5ãµôŒ,¼½ø Üu÷ÝHÞöÞÚý |Ñù<™@ÎEDˆ0ÐF¬ÄKì“1ínưcñôœ§ˆi&СuÓï„×nÿã‡8§( Ó²xÊýliךŸ‹µô-PÆ]ÿòÜ<ägå!óX’ˆ.»jñ[ 5Æ ¸é|ëIrµLÈDÏ®Ix~ÞÜ ðZM2Êæ†M ËPB-UöÓžºTAGeôó ƒ¸°Ž$0"yòž‡Ç:‡‘`¡9„E xÍK™y´YN Lû§í÷W±Ñ]qmû_3`úQ°˜Öò¾K\¾ñ‹/X}öeþôË67Á|å û­6±E޹ô'6r1ªhÚq¿-#׃‰Õ jÅ 0!9cÜ{&'‰úúÌ,ø'fŒšÊ…Ó¦cKyÞhI!Ù;îàG†ö†# vób:õ0NPâD(¨:Î…³ó%9 –aYÊGÞ:=L¯\ÞKåE‘ª"ra&zÁ”½lR¤m`œ0rZC€Ò乪.²&Òª"qcÒceX1ƒ,'•ÛÖCDƒu]ŽY/Ú¶;v˜6e_#¶lmG7&K›„þçõGBBs”“§ÒºddM,Š‘‡Ñ‘Qرk7Q-LvFÕƒHƒyóžgº,t=ÁL¾·üæ;“õ$ïÜþÁ$P‚O½‹©bÿ¼½»oò*Àëwp²—¹A !bø ÀuÃäw¡v,"\ÓP8u‹¿…»I¬Ýõ1~õŠC¸ "LÅò ?1Sn ´Æ„Þ·â‚.£ñäSOaòu“Kçà߯½±! -s2qчo¡ÙÁ½ØÖ{(>9oÒBé4Ÿ›SÅ<1ŸŽO=7æÚ4áÎ ßsyj‘ôb¤žþµE$F&ẮÓèG‚'kÓÓ‚Å´ÂÔÓƒ/U#a[¾³o‰ò¸^¢å7s‰r†.)¥VF¸_)ºR¦ñYÂzžK)õ 8%©r©LȆFÎ7¤#XN>MÙhój¸{˜‚Tñõ¤´Ô/ÕèÚ-”ý¿| (}RàËÖ_™»ùœÌDTÈŠ“ˆÉ“÷©“Ã8dÈ ãÀÝ~jÊ—, *Ï´IHøH&H%H•êìAdÂÄ‘aLLd"aÙ>±ì,ÒL0%d”8“ãàg¹d8$†‰é…‡†“ R¹yõTä@D'L|5jÜßîÚÉS'.¨Ñ[­&6QcOn̤Æ`ßב–šð¿C¹ÇRq„çÓ«ËôˆkÛ­NßÞ Œò£~öfmÇk¯|Œ”£™È¡ÇÝØ6½ñ“+®Ä.´ó uÏ!”W¯K( ŽÖ];Ô¸‹ý£VÀÉÌ‘€#GŽ ü/”ÀÉ“'±páBæ2j Æ…^H`¢usUØ¿?–-ûÐh"Œ9ýûŸy•L Ö¯[‡½yÄ+Ø»Î梜뮙‰dꫳí7¬]×I`\>n~2z4m؉¸¡hlú¹®–Cvgxö°ˆà L%¶bÇ7;y¬ßqs:€6:Z¢W¯žèÛ§Ñ„Öjþ‡Â"Ë×}‡…Â[È“øO'P¸é¼ý¿Z•àRnƒEÛˆÀ]*,b¡ÊI³k(QÐì«Ôøÿì½ `•å•ÿÿÍzC6²²„ K"AÙTÀ*`¨Sh5tFðW•ªˆ- #üE‚tŠÚéLCSB§šŽ´Ô&¨l²D‘ÄBX²Ü¬ÿïyÞ{oîM‚@ ÈrHî»<ëç}ï›÷œçœóÈr ¼ŒBŸçD‹"Ÿ|NIF*hjÁO1’®‹¿Š¬=)ã¥jÃ(:¼8Q¢aR5ÂÊ¥yxI²CE©“•²Ì×eÜ%_d}1£Æ´«4ó-1 “Ïó%±4ùÔ¤”€PJ@ (% :ƒ€•[·ïİ«jª.ê=»žïä^2©çxÏ®¯®çÄr}ÈCÌ{«ó=Û›²‚ˆ÷ùží!‹È«?]Šm4G—·~ñ+q¡ÎÎ@ƒ”/µ,RÃ¥àKéÂKˆò yÈ$*å–ˆÐø2Ã¥’EŠÎ!-í/"Иq³aÎ0sü-»B?LN¦hÈ×–×þã½ã :h­ PO¡­^‚´Gl¢Ü× 2!7о–,ê©Dh¦â¨êÈQ”fqYÄÓ9ˆïSÉ œqxåå_È¥º(y-¯h?—íCLp<¢†0š]DJû‡P1E¹Ï!ÓzÑRþ›’iß?¶§òšpž2§ˆ¾W£LëuèP>Cв€6üË€_uBæ`xƒó ÆeåÄh¦Y}¼9ã/.ÞÌ×ÈD¾–•uÌ/7‰©€ÇÅ¡A"ƒÝ€|i,õŠèŒ¦E\ |¹SϼâN`Ì„¨|¥Kä‚7²¬˜¡°5*¸Í/{#…{ñÑi ³8¥°.ãnÀ±ˆF‹O Þt¼;¥NÑcP «W±ž:ø)kÃ6q,>ô]‘†¢-xÒkvFt ¤3…rµ‰,* FŒdäövb°uMJ@ (% ”€PJà#`b0fÙàAƒ¸sÙUóž­²ˆÊk!¯é}Ôþ}ć¢ýó óˆù†ëÔÊÈ:âü Á Œe5Ÿ î¢b~ÊÕ–@-{νD‘Àm*–‡@rñI¡ÐOÁ\~Dì7>?¢•Wè2 þLŒJ…Ûç?+ЦeÝ­ØG?©CÊrßÛá÷ïÃÀŠ"뛕 Xg…*¼Œ2ÊÖÙdâ!0“8N1·(hDÀqÒCÆKsYnRL€L×Da@Õ (È`;¹ùÒ,EbX*X¿&% ”€PJ@ (%p÷ÓÊò —¯÷Uõž­²å+•×T¦½42--ø)hó!%?ÍPvDQ@a›»/Ü©Ü( Ì$¾<ÔDv§²7ý—šÄ7‰•KVàMóú&ã‡@Å•b…QO…ƒ¸3H=>â ¦ÿ4ð£ðß`Ìv¨  Ä.õ‰ù†—‚ˆ¤"È‹«A3¢8 ÈÏvX'󈟌ƒ4JÔUæeÕF1 ‘&M€B* ¤u_ökK“é¸Ñ ‰¦AÖ`ýÞ¾b/Cõ‚5 Ì/ ‘zc ! ÖÁ¾w£&Ž)„¾CùÇŽ&]ÃÂL`é‰&% ”€PJ@ (%ðM˜YœØ:r,ŸK#†[ÁÆ}$ð÷Õñž­²ˆÊk!¯é}Ôþ}ä•è0cŠ ?í(7ÓáÃÂv½˜î‹)…ûF ÷Œ"`|AÄz@‚S˜XÔèžO7 nHz5ÐÀÌÔSØ3 âÍî}ÅG@p*LÀB–7/>ˆšD!À:̺¥Œ­ ÖF1 ~1ÔP‰!³þ&?­|Ä/IÖN—1‰RC\($–‚ˆöÌ+öÞ´hlð3^<'Ë=J]ÒOo¶+«<4ˆ‚Êî²~öGVi ™„•æ“ÏÈò°ÚÿúŒdm Š(äLE¥âD™Øzéw­3¿kú<Òg¶þ]»<×ô»¦ß5ý®][ß5Yó>Œ+†EsùÝšê³òöÏ^ïÙú<Òç‘>.ÝóÈ+?ïÈÒœÁ—?[@hÐñ hàCe@£Y‡”,(Pûp©&:6°[2ooV, —­(pSh›Ó÷´^ ÐÏgØ2ÈĽ;¡hnʉû,Y ëÄK> ó2Ÿo– ËÁT^X«3ÐÀáºÐD«é³+ð¥rAìDáEe‚èäGœ!|x¬}2y¨ì ž‚ç$H‡¸M°c/xQpo–`‚´ Z€í‹Š ¶+j ô(^ ÅHZ6KbPã%ÁÈZ’²)ü #š–ȶt‡edÓ¢'Ç™Q’(ø_V‚ 'â¢!M>ŽÛäâ¾Xgȶ¯¶ÔÅL戜5åMŽ_‹r‡JÓ©õ7Ëõ"+©[ÜVxÀX£˜\Ò†’¢ì­XˆRÆ´É_B®½7û$n,Æ „Ÿ²2‡¥ì±J:ªeEα󸹠üäeäà,7¦2ÒûH¿kú<âó[ŸÙ| êß5þ­”¿Î|пýüɤïG¼%øtèÐ;$ƒÖÚq–«#‰kñÕöž­²­¤%¡Êk*Óv²Lë•wð€H„fåY]@bÈ*†b±/òšDå MAãÊÀ<"¥óœä>”¨eW,äéä- Yÿ3‹ƒꙇb …D©‹Â<Ý D7Á Å"@¢P1!çD–?|TjÚ›î^´>à"&z«±>ñA—ÎHûAîfqm;Å"ÚåµJ–ˆW‡f¾\øŠ{‹Š5‚ ¡ÊÒïš>`VŸÙò—Bÿ®éß~ëíBßø²¦ïú©ïÙ*‹¨¼v­¿ùüäé¹KšøÇ_fåEEÀ‚|)”Ùg ßb1mfµ)<Ë,³Ñæ‹E€4ZbðQÉ?>ÆÌŸç¨ÕA[–OÃsžúÉcÚ.sòìÅ<ß›3òÆÊ€‚¹˜”KB³Ö%EwŠþÆäÉØˆÉ:• ¢©ûFKš§Dο£dçã0ÿ7ñ˜úc â¼XÒ‹4.Æì\Ú”ö|(ûËœ.·EaÀziÜn”ô¨â?Ñ#°=iC·½ÙgQˆ‹C#ë΋u½¸ ˆÕƒµ,; ŠQðX³P”zh=áM…‰7-Ì <&j…)5ô `…Ë(#e$÷»ÞGú]ã3AŸGúÌ–%|õïšþí×÷#}?ÒwH¾tëû‘Ê"*¯]Ó2­ïïÿëw"kRJ@ (% ”€PJ@ (% ”€¯÷㇙¹t£º£”€PJ@ (% ”€¸Žä˽ŽFÛyCMèÐy•iM—ŒÀ m@hh(­é-Ïã tù#Ç$™ ªü”Ø~MÄž?gÏž5áLý¥”€PJ@ (% ”€PJ@ (w–JÁýˆn+% ”€PJ@ (% ”€PJ€dÉMJ@ (% ”€PJ@ (% ”ÀuD ªª ÅÅÅ(//7£Gtt4‚ƒƒ=(¨ÒÀ‡î(% ”€PJ@ (% ”€¸¶ ˆÂ ??ÕÕÕ®˜'Ož„‹‹CPP €º'¸Pè†PJ@ (% ”€PJ@ (kŸ@QQ‘Q´©( äœ{R¥; ÝVJ@ (% ”€PJ@ (%p UΜ9sΖ••yœS¥ÝQJ@ (% ”€PJ@ (%pm¨««;çív»Ç9UxàÐ% ”€PJ@ (% ”€PJÀI@•Nú©”€PJ@ (% ”€PJ@ xP¥ÝQJ@ (% ”€PJ@ (% œ¾™%xÁËÇðb7šù¿±‘¿¸¡I (% ”€PJ@ (% ”€¸b\v¥—¯/‚ú÷Cô¤ñˆí‰†²J”lÙŠò½Ÿ£±ºæŠ£QJ@ (% ”€PJ@ (%p½¸¬JQtvúÍ} AâáÓ¥ šqûûßpêÝÍh¬ª¾Þ¯‰Ž_ (% ”€PJ@ (% ”ÀAà²* lÝ£ÑíÞ{6|¼üý :)À/< Ýî»5‡ ìÓ½Wí„PJ@ (% ”€PJ@ (ëÀe „èNKƒáå×VW< ãú\ï×Cǯ”€PJ@ (% ”€PJàŠ!pY•fÔ ‚Øn’ãÞç8×n=¨”€PJ@ (% ”€PJ@ \J—Ti`VHpë}ý™2Tì˶VKp;.›Õyù8{¤ ÕQÝUJ@ (% ”€PJ@ (% ¾)mý:¡'¾ÁÁèß¶èHÔWTâlþ1Ô•–Â~º§ì0ä†èBWŸÀ.hf ĺ¢bœþû¨øâËNh]«PJ@ (% ”€PJ@ (% :ƒ@§+ ¼Ðíþ{›<ñ}Q_VŽ“o½ƒãÿójŸ@ٞϻìet¿÷nØz÷BCyŠÿñ ÊvìæÊ U1&­C (% ”€PJ@ (% ”€è«4`L‚À¾}Ð÷‘A—>±÷±&èñO“iiP†ck×£¹®Þ¸(TfçÂËËÍòÖhjî„á´­b`jz&… ÉÞöl@CA>ŠV=мÍ휿‡Q¯L…?ŠqjÎ4ä|ôÕŽX´IÓá]‘ƒ¬±²Ô9’£^_»'8o›,+Q—·'V/ı-ç(ÞãqËæ¥Ž®¼ ~Ÿ·ºeHÀà¿Dd¬û÷‡G6xÛÏÃÌ#ÿÅïX÷jj6½Œ] 6ƒæ`ÄÚé¸Äí^|Oµ„PJ@ (% ”€P× NUxûú¡Kßø……ÁË×Q5Ú¢£Ð%¶—Y5¡¹ž ‚f* øyiÔž—Î7šý ¥vàÉ–… ×÷ rÝO±sqú9r]Á‡mQ Õ¾çï§7]FlÌHX‹^ž£Œ«^æm“BxMc2júî}™Ó–àâmDlðïÅ>„\P¿Ût¡S$¢kï ­«9òBsv(Ÿó^ 5†å©4ˆA`4¯-Û½à>v¨e-¤”€PJ@ (% ”€hŸ@§* šEAsC½GkgÏ¢¾¼Ü( ¤eÂÅ&cýqn}ÎÅV×ü9(Ù°A¡4t1¥9H["GÅ›½ú¬­(+rŒ›÷MýÞÔs[ft õsqZŸ®/íW]«sգǕ€PJ@ (%pá¸út±ïCæÜWÛëùüJôŒ.EþìP:(C=íä¤å¶É«,Ã~÷ŒˆÀ0™Ù³£æËÏpðW`ï;-Óm‰¯þ ‰qeØû98Ötþ7¾È¿Âí3QöÖlùõ¾Ž¡ü8îýÕØÿ¾ÿXù~Çê¸$¥ÆcÐêdÊ‘ðç$gcãÕò}ˆÖ>†7YgÆÙ{üýèuƒj¾ý¯8öNùyÇrïè5>!'‡UÄ@üø;ÑwêÊQpò޵3Ä{¼%‰µzÿxŒ{>6ó'ïFcèŽ_"´h¶L~Ѫ‹.¿cþ’Œ†7º,Ë#Æñ~ŽõBß+±sK“ºÕ>NUHõe8žú&ª©<èÒ«'êΔ£|ïç¨Ê9`zào™µ-9[÷hÄÎøÂnŠ&Æ:c"œ-8ަ³µtsè®CoDcm-ÊvîA]qiû#¸Ð£Í· «Ô‘ç·"þ©.ß_ñ (ÚëP´hö§¹²™A6!fpÎnOÅÖ´Œ]1 Èz_¤Ù´`èîæ¼÷ðá„…ˆZôn˜~+l­\#ìYï!kòB>l%Çð/¡«-GWç#zî$tquÓŽÊM¯cgZF± ·zêó¶"k“Ög쳸ãõéð¥ß{ÞÐqÌÔË_æaIJ©¡y»35å <ÏÕ€óðy?½Ûs{È^‹O‹Ç¸µS »Ð‰³€-4S×£ç¨DsÎjÀŽêíïá‹ä¶® ¶é¯áŽ5c ?ggêÙ×ãóDž3f‚cœÞÿÀŽ­E? D<¿Cf ACÖ›øØùp`EýW­G¯‰‰ðs Y˜¦£iØxÛò=y9örXžÈ®w,µ+Ü'`Pê¿£û¨8·ññÞ/`Ÿ¹õÙQg0Ý7näýá´j1‡¢2ãMìœÝV“ï(ÖòQô\½C8¹O­dÇÙ¬t|麗‡Û¹îrF®}ÁLöÍŒ'#2çÐÖ&YO½‡Þ+ã}BH͇‘ÕN8ªÒ% ”€PJàú"`¬9YÑÞ|w“»Uëæ}8“•ï‚ äß5’ï0Q«Õ *rõfò¾ S ƒ²ì_ò-6"êîÇ1tÉ< ÕQ3Wáž·6â}* 9QuQÉ1ÙÖèu~Ã9ëeb© çÄÝ93^¾‰‹§…AcÑ.äÍ™qÓr5{)æNA—®C°j Šçn¸ ð½Ö·’7²+ERƲ¡: …uÅöÝæÚ–C®ÜºÑ–@§+ ¤‰³G ŒðïKASm”z˜‚öGÏï|Þ]ºàäÛ3. ]úЧ<<Ìœ·õˆ†·¯¨QêûÈ:äF–¯Åé~q(äê ²4c§'Æ4p bŽÁ'®7…L"“çiî3ø39X,,sõ¨± –ŸyÒT Krëe¯ˆEkqóÃÔþµ“lI“pãÆbj¾¬º}E`KDŸ‰­rÛ2ñ)Ü=±ÕaîúõÃ:– }òxILVác‹CèžMÙ„%ûúÔ6~ð¾Ñ‰ˆäL¾IÖ%qìtì£yËs(-šŒèh/øÄ&ðO¨ÈHÀ-@t,;å‘l5Ã7ÛN¥JK²!|ʘ–]Ç–û·v|©‘–àŽ®qò^5ˆ»ÒÀÛ&1˜øéLÓ2ÐgX˾u\˜NrdIláå,tAŸ à¸ã5„sÌ­“_¬ôyæv)œ"¨YöøðÖYM_C¨h¿9¦¶Ya‹G6÷ ]x/ ÛÛ{©y5J(*VÆRÔ^ü¹öqY‹3´)p*Ik[ßwí´¯‡”€PJ@ (ëŠ@{ï¢:ï6¾ç:Ü{ÑKfBEÜY›* q&ež 45{úŒŠ1ù‚~ ƒ§ãˆÃÜ8]ì`Ç©‘º&A‹W?‡Ú¨Œ|8I"/8•£éJ3 ]éÆNÄUg¤aÿ 97‰«’!ó^Q‰¼êßÑ\\ ¯A7™ýÍâ±I(]½gåƒSx8%”Ž{­*5¾ÓžE´ ¢Ïz·³ÞeÎ6ùOãD e { JV¥ aÂTWluä«fÐ"‡ýbjà„âÑÙK.ÐB¹bušÒÀÛߟfðœ8ÐXTd~³Ç ùÔpÜ ˆ(çû<ò/hnbì^ÀÂ7ÞÆé¿}€†Š*£X8³mš©,HB©÷™À‰BÎ7¬+Î|²³Ó•³– aS`å,ôŸúmCþ6ø ƒžXéšuxž–æ2V¢ä·¯SÆ›=×/ÆL8CXåÞ}ˆ›ëðì…(\NMYJ:ü§ÍCÂâdZ xA„Ë`”‡Óì\ê©Þ´_Ì^‰ç¹ÂÁŒAS¬¾œ¿ÅÑ31êõ§ÄŽøðæïÉ2'¥`«4t™<8­T±¹ËW¢.:?†ÈþAÚ¿U¡îž-â(ä‹ã¨/jÕK.…}/û½h!jÙvÛgÛ>ý'aÄ¢·±s™gƒb=qøùWp¬(ƒ?ƒóÇ% ½^X‰÷x uík¿=j›¶²EaÀU"޲Þi%è¿z)â&:…dëîQîvz®zÆ¥0h.úG½Ž¼ qÁ3ˆÏlè±h-N¤‰ ? .…Ý<6¤âKþ‘óçÃ(A8¼„ÇpòصlÛy[¯¦eBîòy-g"ayÊÕš×èûµuv*úÓRźîvœyóu䦬¥×Í ø·g¬ën‹Aä àL›¡‹ÕÃVTpEŒÊóöB3(% ”€P×4ZܶyUà€1—ÌØc'!vÊD4k¦b`!o¶ÞŸ[¸0Øô(N¤<¶! f Èq"dÜ$„вñ…ûº÷6.ÂÎRñœùƒÈ±/cçÜT„ÃI“IèA3r§9jz!pà hÜøªË¤¦ z±˜•oY‹ƒ[eÎ!<{]Š0Ç,xýÞuØ>k%JY¶jK*2'<‚3ò…ìÄT²·Ð­¶ñè7Qg ‘nO³ú-mï™0%EVÛ㦺•áfÅ.žÒ˜!!;ÙÉ“q4Ë_½øcОÙÏ·7p–SáBM6]6¤ÉC'y³tÕ{¾:Ú??½ãCm>9“(Ywö6äÌš†¼Œb«ùØiâ6A%‘£¢Ê7b—C+^šö"¶}Õõ9tâôö›s;Z¿=…×òEǵ\‹=cŠrǵì2v "$/ã‡Hj<ôö,Xkâ&T}´™ËyOš –ŽMç‡ý0rûÃÎY ñå²T7­½3ƒ~*% ”€P×€D Ù± w´úéždsa`ÍÜ3Š„A?ç䄜+FÁC·àƒ~Oà¤y£øÔä>öSͰÞÏüþeìå;Gñ¬× Nt¬{;îùrLý!SC"ßýœmˆÂ@Ü,O®KAÑ>y×¢•꬙&¯üêëxG/M]è:öMn8%–So¥ººÑgÆã6ï§ÖÏÐZœÚ¶…韻Î;7nZÿº¥08K·†§€7î} 2EQ†«ßEooç´ ”¨Åñµ ðgÿ®øxíNSEè½Ïâžû»bä¯f…}û±ÑŽß]€â2¾‡{ÑêxRËu4…®_Gߨmzâ×8¼þkÊlÛpÇ–õºjì«9!'Lƒ_Ÿi29¹÷þ[ðÙ/Þ3î4¶Q3sð|6Î2ŸÈPY=lZtdýv«)kßþ&>›ù„Q8Yî6Ö»³ë^c®Ê )ØÑïä¾iÝÆÂpNÊÖs2w/Ïï}þm×»¼|"ñ½_ЖŽÏxþƒû_F¥¼«óûÔûùѦý«ù—ogtÞ‡î¡7FôÄñ¦:ßÐftŸúmsöÒ^x4ùà’‹55È]¶¾Œ†)q ÄõÀ'€ñ ¡¾ô d•IUÙ¹8óé^ ˆ§¡% ¤xöø sîRürÆ0u¯NEåÜá&À_èäd€æ+ÀkV—[UÞ0ÙZ~1`ÞóóZv)œîiÝQ³æðÆžab;pù<÷øž¾bv”¤¼èV‡<„©àC±©¢Äã¸kÇßµå±Ñlsž`«WzœÁùhF>Ý,Á¾ÕÉíFrÉBgòkqùªùÐ…bÌ–MŒ_a•ñø;”–+Ã{Îb4)ÚÐFX=°j+z½>ɸ˜˜ ü2^h7Iy»q°U!÷z[º€Ý–ñUon»’Bþ¬ ˆ=4‹W ¤ ÏE%­:©Q?¶ ½Uýkql;­?¨a÷êg¬FZepÛ•ûãU·}Ù܆|^Ë›åZr¼¢ýËKžˆ<~FLH¦iÖ¿#(¶/¿w1èâ×BJº§êMì‡ûÝVJ@ (% ®{>Ñ-î»_ÃÌ>fW8,gTü__ÃÀobÿ䟡vÑ·àÍ@䥛Ó9K;}X‘=#¥œ¤K\p“©ö즱gñ³]5w|ûg –¦ááÓ§ÐMئlœƒ}#4®˜^c£ÁxZ¶¡“8a²–ÇøŽ. æ9šâÈÿM~øO¦;€ÇÓ•@„tGJXò ŒórîšÏæü7‘óÎL·c·á†Iݹ_‹Ã?¾[Ö1çÞs á…ÿË Š\ÝâÛ®w[ûöÿÂÛüÆä)yä[½¡CF…!êÁ;QròSœJ¯Åþ{ŸÂ±Aã8¾¯£V.Ž£WÂGéËâƒ31dî„É’èTÙbÍOä”é8óû…Ø“v;"¼(ĉÕoÖÄû$ûmœÜ>†J¨ŽGñ«»1`Å NJ–à„ÃZÁË> ‰?¦»@ñVoÙvÎá6¥ÓÊÅzç®Z@Îô_"¨Ÿ¸`Ïa°v¶ËwúœÌ5nÁ)Kð%-¿ožÎwq¦ºÚJ«Þ®‰è—²]R×açSK0nÁu™%Ö¹«øw§( @K± †Þ]øE‘íúzº!4ÁËÇݧ܋ÞÿòœÞü!rÓ·„ŠgŠw;z?ôÆ8ø;N¼¹E›þP¹>êV*jQ²e+]Jàh‚(ÊÒŽ&V‚cfÕY×W}жéÀòtx‡:](hùס:›ÂŸCse•OÇ jCC&ÆÀ‡š¥Þzûƒ†ÐçÎÊëë§{Ù}¨£rƒÏ8“ºƒ±¸gslWæ•ÐŽ)ŠO™ó¹\T¢® m]BYV’W ºO ~ß>£^±\`¬çûM—œÖêóåÔóJ@ (% ”ÀuB€ï»íFD,¦{ ¸ÒBÆxÄÑM³ ßûÈÏb¾ÛäíÂá§^0ŒrA¶Dž6I&V躹Îó=¤ˆ¬±I‰Ž<ÖG}æVKHã®ÄÓ*)˜Œ&ŪóÌ ëݾeƒ+Gá˽S·Åùµ¿!€îÕ®Qãý§ ßÙC¨ ÐÐñ÷Ò=¡üî€Ó|_®¯jéyýû¬ÓN÷ƒ–C²eÏ÷´T(>iͰ&Þƒ½c~¨w±µ¥ø–g±+vOV«êÏùcÒ Ñè;m*åÈñ&`wøÃÏ ÷Ž}Ž ÊÄÑ¡u Hº‰‡ [6ûF®àçW¥º,O¦Æ’FnÕ±‘¦ÝÆÃûp­‚â”}h¤Ò@®tÕË)(™DKºc‡Œ£<ŸÞã&üâ\·RWçf{²ÌED–K,g ƒ’¶A_VB8½qjŽÅA}Y9Î+ v‡3Q¹¥³ÇŽ[ñ ¸:BÔ=c¹¢B7ZlEUîT:lrŠËBWbè~ÿDt6õå(ùðcÿãcÎÈ[_·*ÛÝ”ûci©ížk}ðت ~â ¶QèΠvgxE’<´ŽµÎ,7’{4ÏMaÀ%‹~â{sP•¹uc_B’Ì ·.ã^þknoÉG#Û%ëÛ4á¤;£ƒ– ÌhûÄ$>Å›wS7ÊU8†Fþ¡8µ·„J· ^Wki¢kKPÙÜrÜ¿_ Kx~™¼òN³7G ÿcÎI¦euɲ5Ïr–]¬4*Û”if`óÐhsædùÞ(ƒêÚÖ Ä˜ ”ΚjŒ‚†‚=ïQˆÆyÆú4ATd“×ÂÏóT«=º8ôî'ªM`P>üš Q±9·d¶( ìy9¨à½W½§Š7a…eµá^^·•€PJ@ (%ÐO‹ØörxË7MŒFÿç§ò]=¡Iñ Ü= ë×àÌÐG=3sÏš|±Á§›ç)YaÍ=É;[mÁ>÷C8²azüxÂ/,):Žç*Q´*Õ#Ï7¹S'ŠjGz?ý°îi«+ xø÷w½šñ¿øñ½í÷ÐzG @ˆW‹ÂÁät)[Zʵ^=Á^+n ÝÑx²÷3.B‰‹PvÇÓÅê`'ŸþâãZÊ_Q[^ÂHZËLÿ‡#juî¿G8Y{„±ÚÆâÌ="iÙîè5c–ܔϙºy6?41FZG“¹×*Yç9’\ïV/äã8ÉÌã¦Ì6º‚£‚a §MBפNdFYà7Ô!}Ê‹ç¨ùê8Ü)JjÍ¡|äü˜ПngÍ’‹¢,«ƒ¢Íé(ßýì§‹Ñ\ßbe å*öeQApÈ ’8ë‡4+Iàt8­ rr]y}ƒƒ÷ÄÈþÖXø†SÑÌv«†“ÿëüJm”²W¢$oº ˆ<ýç4fõí™û·m/ø~.ñç8\B…CfZKž³Zn4gž–³³å% M¢¿WòL÷Xkí:~÷&zg¤ñ¸…æaÎqTÑÜL„f£‘³ñòËœ½`¥GC½W§¡?¿\ Y‘íö5‰ùÒ=òöH¾Õ%Ü7º[apß=ð‰’¥ÝS½CäÓÿVôå‰#n'§Ýê¶w±›…®ñŽœÎÂéD,Ÿæê›XOÔ÷sœæê}fÓ`µ{öñèI’š‹i>ÅÏ0³×Þ/ºŠ™ã¸§žnã>;hWD° Ú3^ÆÇ³äzXIY„œGôS (% ”€PCÀ‹“bãÖNBãsm¡é¶•‚ž#ŽWk%DEA¥yÏŽ˜ý­IeF3`uœ)ÞT!± Ému,9Rµ" Õ\¥,ˆ>ì=¸ß\ŽœlGÞ+àcËÏ6"ñï?„ïÐữ¾¿ÌÙØÒ«ÀÇñƒÕZö=¶>B]Â{ôÅàµcß?ÿÖ:;øWˆ§å54or•û"½þ†’fKd<>Áœ«9É5ÇL ÅSø°Ç0ä4rÊË›Ë@.±Š6Ö6»ê¸b66çpšAç£Gc(cÊe®ÎuëZœÃº  ;·¢öqYŠ>u ·Ì‘+øùõ9#•î–+6·*¸)”Z©b<3ÈÞ¹,¶ä›%*mÑ·bÈ4às‡|7tV‹\µ<͸*T®{‚.OZuOX‰»_¿ Þ!mۺʎœ—Ý…ŽG\$~Á™­Ÿ¢rÿ—. jØcò$ô}ì! úN)ª¥VQ"4VU¡æà!º!|ŒÓýJ?ÙÑ’®A,וË/úGÑ4$ >]<°"îiMlÉÝi[΀ˆ>Ñô©‘Z+ö1Šçù«÷w»)¨ir¦¾«ÖÓ\ËqÃð»ÛúáéÌ÷u?›·¬C…#@žmÜS¾<ÙUeÿ”4ó€v¸  úœ‚¨iSÐS~fÍÄ Õk1öÐ/ÝVØŠ/WÈ—;ÖÖCÞoØ ŒZ=ÇÕBĬ—h½A  ýÞvw-W‚`Þ1©óZòÎ ‰t1©<‡þHü»¾ø1èÒRïÀÔ\FÒMÁBÇÞÛç¨+ ñ;Ö ÷ ÙM`™4š¼¹*r乘t”8Æç=wl|É @È*¢Øç!Ÿ&qc9ʇ]ñ‚t>­¾`ÏíØ¡›_2q3ä@Íö Žãçþè2åYܲ¨å~êϱôpXyˆL1B]Bߨ›\ ÷¡'ºöi ¢I (% ”€PF ™–"Êú%MÇØÍ \·ú5ŒØòŒ5‘RIëRiÉñúÕcÅ&ŒÚ¸ÕŒ%ïH¨|üŽ4Ü’ºc2®2Âø‡o“RçHpÂñ>&*6½}Ž|ßÐáfcû[9¦ñî³ÿ„‡lÅýo¬ÇÔvã‘òåoÇjÀÙÓOõ±Ù ÿÁ <´ïCLݼgþÈàkØ»[>04M¯žwâ»Gwšº¿w¬Ø²,hÎÁ—Oe8\ºãöÌ¿ãÞ7þ†§+L–¤DŸŸ-çœý•–èp»È6D.øîà}tKÊk¸%-2‡/¬¹hrh}lå AŸC‘²CSÓŒÂ@Ü]ª·¤³Ž38/ÊwlIÃNâ:åˆ.SžÁN¨ök²\ð/?H,2«Ñ+¶ñ^Ïûu[‹ B«âZ‡ xÈŒ_bTÚ Iá=½â.ÓF“3ÞÁ·xåeì4KšÄ/èÒ'±ÿü}ØztCñ[L<‚†ê4TWsYÅs‹Êõå4-zïCôŽ5®²ZBÅçYÆÁ‹«'x‹³¼ÄNp$iË' йÛùŸn¥òö‚õ95JÇNiÚ¨§&Ç‚¦,ÅÝö\OJgÎ<ßœµžËoxL=;Ï^Ô§óKÐRˆW¥cüâ»Ììr×éÏàîé"d;žÖÎŒuç3‰wfÂ^궤KËq³E‹‚#Ÿt2<’¼==c\‚&ÎbäÓYåi}\Ís=Ôç_eÑñ.!WêéBmñ݇f´ÊK_µT® øÐ¬e‰JÚeëÍNæ\•JWªb •““¬¥|¢i÷Ϊ :'åq|ÑÙ›¶%M°C“ÚT\žò²c=ÖW‘·a¼å’BW—‹ÍŸVÙ¹rD®,ãrÞDË^‡»~–šRšó¹òãø¢• 35öièÊ~Y+…Lr¸w¸2rÆÈ^Ùê~P·•€PJ@ (%ÐB€ïˆçOV&3ïM Ý/~Ÿh¹çöŸnGiÆý*œÿ¨yG¬Ú¸vºÏÚ)ˆn©Þ˜‡çGb̲© Øω¨xS¨‘|мGÉ[¿5‡Þ¶7GWïF<-U}8Qs|Ù¶¶¾á#û¾w+ê~÷7Ü>óNØâ† œ©ø­u°OFŒcÂß=¶AÉÊoãÃþ†±,xÃþX¥jhºüá(yŸ—$êN@ž<¯‰èó@¢ÙG-c!üà.ì;[²ÕáÞ§Ù6ëˆ7u”áÌ—|§¿! ãîDÔ¿dÊ4¶ˆVVßào èíK@ÏQ¼OxÙœ÷ûä´þHò“L{ 1â7à.Geisç2œP¾‚G1ˆb<¸Œ£ÈUEãÑ5š1×X·?Ùëh·ñžë^_BüûçÏþõaÿ‚q7ìyÅìg•fu´€™‰£Ãç‡ 7²TßX° _\å® 2ŽNUHĨo݉î“'Â/< þá¨É?Šâôéžð9jOJ›í'º1€Š€°[†på…{Q{üýÇoáÎò§*ç Q&˜åYCMºËvq\üÊd}¯¾2Kû'ÓÔ¥˜Å¥ ÅçìXÏ£‰ìpàÍ‘8=Ñ!ØñF5©g6ä#tÊpë8}âÝýú™.üÃù@/¨2ÚÝ!’U¤ÌÃ.g˜çÒÌÇ4ïì}ëó*ùeᘚ¸ð+[s6Ð~¦ú¢Ã¨ÚžŽ=Ž£-¹R±ý~ _;Ë|1å¸KaPq…«ºLÈÌÒ+ÏØÇ˜8Ò•×^‰’uÏ!ÓX0H-ðRÞ²x’eùáTØÉ5«Žk³ÆH&WÚ?a4êøÅîÅ/v 2ß^ˆÀQÎkãÊÞÎM Ú•çøh¹p˖Ǹ¢†¥Étf_J~/}Þæ:tbî4>Û×`àô¶1íyÿ@Ö„y®À= ÂÜy­\U°üaZ8‘‹œwާ‘ ¤¥u8c°}ÐïöƒØH."è’0ŒrU]Þ>SÑöÿû1Acüd´ê»³½«ïÓëýøa”Ö;'ù0ö@ï‡þâ~ü°q!¨ü"Û,±(Ë+^H+…¾?ž‰^ÿç»FQðÅÜ…¨`` ƒˆ;G3â=ŒëË×tå’Œ§èÊ /Uºc·1;i<ô6ÒïYr‘ÍÈ OÿŠjU‹y3o3åƒù0  o–Ÿf÷nÆ‹¬ü‚³GL¥ÿ¨Šå¬}JÛ¥ /¸¢Žd4½“À €tñàŒú‰Ín<Ú©/jZ2ãGp NþÑpòj'¹2àó5å3¸ezÛ,ƒ¦`ð‚)ð©ØŠÏç®…Dcõg®R.±âµh=¾õp"÷Šq°ßDxm+:ÏFum(=pPwˆ+k8–u9W©ºwGG¢Ù^‚j^ cAq®ÌíO {Èp«½lò<Çý1°ù·Å¯*ל}Šõ‡_hÝW²m·Y=¨”€PJ@ (+€@Äü•\®:þÃ8͉§Â‡¨Èø¨ó:–{ì«ßU;¯¥k«¦„Þ¢¯ñä’ >Ç)q…01ãD¦°ã$] ö§]ùn`|´ÐPº4{{›Ÿ;wšN{Ñš_޵N#GŽd¨Á&œ={¶s- š-´ô£mŒïÿÈ®nð êÏ”3È!o$Zœ=ZÀ%Ϻú#J†®CÃ~ªØœ«+)EiÆ6ã÷^[PˆÚÂSFa ¤^Y¡Kl/SGMÞaÔ—yúÆ»*þšc`›ð¬ËO¥bÝ’ÔHŸö´¶ž*úÚTu ¶Ž)ÝÜJ«ÖÑŠ:R.;ÇZ«ù¾¢žâ \ÝBòY‘Ú¯,bÖLôà²?ÀpŒàïTH ž¶¤Å¿¿"%æè׸ō®'øs¡©4­#Š÷Úsq"¥í=åžC¶ÍzȲ᮶f?‹/âZHqMJ@ (% ”€¸’xÇÆ!d˜¼ãqRŠA¸;Sap%SûråÈœŸj-m;1ÿ×énB‹é7_¼*_—h‡, $ ¡¤%ôCcõYØ‹ŠÐÀ˜Îäß-¾A]p–.‘wŽ¡éÈdcPøç¿¢|/MÑûöá  bè‡ æà gô ßx›«áÞÕTS_ZFÓéËo;Áè›Ã}Ó•8C¾ƒKÅ\NAßÕ¶ntÀL.Íò”k%ÑZql®úÎüöØãr}pÖ % ”€PJ@ (+–€Xòò=½•+CguW- :Fòz°4p’«ñðþq”cëPòzêU%#^VKƒ.½{¡ÇwÀ(Æ,ãì!ŠÞÏÀé¿¿ô%Õ.2ëÙËvø­C9v • ¨ÎÍ3–‰ÏÍG}E%Žþ×Q•}ÐÄ/ð¡’!løÍ½1 Õ´"(Ú”n|õ¥gò¢’Á78ˆ¾R6®ÎЈ†Júów²bÁ»E®d8ÖÃÈPNþWÏçZl‚+¦#„OLçu­(ÄÉÕ/`¿ÇR.WÏÈ´§J@ (% ”€¸~ ´oÉ{ýòБ_nÆjÜÝš÷rwàjï¢!Š;AŸý3c 0ÐaX¨ér`|_ ˆ7¼(š=cO–‰eÁÀþ´H¨Ayæå€Ïî&æA—h9¸âZ4¡Û·ÈC–ed` qc¸ˆ'ßzǸ5HC1t舢¢K¿¾tM(CéÇ;¹:Ã6³dcgñ+^ð v¤Ð>ºÄ3€Kg5 õ\[^ÅΑ¯š¶$(ʃÖv.OG´% ”€PJ@ (% ”ÀÕK࢔¡7 BÄ#-…[°•Ñ÷ŒÃ©w6«w²rBÙîLsH\DøÏúé"³übíñ“.%ƒ_×Pø2rª,«(–þQáîÕ rÜô=!Œ Ë-677!|ÌHRpôwÿmÜ< |*ñSWÿï¯AðÊ*ZÊ8š”€PJ@ (% ”€PJàâ x_L‘±à´¿g1âEà—X­Ss}ƒq[×±B@ˆŸgq Åð ‚oW* ¼½L\ƒ¢Í¨ÉËçö.”|ø±ËÊ@òôø§ûÂ51eYGqSÅB—^=Ñý¾ ¹Ñ,@ÚºiÝWJ@ (% ”€PJ@ (% ¾‹²4¨+.5®íµ×XkG}y¹ë”ãH|ß`TÚÎÿÍåà6 ¡ªŠ+*¹ê²uëYŽQîI”þ‘á|Êwæ~J·•€PJ@ (% ”€PJ@ (¯I࢔•Ù¹(Û•‰náfÆßÙ¶Xœùx;ì'N÷‚.±1èóg ë°!FЯ=q'ßþ»Qˆ’@,bø}Æõ1Vâš`gðÄÒwx( œõ77Ô»¬œÇ\Ÿ ~ÐD……&% ”€PJ@ (% ”€PJ s \”Ò@ü¿ÿACYºr¥ZÔ•àÌÖ8ñ׿™¸â:u÷Xt»÷nÈÒ‹’$P¢¸)Tç¤ÅA>ƒÒE…—ŸÕ|PÿxØyÛžýt1*¾ÈâR½áÃÕœIÜjŽ˜‹Îcú©”€PJ@ (% ”€PJ@ t‹RH“å{?·V.Øþ©q=¨+.Ᲊ¹. /o„Þ|#WIhî%B@lO-Œc,.±Xx’. ‡œ8ÀX&ˆCí‰S瑬¼p"m#|lþ»íV.õÎØµ¨ú2'þ²5‡óÏYVO(% ”€PJ@ (% ”€P#pÑJi¦æðQós®&ëh}.£èžDȯ/¯0Á«râØÚõˆ¸}¤QmN§â!Ç={›írºEˆBåþ\Øzt7Ë,ÊŽ¥Ûv+†6ô€PJ@ (% ”€PJ@ (%ðµtHiðU-6××£øÃ-fEƒà„ð¦ B}E%ÊwìFeÖ—¦¨X¾ñW}a” UçŽYàÖXåÙMJ@ (% ”€PJ@ (% ”À¥'ÐùJÆ8C^ý¢î ßÈ0T|–…’ŒOP_rÆcD@Q“PJ@ (% ”€PJ@ (%pùøûû£®®®Ým­V-ìt¥i•+œÙ¾ eŸî5K*¢©ÙX´Û#=¨”€PJ@ (% ”€PJ@ \Í”×ÃÃÃqêTûqÃÂÂ<úáí±×™;ìHsCƒ‰7 «hRJ@ (% ”€PJ@ (% ¾yÑÑÑnÓ9&çÜÓ¥±4poA·•€PJ@ (% ”€PJ@ (+†€(âããQ\\ŒòòrÓ/±>p*šÜ6P¥ÁsÙ´#J@ (% ”€PJ@ (% .Q„„„ÀÛÛr@ðòò2 ‹û‚{ºtî î­è¶PJ@ (% ”€PJ@ (%pÕP¥ÁUwÉ´ÃJ@ (% ”€PJ@ (% ./.§ài{pyÚÕV”€PJ@ (% ”€PJ@ (Ë@ //¡¡¡ÆAÜ||| ŸòÓž{‚Ä4Ÿ³gÏ·¶¶ö2tQ›PJ@ (% ”€PJ@ (% ¾ Çïp³êžÐatZP (% ”€PJ@ (% ”ÀµM@•×öõÕÑ)% ”€PJ@ (% ”€è0UtTJ@ (% ”Óøê@IDAT€PJ@ (%pmP¥Áµ}}utJ@ (% ”€PJ@ (% :L@•F§•€PJ@ (% ”€PJ@ \ÛTipm__PJ@ (% ”€PJ@ (P¥A‡ÑiA% ”€PJ@ (% ”€P×6U\Û×WG§”€PJ@ (% ”€PJ ÃTiÐatZP (% ”€PJ@ (% ”ÀµM@•×öõÕÑ)% ”€PJ@ (% ”€è0UtTJ@ (% ”€PJ@ (%pmP¥Áµ}}utJ@ (% ”€PJ@ (% :L@•F§•€PJ@ (% ”€PJ@ \ÛTipm__PJ@ (% ”€PJ@ (P¥A‡ÑiA% ”€PJ@ (% ”€P×6U\Û×WG§”€PJ@ (% ”€PJ ÃTiÐatZP (% ”€PJ@ (% ”ÀµM@•×öõÕÑ)% ”€PJ@ (% ”€è0UtTJ@ (% ”€PJ@ (%pmP¥Áµ}}utJ@ (% ”€PJ@ (% :LÀ·Ã%µàI ººåå娫«CSSÓÙGíÔ×#€ˆˆȧ&% ”€PJ@ (% ”À¥$ JƒKI÷2×- ƒ¢¢"DFF"((ÞÞjHr™/Áei®²²'OžD=TqpYˆk#J@ (% ”€PJàú% Rå5tíÅ [·n Q…Á5t][E®¯(†JKK[ŸÒ}% ”€PJ@ (% ”@§P¥A§âüf+«­­E``à7Û mý²KqAѤ”€PJ@ (% ”€¸”Tip)éjÝJà×Yq‰àjµJ@ (% ”€PJ@ ¸¨ÒÀ…B7”€PJ@ (% ”€PJ@ (wª4p§¡ÛJ@ (% ”€PJ@ (% ”€‹€* \(tC (% ”€PJ@ (% ”€p' Jwº­”€PJ@ (% ”€PJ@ ¸¨ÒÀ…B7”€PJ@ (% ”€PJ@ (wª4p§¡ÛJ@ (% ”€PJ@ (% ”€‹€* \(tC (% ”€PJ@ (% ”€p' Jwº­”€PJ@ (% ”€PJ@ ¸¨ÒÀ…B7”€PJ@ (% ”€PJ@ (wª4p§¡ÛJ@ (% ”€PJ@ (% ”€‹€* \(tC (% ”€PJ@ (% ”€p'àë¾£ÛJÀ¨ÎÇ'ŸG¯¡cæ¥P 2d~’‹°¡·!.èÚfr|ï.”ÛÖEïÐHôJŠGÞ&`/:ŒÛ÷álf. ÿ¡‰è9n4bB;ûžªDnF‡ÝŠØN¯»Ãÿ¬+Èzo `;G³ö:ô¿ q±!çÈ ‡/”@Áö]¨°û£÷¸!hK³3ö¡)6 ý£.´Ê Î'×9goå9Ú¾àj4£PJ@ (%ðTiðp®×SÕùéXõ›w1ṡx$ìÂDÄÓŸü/¦÷Å ?ŸxM•ͧ?Á‚ŸŠG~= þ誎ãÅU/ƒPðçG_÷†…=ŠŠâö‡x £Ñ{ÇkHˆîž¹ñïÌC›uÚ³ƱYó×"öÇC<+¸NöjóÒq|Ö«çíDšÕŽ {žbzÚ€%«Eñàìæm¸µÕ}lÏûŽÌZLãscEç+ j Òq‚×¹2-#‡µUY¸uT7•€PJ@ (P¥AÁ]ÓÅüüÌð¬ß6Òšü(Ì ÄÅ”¹°š¿™\õ5ù8RþêÍ÷ÂÜäûPíö&*ߟqAÂkƒ½ ‡¶¡Éq›c¶xô\>õ±/¤¹5{Eo%MÇÐSL½m6œX÷SœXQŠž›_GÏPXüx\Å̯{mè7w•+Q±)h¥¤:–±Á46ûÖ¯ÛP»åbÇ#ìç!‰öo÷¼TJ@ (% ¾>kú=ýëãÑ„@õñýȯî…Á½ª±é­tñXtÜPÜ~û`33|šçÓ3+x4Ÿîß°°8æögæÓÅa0üò3±9}?âîû†vóGÝiæûô3ä—U!,z n»ý6ts3ù¯+;ŽÌŸ¡°¦~Ý0pèÍHèfYˆ²CSèʰÔrû¤Q9‡W†x”nIÅFp-ÄŽ{&£êó¬ƒ%ëÍúœ÷ÿ±–Óf«Œ®?uÙ¯!ÎVuÕÝßðŠG÷Yq8–’Š‚¼9ˆv(ìükàßESŠ;¾üí“8¾b·kôæ>›õîX<6Zää}‘´JØaå©KŒEÆØÇ=xË}wòùõ;#µtÈ›¼’×Œî ¼í|¾l;Ïõ¬’†¤cË×ãÎé‰@m!± ç³LÎK’<åŽ{9ÝÈ[¼Íwþ*æõéo®—ó~*% ”€¸n´ŽuvÝ \záü8Cnâ×Rþþó<”DG„Âtä×'aMJ ž™Ð•YÆa9·SfR ¦d×EQÄŒIÆsË_Ą̃ýX`}1wùÖóG¼öoÉÆ¥!ué¯_W‡ôµ–Â`æòžÿ3Ö<÷iú¯ÿ™nf·]}‰‡ekþhêXöø8æ)ÄË/þ•¢:-#öÿÁ( ºŽù^ûãŸñç”×ðÐÍ]Q¿uþY†æ²ø¹( ünƳ¯Yí¼öìwX²¿Y’é¿OÁ<3û~T"¬Á§ÌD‡+³gÑÿ‹Ì5 ŒÂ ï„¹XÃ6þÈ6’Ù²RñÂ;ùÌ ZQ˜êžC Çòçß?‡›yè­¤ŸWtÚñòòãhVòù“›ñ>¹š¹ Æ “3Û_µ#ç!1sÆgg ïò)Ö&}jE-À‹³½ˆ ñ ªÈ™AIÞ!ƒ1ŠezÍã^²©ãv* P± _ˆÂ >ýYïÝ4Å7õ§"ç·ŸS(Øf) &¿„Q‡öð|"&³ŠK›'­^)”³Ö³âرm85a"ÒûM@ÆS/c÷†](…A¨Åä«Çžƒ!ÙÛ0(uë‰D-¸ã£ÁÐ6Çf ¨Ùf”>¾Žj½l=Y†iòÖádIfoÓÂ²Š­– j âvXÌ®J¶ÊpvÞ—WþàrKaA¿õ»Éý–/™óeÏo5×Ú‘ùªúð‰ƒà(vù÷»qÆô¼•-a--`å˜=Ç(¼%óP‰]ß…A<¢6nÂxrš¶„÷÷aœúŽ< Å¢0\µwðü¨kÀ6jfÏAnëvÜÿ€“õ |E¾7ÛP”WwuÞßì}_ â’*2ŗür¹&LŒ3Ç*÷¦…÷¬—xßîá3c#"¦Q)•ò$2·WŽûV¾³– ?c}t+~Ë( üW¥™ûîŽÌõ`mõ‹_?\<­—™Jd~×R§¤þ#¶Xüë<ˆ½ŽçƒóÅ',m£¹†·¼oÝË´r@3ã…ŸIÙ»Íó§¿ãúÙ/-jRJ@ (%pÝpþí¼î®¾2]î‡gæÇáF†ñŒá±B<^ƒ   „J–0Ds›ú“êi¾aùÓ`p\7m{×X$Lxv nE„?ºÝø^x(‰ÛYH?P°K*ÿÇ;éÈ=^†°Á÷#…ËçßæªW*dþãH“–ü‘0þ <~3ËR‰Q-ЇÍ<ƒG’oGP}5töwæ vrUˆ#Ÿ8ú1ÿ ºKXn݆ÎÀ²Ç“qß?ÅÁŸcˆ6ê‚èè®ðwˆå°_—‹·¶Iè}øù#·CºáÔ <»2’#o}âR xùMÀÓXnŒîãÌey&Ž×Y‚‰ôéÊLÛpbÂ4˜ü gñdP½…¨53§Éâ˜}Í_ʮ߂ޯ'3B¿ >T ˜þ¬à›·¼c"Œž'±L ˜GÅÑÂ7 2Þ4³€Á/ÐBÁVgÌ·{O™i…& wUFÍÄŒÛßF._òÏØC0lEmNC-$®ÌdËÿ‚¡›× lÖš;— ac*Êæ>Šì¡£‘ñ[Kð>ߨkÄۘ܇"(:ØÈŸ¶vŽ9´Ì¦ÖñW¿XÆ_L{ŒÉ“ø¹åutuØ*úú3èo2ÐubÊ„4‡ùËŸ®T20U¤¦ã(]TB“&aÄ–H\qk[«“ójø‚îsoJRqŠ}sQ®™'* m+dF»:k·ZÃÆÅÁž·UÅt3™¿ú‡ ¦¢Ã&!z3ÿEŒä_¸"Ÿ ¯901u<ßšˆxr g²ªøi¥ÀT'k ¿kbÙSC·ß«òþפáä3¿aÙ&”˜!:\"ç ±À°ãàªWyæôœ5öJÔØ#‘0÷I“»f/vGòK…à⩈£{MX¸huhÜ•–Ž/óèEžÃ3yßmþ9¨¦õHtQ®f­ÁÈqñæy;Ã׿dò•eä·ägž[‡YnSáý¡ã\¤æòÙ- 7yv½‡ã›v£¦=q“ŸÅ-i8ÎêKK%º¥”€PJàú ÐÆùú¶Žòâ Ð'×-î-ëƒ3¢OøŠtóÜ1“$†–3x(w;€iøŒ¹HÚõ2²2þ€Eü‘3ä><òäCf›óKü EŒÕ¼ã-ÌÛc!ŽU£Ìô‰–OÎrwn”oÞŠŠï”qWúáùÊ™0þ$82º,¤®Örh]½1eízÇmTG¸%¯( ¥N ËQ¸šŸÍ·Ýì‘ÇŠ1Yúöêu«êߤ+@ÿ¿$· Á÷Ž,Zˆš©ø|ûc=ʥ߽è層…Úq£QºÑÞaA²©Â9ª’'ããÖ (Ü•DoD¯EqœÂIiò4#Ø!êV„.{·ô§uËšÄ"’‚UäâáÀ⥰s‰ŠÖÌ_‚†œi–žç{‘}&zµ3>‰—pþDáÖíZyGÇ™"M\zP|öE˜‹N v«Æ†îÓGÓªCzôçìpɆGQ—²R€<æ;mú.NvÅ«0¯²_=Ç}yø™è˰‹½Ÿ‚¾ƒ#“R 0ÛSyŒŠ1šÜ7äUšÑ5¬xÛW´(•Ùg,³÷ïâóA" {¦*Œ'ʱHtíïÆš3ì’šß@¬®Æû[ž©=>y/¤R©4ŒSb\þuögebë;ï`ëçïbé£ùXþÇçU¹½l;Žø…G›º£8íFª£ëÁ3Ï>ЪÏlßRý§ûM^Ó7…@Yþ~œöë…„^ªG m?jŒäïy¼e$Öñ®¸d¥g-WÀ^h¢ccÜøQ Xñ >K‹ΆbTœ b†r+(bËUfßkëÌDuÃëTƒ™}U²^ì#–º’~þ0³¼ÎüMœ!ïÉ™õ‡ÿ7Ìø9ŽÓuâäÞtT,KEÅìiø˜&Ìã¦\y/öG~ÿ]\¾Œé1À»ÀÆ †qã¦"úõlŸö*d¦µI¬.( œ{ìUŠPg¬5œ,=>keïgÎùÝr¦"+—;}Í[ìpܹ“Áÿ “û>œI]‹š´• u°¾Ù/!Ö1®–ÒWÇ–-öF3C^C…âqÎ.{}ñƒn2ÂiyÆ>.åGvœ,Õ–þ]è^“äoFZpø›ìºö³YêÏi4­Ÿˆ† GÉDåŒ,ïM»0 µ,läx›dà WÙýíBïIÓ©4ØŠí¹8âõ6G2.Dœó4Ep“.0½ÖrµÆH±’(LøLáu e÷ü¬Ò•l1¸Öc„³€ß÷ÒÍo¢"eÝ|6ÀNW™\ù¬ cI–rÂíD³uÜyDÚðe ×s¥èQ3è–0î"ù8•µ %«V¢vÙ<ìÉ\‰ñ¯ÜuU+ÉÎ5f=®”€PJ૨{ÂWÑÑsA€bc;BtËš…àêY_Ö¾aYX•×á“ÔwÍf· :¼õô÷ñý%ïÀ¯[Fpæÿé¿Ãs÷ˆE@ŒoLw ±ùSyût¦Ó\€‚,ç³Â1ôööå3”E'qÕƒÁÖc0¼±ôE¼q †+?Ä1oRßrïÇiüzÁR,^ð–ÛÌÕYgžŸÁÑLoˆúÍk‘YÝì:Ww<•n„…9 Œ]ç®… /®PТe¤àžG³îWqp¯›Àøïæp£*_þ‹ÿî0W \f05U6Ü’äq 3’Æó\ j‹(T3Âùó“ˆÊ”™ ´ÆUòÞÄý&`o?z1*ú­?ƒ»²×˜¸ MEžÂ[#ßèfpÒ¶¿‡ÁØv[~òn½©qXx3rûùÆÞ¢„©ðP¦XÕµwÌ­¡¯Øì:XffKP´a_K.Æ–8•BÙ¤:ç»<õž|o˜8£×þqóãx¶Fç`e¼ Ç Û|ZЂBÆë7í&øxqõ‰±tàL³Ä'K¾ÉŒ+¨‚¹ÏÎî¨D\’óÞ‚€ìTTLƒ]_š¹#m7ü¹’ˆuïA|è pQ^ž/U_…÷·sLÎ Œ7-栯˺ņ®É·òv¡°šàb3`¨?ƒ >ŠYvø8§á_ê=Ê „ô{§—£ÿ¨»pÛâ_cDÚÓ¤Xɸ§ Ø8s}ª½íˆQa-x?Õ|_¼=Ü—<˺êa ‹Œ~·à£ … 蘈§ÌÀ¸÷3,ו ÓWFÝPJ@ (%pýh‘®Ÿ1ëH/K_ð.VüºžK(~÷‹s«6â{ •˜Œw—âé²ïàÛzáÀæuØœE Æ>„Oe’?ëxâ…"Ìœ0õEŸbÝû²Dq êδõ7OÇ„ñƒƒðÉºß ƒUt0 þ^hæÒŽ~ï®Â>ù ÿöî<ªêÞÿw’0%‘ j"’á–¨M¬0´ìËå)$žšx¬áTÚ#œz@m¼P h ¯­-—c…Úž¢>‰µ}%pþO!ØÔ’ªDÁ‰B¢‡-„‰Hþ¿µ÷\öL&÷„Ì$ßí“Ìž½×^kíÏš‰ìß^kí»æàú!§ñêoŸÕîþóØa¸lì÷qíÚ}x÷O˰ü«»póõzïHæ×Þ9C»»~Z;¡¯ðë§žÆÔo߈™n(—áŸï„’ÇŠ9ãŸïºW~ù¶nÛ©U/ÿ'SCÞa÷Ö=b_åzGEÏm“GîÉ„…£ï^(ñ8•{*äQ‚— s£áÑB™2Oîâ=9O»ó\/™ñÈC[ñe~*N—nÄ¥Þ Q]â¼vŹoBÜŒ,Œ“ ƒ‹±J.ز±÷Ä/0lÜPücÛ œ*•|OA¼¥ZlÀÉ?Á_×þ›VîçÛÖkw(câÛ¿(ÓK½°¿Uë‹!›ç¢üH¾Lþ6IéÆé;ðÅæ2©ŒÌ>?Å«ÌëÐÖ¹«{²ç´;´ 8öÐjücÆT¹¨‘ Þàm™ÖNà¥Þú=1{Ž/@Ü5Mh|X7•ÑêZ^u«½töYêqyn*Ο8„O9ecbD}ÞC åž™§gFÿ¸qê3k‘y 2剪mr(C´År<%À†ºÍ ° 4‡¯ŽlGýc’î†By”£=–·çlÃ{ÓåI"2—ÁE§ä±£w¯–Ã3pépù|Ó³jíw¬äiŸoÿ¹Xqå#8¬²ÄΙ`è­$Ÿòœ9òJÔOž)ßÝòÝuá„ {j’´C䱋€S~—˜KdÃv|p³¨<&ž2TêÄ“ëµDQ– ï{ü™/Á†ã›åïI^æL’ ã+øâ)ÕŽ™™i—1cÕÚ±¡ÉbÌV-8zö\íïíÚ¡8óV±þ4ŒlÕ‚ (@ P`à 0h0ðÚ¼Ãg<È{×Gý³¯Å¿”d›,Þ4öI3ôÚs2ÁNœ¾rŠ4ÐS‹» ?ÙüÌ…Oaç¾?áé}ú¾¤oÎÂâ%7kÿ¸œºøWp®(Äk&µò£-ƒ’qïòB- àýç^Ò7¯Å¾?=+?z’ä)÷¢ð߯×Þ˜.»Qžºp…+žÅkÏ®…žË ̸k)f¥¨Z –§&,ÆSKVàמ•=oþóB<˜y¥öf°}&]²ûÞ݃?}q%n~­ô0iOQP .»ñA<ñÕ`,{z'þôìÓz2¦7ñbÜjWeœöl õ2 CŒóC„JÒ×Û†…è/c}ãäîëÉ7^ÇñS¹òìôIøöNõóøò1™ïÀS狟܂ñ3¥¶,I9÷ã¸Üu=%ÝØ—¨-ã÷PŸÚ® Ô–aSg¢Fº47m^S×L•-#‘ñöó¨˜W€/Ÿø>Q‰d‰‘1õ×Í)]ƒGâšÍõ8_åd|ö•O-ÆÕÚYIׯ¹«cž$w³eh@©L¤x"çrBl›‘®emñ–"ÂÚÐï{õª_p©@žñÞ«øëC’ ®/dKtv>⤻~£kTÚ´§Jq²_Ú²NkKٜˋ~34!!d€C›ÄO®M¿¬—‰"µIûäsyí4–€W”L\©.g½Ëµ2¡åW2B½8ù&ß5þö×eÊBŒ]+s>< 휿]?Lœ¶üB§hòüe¸Ô›]À«j¯èáYøùöŸÆ•“gÉ|ÄË'&úwÈZ´ú›!OÝ88{‘|w峤íµ!ní&\¯3@Ô?þÃ’dÂSÿë–9L¶ãä§ {ˆ{F&2”cÜGWKûœKø>{jêem¹!W­“ Õç4D¾ÒdbD³ü›ðÆüEž’qVþþ÷î”6ûÔTMðzð• ”€©Y–uÆýød=ŠQ£FEÆÊ,Úeõ Áøo>½þòøEýnÿ y:ƒ?AõÖñ¨ žxñ×2i¡ä¡2i-ÙuZK ’øŸê`j’ýÒI¡Í<ŒéC­{ËPO‘¸K8µ÷i5K¼œ¼YÆ û®S n¹®îðÆÈ|¡ö’¬ºe†õ&¹%w_‡8°«ùrß|-§4/ b¥gÄ`1 µ´wéò6¹{ûNñ\’#”à…zÔ`´ %©Ì“‰O,À·wÍ’Ù$ôEÕ_ë"o[«»'iÿ~—ê/w»­!ÛPöËĆ*à¢zte‰ÄÏwGϳ½¿-ó‘ž9žQL±âÝ2AЫí”<Ä[Ÿ¿üÕ ÙÄ!Žá& P€ @¸ ?~ñññˆŠŠ‚ÉdBtt´öªÖÕ6µ¨uµ¨Áùó絟3gΆ(k»ù‹H@.°Û¼Ä–ç6>ë0¨Z_©¹Tmf¢’´@=^Ñ’*¤ƒoÛ+£ƒÙDt²ÁmL*¦NL=°+—M ´õõ®æÛ—ØÑrB—êÏ=l³í{›wv§ ê+”Ÿ2d¡Üm‡tÁ—^oJÀæÑt_À@e«êßö·ª³…Ghzq,ðžìïb°À—ƒ”Ñ•ï÷øp~mïoF˺Ëç®SôoiÈ- (@® pxB×ÜxTŸ D@÷þ>õaá褀Œ¿nÛbTå¯@½<ƲÞs¸6$äß®ëdfLN P€ (П8<¡µf8uWïG¬a{*lï°mšˆ®˜ê6~VºÞGÉ#!/íÔ݈>mVž (@ ôkOè×ÍË“£(páT·ñÁ.\Ù,‰ (@ P ü‚'*¿²F (@ P€ (@ ô‰ƒ}ÂÎB)@ P€ (@ P€á/À Aø·kH P€ (@ P€è ú„…R€ (@ P€ Â_€Aƒðo#Ö (@ P€ (Ð' ô ; ¥(@ P€ (@ „¿ƒáßF¬!(@ P€ (@ P O4èvJ P€ (@ P€ ¿XC P€ (@ P€ @Ÿ0hÐ'ì,” (@ P€ (þ „±† (@ P€ (@>ˆé“RYh¯ÄÅÅõJ¾Ì4<† †£G†gåX+ P€ (@ˆ5jTÄŸO û tß0lr¸ì²Ë¦.¬Hï X­V¨. (@ P€ zK€ÃzK–ùR€ (@ P€ "\€Aƒo@VŸ (@ P€ (Ð[ ô–,ó¥(@ P€ (@ D¸ƒÞ€¬>(@ P€ (@ P ·4è-YæK P€ (@ P€ˆp "¼Y} P€ (@ P€ @o 0hÐ[²Ì— (@ P€ (á Dx²ú (@ P€ (@Þ`Р·d™/(@ P€ (@ P Â4ˆðdõ)@ P€ (@ P€½%À AoÉ2_ P€ (@ P€ @„ 0há ÈêS€ (@ P€ zK€AƒÞ’e¾ (@ P€ (@`Ð ÂÕ§(@ P€ (@ ô–ƒ½%Ë|)@ P€ (@ P€.À A„7 «O P€ (@ P€è- zK–ùR€ (@ P€ "\€Aƒo@VŸ (@ P€ (Ð[ ô–,ó¥(@ P€ (@ D¸ƒÞ€¬>(@ P€ (@ P ·4è-YæK P€ (@ P€ˆp "¼Y} P€ (@ P€ @o 0hÐ[²Ì— (@ P€ (á Dx²ú (@ P€ (@Þ`Р·d™/(@ P€ (@ P Âb"¼þ¬~°€«•Õuž­f$§¥#Áœˆïû@P{§8Òaí7'Ç¡(@ P€ úZ€Aƒ¾n.ß][ŽyóÖørMž¿%ù©¾÷ÝZq×ÃY×èË".Ñ΀„O£—VÚ1lïdlÞ[Œt‹©—*Ãl)@ P€ (@&À AkqK`·‚„1ð<.Ä·gnŽƒÍSf† .D‹° P€ (@ (ö4PÍÝÃ'ÛÌûÚ=,Ú~vAæ–¤,ì8ÕþqLA P€ (@ P   t-bq»PïrkÕ·X`U=dÛáªJT;Ð$oÍq‰Hw8`7Ìžèv¹à’}µê·©;Y—+NòçåÍr‡<Áª†.Hþ•ûPål„[¦èËÌÍ„qÈ„[&ò«>T gƒ”/0Ëó${*RRìzýüÅ­¹Që¬FÔ»ÞuMƒ—˜„¤¤D¤Ø“ N͸hçàÖÏ«ÔÍW­‡F]£š§Á¬Ÿžž†$ ÆxtðºÎÃU8\]+åªJÇÁ.uNK··(7øH¸êå¸*¿·:ß$9ß´Àóí’¹”nM°†¬C§»øYiq¾Ü@ P€ (@ D´ƒÝ|«¼Û¹7yæ$hÎXŠ’æýÛ24„È&£`VÍž¤]€:·ÏƬ55-RÝ?E²µÙØ~ 6CþH^Š××'aYÎÞT± 3[ ¨Ü+6Üe;œ*È„Chp7Cõ.ð-5ËpÓ-Æ€Ú㙨ïìa,¾ñ–6*uѲ9È*Ü!=ü‹«r nj3` ÒÖ`Ùœ›°Ýé?Òë¹— L¨h5` Ž®,Z‚©÷m×zW¨÷Þ¥¶|5ni5` RÕ`å¼[PØÂÍ…’yÓÚ ¨£ËÖÌÃ…åç«¶‡ZBš{}½tÃÙØ–=ùYñV¯ (@ P€ @d0híÔ{µ´e`éºÍ(ÞºÞ)õôâv¬)×.œíScéÒ¥ÈK¬FFÞ|mûüEód2>_w‚ÀDžwÉŽdé‘ æ@hBÙC³PÊùË7bëÖÍX1?;`OCé¬,¯õls£bˆ€ýy‹°qóVlÞ¸ yŽÀú/[YÖò\u«°yÓ9°H{ñf,/È ÈW](¯Üá-WzS/Ç- U¿ ÏÒlCÞ¢UØZ¼«åy·j¯¥Kf¢¬Ö°p¾´+:5$£`ùºÇ¢t!J»Ð}sw7œNÇÿ¦Ÿb®Q€ (@ P€ýA€ÃúC+võlyxeÇx;ÓÏ^_ sî4xG"˜*àt߉ô$rä§ÙaFÑ-K|¥MÍÍEŽÝß»À™ìK‚äì娏(K†$èÛÜ5;0«Â¿R‡âWæÃnу©©é˜rcnÌõ—S*ësèõt5ŽM^Œõ nólHEúF õGe©Ô?©ž¼}GÊü ÁåÚg§c’c¦ÍYáKV¶¤ó³Èý{¹ß°Ð·2 `ÝöbLJòÔYæ3Ø;! ·HULB-‹7U ³pª ÃpbÓ ÿ 7#[v/GºÕì_3Ò¤W†ÿØçJ!Á¤.›«òÝ5åXì/¶Åù¶ç¬òX:úY ¶È„o(@ P€ (@H`OƒHk±¬oÁSs}=[+&ɘzãb6¼ijRú·Ûxïßî[Kž-…þ€Ú^wà%ßnµ²tc/`àÝaIÎÂÖùÆz¡ü}=$aª÷¦’ךX¼mœõ.ÏF+òKöbïîÝØ­ý¬j0𤜿¶e¹ÖôÛ°ñcwŠ"T¨!gxÉÐ5"oÕF_ÀÀ[Uçuþ:7—–HÀ¢îºÊ€^ùëæûÞcMISõ^ÒBu‚ˆ“ÞÞL§Í=™v×Ù[7ïkg?+ÞãøJ P€ (@ D¶{Dvûu£öd¤Ê“‚›=C¶ô¥JÑñ·y‹sZL&è<äôeÐl+@†¡§‚o‡¬¤dÞ ÛšJß{‹¶Ó‚ñ³¥~Ëü·ÐËÖ,‘¹d§ ÈÈÊÄ´)S1Þ‘"O°³ ZÏÃÔ«õƒv í_æÏû{9¸$0â>éD¥aôEQÑsHqÙµ§=øŽ7›á,ò»©yª$¨rø /‰š€1Û!Q‹9ëw §Åö®oèžsp¹½ÿY .‘ï)@ P€ (@ð`Ð <Ú!lja¶µu±Ý™j&cjJpP†º_&&»µEPÁ»ÓdMBŠÌ­Xá¹X/ÿ{r¯¶Ãž³Ë+gbI©?íS*ʤg€ühKr6oiyW_Ûç°Ë ¡ËI2ø@MihX‚zXÈL‰XViØßÊj½Œ¥H9}Ê·×õˆFy:^áK×ý•î;w¤=÷YéHiLC P€ (@ ô…ƒ}¡> ÊL@œqlƒvÎM^Ð ›/3«öDÇìJì//Çö¢"TÅ´lkŠ0{ZJö.o1ü¡ŶšÔ¦Æ´±4ȼ –æ`yzDð¦6òèú®žpîzé<’ (@ P€è? ôŸ¶Œ€3±"9GîãoÐïã7WÊ£‘4¯‚~îOûz¨-)Éé v²g«Ÿp»]¨sV£ªb; 7”úL2›@yÕ"ÌvX}ÛÔJse¹Ì7p[ÈùÜÎC½ ÔæÀóÿ°ù­ oPÉ‹³&Þð¶^ÒТ§•Û6 ¼NOjÏÈCî¤$Ãq]í9çΖÌô (@ P€ @ÿàDˆý«=/èÙ´¼“Þ~ñ¶xÿŰ ¥(ª4Îlè?¾b³š¨À¿Xce]žD°xÂLpda‚¼zÅh±XaOU„Bì}eyÀЃí†9¼¹©ùмWèÞž× ™¯À¸$XͰ µÃ!C%¼Ës¯VyW^ë÷mA¦,¹oNÖÔ ²R~Ö°$:ä!‹þ¥¢P¯±\7ö­ èU‘—û ÿ4è¨y·œý¥s (@ P€àž0À?9}sœ<Pð† Šî¿ûÉHHœ‹UòhEsG2³:°¸ ó.Ô­¦³Ø±âÀ¬uw›VoyÓÚyyÓ^[;'CmµÕz'ä{ P€ (@ P€},À A7@oßÔ"s+²æ.Ʀ¢yPaƒÊ=Upç§z.ö]¨,Ù„+‹´}Ú¡¶ ,Zº¹“ìžœÜtXˆ—ª›‘·¹v'Ï[ˆ ™ yþfÌ­ÛäÙ7Ó¬U˜?o¥/¯äŒ¬^u'ÌU/aÙÜ•ÚÐoË×­@VªÞÃÀ]³ ç¿„¦„,Z7ß3§•;аiM*¤0Ï’œ‘‡ æb’]?Vm®­Ü ›Ö ¬ÒŸnhræ-0œ‡Û‰Õ÷¯Du½·­^…,»?ÜQSYŠ+Ö£¬Æ|Ërü)ù‹wË/“ºékNÎÄŠÕ‹$ßNÖËs^|¡(@ P€ (.¦fYÂ¥2¬G÷œ/݇™+*$#¶î îi4×îÀM·,º¾mÎXŠ}ës$hPmónšÊÐå; 6bãl‡ìt£dÞX"]rÁ&äWÌ ¹/t®†­Í6l,« ng nœ¹Rv:°YêŸ.=%ö­ÎÂýEªÆ¡—¥Åú0‹ÚòÕ¸eaQèD²5ci1ÖçØ³‡1ïÆYZàbþÖÝÈ÷,*·Ìü !NΓcÞºW°`R’¼kÝÁ“T{YõÊ^m’Å×Ëx0×)@ P€ (@ ôÀñãǨ¨(˜L&DGGk¯j]mS‹ZW‹ œ?^û9sæ øÈE¥?þªGcPWWí!¬¹O¨3ž05]ëeà,Yâ  ÍœÍů dãr8<á¤Ê ó°í°KCòß“÷˜É]ü¼¼<ÌÍHÖòòlE32°|s1Š7/GF@X*K7ã•W6£ÀaÓ“›pè£FWmN¹Àn›0hNÎÆº­êØb,/Èð¥[¶²\.ãݨ(ò ’¥—ÂfI÷ 6./ð¥Û·l5œn½2Áó%¸oó $ˆ1•:¾ëægúŽ/ºÿ>TºôãXî=§ Ï9ÉQëËò»sõòÆ P€ (@ P€a Àá aнS…Ì›ö­6³ÎÍH”0R 6y»8aûŠ\ýâßž$wÿ;Mʰú¹}È]15 ?[ör¼R˜å ¸¡žyà]V{»ýÛ±`I©§÷·q rV-Ù‹ °aæ2m½¢ª³¯KõòÕ”`…Í–ˆ¤ ’f¯‚ÕV§« n‹>Û£©Þ{Xâ’$]R‚üÌF±Õ†}Nšš¬2¹¢=ó¦Ô_ÝØ³F=SB_m}¹Wëa¤ür<0sY™ì«²lÌ7ò¨xoˆÃì+P$f*Ìᙃ]¯—¡®R€ (@ P€è ú=Š,Øø 2“äÂØUuó\®¥›+ËQº]îÁ7é]Ìpúæ$0>€Ž r7~é‚Ì€Þ¾óJž/ó èݾmž•›ÿ¿éŠ­BE¨ëxï±6¤ÈþJõ¾²³n*lÉÈÌÌEæÔIÈuØ}uHž$wùµ¹*±dæMX"uÌÈÊDvöTdæ: ±}9ëyõ½HàÁ».uÏô ¼›ì™yp–iÃê]ªÇ…1h‡ 㹚¥¾âé?' :\/o|¥(@ P€ (& „ICôx5ä‚9oñ}H37.öͰ%Ù‘’–껀v×öMJhBV.«]™ °Ö}³o_sBRd‚‹\ûCþjÈBJb¨=þ4-×PðúF¸æÍCigoC ÊŠd²CùQKÌ50[æp,ØŒE®û°Ò›P†=T”i?*]†ÌͰJæfðÆÔ6m9[‹²²¦N'TÝÍñHôœjÍöCpÍNÑS¿%há ï›}á_š.×Ë—W(@ P€ (@ ôƒ}ãÞë¥6›R›› {;%™múE¯êN/WÕ2áa:¼ xÜ 9Qß|àÉÏ$O^û¡—zÏvÐ^“!|´«Í·– K`A½Õ•‡P^Q†²Ò m€:pÃýË¡Mú˜„ÜÂä.¨Ç¡êJ*¯@IY©Þù@ÒUÈÜ %÷"ß®Ž2,±I˜b—Ž 5²-TÝÝgQçé‘<)5ð¼C¥7d­¯v°^A=Zdà  (@ P€ .°'B¼ÀàÔóÈ5@IDATª8uîöLú×V™¦8ìúÜ~Òí߇ÃI“&é?Ž4Ô—oGII 5 N}qÝVA]Ü×\»ó²²+?Ûklpdå`Aázì8°«ò’}¹65×a˼\ ”dá¾íuHwd!A!Jv@ñò<_:˜‚ÎCÛc†gZÀ¹µ^ý°úƒ¥¾Þ ‰qþ¼:´VÛzu¨&¢(@ P€ (Ðk ôm„dkÇm™žº–.Ä}EûPï–gÈ\å[bIQ)ÊÊʰ½¶e·û s†.T64Ho¬Y¼•µõZOWm-œÕªk€ZôÒU Fæ4¨XSˆí•N-ÛU‹ÃUN-•úÕò4,˜r§'° CÜr?Êz95•%˜u‘ïøÜ©vßzGWº^¯Ž–Àt (@ P€ zG€ÃzÇ5‚rµ sÁFlÚ©?%¡bõý¸iu`õ›‘Õ¹j¿aÂ@íæ0^«ûùÞ¹ÜgCÝÝ×Ëðîñ=ø@ßìûmJšŠ¥òÌÆejfÁ†RÌ»ÅøŒ=Ysæ\¤ÈƒÙK3PªÍÉPƒeófb™/O:dbjªôìH íL˜4WÊÙ¦•£ævX()/Žù›õÉ#eÚÄÖÎUã='ýøNÔ+¸@¾§(@ P€ (ÐÇìiÐÇ Ð[Å7‡œŠ0ti&™3 øõÍÈsLé§%NΜ’½Ka·ÈE»,qCµ4' jQBkûl©úÄ-êdòO˜8IæH0.þ´ä¬ßåyÆÝžJØ=ÊWè}´ç¬ÂfŠÐò,íñ“{ Q÷„Sò±!ê½2¤XLÝΓô‚Û¾‡Qݨ7§êU¶÷!ñèX{¸ÿwk$bÞ+Æ~·zX˜ÙQ€ (@ð`Ð |Úb€×ÄŠØÉ8' ±“{æÎhô°Äd† éˆãEM¾Øö=Ý8ådâËÿu!Fzí˜{:sÉÏ$½w§ü\ þOD¸P€ (@~,ÀïõãÆ yjîz8ßr¢Ébƒm¸Ÿíهӧ܈ž {æ¹0.nÛ¿Ÿ½[‹óÍ\ô Æúºù˾·ãK·\6ŒKEÔ‘J8÷|kÎLÄ:ŒÆS²ýÚ‘p¿»uïÖ#*> —gNÅðøF¼¿}7\ÿ+]§¯²cø” ÐF"XF"å±ûá’üÌÃmžJÈÓwIù58ÿfà2©ó¸ °÷ß7u;Œ©‡û„ ¸$ƒ¯IÇØkõîÒ‰™Y87z`±J_ÿ¢Ž9*C!šä¼£†Ä°qŒ m1ø mÕ|\’·J7"s’^WV=³Ö‰2¿>U‹ö€V'9_ëõŒ–îçëîo³¸ÑIøò­J|~LÚf˜´ÍÕ6Æžmµ=ðù‘Ã8!.±£ÓÅÖ‰ê²Àèi3Ü…ºc²]>OV·8ï9ŒóâIÆ4¤Œ²âãý;¤=õ2mã&I[Š{¸´=ü>ê³{F>»ª®°$`˜øhum¯Å myå¸$4ì)ÇgGû¬,í³ã>õ‘´ß!œÑ>SÊ}’¸{>{ž¼Ž€s¿¸¹­.ù>x¨¬e‚9«ñǦáÓÿ¼ jÃF"ÙÓ…ºú?‚Ož¨hqŒùÉ"üŸ™©hÏ'U|†÷p¯…Ž–Ù°+ÞÎÑvÙ…øöº[Úï‚ÞJ›é6 ÝYŠq£å³½¶—‹Ì¿~;ÃÓýÝOõÐsˆ/¹_f¼ITä¦ÁûFº³ã®t†EÛ£M á |Ó\ < ß»Ðm©þ.Þ³ïݽֻ͗¿­ ßš¨÷®ùpëOPóXËϧ–6a¾ý?pàý{W²7ŒS4C{hßTTNÏÐÛAó—™x?d{aÚ¿YÛþžKîŸËçî`¨ÏV©ñýÞoaïáï„–5Q€ (@0èÈõW¸œÂñãǨ¨(˜L&DGGk¯j]mS‹ZW‹ œ?^û9sæ øôeý²¨ûŽÞņ¸µ«1dŽÃ³a;>x²R[¯zôNOÀ`¤¤Ù€ËŸÌ×Ó¼¹UÅÉ'ÉâÃcCŒePÀvóC‹qq¶Ý“¿þûh!.ž¬® ÕR&wNµ5ï‡1Fêøõ±}¾€ùÉMÿv †dkÉðåÝq¢¹ŸxrÑ|ÍÛe¸êñL=ÁæxÿX³§ µÚ¤w¡vIÀÄ0¸!—?³±£ôCšÎCå¹}à£Î}%.¾Á[×í¨;¢×U?ª‡~w¤L¹ ­ò]¸e`È3«ïu--ÄAÕ&í-Amf~h%¼í*Wó'g¬×®éÛm{)'jtPaB3L:Â3lÏΗϖ§MÔæI3glËð%úbS[‹¶òQŸSÿw8Y¦×ÕWùP+m©LÂXDÕ–ûÙ2ÏÀ¶Õ¾ÏÕ©üŸÁén–ÏüÀ !C7«4†BFÇjß-¯U”á«ìmõÝÑo;xi¥½Úmëæ Ÿ;ýïÅЇüíg¨W)@ P€ ú©‡'ôÓ†íÈi]¼¹7L‘;œ9“ðΉ‰¨/•ÈÒæb»ßІ¹ÂS‹\ä¥LI—qËv¸K¶á‹7sÏìƒ+g‚¾_ûƒ”· q•ê*}ö0öx÷ÌÙ„ÿs¤;›Ž=¥yÚ|Q²íÆ“m³ìØ#=Ô¡kpÎíò悦’í8:,ÃîÞë.–îܱn·~¬Jµ;>)KÀåçàÊmùˆf…%8ãËA_ùdóÏ–L¤-„ôú&K=¤‡ƒªÇ)u;EmÔ—¸’-úÜÌD]½;{éµµ2Oì)óïÐ7Ö`œºã-ÃI*OHo ­M*qzæH îh½¼mƒ,ÔZêñÞªÇÆ6|ü^Nµ×ö3gJQ=^’ VÙæFe‰gWB>®_·P.™e›Ô]ë}`»×=6KB83ñ×=ÒSAz$x/€ Ê4ú¨í=q1UßçT|Úª«±Þ-Ö³WJ,­ˆsë¿{vÛpù9H”19Ø¿i ƒ8!Á¨¨·^ñ¤‰ÄWWâÚaò}š\‚ÿ‹&µçD³g'^Zch/õ]}¸íïùÿ޳èeK‘o“¿Zˆi¨::Ÿy?'¨“R€ (@ Dž@¨³GÞY°Æ]ĉrT[,‘?˳^³r­â[6ààõSð—ë³µ‹SmûÑ—ð™aLsÌã³õ€ï }eŒa^¢®ôl“»ºm-–ÑYší©ß›ÛñÅœÙø {.>É]ˆº€5v$F®Ëѳ¨¯DãÃ82#Çógã£= ÒõÆx*Y“Ì{à¹@ÊÎB¢·ðX;âT¿]Œ”ìe¸ôúÙ²ÿ4ìü¾,©ÐJkzb.Þ·ÇðÔá¼·>2 êY OØ„ó„PKÔ(›\Ú¹ô7r?<ÖcãÙp^Z/óü)OÀõr'^î8{ÆŸûÂsU7ÒŽ‹:QÃ&™4ÏÛø_KÞÚ£.Õñ^#µÞFÛ{13ÒB÷nhq‡[ò µM•Óbé›¶·näÇÄ›[ÔªsÆã’áÆ<|W߈_»Z>gê½Y>ƒMøZÖ– ò·¼%Ô‚Wn|å vyw{^›äsìmà]­¾mµ½Ziëøæ:èáŒj|)“›z‡Ÿœ©ÿ¨Õ2¸ƒ (@ P  °§AÿjÏNœÍG¨[[ŽÓê÷Gøèñ2ýØ„«q©Õì»ó3,×ÈSÆådá÷!œüõFœzÒ‰’º’ôô‘bü÷¨ ¼=câ2sqãs/áúg2=Y’Ùç«°gÔxüer.\ç ã©ÿÀwßÞà+êœÌธ1,Û¡ozs=ªÞÕ÷þn™ÖM^íˆf <$ŒÞÙ&fyjSšíÕúú?þ†šå•úºåInÃàQ—²í5 ÿE~{–­í7Fd£â½Ÿ­ñ¸xÜT|S¾Oßœ’×¶bœ|¼çã­ˆòvͨ߆ê=ú¥úé#å8ùFè¾,•!)²ë´ÌÿqJ†¨tz±´ÿ=¿,~¨'ÛÔ=Yæû{QóK=@×é2y(@ P€ @Ä ÿ]q•g…»'p^& Ü¿ÝÔ;}™ˇ͔„Ñk3µqîç6ÏEùf¢lõ8ß w™Žý¹ÑÍúE·ïÀ^‰µx»¼WàÈ53qÂY h? ¸|ç/0\ßÜ䙾_Éd‰çÔ…”L½÷L êѾ%A¿ ?“ÞØä{b‚/` ORH~[ûðØ8ÃñrGÖ]kQW_=±ÒV™V|ëÕR ɵëyâ1TlÏ·ýº ÊÍ‘sò¶«¤—îé×=•¥VéLÛ› U6–j’vna%câ/6&’õLJEÛK¯C=c<=PZÔÕ¦åjŒ\ O|»È÷Ä_À@>{W½yŸÞë?6ãw®ô}Ö´ÏçH{PÖ¤l\P?L–É?=Ÿ‹à:·CðþöÛÚ‚o>õ*â}O;Qƒ‘0{{íxÇ7Õ’o)@ P€ ú€IžÁhÉÛNl žIGžªºýBx20öè`„ÌTÿ¹Œc‘;ûÖ ëÝÐ —ŒWwô/–.Ô!“ô2¶[êø¥ÔÑ,”3”ùµ?öN·ÚÔkKïç¹uwˆ»šwCµ¯Lu0;C¢0j1&îšéù¬xÛ&ð3XÛμóæ×ú÷Üû1ËßÁ†¬Õç±îX+s¢Hº¯¾j AfÃÆÕŽ™à:(@ P€Ž\…K]?.“ÅÇ#** &“ ÑÑÑÚ«ZWÛÔ¢ÖÕ¢BçÏŸ×~Μ9ã¿©¥íå¯$àF“6¡ž>©aë'ni傲õ#zzEm]×GËÎÎ^ø[z0Ò°}9>x¬¢ÕÓŽ¹N†Qü­µÝ¼yoƒC<³å·–̰=Z<ÔD”ÁK{õòês¾CΫ ‹šÐ2d°À›Œmï•Ð_Ý8úP®ÿ)";å]ÇÚ²½Ï³ÊÖ*ŸO}qÃ7§lð5èé¶i?¿Ö¾3gíÀa-é©rЋi”üGë´C{Û1³PGr(@ P€ À…ðöº¾0¥±”>ˆ±ØsC†Ü•7<¯ÏkÙ1KzlxŸXÙ‡§Òn=dÂËŠÉÒör§\õÚàÒY E;{P·Ó[®ÍDL‚ É.Ã&ºqe0xôL|÷èÌÊÙP€ (@ „›‡'„[‹t£>‘Ô=¦§ÉC)@ P€ (@ ô¹@$]ugxB8Þ¸êóÆg(@ P€ (@ P€œ$œ (@ P€ (@ PÀ'Àž> ®P€ (@ P€ (``ÐÀ¨Áu P€ (@ P€ | ø(¸B P€ (@ P€ €Q€A£×)@ P€ (@ P€ð 0hà£à (@ P€ (@ P€F Œ\§(@ P€ (@ PÀ'À ‚+ (@ P€ (@ bŒo¸ùGü“àP€ (@ P€ @X°§AX4+A P€ (@ P€?ö4¿6éV†Þ­ãy0z[àØ±càç´·•™?(@ P€ @o ¨×„…= B+ó)@ P€ (@ P€]`Р h<„ (@ P€ (04­Ìs¤(@ P€ (@ tA€Aƒ. ñ P€ (@ P€ À@`Ð` ´2Ï‘ (@ P€ (Ð º€ÆC(@ P€ (@ P€A€AƒÐÊÜ‹é8bÈLÊ|];ôêhü¿;FàçÅWÖß¼#Õ—NÕ÷^©ïﮃ´æqÏóoWCÒ_¢=’$hlùE~c$ûk5n¿i:æOL’|QöÌKxÌ8%‡2ôe¯Ü^ \(@ P€ @o pNƒÞ«ü-¸áž{Óçxí­:_ÍþþÇ€æqøñäDÔ½ñ-`ð½eEؽoöíÛeß»øø¨ 1ã–Ňæ¡ÖÀ.²ý®ª6‚Áu?Uƒ?À–»%¿ÝØ$G8ù"~ú{¹¢áBVì·æáÍŸM ¸»ÝJÒö7kwxU²xü®à_ðfá,¼01^ÞŸÅAçy=‰ç·èƒ{ošŠ7dÿ®»oÀÍrQ¾óÀ>”9›ÔÁm/ž2š1Oþ0[ÊøWìúa ’娧_ÝjuÙø.TƒØKñûU=þ¿› õ8WŸ?¬å§îî«€AÜ¥øõÓðÚ­#¤g€¶ 7Oœ$uûWÙ¦.LUÀ ßñOZY¼)AÛöÛýk¯æ˜AÒ~|7›Pñº\<ËòølU¦ÔëŽÑÚû_È…¾ã¿´€Á¥xáÑ•ýwà÷›_íÒóÒê$ç§¾Ù®·w郸$¼ ÎáÑÛôsÀiüøÙ½ÚÄ|f­ë¼$– üÙ,É3_!f?íØx~¥ Üþ­4¼p÷?!í«öì>Åo=ƒÇffjçùFÁÌÐÎÒsC¿À6Ç6#Ù³îÙ¥õЭdË hØåÅ.m¯––í1UÏïÒ·Go7 ôÏÊ ¯ÿ¨”n ûwkƒ¡¾óWí~á­1±Z­˜¬÷n¿u2Ò´îûra8北"+>ünç;xZîØ3wN!ŸýhXeˆÀ#?Ô/®Ÿ= _ÅóïKï‡ØËqÇwF ©QþF‰»¦«v>ªkexÄç²…'|ç‘eá§q2!  P€ (až{)VkV·ɸõÞdìúÍïñw÷ÍÿÙA¼&¹ÝûƒqZž¶”o ùÓ×ðó; ð¦ñ:ÉÔŧõz?S‹g‚kÝð<>pÿ㥃Óð=zP Ã8¹ÜQVKÝYÿó;¯PšåJ‡™ã `kÛo’¯œ@Ïê™Ï Êye>µ<]Z&?AùœûLëÐtN.,‡ôÝéV©Ô¶dgïûªœûZ‚yÀøéä]LþóðnÒ_c0þ¦q˜ñì[ØùáÜ.?j?üJütæTY‹ÆÙIØYZ‹§÷¿¥ý¨ý7§_ƒŸæŽ5ãE¾^†)ö m‚J§/f™#@ºC|úµ!¸˜»Ú˜ã$À ‹KêÞÑeŠ6w€'u;v|y•–ðŽ Am˜ª,µjCcýZ´‡ÔCû«vö3|ÿÏ“jëϿ߀¹jÈHL°Q&^-:~þ-2ç P€ (Ð ôz_ùéw¿ù9Þø{âj^CsótL–¹Ôrð×¹( †N¿k¾ÉceÈÂËÿŽ‚§õ‹ŽÔ½©Aúçz·º±Ö|)–üÇ/1Vn˜si’®âc}WAÞ#øJÞˆ’»öz  E gõ-nãüj“\ˆ?³-Ž ±ÁÜõ=V¿X*=šŽé<–}ƒÖ­Ü˜·[.·ÕÜ jN…ä!ê®uûKC/À-W¤ã‰GÓñȧŸ Úy{öà ǎãG¿~Y†$äÂ>a:þß„&Ô:¥§ÀûN¼ôv-^=ô^=¼ywZ@EN+£sMšKÀ××SuNf5Ù_ÌE0Ä òèÈ5\ ù O/ 9ÀkÕš=ꈖ­³^þþŒÑ͵ î3ZÀ x>c´ÀQ+Ÿ Iª=†¿ k§^…&CÐ bw… ·È¾sg´àB’¡ ÆFu­—cHÊU P€ (6ž6Mqá*bI¼÷È ÊŸ|?ê-Øn¿Uw-÷ññþ“r‘ÿýßaâøyú5z àÁ[]“ ¦†øÔû^†4ìù߻ı“µ9Þj°!%- iÚÏXÔ½8W‚‡ w%}‡p…\ÀrE‚>÷Àëû CgyJm½C:P³çöW¤ªÚ\{Ÿ:$Nfåׇü"N¾©ò}P?#Qûç7q¯Lè¿DÈ¢›oΠtÕá†göÊð£«à˜(]þü¡Lú%à šÐxöC~O½ð Ü:ÖŠ¿¿ü~³Ký÷üËÇxð[U1–L»ðÈ/àá[Ç¢f×ïð”–^O§æPøÖâÅ¥¹ò¯ìeøÞ7løàå'±v0î!5žš Â@`ȵxÄþîuÖbúªíxâ;WÊ<áñ÷OKåã§ÓU7ýŽ-'elý=›Oã®q—¡®ª ;å‚4& “åî·i¸]ž ðŸÿ[.ÈGãæ«ãñÁþwð+‰º¿>©ãßÏÅt[wÏýµ•ùÔ-ÿOàG¿=ƒŸŽ“g4Ô‚ÿ Xí"_zB¨|^Ý_s£ÔiL¼<¢°Z2€ µP}¾ògÇM×bè†*üª¸µGRqù¼ºëì”4'Èüò§!}O­Z%àØ¦Ýø©´áƒÒ†?øõü\z¸>¬ÆcUžn$ZE¢ebC“Ø‚_ï—s–yÞÓê®v{{3´]ç8̘z)~õúç2<áe<>=¶sµøåëµrX¬Ì¿p‘Ì ¡=ýr)>MÔ„&1ªÆ~OÆ+§íZp/(@ P€¸P \(é0+'yâ­ Œ¸“½—îLt vÝ6»Ö.•½² Ž0cíÚ×ðâ#{ðãÝ“µ»¡6Ï-ÑÄ÷à×bíkkQ &G§0ÜsÏ÷ð›g^óÜ5Mă;¶?›%O•D-7ü`þï÷“õ7üM êñtêclÓþ FÃ1û6<ùükxøýÏå®»šÈNy‚ÁÓ³³æÐw´þ{è™%¿æcÜ+?j:ärüF& Ô]ù 'Ïìà UGän½žÏÄ1)xâV5ß8€¾/äoËEÚãJë‚þ‚ûÏGŽÒ†IÈÝzù&N¹ë»øé³»ñ«ãµxP~´%&Vžòð=9·8|x;©:¶Ûív¡©É «Uÿ'¾ö^.>¬–Ðÿäw»=Ïc”ý¡Szžr c¶¢•l:V9¦ŠhcÇŽuòs*Oñ÷ Þ‹Ög¯þ„ÉÝãÖ¹(¶~ @k‰Õö3ÚS ´Æz¢c²­V&2lõ±”‘xöLþÃ'xdö¸Í-yH À2ÝgeÖ}9'sl,­\`Ñïd.·6÷@4,¾sóçë–qøZp@ê<7ƒ?•Í­ž oÍqrþÍí¬µoÙ^{µk'çá’¹tÛeøE9êÒ&áÅ™c3!+È (@ (õïÚQ£FEÆñãǨ¨(˜L&DGGk¯j]mS‹ZW‹ œ?^û9sæLk÷‚´´ü5€,–À {í}–D‚ól#;A  {ž/Çã­\c© ó®—ôïŽXrÞxpZ'.jeAð ~çŽã—[öùº—ä¯ÞH;³õ­MÚÅhœäœIàQ,ÜÔûï´`@ëÅXdòÂÎü_ÁÒ©`§ÜX¶×^íÚÉyøÚPÚ£e°GMä\»½H`©Íù.¤œFîcïàv 0µ¶|Ó6ï4…¦|ä‡ÿ‚Ûd¸  P€ (p!x¯âB(³ P !{á¿ÂsMÞ|ºqh̬-ôÞ¥ûÃÝÚ[›‘¡P[;`Ù£žÿË¥„Q»XÆLÛ…=z–ÌŒ (@ ôšƒ½FËŒ)@$À Á0mm Rü¦€O˜ÖœÕ¢(@ P€a! ^‹ª° (@ P€ (@ „“ƒáÔ¬ (@ P€ (@ P Œ4£Æ`U(@ P€ (@ P€á$À A8µëB P€ (@ P€# ¨1X P€ (@ P€ @8 0hN­ÁºP€ (@ P€ ÂH€Aƒ0j V… (@ P€ (N „Sk°. (@ P€ (@0`Ð ŒƒU¡(@ P€ (@ „“@L8U†ué¾À±cǺŸ s @/ ðsÚËÀÌž (@ P€=$À AA†K6£F —ª° (@ P€ ú­ÀÑ£Gûí¹OŒÃŒ\§(@ P€ (@ PÀ'À ‚+ (@ P€ (@ 40jp (@ P€ (@Ÿƒ> ®P€ (@ P€ (``ÐÀ¨Áu P€ (@ P€ | ø(¸B P€ (@ P€ €Q€A£×)@ P€ (@ P€ð 0hà£à (@ P€ (@ P€F Œ\§(@ P€ (@ PÀ'À ‚+ (@ P€ (@ 40jp (@ P€ (@Ÿ@Œo+Jàl­û‚³ª ¶D¤$Ú‘îpÀž`Pn¤(@ P€ (@%Àá «½Ñ\»Ã0h¶!;¯yÙŸBÑây(¯mö½ç (@ P€ (@ \ö4`mßäjôqÁ–W0;] G˜¼ŒBÜ¢z˜PÙЈ©IV=«•û*q¸Á‹Õ{º{‚/WíaT×5".1©žc¼ÛÌq‰HOM‚Ë)i$O[ráDùžJÀ>YŽ$ÉÇÚC•¨¬vÂ+’d˜Dš#£$Üõ8TY g]ƒ¤I@ª#Cê੟¯&\¡(@ P€ (@ž`Р§EÃ>?·¯†ë W"qq)v$e-ÂëŽ455ÁjÓ/Èë+·á¦yk|é½+Í™KQ¾"G.ñÝØyß,¬¨l›±cvº–¤¶|1æ­©AóÐù(/ËÅö¹³°æ¤÷hý5¹À!Aƒz”ÜwVVî’±¼x²ì¸kË‘ËBH‹cþFlÌ÷÷ØÉ7 (@ P€ (Ð#žÐ#Œ‘“‰Å> ™žêšjJ±dÞ,Ü4íFLȺÏí©’«ÿ$XUç÷!,™« 𑥫Va~f²v¤©lowjëfO§»Ù¬½W¿,±ªtZiÚ?R{ëûe³©6 —,ñ y…Xµ|ÚȈ,ž[Ws-6x¶L,_·Kóô@Aåšy(9ìòåÉ P€ (@ P€èy zÞ4¼s´Ø±bo±\|gÖ³¡E+â–s±¯V† T”¡Ò¤'YùÊ*äLŠü[°Èss¿bËAégÐÎRïÉÀ›LæPX^¼;vÀŠœ8”n’a jÉXu ²15+KWç!99öT ¡ºEz ä-‹Ié)˜š7×X¶UÖzöò… (@ P€ zC€ÃzC5œót»åbߎœë‘SàB­³+ʱ~C´z×`YQ6¦éã š‘´$Õõ@-V8²%ØPYæš¿Iú›õÍê·7‰K˵ s1Õ;ÁÙT×K‰+df§ùOšº%ò£·³D{U¿ŠîŸé ¨cÔâÜR w~ªïX}+S€ (@ P€ @O 0hÐS’‘/åߨÍAÐl+@ùŽÙHJuh?Ùy¹(¼q&Jå<öÆi»º¢WKšÜ2fÀ¢_©»]MÚVS²]Bþ¥ÞÙè3È¿j\sLñŒÛõ<µm.™(QÊwË„‡é#=ÑÙ‘¹h92ãÔ´‰R PHì&«];„¿(@ P€ (@ P w8<¡w\Ã4W ¾‘«/05lÀÂÕåøÿÙ{ਮsßóßµ@B É a¬Ææa‚J¤™:èÌÁrá|1u.œDÆqÆÆñ@|T“€€oˆëSœÄØe›8ä)ƒ«®E*ÀÁæÔ•2g$çð°Aâ¡Æ X/$Ñ’ÍíÝ»{w«»¥Fèñm íÝ{¯ço½¿õ­µ\íÔ<àÜYUŽ O¨Óòç`fn¡öËÆ·ï«Ñ¿´ÅÞ]ž%éiÚ~FD¥ep*áB—‡ö._ƒÜ¹ïÁR‡þ¾rÇ~T+» œÜµ%¯½†-¯•¡Ûžäµ8Û‘Ãå K±ti>ºªãÝwßÅÙîdÑ2ð’! „€B@! „@ì ˆ¦Aì™ig¾„ln"x•¡¬*å¥}ƒ»q%5³±1{x޽ñÛ›F¥.`ðLþoÝ\È{f=E!C´QGaÕß+=«W2¾¿m#¶ÿh·f÷ùHKoÒ¼P.86Ãápà×ß^ûW`ïºØ›–´¦«ú2 î°av¦UÏÄœB@! „€B@ôƒ€hôÚp¶bKÏÅ#¢xQvŸhôfó„nT¨Ž:Tû¬9ð6/÷˜Su¥-¯?<‚"Í OAX¹Å&§ÔI ë×ëZ HWÚ¦K9kº’sÖà³}›yÀ"/›O`°|ão±Ÿ{¨–î8_›6ƒ´\l}÷O(ÈòH0LnÊ£B@! „€B@ÄŽ€­—W윗&+W®àñÇ·„®v´syB;m$''ó¨Å€Q½á’2§m;`§¹àfºÚÛ¡ŒØéNp†cÁïí´¯®ö¹„¢½[ó!d‚»,o…€B@! „€±'õø+öA°ìb]]RRR0fÌØl6ÄÅÅiwõ¬Þ©K=«K‰îß¿¯ýïìì„,OаŒÒ? JPÀÿ‘¢¯™ o(¡ŸÂÃU%´{Q R¨Ö¢|B@! „€B@þå ý%'ö„€B@! „€B@Œp"4á ,ÑB@! „€B@!Ð_"4è/9±'„€B@! „€B`„¡ÁO`‰žB@! „€B@þ¡Aɉ=! „€B@! „€#œ€ FxKô„€B@! „€B@ô—€ úKNì ! „€B@! „€áDh0ÂX¢'„€B@! „€B ¿DhÐ_rbO! „€B@! „À' BƒžÀ=! „€B@! „€ý% Bƒþ’{B@! „€B@! F8±#<~£.zW®\uq– ! „€B@! „Àƒ! š†«¸*„€B@! „€B`ØMƒaŸ„þxôÑGý_È/! „€B@! „€ˆ9ëׯÇÜÍ¡è h ÅT‘0 ! „€B@! „€Dh0A‚ „€B@! „€B`(¡ÁPL “B@! „€B@!@@„C $B@! „€B@! †" ÅT‘0 ! „€B@! „€Dh0A‚ „€B@! „€B`(¡ÁPL “B@! „€B@!@`ìƒa° ÜïEïÝ{°õö¶ÏâŸB@! „€B`hHˆÚáäЉÐ`?tï(0@Ï]Øz@$B@! „€B@!0Ô Èò„¡žB1_ïÝ»1vQœB@! „€B@‘J@„#5eCÄË&+B‘×B@! „€B@!H@„DFéïž.®Z°÷n4·»£0oÙá~ì麅— 7\î~ÙïŸ%7š\ hj¼…úÚÑÞÀð7hLC…¥£ýZ™FÁ.#í¬§_0Wú÷®§«Üo ™¼Ð¿X„¶ÕÄ|Õ‚{h[#ãK¸<7b8¤Ò®ËÍòÝ=° £»‘uë|óÕÓÕ=¨å¾Ãì¹<*7ÛÿÔ˜÷Cªl2fVÚòVUø?T;<0"ÖöPk§­ôyÀ:Sõ‹šúÙ×|˜ý"0O©°Ç²Ì„Ê!1+›Ã¼ ÅGÞÇ–€ bËsXºæ®-ÅÙü5¨©¶VÅ]ûd3œ/Z6ÿ ¡4”¿Ï°¿‚kE%p½ˆ¿í<þà+ꮋ8“û"®½Š«K_Auai¿ý¼vô7¸Pð*ÃÿªÆô«`ipçkT¼‚ËE}ýqW«ø¯ÁÙ\õœƒ:ÀíFí¦WÉý3ø:ônœÿþüíàצdöÎôyˆ>ö¸þÂ4.A}µržvj+¿Fkà‡áð;LžÁhÚÅ6Žn\Xó"Ë÷VÔ=ðò醳²7Úc.-euðªÏQæsùk­ ¸þ§ŸàBáGó,ù¨ ˧öTûSSæ´~£®¿Òì*›–Úr7j6ÿ—UàÿËù?Áµ(Ëñ7þ_²Öþ¯,µœU¾ÿ’æû×I¬6¾ þÝJŸÇ}å¾ÌQë]e_óìs"r˜ûÛ/r×~Ä4Úuú†èƱ7qmy ¢Ì'îDú]Ù çZä6ªÕõ5jÏÖª 8\ˆåÛÃ! !>îÃÚ×”¼h+¾„Ä´¤‡Ær\Ûð9»“·­DoÕ;hÙöjÙøîªY,l­g?‡Ú"qÿN<’æF[WúGâÚvž W#kËbÜuº0ÞÄ¥qÙHÞ¼] ó`ˆ•=ëYLÞ¿½õŸ¡esâ¾?Пígp»°ïy©fRø#pR5Ø;³!ø|ýÀÇ ÕbLÍ ’&ÂÛÓx-/}[Åš`ÁÂP2&Ï ¥`† Ë@Ó.”»ý{Ÿ„¤õE,ß1îAçƒ;WѼn7¼‡©sú—oãØ\ùîñejѼÀOƒö»Wãé° r¯G‰éy_ÜŠ¸`m“×”éA«ëCë½ ¥²i©-o¿„ÛÇz·íg˜^…ÛN7Ò¢,Ç©K_ÇýüÜ9ð:ÎÁr>Öòý@€¡ÚiS’ Þ£µ>Ï·Ÿ–2Hs1©l&t¹Ð>3ª ö¿_¤vÊïÐ껨<ô3Ü€ÆÍ°®ÙG”ùÄÏ ?¢.›!ÝŒÜFµŸ| -»–à‘ªç!ç „9â?H<â“8vT*emíl<“çaêº<¤%ºÝM•¬Fس²ÈYÖëÕNÜ‰¹ó‚4²nÔ=;TÙ“6 ÎɈº"ª«(Ó²ýe8Òù˜õ Ü«\ƒ¶‡psÕÏ1%0xßÂ7•—ô#aÍÉô “Š¿›’ãÖ“§5—ÆL°£—&&͘ÑeÝT‡lÅý®¯ÑÝÌ/íñt…\)ìðïŒ(süB? V`lr_FñÉYpäd¡cÂi´À4èï¡ö«£Ñ‰›5.ܧˆ#%g!2 Øe@IDATú¤_Ka^}sÄÓ°çg„1þSSí×h«¿…ûö$ħe •y'p€9ÜJ°³2ÐN÷ZèÞ²šœ3Ë_˜ÁùšºêK¸ÓäÆûDŒ¥ù4þO DΤÞ*ešl_†´ ßÝdù-…<÷»íÌ¿©HÉbÚ%ûD:J•µÍIý ["î2 ˜Ï™V}ËL¸r âÕJ{é~n«å8MM=HfØÍÃÂðœTÙlE"íÜu]D³³Ì305o–ŸJÚ)Ï8TÚÝR¬éÎdºã'8R†Î §bÜt¶nv~ã2³1mFß|ÜàÉc˜Æ2é¢Å•i;~ó…á1ïJ]¾½;žæ&ú•QÍH¨´ÓÂÍÎ-Ã?Ö=3X÷ø_VóSøúB¹ÙÌå-Ý á¸œg”—ŠÉþy…O;¯1„â¤Lhõ¹¨“qzéoSrÓ8éæ\¢»e¥Üé&ݸ¹ã°è9dª:6àcg÷‘K”jÏ^ [×ߨýõ- Ûƒœ`íëU#_R4—™…IAê³÷­Œc'Ùª¼m'Vi]¯viÎÅeÍÄcÌGæK¥Û=–׌d7Û„óèa;5.'ÓÒ}eÜl>ä³…<§·«`â_éa`ú1«™*ÏqÊ\»×Ï2üä9aμ€º\•á«h£YU?ËY€i¦ºI¹Û›œŠû®ólϳðD^šj/r¨”„É,g^ß´°wP=¬]ñéþŒŒ0u4^¤6Vƒ–ô¼lı ¹uR¨²éq,R½jø®èfÜ—ç,µåŒë¯;Õº“Z^&8}ÚŒHù)5K¯ëæ§£Ã㜿»*KU,K,I¹y“´ubý¾ý1»¾¶Ö&ö´7²¼Ô0?Ak[ST_Å”§”­.´°M¼×ÝÍò;¿~‘Ê“‘û<*÷‚ý¬r]èÇâ6˜`,wáë^ëý¢nÔUžA'Ã<ÖÁ>‘'÷N¾¨åõg¯¢‡æTßa<ã?5H½ª8¸«ks&Ö,P?Ìe¡lZ­Ã”¹ðmÓ®±w\Ì‹ilÓØö2VãY™ÛäѮƸ^U¾¨<^ﺴ9_ÂûÑüZ„£9õ£Œ{Ã'¯¢é_ K©h/ÿ½¿$õÎ%ª“½ ÎŽ7 ¢5m1î{™²çêrâ\ñt›Æ¸-…Ïá‰3Ö†ù÷{ÚáL$˜¿“‹£íð)´Ôº1e†·ûÂßëWÎíòëì´ä.ÃôwŸ÷†Û?þÀíU?ÅmåÄú×ñ½E1 GNW'ãªø#®ü‘=­x¸9+í•Nš ãOÈNšÇºïàf ;ÿž«•~µüf?n‘aW¿³ƒð›zØ6ýÓü?XüÕ€¯^z]UþÆï” àÀ÷ÎR¸9»ªòÝõÂlôóeªζ¦zf[;\•¨.Úí—¾j&Ã^õ‹>@åûS‡h6©_`´'ªz~¸-{}þ¨×mÛw"m©p7øÙt«mÌ'mÚã2Œ£”ÞK­ˆi§B“2b®ŸºK¶ê"ÄÀ:…r+åN¹§®×_Ñé¶/ZNî¼û¾ S×wÔÁ…UinZYî:Êéž¼«8Õìü%n—ÖF´{+ËÔBo™Ò?gÿ(ç†-JKë$ïx¦Oðs(àGCåG¸¶îˆßÛ¦õ1ÿGyw˜n?dº!×›¯zËŒª3ïûž0²¡Ÿ ÁXÉs-'wãæ6ò=y^s©½΢½°m~^­9.Ö+)Ù°9}ája»a´?¬SNç³Î0G•¼öƒïáI­=¤ˆqëTÂjÏõe6<ÕYóöídîо¸kÁµ–š|ž+aÿ{øN€ÖUku.¯)5ŒxÕîm…ÞW~«Wé”…r`%ϙˊ `°¶\Åÿš)þ÷¶¼ §2Œ%ˆ«ú±·GÎOš%íÏ}›o!Ÿï­ç©ýkœ.xÓ›vÞÚÕa6i¥ý1›ÝN[m¯}‡eÍ×wP®·,z){rךõk5­C³Ï­Û^ÇBC;)°] ÚçQõ¥¯ÝΣ‘Ë?é¨Òœ2Úrku¯’ÀöÎ>Uï\Øü":ŽùÞêÅ"Ó÷‚O •ï³¾ð•õQÅ?ͳq !ö—Ž"Lɲ›?Äô9rÙ´Z‡)æÚ(•vKþê)4yòB˜þhL#ıØÖ«Ê–«ÍìÏW°® RÏ ¨}%BƒQ›ôÑG<ëï!ùŸ{Õ³­ ª ¯$´÷؉LØ·³9ƒñíÑ=¸É§±ú9dxp—÷(A*&ÜÙ”F×q_‚›8p.ÊÂ|¯h!bÇg:h¦-'/búÒYšy7gÔu¯[»YüÃNë•À ©e¯ã‰¬$O˜Žàú‡yÞ™ŠÿÄpöðÀ/9hì ê—'$÷à.%ÏQ]ãæá‰“ï±v:+È‚ÖÇ×ÌÕœœ`rÉcn¬½NVìÝæo&c‘{¸ŒC lÅ/ »äLfÇò« [pûŸ?@CÕ¯P$’;æïUYvWÿEŒßÿ6ææèiÞÌYÒq¦¹åpÛâµ¼xûöבÅÙ‰™9Ë~…æmGPwâY¤=í ¿…<×Tñ&0°osXîº]å¨á§ñ¿–#ó¿äûpjfŽWoy&üa'¦8Xvª»1Í›ÙiÛ  Œú‚b3F„f‰3– Áñ1ºþ+Ü…Å¿ŽÚ 6ñœØÜ¼Ø§n#LÚyÂÇbLÙÿ2g_Øi,AÇ®·p©ðfy/b~²V_€¡s|ñ;æxÖ%ôôU¯·–v‘9©¸kõÓ2½N‰ß¹3X¸W!ÌuŠ•r§sÔÿ^? ü\¢c*KÆw-?E¨ëÓCâ¶õH+`=ÊrwƒvìÍÇœ˜^äМºvðMM`·‰åmMž6ÓfÃÓ² ¯<÷$MpQûá&Ö¿õˆcœç)ƒCÿäÀìºä.Á”=x&Pöá›hÛ»5y¿ã X¯ËãfЉª«ã ÓØÚã¨^Å|’k«WE! ¶ç¦<‡oñ&gW}n×äÀƒBЉKõv͈PÜ>UP`Pü²Ö-¨QГe OÂD$m{öÜ@&Û±n K/±éØu {ŸÕøÙTÜšç"½l5ÚÖªò—ɶìÇpS¨ØÓäÔ&嬆ãäjÄíTŸ1P®iª‘ýf¤÷à2…?­Jø´ý S6-Õ«ÖÊ•«Ê\RB*ƤøÞEûtCu@y[ÊÙTònhgDzHM/Qàâ Ú»Ó̇þc¨,¿  †C›‹ü¥‡ËnxveŸœî¯žk9ܽ=ZþŠÛöæ/GŽx,÷>ÞßBç¹K< AÅ9 iP/ì-?MU¶[Z~M¢K*™žz9IEBy3Ÿ¨ôõ^ËÁ´§WÑJ=Z«4«ÍçNiË +©Í㹬rÒËæM` ¬:Š^fSÉòs̿܅ÏsÔ¨ÊX6&PØs‹ùéNò,Œçø>ÅÁOß+ñ5Z¸„&•êÞ3òÞ<®Ê]5Mâ8»iÔ*]2¨–î#ÅtziPy7(äQ×Í“á_T<<í¥çODÕPÄ&ie!OnÙ ÙtW¹<.ð!?Y­/”ƒzýÀ:ƒB£`—•´³Æ Ðê'Ob£êhRˆ:E…#\¹ó†3‚*yĺ^9Ä¥lOåkõs*÷ŸQ‚åûíFMÏÝèwpš›šf³<en2ó€ßR­^UW~M]` V ƒ Œ×)€lâÕ¾>)w*?×4R¶+z“„™?Ú¨­1ï:zI½Ð®^UéSh¸Ð¦¤ µ²8VjîÚïÊ?U·6º=.˜n‘ò%2Ûqˆò¸…[¿%“üÕ˜ozëù™¬æ”<ËÜD sbºŸ™‰˜Y´ŒË-ô¶$)kÆÓmp»ùÚ¥ÅmãjšÉ‚M•)j‹Í àa¬Cÿîû«·IÞvÊ÷A{R3Õª&µïy™U“$ቒ_ ÁÔåµ±lÒd¸zÕj9ˆœç! ³Î†KÛûIÙQ—•>â›DÎj‰‹êc%ó·²gô‹¢©{• ¡/ªÜ¦Ú±Åx”õˆ wRV>ß·˜OæƒË#Tß«†Kñ¸œI-O§`.piMh—w)ˆ' ÷±¿‡/›Ê?«uX¤6J¹¥Ú­ KKÇD-Mú×ïUnÅìŠa½ªÂ4•}þïû벘…pD:$š#2Yl¤Bô ¼žÚ’*žlÚc°y¿˜ª8 [ 6š3]\Ém“i>f`þ±ß¡öègœÕ¥2ßÊ™—|mÛÎø„þ‚þên× ñÕtø®®ÏÄáÓ”ªSدu0}߆ãSçÚWá x÷¨PRÖh.ŸÊò"­±µb70$ÍY‰”b¦ÕŽ=pí`÷"ËLT畃~ÿT ªºÅpŸ2(ÓWmDë¡­è\¯«˜:sçb†—9˜õ̰{mFP/¤ÔýªjßÜrEüφ+nÃsìx<Ó'XÊËÊA|Öß!Áñ>ºöW¢'ÿY4ä¬Çä%HŸÑ7ͬp2—MMð•ËIA–;c@áÅñf?stGMÃù••LvÆ|"³q£Ü©½%Â]SóW Þ¦6Ýd9-âR½,ëÅÔðóG¹!í”î11ÖŒŽkþUÞ¼ßt‹ýóO¨üd„;–õE¸´3ü‹ÄIE/ÒM¹ÓUÉ3ƒ,ÑñùbÎO¡êú: |šöÿ7Ü5–¨Ðº·>¸ãÉ{³³û”{Ÿ/Ê­é ºJÕÛlL,˜å÷Ùøñͪ3ïõÏÍ“Ê`†'¯t6:iÔI›*v–ƒ»ê¤]ÓA¹gË2×DÌ7 [ÔïåFíSfujõa1ìU/ûÏ2ZÈsSÖ,ã å ^FZo•&͝ ^¿Úrgš„jÞÀxZk+©!w]¾ê}ÆÏÜÙ³ÙTLâ1f†‘ÝQ¶½”pÆU]qÆ Zû¥»©=úý‰P6-Ô«Ñ”ƒ°yÎ/\‘„«Ç£ÉOa}êêÐú,6‡9Ï1Ͳ9‚9f²i“a:R;¹Mt£»ž®¥Q@l8âÞÓîDíÁá.=£V5s™AµQC8a鵑^÷öàžÆ8R1ÎäóXîOä%!kç ¸B-£Ö5%ÚòÛ÷—`RÉsÜGËÜ([¡—‚ø»9x¿"×aƒ–˜ûãz5æá¡šÛ‘E‰ÖC%ÀY»ÀKÍPÙ6½Nõî™èîò}·QšéßdÚ ö{"šÏKõo7ŽâúñTnø| Ìï;5mb¾î¨чÈìÄPxîíUñHŤ“ocнÇÔ9¤º|B`Ã9ÄáT–5Ûœ%è-¿ ÷Π©TWìrj_L8ÃW² ëp³²m{¨‚Î5£á[Su¸ÓÏäfaþ§¸‰7¬úíÛNá6…5÷s‰Œ‰õÂiK9`-Xkg)ô8ÄpïýW«z0Þ£þkö6ܳµr„G8Ó~mËq\¯ÍBç153¿ÄO‹"*N¦¨† [¸oÚ †c‡žGgv}—ÒŠñýÒŸügm¿j¿#…‰3±É…ìÿu³Õ nFÆå<}. iUy—Ÿ”çßûQ_ vÚ©ÙàHåNçF•¼xÏ‹ uý î=pSí=°h1&ìY„$÷RQjðnØúä›Z–‰ÄíyèÜR†o—¿„ý1ÅÆþWZÑØ Ìy2“#¹íuÂ'ìö¾2=ô<&aê¡7æ%'i1"·!©Ü(s,Ž “›ì~“|œ¾R“&/„úow =_ ;\Çq™%X¿c=’fe¡ûдUøÌÄìÉÈÿ~å7^õae¡lZ®Wýüë«y®¯ÍX¿ ŸŸ‚ûæo'˜Æ¤UNÛi+m¢X'û œú†Û…ó[p—tû–”3q®24–8ûÕ#ïîõ£îÕ48ØW1_^a¦éeêŒgðݪÅÜ„ö<šNòx齟£ù_]ˆç¾,fÁµ¶„ æ)3¦`}ìS.ƒš./c\¯—h?äp«“rÄû‡E@Û ûA{>n¢&}sg¸•Ú“ñ?r!pT§½©ŽºáÒ‡GØÐY½’¨š©‹;\Ãê»x”âÇõ”D/Ä$ËNŸí‡ö$¬ã©ö¯Ö º«¹{¾‰wðª,ëëûBÆ“³×Ʀe†ºw0³‰Tuœñô ,üô=$p=Á=ÓšÚ˜†Ûãùdîb=“jùß-ÿ™ö¦' nY½*ŠÓóžÂwvüiëÙÑ‹v¶>ŠrñÔ æÏz´¬Rk^¹'A¾Ãe4œºOú6(7ü묢oùBºjõ¢ªè|šuri U›²«ß­º¡›3Ê]׊€W_w28ËMpsMË$5-‚l2e-íêÑv¶Áë›*Öª#eÏÉö¾‹ô`„;õ…•´3ü³Â)RØïáÊ2cE•Üp+ôÝ–w)0à¦`Oî¥VOþBî#Á>³…qY°;(dÜõêÌïŸÕriyxbi17î\Â5ùŸãÚæ¿p ÿ+)= ÓgÌ2ýwøÍʧ>9˜jL6y¤žVVFStÓüüRþ;ÉÁJžËBʦLrx ÜÑö¬è׳ÚÒLd”ïÀÜÂ|<Æ=ƒzjüÅê—‘/»Ï¹|NR8Ðé4´|¯­•MšS¯þ…/òœ/X~êo~ê38Uå€í_/÷ñåÌÜR}`}™0œ´HYm§i8t›ÈIœålœÜ÷&Ì2FUW¨¶>¡ô=Ì_õ O£qPsË”'LÙ瀑¢®{ûô‹’0NÅ­ê —+úÜ¿y²’?‚õDíȘ±Oþ蘳Ÿõµ üEwž¥ ù£þ©ûä\:q5GÕÿ¿ ¦Ò¦ÀöW2wr½;}W_zµµ.ªd^Ä壥¨ Ó0÷­5|„Ë•_ãZu9άÐwžðó%~ÅàvMo“"y¹ê¸¾‰3e•¸Áð\ØÊxœ”°¯îD“«ƒÿØÛ¶å¨åzûSå—ñôóšjj÷ºWqîèiÆÇ-Çù²ªˆƒµÀH*Ë“y^uð‹Ç9®â,pÓ\Ùyœ¯ñÕoi³ñ‹L3nèwþÀq|ÃuÀM<ª©öè5•\›±Á e¸Ýµ_àüQ•¶jíñEÔP³A]cüf­ õ•a:ìÜ8í ó*ó­Z3}ª×·”:´ƒG8j.úþØ´\ØÊMðʵ¦•ÞÊä\jì`sìÜã€ËvŒî©9Ïjå´‹­]`Þ½AUþ3+õú)9Êr`Á;¯+ynZÁ?zÍ'- ¢IãýúaÂìYüXæC_ÀYùξ¢ Dü6Ó£‰˜Ì8&s™Ä" z·mÕÊSÝÙãø[Aà 5*¬Vʦ…zÕR9°’çT˜¢¸gµMV£ÎOjoŽ&ždSYÉ~ËE“€€G‘þ”í#—«]2êguº“žq6Ü+€§ +íOävš‚A mböªS`ÍÓ%Š^Åy.ßQuý7•Çq¡ÜW׫¼ººöŒÚêÓ8¿õ'hÞ¡&`¼…\ûÍM›-˜…þÔ½!úES ž¥WѰé}Ö•ìxöKQÞšÛ¬ºŸ±mcÜÙÞ«ú¢vÏç4ÁåJÊ çò.Ù|I‘an8ßÇPP¥úuW™n°ÿpþCnÐÐNÇ2~MÜ´ºàEœ;x1¤³±¬Wk?ü .ä¯Áž¼&Whæ|Ú”|Ñl˜¤ÅïÞÞ9ûoº¨‚ÜqÈb2ˆ4–ú…’B™gOÿw¶¿ÏM ?çì)Gôxißù—‡ŒšÂe‚ªÄ‹«–/ì…k¯îÊ„OVòAOÈhÊšio;| ܘ­oɳÆé>}[¼„‚ƒ}¸é ÚPÎoy†)²¡ò\|ÖSxâ`œTƒ¾½n«~쨲·<ÜÕ?|ú†¨&gýg”ì¤ÀeZÈ´ñÏjï¹È~å8Kœ´Vš}ëKi§œV*´Ü廫äMÜмâ).ŸlôS3õ„ ì-šú"¬Có¸eNT9NYŸÉ]®õ=7”ßc·sdçQä·Rîz¿ðînÔ }â`±®Ÿ²fn•²N*yK«—â6q7~®}î1 Ä&罌î}©¨_Wæ]7¬ü‹ÛNá²g™Y|Š®ªotX¦þwÏpSÄÒݨÎóyR„µ+ s¾Žó/½É¥Q[=BSÕnüÌô H¸OIf×LBÍ&ú¾¶˜çâ³rY3¼n¶»Á4iú:Ü÷Íäîÿ‘{ŠB£}ú~#…˘FMž žòc2nKÖK©RDŠQÁ¯$ÌÞñ:….lWXžnÒP\ñ2ظ7Ã=“›–ʦÅzÕJ9°’çŒøXiËíÉƃܭä'“5›ªûϳÞT_™°›ŽÍVô:Ü•¯à¶Q?sIOÂò tÕs:à Kœ¬µÓVÚD$çavÙF\^¿[ÛèšnÆÙÁzXËEÉs‘ZœÉS3Ž …i¯„°‰Ûæ±/åÄ=UÎ K¼[êóÄkÚlçLöLÑÔ½áúE‰3V c;…ì‡6)A@6÷UZÀ²ÓêãM!›KÕQ|·½aÈÄ„¿ðž^¤^³ï}þ ~ÊŒ×ÚƒxY6ûQ‡)V-‰G­äFÇ»q‡ûBé}䩸)áp7ÁR»Gwu£¹± \î`Åfd–*í:´zïé»Þº…[w§‡ëïÛµåñÜ0³ïþdÓI.i¸øÂÓ/´#}a |j¥»÷è®Ú :x÷;å@÷Õ®€ô¥ºêßþþMŒ;¸sgPѧ› ò˜Hëë½c¥ÿÖãÆüÄôPyaÚ4‡Iu¦öW+_D×ì³Ã8Ü&+i§Ž«¦2£ê÷˜®üc½¡ÕæN­ÏIKO‘ó¸îÌõ?ýßþaž8ö|!¬:ºÓBÙŒÈI÷Oñ¸ÃÜ,Ÿ«Í–;úÑD6)éCäWKhL†Xo¨üƘO[V”9}ŒHõÉñ~?ywëzAW¿ìc1ª<ç)£c¶ïÄÂ¥}c}óBÅí.SÏJ]Æ‹Ÿôv!Ž»Þ§&ô•l[)›†G¾2¡^X¬æ9Ãçßc•ŸZy"Ç=æHó) ¡ ÏIO+ít¤6Ñì¯×O»:í)D:³s1žõƃ*Oá‰E_ÍÈŸãƒô ÿ:˜ßîxÚ§`í¸–f1hO ÿ†î]¯£ãBäX‡[•©ø õóƒªWÕé}ú¡ Á{mq½~ý:üñÀ×Còw]]RRR0fÌØl6ÄÅÅiwõ¬Þ©K=«K‰îß¿¯ýïìì4 Ô´ÏòG &ulÌ@ý³G´Eãƒ:~&iÀaŠÆÇÁ5«Å¯ß^Ú¡ŽE´zEfi=ín=ÄÚ‘¡aˆ‰A§Pq&<©t7ü‹r û)\÷µŽòÏz:† {丅³múƽ6‚ 0:¿F½“BÁ³ÔØprÄ‹ƒd£I;MjÂ?Sˆ,=†Ëã.njwΤÌ4ïæ 97ØzwŸ7‘ÒN3i1ÜáyD(wô#¶§Å°Þ°”ß”¹¾ƒ¡Ø>Å,ïZ –’Ô†H»¦êJžûNí«=Jµ.Ò(0PÁÔ¸Q?"\»>/úóT”•,}®YÍs>}ŠóT e%<§ðéaŽg¤6Ñl6¼ŸTØWíç ö´ðÄÀ?+ùS핦ï)Íz(¨ÕN2 xާ ¦G;:àƒé§2cmk²ÓÇàutG„ps†‚á&ƒ°a±÷a±LŸ^í#0ÞÑùÑ«õ4:£?úbÝKá‘MtKF_ÂKŒ…À0$Ðòå!´ª%1¼Æ†YJa)jšÀĒɘj;ûtðä}îœË ~¾xPfàbxq$6"æ9žOaRVšÚR™Øø,®!0Pƒè,µü"^)4á·>8ˆâ×ñ½’yA><¼WרE× „âa¯éüê]ÜÜ$—Lªøƒ÷xÜà†—¿_U¬,X-V‚l5í®}²™õÜ‹¨`Øõúæ'´¾±ïXšQåèBáGë¥Xú)nE&PW¶ò_ÑÚðkKÙ†þ µ®`þ|µ~ œlëöþ\@à®=Ž¿yúÊÌ…•ïàF`›ÙåĹÿ´W=î\f»z) ÝSíÀ債¸P;°: rìÅ„B`t¡ÁèNÿAý·Ÿ–Ò¿¹˜Tö;L?ø:¦Í â’ÖÁ¾~1Æ%ùü_õª0µG „:‹¶÷"ë÷w7š~¸ÍG¯öÛÃbOãq´¼ôZ;k†Ï=1=ã‹#É‘ðåáýü0±#\ô&î¥-ÆôU³‚F\ËA¿DórðÓ㲑¼y 6̃=š 3›ìåÐ~gµDŽ…õ´KÉ[„â%HLh™ }öuäðMz9ŠçÉær %ç/Ãøí‘Uö6Ò÷­†­éeúfmåÖ´9æÎçCˆ[Gí\Xõ‘ß×VjAt”ÿÓÁi;UÑ 8³ì1åõÕa¶Æå›_D;IÆ¥‡)ÓøiùÞáªD5gQü…š {Õ/¼BwÍǸºösÝÍÒ}¸Vª?61þßñ²äLÏúµZ‡Ëìyë¶×±°hžç;š…¾ti[õSp¬ÇkÆU=ïH»kÁeøÇ¯ ûéOŽ‘fšþáŒÐÎ_âvi½ñB»·n߉…K³üÞ…þÑÆm%¸]µ¶ªŸcŠÇ`SŸ¶¾ã¾‡ÉªsÉËR˜”Zk±>Kåq -…Ïá‰ÏjyªÃÅY­UkyÐÇMs]χk~†Œ’…†U(õý¦ßÔ#nÇv<¡6!®¤9ÙhÈouÃC1}Á2|U•aãZÿ:¾÷##/Èõ…a6 ãòn hå—¥ÕWã²ó¤ò½o¦Þ¶1_¦yó¥µr`¥¾°V69þäU4ý‹ ‰ºRÑ^þ{8Œ:G p&ö\ñVm–Õx¥îe0; H%eÍä’Ý0[ŒÅ³…0Yád©¾è¥˜Ôù.¾ôÿà^•¯Ftïyóó}y«îÄ;¸YâkÇZ{ãÑrðw˜ýx°ü‚Ûù\kŽø>öîÃM›.ˆQšq`ý;M}er:‹·=P¯T›ÐκéIOÝÔvâWhÞÝÊvòwl'•à€uñ¶9ˆNŤ“¿×ÃÊ¢…ËKΖ »Éç`ƒz$TüÁ+hÄ鯉=p•T“D†Íä»sÞ]ám|>ÝBÛ¿ª:Æ›òÞªã|G¡¡G`à³à}º}A-Mœ‹7õ!3ï)4Ú>F‡Z~À¶¬µ^ïSŒg}j\IŽE`—¡§†±4ú!ÚÇ Ì ¶Bõš2¸jW Õä®aWîB@!00"4¿aiÛÍ5€šÀ`Q²v¬¤ö€Ê5 ½+Õ;ËØzöcM``[¿ÿ(©ì0}µa ºÖýµ¦ÎšM¿GA¾˜—…oîÁM #«ŸC†6ˆM‚ã‹÷hªε ` ²ösÆšoýfr•¹ßá®=ÍJÐRÚW5UÍ`(ÁØío`g0ŒŽÎx˜çzÌÖ’$1ý1$n[´‚<ÎLÙq£ò#¸ÖAó1'¦94Gj÷½¥u<‚’åïÀµÔ4ŸjP«q›~†Yk¢×UŽš¢½ŸùpOM'Þ§½lL>ùšÖµ (]1y‡¤œç0çä³:ËEëðƇ8~Ëäà-¾Ë…š d¿íóð¨ÖÁKÂŒŠ÷àf:_[÷9R¸d$“³;]ÌæîvRÎj8N®F\ûy\¡@#˜îúµƒojƒ¸MáX“§ ˜ÕŒ[oZÀè&\äéð” Ëp{-Ó€k¼§héÉøÿIud—`Š©h%L—÷(A*&ÜÙœ¬+77ü‘ä<,à #1s¦6‹Ù7>­è¥€Ë–åßynþªBKÓ„À[@œ&缌{»†‘¡˜¾7Oœ|cí,ŸÚu›:ÿF¸­Ô†Yu¤äm¤Ø};ó·Ø?OÔT¥‚Í.S r‹BOËæ|–M•*ªž»ë6kåÀJ}a­lY?xÉÿÌz³Z•½J‚/ "7è£^§`®ÁÕŠÄ`E*y!¦—ÍÃ]b-LV8Yª/lJ|÷ìl£NcªB@Ø»w‡;¿X«£zË5­ød—<ƒÉž6êö?€†ª ¡µq³ÍQWÜöבnÿœnVhyæGëŒÓÐ6ÚTå"a"’¶={î? “Bªn y/±NìØu {ŸÕ»ŽìDç/¢sÝ[¸VµIû´Y÷„};¢¨ðXaÙpâ#M`0á“÷((‰Ç¥/¢­4é_ìÂcž²l…S|ú¶‡³¨ÑâШ0@mWl÷Ô•ú 6 ¸tèÚ÷ŸB/…ã÷ýØ+äÕŒÜéæ- '?Ù2Ö›ÔÚJX¹Oä9<ý 7:SÜ’»(˜?³–í,',fo˜«ÕɽÔl£NZ5ÁB6Ƨ5P0ÿKt4-Ä”=Ïa|.pûÂ-m#esM›4{1ËÒúˉŽ}ŸFðå.„€ý#0¦ÖÄÖp&pcƒ?Sö²3æQéO¢ZòT“fãA5ó²*Šl‚ßÙþ2Z9ÐÔæ2Ô[ª„²O¡Íªè3~PÅßx¯=ðO´Iì8Äi£ßD$ów*ÿ›|e6)y"ÕÙ©†lÀïÝíß*c°ÏÖ×F¦9‹Ié=ÚîÝãµ*à.ÝÖRfœ®†ß}Ž×öJü-—›÷}ø5-é;‚Ûsõ<¦„Ôjõª¿Y2}ÒÌpE݇Zú2õ™¾I ÌS!FVê s U]mÚ›íGû<™å 0ÿ›Ýè-?z×--ÝUØ—ÌX*‘ê ÍCke3^+³FÙ3‡Tîá`´£J b·³îÑëU•ç3X¦‚ÇS}›Ø§>íërÿßXSDNë‹^Uÿ-†Ck£øÈög|±øoœTí0n)5‚\\ªÕÎ:¬H•Û ´ÝœO3â5–ÎÃ#Z›Âç• ñH¦Öò™„:1³h£À@µ]IYœa/ä—°¥ð\I˜»ëgü^ECáOख”Þ†˜–+F#Ý#²ô8@­‚qšf—KhB%;4™‹ç³Ni9O±=\†ÙKŸ ø¿³Ÿž§µ#FpÝ® ´QP¢ê²ùÕónÜÖ´Xwoù˜Ë¼q¿ê :Ömy#D›Ö`›Î%w=è-ýŒé×£µk=Õ º»Ú_6Åí è<Æzºên9©§ªà¿^êÛÂÚ2Ëo‡+ÙK‘K! bM@4 bMtÈ»çFw=ɵšB†•fÎñ#;P~CÅäL­Õãd£nRµ%û†ÿñii³!À‡$Gý/ãÌÎQ»aîìW‚L®KÞ•ŽäU]å4íÿo¸[áëbp’Y¿º:4ˆÍáGc³)¨8fêÁ=Õqr¤bœñŠ÷±\7ÝŸkúªh=´ëß„“8sçb†—1;'úYÛžv'j~ wéoçN± œÕ œWŠ:Üwô39þ¤¢v‰¸GÅÆTª÷–¡®$öŠ¿hj¶)ùŽþ8ÆN&gr >ö·KµV=Ήˆ£³=ö[øæä!ýÝÞCøfé ͼÚwÃ|Ù´ý7®B“åøu’ͦÂ?Ëô¥%r}>Þí+µ(Ù¾˜šOGÐXÄÿ\Åmx޾gú5¬”ƒ°õE?"ÊÏnî£.µŸÉP¹¢ SXNVë E{¬GÍEÍžÍ%_Þº×G¦sí«Z}é{Ãrͨ>º4¿ ÿlãº|cð¯êIãÙl«µ¶×?<„®?SPj\¨úu¤¨ù‘ýî8_RËÈazIža2ê{X–t-‘û<Àv-¾€-hÙÏPS@LhŽÓµ²_¡aë%µ6ZwgÉýß0‡ËºŒV6iÆ |¯j:Ú/¢vÓVt¬ý%œÞ%6I_‰Îª¤r³Ä'4!+—¯m}·w½k+wiËÿ4öô…ëàþ‹VP€Ï¥%ô2žÂnuåCí³“ôýTÜn^ˆ4G Ú”<‰s¡Û›¾ZŠšƒòG! DÀ¯­KbyøPÝä€ŽŽ•Ð÷öhé°Fiæ] ³0™ƒÊ†ÝŸ£¥BuÈ21aÿÏ=êöÑùzãèo8˜8Ã>ÝbLسIvX¸¶»oè}åCÕÅÒ3kb„À+x0^X½3~ó?=Àø¸¡`ÕçhßvŠêú¯¢æà~ªØŒbúéÂù‚-¸ËA’}Ë \Ò0q®24–8ÃÚÈGÿÙ¦þ»4íéU¼[g/"î ×´æ?‡)Ì«Ñ^Jƶéuª¥ÏDw—/UmœåÕ;›¼/gç–ÙÆ¦zŒÝ\ŒÞ¥hÜÀŽ3óUR€ ªW›ùÌDB4Éèáš¾ý­/ãÿ~O[ú2¦¬Æµ³ÜLïÐÇèÚû®Võ`¼GÜj¬×V]´`n yÍ‚óý2!LV9õ«¾P‚Ó¥—IµWÀÛ˜ÂÍÿŒA¦Ú´7i@Õä‰é±Ãu—¹ã¿Ò8¿c=’fe¡ûдU˜ yÛjU=¢.ndÉ%f”5G{Ya™4ƒ'ž8˜¯wyN0È]„ÉÛ—ù ¨­pJt,Æø7"γTÃV.}»oÏ:˜< ŽMj#p;ÝÚ>Ê^œÖd&žfd æ§ãöaÃUþþO©èÜ]‰ÛÉÏbÁ¡?hz\ÑÒЦi¿QêÉ…Üûè—%LÄìßü^·L À›U¬Û 5mÃÅÀ;Er ! „@Œ ôÅØ}qnÈPƒ%΄; .¤ 'UgWrVÇY‰ξWëÅJ­Q·çd¯õÞãúœî¤Ïµ§OV@NÕ®ð³ðܵºÕ~_`Ýhy—GžÜË™üü…Ü›Z>ÔyÍ‚}2g;¸_»Cž«·>®×.ú nú¦XRý²ÙäÏÍ“•üzÄp-Ô}rÖ,ªÁ¾Œï–ÿL3ÒãÙ=м¯“ìÿÅ]}\[†Púæ¯z3æ8pŸ'.½àYƤÍE{P[ëµÊÏpe­¾ÏAzŽ¡¨]d5ÑÙò™6æ’îí| ¸ý,ÿ©þþ µ}s Ü8ëE\Îß„k¦½îû‹8xà¹Ðµ'NóÿGø²@ßôcLÏÅ#JáUÜ/}øêjOãÜK«¾ººöŒZWv~ëO´Nl0u]›¦tÚÊÍ'Ëé/ÏÎ>øQ–‡ƒÞ&"sób~9ƒ›…¿Áåjó—Fý —Õ.ØQ_Ixd½ÚA] 0QÛ\ÿeýo2wÒæÏqõ¥w´üÛÀ¸]>Zê·É³ulÅ«1Ž?Zø¿è^.ËáÛÔÙúž'Dë¡1LÏô¥HÅB}aÄÑÚ›ê­_Ë2¼ µíÖlôÏ”›ù™iÎ2ÒÄãE¯q9Ò­½8x”i€ƒáË•úÂp0|Ù4L…½S½}b1‡=‡?@õ/KY¯4ðhØó¨ùä/~çÓ‡u#àcGíg¸¿;à€ ÿÓR˜¬pŠ]}‘ñôóZ:v¯{玞fýÅcÏ–ã|Y•·. ©è¾N˜=‹Ø&ú‚òô/p¦ðªáó•YÓŒ'L8¹ƒ?­Æ¬U+¸¿Íj­ú&jîVXúÂ{ý«¾öPµ‰ë? O»;N<½èÃ=øŠBño¸ÇËå2ž¸ ´õËiÚ¨6‘Ú‰‹ØFn)ÁWLgùGhàÉ3jãXÆ´žŽÄàw®ù%.U;ñ OsjÜE3…«}šeéy˜PHôŽ7ñÕ‰¯qƒÂp½²éAÚ·ë¼ÖæŽ/`»-—B@Äœ@`ß)戃C@rf—mÄåõ»µõó׌ r3»Žü,mV!1ëdïïÀ7kyŠÂª ÝDÚL>°Á·SrBb_u}[ÁºñóÙI8gx殎ÛJîûÝØñ¾×–ûzÎÆ%pàÏŽb×íß:ÊÁÜôÁr_›}ßLY³·J÷¢«ä-ž N?à Ü G­—ô\ÓŠ^‡»’k1÷ò®½|É¥ Ë+ÐUÏÍ=f¹¾3c;/[>çºiµd" ›P]”§1b˜Zþ{ ÿÀÒXñÔõB…ɺÁzbÐ6E‹×Ž¥49¢Á-|µòj›,BVùL Â<’›Ã;}­Ô‘èßoû\›)U¿/²æ\XSÞ:Ž¥ 5L9WŽ„/Öê -0ÊfØ›?zÜQÃÙˆõÙ^à3ׂŸÎÛ‚ûÛÞÀw‹f~îwÄ0Yå»ú—ÆñÚi ÑE(:Ó*Üez¤±~~ðW8–ÔØyéEjU¬ÆœwM‚°ÆSøré;HØÿ¾“ãÆXpê`úßa²Ïà£b°J!«à}Ư‘ÇܲާæNȶŒòÑ–j²A.! „@ \¿~?þx ]|pNÕÕÕ!%%cÆŒÍfC\\œvWÏêºÔ³º”ˆàþýûÚÿÎÎΨ'C5GäÏÈ! ŽÙóïVÆGî…=B.Ðüƒû=ngR8«Òvò4êx¢Àý¦‹hݳO›å™ðT•D®sv`g§ @¹þJµ`&‘C "¯%¶sM®í8¶°a6…5Bü´0ÑØ0¹à}Leø~)ž}4=Îhg‡ñ ž«Ó•SGF†ñ8ªOñoàLþVÔ+ÂÔ"GT¶•áá¾uðÌõFnhë;ï ˜ÚÞ1ƒIe< ™ö‡µäFíeþ«¯™œ^ªºŽçyãÆ&YAŒÈ+! „@T†bÝû ÂÔÓ1”ØàÁilE…~ȶÙÇ¢×£v;d)B`Ø-Bƒh÷jv )™ƒî¿0ô⚘œ…Çæ ½p ^ˆì˜ÊMºäB@ &¡X÷>¨0‰° |ÎRcâE`ž’|B@„' Bƒð|FÞWÙh䥩ÄH! „€J@mé%;~E#/…€– èÛ$Z6.…€B@! „€B@ÑB@„£%¥%žB@! „€B@! ¢$ Bƒ(‰q! „€B@! „€£…€ FKJK<…€B@! „€B@DI@„QãB@! „€B@! F Œ–”–x ! „€B@! „€ˆ’€ ¢&Æ…€B@! „€B@ŒcGKDGK<¯_¿>Z¢*ñB@! „€B@L@„ð`;ÿøã¶—âŸB@! „€B`Ô¸råʨˆ³,OÉ,‘B@! „€B@!=DÏLl! „€B@! „€Dh0*’Y")„€B@! „€B z"4ˆž™ØB@! „€B@!0*ˆÐ`T$³DR! „€B@! „@ôDh=3±!„€B@! „€B`T¡Á¨Hf‰¤B@! „€B@è ˆÐ zfbC! „€B@! „À¨ BƒQ‘ÌI! „€B@! „€Ñ¡AôÌĆB@! „€B@QA@„£"™%’B@! „€B@! ¢'06z+bcxhÇÙª³èFBhØ‘9{6²’ƒ} bÜôª«Ý‰s5MÚ›ÌÙߣ6ÓWyB@! „€B@áH@„Ã1Õæ.'¶­û)®†qcù¯bÛRGþŸ«öcÙº½Þ—ÅžÀ¦œdïoyB@! „€B@áI@–' Ïtë¨{íÈêõXïMCvv¶þ?Íçäá-ëpÒeò½õTuÐd£xýV¬tLeTÞ ! „€B@! „À0" šÃ(±bÔÜ-û°o¥ÃëìÙC𣀭 ®öÛ|¯k ¨¥ågájjǸ´,|/Ú†.8««Pñ¥îDÚòµX^‹LÏÒ„Ðöh¾«Õç®â¶= ós2Q_~åN `åRdquD8»ÆR{Z&ºQÎ4µßAº#OåÏñ_xÑîByù—p6µ!™awÌ™Gº`ÏßvgÊ«œhêêBZÖä/Ê…y…F»«UgkÐÄøÓ8fç wN–ŸòC! „€B@!0 ˆÐ`$¦j?ãäà  ÐÀt¹NRÓýqã¾#X3¿«~ðSï·¦Ãoà‡}ÿýÿCfÅîÐörÓÑå<†¬Ûíµ«?d#ge!p2¼]gÙ&¬Û|Eoá¯Q¾}©&8h¯:€§ûø¤ïÄg› h¦ å»Öà§¥þnõbÞ=ñ[äRøá,Û‚Uo §Ù>Ÿä…B@! „€B`Äå #&)£HmU9ªªª´ÿåGaËK{5GÔ 9ŸK zëNzþi…ëñÛ}»Pœ«¯cؽî5TÃ};·b‘g%ƒ2³ó·ûÝôEx{]´@uóÅ¥i“çÀ^ÙnÂ8ß,oörlýõäzÂ`;ö)j”ûwªQò’!”ÈÅæ;±¹X E€¦Ò”ži‡ëä^¯À pý¯±ï·z\lœ¬+ùí½.ì÷ Ò ·â³G°o …7¢Y¡Y’?B@! „€B@aF@4 †Y‚Å2¸ÍÇvcyݦ»¾fßv8l¨þ«GÀúÍ/-GNZ/ý¥Úì=… N`MA!êóÞ@EàÈ-@A¾Õ‡Öé…´w›îûb’F퀃ÔP‹!¬ØÍ÷ZÍÆ»û·j…Ž;øûèB;¿7~yUžøl=ø[) fÃY³倽§ Ý㉊ñÒÊ|(qÈÚM‡Q±›‘©:g{.ê•0‚î4UƱ“iXô½bß¾b¤¥%î¯Þð†F„€B@! „€#€ FZŠFŸÞìB¬É×5Úå8\qU³]ún^Ú· 65üæÅ=JV-ÓŸMU¹°fŽÃ÷†{¨ËŠ=ŽÑ½×K/xvO°f7œnµ7­³£4n75i†zAa‡WB‘…Mûa“ö¥ ‡ÕÖZ”K±êiC€ »M©ÎÖSX²}9V½Æ5MUØûÿ{>n܇­k†a¹ ! „€B@! F$ŒÈdµ©¼5/a“w#ÄM(ܵRWׯ:„švΦ÷v{Úøëœ‰WBþëÒž’ºÀÁkÈxˆÊ^.fgz„ʾ»_éÙœí7ü ¼÷´{ÞPx -‡ÐÕ®VÄ…Æ.$;r´8(C½(ÄöOÑ(áƒGøáH³sãÄm8‘³5558y² ¥‡+4wí^‡ÙóO`íQ7ЀÈ! „€B@! F$ÙÓ`D&«ÅHydžéÜå+=WQíº LPSñêÊ…#ç),]ºKó3Qõé§xw÷§èN1`ŽÒžßÀ?»º @£ÿßÌï,Ò^¨ý Þ=¦kPt¹Žb庼öÚk8\ßdǶìÙÈ)`Ü¿üLj ¼û.Þýô’“Ø›‡§W¬‚sêSØ´m*¹A¢!*qÖ‚ ¿å—B@! „€B`¤Mƒ‘’’QÄç?h‰³ìÆÕËYö—°üµ£àîøéŠÿ“³³Ñ|U€ƒûÌVg#z´ kênÉžÓlÃ÷lÅnèðûÜI˜S„Ù»¡Y8öÆ*TíM㑉ú’%ùá¢løk`Åk\¢°+òö";; W¯zÌälâÑÉHóÈ%vü`NQq¶ Mj“^¹s ñÏ_yB@! „€B@Œ$¢i0’RÓJ\l Þ0×ø] Yï)_9¹—A6¶ø“÷ÄC`–[Œ}GÖ#ËcÛ˜±÷ºgÑž²Þ¸À »H÷™ ²OPMî%cÍϰyy¶öÞ¨Ó~{p'æp£Ç„¬¥8ñ§_c‘gìo r‹·áȶ|ÚËÂÖ#ûôøs_‡ŠÃÜ$ñ*Üàqý΃ú抚ëòG! „€B@!02 Øzy̨¾X]¹r?þø‰xWW;ºµ)~;Õö¤ a|ì¯=åä@ìú‰aoö®vÆOY²'#hô¸”£]°3þ›.úù%?„€B@! „€äø+Öëêê’’‚1cÆÀf³!..N»«gõN]êY]JDpÿþ}ígg'dy‚†EþD"@UýþŒ•ûkO…g výâð˜ %p†°¢F^„&`ò(„€B@! „À¨ ËFE2K$…€B@! „€B@DO@„Ñ3B@! „€B@! FŒŠd–H ! „€B@! „€ˆž€ ¢g&6„€B@! „€B@Œ "4É,‘B@! „€B@!=DÏLl! „€B@! „€Dh0*’Y")„€B@! „€B z"4ˆž™ØB@! „€B@!0*ˆÐ`T$³DR! „€B@! „@ôDh=3±!„€B@! „€B`T¡Á¨Hf‰¤B@! „€B@è ŒÞŠØÊ®\¹2”ƒ'aB@! „€B@aD@4 †QbIP…€B@! „€B@ &Ñ4LÚƒà×£>:¾ˆB@! „€B@ÑMàúõ룀hŒŠd–H ! „€B@! „€ˆž€ ¢g&6„€B@! „€B@Œ "4É,‘B@! „€B@!=DÏLl! „€B@! „€Dh0*’Y")„€B@! „€B z"4ˆž™ØB@! „€B@!0*ˆÐ`T$³DR! „€B@! „@ôDh=3±!„€B@! „€B`T¡Á¨Hf‰¤B@! „€B@è ˆÐ zfbC! „€B@! „À¨ BƒQ‘ÌI! „€B@! „€Ñ¡AôÌF´ûmNTÿÛ9ÜB±¬ÿ Tÿ»÷Ä©þß+P{¥=Œ ù™@;.ÿÛ—¨o oÒJz„waà_o_ùÕÿQ6O Ü—»0˜ùr0ý²BF«KŽW„ÍOC±¾±7³™æ:'®¯A]]=ºÍ¢|¾]§Ü^‡u4Ô£Ž~\»RÛ]wáV[{Ÿÿc·¯œÃyÖé·­Åàw¸4Ã뛌Scˆú&V\ ¿ä.„€B@ôŸ€ úÏnDÚ¼Sw ß¾ø¿£æ?‚w^?Ò]¸¹þÿÄ·ÏÿF'¸oÚQÿüÆõü4fÂŽûu(öÿF]Ÿz_ßGÌ›®«p½ø .}|.L”¬¤Gë¨Ç—Ï¿‚s—£Û…‹Ûÿ3þýÏN¯Ã-Ç_Á·«ÿ W£qÆk;òCÒ¿¯ØçËÐ!L¿B‡ÂüåNÝ_ñ-Ën}a^ì뛾yÅ&«Ï}Ó2ˆÍ.'*Ÿý;|µd5œÿô<®,ù'ü¿ÿÓ¯PbÄÓ«zœ[B7þ|ÕôN=váüöÿUÿðO¸B?œÿøOøÛÂÄ™ó¾úùÖ¿ÿgÿÇï÷ùÿµ§¿õo¿Bã‹ÿZ@Y Æ¡âã‹Þýºã¨aœ¾ Ì1åêóOž„€B@þÛ«bs$7m1R>Iö!½13(É -áJÆ”÷~‰¦¶lÄ*ÔwÿöÞºª"Ýûþ'Ä$b˜“ ahP \&p)¡_×Õø-¦ï"ôµÁë Díå6^„´Ch®¯`߹ˠßYK‚ÝYPˆ´M8Q’H'‡!‡éÄ|ÿª½÷9ûœœL$ÚžRΞªjWýjÈ®§žzÊ[Ž‹G .û™ì6-$G"®Ïz{Æ4mÓåÑXp/ÎíÞƒð°ÆüÔv~G¼=ý÷£îþ¢¢Ï Ê«MÏ.¥üë‡iûzÙp&Îw5œŠ€'£5†›Ç€gæ…ÕßDõl«– וPïmê^ý²¬â›ìGpþ(Ðaæ¤d ‚{ãáïY›Q:zþq|³ú¢Å.œ,ý*–-Á|E‡È@'v¼Êµ$Œ˜gÖL„oAñ¯_Áé~凟G<Ü-þ¨»±ÿ3©ë`iduI6ZG˜Cµçü$ì}elði*?Jrœé=uh*2ßÐÂFXë·-¸ZqÉQ! „€h?ÉwDÛ$Mbù)œ ªêIo ’ÄÛá”îþµ=û¢g¬ÝFC·žþ/¹ê ŠvÄgê‘Ð7Þž‚.*q^7Ž~Y‚Ný‡¡G´‘ÚsTÕ-/=ƒëúB¬yO©©–—q#RÐ5T¦OÑŽ½8[ÉÙ³èîèvË0ôî4Œ«ƒ‡q»þú7ÔpÆìš¤_ q„Ìø®eÚ»q°k¯Ð'¨Â^¶Ÿ*ìôï¸yúä8Çt•ìû?þx ®‰‰aú†Ù‡R.Ù±1{pò˯P‹Ä7Øx…ÊDà=Gm\<:U•ãè×ntIŠGõÑ=8YÌ…ÌcÌ?q`§{pŒìë˜þ€ª0næ£ÛŽÁ±åhÑy>o /Ãn'ÏûÌú·q Žm¤ÒÉÆ|ü¸û¸È¤†á“8ni¸<×b€M¦_Οïß}®ÅÖ¥>º1 Î/_€›ªàõËŸK!–ÝÇY_#ýV@·Ðíþ< „Ñõ#Šõ§‹R»¦*ûyÛ Q±)›ùëåØFe1Õ¥Ÿ³­ý'0ó ôXÈòÕ΋oâ ³ønt9¼ÇU°ÞÕh†²eÁý©¿¨-ÝÁ>€m™uá:¶ÓXž;´™uó8Ø–ZÆËqüã?àHF`Rù¹Že}3ûËy80¶xª{¾º?"Ø÷¨þ¦ó»[;$ jY@Á]Oà¢XûOĉ­f96ÂðæûJBÖ•v„ŽW©ü·´,ƒÚ¿÷ŒžÑ›2Ì êxˆ»ÿ. ¶âd¥ûÞCYæf”ø¤®½‡mÛƒ}SX·Ø†£˜_JLpóÖ|ÃF§{ƒÚEk¨>DoIã)èÓ/Ð?=FŒcþ˜åS$;páÓ:ÔÝæ¦-š-8Èðž)èu·%TATZ€2ökvw’u÷j2ôSw›î3K?|Åó?¶G¿\5•zv ÂÐÝÌ;5ÎrYÅ‘_Ö4ƒkõÛqlm"¢²Áy÷ teû'„€B@ü´'O~ÚwIìÿp8çs€Špïæ§éÊß5f³âÕ ßR7UϪöàk%0Hš‚¤Ýù¸ýë|ôÊšT¾ÇAùWü8‰Îjt±vid«g?4“Þ­¦áBÎ,©Yâ°ßNÑó•æß)†À ó[ïbä׺õp0ÎÌô¯{7­‹uààj0â·ÉÆLœŽ¾oÞu鸴ÊÍ%¨|hÐùHY«8u§a Fn 0è7þß—/fŽâ‡ÿCðìˆÇukßEoõ1kòT³…±nÁ­|×àÍK ´óÏ2}ß;øá>“ƒ·£oáû +NŽ¿¾ pæF¾ÂJx¢Ã/O;îÇÙJ`@Æ©ÒëPKyãŸ]£qwç n³.»„£ŒØÊóú™ä‚ÍT&—ºHtàU5Æ ;¼ü1-0pdý©êäxÝ=L«r¡ÖxáÊ6Ý6«ú¼r ¯Üé¬Ä=¸‘åû†Îûµo½‹TƧ5’—ÚSº{#:'1 }†²lnà 'o8 Í’FËóãÆé8;¯SXÖ¬O¬×Îå¬×X}ÿá¢ü;rP­÷-Á`æKå-Y‡ÙOquÈ0£})‡Ê§\³ø exÍó6zZû|]Lu˜F\dòíˆR (h:nù«úªTÛb[Qâ9û»oÕH˜3…!NàôׯpþDñ3Ö]¨ VrîÝÛù; q·«Øƒ™ƒQ¸^×ë| x× jÕù€ì4P÷•«¿Q•Wª•À 1¬³ª­ü‚q†©v9e£îccXâøçu%(åL\ZYÚã9W|D 6:öñ GÔónýoÕÞjªªÑcâ"\?³´ÿ‰ƒ³Î}ø‚tÈzC( ÑŽ3õ](Ôè™Õö¬¥úçX¾¸ý.;ð»ðØ›´ßš vŒÔʨV-êÓWh‹f<˹t!óüí–GPTå£Ï¸Ä!ÙìÇ“W¨ò߃2?Ô¶b¯/¬#» ûÌ^tè>Ó,4–Óc—è(t¡¿n=UßèšÃµgÆ»¸nf<<™áÀ-£°ûí|Îk`´r%„€B@´’€ Z ðJ Þeȸ†‰ö®Ýk,§šm pÏ$êwŽÊ?}_ÏTw~n*®wTÓ:w5zÞó/ÆàT ¢—1ŒB„wQ¡>Ú*Žà¼ú€áùÆIÔlåýqÝõöúˆÕÅ;¨ZOOœ=v»Sχué9CÖÍS;˜.Ÿ›ˆ>œÕ³JTi°p‘NGÍÛV>,¸®P3ÓT}ðtôzpÎÛÉJÀôì+Ê—r—?Ž>æ2ˆØ÷#yÅ"DeÄ#R}ÐÆv¡~ÜÆFñZ{ñC5é›±³ÿ(™Ê!¦àúq+?àoaäÇ äà¬Ø®1V ®kŸñè›m RÝ,&N}‚w)ÈùÔÌoÅWÔÌàà}Á} øÝuþñ¦ –ö&ºÜ÷b×þ72ÎZ¯UT96¤2¶Â~Ë´ Pâ@ïÛÓtdŽÌ©è¥g_Hšú ÿ晸pF×½–óÆÔæH¹oÖ€wÄãæ,j¤Ô aÝppiItUïmז࣌§ `#nʦ¹v‰é¢DÄré9«ÎÆóâ@'ªñ+•¨°„=Øî´š*O•ž@ç`|u¼µåTíVŽÞ÷<[ßEŸÛº‡,ÿšÈ“ýÜ´x¼^bsŽÖê/Xcf£É:CžªÅ>‚þSÙiÍsùZ\7ÿqtn°ŽY)Bü¼»x¡<Æ‹+woÖ¹è>^ •ì®é6Ðõ扺Ï~üèæÀ_ ¡T=ô¨làÝoQðvÛ}¡¨ò­fb[í§ëµƒšÔXY~7 êcC¥cfÍk¤î›^ô¡º¸@÷æÿÎä.PŒ;¹1j‚¼r#ÜM1lº®¨µº,GÇhÕ¦Xî=í-”qゾoýô[ø'\K!èùŒ©Æ û¶E¢˜Á®.ľ a‘F[ ^¾bó{®ô;Ý¿^³ÀD)Ø Z8È¥Y‹ó„1YO §Ùî{ÞýbïcÚwçr™JÓõ¥ü3C#@õ™If…Påí\0,8'Ƶ-¡=(Võ]s¸FF;¹$ä-t»>k ¼Y¿Á‘£°sÙf|ßÀîõß$w„€B@–å -¡Õ.ü:7?eËßF¹÷$ÛÃaМ¹ŒÕV©a¿š§Up}fû¡ ÀíÉèýpáY=cyŠ3WJ½¹×GáØ}‹h˜†Âv¿Ë{SpCŸ†GEQ÷p¼ÍErx½Y7®îló*«Ÿ÷(ØÌ‡ÝÕêïo΢Ý5?Øð¼ny!Î/Vùâ ê-³„=ž¦ÿsAáê_z±æxœÞÍ'pÞ´x"UˆÎcDO%„ð»ÎqÆõ…ârDN¥56ÎfSU™ÖÄÏîVèÝ9Ãë4¨ MAU‘ G×/zw->ùž8š¨®¬ÆSƒÿ.#ýk@Ó1Ù?бîYa|Gó£?lʨ@;޾è2‚š>­_}’ôàRœüðü¸vŠ×żÛá¾Çqã®Å6½ªt)Õk%ÈÐÂŒfäE…hÈõ´ñò 'µNQõþ,geðáòv,ø Rtj¯ÁšÈ¸ÜÀ¥;Åÿ>ßZÂ3R+_ÁaÌÇæÁ0P‘qG ÏèÜ<'xÐÒºêq[:lÅéwÿ,üʨu.¹Ñª´–G›jgæLEW6÷Ú|œZ˜Èå4n\“ý߈ÞÊe [©%t{—û°t¦ ½l"ŒBªm(¦ÿ­:oK ír4\÷1Àﳆ‚ å..…Ëý÷³ ÒNêApã -INpxÿuëË’+w8DÀ©ÏÕO­WÕk»£ mÕÓøüþõÍëþs¢o ’ÝWÈs¶AÕVj)Ø©žjB†ùßÑ)ùÎúÛû?ú>ÈåQog?R‚*±òŸ¨…ö÷tÔÝßœæ1MÕ—ó‹UõÛXÛ'q™ê÷ÛÆ5Ÿ+5k(Äìwßü÷ÊwoÅ·\¾ãbŸãæò¥!ÔF'„€B@´´Ë+&¦ÞM¡Á³4òÇk1´¼o©ËÚra î¨Þ¾öEt‹ œªå ½¿Ë"{þ×rØzþã”sæ6ì¶'h4pÏ9W¹ão8»‘ô™cC,M`|^cVÎø`µ½8Ô,U½Ý"1"Œ£ÒDè k[p¨-Õhoxk "|ñÌ€RÜŽG‡ÝJâ6fŠmß–§Šà,?D{a´Çm;wô瀜æ wÅîDq}màðÈï9Ø‚¼2§\DœúrÂ:Êxƒ3eãñãŠr\‚Ähí…? ÄŽ· 6Ü\?ÑPuŸ¿„Â…¾ˆáìŸK­—®·l@}ìÛ2É+c`xÏzSð±®Ìùïs Ó€À@ù‰ì9 £>¥áÂR þܵ8¿ñ}ÿ+tØoX{÷Ç¥ÎZ’—ÀÖ•QO[Vž±#¦r°ÅuíÅ%¨ 6ÌɯèË´ipëò±VÔ¾ã Ú-ø44ÔnoÑþDr_\Sº‘‚‡7fs}8©¡DÀéJM˜ï¹=j€³éµÙÑ¿@ws <#gvs©CöØÐu®‰6Љuâ†û¦Àý!׺ï¤5„b† Âõ´ëàÎ,@‘‡rùˈxZmg5T½¢j¼£ÛªR(!Tãuß©9Þwd½øþ‘Úà©~ªâ§jÀÉ嚣uü Wª÷mà:Æ #Õª±öÖFŸ;´E¯Ëö¿Á‹3T#?§|Jûbv<±…Â'úRscøßC¯'( c=2©N§Ö ¢y©«®ö¿Öv¦v hª<Õ•N <ª%%›Ë›œ‚§bÌŸ·v¨šªüÏRjü´8¿õy ¢5ÿx%XRVÜéÅ Ôîn©tBh“â{+ï {bÇ+pM¥êzð^õ:æàX‡÷PôĶ‘ÄÐöšÑTt¿e,ë\ Ü3i‹€‚ÃnÔXèÞ¼Žÿe nDF€>{j¢(4QÌoüÊv[©¹ÿQ_Gضlªî[tbY¨þÀ»û zSðhµÝŽ_¿K#QÃxs6TWÔ»Z]–*NÓ«´Ôý~3ØMçAÅÛìO놡«©ýQN;§?UÂ?â†ß²_út öovYš8r¹ÏîÚÞI‰MPWºýc.’Ceÿ•†4§â[»MŒ*ÃŽ ’Æ"F t”¸aÜ;Êmïs£|ÓÊž_-jªÏŒ ì¡œ ÜÀò.¦ …6uÍäªÞYyh—ÏŽÌéùwæ-ÚIùú½†OÛ4™B@«€ ®ÂBW³Kñ ïÖ3QƠ۔Ѓú.CÆj-oæ}46µ…ÛuíÁ>];É´ˆ‘}}ƒÚžwOòQì¢>r9h‰¹Ÿñk7=|jõæ-ëM»Úà+8@#w?Þ…ý«ž¥‘BCû!ñn§áSO±ÑÚ]`?× );#Ñjù3n·Í°ÞiwA9÷]S°oó.n¹»ÿÏTmÔ¯·]Œ¥¥ûkøüââ™F¾vóãó¡ÕË4®Y0ÖˆOk(œ@Ù‚?ð#1ØÔ±þtŠ \_{~ñÓ8¤××n „(cwË6ân··ÙãøA¯o§j¹Å¦c â”QÂÊ|—I„Ðü|³Zj±{>dþh5½€kϵ=‰Ýy(jhj¬ÇUÿ:†ûÂÆÒÊîyûwìÁ7›ßÅkgn©„ çmÄÞã±çmCs4üyv/x‡™×C›×¢ô%¼iÔSµúì‚°‡ä+M9@ãy1=1¿_¬zET§¶»æ”gÙ»3u:¿:Ä)kL”`åGÚvPõZ•ÉW«^Ð.°~èÛ ò¯Õƒ/ÚÃ`¾ ÿÏR3„kº3ïeÆLµöƒ°xôVëýilÑõÏKpï<¼‘Ú F]ïiÛmÀú·ëÃ&ÉÅÏJ¨ò± _¯Mµ„š ](ÜÓî0yÞëºm¨{×LZ¯mi¿üéJM ¥at1ë!®%§ÆÅŽ|ì™?“š '´–L²}9RSuߊ”–õã”áÀOãS³þ|w‰Þ^#RM- ¦6^WÔ«Â[[–:½ñè±€ÂR p=´ß°oÜ¿€FPò–)„­.ýߪÝh¤ÿÄAè;˰âêÝÁ :¬£äOðäÞÜC Ö+¹Äç+ÕžÞæ"¿Wƒý)èžãú»çë §ï¹_P-ÿðÇìÿÏ¿š G”ßù úa›+ÒýÝ¿hM¥0–™2QÐT}éÊ6®Ê»n¹*o‡ñ·@/ÏÒ)ýœŸ_ÍØ|Ô4×S\*¡ß!.M°#s»]Û"àMr!„€B@´’€úvwˆ¿í~qÛ®:~Ðö²}àGÌÛÇcøîÿÁá|Ö"”™œ:Ðêÿ€©NµÈ䡆±¯Ê‰ZM^=ˆé?V¯m›I¡Ïgý“¾œ­¯‰¥–Âò÷h­Ý„V¾² 4À; ׎(çŒþ#zË6¥|- ¡ôp¿>ï¡´Ú¿ÿ¡giaû s¥twtÎ~ƒô ݉a;ÞÀ>nuèµåËA«öƒ&ÆëDvJæû’ºãü‡ïqõ~¨™¨†½ ;ÿúÚ‡ðýÇ[àÊØˆ“™ãýy¿Ƶ/PX`ÆqÛ#Hþ/®+·E™8u Ÿ¿Hkø÷ùÃéçΩlZކ>oq->w¶8;ÿ œU÷iÈ2*¶žµ´KÉAú|ÈšD-#cá!r¢?îixM¹ðŽú€î·?g6•’¹5åé_hîa÷Ѐއ›q1FÍ´R»Á²~­Â80 k#öqÛ?—$ü}£Xv±4˜gØOKDWÚÏp¯ýgŸ—Ûí5™—…ƒ“1…VáßÃyªzGRƒ¡SGõvkXÞty†qÀ©fÆ/C¶þ{¦<¡Ëßg÷B•IÖX=p .ÿ›Þ¢Žÿ•»ƒ¼‚(4Óõoñ#¸À:s‘åê΋>õêÌ/n«4=îùn¯×‘‚¨ÍœÝ§°@¹$.£ùãÓAåm< ý«l’pËDîÒqN 6Šý]M·»Ò\ ×OßÃ5\Z£“JN]¨9sòCî`j×_¢Tßð¿¡æÑ Ô22t -“þYÆRšsf_Òa&…O Ô}¿V‡¾ßÂÿ^²^k«?,—¤åjËB S“ £êÕ_’Í“žLãñ—eýößûÁ¸ GiX½>5"£}“zKL²ÿQÚ)±œi¶óx úð?PxÏÂýЇ8Ã-V}Ë™Ìöj³íYiî2`* ´–£˜ª“3 ŒÛ¬3 ÜÝE‰gÑónî0áÅ×S—àbt_7Ì*?'—L°Ý_¼ÈôC[#Üæfµ=+]ÓõÅö·€Zeeº¼¹àžað~¸‘Ñ¶Š®"l ?ê‘åêçÊ•*ÜMå"™Î̉!·µâ—£B@!ÐvÂêèÚ.:‰éI ¸¸½zõúI’P͵×jýr¸#Šê¬?Å+”ú8„ªí¸rÕT3¯æŒn¤VUöûRjñ§+ÇÐ?Oõˆó±²À®făý[!U|5zðùÓäë\ñû$¼ˆë>äVX(ù†òxœ[1¡–EÌÖ aÜÎJ³ÿèeþhcÀÆLåm^FÍ+ºÔò¯o9‚±K‚ýi¨ó¶ÉKKËÓðOÍ Þ%ßÞzõO-{`»PÛæ5ß1¯ £–hXájÙ¾Ž—ËBÅs w›ˆmÑ;ü±4Õü>/íL•õ9¶ÉÆû†úå«ÞV[´ŸM\‚¨[h¼Î&Ó\ëê3liêÛ¦,ºÞtþ[šº`ÿM·Ásd¦\ûÏ !¼?2“kceÕT}±þ4Ô§ú_Öº³æÕ«Ö½CB ! „€h-cÇŽ!99¹µÑü,áKKKððp„……¡C‡ú¨ÎÕ=åÔ¹rJDP[[«ÿ?¾m–?ë˜å§]ˆä@´9ƒªK‡`ìWÞTxµµúçw|³ñ†}ne¦UËý¹]Ÿm b»oêø8`ü©e|PíQìÎѰY Õ›ÝjYƈÿ€³Y‹Úb.0íMå7øÝÍ»n^ÙØã g—…ýyýó¶ÉKKËÓð_?5 ÝÑþíõç]‹ ¡ ¶ûÌ« c w¡t ¾¥p©!×!ë=n‘Çå+—à~š:áOˆ*ëÆ}†Ïúå{®tf* nmàšŒ³>Àð͸h›²T‚Ôæä¿ jÔKÓm°Y¹f¤µ©úòÓÿ-0@ü<\….…€B@“€,Oªpe¸PŽã Ô¶ƒÃÐm¦2Âxyºzê»¶dº7?ÎåÊ–Á0\Ÿe¨aÛËéU@ SòýÜÍáþ« §þ,žøøœçö•j¹S|œÿ¾œ ! „€B@\^dyÂåU­JÍO¹<¡U “ÀM0Tjuß&!…€B@! „€hYžÐXâUÖhZõ¸5±KX! „€B@! „À¥¸\µ¹/5?N! „€B@! „€h#"4h#B@! „€B@öF@„í­D%?B@! „€B@! Úˆ€ Ú¤D#„€B@! „€B ½¡A{+QÉB@! „€B@6" Bƒ6)Ñ! „€B@! „€hoDhÐÞJTò#„€B@! „€B ˆÐ @J4B@! „€B@! Úˆö–¡«=?ÇŽ»ÚHþ…€B@! „€B ˆÐ @^Ñ$''_É4! „€B@! „@;! ËÚIAJ6„€B@! „€B@´5´5Q‰O! „€B@! „@;! BƒvR’ ! „€B@! „€mM@„mMTâB@! „€B@!ÐNˆÐ ¤dC! „€B@! „@[¡A[•ø„€B@! „€B@´"4h')ÙB@! „€B@!ÐÖDhÐÖD%>! „€B@! „€í„€ ÚIAJ6„€B@! „€B@´5´5Q‰O! „€B@! „@;! BƒvR’ ! „€B@! „€mM@„mMTâB@! „€B@!ÐNˆÐ ¤dC! „€B@! „@[¡A[•ø„€B@! „€B@´"4h')ÙB@! „€B@!ÐÖDhÐÖD%>! „€B@! „€í„€ ÚIAJ6„€B@! „€B@´5´5Q‰O! „€B@! „@;! BƒvR’ ! „€B@! „€mM@„mMTâB@! „€B@!ÐNˆÐ ¤dC! „€B@! „@[¡A[•ø„€B@! „€B@´"4h')ÙB@! „€B@!ÐÖDhÐÖD%>! „€B@! „€í„@D;ɇdÃ$PY¸+6¹Õ’ =£ÇôCƒ^ ÚÖ÷=.¬É^WuÆed`\‚£­ßð“Æçq"oÛ6¸Üê517mR¯°<ØÕ•bùªMð ³LƒÓf,çB@! „€B@\…DhÐÎ Ý}8¹¹…æjݺ|žŠ•¼ÉAî?n`è-Û‰¹¹:­)ÎáñÊÔUnÃ<È9-ý'T"gÞ\¼}¤©ë±l²3ðÝmpUí9ŒuºmõVoÉG˜Çwf­ÜeÞ*ÄT¶iᆺi¼“i1¹¨ãúYóÑaŸ+>pI@Îbs2®ý0ýzñËdîÃó±Ë\IR˜õµDæaÃJ4´¯Íå ã[OZ:ýìtùü¨“Ƙ¹]Ì[º*Ûl “ìeèAá²¶•óð õ㦨 …­ÁÜ yOŸT‡R$B@! „€B@\ÎÂêè.çJÚZFÀõþ<<°lê09Ÿ½VOÓ(Cö„{±N«¤bõgoš.W7…æÀ4ø­+ÿ‚Y©\ëpá0æÞ:£AF¸iøà‹L$¨‹&âµÞ3ÿO0½Ÿ¡_˜ÃëòBëQà1q>þÂ%jÕ…kÃ\é¤e¼Œõ|€•K§ùÀ¬˜û ›³ÏÚ ©Ó°rýø`õ2Œ «ã=@IDATö‰ŸÖaÛa¥ÀYòeþxë'áµwè÷ƒÕÈã‹Û~âumð bÒ°tõz|°~%¦ãl d9žÉ9l H ï%bw0ÈÌLEdÅc¨—:¨ÂÿÎÊ•X0)ÑHß¹àeœW½Ì=Ž`ù2K`ˆŒ¥+™×ðÎk ‘j²q­;`jAx°až_`:m1Þ!Ç +—ú9æ>ƒ\0yÕ;Xùò¾ÉpÝÓ`%Ó°&ÝIÄðÌ¿À m¾Qvï¼6Ÿû9®dÝ£XWè1.aÖÅô¯Y׆g|ƒîió±ÚL£•ŸÂs‘£ÊšÂ¤·sÜ:£œYn¬ÇҌѾXŸËòkcønʉB@! „€—5±ipYO+æÆ33îm0‚:L†—ÇëÙgoÉ6,6'ìG/xËÒûép΄L|ƒ;õ,v!ÞÞVŽecýQjmN » X¶êý®ó{¸àÂûy¼¤ÀªiØó;s¿Ìz}="ÓïÄò¿w%dÈÏÎÒ7TÜk>XjÚ[p"så'ˆ¡ÿôÿÅòu(›¾Øñ3?ŸYñ{‘7oFÄð ã‚Æû¥¦ÂQF=Š\•øX¤Žfæ« )θÎP“‘YœF˜„û‘1ï#Ì^Qˆ0÷.ªþ?ˆ”òm¾¼ÇLz+3Ç~ xyÃܪgü¼Âr¤³8Õ?¯ž;œéøä“4uŠÈ(3—Þ úZÿÐp¡å¼&µ¤`Τë¶>:œã°0ñ9,S#ûJt‘Ú|2³=°NÏTÂMA…únCî&¦Ü*¸´À@y ;|ÕŽ¤°l ÕÂu˜ñËu@L"Ò¸ eÚ¸1H§Ã¡ž‰B@! „€BàŠ" Bƒ+ª¸Z’ØT¬üàe â˜Ó,z=.äfÍÆ -  ƒç¶á³•Pv@ß0"ßµÏÙ.ío,ÜpÞtÿÀ4u°Óþ¸Þ¹·ì°o`9&%¦ÞóȘ {4²˜ï¢7Sаî¹çØ÷ÕÐôHy5‡É–KÄ8{üm}³4˜ó—Wä5¢ì@¾é'ýzr0ä¢,aAÐýÀKþ–£‡éÔ´µMb$:«â*Qÿ8ÛÏ2Lo1 ŒX_©ò³l\(!NVCƒÆ!˼÷!ã/+á¡‘H­€¡bp— oÝrýO]f¼öfIP§â„€B@! „€¸BˆÐà )¨KIf—˜.Üv/Ì7Ã5³^^]cfƒÁJÎ3â(§YƒÏÔŒ¥˜38ÖØr‘wíÛv‰O£®ÌŸÛŒ·ÿ¦ÿLi(±€›ÿÔ ?ØU»ÞR³ÕÔ`דò4,¸tÕ2ÄòƼº} Á.\à€{<]ìãî¶ŠÇŠ¾™Gïae!ÏôMû æP˜áD<ÕùË7<‰¹\ž`¹¨‹y w‘ $Êa=âуÅGp†‚€øAƒAe\úr‡ et Ì' ôzÆ4 X3ñ¬ g|ƒ˜ùîûOìåÇõ4x8ØW/Ô~޾dEÆ'ŽT,Þð2+]8RxÛ”†Eî.Ÿ¡Ç>‡Ñ4¼¼ ¨ÿr&„€B@! „ÀåF@„—[‰üÔéq¤`òpÌÕø5VÍOS‹<Áé{k|ü`®—Oð]s¡;²Ÿ[Å_$Òæ,@?ÿ“&Ï¢Bõö]Ô(ÌÙOú,ÛL·[–­ Š#ýœ¼¥¤ ai̵û¶”èíWå3%)iXÚXJÚ*ž ä5qé*´´€¥48!×΋=»üu+ÆéÔOÔOÞÎ#Hwö]{lÀ sÙFÚÒ°l‚½qÀn9¿¬¡ëv•cñ8¿ßºÊB¬2_Ö%Z³÷ ¬>†u‰“åçVÙ (•åáwj+È,äSà‘’6ÃÊvâáÙÏq¯ }ÙzLŸ0©ü—¹ØƒmÙ³ð亴ڜ ! „€B@!pÝ®€Bú©’XÇuêå´ àHN3…†Ëåî 9;]zFÙSyk}ëò8cLûeÞùñ˜dE\²³oB™‡šÞ28Ÿ4ÖÚdÎa÷[ÖáÞy9pq-¾—6\ÛÖàgÖ1y\[_ÖÀ¼ºY[ÅcÅ×òcþ¶]4#À¼zʰmÍ“>C“*¦34 hg^¸|6²?:l0wíÄ“³V˜/LDú˜xßË-ú…yÛp ¬ Œ‰côí’›i+;W! a>ê›åŸ4o”_3Àc'ðG·n'*•懧Rçé™u¹º<6•)]5DÜ(á¿å ³QXViä‡ét14¬ÐÄ å±B@! „€—Ñ4¸ÜJägLÚ–ñˆ6†˜ˆù+§!Oí|ÆA˃ÒQ—¶éý¢¸µžßFB——¤Í{ Ë·ƒ×’Üçp/ÿ5æ&d #;+”Zý®åxà—Á)á,~vºž5wÛ"²ÔÖ­¶ŠÇН9Gç˜Iz©€ò›·üQþ ж$~9«?[ïgNoëž™ŸÞE˜nÒ<.[0/:rGë~á ̾w翃 ÓÇൣ1#kWƒeî|iÓ@°RÌ̺ï?²ü2WbÕc—„]Ùâ—Ùþ§êLíÀM;a'~Ç=7ŸSj%î\̽77УòKc‹Ïzä†B@! „€—)Ñ4¸L ¦µÉ2„ŠÅAF( ØD•våbS3ñ—ÕK‘ªŒ¹Ió_ÃÎeŒ™ê0‡oðiS“ â›ÕKƒ>X‰´Ä`©È˜?æø åˆ4ϸcÞ_°tš]ÞxT—8 ¯­ÿ œãFçh} m°­â1“ÕÀÁÎÙᜌõK§Õó©Ò½`> A®Qæ Vâ³Åãl!±r¾O«@=ˆ5ŸöKë¹õ¥ÅÒi/ã“•†Eßo”™Òž§°ØT¬ÿËjL Q1ÓæÛ¶¹t`òë±Ü̺eOíSLšÿ2¶YuÈþLÎ…€B@! „€¸¬ „ÕÑ]Ö)”Äýìóça~£bÔiƒÎ« PÚ Cú½ªx¼J˜C?Q±±mVvê *ýn½U$ÓÛpùîJúSå¥vhTÛF6’âåL! „€B@Ÿ„@ii)¢££ް°0tèÐAÕ¹º§œ:WN‰jkkõ¿óçÏC„‹ü! „€B@! „€hŸZ#4å í³NH®„€B@! „€B@´š€ ZP"B@! „€B@!Ð> ˆÐ }–«äJ! „€B@! „@« ˆÐ Õ%! „€B@! „€í“€ Úg¹J®„€B@! „€B@´š€ ZP"B@! „€B@!Ð> ˆÐ }–«äJ! „€B@! „@« ˆÐ Õ%! „€B@! „€í“€ Úg¹J®„€B@! „€B@´š€ ZP"B@! „€B@!Ð> ˆÐ }–«äJ! „€B@! „@« ˆÐ Õ%! „€B@! „€í“€ Úg¹J®„€B@! „€B@´š@D«c.ÅÅÅ—MZ$!B@! „€B@Ÿ›@rròÏýÊvÿ>´³"–FÒÎ T²#„€B@! „@³È$j³0µØ“,Oh12 „€B@! „€Bàê Bƒ«£œ%—B@! „€B@! ZL@„-F&„€B@! „€B@\Dhpu”³äR! „€B@! „@‹ ˆÐ ÅÈ$€B@! „€B@«ƒ€ ®Žr–\ ! „€B@! „€h1´™B@! „€B@!pu¡ÁÕQÎ’K! „€B@! „€-& Bƒ#“B@! „€B@! ®"4¸:ÊYr)„€B@! „€B ÅDhÐbd@! „€B@! „ÀÕA âêȦäòM tï8]UN}ÃÙ+êœÀ÷{=pí=Œ ¼Û±W¿Ë/}©mó+wÑ8~¬‘½ã¦ä†Êƃo  Ú‰è¡©èÖæé¸Z#¼Xußì-]7ôfÄ8B“¸<Û¿^te½H¸‚ë…æë­FDt RÆú áì1ŽîݳûŽ 6&ží$ q£Rqc\ý‚:[qGówÂsˆåI1 =ÆŽ òk0«áóðh'ß• ý?mÄÓ[†#.Ô°NźÝlo°ŸzŠöãûcgîˆGßQIè`xž»…û˜µˆBÜÀÄ\r}ôêx£cÐ-:°œ/z+QzÐ…s^þ½ˆs¢wŸØ¸5àAYQÎTxØîc˜Ö$t |ÍÏCŸûÜUÌH=ç@׸(_¾ÎV”¡œé­A$ºôIB/>'„€W\]åýÊ­esç ª’¯ÍDÜîèüJI½×V}O†ÌáG¦éf¯sÑÍÖU+Ž^|µd>ÜEŒ¢Ï½¾hÂå“ç€\yQ<Í,›§Öà¦k ïÞ£(™ž¡Cz6ä£×жúh¼R8@kÓ‹ Ƕ¡töë:Γd;:$ÛË´ ±^|Ïz¡ÚÏñg×!áWýÚ”ÍÏY ßxöQª†Ëý?º) çxuŠÿJ_݀ѓ“|ÏŽ¼ó[|¿(Ïwmœä Š'%KVaÌŒáÆ ÌÆLù©Þ^ˆA½L!œíYkxž-úßÏÎfìIˆ<´žÒÐB>wÞb”¿ä¢¿yèYœ¤óÌ‹V;oÑG(xöO×ç—ã–>—:"nnÿP‰¿ÎJCÕvÒKyÚiu.Fµ eüÏ;sýwŠ–ìï²õ‰î‚w°oºâêwEIó0àÃÙHÊbCqxÈæ‹ñ üsÕ÷ü×uCR‡ú…Uþ7ütgg‹>À¾Iù1Þ–„DÖ››XoŽüéßñýó»Q4{F-ƒ ,ø‘ ! „€h_dyBû*ÏË67á}̤õéxY¥±bûzßÇ[äìyˆŸî´6¡Þƒ»P³ÿò=¸œ¥sVÙD8ûŒE—I£qG:ÇE¶M@ø+…S@¢Ûð¢cÏTtºC±Œëak•.§6Tç€õGÄ:¶!šŸ5*;_•—‹Ç>² bБýC§ôT_šÎ=6_«Ó×Ç6 ÂG²·›/J¶•Ñìq°Â(£æ$ã,%Û_¯Ù0@@Ÿxz?öû1ˆHŽ1¢<ú:½¸Ó}£qÔÅ>ŸÀ@Õ©éˆ0å§Ò‹o½ÍO«ï…­89yè@¡oÐõƳ÷›À á1FújVg`O¾š'„€W Ëy,sµ”ÁÏ›OªVºöºPM•ÈžCàÎ߆㜠wΘ¥mëUªÒùpžj“áq Zµ¶—©ž©ž•t÷£9C_ˆjª6FôŽ”±ýfÒ•Ê»+ÿ0j±ˆ›âÏ£ýŽiù¶ §Urà‹ëFAŠO=Þ‹c\2 T@» í‡ð¢BÆwQ“@J/•ÐFÂ6‘G+1îc‡ñ]þã2v2âÒÇ!Î\:q±ª E_ÁSQ¥… TCí1j0Õ¯íkªSùt?ž®‹EçäôšD|¿—ªü曪 hï`ôØÏ§z^vð ”3µˆèíD¯±Ã5"dúDç_ðCŽßlæò#*Gª7.ýØz8—_ì„›þjQ¸¶Ï`$Q9XÛ#ÌáÅÉcûY®˜ç(DƒþT½ÕNÕ—‡¥z*(4°1h¬Œü½tN¾(9¹ÈýSˆ![·¯nǰŽgÙœUõS-9I®U´ÝTÇvWT#üú~¶¥ŠÏa·ˆ^NDP·¡zça)aûñjµ™(8úôc;êçS-.;¸g8µÜêÆÞƒ;QNæáÑlGiãXfgðõ¦Oàù;UàíeÞ1ñO?ÊFRhàgÛ¬6dãâoŸN$õçmÃY8qóäázFð¤j“JeÝk2a³T¢OFÛ¼ZžÓÙ{Øh»¬G ÇÀõy/¾+؆ Uçâ’ÐchCÍÆêEÈúÝÒ>¨~#ؾa›=Ë6«Xª²IÕ¯å³çfu¡Ê7”G— ¹9T•ÍlTŒýökí7û‡3Ôžb_ñ˜¥a„Ø-k8³¥KÆ>«\ýØËp¥­`‰¹Ï—â«üÑ-žo´ñ½Â‹Un|»·m›u1Îh Ö˜ëŽCâÓc´Š¼ýc ±ºbDMÍ ÆY~ð¨î+®e»QË T¼.ø~Ÿ0ZM.°ol@¿‚}aYQ9jT¹©~tû'Ö9G3úQ#-e({Éeœ¦gá¶—& âØNl¿CiÀ¸Q±éÎNŽDi‘Qž1æ /—’”è{Fßž¿‡|åhDgÿ-Ûž£ÿ€}M÷-¹J퉃KîGùj¾{õŸðíÓ£Ñ)¯ñ8.–òo¯Ž4=¶ç&3µÝö°>ƒ›´¦‘ê“°ÌÈåtû+öoC‡-ãßHöëÇ–¡–¦k‘ª—˜ø{õ¢ÆýT4þþE,Ù€±3‚æ^|¹úeÆÆ“ÑïÐbôr”c׈I8ÇænÉ6xÆNÀþ‹“å ¶8! „@»&`ÿNhוÌÎR5²Èœ-Q*‘†KÂéiø1/‡αnê£RÃ-ÏÉÃ?ŠÅë39ëàÒ÷Kô¯ÿ§`?Ö^› 'Gþôkú+ô=TŸðÁÎ{ìì¼#Óü3ž*åÏ®Âm¿¢/.ߤÏÒÏýéjÆÞ /? {¡‘<Æé õ>dï˜~ò®rJ'm‚R¿9z' 8ìNðFyn>RFñCü |Êe jm9­²;ý>NÑô ë6À¸ËÓ7á´V=÷â¯Ó¨Fû¹ÿ±:;ÁÑ®¹ïë¸C—QS\=Ï“¾½^â‡>ë ”èfª“î[¯ïÙ.V0#Ó©8çG`Ÿ}‹áŒöûþqÑ,ìñ_jµê ª`ß®T°UyL2Ê£ óÇÛ&Ëq]:'cÐeKN½S5¨.ÎYªÕ»8¨ËGGG¨ºm;Áüö=´sgáT±º7×?‰8žyö®g›x]{ì²î œ›öHÈz÷]î2|¤ª®GYfÁy‡MFö:«üœzšƒxubs'®ï¡,ÄP•¼ÈTVùI¶ÍiC¶¨ôiÈöÉå@Õ“£hÉ4cpc t£Ñ{ß ¤DWãÛiÓŒåC¶çêTÕ÷«¹¬ <£ xPÝUu>Ø5U/kŸ—Ô1§–áÀøÅÁIÁqÕwdr°£>-uþµÞgžZ†½ÏOÇè>i!†Œœ‡Zeÿ€ƒ¥³¬‡V?ùêË>z[TŸ èûê'8¬£»PQtÎäúé87{)\‡Bê{åúå ¼Ùû°]pLó_òlŸYïn$“ÊM™(R}:Æè%^„«+Šc% g¥á”5³Ï;FŸ¢&9¸./KxÓtž¦ª?—?ÄÏàx°÷Ñûq"×myÕGU§Êîxÿ´2±‘~Ôß?\¤ ψѳM•ù^©èz™059àJA®ÙÆ¿g[÷Ú`ìy?Îé¿ÃÐçÐ[· ‰d^œà2í’g!Å\nÑgú,¶«Å¼½'Y¶‘MÄÑ!.FkæÔ2„·Jýõaý±…é×…Žâ³éÚÆ=øœâRòâ:Œ} ý”a×DÞu_fxÑe02CÖÍà_º&ýxp>ße..Dá’58WAâÈ HyàN-„=—k<vºÙ†Ð{QšQŸ‹ÿ‚ãU)ø~$ÿ.Ü1±§ã¦Âh#ùB@+@ø•žI øÔV­pü¼ˆí‹ð²m~Á$ªéçd£ÓHýéªé¿…‹j“a4–åsÉ“ÑõÕ…~Õ×ÜÿÅ÷ôã=–çĦ¡ûêlt¹ÃˆÇ¶® {î˜o ´ŸUè:;U?®y~öô65]_8~ E8ÜM‡m ²(ôÔy4cWéxszö©ÃÁi Ì›£Ï¥~²|ù¬¢¶ƒ8ì£k Ðé¥,Ä.™n„©ÌÁáw~D¿œ…f<˜q÷ê ×/ð "f/FW3Mõn7NMZŠï”zjÈôBÔL§ç† üxÿ»šåRnv&nªgô˃=´W`Og÷g'þ± Åæq&:ÈÝ1=Þô—kõcOâë*¦ÉVáz*«eØ NA©ò]*#dG6½ƒüèæK•d ” wDq‹A—W³}õ Ìï7/@2~†ËÁwEÆ °4w“yo:’‡tó©Û›7y`½ sùvõÝž‹ÄW­¸ò¨­£L‡þ5~"ŸZˆN“œ¶;4œöìb.E°ÚÃqfÑ^ÞŠm³ÚP@¬ÆE@ûT·bùžQQ8µåuŸÀ ò©,ôä Çh »ðý\C5Þ§šÏ`áé™ènÕg^W¿JÃ~<–mzÙWw¡úO,¸fÔ‹õûú õ^3?ä¼f¦" ½9ƒëklGóËRØÜ‹Î}ÆøUö‹7áÄôiØ7d4¶ÈÀ‘¼œ^MКî½;}Qvâ y°‹8ÆwëÜÞJß¹>‰uš×»Pô"¥ª7ášÕ‡ÄÜòØŒ°¯ÎpÉ‹ª»yM××; |£³ú‹£¨œ¶Q36 ë$"ŸÍâß“q~–棳?ð "ž]›·o@wóï@Ýö5Æõó—£ cëGý±¼nór®ëÅA·vÄŒmœÆ…!jèl8ŸJ5®s3±-9ÍЖ@îà¤åÆÿ…qÅ{pWq>ºŽ4¼ú½l—Æ{ÂÓýZuŽžý|”߯ã Ç͈Ÿ­|ªþ~,¶Þ=–ð<=ýiÛ¢äLŸÀ zõrY…NÉ*  o~¡ÛâWÏ>h ’ØÏ­@;èól|µ^ýšôþôŒ)t¨Y½ §VoBunÎ-Êľ/£Âë¡0ƶÙëu·Ãuüê'Âú»°=•ÓÓQ<Ÿý)ßQø N! Úû7nûÊ™ä¦iÔQ¼wíþ=Âwæ˜þ©6ùØdÄóÃà¦Çf™÷öè2„Ièýþb¤r©Àp~€YN DŽçÿ¯y™„ø³0tì¹æmØ-œ?´Ë÷ÁÖ饹TÇì‡äÙs}_UeV”æq2Röòcn ú{¿hYX[Õ‡¡Ýõu'z?`~Xr€00VΣ£á\³Š rŸAŸ^1pDû?äÃ9à©«8€S¦¦@Ä‹4†–>·Ìx=fFx’Z\GUÝÐ3]Ú™q÷Žv£|éÆ=¦kÔ¢{1hò ŒÈ]hzTƒÏjóÜ<ØÒó¿Ì ðWWQ¨ÕDÕÍ®ÓÍbÓ‡:(-ƒ*ó£0Rmc:‡þê9ôYbä¹nûÿòÃЀ³²7¯™Ai,×-‹ÍGá1Ä6Ÿ8KÕ{c–ŽË%(ÃKçÄ‘s;Ëe}êר6 ß?–šJåk‚Ÿý=úrÝô? Óiõ:Œœ|'R­@ì$ =”[½g¦ûëY¾‹7Ëà6/KÒgzUÞ•J®UïR¤ oëÅêèóüèÁæˆèxà AŠï’BœU¸ýßÀèý§pÞ»õW÷bôÊl_´jŽ-˜:mN Rï²#Ëû®ÝlÛÔþùû VÛžŽ¤c¸¤àNÄ?kÖ—ÏÿŒãö:œ‰á/ÍÀPÖç>Ϙ~8è §š³Ò8ÒŽõdÈk³1Z?·½ŸðîæÔ‹€¶úݲ>ˆ[/)= å ©Ž¾âRÑ›åÕw˪ºGZúëHÂí‡8f»p•lwXíçlðÁc^.?±âOµ¶¾ÂÆáïý”v‚ÝE,ÊB¢Ù±z ?/÷× »Gß9­Ø·°é´úm_[P³ïÚqé˜Ç'Oô’ .=y±©ºR‰òW uȰ;¬>ì |sºîûµÑIòøb_ìÆŽÃ@Î@¶P.sêÃþ—e¿zΠ].¹r$X}­²Å‰^!ûÑÀ˜:rɕሠ|dÜ6—›ôù·çÐ)HÈñì­Ýe%¶ƒ>±Ò`Ý5ŽáJ-‰Ní¨Ð˜k,5ûú -t±Û‘ÏåÐÆ¦-çßrÉYÇ6–@ìkøwH»8 w¨õåÞ`†£°8eì`$¦M÷ :jÞ¤¯~Ü¥G|åˆÙYl+kÐ5Ýi¦'_o>å‚FÄ…f‚Ž)¸íPzSømݸðüoñ mzlö;ì­÷·ÜŒ^B@!pÅœ|½â’/ n ®ãçˆáÂ}ó@nŸÆYž@w†ü{Xd1“gÍj×›54?.FNG¢å ˆ™íD©5+îðÌÎÍNÇîÀW¡–³)ggØf3–ÌBo+.+ ÓPØócýžìy z¾¬½`Þå:w5#¯BFó©xõjœÙ° ¥æcûá×¼[.zT‚uŠA‹þ Xd]z©¾lž[q_ðÏÜt?Ø÷ݹOª(¨÷_à¶Và‡·åìéwô£?zÕzÒ3ùPÒëÓÕ¦MuY+œ:ÖØÔ^£i¿Ârñ£&P]x/KpÚ&5ˆxŒƒfÓSç^ƒ}i:·—i`…6~Ä –ƒ{”°Ô"NA¯R—•y‹QµÁe>ICÜGÑ—³º¶d˜ÏÔa…6Ö‡½7NŸÊÜÞ¯ÄmSÌ6êbíÿ݉ïøQnT®NsÒÏþ3\Ä[½ã›:;Nàû7ÿ¶+uëÆÝ5ÉVËòû ïiÞãì\ã.ÒxÜXj4‚aè©óbzR…ª…GÔ‚¢8ØR«¦ýëm“t2ÏÃ;šé°Û!á3UO,º¨®bªNjgËZKÛg‹ú –±?¤pðm,)â å¥Å\²c$'œK ú½ô€™8ÿÁÃ%ߪzÝÂÂ~!ëÑEÚ~¨á]?­¶È;Œòüm8õ’Åð(58 ëP+Ó°;C „À²¯©ªð½¸£¶™âñ]£*7ÍxÇMÒõð”¹C‰ßCÐY‹ûÕâÍH¸®O µܬŸPX4Ý×ëûÞÒT]9ôªÍ²¾&Ý߇ť=‰;Ó|±àl_8bJ,F~?pPU¿â#TäpùÖl—íý4D?jÌó UVåÛÅþŒïU6oèªmšZ}'ÃÖ-ħã—é+¨eE¿ºÙËv¸ÿf~Nö\Q‡ÆiÍ¿ã7¾´»ò ›µ•þ—Ÿ£-#ô1þ†ÔÚ¶hT³:ÐnQ …ßꟲùräÕ œRBÏ©óh <æN$þÉ™B@\‰Dhp%–Z›¤Y©rFÚbòäES­;R¯ä‡Q5?€kø±‰½¦÷›¢ëlšju8^ÕÁõ}/ιL‡È%YÜø‘·¯á·ÞE5ÐŽvê«å³SÛP5¶˜AƒóhÅØÈ‘Ö­ FÎ2g`’ÐiÉÄ ŒAEúspÖ'àme<ì;~\u`úûŽuú=r¦6ØÕØ>¼ÔrËEÆÅZ§<§?½3‡ŸÚ…Ú—æø®×,¡G[(ë´Æ* Þ¨±úÜz_4gF•¹1ÃÕèY¯$óʺËA¢ù¯M¾CÃåð•ÏÏ¥ròEpò <4xWÎ^§i¸2à1NRe™wÌqÊù ÿǯò—4}X‹©!ü:¾¯îÐ|Iªœ,!oÙëÚBn_úíÉièúؽÜÇœFE§/6îýÖÙx=jò²¥m¨^„É¿äzäúuN ’bß¼S ³ôrªÔ`ª…düÄ×N ;Œ™RóFˆC@=©£•ü@¼¾ ׋¦ŸàúÝ‚>((i;à¶}ƒi˜³°[¶qÙÊ&·ZÚ¡84  ¶móÔËOä/Éç]TyÏDMH¡û&Ž6ì_ÄÌð¿rK=j_©øUºÍ9gìZËg“ÎÓ™Õ;ájØvÑ7¨¡qäÍl㔿‘ªõµ8žêmæÌÞé¯ûü5ç¤y}H‰6êiµ…ˆØ(F­J»?:±ŽXK—꿯º{ E†³/ÒHî¡ü#ü[ƒ^4:ê*¿ö¿3f`Žmš’Çvé&ÑÆ Ñ^«lËw~®)Ú†¯” 6¹QÉø[­4¨¹jÓñ Vœáy;2*^%è·º÷lÓGÕÁÚ? ½Ue(ÝûÎìUõKœB@´7¾ ¦ö–1ÉOÓìðý*—Ã8X‡[&Såž³°žœõ8±„ÛúTpoðl‡íU¹ÎW;Κ|mn1v–'Ê •å:„©WÃEöL}ªÍOƒ?ç×WVò#—VéýÊ–W}Tªš–k(¬ýÕžG+\£GÛìT§œ5=ƒF¡Fú> UØÎ}R|K§^Ýf h¼êëñ¿Fåc p|CŸóãÕš`ü{•ñáÞщ.æZÙšEkàR¶è\ëWúâV N¯ñ3tÿ-y§¥ø/mg{Úê–/Lí×/f›¾n ñ@[€ÕYøÒ\çÿ­-M)4 vÍ)‡n}ú^§à—ñ:qÆÛH|s1"“Õã8÷üoQt‡Zü[«Zóy9Ëå¬òÊr9º$Oq€ØŸÓa´ªohl7ßN®·›„ýyo —„žTÉOe}½!Úc÷ÒfçÍiC̾\òïÈŸõk|2íeÓÆ…? öŽÝ·t"©ÒT[›€ä^Õ8Ŷv*ç"m;64,ì ê¸¥"½zöšõä»Íkl¢)Ž3ZÑ>[Ô1«þA}^j(‹õ¾±53b¨f­ë%½ç.ÀΧhsäX%ÜÜE FOäšqré‡e@Ïz‹uì6Іä¬e4ÖÍPÇ÷!\Åú¯ÛFSñÈ\"þ&ëJ/öaf^u¦l°°.¢põ8û¾ÊÇ6Á«úr²\M•[ ž­këX[Å>R9 eR_{’KÙ(l8, Ñ¡ü¿ÒªÁ­rçžY2¾Ú½—3õÅÆ½ÈéÃuÛ.£ÉWÆ#Ô<Ž/¹Ä¤9.füÃëÃ×ùDsdíïK¹¤/„¶Wp¼µÌ<ƒMûgiõÊ…¼aVrÔ§E\úÁ¥0í)@ N"-!ˆèöóM_ç= ÿ~Víãr5®Ó°ÚGC~ rpfõë8³(û™Ÿ‹,GW.í–˜ÜÂãnÀu“vR»úM½G¶-YZh$vÒ½èÍþ´‚KÕvÑ ¥j¥j阯îÙðñ¶Á&¿B@!pe“\™¹T·š@¯Is9ë“Çá=øáŽTüääØÌeÆ;=8{sÞ¼jì7~ç•hP‰ž.Pyë~tTº‚D&ÓÈ•¹a83}¶Æ:éÇeú‰AÔÀxž{ÌëÀƒ£O3ÂZ]A[|unú,|ÆõÌÕ«­­¶Ì(h̪×SN¸^réAÂî‚—Í<òƒŽ.úaîþÀO¥kÇRXÍÞå cÀëèºeRžÎÄn¼R†ùІlÂј:ÔºpjpõáÙ˜î{´aÜg¹Üš!×oúéÈí¹v½äy~äq0³›„Æ,£á/âÅ'q#_gÉ6Ô`¼’ÆÃ¶ªïD_‘M‡S¥É7J2Â6« —Ì)5øÜ…›ÒîÕÿNrvO-9µ:jÖy85;¹ëÀ 5CfºÚÕ™(Øä´Õ+~k/šnªÕÇ¢'56¾¡Æ†áì*Üæ­GQzNŽÂ9õþ¶pAõµ9mH½¶&—ù^§Û›]XæO–Ÿÿ=>½ã·F=L~áÜ_¾Vk•Ð×0Úâà¡ÜÀvf pŒ[}{Š1ÆÃÊq¢^=1ü4«^å×zakú G/ÆÂAÿ‹¦á“-“i í€O>b¤5Çv¢šâÕ2ÚñÕˆ­–è𚮊Üöë3ã'âáT=Èìxï\]:GÏ`×°>íV§÷îÓó×akV˜[ü1þ ‹ñÍ[„ú”6^Ö46€rà–çßÂ'¹Æû‚Cû¯¹£¥}Ȇ(È·ú(#¦N3ËnĹÃVöÃþ!Ó´°¶òŽ×Qaî¤âWÿ,†‚Žì«®ðÜlþݲy‰xq–ηíVÈÓø ÿŠô ›†ÓÓ‚úZþíHï X6hã"¿Š¶"öæ°îšÑQ`_–„>¯¦éí!kègÛj.óˆávºæß‘ŽÿáD‡°„&ýÜ8t.wÎ0ê›Ê϶€OF¹Å}’ZY ødÊïÐO¨¥s¼ûñÕ$µµ¥á”ÆÈõOGŸ—޼D.„€Bà "` £¯ $KRÛ†‚vÇ‘QûÖùvL𠸮º÷çóÕßmj¢k¤fÆÇxFÐÚs¤9ó£É£iîÄ gJøqzËkÛk³±ç¬3ö®¶¥-°’¶$lPmqZ§á£ŒS+Oh÷]=Ï|Ì%J`œÊ2C+ f ÷çÓ>ÿ¶=¹,@;ŸP$ ÊÚõ? 4âL¤j©Ýº¼ZÛ5tnÎY蛲>ô”!ª[ÖLú¨•~¶ÒGcpdØÙLm¨ÃM¿ZÞV:­/{;½º·«å’¿N¯ÛŒ)v2Ëž[±©KGð´yåp©œBåź×êÈ©‹~»å£—(Ég jp "g§ ¸•çS½VÅsãøÙ¾2@ú =cfÅoíõ®sÎ$>5ÚxTLCž„Lµ¼â\°Á¯ñ"B,¥PñMËé|4³ 9(”2\OjŒ‚'}mÕeó©£W†åÒ £Ù&-²|?ä¥1ËÂlU.â:5ü£3ãSñÜœ“iÜS¿¬'áw¤ùâ5Â6¯^‘Ø^¦n\b¤»ƒ^Ê£7£€~oúÛ‘öÌŸˆÙÙn¶‹qÃ1æó5ÜíÂi=ö¹D%vK>†šÂ­½ÆàöŸ]&Õ÷Î&úì[æZ{„ùgŠí¼=}_›ì{GÀ3ß]nÒ×¢>„y¢Ø.¸\²#Æ•!£!JeÖçBh§ÝøÀBßÎ:ªÌ”À@ x gla¨ÎCõ£¦'ß!.í9$¾èg§p·…øíË)­Æ¾§¬-…7dÜ-š*ù«§›áspÐÜqÀ¡Y]Ë £>_…ŽÉ>_ú$’}Ë(³Î< ‡â;b —ÿYq˜ì;±z;ßXô§]k6§fƒ$!‚‚>íh‹ÂM펄ɿ§PÀJ¿Û°_q †QCP¹&ý°¾©¿Õ­¸u(þpûDµ¯Ú¦´C¯ B!˜½/Th£sr¡7z÷õÞ°o·²|E``‘”£B ]«£kW9ºŠ3S\\ŒädëkäÒAx¹ÕÒ9Î*ëÕQ\ó~©Ní?]ÍÀÑ\ÚÐà:iª±žÔK"éÏѰ¿P‰hMØPñÜóÂCõÙ2èÖ(Ë_Ãù4 ªQ½;¥‡|ÔlZ¤#ŠFö^òB­>\à‚'—*¥üÀV6½…›ìÆ!ÕM:;±<{‘.[yxËðåŸV¢ò¥M:öh~ÜZ‚}£YåàCõ¡!NúÍø9[´žÆð–Ñçhô-þ/ÜÈz|RÕcòòe؃#ù4´yð#œà@S¹zùÑwCÿ¨4V±¾FPˆ\ ¡ƒ´ên³ÚP Þ`ÅȤh¯VY6Ñ6šU/B¿ûÒû Ú´5þÐÑÛî*ÿÓã —öDüõÄæ…§Y—ª¸Ÿäž3gæ™™ïÌ™sæ™g攫g_=}‰)†ûkS{—=Xêíׇ1ÕACýš©®ëOÇA!éE$@ßw‡¾‡¬7{’§OŸ–ñVÚµkQ{zj¿êXù)§Ž•S*‚ï¾ûNû«¨¨0+´µ‹üGŠ€ ²x'v’o/’f!’PC/àæpŽ®&®#y6~>.*Lœ‡ó¬ç…Ù_ø4Æåmƒ‚e9–(=î¢Â@EqžO“`û|•É&€&… cð3Õ]ªçé×ÇÉ61WÎ*eçtÑ…J;V‚Y;õmøS2›lv²ž:Òhbökà@å±Éíµ¹õ]réª/²÷Ès^—ZÒWÁªé}‹y3³ÑÃû[Œ=ÌW¬ÂV™y"uV©<¼¼¢Þú¶KÄîÔþ—±síìªýߘæž:*<€Ù/f`ÆŠˆnêøÕ8øöJìì}Ÿ [»"{ÿzüò½lï˜ñS±|\ŒŸ+'yŸ¼ƒç6ž«Ã»ôÔ‡øÅ {mêÞu 6̽עP2&P¯ŒÌ˜¶ú€”ÊÊ…Ɔ…÷¡‡•Wó^ÀúÙ+ð±}"µýÌuߘ²Ú‹á9 ´&T´¦Út±,UŽKÈ>X·ë} òw1’“`UÃáƒ÷; ÅË$à>¡?N@¼o º74óÖ/6ufÚ}¹Õ%yÉoyCvwŠî‘gÐ8šò]”×”ÂÀ c§MÅ#Ãô™ÿ³Ù°âÅ8²%‡‡½€A¢\(ÍÞfT´Ç”§A|L˜f%P|& k–nÇ‘=[0;°+^6* ¢0P.ã½7q°ÿ beb›‡¦•«‹Sþõèø–÷m²5ƒ}Ϊ*òqâr>ªì/4ò<óX12:Ú ³m%ˆrâWšÂÀ3æLÅÀŽ'±ö…ÈØþvôOBB°©þl£Ùœ‰•Bæ©\dŠUÈ[ûóå’ýâÞ2* î˜<÷÷ó’¶ðR²>ênÆòÑ¢èq*ã¬Ð^ˆŸ6ñQp$e#Ö§¥cÅGƒÍmÁ&_ÍqR/ª«ZÄŒú)îàêj#[ïîFe‘ emŽ|Q& \‡¨4¸+¥ù²T‚ܬã8øÉI¢ Žg 78 aÁòbXR€ÃÇ2PPT|ƒAQÁ6Y)È:ŒŒã'Q%×£0pn¡PTƒûOÊë~Ždü]Ã¥dŠ«,ÊÅ‘ÃÇPPZ ?¿0Ä ã%=ÍÜj Š‘¼ÞƒÇ+ˆo½ƒíÒ®DnÆ1ìým²Ç“&3áÂjôã³™Ÿãœ¼4wïP‚#_dálE-B£úá–ÝD¹Tê:«ˆŠ±5?›ý…˜•{!Bê2/#¡1à-Ëv¥fËlòx òFiápäX&ÎÕÀØáýÄÜh֬䞨AlÏìùä ò*¼$Ý%fü¥_àÓ/2Q†öb*=ƒ"»k%õ÷íÑÃj,¤‡ÍÆYx"2r(b¼îâTº95!ˆéí…éÿÀÑò>˜8úf0V#;ã ŽdÉÀ©ƒ?¢n¶2¥/ÏÅáìÄÄ„ ë“qPÊÓUòò?Æ#ÔÆâá2ŽÊà+ëT‰”·þçæhøÕÉŽ“tNÕ`Pt_az{Ž•bÜø»´òVªe_ ï2Уw4n½ÙRyR‡U=oF¤÷7Â2e½oEBLwäe¦K¾ P#Lºõ”û×X§n6Õßà°,UP}È£Â@õˆ޹à[ŠqøÔeÉ7°-ù º„)ÏÎGb¤Åä»KÏ¡X¼Ê³g¿ƒ[>@æmó!Á­Ü¬X³ &6r¶¿Bê-]«·Òí9C¢{YÉ•C©£‹Ò?ýü«:í[ìÜ3ƒÐÝFipVÚòg§P,÷Cd¿!²$ÆVfia>“6š'KBeéEL¿›eY€(M sñ¯ÔoDj ²2¾´» 6ÚÖ‚B¥©9YÂqøØ—8QXo„÷î/ U:R&©»#R×8sL– \A—ÞìÚ÷Eº´ZˆUÍlŒ‹VV5aXôü9üï ±3í$ŒïŽ£’o¹Ï¬òŸ—ùÎIĹйŸ@IDATg}ÏìÆ¯V)qMζÁV¤i3óGLƬÛtë…GNʼnéo!c:JEi`p"ì=<^NºŒqMÀŽ´7Q\TaJX~/#3ãKQbœ—ryjõ¢–¦X/[iè>ÐiKhÒqôL üä¼åæ¡fnUg25«–q#†Ë’«d‡®”µ¬ð‚ô›]ëF¦ ´2¶o­¬p,ŽÊ øõ´™8©yŸÄò™ÓÑuV2¶8‚ÄÄgqÁ.xŸëðþƒƒÄ·ÿX6‹>,¶ Ñu¶mG§ã~ˆ§§¨Éüë}þ°YasAò.ü$Ê•û1sú ¼¥Ž|¦ç*QLo»0Éû^E”iÍ„„›8}úH^ß·SdX‰åa£ Èzcô®Úø%.ž€¿LÁÎ7¶`»œX»2ð è‡ «¦áÜž°dÿÄÏYŠG¢MƒÀo°öÅÈðê«j°díFT÷Ü!œMÊîB÷¬7ñ‹d[³i`Æ>õ ží‰ýà¹=W¬S$ݵ^ÒEÕÔXùŠ˜Écùm}qbÏ›fóéÌ=¿Ã¯>P³£&÷©é Þßû7j2zÔà´*wm G‡à͹/c—5‡?íÅ­rï<3, ¥§H­Vºøw¶¼Ì×MçË3°@Äæ”ÓñæFãIOãoíi¼öŒ“td×+´=Nçélbãï¾sÿ7¬ÍýbýÛÑxõµG*Ìk¶ GçšÝ„|hoyÛµ¾)C²®£çl\ÚØA¹Ä¥R/©–Œš“xÿóÿàþ›-¹ÐÑÏ`ÀÙ³@­å_à³3µ@èpÄ[) L9@ÇÌ׿ÚYŒÙ2À—e2ó‹Ðx8"or¥æ¶ts|ûƒê\¬™·[פ D EòÜñºé¼äyéøÅütKl«öÝCø¥¬™sYB²qÃ2@•eï­ÀŠýz[Ö"Iø5ƒ°iúpM±àhI€jÛO-y'­“ºÐݻ륨{äµ0›¯ºª–{L’åjÐoq{Ñyļzó1ürµ±¾Äsò?'ÚêþÓc©<úc¸Õ^ Q.Äà 2²³…ml[»Je<åÙ¥š§4ó=üB) ú#yÕ"ÀÆ—¤Ž#o¿€UéÖ÷žäó²Îa¸(À,®/†GI{›R>D:‘jQŠËËåž(Wä°4û˜6€P¤\.V>±ŸÚ$/u)ùܰê-ÎÙϺD„X^<1ÿ/š\M$ÒñîÆ¥^b”X]˾Ê;q@¹8'|b Ç£RÒyYe)ËüØÐ3MÀ¢ 2è ñ? ´:üzB««Ò ä†÷÷@K6ïÂ_ï‹ÂgëEaPÛê<55©»’q·„8ù·ãP¯ˆ%‡×k ƒ.£`ÛÞ½f/Ö=s pa –}è/~J¦lFW;›wíÅ6‘)‘ð¨¶oÂ-xI“+þ «È[6u%r+åÀGÅR ƒ ^Âæm)˜ôÂÀã><"ÓÙF—µÿÏ"[w‹\º«' 3å{¶¯ÇO§¯5Ë_¢ËÐ!øÝ}4¹~uñ½†& ùÕ•øË«óðÔP±¹| K¶ÿ±ñcÔx;eÛäJeó:58¾ãѱð“YìPuA½Á²çg˰oj ƒHüöå$üeÃJ$?{«&ç³cùšƒl>¦œ·lX¸AÒýcÒD(QÜ:a*þ(q6.I@gñÊH‘MùÕã2@X ^}°,i©–Æs†ji(1õ9ƒÌèªYàÓ—ý1eÆTlX1'Þû½¦0¸U̯µt“&c Œe>M~‡Ë%¸qɃši7¥õÛ ÒþQŒÏNé3¥Þ%Šø üÙd| Ç—gcJ”q@¤dˆ;ºÑµt”ÂàÖŸ%àwIÏÈðsüJ) "±l¥*çR,û™¤]“‰ïÉL¶8Í_) zFbîœÇñêȳºÂ êv=/æáau+9ˆ]§l‡©š§ÿº"á5h¬AÊoaòôg0uéïðÚß?ÄÑ‚mæÕ WK3¹½b¢ëLE Ð÷>8qVÀiËj:`܃b^/2>M~K¬?œfH pXêM) "Fèm÷Â|j¿ö@V–|¤fûÅ¡õµoDã Z¹”!s£¦0è<øv­]ªz|¸_T§ïÀ›¢f½”¦/ v·(éy­Ýmxj°Ä¼‚µ«ÿû_^ŠWgô“s/¬ÎÃ×(ED]wà=¥0ðÂŒgçi2þò²^þ‹b½€hiC/ÏÄXÑÃ@”å²(ìL²*dSJÕ[wCwã=¬_ Á ¥¤’û±J,'%h÷Ä»/&#[¬#VhV]°léæýü:È}€îA]LÂÍ¿'¾PŠ?±( 4) Õ%o±RPa‹Í› 6$Þ7bÞäÉÓW˜>ýyÌ^ú<&¿¨,Rº`î]7*8›º]S œü°vþEµYY Ëb©¤ÚC¹³ûà^%ÌiQ̘óŒ¹?è¬êå…$§¢,È>¯’BJò¼µóKÙcã ,¯Y‰'>ø\ów^ÖL|~<îÈÇúÕëð¿Ó—âÍÔ/´¾IÀ$@$@$ЊPiЊ*Ó¥¢øø#P“j"¬§:¨DÏ»_Äo^}w†Éye%JJeH¦F÷òªoëÿð÷Á ÏüÁ>jú߃~¶/>3 ÿ/Ì%SÞ„kÃÑÓßGÛ1÷ÀŸ5ëƒë–bˆ’+¾Á1?Á«KFÃÃã#ÌÕg¬äF/ØŒÙ?"Ë$Üïg][‹-–”•+À‡kdnlÈlôSbèšL@™PoÛ„ŸÎ^‡µ;³µèÙDðå‡åËú IÞóŽ.ÊË»+F=¤`N§~‰ÒNC0E´ÕéioùDÍ‚†ÈzõnZt}¶øV¼<=Qv ï?ƒ 'ß.¦Ò“-;¹W•_FY¹>zó•ˆîÔ¹?ÉŒZn`€áÚŒ|$¦ŒŽÑ~ÁC`š¥7Æ2ýdK~TvÆ>1Y–<èšîÑb3ʵF3Ek‰²c óx_,)”eÅ™MUyE§~x䥸‚ƒ²û¿ÉÝñ”%­hYšÐKfÉ3²sΰMÍЊŒYw Љ{!ñ‰û´0züo\N'ægã™»äëA²Ä íSÍz`ìäÙT¯ÅåÕˆ¾ínm€}Q,j0¤»üöùbFß¡þº—,!ùøÈ$Ž úg=WŸ·Z×»)®óßÐaà+KhDôôÂż|ìkŒç­ÀO¾-fúJ©#våd‡ýúœiÞIKÎ!mC pçÊæ•¢}Äs¯}X_t‹¿bž¦˜÷Ç¢ô¶kæãgME pb§®hÒ#4о-娠ڶì0þvøV_–awÜ1áN-Ôg_œDvúAs»d\jÓ#æ>üvÚPÄëƒìÂß½“jò%1c78Ä]î·%à©§ǸHéx«+P|I”OÚ­¡"xïcwøÊQçN]䨑ˆì-ôpräØ Ç«ÓĤÙøå|Ýfʳ¿¬gcɺõÖ½·ºÇ; ´“xãýlç+§ueˆ¶Ÿ}®+‰Ôõâ˦0ÅØqä´."pž˜6soSûeT£ôR‰„T@ô²g;¹Ή5Ð.ÛkÄݸ5Ò_â_FÇè»0EéoDÁv¢¼Y™ò ò Á¼%º²gã’ñš¢ò´ì³±S–k¹RÖ.½‡bÖÂؘ4SËþÉ5%ÚÊ?ÀY•]zùŸH€H€®o¦7öë;—Ì]3ðAÔÀ~8óç?àþ™[ŒKŒÉ—Þ–VÉæd}î³´ûcÄÏî«?_bæ­·Øô{Ê.ÛÀG6ñFhSaF/±†˜ðD>Zÿޕ܉A¥Ø"—&<8DSFØDä‰ËJ³?Ù¼t-üø˹Ýû½ºh3úá]d¨$îò91?öÆãû᭵ǰ3ó2¢cÎkdï¡·Êz cT$ ¼Kí `rÝ1¨_lÞ¾KÒ­‡rÙ2ˆ’ˆ^1ˆ0Ï’]µÁ’.É4¸0ɵüVkƒ1ÍŽÖ´]æ ÑÃd”°GÍbÖç”ÌHXb2˜(–Á¿²¬˜>{~HË@qÊ0åí%3¸Vi÷N0 €”â¤ó̓ͳ·š Žý0ª§ÞÕfJ]M²oƒRXè®ZOÊíZ/Ët/Ëm0$³ãJ~¿Á–úƒ†`Þ(ÙdnO>Þ’ûê-ÃËñLÀ#j éÀ©Á¼¥þꨒMã n”=ÔŸ\—u^ö—Ø,æßŸÊ òl™­Ý4JgZæpàh”)dåô2æëžÆ6àýžêw kíÅk™¢0ÒL×ëi¢˜PõÖÙú“š4}¦=Ãz×`ûÖ³ ÿ™Zr%X1¡õíøâ'_àÒ]ª>µ»ñæ=”UŒæÔ½alfFã7bDããíÛ0um¶¾¤ÄÀ|?˜<êû•}ŠäÚå“òIO1N0§#n­ìÆS‚ô6 ãw.Ô­OnµÙk¢>é&ÿ‹gÔý[‚Œ3²œ¤·ÅÚ JoIÃÞÑo©|9á­¬ͪèY Ú=búJÁ®õÛ‘°áI„FFˆX<7w‹l"i-EOÓÙ}ðßÝrêôþí˜,¶N”baóÄ•H´ºà<ËžÊÄdéÛ²Å2#¢eõ ŠA¢,ïJ|H"{ÞAÒöøôï{e¹ÒÂz2V óH€H€Z* Z@%5o‹ðrb<¶ÈêèKð«[¢pC°>šˆ5ê%Tœ‡úë50m3 , ²dVU6R ë{WY%¡%b©m$ ¦<¬œ(útµ3úa`ý"|x¬~FmíhüÌ]Ÿy°Jº-d£>eÚQþŽÈ†iƒzv“Í¿”½³Wg&Ó=Ô ¡?|e0âs»ÌlÃÇ{¾ÄDoÙ^.=DÙ¥_-E*¿£¸sO+þ2Rrß/ñ—ñ—‘-kÇÈŠ;ödˬ¨l^WþxÝê3·/•}JðTÒJŒ2³T …Z?YÌÚÇ>õ¼ìGalCbÎ=Ï<ÛŸÍÞ‚j1«¯z@6¢Ä^dìIÃYYþÒÃT«ßOSÔL¾lcÐɱEȨG'#Eö†Øµæ`€U]Xɰ>,3*!¬ý̃v“§“öm ¦ýªú’¥óf‰¥Yù¡XH»’ ñ…RÁTC >lŧ2pNf±£ƒ­Kı;‡5³Vâc)^̈[1K6µŒ9·-”ýìÚ‹ãøÊ·É¶K½i÷‚©ÉTŸÄg—E ÖϲBi懖ý-.ŠmÙc\Vôê'ËIÄjöˬý1¥<‰´[á8·çòôû"^6õ4µR¿Þwcîˆ4<·¿X“÷ÁB¬ËoÙ„uî´Áˆê‚Ó)¿“ýTô6àì>Å—Zâ6¾nÙ¤®º‹ÂCmYh£e’¡£þJ”‘uN¬[ÖjQœÃÁÓ .}®f­å}I€H€H Eàò„U]ÍÙ+E8.J>36`éƒ2³y/8)iª¹FÙ/M^|/¬ÇGYb¶mrEŸ!qÚ4<úgQ(ç ú‚uvƒü?‰·>ÌP§FW‰ýoióœèjÜÏ@×H˜®Å ý™À‡¿|O¯<‚°'&ˆ±2ÝÕ0ˆYò;²þý·>O3òÄŒ|=ödž¶« °.ËNð…VÞ—dyF΃Di y‡!qœ4’c;ðı^Í݆ÛÎMØ&WZtR3@ø¨lz8Zv*ïÝK 5úfhN¾n`’áì7T,Ô öÍ_X­À.Ù³ÁÆÉ ²T–Ø©®,A:öÁ TË^ ]"cd—ùú_Ç\<·v;2+L£1K”:GbJã%2Ò÷ê{ ”fþ)¦YS—ÓŽ”ˆè§LËkp´DíÂoÌ[t?œOÖ-IÉrh ¬L~:}žÔ¡˜°Ë×'ï›w^ž )‘L»Õ«e¥F‹‰MÆSfeµºÑÂAůg˜6a¾+y»ldgçÊ}D€¿ä¥/î¡–(dcÉ{ive¼ÏßÄú,ñŒ;z×ÃU6Kœ7C”75²ü!] LëÑs›˜ïÿ» óª‚ÝÆõö×Ú·)«2¿YÚ¹løx1Ъ-D{áýÕ[ð~ö£ û¼³ÓªŸ“ .×¼ð~õ‡VëÛPx¨OÖˆ•„|‘`ù?Õ¾–ÑE”?Å ¿ b­->ʬ•X¦l»‹"PYìüÂrO—ª¯šˆ/¢_ˆJ–q¬X­,od†_'hËeÞ}ñ÷ȶjcFqüu9Ö{š@6ýÜ£mx)ƒd‡±l=};èuxQ¾aqäk ¦ç‹|5á˜â-XNÃpùRKN]åË :Ceäì>è,_QJÒ#bÑ`¹OäëÙ²„fíÙ{áf¯Þˆ_­µ®#YÆò¹zðA,£DÉârYÕWR>Àrýbõv?÷YÙGa•lLZO³Öá?  hAêykA%`V›LÀ48PN®_†?ôTû”àÏËé Ž|„ÏrGà–ûf[b¥( ª–ü 7øà¿\.Š`¶ú.·8Ù AÜ,¹ ÃGŒÆƒîÃYX°eÍtü¢àÜ7$¹¾†õ‰IÃ-K0"L¬ ŒúÓÖÉÒ‡'`å‘-²úU¥qƒíež5‘@DJÄ˯%ʧÈNâ;d³¶—±VÌ«™3ñÑŒŸ”ÍÂæ/EÞ´;k(À»Òôuôn7oâ;âvxï”ÍÛä=>F­!o G¦%GÞ–ïÅ{ÝŠ.EǰfË1m,|Èw£ÃغYµÃKu<»ô»·Šõçû7b¶ì¢?åæ8¼çoHQÊ+wt{’ö•†±sd–<Ú‘¥Eâï‹” þ²ñ‹¹¯àáÉCн"ë“•ò¡‹¾œL+ñÆÃîb¦‰]"cÉÜ$LyèNô*&†f!дtüd3Ã[eõÓ÷ÖaqÁp$ è†Ã;?„è Ðk\”ÍÓ”/?À+EÃ[/¬À9Ù[bûàžÝZF(sï l^ø‚|5£½˜S¿€ˆ²¤C6…ûxí:„θÝϤa•Ú/@ʯ³ÀpÌ,_ÝH—%O$!~üDËF)e'2±yg¦V¯wÄÓòûÀ#¸5UvÁñŸf}‡ã#Ôë ïß‹mvº½ °î3·+%v³ë=NÅŒ¨ùº‚AÊáØY1Ÿ„ñ“åó—åßàÝ?é– S£åGÿä¥óömJ#v”´ó=;°~þóÈ›˜Ó êø€H‘ gK>§ 芲‘ RLÜ?Ô&¾ŠãHFCd@¯_‰ª»îƨpù̪ìð©(I¼KŸ#– Yª‰EÏŠ>D¢0=(m|—(T[Ý)_ì˜5ÔÉ}ÐñF<2øi£;0uM.Ò,ùÇNY#_ é.û”¨ý)×Ê—(¦--ÆŒ»¢qQ>kúnš²‚ˆÔúBÀyY~$_pÙ¢,äþ|3–O0ï©¢yò ´T´’Šll1j=‚(ãvùžûÝ <úôzYð´&¦ëè ˜ÕU>+¶å3<»=©¿”u•Xø‹åX³h¦ž”G<³îw¬ AØ-ÿkà£-ëQrÃÜ)¶³wmFàÂ_cý–•ñ¿æÞ½KŸ»S[æ éjõ vÿÜ[j·à³°Y¸Å˜†]ž^Ê”|épÌP›#Êæ][e†->ZÍè*¡^ˆ‰¶ËLòv- /Ü1mª˜¢wµ¤4峊ïžñÇýƒmí@|em¹µ3D&ˆéñ7Xµ_­©×B̈þðþâK‘ïÎïqùñž8²GÏ@„ ¢ÉþšÙ¼îeùt7^}ô¼!;Ä‹¢(ÅxE}e`Ù]j0Y!›ëÉÏe}½¸_d";‰%2ÀÒ>¨…—ÍŸŠPcÜá2«ŠNï`Õž“HÙ" £¿Zß?^¾B1u šõV. ϼ:7¾ñÞL—Ï(Š"Åä¼C£±è‰©2È2ùè¿ÞbŠîgãåq²ÁåÎÙe)Lý,­Þª“± ù¶K9u穲Áàxmý½q©ŠÔå­‘õ´ocSúb¡“ÞŸŽÐa u”è8¿}6Ͻ¸Wäl×ä¨{`üSObx'õ%ˆ4ÅKgùƒé^½íLùd)Þ•}#ÞͼU¾Ê`Q¤é– çM$Œ¿xdÉÃ(ûÍ;’¹ÏöëÞïš ÖaÝíÂÊ2ÍúÁNFÇ›±îù+xq…Z÷/ÆXZ~h¨vÿDÈ×2„i†jã²Ô#~”l4(ííã·âáaӜܢڒ¯z”yÿëÓ¾ÄÊcºÅ‘wh?,š¥ftÔ¬gpqÃïñnz&Ö¾‘©¥«®/ë†Ú™³²Ê+DI3J6±ŒŽ6ý—&€ÿH€H€H Õð¨×jJÓÆ ’-ßâ 5½Ö7F%*KªPe0ÀßGWT–ˆù¨Á_¾Ž`‘U©L Ô~²†ÁÊÛÀÑ‘Ä)‘8;YŽ‚šý þa‰‹p÷‹Ûð܈`³7êÈËË»Šz·–W!ë¨â] Á—& æê¥2;iO°Y›Hë1r±xú:é—€¿Ìn-¤þcµ4@ͺÊúw?mp­vE¯ õɯ_Œ³+ÊÔ^Íp:η³ØÖ×Õ&rj©v¯×ÔÖW];6É+Ž:kéMLa—Ž©œž&yõÿª=t6Ô©]<áx¶\úY/ßEíüowY?Uu¨jÏ·£ܤþÕ2•¾oÇú¾à0‘F{ª%(ÎêMgQ·}o[øŒlVÙWÙ~Q—Y»2ÉkL}ØÌÔ,mEKÓ|¿Ø†nèÌ•ò7ß•k¦öw5÷™3öåÐÃ[ø¨|šdÔËÝÔïH;vxjו$u]ýÖu¦4®¦¬u¥Ò‡H€H 9¨÷âÈHµœ“ΞÀéÓ§€víÚÉ×ì<àéé©ýªcå§œ:VN©¾ûî;í¯¢¢¢i-8ÿµò©DãçMeöQ›Ø9¥A°Ö"Ø]wx*áMЇ׭<+ cF޽µH|oÁƒ·Pa`…çÚjŸ¼Sƒ#y‰–i[W!ëwÓñYênÍ{¼˜›»ìd–ßO›é7ÅA²Í§MþW÷ë¾{5ˆ7š49K®Èp%LÝ 4¥œªNþëʇ(‹zÈ_ÃNÕaw;Ë€zbHÝw±Þt®ž`îðösšo½}lÚ÷e|ö÷-úþVšòãL¦ÎÖþ~1Åvå·n;p–f}R›¯>yŽü›Òþìå8“a_GáùÙ¤S§ß±¹*º%Õ/ÙùÙ:MÃ.\žP?[.O¨Ÿ ¯´@Ž–E´Àb´Ú,ÓT·ÕV- F$@$@$@$@uè;Ôñ¦ @['@¥A[o,? ÔC€JƒzÀЛH€H€H€H€H€H€Ú:* Úz `ùI€H€H€H€H€H€H TÔ†Þ$@$@$@$@$@$@$ÐÖ PiÐÖ[ËO$@$@$@$@$@$@õ Ò 0ô&      ¶N€Jƒ¶ÞX~      ¨‡€W=þôn¡òòòZhΙí«!Àz¿zŒK$@$@$@$@$P* ê#ÓBý###[hΙí¦ÈÎÎ뽩ôH€H€H€H µPïÅtî'Àå îgJ‰$@$@$@$@$@$@$Ð*PiÐ*ª‘…       ÷ ÒÀýL)‘H€H€H€H€H€H€Z* ZE5²$@$@$@$@$@$@$à~T¸Ÿ)%’ @« @¥A«¨F‚H€H€H€H€H€H€ÜO€J÷3¥D      h¨4hÕÈB €û Pià~¦”H$@$@$@$@$@$@­‚•­¢Y      p?* ÜÏ”I€H€H€H€H€H€H U Ò UT# AÎ ”dg ý«cÀœ8ô/ü;»ÄyD7†(ü*™ùån”HQ$@$@$@$@$@ÍI€Jƒæ¤KÙ$pÈÿ×A<úÞaèj‚ ìÝöoL}s¿ñüZd´»Þ;ˆû÷ä^‹Ä˜ €x¹AE ´/OÀ˃–׸ý'a( 5ž_›ÚáÞ’:     A€JƒQMîÌd 2å 0&UY»±ïh üýý7 ±!þ–„JòqèèQä•?„„Ç".:D¿®®eU!&.Y©©È)(…!0ÃFÆÁ¿2©û£ ª!±‹TTæ íÐQ(±þÁQ:,V©Z…äasðClpgsýTŸÂÑϲ‘¥ †öƒˆn­²Q.K2p4ç2*=½…Á7¯× ÿ«ÿ 2øWdc÷\ ã‰nÄû÷¶’ÁC     –D€Jƒ–T[îÈë•|Ìì!82Ÿ·q/£ýQ™¿ sQd—^ØÌ×±mZœvý±Ç^²»*§µ€‡m¬Ú1Ë‘š4V˜æ§&#á©u¶ñ'bÇ®90ª#l¯ñÌÍ”¥Idv%ïǪšPìO|õŒxÏ®Uìÿ7æ?pþ÷&QëTŸÂÊ»°éŠ)¾üþë8 ЇýOÞ)ŠŸ*ì}ÿ È;h@çbØñxõÞ(ÍßZdg$@$@$@$@$@×#îip=ÖJ3ç)¤VO`ÑÛ;žžŽ½[—C†ûØ$Ê\' Q,ߺW»ž¾w#îÿÜ”L}ý»>ô«ß·÷þéÿܧã$€( Â&.ÇΦ‹ßVL ¯ÝÛ‘U) –“Ja6÷~*rÿ‰·‰Ô¢Íx2ù¨J–®™ DÜ;GÞi¶,ð—¥hï)Ë*ñÏ¿ê ƒL‘¤G°ÿ´Ü$ýù°\ÒßÓÃ~ø#ì_2 Ÿÿ –ö%DA.žýkŽV“'GcÿçÇ8(2ŽÌx9OýJ¬´‘°ðüé~] yñ ÀuM€Jƒëºzš'sU"6pâëˆÕç÷ýÃGâ1àçn;.ƒ»Jô¼g5VoxcÃe†¹²%%²–@iÐÞfýûœ÷æ!Ö_>!vÏP-³ONƒ ¥Sð Gâ4ÝÏ §9©Û5놙ó'"ÜP-2«5fš¦ŒÈÙ|ènƧe“ÿlx¿‹î‘òÉç8q²þ7݆ý3Çbû?„OU6¶ŠQÚ‡âÅÿþÞ^ðéŒøÇïÀXñN=|ÜR^ÁXðÿ$Žç…É?’Ú¿rùÕʃŽH€H€H€H€H ¥0+·´Œ3¿WG`ÌHÑX9Cœ*YËÞÛÞAâc›m—1„‹Å€Ù…!.B©Œ®J©"â,z³ÓüŒg¥šp¬{,v àQ¸Y¬¦"ÎÇÕג€ÿäfŒýýçØuü8ÆËŸrq½{ã™ûG5µ¦i»ÿ„G7 óvÕȶ¾R~Ñ;Ô&ŒAz˜Z|‹’i;Þ¬_Å•ŽH€H€H€H€Z* ZRm¹)¯…2v»Í×zto-¸«Æas0fær̨R¦%à%±‰N3O—%‹6¬B”Œ0•ŠÁäªd˜E… Ç÷òëÒ/.íù9ÈŒô‹µIåÉÏ‘$+WÐÞôÇ&&Ïh$@$@$@$@$@×-Z\·Uó}dìŠÙ wÝb$Ï‘= J°mÉì–ìÔÚ´œ‘Ðw*°ÍŸiÆÙÖ×ræ;ñ6/HÍ—Ë~ AÈÜ–„—þ žmޜσG׎€ìi  O Žãþ×*07.Uçs±öëoá!ê¯îHÛ¯üí]± FD"ðʬýW–Å%÷Þì´þœµkWV¦D$@$@$@$@$ÐT4†V+ d™,¶-‘lŒhð‰Æ¢Wfâ!ùÒÁºOi×ÇLÄÓ©xisælËAj¢òV› Xœ`°œèƒH³¯‡Zén²hÁù æ>…Í/-À_†Ê×’ÃÍQxð}ðÁíÅÜß„Uyy˜)šóꀗdYJ„20ùñxlÇßðÈßò±|ÿ1ír­(–&ŽCBdG9/×üýó@uhúÒG2éG$@$@$@$@$pmxÔŠ»6I1•æ&ÈÈH7%#ßQ/Td¦Ùø‰ÅÊ’ÙñÎÆÓ&§SYYµG¢Á ²šœ‰V±iõ^#K’±¨&û޳l^X-õ^ó­Ðñ”/$8Ú‰ F¾|Q¡]÷÷WÊ:    ¸>4í½øúÈ{sçâôéÓ@»víàááOOOíW+?åÔ±rJEðÝwßi ¥†…ÿêð|NÑzØè£Ù°× ÙXŸ«W<46M†·&P‚ô?Œ·Õ~½»Z*ˆ·Ôyƒ[WxÉR†ú6Ñ´NƒÇ$@$@$@$@$@­•­¡Yh ò,ÿ×yœå/ÞÖ·11–H€H€H€H€H  Ò U8‹Kè…?%E 8%ÀO.:EÄ$@$@$@$@$@$@$Ð6 PiÐ6ë¥&      §¨4pŠˆH€H€H€H€H€H€H m Ò mÖ;KM$@$@$@$@$@$@N Pià @Û$@¥AÛ¬w–šH€H€H€H€H€H€œ ÒÀ)"       ¶I€Jƒ¶Yï,5 8%@¥SD @$@$@$@$@$@$@m“€WÛ,vë-uvvvë-KV/Ö{½hxH€H€H€H€Hà*Pipð®Ç¨‘‘‘×c¶˜'     hVœHk¼\žÐ<\)•H€H€H€H€H€H€Z<* Z|²$@$@$@$@$@$@$Ð<¨4h®”J$@$@$@$@$@$@-ž•-¾ Y      hT4WJ%      O€Jƒ_…, 4* š‡+¥’ @‹'@¥A‹¯B€H€H€H€H€H€H€š‡•ÍÕRI€H€H€H€H€H€H Å Ò ÅW! @$@$@$@$@$@$@ÍC€JƒæáJ©$@$@$@$@$@$@$Ðâ xµø°M"p%?ENFŠƒŽØ¸8„ù4I#‘ ´>T´¾:uZ¢œ]‹qï‚¿: 7qùVÌîð=I€H€H€H€H€H€Ú.Oh[õÚü]…Am î™8ï‰3SØ<ÿ1ì˯5Ÿó€H€H€H€H€H€H í ¥A«ûª’Rs‰g&ïÀ´Xµa&]Œe}àQ„CE¥⯇+ÉÇ¡ÔCÈ,* Âcãd–Q’Ÿ‰¬‚RøG!ÚÇägð FltJr$ŒÈ ‹A0r°ï“C@øHŒ 9•È?z‡²rP„È2‰˜¸Xج’¨,ÄÑC‡SP$a‚7Tò`ÌŸ9'<      p7* ÜMôº—WiÎá+‹W xþDÄE…#dì<쌛‰ªª*øêòÂC›0î±—ÌáMµca_R¼ ñ+ñ''#)œù6vM‹Õ‚äï›Ç^ÊEm×§±ow"R¦OÆKL±õß°™q¢4(Ķ'ÇaEší5 Ë·nÂØpTæï䄹$l\ÜÓ¯ãõI ›‹P¾Ú©ù_` èƒÌm Ì ƒ¸‰‹±jù<Äi+#r1ú6”ÔæcIa8Ë×¾ŽEuEÁ¡—ö̳L ¸Ÿ•îgz}Kô GÒ?·Êà{¨m>‹aóйHøq"RóeÉ@ÚnòЃ¬Ø± ñ#GbRR2æ'÷Ó’‹Wh` &{(,ߺ»v¥#)ÞÝ Ë”š„µsîÁȱ‰X´z" e¥a³MǰØ(Œœ8ݨX6Ê7^å @sàò„æ z=ˬ¬”Á~8â缂ø™%ÈÏÉÂá´}xeÝfiùÎÅ’Íx=F_OP‹{¢L”óGÜ=¢l8”†ÚÜKøŸèÞê¿)ˆÅ§îÑàéiÚ‹àJ² %ˆèÆÜcŽ2r¶ÉŸr•9Û´_õoóS÷š*Žr9ɇP9)ÚW÷å      w ÒÀ]$[„œJlŸôcm‚ÚÀ™Ø·kB¢ã´¿{&&bñïÅ_¥E©™( W#zåŠPU)k|ô‘zeI•æë.*‹+Ì)µœx[­ân³(¬ý‹Ju™š_‰l”(éWʆ‡±Fí€\3o9Æø©m%+¢ Ý<üõ(üG$@$@$@$@$@$Ð<¸<¡y¸^§R}Ð/Q__àQ´sWïC~‰XÈ<çP*ÒŒ¹ÄÑÎ<ÄwÃî,ýÊå£X·Ú¸¤ (PÛ¯ÀTМÍ)ÈQÊ…ÊlK6I2]uð+ûŒ ×ýÓW$#SÅ•À¾Õa¿ U_sĨðXY¾0cÇCå¡¿bÆ 8ZåO+3! €û ÐÒÀýL¯k‰Qc¦#L6Ì•\Ú,{l®›Ý§Å" < O‡­€|»—LÆîubt Œ“ÿ‹æ‘»n¸M”ò9D±Q¸÷ÇÊNÁUç;?¤‡^ÒâN–†À "- %!üé‰Çò;ÿÖ=–€ua,ÊÕ—QÈþOF»šÑ 4- š­%GñŠÃ¦ocâа:Ũ “/ÈF…êS‡jÿ‚I›v`Þ=ÆpJa \àP,{'âµ0ò„Äy˜h%J}iaæLÝJAÊzÀÊ)±VÎ?vv¼>O>°(Îâ0¸çéµH–½ ÔF cWìÅrÓ¦&…A`m؈‘!F †•L’ ¸€G­8÷‰£¤ï“@vv6"##]ÏBe JdyB‰Äð÷÷—O-ÚêM’T8mÛƒ„s¦²¤*ˆAä8aæø·Dâ+Wo|YBQR¥¥PoK¦/ @[ ÐèñP[€b,ãéÓ§€víÚÁÃÞžžÚ¯:V~Ê©c唊à»ï¾Óþ***Àå –6úÏG) äÏYñµp òi¢²À$U)-t¢Ð¨W©Ñ`D^$     h*.Oh*9Æ#      VN€JƒV^Á, 4••M%Çx$@$@$@$@$@$@$ÐÊ PiÐÊ+˜Å#      ¦ Ò ©äH€H€H€H€H€H€Z9* Zy³x$@$@$@$@$@$@$ÐTT4•ã‘ @+'@¥A+¯`H€H€H€H€H€H€šJ€Jƒ¦’c<      hå¨4håÌâ‘ @S PiÐTrŒG$@$@$@$@$@$@­œ€W+/_›+^vvv›+3 L$@$@$@$@$@$Ð<hiÐ<\)•H€H€H€H€H€H€Z<Z´ø*´-@hh¨­ÏH€H€H€H€H€H  ÈËËk¥¼öE¤¥ÁµgÎI€H€H€H€H€H€H E Ò ET3I$@$@$@$@$@$@מ•מ9S$      A€JƒQMÌ$ \{T\{æL‘H€H€H€H€H€H€Z* ZD51“$@$@$@$@$@$@$pí Pipí™3E      h¼ZD.™I÷ø®µ5ߣ¶Ö½r)H€H€H€H€H€ÜMÀÇÛÝ)¯¨4h¬VT¨®G«( A$@$@$@$@$@$М¸<¡9é^‡²kkj®Ã\1K$@$@$@$@$@$@×#* ®ÇZiÆÿ<Šâ.a.”T9D êVûkT=;ÕO=ïeMˆÙ¢”©:–zi“®6×x\_uw=ö«®×buåyœÉ¿hó\¨F•͹ëÒš²üjûÙ¦%ËXB ¼äâÕ?ç¬H^_÷¦UÆ®ò°µsrå§LÚŠûž˜­ûýÂYss/Kg©ñz['@¥A[oRþ²›qtØ$deºÖŸúã<äŒ|ÔåðÍ‹ø"N¤ü‡ã¦!sÒæº¢+_ɵIÿ޹XÞ¦å¿ Yó¦â›±³pJþ¾6§šøB[–½‡‡=ŠSñ³+ÜnúÊA–ÊðŸIJ½,Âiût„ÁQa ý5¢ž$Ò$¯3»—áÔ=sqÞ*_ygàðÛúÒümýš”à5”ÿª:ÞšÛÄTË“žŽ3%MŒþ½Fk Í}¯ùr5ñ«­;WÓq-ܵìWÏMGN¾ký½k¹¯Â‰9³¿åæeøïÓêé¯ÌÜvPvâ]üGúÙ«~ÆmÂ)¨aòŒÉù¾‰w­ÿvÞ×__÷fÃ…oÄÕVÍɵwžryßTm%+%§àêêèý¢þÐ×êJ¾¾sŽl=Þ¬ º›e³f–Â[n„Ø*ªñÚ"`p.Oü/:ú^Û„¤vjÓ,¯6ÚH:Ð>]^™‰ï rM&áÛ|q%ù7¨H<•_s¹’ÿ¢tw5<ÿ½G† 4§ >MKìÜöÍñ‡è’ò$ü*óQô‚|á;359ÑÞ>öaè–ü¾­ü/.<ö¾ƒ¸Íéu…ód@=éi„[å«ÖêØ”ºæ×ÂÏÒwà[)@§ø›LÅhÜï•\©“—à³é ôŒþþï§Æe¾6×8AßKè«®;7çúÚõ«e(šú*Ÿ|á5±ÝÚ—½äß(M ¯Ü…NÖ×p 7ÝÕwÕö´NŸÇ׆€TßM¯FNqÝ˲„x‰Œ¨4`Sp™@yÉy\.‘°ÿMèùØ`úÛG­úBBBб$y™9ø®ª3:ÇÝä`À\†ÓG¿Æ1§oxB£»¡)Rñ¿WÓwÀëŸ qþ ûü¨óΈ6ÌæBÖ69 nMyhUáŒä»¢R= :¢Óà›ÐÕZzåEœ/ªÆ•LyƒVÎ ¥’™/¿ðp Ý8wA–#Ôâ<ÊRõRr´C7øl_Ë.ä‹"Aèµ ßÁló£%é‹Þ±jpàbgY㜣Çñm•Þá?DŸ¦0²$R–ù‘ÒÕÀoÒ,ž<*+ÌÁ¹œ|iKi+&mÏ–\È·âéÒMÚfòŽæKÝt†_ôMèf׎‹N|…ËEÑä ïÀnè$í¹“%‡ØèàìŠýÀÐT·PÊ\»àh.ª«¤KZÂÃÐ3ÈÂSÝOÊW}ɤVê°ÈßWÒì„nVaLRUž.JžÚº¡ëàlfZýKòLæ—(Ëvá7!"¢³kâÍ¡œ·9­-Ë}ÒÁ®ªÿ(©ò–úëlΛ–¯JÉWP5rR¿Â·ÒB~€Þv}ù¥Â|K¿ú­ôOžÁ7!Ü*ßJF™Èð¬Ê•0@а›à-÷r¡<ÿ}Cn°ª›*É3¿ZRï22^R'öíH+¦´Ý‡Žk÷®oÜ`´óièiX_¿JNæ&£ÔÇÉê¼ñY¦&K¼¤¿T÷¹-y7´qWßy$Ü™¢rxÊ;Y÷i÷£CP=÷‰±Ÿ«•pÂCà]%ïEÞè[÷ª¡÷ WŸQ®¿Ó–GAÖyí½"hp‡næÙÖºõ«¹’²IæäÌÕùQ‘#õ‘4ÂîELZJú[8õ˜¥¨HÅCŽÀu£Í/ÕÖ÷SÕÜEÐJYë²´w¬î+1ñ^5—7_2§[Œ!hß³è£ À«P¸x.Jý‡žEc¨¢´ÿé™iè°õ t5*ÇœsRŒ¤-]0'¥\B0J÷­F„yÀïZ›;½÷÷8;W”*FwIÊV¼õ5DEZ ¿ÅÒBõycÂP»ÛRÏåvm,Oòcÿ²w)nÂ6L±ê3šÖžìû •+ëv 6+uûU ã¼î”$1nˆ“ ‘õ>rMýÓæ×qj³Š'úáÝÏܧ¸vßé1Õ|¾X9£—ÅS?R/λÿ€#+¾63UíàÊîwÐרh+?±ÿ¹÷]›˜—DQžúÉÒçÄZ= įüô'øOÂë*«š3OrÈ{M÷ÝáF_ÓOÃïÎïÀé3ÊÅwÚK™)Ò×;SÉžéÎñcÊ«úuö,×Öåì@þ¤(Ú µiFIÒgÂê½À9Kã³\ÞOó.XÞwìû^ÕwgŒ™‹ª"K>Eí!¶žžÍ„ƒ%HÝ#7÷a*µì䬲R•>%оO©›ú\#í®Q:L¦™üÂ÷½Þ¯ÒJãÈ Tó…Ïë«{hz$ •—ý(´Ú?à›W” üd1H…yEäÉ‹enªÖ]5+©¼Mêå4 ]óRgÌQîÛ 5…Ï+IZ¾ûn)Zs™‰™ö>Lã,ߨûÑ׊‘â-çáûîw<¯·´¾ÿä ‰» >á(|BDŽ’e~¹W‘U¸×Ð7õ t™©æÒ¼Ñ4M ¼€*…Aøn’ò½€™Á¨™¿ÿUšŠ¦8e²¼[´×óF7²ìÆÄäAtQs~­µ%ÕV¢÷­A¸,÷°8×óí-±”Â`âƒÂò5„lú5ºÝ{“YT™ 2”B¨Cò­~Uzá»Ö Ô:9sh@oK#,+(ùy¢0z?ÂD†’›úÂVŒ0¿Ø+1ê~ê›ò´fi`X•„h©CU—½¬Jjð¯QÞ¨û):e¦„ÿ7 W¦ÊìžrôxrœüþÌ÷˜Ì&oLºŽB£Â@)¼\©_ÏM(T~TzáÉ÷‹GŠÍé©ëÎÛ\uaª¦0ð˜øs„‹œA©Ið\Òÿý?ɉ•óð–{H ,s¥Œb./õôús¶jã§¶.ÓžsžF_%Kå+eBMíÀ#UÛß½gd¶DsòÒ|Y½ÃMœ‚P+žÆ« Ô²æQN,Zþø†–V×Å¢=´S^0mrgíÉ•þB¥ä¼_u­îœr’´|c´ô)Óúp¹­x+å¯óûNå\w&Sòã€Óä¯~=Ô¿C_£±î¢·þ\±&5­“ ðò5! ×ó5IЉ´tÞ>¾è*&Ô¾¶nÖåúNN ¯¿†~ƒÅ¬LŽ»kfñ³D£“€JÕË|Ü]è"æ“ꋇj‡š”/­6Ñ2EpãoÉ—¸¸¹3läK¤Êƒ˜®­ïOf¢ß°p-S"†!t•˜ÝîXÍKþù¢“5#ÿNð•sÅ­±NÅó•øÊÌM½ û˹’­?R,Ò|ý;Ë,š˜šg‚-×\=*;¡/#ðœ8~RÖóùÕ²ôâ6-zÙ¡|WÅØ„3›,¾ºN¿6õKÈ®ìÊ–ÄWÌ_­— 4&ßµ"@,Z¢çÞ…žÂ¬gtôvÀ¬:ó¿²1¡þÚß5ÈÞœÓXDiǪ-y&³š}¶¿]€gI1¯S_õð–—p{3ju?uêªßKb6ê+çªXêWÌîßNAað“çE¹W®øß€CÅëoûqΘ\'Y–¢Erÿh®äkmÑsî(óRW9Õª‡‰¿FÌíáZ>ºÆ& ‹(Hz&ŘJÃY›;³/ECû±ýe2C}]¢3|ãeêi(ηz“ª­ÖúÏÅK3V–úHýö±1‹”/¬Ù©·& 6+Ÿº*S[«JÈØ{Ev.Òö2ök¿DðÛ;'u§÷a+ð£EDxüãÚûmÍnÛþ©áöäj¡ÚFÃýªkuç'¥dR}Šzð«6§ú®r/8êºï,HM&Ò?G°ƒ{IoOO£¿±î|#úëc«&ÿ›pcü01£ÕG=ãFhσïJ,Öcg”Å’XôX7Qî]=œê zÚ/s¨-ÄåM¯ ç±P «¾b uŸ– Ë‘2©×—f©/ŸÈ±õ×mªóÓ5k¯U¢¤2šô÷ö0î‘ùuû¡nÍÕªBŒ@¸–'9ô —µÚú%›ÿ²$Dûò¤eJÏR2=¤ó6ç‹Àéò¬I߉3ÆgÍÙ}—È2P‰µ3óÖòõCt“=pÂeÙB`HÝ%½o¿ }£õ¾ÍÛ'Ý'Šl™u¬1gü¢¬Y‰GŸè`tË™éŽöêXzkÅ´ö¼ó‘6`Žl9¨Íå¨&>-ïús ç°Çõ>Å ÒW}o’“j¿å‡Ä¤=)Éü¦ÞKºIŸiºÏÝÛÆ]}çÑû9Ó3ϪևÆNA–øtW}»Oùƺ½™â9}¿pr¸úŒröN[.¼UÎ ¯<Ž­?òEß¹ÏÃÇb|$W]{–›Ê¦~;n«¼XYV%K Ô½gy/PW±”¾JÝ_rß5Ø÷*QbUÐ^{Ö [€‰3ÀCïbÕ‰Ëέ}˜¤Ú1dúï^oî7\Î6+G÷b³&Há-Ÿ€M_è 8þ–îÍ;°>ÛdîÐû85ÒnS>YæL¶½˜ÆœŸÚ÷>j¥ƒlÒ¦uWÎk³+?²}Aë 8xüWŠdiµ½9Ëј276ì·+d9Å ÛXßÉ $´§‰í…Ï0Y®/^­2Jf±º‹éÿÙù;Q/RwžOŠÂgÒh›£çj¾=â~`~q²Ï†ot¢hÚ¿Äå¯ _äÃGL®#d0k—3œÙ¿MÚj0: ·#ç¾Yõsdßû¸4i®fªèqç(t™û Âí9b×õ’Í㟰´+%–úL*´³ìAðt'\x)§çƒ!MÂ*À¨Ü²–ç 'Ëý«âz…«º?/ûxÈ–žòuÍUL›…» Õ².Z½˜X;¯˜zÚ—ñ¾CTX:°Žï2DÆlo¡rS:ª‡Ý…­bé!–A¶é¨8 ×.ÕºS ;¯8jJÿ¤rníêmOì/”Lg}Fƒuç"'ë¼×{܈ûÎbJ>´Þúñ±¾{„exÝÏûž–ÁpQò_Pc2Ç•ÌiV Z&ËPU ¢<ÓÎø'V •«Ó´í‡š•LÖ1Ê2Ūm’´+gH–ÙÁX}HUU’¯]ñ·Î·4}Y§Œ¿~‰¹4…•Z§f¬å„‹Å‹•‡žÜ$ËÊÖÙ¶œ.2“aQÂ9ks=‡% @ž5¥i’¿xY¦¥”Ø·™ÔRVù l»—‰m–do‰œØú>Ê6ÿ[\h׃5e]P|§-ýÓûýØ>Dç•åšRÐÃŽ¥—R>ØqRRÜqo¶uN¦öë%JÁúœ)Œ»Ú¸JÇYÿU_^,þýKÙ†¬½²¶¢¤Í‘ûbâ<³]x¿pá>Pi»òŒ²¾7íßikeåáÛ¦ÀGf,-N¬P¬Òîuû½¢4x eKÏ­’·a¢‡5ËÆp2šGšÓ²JÇìçä V›åé„.bBÞC6V²¼PzK¾ëämœÙŽÌžmƒé3Œùó?B^~*d@â™d±´°„u^wZXY´È°:j°=Y…36©¿hDÝ9çT7OŽ|œÞwÆH ™’;’«ü,mAqf׋¢ü·,ç+§W†Â7\ö¾uò6OŽ" +íÙ•Ãâ)ÀÛï¢âÞ¹øf×zóÞ zj¢® O@·­#L§ÚoGG/èFîæ€W”‡“'”6À6ÇÐãWÀc¤õ½ëíx@ï¬Í‰E†ÿQ$‚ÓQ¢(é~“XÒ¨TK´¤ëù—¯GÎGôφù?—%+?€g~ çæÔÞÞ¶ŠHÇ&®nº7ÉI¯0gíI…rSwG Q2zßù ìѳ •s¥£ö¥„}âo¨#Þéû…)F÷A£ŸQ&™öï´&†6¼½5¥§u_çê³\OÆÞÉ”øÕýZçGIòÕ–=É{Îê×q^yÄÉ’&±žtÒË©Ž Aso:ˆN¯ïŸ€ã¾ûûÏsð=hg?XhŽ<´ï¬ÍfÔŠÙõe™mT&Ù¦¿&wT’Ïvj*/•–Ù)ÛÌ«‡Œ2½ì0}¨™—m¸zÏÄnëÿ·wn±Q\ÙÞÿ·wƒ±;q›k` Ö9øáƒ‘Qæ@"/p¤)9LŽ3Lޤ ˆà}€f’ 0LÐÁHyˆs”€…äÁ~1 ÁáÚM0â;vûŠñ·vuWuU»/ÕvcÚöKvÝöeíßÞU]{ÕÚk»<òÂ[zÑpr£âÖVTÊ™‚àI|`­Ò?•¡]b§B×éš¹´Þ&jk}ݳ#qÐdY®‘L–C94™Ìa…í½ÐËž˜öO“©¿Úû7LÜ(Ò˜¾ø&Wî@©bö\øÒ*ýó0Ô<Ýñøl±<6›ã©¯éybŽýüoßÜ£â·CÌ-!käGÂ~¾œö‰I¿˜$›ÛĬ0P _ÚÅ9þºšiª{õ˯mNU7™`‹R¬µL”m‹0>åÁ™Ö¡æ{û¯ÊJ¦{<¢Â@U ZÐï»ýŸãn´8Áóê+†Cù_gÜ“‘,-ì¶]×YQ¼èAœ5¶Ÿ“gËŠ¢ˆ_®õh–­.wž¶ÚN/Ï']ÎðHý¼±qßiq SòU¦¹ÄÙñ£ñcQˆ³Ôçý³aJV·6 %eò ñYá¿æ©-¡¦½l±${óÿñ®ô…f4/ÿshž~0Vº;Ó gYþÌ_Ö2Åœ_ýŽtœ“~o?ž”'±û@%ÏÌÍ·”5­ÐáË©<{mô¹¼µâïÁW†kËD±¤é‡ƒYu¨ßBwéaÌ_óÎñ౬¨ñD‚ê—ò í?G!µI-šKù½6‡dÞ›#™“Þ;WDî©ÇIV7·cÿ÷»pc—rB«ýÕ|êü‹L%4ß›¼í¾_Ä–$Y¿Q:ˮ˦{Hü-´ûÌï£öËcKÜ«÷OD§È9öø_~£>–)M1¬!ÛäÃEè>î+K²ŸamºF¦oY<ótPiðt¸§d©]g¿Âo¿ÆµÓêïK\«ô%8ȱS­|LV~¾Áí·>„×[#ó=¯Ë¡Rx㾆çß…ŸËEV‘¹é¼Œª|•¸­ÉnØ«ùd÷ÔÒxÇpáyÅ;–e‚6ŠÜòÂvkÇç¸SSoÙ_а_^~V¼Ö÷ëqŸuš†:žx \Tñ=¼ååøY–ÓCzþ‹9»¥paß—òr^+K UáÚg_†œËé‘ãl “åMÖ«9ÙÄâ“Ãf4lú>¯7üÈ®+6½P‹·á“Ò/¤¨¹ÀwÄ„¹I™ùzrŒ/ŽÉ”â<¯êø×ÂEÍ=–ö=}L›ÛìS²è+BL‰ñÂ~÷ÛÏåþ¹‚Z‘û¾·ÞƒÊZL³ÍÔ¾8TáÎý§¤\Uf)ª*5¿³`ÃÙªÁÏfÉïºÜ+5²Œšº7M/(ZÌÌ@ÿÔö_@N±u]NšÒÍ'÷æÆO´vQÞøÕ€Ù¹y‰ý³È÷ÒZ]»6¼ƒË§/â¾È]}©Ue碾Èj¢÷ù—ƒÉ[ÕWáð`Ù_pS|DÔÖˆiõéÏqÓÔµdê+Æ Q*©°lµÕÒ"pvÚNµGøwù¢ë½ˆË¬¦¹tf0;›Ÿ1²´×v p5 S¬è±ÿnȽu§òküxRæA2Ä¿ïTTÝ”|‚îÒHŸÈN&F/’6óÉ3éÛ‹ò÷)Î/ 8Λ Öü§6o]ùª*ä¾’þô³ÈýS¹ù>ªäÔ'ʬ"ÌþL,Ä¡â/k? ù!0rŒ±#i³VHØ¿?”UJß½ŽŸvȪ">±tؘø ?1J2.Ùísºÿ•ÐùÖ«†Ï##c§Û¢5NËŽä¨Ðyô¼²TdՎߣa¯R˜€k1ÌBÚ‰~ü“å3ÿP Î0Oà†~?©ˆ|’•((C>Ä“ýQ¥Y)ÎúÂåKN68IÿÍ)%ÛGpõ¿Kµß3ý·ü®º7Tx}< ï<µßZqô»ú·¸´p-Îÿ.þ½Üx>Ùy¿TRý~$í7*K¦<.RŽDwàÇo¯ÈRâ_ãÂÒ},ªìÿ–¤W–DƒZ7¾cá}~ã§}Þû”ãÈŸ¯ÇÕ·>¨8Hö½Ùpî)o.–], ,Ç>ï°6Ò0Ê0#àÀx­F=‡NÈ×S³è¶Sž(ƒ…v²ö²zpD i¦÷€¼—þ„Ž=Ÿˆ7lq ¸æ{4¸ö½(Sç_¼#åÑ÷\7ÚŽѾ¬®ÝCû¶#²+Λ–î7æ©õ’â:¸*ÆKVßÜÃÏ(¹ý;EQ°ój¿øcpˆÂà¹]¡eÞô4®¬gôÝnÓµ¯Íí—md´¶ˆSµOÏþ#sÇÖ]xnÍ,#úÌ]‡q#k·8®:†ò§1aMûµ|%ñ"gdÜùù£Od/Òª¡˜…¯ wÏuÔÉj Ò´°l…Õ±Sœî-“¯ŽeF8IËÝoö$Kî¶š*´ï?!âÛ2(ª2,01ê®û.°"ÄG¿1)7Bõ ì‰6^¦R´V­Æ¥É¢Í¯¯N÷,m•ŠÆC¿ *ú¨=ò\ØM gœ”õÖÅ?B놡üVd Mœšï¼E«P-ƒkl\…)F¹¡;œ4Õ²%pÕËtŠ5JÑ!z q(7s¹GÛïó/jŸËÇ gwáÇ­bjºí}±±ÐÃ"´¬\h‘[¿m;¡øwèú(÷6”>"T\çQâ…™•OXYŒ‡_TÀ½º¸OöÚ.à´uTÉË¢8ø¤R«WÌ.4=Ä¢ k:ŸÈó”,â®¶³ÏÉ…‰ë^–þù Š ¿–½ ¬ m뾫•BStÌË"Z…*'¬'G&EܤµÄ©è!Ã$Ù¹ù5™bsÝæAHV1fËJ#77ŠrqãnÜÑó™m‹÷í¬[B!sº,‘xð.îl%ؾ"LÜRºgoæ®ðþm;}w”,I8÷¥Pé㬊9-Kí~HÜêì±$¶×çò5…FãÊ’&Êo¥’a¶iM÷ðºfÍÕ¼»7—~…Æ3_ÉÕ¹²zÅ<©«=й[þTžn–)Ú¾Xú]X¼i[·£È´r}ù_p{Óm㹘ÈûE´¢ÂÏÏáÔÿߨÀ3Ñ)«ÓÄ{.%ë·<¼‰ËÒœo­ Ž×0çcÓ‡˜ºïq~ù‡p‹sØ_-°>ÝÕ3£;ÒûÇz†©é áÏxQlØ ÕÕÕ˜>}º¨#.ÎÝ»w1nÜ8¤¥¥ÁápÀétj[µ¯Î© öUP*‚Çkíí톢W»È$0¸Ô²1ƒ[b2JSK¤õg—ŒœS#m ¸ðÁr¢e‹O»!P–õ‡)<­ZÞ/vŒ@ŠÊÈÅS1Ú^ÍåϰÉ1CêMY óÈ–8Éq9û%«<»í¿n®(ÊZ™n#æÕ²¤jãf±@x·¯Â@ež@Û=Ö¨ç“ý¾M~Å;úó¢×d©L—|öT_} bþ(Çm;MˆhœÂ$¹&Ä蟱ÛMÊÐüV„åÙïC%³=Ö±åê·je ÒoT¬>×Vw÷|âëá’8-óÉä}K¤0P•Õž9ƒT7U^Ìß…˜÷ˆJ äbw/Î=®Òf)¯øPqJ„Gg+à]˜1în´–%«É—ðE¯#/ø¬ŠÙbýbrž)öŸ‰Éúm^#;WÄÒg²Ä˼»•s1N¬ö:®~‡Æ-Ê2E¬‘f÷}ëRÏŒX!Ù÷f…A¬ÂymPèÖaƒR yúzEyä mÉÓoJ@©H@æ' µÇC›÷7} ¹B–µ\îIE²ej«¹ˆÖGŒk®=úNa1®rg¤h< Íj:ˆ„Qý˜*3R¸‘ÓPnéÞs×¥M\È\XŒ4wF”úÅn;ˆmBmMrDîé ÒÒ²ò0aÁ,d›sŒ#·ŠªÚØ)ùd´øP}©Fc:Võ°þ¢Ê¬Ú|@R,AÁ–ŰHnâØVs >Õ7ó1¹Øc'©»[êðÀw2Í%M˜MZ`ŠÌ'+«¿Túà*^Œ)®ZÜ‘üÒ&J»„ñT÷A“ª»Kê^VwU¹$»œbÊ”';÷j“»—ªÐ!ý-mâ,LÖo¥þm-µxØ™gs»á+¿‚yf¸ògbZXŽ÷¼°×ç\˜±åÿâò™wи­>.±öÇ$¶³"   ˆO€JƒøŒ†aŒZüøÖ;èØöãÞ )ËXN½ uË߆ú€è>~ÙsÂó2Š‹°Ó…kסµÂz©yçv­œgœü¹ìϨÛYe7{¡æ«e¸¹VY>BspëX¦Ÿ±¿õû>GÍÚ¯poQ6z+‚9 +Tüž Æïý×Ö|dáÔ´g^X>+Xpú-èDªnñóŽ3ÿÀŒÜ`´–+¸¸t7z‚‡ºÜðO7¶Ú®ã¶LÙêeè=sÛÈ ÍÔ.ñåVÉ”ìÒÆã àð…doܸy¿ µ‹Óû9Ú¥ßýìu„§è,›Œž3÷ yšKþˆ[Š ÌÝ3ƃ­¡öU½†‡ViƒK=#ƒÞðÀ¡«´æçÞÃí¢7ömÁÃRƒ"ñrÏþ ÏõQv¹ `ǧø2éõ‹ÇÉÎ}€N.—lCW¨ùѸìuÌØûªe ^_¶µû»ñ è1⾂ çÞub $«ÏrËCáÁWpumj¼Ò®´H@¿cR   NO¿!™ÚõKMa0æèø×sǵ?Ïé0Õ¤0è®+׎’7áQqÊ÷À]ÜÖ?"6‰„Lx¾;Œ9g÷Áí‘tž—‘ö0fÈß섪LƬ~ÙÇ?Àó"Ó‚rÉs¡ bvžBu§º.¡¥< 0X´Óʃr{´+¦òÕYSÈT‰ÓG¥þ¢¼(™¸o ƒ)c×°'è-oÃØ£û¤nû0þã]˜T@ˆy•ÂÀ³D¤Š÷aŒÛ8¶íÀ “ùµ³Pr…AÚæ?b†ÔoÎÉ7ì><òáýè}Maàþ(Ðvù—Ä0Àm·#NI­®=Ûá9ûr?ÚŽÉF»Ø“[ àœ#ÿ” äuiß¿"ÿø»È[cU¨x¿œUJ¨¹Èœ¢5ÕÙ`rì9“#SXkœÆ®¶¥¥¸¯·¯D;k ÆÊ´˜ª}…åøÍ2’­8{Þ`ãóqJ&í[¤1tl~“4V5èæU_qDS¸Dy³@ñ.Û~@Ýÿ+j5£‹Úßmž)yAöjð(˜ö~Å72 NGöÞä©A»h*~uôÝàÕÀ¦­¦R&Tˆ âàïP¨™ªgŠ9ô{pG7—°¤vqJ¬0È1—/“z½~~ï×ZyÎ슡¶¦ÙÅ/jÙøÏ‰)0ôÖËŽ|U/Z[¬}åÍ,,5‰w¯O¹òÅ\âüª8OKõìâßa|‰”¤Òƒí¶ëí–)2àß¹ ó—ÏÙšð\±lƒùØ•[EïU†ò…zΖWñlVžS„i}¾ÖûÑñ…|Õ÷ÌDŽ¡P ¦o¤íÆŸ}…šÙ{&&¾R¬]Ñ-+ÔAvዘ½h^p*M&&-R}@´Ù/uzÆ£L4duž™˜t¡%š™üß+d[€± òÐ$}¼#kƈŽÿû=~QQž@ˆÍI¦‹Ø•)';÷Ä1bk©Trá«/ÓÔª&c<‹´ú£²‹h3׿Wu¹È+ÛO~ŽLÅ™'þ(BÓ‚’ÕçÌE‘§)ñE%B¶ –<    œž0S­ˆÌ9«1®ä"î=ˆš½2äž8îÍÿ‰Bd™JîöuïÀVîeê­ gî<ùÃn™3ï=yþÒ  ²µ"'kµÛÝÜ$_–saq—ž._C¡Wæ„«àœh®m:ÒÔ—þ~‡ÉÈ ›ßžUÏÞÝð osx\¯dÉ7N9òÍ2e`”Çä+¢³Mä;<æ8À¨Ÿ1²0vì¶Ý¨ù¡òĦ;r«èŽ…3/Ϧä–]‡²ˆ‡®ä±\T©³©kyV ú¯íˆï‡÷ô <üø{ôøôi+æÿ±(Я*E¾oŽÜFÃÊ·Ñ`9Y°Fp[N&í >'2Åádç>0*tîî,=aj;âëâCsˆ ÁSŒ q˜$«ÏY…I¨¬DxD$@$@$@ƒI€ïbƒI;eÊÊÄÌ-ûѶ¡*+ððà tnÛëØƒ¢åMÊ^íË¢|¡“õI.³Ï‚tdºM£ºA­S ª–nÃ#ùÊêÚö&2Ì„³¦ u[|†òñ¼o3|KPMU°T# X° ”±lþ·|—µ¦ –ç>*–³Ó ÃÉåŽ`¦oJYk}ÂÍ…n;‡©\m7Q¹-_úÃ3 늻ž¾õï­ëñðŒ(}JV"{kÆdÕàþÚ#‘ ŒqV“Åó <§Þ€»3`qˆ.–1qÇ1²)§þÊÎÉÖ} ’*KÇæí˜¿v&º:C7Cú¥U5%[¢W-i}.JQ•LQâó4 @ò„7’—3sJybŽ^øÒ*ýó0Üb“ÞSøŽ‘é £`ÿÕfdÈ"Óø³Œ´­u ε¶žLüÈ/S&B×PzÿÕ¯µ/ÔîÒØ¿æ7(œãÁãúšPÙSû{h­ ë/}oùjš)¦ÕÊò ë²)mËh÷ÉJÎ';¸dº‚ ]§oÈ” 3˧hŒÎ‡KÚ©÷L•¸ÔC-šNÜLSúÕvzv¦mÒä6ò”è ™"໦ _ã’ÝŽh…Cœ,m)Á ™N‘Ù6ÿÞV^Â~¾DôÉtñ+êß1]û¥­âlEê‡LQòµs`tŽfÁÑ+S"ŠÍÌ Â J9úédõ9=?mÛY£ù^q¬(²8e´Äá À'@¥ÁGœ‚t^GÕñ¯ñ³Ìa®—å ½§¡S쳦AÞKoh&Á]ÞÁåÓq_–¬¾TŽª²sֹΦêõž«€·²Þ«‘qúWfS’>»õåâêÒõ¸|òzŸkj°¯BçÑRÆETíø=öÞ–3!EÆ„ùAçikvãÆU|§?Äí âçÀ²ÄŒ^æ®÷ìÜ¿½"ËÍ} KX æèv÷ÍóîÍiÒó_ĸ2Ø/=€ û¾Ä]á~ß[…kŸ}iqðgNy?9(ïÿ'pãïßá®,+yY­‚á“Øbö¯íOÛE*/yr‡rÏ[úªT¡éZ`ŠHèŠÚ‹¤* Ä0ØŽ,Ø{è®U^Á“ÁÍueZ¤Df6Hºf4/ß,y]—9ý5²œà—²oR&Š—Nw¯Ã¥ÕŸ†MeÐ#$gkO¦øœlÝ2-fò>ñÑð n¿õ!¼Þap7O—ÂÑoI·ÑÇÂk›¬>gÎ×_S¥9ý³Tú< ÀS#`×Jø© È‚“O M^ÆÛ÷Ÿ?hKªâõ¾`Í,Saùxáì.ü¸u·L]x_\ êaZV.ìcº¬ÍU?óå+0<+ñÌ©’°ùíéÚ—ÝöËz>Q¶ÆÒxFYsµUšKU9_Is‘±sÚvúÄ ž*“r÷,xŽ¿.Ë)Ãõ۴B\[_F÷Þofè21{ïvü°Zê¶e7H,gÉ+pHž=&ʼn–Øö¿|Ãña¤$3wƬÝâÈðÈŸdšEÚ¯Å[}¾LV2˜)¥œ3É4eåvø+ßFë!YFð\[´îè¼—iš÷XÛE)U;_îX©û^Ë(| O mÝ1ÔžÛeÏ¢‡Û|¤ŸUÛ “Oƒ<䊇ÿšMß uÃnÍé¥{ïJtm+ÃcÕŒƒ4é‰èU}C ¡oèJ)2ãd7|kŽH^;ЪGY‘¶âü>}\»ì_ b%on¿žU¢[;2Ùâdë>ò^ú:ö|‚Úmß qÍ÷²äd ¸ö½(ý2?$~¯p›PØ„Nš÷’×繪•;ÄÏÂÂ×0­0ö³Ü'   H>G¯„ägËŸ[·naêÔ©±‹6æ-w¡¹¥-°|ŸKVF0U}“û[š ã$¸Éª²êÁ“ÍR^ºxà ï¬%¶ÉõvhLnô89êõâ°q´ä½n]h¨k†SVvÈ$? Ý"W‹6§=][EÂêÀZÏXGÍ-µÒv²âAìöHVÛ%Kn­N-Ųã}Yžqƒ¬¶ðb˜r)V­Mׄcƒô_§L±ÉŽÑwM)bîª>§¬œr/DÎÏk×£õÞkxþÔª¨}3f! ^Œ/“ mÝ*ÅS)êÒá–>»WÅ.wà}® 7÷­Gsi†øTù MSo"–ìîï]17ž$  ª««1}úô!\ƒ''úÝ»w1nÜ8¤¥¥ÁápÀétj[µ¯Î© öUP*‚Çkííí Ò@Ã2<þ%¦4u~Rµèw…ÝJS#¤‹¢Ã•€b\RË9^•/ü'âùBÃ$FЧ|I¦'\ÞYÜoàÙ$()žrmRºø6o)~ZS†±ŸÆìé6ÔT¤t{R8  LTD§M¥At6#ê •IjnY¿þü¯Åô=Np‡Œ l jâä3R/+ëˆcH*^Fjˆ^o¿(ì¢[…ÒõŠ2Üì‹%t…{$@$@$@#‘•Ñ[} Jú4ˆÎ•WF*Ñ3‘ò=±5u5…^™b0ÆC…Á@ºˆZI‚"°£0PéÒÒG Øi¤òyŽH€H€H€H D€Jƒ î‘@€ ÏÎ%  % , 4…ApÞ]ŠŠI±H€H€H€H`X Ò`X4c•àüß`1* @*P.zèÁ7[†2‘ G‰.m>°N$@$@$@$@$@$@$@Pi O‘ È´PB        H¨4ˆD…çH€H€H€H€H€H€H€hiÀ>@$@$@$@$@$@$@$™- "sáY      ñ¨4ñ]€H€H€H€H€H€H€H 2Q‘OóìP%P]]=TE§Ü$@$@$@$@$@$@)F€Jƒkˆ3}úô$gZ      °àô  è¨4ÐIpK$@$@$@$@$@$@$`!@¥H€H€H€H€H€H€H€tTè$¸%      ° ÒÀ‚ƒ$@$@$@$@$@$@$@:* tÜ’ XPi`ÁÁ       •: nI€H€H€H€H€H€H€,¨4°àà €N€J·$@$@$@$@$@$@$@TXpð€H€H€H€H€H€H€H@'@¥N‚[        * ,8x@$@$@$@$@$@$@$  Ò@'Á- €…•<       Ð Pi “à–H€H€H€H€H€H€HÀB€J  è¨4ÐIpK$@$@$@$@$@$@$`!@¥H€H€H€H€H€H€H€tTè$¸%      ° ÒÀ‚ƒ$@$@$@$@$@$@$@:Qú·CŸÀ­[·†~%X     è'éÓ§÷3%“E#@¥A42Cô`. But if your favorite templating system is not supported as a renderer extension for :app:`Pyramid`, you can create your own simple combination as shown above. .. note:: If you use third-party templating languages without cooperating :app:`Pyramid` bindings directly within view callables, the auto-template-reload strategy explained in :ref:`reload_templates_section` will not be available, nor will the template asset overriding capability explained in :ref:`overriding_assets_section` be available, nor will it be possible to use any template using that language as a :term:`renderer`. However, it's reasonably easy to write custom templating system binding packages for use under :app:`Pyramid` so that templates written in the language can be used as renderers. See :ref:`adding_and_overriding_renderers` for instructions on how to create your own template renderer and :ref:`available_template_system_bindings` for example packages. If you need more control over the status code and content-type, or other response attributes from views that use direct templating, you may set attributes on the response that influence these values. Here's an example of changing the content-type and status of the response object returned by :func:`~pyramid.renderers.render_to_response`: .. code-block:: python :linenos: from pyramid.renderers import render_to_response def sample_view(request): response = render_to_response('templates/foo.pt', {'foo':1, 'bar':2}, request=request) response.content_type = 'text/plain' response.status_int = 204 return response Here's an example of manufacturing a response object using the result of :func:`~pyramid.renderers.render` (a string): .. code-block:: python :linenos: from pyramid.renderers import render from pyramid.response import Response def sample_view(request): result = render('mypackage:templates/foo.pt', {'foo':1, 'bar':2}, request=request) response = Response(result) response.content_type = 'text/plain' return response .. index:: single: templates used as renderers single: template renderers single: renderer (template) .. index:: pair: renderer; system values .. _renderer_system_values: System Values Used During Rendering ----------------------------------- When a template is rendered using :func:`~pyramid.renderers.render_to_response` or :func:`~pyramid.renderers.render`, or a ``renderer=`` argument to view configuration (see :ref:`templates_used_as_renderers`), the renderer representing the template will be provided with a number of *system* values. These values are provided to the template: ``request`` The value provided as the ``request`` keyword argument to ``render_to_response`` or ``render`` *or* the request object passed to the view when the ``renderer=`` argument to view configuration is being used to render the template. ``req`` An alias for ``request``. ``context`` The current :app:`Pyramid` :term:`context` if ``request`` was provided as a keyword argument to ``render_to_response`` or ``render``, or ``None`` if the ``request`` keyword argument was not provided. This value will always be provided if the template is rendered as the result of a ``renderer=`` argument to the view configuration being used. ``renderer_name`` The renderer name used to perform the rendering, e.g., ``mypackage:templates/foo.pt``. ``renderer_info`` An object implementing the :class:`pyramid.interfaces.IRendererInfo` interface. Basically, an object with the following attributes: ``name``, ``package``, and ``type``. ``view`` The view callable object that was used to render this template. If the view callable is a method of a class-based view, this will be an instance of the class that the method was defined on. If the view callable is a function or instance, it will be that function or instance. Note that this value will only be automatically present when a template is rendered as a result of a ``renderer=`` argument; it will be ``None`` when the ``render_to_response`` or ``render`` APIs are used. You can define more values which will be passed to every template executed as a result of rendering by defining :term:`renderer globals`. What any particular renderer does with these system values is up to the renderer itself, but most template renderers make these names available as top-level template variables. .. index:: pair: renderer; templates .. _templates_used_as_renderers: Templates Used as Renderers via Configuration --------------------------------------------- An alternative to using :func:`~pyramid.renderers.render_to_response` to render templates manually in your view callable code is to specify the template as a :term:`renderer` in your *view configuration*. This can be done with any of the templating languages supported by :app:`Pyramid`. To use a renderer via view configuration, specify a template :term:`asset specification` as the ``renderer`` argument, or attribute to the :term:`view configuration` of a :term:`view callable`. Then return a *dictionary* from that view callable. The dictionary items returned by the view callable will be made available to the renderer template as top-level names. The association of a template as a renderer for a :term:`view configuration` makes it possible to replace code within a :term:`view callable` that handles the rendering of a template. Here's an example of using a :class:`~pyramid.view.view_config` decorator to specify a :term:`view configuration` that names a template renderer: .. code-block:: python :linenos: from pyramid.view import view_config @view_config(renderer='templates/foo.pt') def my_view(request): return {'foo':1, 'bar':2} .. note:: You do not need to supply the ``request`` value as a key in the dictionary result returned from a renderer-configured view callable. :app:`Pyramid` automatically supplies this value for you, so that the "most correct" system values are provided to the renderer. .. warning:: The ``renderer`` argument to the ``@view_config`` configuration decorator shown above is the template *path*. In the example above, the path ``templates/foo.pt`` is *relative*. Relative to what, you ask? Because we're using a Chameleon renderer, it means "relative to the directory in which the file that defines the view configuration lives". In this case, this is the directory containing the file that defines the ``my_view`` function. Similar renderer configuration can be done imperatively. See :ref:`views_which_use_a_renderer`. .. seealso:: See also :ref:`built_in_renderers`. Although a renderer path is usually just a simple relative pathname, a path named as a renderer can be absolute, starting with a slash on UNIX or a drive letter prefix on Windows. The path can alternatively be an :term:`asset specification` in the form ``some.dotted.package_name:relative/path``, making it possible to address template assets which live in another package. Not just any template from any arbitrary templating system may be used as a renderer. Bindings must exist specifically for :app:`Pyramid` to use a templating language template as a renderer. .. sidebar:: Why Use a Renderer via View Configuration Using a renderer in view configuration is usually a better way to render templates than using any rendering API directly from within a :term:`view callable` because it makes the view callable more unit-testable. Views which use templating or rendering APIs directly must return a :term:`Response` object. Making testing assertions about response objects is typically an indirect process, because it means that your test code often needs to somehow parse information out of the response body (often HTML). View callables configured with renderers externally via view configuration typically return a dictionary, as above. Making assertions about results returned in a dictionary is almost always more direct and straightforward than needing to parse HTML. By default, views rendered via a template renderer return a :term:`Response` object which has a *status code* of ``200 OK``, and a *content-type* of ``text/html``. To vary attributes of the response of a view that uses a renderer, such as the content-type, headers, or status attributes, you must use the API of the :class:`pyramid.response.Response` object exposed as ``request.response`` within the view before returning the dictionary. See :ref:`request_response_attr` for more information. The same set of system values are provided to templates rendered via a renderer view configuration as those provided to templates rendered imperatively. See :ref:`renderer_system_values`. .. index:: pair: debugging; templates .. _debugging_templates: Debugging Templates ------------------- A :exc:`NameError` exception resulting from rendering a template with an undefined variable (e.g. ``${wrong}``) might end up looking like this: .. code-block:: text RuntimeError: Caught exception rendering template. - Expression: ``wrong`` - Filename: /home/fred/env/proj/proj/templates/mytemplate.pt - Arguments: renderer_name: proj:templates/mytemplate.pt template: xincludes: request: project: proj macros: context: view: NameError: wrong The output tells you which template the error occurred in, as well as displaying the arguments passed to the template itself. .. index:: single: automatic reloading of templates single: template automatic reload .. _reload_templates_section: Automatically Reloading Templates --------------------------------- It's often convenient to see changes you make to a template file appear immediately without needing to restart the application process. :app:`Pyramid` allows you to configure your application development environment so that a change to a template will be automatically detected, and the template will be reloaded on the next rendering. .. warning:: Auto-template-reload behavior is not recommended for production sites as it slows rendering slightly; it's usually only desirable during development. In order to turn on automatic reloading of templates, you can use an environment variable or a configuration file setting. To use an environment variable, start your application under a shell using the ``PYRAMID_RELOAD_TEMPLATES`` operating system environment variable set to ``1``, For example: .. code-block:: text $ PYRAMID_RELOAD_TEMPLATES=1 $VENV/bin/pserve myproject.ini To use a setting in the application ``.ini`` file for the same purpose, set the ``pyramid.reload_templates`` key to ``true`` within the application's configuration section, e.g.: .. code-block:: ini :linenos: [app:main] use = egg:MyProject pyramid.reload_templates = true .. index:: single: template system bindings single: Chameleon single: Jinja2 single: Mako .. _available_template_system_bindings: 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 | 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: http://docs.pylonsproject.org/projects/pyramid-chameleon/en/latest/ .. _Jinja2: http://jinja.pocoo.org/docs/ .. _pyramid_jinja2: http://docs.pylonsproject.org/projects/pyramid-jinja2/en/latest/ .. _Mako: http://www.makotemplates.org/ .. _pyramid_mako: http://docs.pylonsproject.org/projects/pyramid-mako/en/latest/ pyramid-1.6/docs/narr/testing.rst0000644000076500000240000004412412634676134017674 0ustar michaelstaff00000000000000.. index:: single: unit testing single: integration testing single: functional testing .. _testing_chapter: Unit, Integration, and Functional Testing ========================================= *Unit testing* is, not surprisingly, the act of testing a "unit" in your application. In this context, a "unit" is often a function or a method of a class instance. The unit is also referred to as a "unit under test". The goal of a single unit test is to test **only** some permutation of the "unit under test". If you write a unit test that aims to verify the result of a particular codepath through a Python function, you need only be concerned about testing the code that *lives in the function body itself*. If the function accepts a parameter that represents a complex application "domain object" (such as a resource, a database connection, or an SMTP server), the argument provided to this function during a unit test *need not be* and likely *should not be* a "real" implementation object. For example, although a particular function implementation may accept an argument that represents an SMTP server object, and the function may call a method of this object when the system is operating normally that would result in an email being sent, a unit test of this codepath of the function does *not* need to test that an email is actually sent. It just needs to make sure that the function calls the method of the object provided as an argument that *would* send an email if the argument happened to be the "real" implementation of an SMTP server object. An *integration test*, on the other hand, is a different form of testing in which the interaction between two or more "units" is explicitly tested. Integration tests verify that the components of your application work together. You *might* make sure that an email was actually sent in an integration test. A *functional test* is a form of integration test in which the application is run "literally". You would *have to* make sure that an email was actually sent in a functional test, because it tests your code end to end. It is often considered best practice to write each type of tests for any given codebase. Unit testing often provides the opportunity to obtain better "coverage": it's usually possible to supply a unit under test with arguments and/or an environment which causes *all* of its potential codepaths to be executed. This is usually not as easy to do with a set of integration or functional tests, but integration and functional testing provides a measure of assurance that your "units" work together, as they will be expected to when your application is run in production. The suggested mechanism for unit and integration testing of a :app:`Pyramid` application is the Python :mod:`unittest` module. Although this module is named :mod:`unittest`, it is actually capable of driving both unit and integration tests. A good :mod:`unittest` tutorial is available within `Dive Into Python `_ by Mark Pilgrim. :app:`Pyramid` provides a number of facilities that make unit, integration, and functional tests easier to write. The facilities become particularly useful when your code calls into :app:`Pyramid`-related framework functions. .. index:: single: test setup single: test tear down single: unittest .. _test_setup_and_teardown: Test Set Up and Tear Down ------------------------- :app:`Pyramid` uses a "global" (actually :term:`thread local`) data structure to hold two items: the current :term:`request` and the current :term:`application registry`. These data structures are available via the :func:`pyramid.threadlocal.get_current_request` and :func:`pyramid.threadlocal.get_current_registry` functions, respectively. See :ref:`threadlocals_chapter` for information about these functions and the data structures they return. If your code uses these ``get_current_*`` functions or calls :app:`Pyramid` code which uses ``get_current_*`` functions, you will need to call :func:`pyramid.testing.setUp` in your test setup and you will need to call :func:`pyramid.testing.tearDown` in your test teardown. :func:`~pyramid.testing.setUp` pushes a registry onto the :term:`thread local` stack, which makes the ``get_current_*`` functions work. It returns a :term:`Configurator` object which can be used to perform extra configuration required by the code under test. :func:`~pyramid.testing.tearDown` pops the thread local stack. Normally when a Configurator is used directly with the ``main`` block of a Pyramid application, it defers performing any "real work" until its ``.commit`` method is called (often implicitly by the :meth:`pyramid.config.Configurator.make_wsgi_app` method). The Configurator returned by :func:`~pyramid.testing.setUp` is an *autocommitting* Configurator, however, which performs all actions implied by methods called on it immediately. This is more convenient for unit testing purposes than needing to call :meth:`pyramid.config.Configurator.commit` in each test after adding extra configuration statements. The use of the :func:`~pyramid.testing.setUp` and :func:`~pyramid.testing.tearDown` functions allows you to supply each unit test method in a test case with an environment that has an isolated registry and an isolated request for the duration of a single test. Here's an example of using this feature: .. code-block:: python :linenos: import unittest from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() The above will make sure that :func:`~pyramid.threadlocal.get_current_registry` called within a test case method of ``MyTest`` will return the :term:`application registry` associated with the ``config`` Configurator instance. Each test case method attached to ``MyTest`` will use an isolated registry. The :func:`~pyramid.testing.setUp` and :func:`~pyramid.testing.tearDown` functions accept 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 :term:`request` object into the :func:`pyramid.testing.setUp` within the ``setUp`` method of your test: .. code-block:: python :linenos: import unittest from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): request = testing.DummyRequest() self.config = testing.setUp(request=request) def tearDown(self): testing.tearDown() If you pass a :term:`request` object into :func:`pyramid.testing.setUp` within your test case's ``setUp``, any test method attached to the ``MyTest`` test case that directly or indirectly calls :func:`~pyramid.threadlocal.get_current_request` will receive the request object. Otherwise, during testing, :func:`~pyramid.threadlocal.get_current_request` will return ``None``. We use a "dummy" request implementation supplied by :class:`pyramid.testing.DummyRequest` because it's easier to construct than a "real" :app:`Pyramid` request object. Test setup using a context manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An alternative style of setting up a test configuration is to use the ``with`` statement and :func:`pyramid.testing.testConfig` to create a context manager. The context manager will call :func:`pyramid.testing.setUp` before the code under test and :func:`pyramid.testing.tearDown` afterwards. This style is useful for small self-contained tests. For example: .. code-block:: python :linenos: import unittest class MyTest(unittest.TestCase): def test_my_function(self): from pyramid import testing with testing.testConfig() as config: config.add_route('bar', '/bar/{id}') my_function_which_needs_route_bar() What? ~~~~~ Thread local data structures are always a bit confusing, especially when they're used by frameworks. Sorry. So here's a rule of thumb: if you don't *know* whether you're calling code that uses the :func:`~pyramid.threadlocal.get_current_registry` or :func:`~pyramid.threadlocal.get_current_request` functions, or you don't care about any of this, but you still want to write test code, just always call :func:`pyramid.testing.setUp` in your test's ``setUp`` method and :func:`pyramid.testing.tearDown` in your tests' ``tearDown`` method. This won't really hurt anything if the application you're testing does not call any ``get_current*`` function. .. index:: single: pyramid.testing single: Configurator testing API Using the ``Configurator`` and ``pyramid.testing`` APIs in Unit Tests --------------------------------------------------------------------- The ``Configurator`` API and the :mod:`pyramid.testing` module provide a number of functions which can be used during unit testing. These functions make :term:`configuration declaration` calls to the current :term:`application registry`, but typically register a "stub" or "dummy" feature in place of the "real" feature that the code would call if it was being run normally. For example, let's imagine you want to unit test a :app:`Pyramid` view function. .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPForbidden def view_fn(request): if request.has_permission('edit'): raise HTTPForbidden return {'greeting':'hello'} .. note:: This code implies that you have defined a renderer imperatively in a relevant :class:`pyramid.config.Configurator` instance, otherwise it would fail when run normally. Without doing anything special during a unit test, the call to :meth:`~pyramid.request.Request.has_permission` in this view function will always return a ``True`` value. When a :app:`Pyramid` application starts normally, it will populate an :term:`application registry` using :term:`configuration declaration` calls made against a :term:`Configurator`. But if this application registry is not created and populated (e.g., by initializing the configurator with an authorization policy), like when you invoke application code via a unit test, :app:`Pyramid` API functions will tend to either fail or return default results. So how do you test the branch of the code in this view function that raises :exc:`~pyramid.httpexceptions.HTTPForbidden`? The testing API provided by :app:`Pyramid` allows you to simulate various application registry registrations for use under a unit testing framework without needing to invoke the actual application configuration implied by its ``main`` function. For example, if you wanted to test the above ``view_fn`` (assuming it lived in the package named ``my.package``), you could write a :class:`unittest.TestCase` that used the testing API. .. code-block:: python :linenos: import unittest from pyramid import testing class MyTest(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_view_fn_forbidden(self): from pyramid.httpexceptions import HTTPForbidden from my.package import view_fn self.config.testing_securitypolicy(userid='hank', permissive=False) request = testing.DummyRequest() request.context = testing.DummyResource() self.assertRaises(HTTPForbidden, view_fn, request) def test_view_fn_allowed(self): from my.package import view_fn self.config.testing_securitypolicy(userid='hank', permissive=True) request = testing.DummyRequest() request.context = testing.DummyResource() response = view_fn(request) self.assertEqual(response, {'greeting':'hello'}) In the above example, we create a ``MyTest`` test case that inherits from :class:`unittest.TestCase`. If it's in our :app:`Pyramid` application, it will be found when ``setup.py test`` is run. It has two test methods. The first test method, ``test_view_fn_forbidden`` tests the ``view_fn`` when the authentication policy forbids the current user the ``edit`` permission. Its third line registers a "dummy" "non-permissive" authorization policy using the :meth:`~pyramid.config.Configurator.testing_securitypolicy` method, which is a special helper method for unit testing. We then create a :class:`pyramid.testing.DummyRequest` object which simulates a WebOb request object API. A :class:`pyramid.testing.DummyRequest` is a request object that requires less setup than a "real" :app:`Pyramid` request. We call the function being tested with the manufactured request. When the function is called, :meth:`pyramid.request.Request.has_permission` will call the "dummy" authentication policy we've registered through :meth:`~pyramid.config.Configurator.testing_securitypolicy`, which denies access. We check that the view function raises a :exc:`~pyramid.httpexceptions.HTTPForbidden` error. The second test method, named ``test_view_fn_allowed``, tests the alternate case, where the authentication policy allows access. Notice that we pass different values to :meth:`~pyramid.config.Configurator.testing_securitypolicy` to obtain this result. We assert at the end of this that the view function returns a value. Note that the test calls the :func:`pyramid.testing.setUp` function in its ``setUp`` method and the :func:`pyramid.testing.tearDown` function in its ``tearDown`` method. We assign the result of :func:`pyramid.testing.setUp` as ``config`` on the unittest class. This is a :term:`Configurator` object and all methods of the configurator can be called as necessary within tests. If you use any of the :class:`~pyramid.config.Configurator` APIs during testing, be sure to use this pattern in your test case's ``setUp`` and ``tearDown``; these methods make sure you're using a "fresh" :term:`application registry` per test run. See the :ref:`testing_module` chapter for the entire :app:`Pyramid`-specific testing API. This chapter describes APIs for registering a security policy, registering resources at paths, registering event listeners, registering views and view permissions, and classes representing "dummy" implementations of a request and a resource. .. seealso:: See also the various methods of the :term:`Configurator` documented in :ref:`configuration_module` that begin with the ``testing_`` prefix. .. index:: single: integration tests .. _integration_tests: Creating Integration Tests -------------------------- In :app:`Pyramid`, a *unit test* typically relies on "mock" or "dummy" 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 exercises the functionality of the code under test *and* its integration with the rest of the :app:`Pyramid` framework. 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`. .. seealso:: See also :ref:`including_configuration` 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 .. _functional_tests: Creating Functional Tests ------------------------- Functional tests test your literal application. In Pyramid, functional tests are typically written using the :term:`WebTest` package, which provides APIs for invoking HTTP(S) requests to your application. Regardless of which testing :term:`package` you use, ensure to add a ``tests_require`` dependency on that package to your application's ``setup.py`` file. Using the project ``MyProject`` generated by the starter scaffold as described in :doc:`project`, we would insert the following code immediately following the ``requires`` block in the file ``MyProject/setup.py``. .. code-block:: ini :linenos: :lineno-start: 11 :emphasize-lines: 8- requires = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'waitress', ] test_requires = [ 'webtest', ] Remember to change the dependency. .. code-block:: ini :linenos: :lineno-start: 39 :emphasize-lines: 2 install_requires=requires, tests_require=test_requires, test_suite="myproject", As always, whenever you change your dependencies, make sure to run the following command. .. code-block:: bash $VENV/bin/python setup.py develop In your ``MyPackage`` project, your :term:`package` is named ``myproject`` which contains a ``views`` module, which in turn contains a :term:`view` function ``my_view`` that returns an HTML body when the root URL is invoked: .. literalinclude:: MyProject/myproject/views.py :linenos: :language: python The following example functional test demonstrates invoking the above :term:`view`: .. 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 ``Pyramid``. See the :term:`WebTest` documentation for further information about the methods available to a :class:`webtest.app.TestApp` instance. pyramid-1.6/docs/narr/threadlocals.rst0000644000076500000240000001766212622227523020662 0ustar michaelstaff00000000000000.. index:: single: thread locals single: get_current_request single: get_current_registry .. _threadlocals_chapter: Thread Locals ============= A :term:`thread local` variable is a variable that appears to be a "global" variable to an application which uses it. However, unlike a true global variable, one thread or process serving the application may receive a different value than another thread or process when that variable is "thread local". When a request is processed, :app:`Pyramid` makes two :term:`thread local` variables available to the application: a "registry" and a "request". Why and How :app:`Pyramid` Uses Thread Local Variables ------------------------------------------------------ How are thread locals beneficial to :app:`Pyramid` and application developers who use :app:`Pyramid`? Well, usually they're decidedly **not**. Using a global or a thread local variable in any application usually makes it a lot harder to understand for a casual reader. Use of a thread local or a global is usually just a way to avoid passing some value around between functions, which is itself usually a very bad idea, at least if code readability counts as an important concern. For historical reasons, however, thread local variables are indeed consulted by various :app:`Pyramid` API functions. For example, the implementation of the :mod:`pyramid.security` function named :func:`~pyramid.security.authenticated_userid` (deprecated as of 1.5) retrieves the thread local :term:`application registry` as a matter of course to find an :term:`authentication policy`. It uses the :func:`pyramid.threadlocal.get_current_registry` function to retrieve the application registry, from which it looks up the authentication policy; it then uses the authentication policy to retrieve the authenticated user id. This is how :app:`Pyramid` allows arbitrary authentication policies to be "plugged in". When they need to do so, :app:`Pyramid` internals use two API functions to retrieve the :term:`request` and :term:`application registry`: :func:`~pyramid.threadlocal.get_current_request` and :func:`~pyramid.threadlocal.get_current_registry`. The former returns the "current" request; the latter returns the "current" registry. Both ``get_current_*`` functions retrieve an object from a thread-local data structure. These API functions are documented in :ref:`threadlocal_module`. These values are thread locals rather than true globals because one Python process may be handling multiple simultaneous requests or even multiple :app:`Pyramid` applications. If they were true globals, :app:`Pyramid` could not handle multiple simultaneous requests or allow more than one :app:`Pyramid` application instance to exist in a single Python process. Because one :app:`Pyramid` application is permitted to call *another* :app:`Pyramid` application from its own :term:`view` code (perhaps as a :term:`WSGI` app with help from the :func:`pyramid.wsgi.wsgiapp2` decorator), these variables are managed in a *stack* during normal system operations. The stack instance itself is a :class:`threading.local`. During normal operations, the thread locals stack is managed by a :term:`Router` object. At the beginning of a request, the Router pushes the application's registry and the request on to the stack. At the end of a request, the stack is popped. The topmost request and registry on the stack are considered "current". Therefore, when the system is operating normally, the very definition of "current" is defined entirely by the behavior of a pyramid :term:`Router`. However, during unit testing, no Router code is ever invoked, and the definition of "current" is defined by the boundary between calls to the :meth:`pyramid.config.Configurator.begin` and :meth:`pyramid.config.Configurator.end` methods (or between calls to the :func:`pyramid.testing.setUp` and :func:`pyramid.testing.tearDown` functions). These functions push and pop the threadlocal stack when the system is under test. See :ref:`test_setup_and_teardown` for the definitions of these functions. Scripts which use :app:`Pyramid` machinery but never actually start a WSGI server or receive requests via HTTP, such as scripts which use the :mod:`pyramid.scripting` API, will never cause any Router code to be executed. However, the :mod:`pyramid.scripting` APIs also push some values on to the thread locals stack as a matter of course. Such scripts should expect the :func:`~pyramid.threadlocal.get_current_request` function to always return ``None``, and should expect the :func:`~pyramid.threadlocal.get_current_registry` function to return exactly the same :term:`application registry` for every request. Why You Shouldn't Abuse Thread Locals ------------------------------------- You probably should almost never use the :func:`~pyramid.threadlocal.get_current_request` or :func:`~pyramid.threadlocal.get_current_registry` functions, except perhaps in tests. In particular, it's almost always a mistake to use ``get_current_request`` or ``get_current_registry`` in application code because its usage makes it possible to write code that can be neither easily tested nor scripted. Inappropriate usage is defined as follows: - ``get_current_request`` should never be called within the body of a :term:`view callable`, or within code called by a view callable. View callables already have access to the request (it's passed in to each as ``request``). - ``get_current_request`` should never be called in :term:`resource` code. If a resource needs access to the request, it should be passed the request by a :term:`view callable`. - ``get_current_request`` function should never be called because it's "easier" or "more elegant" to think about calling it than to pass a request through a series of function calls when creating some API design. Your application should instead, almost certainly, pass around data derived from the request rather than relying on being able to call this function to obtain the request in places that actually have no business knowing about it. Parameters are *meant* to be passed around as function arguments; this is why they exist. Don't try to "save typing" or create "nicer APIs" by using this function in the place where a request is required; this will only lead to sadness later. - Neither ``get_current_request`` nor ``get_current_registry`` should ever be called within application-specific forks of third-party library code. The library you've forked almost certainly has nothing to do with :app:`Pyramid`, and making it dependent on :app:`Pyramid` (rather than making your :app:`pyramid` application depend upon it) means you're forming a dependency in the wrong direction. Use of the :func:`~pyramid.threadlocal.get_current_request` function in application code *is* still useful in very limited circumstances. As a rule of thumb, usage of ``get_current_request`` is useful **within code which is meant to eventually be removed**. For instance, you may find yourself wanting to deprecate some API that expects to be passed a request object in favor of one that does not expect to be passed a request object. But you need to keep implementations of the old API working for some period of time while you deprecate the older API. So you write a "facade" implementation of the new API which calls into the code which implements the older API. Since the new API does not require the request, your facade implementation doesn't have local access to the request when it needs to pass it into the older API implementation. After some period of time, the older implementation code is disused and the hack that uses ``get_current_request`` is removed. This would be an appropriate place to use the ``get_current_request``. Use of the :func:`~pyramid.threadlocal.get_current_registry` function should be limited to testing scenarios. The registry made current by use of the :meth:`pyramid.config.Configurator.begin` method during a test (or via :func:`pyramid.testing.setUp`) when you do not pass one in is available to you via this API. pyramid-1.6/docs/narr/traversal.rst0000644000076500000240000005660412621241570020214 0ustar michaelstaff00000000000000.. _traversal_chapter: Traversal ========= This chapter explains the technical details of how traversal works in Pyramid. For a quick example, see :doc:`hellotraversal`. For more about *why* you might use traversal, see :doc:`muchadoabouttraversal`. A :term:`traversal` uses the URL (Universal Resource Locator) to find a :term:`resource` located in a :term:`resource tree`, which is a set of nested dictionary-like objects. Traversal is done by using each segment of the path portion of the URL to navigate through the :term:`resource tree`. You might think of this as looking up files and directories in a file system. Traversal walks down the path until it finds a published resource, analogous to a file system "directory" or "file". The resource found as the result of a traversal becomes the :term:`context` of the :term:`request`. Then, the :term:`view lookup` subsystem is used to find some view code willing to "publish" this resource by generating a :term:`response`. .. note:: Using :term:`Traversal` to map a URL to code is optional. If you're creating your first Pyramid application, it probably makes more sense to use :term:`URL dispatch` to map URLs to code instead of traversal, as new Pyramid developers tend to find URL dispatch slightly easier to understand. If you use URL dispatch, you needn't read this chapter. .. index:: single: traversal details Traversal Details ----------------- :term:`Traversal` is dependent on information in a :term:`request` object. Every :term:`request` object contains URL path information in the ``PATH_INFO`` portion of the :term:`WSGI` environment. The ``PATH_INFO`` string is the portion of a request's URL following the hostname and port number, but before any query string elements or fragment element. For example the ``PATH_INFO`` portion of the URL ``http://example.com:8080/a/b/c?foo=1`` is ``/a/b/c``. Traversal treats the ``PATH_INFO`` segment of a URL as a sequence of path segments. For example, the ``PATH_INFO`` string ``/a/b/c`` is converted to the sequence ``['a', 'b', 'c']``. This path sequence is then used to descend through the :term:`resource tree`, looking up a resource for each path segment. Each lookup uses the ``__getitem__`` method of a resource in the tree. For example, if the path info sequence is ``['a', 'b', 'c']``: - :term:`Traversal` starts by acquiring the :term:`root` resource of the application by calling the :term:`root factory`. The :term:`root factory` can be configured to return whatever object is appropriate as the traversal root of your application. - Next, the first element (``'a'``) is popped from the path segment sequence and is used as a key to lookup the corresponding resource in the root. This invokes the root resource's ``__getitem__`` method using that value (``'a'``) as an argument. - If the root resource "contains" a resource with key ``'a'``, its ``__getitem__`` method will return it. The :term:`context` temporarily becomes the "A" resource. - The next segment (``'b'``) is popped from the path sequence, and the "A" resource's ``__getitem__`` is called with that value (``'b'``) as an argument; we'll presume it succeeds. - The "A" resource's ``__getitem__`` returns another resource, which we'll call "B". The :term:`context` temporarily becomes the "B" resource. Traversal continues until the path segment sequence is exhausted or a path element cannot be resolved to a resource. In either case, the :term:`context` resource is the last object that the traversal successfully resolved. If any resource found during traversal lacks a ``__getitem__`` method, or if its ``__getitem__`` method raises a :exc:`KeyError`, traversal ends immediately, and that resource becomes the :term:`context`. The results of a :term:`traversal` also include a :term:`view name`. If traversal ends before the path segment sequence is exhausted, the :term:`view name` is the *next* remaining path segment element. If the :term:`traversal` expends all of the path segments, then the :term:`view name` is the empty string (``''``). The combination of the context resource and the :term:`view name` found via traversal is used later in the same request by the :term:`view lookup` subsystem to find a :term:`view callable`. How :app:`Pyramid` performs view lookup is explained within the :ref:`view_config_chapter` chapter. .. index:: single: object tree single: traversal tree single: resource tree .. _the_resource_tree: The Resource Tree ----------------- The resource tree is a set of nested dictionary-like resource objects that begins with a :term:`root` resource. In order to use :term:`traversal` to resolve URLs to code, your application must supply a :term:`resource tree` to :app:`Pyramid`. In order to supply a root resource for an application the :app:`Pyramid` :term:`Router` is configured with a callback known as a :term:`root factory`. The root factory is supplied by the application at startup time as the ``root_factory`` argument to the :term:`Configurator`. The root factory is a Python callable that accepts a :term:`request` object, and returns the root object of the :term:`resource tree`. A function or class is typically used as an application's root factory. Here's an example of a simple root factory class: .. code-block:: python :linenos: class Root(dict): def __init__(self, request): pass Here's an example of using this root factory within startup configuration, by passing it to an instance of a :term:`Configurator` named ``config``: .. code-block:: python :linenos: config = Configurator(root_factory=Root) The ``root_factory`` argument to the :class:`~pyramid.config.Configurator` constructor registers this root factory to be called to generate a root resource whenever a request enters the application. The root factory registered this way is also known as the global root factory. A root factory can alternatively be passed to the ``Configurator`` as a :term:`dotted Python name` which can refer to a root factory defined in a different module. If no :term:`root factory` is passed to the :app:`Pyramid` :term:`Configurator` constructor, or if the ``root_factory`` value specified is ``None``, a :term:`default root factory` is used. The default root factory always returns a resource that has no child resources; it is effectively empty. Usually a root factory for a traversal-based application will be more complicated than the above ``Root`` class. In particular it may be associated with a database connection or another persistence mechanism. The above ``Root`` class is analogous to the default root factory present in Pyramid. The default root factory is very simple and not very useful. .. note:: If the items contained within the resource tree are "persistent" (they have state that lasts longer than the execution of a single process), they become analogous to the concept of :term:`domain model` objects used by many other frameworks. The resource tree consists of *container* resources and *leaf* resources. There is only one difference between a *container* resource and a *leaf* resource: *container* resources possess a ``__getitem__`` method (making it "dictionary-like") while *leaf* resources do not. The ``__getitem__`` method was chosen as the signifying difference between the two types of resources because the presence of this method is how Python itself typically determines whether an object is "containerish" or not (dictionary objects are "containerish"). Each container resource is presumed to be willing to return a child resource or raise a ``KeyError`` based on a name passed to its ``__getitem__``. Leaf-level instances must not have a ``__getitem__``. If instances that you'd like to be leaves already happen to have a ``__getitem__`` through some historical inequity, you should subclass these resource types and cause their ``__getitem__`` methods to simply raise a ``KeyError``. Or just disuse them and think up another strategy. Usually the traversal root is a *container* resource, and as such it contains other resources. However, it doesn't *need* to be a container. Your resource tree can be as shallow or as deep as you require. In general, the resource tree is traversed beginning at its root resource using a sequence of path elements described by the ``PATH_INFO`` of the current request. If there are path segments, the root resource's ``__getitem__`` is called with the next path segment, and it is expected to return another resource. The resulting resource's ``__getitem__`` is called with the very next path segment, and it is expected to return another resource. This happens *ad infinitum* until all path segments are exhausted. .. index:: single: traversal algorithm single: view lookup .. _traversal_algorithm: The Traversal Algorithm ----------------------- This section will attempt to explain the :app:`Pyramid` traversal algorithm. We'll provide a description of the algorithm, a diagram of how the algorithm works, and some example traversal scenarios that might help you understand how the algorithm operates against a specific resource tree. We'll also talk a bit about :term:`view lookup`. The :ref:`view_config_chapter` chapter discusses :term:`view lookup` in detail, and it is the canonical source for information about views. Technically, :term:`view lookup` is a :app:`Pyramid` subsystem that is separated from traversal entirely. However, we'll describe the fundamental behavior of view lookup in the examples in the next few sections to give you an idea of how traversal and view lookup cooperate, because they are almost always used together. .. index:: single: view name single: context single: subpath single: root factory single: default view A Description of the Traversal Algorithm ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When a user requests a page from your traversal-powered application, the system uses this algorithm to find a :term:`context` resource and a :term:`view name`. #. The request for the page is presented to the :app:`Pyramid` :term:`router` in terms of a standard :term:`WSGI` request, which is represented by a WSGI environment and a WSGI ``start_response`` callable. #. The router creates a :term:`request` object based on the WSGI environment. #. The :term:`root factory` is called with the :term:`request`. It returns a :term:`root` resource. #. The router uses the WSGI environment's ``PATH_INFO`` information to determine the path segments to traverse. The leading slash is stripped off ``PATH_INFO``, and the remaining path segments are split on the slash character to form a traversal sequence. The traversal algorithm by default attempts to first URL-unquote and then Unicode-decode each path segment derived from ``PATH_INFO`` from its natural byte string (``str`` type) representation. URL unquoting is performed using the Python standard library ``urllib.unquote`` function. Conversion from a URL-decoded string into Unicode is attempted using the UTF-8 encoding. If any URL-unquoted path segment in ``PATH_INFO`` is not decodeable using the UTF-8 decoding, a :exc:`TypeError` is raised. A segment will be fully URL-unquoted and UTF8-decoded before it is passed in to the ``__getitem__`` of any resource during traversal. Thus a request with a ``PATH_INFO`` variable of ``/a/b/c`` maps to the traversal sequence ``[u'a', u'b', u'c']``. #. :term:`Traversal` begins at the root resource returned by the root factory. For the traversal sequence ``[u'a', u'b', u'c']``, the root resource's ``__getitem__`` is called with the name ``'a'``. Traversal continues through the sequence. In our example, if the root resource's ``__getitem__`` called with the name ``a`` returns a resource (a.k.a. resource "A"), that resource's ``__getitem__`` is called with the name ``'b'``. If resource "A" returns a resource "B" when asked for ``'b'``, resource B's ``__getitem__`` is then asked for the name ``'c'``, and may return resource "C". #. Traversal ends when either (a) the entire path is exhausted, (b) when any resource raises a :exc:`KeyError` from its ``__getitem__``, (c) when any non-final path element traversal does not have a ``__getitem__`` method (resulting in an :exc:`AttributeError`), or (d) when any path element is prefixed with the set of characters ``@@`` (indicating that the characters following the ``@@`` token should be treated as a :term:`view name`). #. When traversal ends for any of the reasons in the previous step, the last resource found during traversal is deemed to be the :term:`context`. If the path has been exhausted when traversal ends, the :term:`view name` is deemed to be the empty string (``''``). However, if the path was *not* exhausted before traversal terminated, the first remaining path segment is treated as the view name. #. Any subsequent path elements after the :term:`view name` is found are deemed the :term:`subpath`. The subpath is always a sequence of path segments that come from ``PATH_INFO`` that are "left over" after traversal has completed. Once the :term:`context` resource, the :term:`view name`, and associated attributes such as the :term:`subpath` are located, the job of :term:`traversal` is finished. It passes back the information it obtained to its caller, the :app:`Pyramid` :term:`Router`, which subsequently invokes :term:`view lookup` with the context and view name information. The traversal algorithm exposes two special cases: - You will often end up with a :term:`view name` that is the empty string as the result of a particular traversal. This indicates that the view lookup machinery should lookup the :term:`default view`. The default view is a view that is registered with no name or a view which is registered with a name that equals the empty string. - If any path segment element begins with the special characters ``@@`` (think of them as goggles), the value of that segment minus the goggle characters is considered the :term:`view name` immediately and traversal stops there. This allows you to address views that may have the same names as resource names in the tree unambiguously. Finally, traversal is responsible for locating a :term:`virtual root`. A virtual root is used during "virtual hosting". See the :ref:`vhosting_chapter` chapter for information. We won't speak more about it in this chapter. .. image:: resourcetreetraverser.png .. index:: single: traversal examples Traversal Algorithm Examples ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ No one can be expected to understand the traversal algorithm by analogy and description alone, so let's examine some traversal scenarios that use concrete URLs and resource tree compositions. Let's pretend the user asks for ``http://example.com/foo/bar/baz/biz/buz.txt``. The request's ``PATH_INFO`` in that case is ``/foo/bar/baz/biz/buz.txt``. Let's further pretend that when this request comes in, we're traversing the following resource tree: .. code-block:: text /-- | |-- foo | ----bar Here's what happens: - :term:`traversal` traverses the root, and attempts to find "foo", which it finds. - :term:`traversal` traverses "foo", and attempts to find "bar", which it finds. - :term:`traversal` traverses "bar", and attempts to find "baz", which it does not find (the "bar" resource raises a :exc:`KeyError` when asked for "baz"). The fact that it does not find "baz" at this point does not signify an error condition. It signifies the following: - The :term:`context` is the "bar" resource (the context is the last resource found during traversal). - The :term:`view name` is ``baz``. - The :term:`subpath` is ``('biz', 'buz.txt')``. At this point, traversal has ended, and :term:`view lookup` begins. Because it's the "context" resource, the view lookup machinery examines "bar" to find out what "type" it is. Let's say it finds that the context is a ``Bar`` type (because "bar" happens to be an instance of the class ``Bar``). Using the :term:`view name` (``baz``) and the type, view lookup asks the :term:`application registry` this question: - Please find me a :term:`view callable` registered using a :term:`view configuration` with the name "baz" that can be used for the class ``Bar``. Let's say that view lookup finds no matching view type. In this circumstance, the :app:`Pyramid` :term:`router` returns the result of the :term:`Not Found View` and the request ends. However, for this tree: .. code-block:: text /-- | |-- foo | ----bar | ----baz | biz The user asks for ``http://example.com/foo/bar/baz/biz/buz.txt`` - :term:`traversal` traverses "foo", and attempts to find "bar", which it finds. - :term:`traversal` traverses "bar", and attempts to find "baz", which it finds. - :term:`traversal` traverses "baz", and attempts to find "biz", which it finds. - :term:`traversal` traverses "biz", and attempts to find "buz.txt", which it does not find. The fact that it does not find a resource related to "buz.txt" at this point does not signify an error condition. It signifies the following: - The :term:`context` is the "biz" resource (the context is the last resource found during traversal). - The :term:`view name` is "buz.txt". - The :term:`subpath` is an empty sequence ( ``()`` ). At this point, traversal has ended, and :term:`view lookup` begins. Because it's the "context" resource, the view lookup machinery examines the "biz" resource to find out what "type" it is. Let's say it finds that the resource is a ``Biz`` type (because "biz" is an instance of the Python class ``Biz``). Using the :term:`view name` (``buz.txt``) and the type, view lookup asks the :term:`application registry` this question: - Please find me a :term:`view callable` registered with a :term:`view configuration` with the name ``buz.txt`` that can be used for class ``Biz``. Let's say that question is answered by the application registry. In such a situation, the application registry returns a :term:`view callable`. The view callable is then called with the current :term:`WebOb` :term:`request` as the sole argument, ``request``. It is expected to return a response. .. sidebar:: The Example View Callables Accept Only a Request; How Do I Access the Context Resource? Most of the examples in this documentation assume that a view callable is typically passed only a :term:`request` object. Sometimes your view callables need access to the :term:`context` resource, especially when you use :term:`traversal`. You might use a supported alternative view callable argument list in your view callables such as the ``(context, request)`` calling convention described in :ref:`request_and_context_view_definitions`. But you don't need to if you don't want to. In view callables that accept only a request, the :term:`context` resource found by traversal is available as the ``context`` attribute of the request object, e.g., ``request.context``. The :term:`view name` is available as the ``view_name`` attribute of the request object, e.g., ``request.view_name``. Other :app:`Pyramid`-specific request attributes are also available as described in :ref:`special_request_attributes`. .. index:: single: resource interfaces .. _using_resource_interfaces: Using Resource Interfaces in View Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of registering your views with a ``context`` that names a Python resource *class*, you can optionally register a view callable with a ``context`` which is an :term:`interface`. An interface can be attached arbitrarily to any resource object. View lookup treats context interfaces specially, and therefore the identity of a resource can be divorced from that of the class which implements it. As a result, associating a view with an interface can provide more flexibility for sharing a single view between two or more different implementations of a resource type. For example, if two resource objects of different Python class types share the same interface, you can use the same view configuration to specify both of them as a ``context``. In order to make use of interfaces in your application during view dispatch, you must create an interface and mark up your resource classes or instances with interface declarations that refer to this interface. To attach an interface to a resource *class*, you define the interface and use the :func:`zope.interface.implementer` class decorator to associate the interface with the class. .. code-block:: python :linenos: from zope.interface import Interface from zope.interface import implementer class IHello(Interface): """ A marker interface """ @implementer(IHello) class Hello(object): pass To attach an interface to a resource *instance*, you define the interface and use the :func:`zope.interface.alsoProvides` function to associate the interface with the instance. This function mutates the instance in such a way that the interface is attached to it. .. code-block:: python :linenos: from zope.interface import Interface from zope.interface import alsoProvides class IHello(Interface): """ A marker interface """ class Hello(object): pass def make_hello(): hello = Hello() alsoProvides(hello, IHello) return hello Regardless of how you associate an interface—with either a resource instance or a resource class—the resulting code to associate that interface with a view callable is the same. Assuming the above code that defines an ``IHello`` interface lives in the root of your application, and its module is named "resources.py", the interface declaration below will associate the ``mypackage.views.hello_world`` view with resources that implement, or provide, this interface. .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_view('mypackage.views.hello_world', name='hello.html', context='mypackage.resources.IHello') Any time a resource that is determined to be the :term:`context` provides this interface, and a view named ``hello.html`` is looked up against it as per the URL, the ``mypackage.views.hello_world`` view callable will be invoked. Note, in cases where a view is registered against a resource class, and a view is also registered against an interface that the resource class implements, an ambiguity arises. Views registered for the resource class take precedence over any views registered for any interface the resource class implements. Thus, if one view configuration names a ``context`` of both the class type of a resource, and another view configuration names a ``context`` of interface implemented by the resource's class, and both view configurations are otherwise identical, the view registered for the context's class will "win". For more information about defining resources with interfaces for use within view configuration, see :ref:`resources_which_implement_interfaces`. References ---------- A tutorial showing how :term:`traversal` can be used within a :app:`Pyramid` application exists in :ref:`bfg_wiki_tutorial`. See the :ref:`view_config_chapter` chapter for detailed information about :term:`view lookup`. The :mod:`pyramid.traversal` module contains API functions that deal with traversal, such as traversal invocation from within application code. The :meth:`pyramid.request.Request.resource_url` method generates a URL when given a resource retrieved from a resource tree. pyramid-1.6/docs/narr/upgrading.rst0000644000076500000240000002321012622227523020157 0ustar michaelstaff00000000000000.. _upgrading_chapter: Upgrading Pyramid ================= When a new version of Pyramid is released, it will sometimes deprecate a feature or remove a feature that was deprecated in an older release. When features are removed from Pyramid, applications that depend on those features will begin to break. This chapter explains how to ensure your Pyramid applications keep working when you upgrade the Pyramid version you're using. .. sidebar:: About Release Numbering Conventionally, application version numbering in Python is described as ``major.minor.micro``. If your Pyramid version is "1.2.3", it means you're running a version of Pyramid with the major version "1", the minor version "2" and the micro version "3". A "major" release is one that increments the first-dot number; 2.X.X might follow 1.X.X. A "minor" release is one that increments the second-dot number; 1.3.X might follow 1.2.X. A "micro" release is one that increments the third-dot number; 1.2.3 might follow 1.2.2. In general, micro releases are "bugfix-only", and contain no new features, minor releases contain new features but are largely backwards compatible with older versions, and a major release indicates a large set of backwards incompatibilities. The Pyramid core team is conservative when it comes to removing features. We don't remove features unnecessarily, but we're human and we make mistakes which cause some features to be evolutionary dead ends. Though we are willing to support dead-end features for some amount of time, some eventually have to be removed when the cost of supporting them outweighs the benefit of keeping them around, because each feature in Pyramid represents a certain documentation and maintenance burden. Deprecation and removal policy ------------------------------ When a feature is scheduled for removal from Pyramid or any of its official add-ons, the core development team takes these steps: - Using the feature will begin to generate a `DeprecationWarning`, indicating the version in which the feature became deprecated. - A note is added to the documentation indicating that the feature is deprecated. - A note is added to the :ref:`changelog` about the deprecation. When a deprecated feature is eventually removed: - The feature is removed. - A note is added to the :ref:`changelog` about the removal. Features are never removed in *micro* releases. They are only removed in minor and major releases. Deprecated features are kept around for at least *three* minor releases from the time the feature became deprecated. Therefore, if a feature is added in Pyramid 1.0, but it's deprecated in Pyramid 1.1, it will be kept around through all 1.1.X releases, all 1.2.X releases and all 1.3.X releases. It will finally be removed in the first 1.4.X release. Sometimes features are "docs-deprecated" instead of formally deprecated. This means that the feature will be kept around indefinitely, but it will be removed from the documentation or a note will be added to the documentation telling folks to use some other newer feature. This happens when the cost of keeping an old feature around is very minimal and the support and documentation burden is very low. For example, we might rename a function that is an API without changing the arguments it accepts. In this case, we'll often rename the function, and change the docs to point at the new function name, but leave around a backwards compatibility alias to the old function name so older code doesn't break. "Docs deprecated" features tend to work "forever", meaning that they won't be removed, and they'll never generate a deprecation warning. However, such changes are noted in the :ref:`changelog`, so it's possible to know that you should change older spellings to newer ones to ensure that people reading your code can find the APIs you're using in the Pyramid docs. Consulting the change history ----------------------------- Your first line of defense against application failures caused by upgrading to a newer Pyramid release is always to read the :ref:`changelog` to find the deprecations and removals for each release between the release you're currently running and the one to which you wish to upgrade. The change history notes every deprecation within a ``Deprecation`` section and every removal within a ``Backwards Incompatibilies`` section for each release. The change history often contains instructions for changing your code to avoid deprecation warnings and how to change docs-deprecated spellings to newer ones. You can follow along with each deprecation explanation in the change history, simply doing a grep or other code search to your application, using the change log examples to remediate each potential problem. .. _testing_under_new_release: Testing your application under a new Pyramid release ---------------------------------------------------- Once you've upgraded your application to a new Pyramid release and you've remediated as much as possible by using the change history notes, you'll want to run your application's tests (see :ref:`running_tests`) in such a way that you can see DeprecationWarnings printed to the console when the tests run. .. code-block:: bash $ python -Wd setup.py test -q The ``-Wd`` argument tells Python to print deprecation warnings to the console. Note that the ``-Wd`` flag is only required for Python 2.7 and better: Python versions 2.6 and older print deprecation warnings to the console by default. See `the Python -W flag documentation `_ for more information. As your tests run, deprecation warnings will be printed to the console explaining the deprecation and providing instructions about how to prevent the deprecation warning from being issued. For example: .. code-block:: bash $ python -Wd setup.py test -q # .. elided ... running build_ext /home/chrism/projects/pyramid/env27/myproj/myproj/views.py:3: DeprecationWarning: static: The "pyramid.view.static" class is deprecated as of Pyramid 1.1; use the "pyramid.static.static_view" class instead with the "use_subpath" argument set to True. from pyramid.view import static . ---------------------------------------------------------------------- Ran 1 test in 0.014s OK In the above case, it's line #3 in the ``myproj.views`` module (``from pyramid.view import static``) that is causing the problem: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.view import static myview = static('static', 'static') The deprecation warning tells me how to fix it, so I can change the code to do things the newer way: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.static import static_view myview = static_view('static', 'static', use_subpath=True) When I run the tests again, the deprecation warning is no longer printed to my console: .. code-block:: bash $ python -Wd setup.py test -q # .. elided ... running build_ext . ---------------------------------------------------------------------- Ran 1 test in 0.014s OK My application doesn't have any tests or has few tests ------------------------------------------------------ If your application has no tests, or has only moderate test coverage, running tests won't tell you very much, because the Pyramid codepaths that generate deprecation warnings won't be executed. In this circumstance, you can start your application interactively under a server run with the ``PYTHONWARNINGS`` environment variable set to ``default``. On UNIX, you can do that via: .. code-block:: bash $ PYTHONWARNINGS=default $VENV/bin/pserve development.ini On Windows, you need to issue two commands: .. code-block:: bash C:\> set PYTHONWARNINGS=default C:\> Scripts/pserve.exe development.ini At this point, it's ensured that deprecation warnings will be printed to the console whenever a codepath is hit that generates one. You can then click around in your application interactively to try to generate them, and remediate as explained in :ref:`testing_under_new_release`. See `the PYTHONWARNINGS environment variable documentation `_ or `the Python -W flag documentation `_ for more information. Upgrading to the very latest Pyramid release -------------------------------------------- When you upgrade your application to the most recent Pyramid release, it's advisable to upgrade step-wise through each most recent minor release, beginning with the one that you know your application currently runs under, and ending on the most recent release. For example, if your application is running in production on Pyramid 1.2.1, and the most recent Pyramid 1.3 release is Pyramid 1.3.3, and the most recent Pyramid release is 1.4.4, it's advisable to do this: - Upgrade your environment to the most recent 1.2 release. For example, the most recent 1.2 release might be 1.2.3, so upgrade to it. Then run your application's tests under 1.2.3 as described in :ref:`testing_under_new_release`. Note any deprecation warnings and remediate. - Upgrade to the most recent 1.3 release, 1.3.3. Run your application's tests, note any deprecation warnings, and remediate. - Upgrade to 1.4.4. Run your application's tests, note any deprecation warnings, and remediate. If you skip testing your application under each minor release (for example if you upgrade directly from 1.2.1 to 1.4.4), you might miss a deprecation warning and waste more time trying to figure out an error caused by a feature removal than it would take to upgrade stepwise through each minor release. pyramid-1.6/docs/narr/urldispatch.rst0000644000076500000240000014221612606630333020530 0ustar michaelstaff00000000000000.. index:: single: URL dispatch .. _urldispatch_chapter: URL Dispatch ============ :term:`URL dispatch` provides a simple way to map URLs to :term:`view` code using a simple pattern matching language. An ordered set of patterns is checked one by one. If one of the patterns matches the path information associated with a request, a particular :term:`view callable` is invoked. A view callable is a specific bit of code, defined in your application, that receives the :term:`request` and returns a :term:`response` object. High-Level Operational Overview ------------------------------- If any route configuration is present in an application, the :app:`Pyramid` :term:`Router` checks every incoming request against an ordered set of URL matching patterns present in a *route map*. If any route pattern matches the information in the :term:`request`, :app:`Pyramid` will invoke the :term:`view lookup` process to find a matching view. If no route pattern in the route map matches the information in the :term:`request` provided in your application, :app:`Pyramid` will fail over to using :term:`traversal` to perform resource location and view lookup. .. index:: single: route configuration Route Configuration ------------------- :term:`Route configuration` is the act of adding a new :term:`route` to an application. A route has a *name*, which acts as an identifier to be used for URL generation. The name also allows developers to associate a view configuration with the route. A route also has a *pattern*, meant to match against the ``PATH_INFO`` portion of a URL (the portion following the scheme and port, e.g., ``/foo/bar`` in the URL ``http://localhost:8080/foo/bar``). It also optionally has a ``factory`` and a set of :term:`route predicate` attributes. .. index:: single: add_route .. _config-add-route: Configuring a Route to Match a View ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_route` method adds a single :term:`route configuration` to the :term:`application registry`. Here's an example: .. code-block:: python # "config" below is presumed to be an instance of the # pyramid.config.Configurator class; "myview" is assumed # to be a "view callable" function from views import myview config.add_route('myroute', '/prefix/{one}/{two}') config.add_view(myview, route_name='myroute') When a :term:`view callable` added to the configuration by way of :meth:`~pyramid.config.Configurator.add_view` becomes associated with a route via its ``route_name`` predicate, that view callable will always be found and invoked when the associated route pattern matches during a request. More commonly, you will not use any ``add_view`` statements in your project's "setup" code. You will instead use ``add_route`` statements, and use a :term:`scan` to associate view callables with routes. For example, if this is a portion of your project's ``__init__.py``: .. code-block:: python config.add_route('myroute', '/prefix/{one}/{two}') config.scan('mypackage') Note that we don't call :meth:`~pyramid.config.Configurator.add_view` in this setup code. However, the above :term:`scan` execution ``config.scan('mypackage')`` will pick up each :term:`configuration decoration`, including any objects decorated with the :class:`pyramid.view.view_config` decorator in the ``mypackage`` Python package. For example, if you have a ``views.py`` in your package, a scan will pick up any of its configuration decorators, so we can add one there that references ``myroute`` as a ``route_name`` parameter: .. code-block:: python from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='myroute') def myview(request): return Response('OK') The above combination of ``add_route`` and ``scan`` is completely equivalent to using the previous combination of ``add_route`` and ``add_view``. .. index:: single: route path pattern syntax .. _route_pattern_syntax: Route Pattern Syntax ~~~~~~~~~~~~~~~~~~~~ The syntax of the pattern matching language used by :app:`Pyramid` URL dispatch in the *pattern* argument is straightforward. It is close to that of the :term:`Routes` system used by :term:`Pylons`. The *pattern* used in route configuration may start with a slash character. If the pattern does not start with a slash character, an implicit slash will be prepended to it at matching time. For example, the following patterns are equivalent: .. code-block:: text {foo}/bar/baz and: .. code-block:: text /{foo}/bar/baz If a pattern is a valid URL it won't be matched against an incoming request. Instead it can be useful for generating external URLs. See :ref:`External routes ` for details. A pattern segment (an individual item between ``/`` characters in the pattern) may either be a literal string (e.g., ``foo``) *or* it may be a replacement marker (e.g., ``{foo}``), or a certain combination of both. A replacement marker does not need to be preceded by a ``/`` character. A replacement marker is in the format ``{name}``, where this means "accept any characters up to the next slash character and use this as the ``name`` :term:`matchdict` value." A replacement marker in a pattern must begin with an uppercase or lowercase ASCII letter or an underscore, and can be composed only of uppercase or lowercase ASCII letters, underscores, and numbers. For example: ``a``, ``a_b``, ``_b``, and ``b9`` are all valid replacement marker names, but ``0a`` is not. .. versionchanged:: 1.2 A replacement marker could not start with an underscore until Pyramid 1.2. Previous versions required that the replacement marker start with an uppercase or lowercase letter. A matchdict is the dictionary representing the dynamic parts extracted from a URL based on the routing pattern. It is available as ``request.matchdict``. For example, the following pattern defines one literal segment (``foo``) and two replacement markers (``baz``, and ``bar``): .. code-block:: text foo/{baz}/{bar} The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2 -> {'baz':u'1', 'bar':u'2'} foo/abc/def -> {'baz':u'abc', 'bar':u'def'} It will not match the following patterns however: .. code-block:: text foo/1/2/ -> No match (trailing slash) bar/abc/def -> First segment literal mismatch The match for a segment replacement marker in a segment will be done only up to the first non-alphanumeric character in the segment in the pattern. So, for instance, if this route pattern was used: .. code-block:: text foo/{name}.html The literal path ``/foo/biz.html`` will match the above route pattern, and the match result will be ``{'name':u'biz'}``. However, the literal path ``/foo/biz`` will not match, because it does not contain a literal ``.html`` at the end of the segment represented by ``{name}.html`` (it only contains ``biz``, not ``biz.html``). To capture both segments, two replacement markers can be used: .. code-block:: text foo/{name}.{ext} The literal path ``/foo/biz.html`` will match the above route pattern, and the match result will be ``{'name': 'biz', 'ext': 'html'}``. This occurs because there is a literal part of ``.`` (period) between the two replacement markers ``{name}`` and ``{ext}``. Replacement markers can optionally specify a regular expression which will be used to decide whether a path segment should match the marker. To specify that a replacement marker should match only a specific set of characters as defined by a regular expression, you must use a slightly extended form of replacement marker syntax. Within braces, the replacement marker name must be followed by a colon, then directly thereafter, the regular expression. The *default* regular expression associated with a replacement marker ``[^/]+`` matches one or more characters which are not a slash. For example, under the hood, the replacement marker ``{foo}`` can more verbosely be spelled as ``{foo:[^/]+}``. You can change this to be an arbitrary regular expression to match an arbitrary sequence of characters, such as ``{foo:\d+}`` to match only digits. It is possible to use two replacement markers without any literal characters between them, for instance ``/{foo}{bar}``. However, this would be a nonsensical pattern without specifying a custom regular expression to restrict what each marker captures. Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL ``/abc/``: - ``/abc/{foo}`` will not match. - ``/{foo}/`` will match. Note that values representing matched path segments will be URL-unquoted and decoded from UTF-8 into Unicode within the matchdict. So for instance, the following pattern: .. code-block:: text foo/{bar} When matching the following URL: .. code-block:: text http://example.com/foo/La%20Pe%C3%B1a The matchdict will look like so (the value is URL-decoded / UTF-8 decoded): .. code-block:: text {'bar':u'La Pe\xf1a'} Literal strings in the path segment should represent the *decoded* value of the ``PATH_INFO`` provided to Pyramid. You don't want to use a URL-encoded value or a bytestring representing the literal encoded as UTF-8 in the pattern. For example, rather than this: .. code-block:: text /Foo%20Bar/{baz} You'll want to use something like this: .. code-block:: text /Foo Bar/{baz} For patterns that contain "high-order" characters in its literals, you'll want to use a Unicode value as the pattern as opposed to any URL-encoded or UTF-8-encoded value. For example, you might be tempted to use a bytestring pattern like this: .. code-block:: text /La Pe\xc3\xb1a/{x} But this will either cause an error at startup time or it won't match properly. You'll want to use a Unicode value as the pattern instead rather than raw bytestring escapes. You can use a high-order Unicode value as the pattern by using `Python source file encoding `_ plus the "real" character in the Unicode pattern in the source, like so: .. code-block:: text /La Peña/{x} Or you can ignore source file encoding and use equivalent Unicode escape characters in the pattern. .. code-block:: text /La Pe\xf1a/{x} Dynamic segment names cannot contain high-order characters, so this applies only to literals in the pattern. If the pattern has a ``*`` in it, the name which follows it is considered a "remainder match". A remainder match *must* come at the end of the pattern. Unlike segment replacement markers, it does not need to be preceded by a slash. For example: .. code-block:: text foo/{baz}/{bar}*fizzle The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':()} foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle':(u'a', u'b', u'c')} Note that when a ``*stararg`` remainder match is matched, the value put into the matchdict is turned into a tuple of path segments representing the remainder of the path. These path segments are URL-unquoted and decoded from UTF-8 into Unicode. For example, for the following pattern: .. code-block:: text foo/*fizzle When matching the following path: .. code-block:: text /foo/La%20Pe%C3%B1a/a/b/c Will generate the following matchdict: .. code-block:: text {'fizzle':(u'La Pe\xf1a', u'a', u'b', u'c')} By default, the ``*stararg`` will parse the remainder sections into a tuple split by segment. Changing the regular expression used to match a marker can also capture the remainder of the URL, for example: .. code-block:: text foo/{baz}/{bar}{fizzle:.*} The above pattern will match these URLs, generating the following matchdicts: .. code-block:: text foo/1/2/ -> {'baz':u'1', 'bar':u'2', 'fizzle':u''} foo/abc/def/a/b/c -> {'baz':u'abc', 'bar':u'def', 'fizzle': u'a/b/c'} This occurs because the default regular expression for a marker is ``[^/]+`` which will match everything up to the first ``/``, while ``{fizzle:.*}`` will result in a regular expression match of ``.*`` capturing the remainder into a single value. .. index:: single: route ordering Route Declaration Ordering ~~~~~~~~~~~~~~~~~~~~~~~~~~ Route configuration declarations are evaluated in a specific order when a request enters the system. As a result, the order of route configuration declarations is very important. The order in which route declarations are evaluated is the order in which they are added to the application at startup time. (This is unlike a different way of mapping URLs to code that :app:`Pyramid` provides, named :term:`traversal`, which does not depend on pattern ordering). For routes added via the :mod:`~pyramid.config.Configurator.add_route` method, the order that routes are evaluated is the order in which they are added to the configuration imperatively. For example, route configuration statements with the following patterns might be added in the following order: .. code-block:: text members/{def} members/abc In such a configuration, the ``members/abc`` pattern would *never* be matched. This is because the match ordering will always match ``members/{def}`` first; the route configuration with ``members/abc`` will never be evaluated. .. index:: single: route configuration arguments Route Configuration Arguments ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Route configuration ``add_route`` statements may specify a large number of arguments. They are documented as part of the API documentation at :meth:`pyramid.config.Configurator.add_route`. Many of these arguments are :term:`route predicate` arguments. A route predicate argument specifies that some aspect of the request must be true for the associated route to be considered a match during the route matching process. Examples of route predicate arguments are ``pattern``, ``xhr``, and ``request_method``. Other arguments are ``name`` and ``factory``. These arguments represent neither predicates nor view configuration information. .. index:: single: route matching Route Matching -------------- The main purpose of route configuration is to match (or not match) the ``PATH_INFO`` present in the WSGI environment provided during a request against a URL path pattern. ``PATH_INFO`` represents the path portion of the URL that was requested. The way that :app:`Pyramid` does this is very simple. When a request enters the system, for each route configuration declaration present in the system, :app:`Pyramid` checks the request's ``PATH_INFO`` against the pattern declared. This checking happens in the order that the routes were declared via :meth:`pyramid.config.Configurator.add_route`. When a route configuration is declared, it may contain :term:`route predicate` arguments. All route predicates associated with a route declaration must be ``True`` for the route configuration to be used for a given request during a check. If any predicate in the set of :term:`route predicate` arguments provided to a route configuration returns ``False`` during a check, that route is skipped and route matching continues through the ordered set of routes. If any route matches, the route matching process stops and the :term:`view lookup` subsystem takes over to find the most reasonable view callable for the matched route. Most often, there's only one view that will match (a view configured with a ``route_name`` argument matching the matched route). To gain a better understanding of how routes and views are associated in a real application, you can use the ``pviews`` command, as documented in :ref:`displaying_matching_views`. If no route matches after all route patterns are exhausted, :app:`Pyramid` falls back to :term:`traversal` to do :term:`resource location` and :term:`view lookup`. .. index:: single: matchdict .. _matchdict: The Matchdict ~~~~~~~~~~~~~ When the URL pattern associated with a particular route configuration is matched by a request, a dictionary named ``matchdict`` is added as an attribute of the :term:`request` object. Thus, ``request.matchdict`` will contain the values that match replacement patterns in the ``pattern`` element. The keys in a matchdict will be strings. The values will be Unicode objects. .. note:: If no route URL pattern matches, the ``matchdict`` object attached to the request will be ``None``. .. index:: single: matched_route .. _matched_route: The Matched Route ~~~~~~~~~~~~~~~~~ When the URL pattern associated with a particular route configuration is matched by a request, an object named ``matched_route`` is added as an attribute of the :term:`request` object. Thus, ``request.matched_route`` will be an object implementing the :class:`~pyramid.interfaces.IRoute` interface which matched the request. The most useful attribute of the route object is ``name``, which is the name of the route that matched. .. note:: If no route URL pattern matches, the ``matched_route`` object attached to the request will be ``None``. Routing Examples ---------------- Let's check out some examples of how route configuration statements might be commonly declared, and what will happen if they are matched by the information present in a request. .. _urldispatch_example1: Example 1 ~~~~~~~~~ The simplest route declaration which configures a route match to *directly* result in a particular view callable being invoked: .. code-block:: python :linenos: config.add_route('idea', 'site/{id}') config.scan() When a route configuration with a ``view`` attribute is added to the system, and an incoming request matches the *pattern* of the route configuration, the :term:`view callable` named as the ``view`` attribute of the route configuration will be invoked. Recall that the ``@view_config`` is equivalent to calling ``config.add_view``, because the ``config.scan()`` call will import ``mypackage.views``, shown below, and execute ``config.add_view`` under the hood. Each view then maps the route name to the matching view callable. In the case of the above example, when the URL of a request matches ``/site/{id}``, the view callable at the Python dotted path name ``mypackage.views.site_view`` will be called with the request. In other words, we've associated a view callable directly with a route pattern. When the ``/site/{id}`` route pattern matches during a request, the ``site_view`` view callable is invoked with that request as its sole argument. When this route matches, a ``matchdict`` will be generated and attached to the request as ``request.matchdict``. If the specific URL matched is ``/site/1``, the ``matchdict`` will be a dictionary with a single key, ``id``; the value will be the string ``'1'``, ex.: ``{'id':'1'}``. The ``mypackage.views`` module referred to above might look like so: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='idea') def site_view(request): return Response(request.matchdict['id']) The view has access to the matchdict directly via the request, and can access variables within it that match keys present as a result of the route pattern. See :ref:`views_chapter`, and :ref:`view_config_chapter` for more information about views. Example 2 ~~~~~~~~~ Below is an example of a more complicated set of route statements you might add to your application: .. code-block:: python :linenos: config.add_route('idea', 'ideas/{idea}') config.add_route('user', 'users/{user}') config.add_route('tag', 'tags/{tag}') config.scan() Here is an example of a corresponding ``mypackage.views`` module: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='idea') def idea_view(request): return Response(request.matchdict['id']) @view_config(route_name='user') def user_view(request): user = request.matchdict['user'] return Response(u'The user is {}.'.format(user)) @view_config(route_name='tag') def tag_view(request): tag = request.matchdict['tag'] return Response(u'The tag is {}.'.format(tag)) The above configuration will allow :app:`Pyramid` to service URLs in these forms: .. code-block:: text /ideas/{idea} /users/{user} /tags/{tag} - When a URL matches the pattern ``/ideas/{idea}``, the view callable available at the dotted Python pathname ``mypackage.views.idea_view`` will be called. For the specific URL ``/ideas/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'idea':'1'}``. - When a URL matches the pattern ``/users/{user}``, the view callable available at the dotted Python pathname ``mypackage.views.user_view`` will be called. For the specific URL ``/users/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'user':'1'}``. - When a URL matches the pattern ``/tags/{tag}``, the view callable available at the dotted Python pathname ``mypackage.views.tag_view`` will be called. For the specific URL ``/tags/1``, the ``matchdict`` generated and attached to the :term:`request` will consist of ``{'tag':'1'}``. In this example we've again associated each of our routes with a :term:`view callable` directly. In all cases, the request, which will have a ``matchdict`` attribute detailing the information found in the URL by the process will be passed to the view callable. Example 3 ~~~~~~~~~ The :term:`context` resource object passed in to a view found as the result of URL dispatch will, by default, be an instance of the object returned by the :term:`root factory` configured at startup time (the ``root_factory`` argument to the :term:`Configurator` used to configure the application). You can override this behavior by passing in a ``factory`` argument to the :meth:`~pyramid.config.Configurator.add_route` method for a particular route. The ``factory`` should be a callable that accepts a :term:`request` and returns an instance of a class that will be the context resource used by the view. An example of using a route with a factory: .. code-block:: python :linenos: config.add_route('idea', 'ideas/{idea}', factory='myproject.resources.Idea') config.scan() The above route will manufacture an ``Idea`` resource as a :term:`context`, assuming that ``mypackage.resources.Idea`` resolves to a class that accepts a request in its ``__init__``. For example: .. code-block:: python :linenos: class Idea(object): def __init__(self, request): pass In a more complicated application, this root factory might be a class representing a :term:`SQLAlchemy` model. The view ``mypackage.views.idea_view`` might look like this: .. code-block:: python :linenos: @view_config(route_name='idea') def idea_view(request): idea = request.context return Response(idea) Here, ``request.context`` is an instance of ``Idea``. If indeed the resource object is a SQLAlchemy model, you do not even have to perform a query in the view callable, since you have access to the resource via ``request.context``. See :ref:`route_factories` for more details about how to use route factories. .. index:: single: matching the root URL single: root url (matching) pair: matching; root URL Matching the Root URL --------------------- It's not entirely obvious how to use a route pattern to match the root URL ("/"). To do so, give the empty string as a pattern in a call to :meth:`~pyramid.config.Configurator.add_route`: .. code-block:: python :linenos: config.add_route('root', '') Or provide the literal string ``/`` as the pattern: .. code-block:: python :linenos: config.add_route('root', '/') .. index:: single: generating route URLs single: route URLs .. _generating_route_urls: Generating Route URLs --------------------- Use the :meth:`pyramid.request.Request.route_url` method to generate URLs based on route patterns. For example, if you've configured a route with the ``name`` "foo" and the ``pattern`` "{a}/{b}/{c}", you might do this. .. code-block:: python :linenos: url = request.route_url('foo', a='1', b='2', c='3') This would return something like the string ``http://example.com/1/2/3`` (at least if the current protocol and hostname implied ``http://example.com``). To generate only the *path* portion of a URL from a route, use the :meth:`pyramid.request.Request.route_path` API instead of :meth:`~pyramid.request.Request.route_url`. .. code-block:: python url = request.route_path('foo', a='1', b='2', c='3') This will return the string ``/1/2/3`` rather than a full URL. Replacement values passed to ``route_url`` or ``route_path`` must be Unicode or bytestrings encoded in UTF-8. One exception to this rule exists: if you're trying to replace a "remainder" match value (a ``*stararg`` replacement value), the value may be a tuple containing Unicode strings or UTF-8 strings. Note that URLs and paths generated by ``route_url`` and ``route_path`` are always URL-quoted string types (they contain no non-ASCII characters). Therefore, if you've added a route like so: .. code-block:: python config.add_route('la', u'/La Peña/{city}') And you later generate a URL using ``route_path`` or ``route_url`` like so: .. code-block:: python url = request.route_path('la', city=u'Québec') You will wind up with the path encoded to UTF-8 and URL-quoted like so: .. code-block:: text /La%20Pe%C3%B1a/Qu%C3%A9bec If you have a ``*stararg`` remainder dynamic part of your route pattern: .. code-block:: python config.add_route('abc', 'a/b/c/*foo') And you later generate a URL using ``route_path`` or ``route_url`` using a *string* as the replacement value: .. code-block:: python url = request.route_path('abc', foo=u'Québec/biz') The value you pass will be URL-quoted except for embedded slashes in the result: .. code-block:: text /a/b/c/Qu%C3%A9bec/biz You can get a similar result by passing a tuple composed of path elements: .. code-block:: python url = request.route_path('abc', foo=(u'Québec', u'biz')) Each value in the tuple will be URL-quoted and joined by slashes in this case: .. code-block:: text /a/b/c/Qu%C3%A9bec/biz .. index:: single: static routes .. _static_route_narr: Static Routes ------------- Routes may be added with a ``static`` keyword argument. For example: .. code-block:: python :linenos: config = Configurator() config.add_route('page', '/page/{action}', static=True) Routes added with a ``True`` ``static`` keyword argument will never be considered for matching at request time. Static routes are useful for URL generation purposes only. As a result, it is usually nonsensical to provide other non-``name`` and non-``pattern`` arguments to :meth:`~pyramid.config.Configurator.add_route` when ``static`` is passed as ``True``, as none of the other arguments will ever be employed. A single exception to this rule is use of the ``pregenerator`` argument, which is not ignored when ``static`` is ``True``. :ref:`External routes ` are implicitly static. .. versionadded:: 1.1 the ``static`` argument to :meth:`~pyramid.config.Configurator.add_route`. .. _external_route_narr: External Routes --------------- .. versionadded:: 1.5 Route patterns that are valid URLs, are treated as external routes. Like :ref:`static routes ` they are useful for URL generation purposes only and are never considered for matching at request time. .. code-block:: python :linenos: >>> config = Configurator() >>> config.add_route('youtube', 'https://youtube.com/watch/{video_id}') ... >>> request.route_url('youtube', video_id='oHg5SJYRHA0') >>> "https://youtube.com/watch/oHg5SJYRHA0" Most pattern replacements and calls to :meth:`pyramid.request.Request.route_url` will work as expected. However, calls to :meth:`pyramid.request.Request.route_path` against external patterns will raise an exception, and passing ``_app_url`` to :meth:`~pyramid.request.Request.route_url` to generate a URL against a route that has an external pattern will also raise an exception. .. index:: single: redirecting to slash-appended routes .. _redirecting_to_slash_appended_routes: Redirecting to Slash-Appended Routes ------------------------------------ For behavior like Django's ``APPEND_SLASH=True``, use the ``append_slash`` argument to :meth:`pyramid.config.Configurator.add_notfound_view` or the equivalent ``append_slash`` argument to the :class:`pyramid.view.notfound_view_config` decorator. Adding ``append_slash=True`` is a way to automatically redirect requests where the URL lacks a trailing slash, but requires one to match the proper route. When configured, along with at least one other route in your application, this view will be invoked if the value of ``PATH_INFO`` does not already end in a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's pattern. In this case it does an HTTP redirect to the slash-appended ``PATH_INFO``. In addition you may pass anything that implements :class:`pyramid.interfaces.IResponse` which will then be used in place of the default class (:class:`pyramid.httpexceptions.HTTPFound`). Let's use an example. If the following routes are configured in your application: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPNotFound def notfound(request): return HTTPNotFound('Not found, bro.') def no_slash(request): return Response('No slash') def has_slash(request): return Response('Has slash') def main(g, **settings): config = Configurator() config.add_route('noslash', 'no_slash') config.add_route('hasslash', 'has_slash/') config.add_view(no_slash, route_name='noslash') config.add_view(has_slash, route_name='hasslash') config.add_notfound_view(notfound, append_slash=True) If a request enters the application with the ``PATH_INFO`` value of ``/no_slash``, the first route will match and the browser will show "No slash". However, if a request enters the application with the ``PATH_INFO`` value of ``/no_slash/``, *no* route will match, and the slash-appending not found view will not find a matching route with an appended slash. As a result, the ``notfound`` view will be called and it will return a "Not found, bro." body. If a request enters the application with the ``PATH_INFO`` value of ``/has_slash/``, the second route will match. If a request enters the application with the ``PATH_INFO`` value of ``/has_slash``, a route *will* be found by the slash-appending :term:`Not Found View`. An HTTP redirect to ``/has_slash/`` will be returned to the user's browser. As a result, the ``notfound`` view will never actually be called. The following application uses the :class:`pyramid.view.notfound_view_config` and :class:`pyramid.view.view_config` decorators and a :term:`scan` to do exactly the same job: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPNotFound from pyramid.view import notfound_view_config, view_config @notfound_view_config(append_slash=True) def notfound(request): return HTTPNotFound('Not found, bro.') @view_config(route_name='noslash') def no_slash(request): return Response('No slash') @view_config(route_name='hasslash') def has_slash(request): return Response('Has slash') def main(g, **settings): config = Configurator() config.add_route('noslash', 'no_slash') config.add_route('hasslash', 'has_slash/') config.scan() .. warning:: You **should not** rely on this mechanism to redirect ``POST`` requests. The redirect of the slash-appending :term:`Not Found View` will turn a ``POST`` request into a ``GET``, losing any ``POST`` data in the original request. See :ref:`view_module` and :ref:`changing_the_notfound_view` for a more general description of how to configure a view and/or a :term:`Not Found View`. .. index:: pair: debugging; route matching .. _debug_routematch_section: Debugging Route Matching ------------------------ It's useful to be able to take a peek under the hood when requests that enter your application aren't matching your routes as you expect them to. To debug route matching, use the ``PYRAMID_DEBUG_ROUTEMATCH`` environment variable or the ``pyramid.debug_routematch`` configuration file setting (set either to ``true``). Details of the route matching decision for a particular request to the :app:`Pyramid` application will be printed to the ``stderr`` of the console which you started the application from. For example: .. code-block:: text :linenos: $ PYRAMID_DEBUG_ROUTEMATCH=true $VENV/bin/pserve development.ini Starting server in PID 13586. serving on 0.0.0.0:6543 view at http://127.0.0.1:6543 2010-12-16 14:45:19,956 no route matched for url \ http://localhost:6543/wontmatch 2010-12-16 14:45:20,010 no route matched for url \ http://localhost:6543/favicon.ico 2010-12-16 14:41:52,084 route matched for url \ http://localhost:6543/static/logo.png; \ route_name: 'static/', .... See :ref:`environment_chapter` for more information about how and where to set these values. You can also use the ``proutes`` command to see a display of all the routes configured in your application. For more information, see :ref:`displaying_application_routes`. .. _route_prefix: Using a Route Prefix to Compose Applications -------------------------------------------- .. versionadded:: 1.2 The :meth:`pyramid.config.Configurator.include` method allows configuration statements to be included from separate files. See :ref:`building_an_extensible_app` for information about this method. Using :meth:`pyramid.config.Configurator.include` allows you to build your application from small and potentially reusable components. The :meth:`pyramid.config.Configurator.include` method accepts an argument named ``route_prefix`` which can be useful to authors of URL-dispatch-based applications. If ``route_prefix`` is supplied to the include method, it must be a string. This string represents a route prefix that will be prepended to all route patterns added by the *included* configuration. Any calls to :meth:`pyramid.config.Configurator.add_route` within the included callable will have their pattern prefixed with the value of ``route_prefix``. This can be used to help mount a set of routes at a different location than the included callable's author intended while still maintaining the same route names. For example: .. code-block:: python :linenos: from pyramid.config import Configurator def users_include(config): config.add_route('show_users', '/show') def main(global_config, **settings): config = Configurator() config.include(users_include, route_prefix='/users') In the above configuration, the ``show_users`` route will have an effective route pattern of ``/users/show`` instead of ``/show`` because the ``route_prefix`` argument will be prepended to the pattern. The route will then only match if the URL path is ``/users/show``, and when the :meth:`pyramid.request.Request.route_url` function is called with the route name ``show_users``, it will generate a URL with that same path. Route prefixes are recursive, so if a callable executed via an include itself turns around and includes another callable, the second-level route prefix will be prepended with the first: .. code-block:: python :linenos: from pyramid.config import Configurator def timing_include(config): config.add_route('show_times', '/times') def users_include(config): config.add_route('show_users', '/show') config.include(timing_include, route_prefix='/timing') def main(global_config, **settings): config = Configurator() config.include(users_include, route_prefix='/users') In the above configuration, the ``show_users`` route will still have an effective route pattern of ``/users/show``. The ``show_times`` route, however, will have an effective pattern of ``/users/timing/times``. Route prefixes have no impact on the requirement that the set of route *names* in any given Pyramid configuration must be entirely unique. If you compose your URL dispatch application out of many small subapplications using :meth:`pyramid.config.Configurator.include`, it's wise to use a dotted name for your route names so they'll be unlikely to conflict with other packages that may be added in the future. For example: .. code-block:: python :linenos: from pyramid.config import Configurator def timing_include(config): config.add_route('timing.show_times', '/times') def users_include(config): config.add_route('users.show_users', '/show') config.include(timing_include, route_prefix='/timing') def main(global_config, **settings): config = Configurator() config.include(users_include, route_prefix='/users') .. index:: single: route predicates (custom) .. _custom_route_predicates: Custom Route Predicates ----------------------- Each of the predicate callables fed to the ``custom_predicates`` argument of :meth:`~pyramid.config.Configurator.add_route` must be a callable accepting two arguments. The first argument passed to a custom predicate is a dictionary conventionally named ``info``. The second argument is the current :term:`request` object. The ``info`` dictionary has a number of contained values, including ``match`` and ``route``. ``match`` is a dictionary which represents the arguments matched in the URL by the route. ``route`` is an object representing the route which was matched (see :class:`pyramid.interfaces.IRoute` for the API of such a route object). ``info['match']`` is useful when predicates need access to the route match. For example: .. code-block:: python :linenos: def any_of(segment_name, *allowed): def predicate(info, request): if info['match'][segment_name] in allowed: return True return predicate num_one_two_or_three = any_of('num', 'one', 'two', 'three') config.add_route('route_to_num', '/{num}', custom_predicates=(num_one_two_or_three,)) The above ``any_of`` function generates a predicate which ensures that the match value named ``segment_name`` is in the set of allowable values represented by ``allowed``. We use this ``any_of`` function to generate a predicate function named ``num_one_two_or_three``, which ensures that the ``num`` segment is one of the values ``one``, ``two``, or ``three`` , and use the result as a custom predicate by feeding it inside a tuple to the ``custom_predicates`` argument to :meth:`~pyramid.config.Configurator.add_route`. A custom route predicate may also *modify* the ``match`` dictionary. For instance, a predicate might do some type conversion of values: .. code-block:: python :linenos: def integers(*segment_names): def predicate(info, request): match = info['match'] for segment_name in segment_names: try: match[segment_name] = int(match[segment_name]) except (TypeError, ValueError): pass return True return predicate ymd_to_int = integers('year', 'month', 'day') config.add_route('ymd', '/{year}/{month}/{day}', custom_predicates=(ymd_to_int,)) Note that a conversion predicate is still a predicate, so it must return ``True`` or ``False``. A predicate that does *only* conversion, such as the one we demonstrate above, should unconditionally return ``True``. To avoid the try/except uncertainty, the route pattern can contain regular expressions specifying requirements for that marker. For instance: .. code-block:: python :linenos: def integers(*segment_names): def predicate(info, request): match = info['match'] for segment_name in segment_names: match[segment_name] = int(match[segment_name]) return True return predicate ymd_to_int = integers('year', 'month', 'day') config.add_route('ymd', '/{year:\d+}/{month:\d+}/{day:\d+}', custom_predicates=(ymd_to_int,)) Now the try/except is no longer needed because the route will not match at all unless these markers match ``\d+`` which requires them to be valid digits for an ``int`` type conversion. The ``match`` dictionary passed within ``info`` to each predicate attached to a route will be the same dictionary. Therefore, when registering a custom predicate which modifies the ``match`` dict, the code registering the predicate should usually arrange for the predicate to be the *last* custom predicate in the custom predicate list. Otherwise, custom predicates which fire subsequent to the predicate which performs the ``match`` modification will receive the *modified* match dictionary. .. warning:: It is a poor idea to rely on ordering of custom predicates to build a conversion pipeline, where one predicate depends on the side effect of another. For instance, it's a poor idea to register two custom predicates, one which handles conversion of a value to an int, the next which handles conversion of that integer to some custom object. Just do all that in a single custom predicate. The ``route`` object in the ``info`` dict is an object that has two useful attributes: ``name`` and ``pattern``. The ``name`` attribute is the route name. The ``pattern`` attribute is the route pattern. Here's an example of using the route in a set of route predicates: .. code-block:: python :linenos: def twenty_ten(info, request): if info['route'].name in ('ymd', 'ym', 'y'): return info['match']['year'] == '2010' config.add_route('y', '/{year}', custom_predicates=(twenty_ten,)) config.add_route('ym', '/{year}/{month}', custom_predicates=(twenty_ten,)) config.add_route('ymd', '/{year}/{month}/{day}', custom_predicates=(twenty_ten,)) The above predicate, when added to a number of route configurations ensures that the year match argument is '2010' if and only if the route name is 'ymd', 'ym', or 'y'. You can also caption the predicates by setting the ``__text__`` attribute. This will help you with the ``pviews`` command (see :ref:`displaying_application_routes`) and the ``pyramid_debugtoolbar``. If a predicate is a class, just add ``__text__`` property in a standard manner. .. code-block:: python :linenos: class DummyCustomPredicate1(object): def __init__(self): self.__text__ = 'my custom class predicate' class DummyCustomPredicate2(object): __text__ = 'my custom class predicate' If a predicate is a method, you'll need to assign it after method declaration (see `PEP 232 `_). .. code-block:: python :linenos: def custom_predicate(): pass custom_predicate.__text__ = 'my custom method predicate' If a predicate is a classmethod, using ``@classmethod`` will not work, but you can still easily do it by wrapping it in a classmethod call. .. code-block:: python :linenos: def classmethod_predicate(): pass classmethod_predicate.__text__ = 'my classmethod predicate' classmethod_predicate = classmethod(classmethod_predicate) The same will work with ``staticmethod``, using ``staticmethod`` instead of ``classmethod``. .. seealso:: See also :class:`pyramid.interfaces.IRoute` for more API documentation about route objects. .. index:: single: route factory .. _route_factories: Route Factories --------------- Although it is not a particularly common need in basic applications, a "route" configuration declaration can mention a "factory". When a route matches a request, and a factory is attached to the route, the :term:`root factory` passed at startup time to the :term:`Configurator` is ignored. Instead the factory associated with the route is used to generate a :term:`root` object. This object will usually be used as the :term:`context` resource of the view callable ultimately found via :term:`view lookup`. .. code-block:: python :linenos: config.add_route('abc', '/abc', factory='myproject.resources.root_factory') config.add_view('myproject.views.theview', route_name='abc') The factory can either be a Python object or a :term:`dotted Python name` (a string) which points to such a Python object, as it is above. In this way, each route can use a different factory, making it possible to supply a different :term:`context` resource object to the view related to each particular route. A factory must be a callable which accepts a request and returns an arbitrary Python object. For example, the below class can be used as a factory: .. code-block:: python :linenos: class Mine(object): def __init__(self, request): pass A route factory is actually conceptually identical to the :term:`root factory` described at :ref:`the_resource_tree`. Supplying a different resource factory for each route is useful when you're trying to use a :app:`Pyramid` :term:`authorization policy` to provide declarative, "context sensitive" security checks. Each resource can maintain a separate :term:`ACL`, as documented in :ref:`using_security_with_urldispatch`. It is also useful when you wish to combine URL dispatch with :term:`traversal` as documented within :ref:`hybrid_chapter`. .. index:: pair: URL dispatch; security .. _using_security_with_urldispatch: Using :app:`Pyramid` Security with URL Dispatch ----------------------------------------------- :app:`Pyramid` provides its own security framework which consults an :term:`authorization policy` before allowing any application code to be called. This framework operates in terms of an access control list, which is stored as an ``__acl__`` attribute of a resource object. A common thing to want to do is to attach an ``__acl__`` to the resource object dynamically for declarative security purposes. You can use the ``factory`` argument that points at a factory which attaches a custom ``__acl__`` to an object at its creation time. Such a ``factory`` might look like so: .. code-block:: python :linenos: class Article(object): def __init__(self, request): matchdict = request.matchdict article = matchdict.get('article', None) if article == '1': self.__acl__ = [ (Allow, 'editor', 'view') ] If the route ``archives/{article}`` is matched, and the article number is ``1``, :app:`Pyramid` will generate an ``Article`` :term:`context` resource with an ACL on it that allows the ``editor`` principal the ``view`` permission. Obviously you can do more generic things than inspect the route's match dict to see if the ``article`` argument matches a particular string. Our sample ``Article`` factory class is not very ambitious. .. note:: See :ref:`security_chapter` for more information about :app:`Pyramid` security and ACLs. .. index:: pair: route; view callable lookup details Route View Callable Registration and Lookup Details --------------------------------------------------- When a request enters the system which matches the pattern of the route, the usual result is simple: the view callable associated with the route is invoked with the request that caused the invocation. For most usage, you needn't understand more than this. How it works is an implementation detail. In the interest of completeness, however, we'll explain how it *does* work in this section. You can skip it if you're uninterested. When a view is associated with a route configuration, :app:`Pyramid` ensures that a :term:`view configuration` is registered that will always be found when the route pattern is matched during a request. To do so: - A special route-specific :term:`interface` is created at startup time for each route configuration declaration. - When an ``add_view`` statement mentions a ``route name`` attribute, a :term:`view configuration` is registered at startup time. This view configuration uses a route-specific interface as a :term:`request` type. - At runtime, when a request causes any route to match, the :term:`request` object is decorated with the route-specific interface. - The fact that the request is decorated with a route-specific interface causes the :term:`view lookup` machinery to always use the view callable registered using that interface by the route configuration to service requests that match the route pattern. As we can see from the above description, technically, URL dispatch doesn't actually map a URL pattern directly to a view callable. Instead URL dispatch is a :term:`resource location` mechanism. A :app:`Pyramid` :term:`resource location` subsystem (i.e., :term:`URL dispatch` or :term:`traversal`) finds a :term:`resource` object that is the :term:`context` of a :term:`request`. Once the :term:`context` is determined, a separate subsystem named :term:`view lookup` is then responsible for finding and invoking a :term:`view callable` based on information available in the context and the request. When URL dispatch is used, the resource location and view lookup subsystems provided by :app:`Pyramid` are still being utilized, but in a way which does not require a developer to understand either of them in detail. If no route is matched using :term:`URL dispatch`, :app:`Pyramid` falls back to :term:`traversal` to handle the :term:`request`. References ---------- A tutorial showing how :term:`URL dispatch` can be used to create a :app:`Pyramid` application exists in :ref:`bfg_sql_wiki_tutorial`. pyramid-1.6/docs/narr/vhosting.rst0000644000076500000240000001251412621241570020042 0ustar michaelstaff00000000000000.. index:: single: virtual hosting .. _vhosting_chapter: Virtual Hosting =============== "Virtual hosting" is, loosely, the act of serving a :app:`Pyramid` application or a portion of a :app:`Pyramid` application under a URL space that it does not "naturally" inhabit. :app:`Pyramid` provides facilities for serving an application under a URL "prefix", as well as serving a *portion* of a :term:`traversal` based application under a root URL. .. index:: single: hosting an app under a prefix Hosting an Application Under a URL Prefix ----------------------------------------- :app:`Pyramid` supports a common form of virtual hosting whereby you can host a :app:`Pyramid` application as a "subset" of some other site (e.g., under ``http://example.com/mypyramidapplication/`` as opposed to under ``http://example.com/``). If you use a "pure Python" environment, this functionality can be provided by Paste's `urlmap `_ "composite" WSGI application. Alternatively, you can use :term:`mod_wsgi` to serve your application, which handles this virtual hosting translation for you "under the hood". If you use the ``urlmap`` composite application "in front" of a :app:`Pyramid` application or if you use :term:`mod_wsgi` to serve up a :app:`Pyramid` application, nothing special needs to be done within the application for URLs to be generated that contain a prefix. :mod:`paste.urlmap` and :term:`mod_wsgi` manipulate the :term:`WSGI` environment in such a way that the ``PATH_INFO`` and ``SCRIPT_NAME`` variables are correct for some given prefix. Here's an example of a PasteDeploy configuration snippet that includes a ``urlmap`` composite. .. code-block:: ini :linenos: [app:mypyramidapp] use = egg:mypyramidapp [composite:main] use = egg:Paste#urlmap /pyramidapp = mypyramidapp This "roots" the :app:`Pyramid` application at the prefix ``/pyramidapp`` and serves up the composite as the "main" application in the file. .. note:: If you're using an Apache server to proxy to a Paste ``urlmap`` composite, you may have to use the `ProxyPreserveHost `_ directive to pass the original ``HTTP_HOST`` header along to the application, so URLs get generated properly. As of this writing the ``urlmap`` composite does not seem to respect the ``HTTP_X_FORWARDED_HOST`` parameter, which will contain the original host header even if ``HTTP_HOST`` is incorrect. If you use :term:`mod_wsgi`, you do not need to use a ``composite`` application in your ``.ini`` file. The ``WSGIScriptAlias`` configuration setting in a :term:`mod_wsgi` configuration does the work for you: .. code-block:: apache :linenos: WSGIScriptAlias /pyramidapp /Users/chrism/projects/modwsgi/env/pyramid.wsgi In the above configuration, we root a :app:`Pyramid` application at ``/pyramidapp`` within the Apache configuration. .. index:: single: virtual root .. _virtual_root_support: Virtual Root Support -------------------- :app:`Pyramid` also supports "virtual roots", which can be used in :term:`traversal`-based (but not :term:`URL dispatch`-based) applications. Virtual root support is useful when you'd like to host some resource in a :app:`Pyramid` resource tree as an application under a URL pathname that does not include the resource path itself. For example, you might want to serve the object at the traversal path ``/cms`` as an application reachable via ``http://example.com/`` (as opposed to ``http://example.com/cms``). To specify a virtual root, cause an environment variable to be inserted into the WSGI environ named ``HTTP_X_VHM_ROOT`` with a value that is the absolute pathname to the resource object in the resource tree that should behave as the "root" resource. As a result, the traversal machinery will respect this value during traversal (prepending it to the PATH_INFO before traversal starts), and the :meth:`pyramid.request.Request.resource_url` API will generate the "correct" virtually-rooted URLs. An example of an Apache ``mod_proxy`` configuration that will host the ``/cms`` subobject as ``http://www.example.com/`` using this facility is below: .. code-block:: apache :linenos: NameVirtualHost *:80 ServerName www.example.com RewriteEngine On RewriteRule ^/(.*) http://127.0.0.1:6543/$1 [L,P] ProxyPreserveHost on RequestHeader add X-Vhm-Root /cms .. note:: Use of the ``RequestHeader`` directive requires that the Apache `mod_headers `_ module be available in the Apache environment you're using. For a :app:`Pyramid` application running under :term:`mod_wsgi`, the same can be achieved using ``SetEnv``: .. code-block:: apache :linenos: SetEnv HTTP_X_VHM_ROOT /cms Setting a virtual root has no effect when using an application based on :term:`URL dispatch`. Further Documentation and Examples ---------------------------------- The API documentation in :ref:`traversal_module` documents a :func:`pyramid.traversal.virtual_root` API. When called, it returns the virtual root object (or the physical root object if no virtual root has been specified). :ref:`modwsgi_tutorial` has detailed information about using :term:`mod_wsgi` to serve :app:`Pyramid` applications. pyramid-1.6/docs/narr/viewconfig.rst0000644000076500000240000012142112611324641020337 0ustar michaelstaff00000000000000.. _view_config_chapter: .. _view_configuration: .. _view_lookup: View Configuration ================== .. index:: single: view lookup :term:`View lookup` is the :app:`Pyramid` subsystem responsible for finding and invoking a :term:`view callable`. :term:`View configuration` controls how :term:`view lookup` operates in your application. During any given request, view configuration information is compared against request data by the view lookup subsystem in order to find the "best" view callable for that request. In earlier chapters, you have been exposed to a few simple view configuration declarations without much explanation. In this chapter we will explore the subject in detail. .. index:: pair: resource; mapping to view callable pair: URL pattern; mapping to view callable Mapping a Resource or URL Pattern to a View Callable ---------------------------------------------------- A developer makes a :term:`view callable` available for use within a :app:`Pyramid` application via :term:`view configuration`. A view configuration associates a view callable with a set of statements that determine the set of circumstances which must be true for the view callable to be invoked. A view configuration statement is made about information present in the :term:`context` resource and the :term:`request`. View configuration is performed in one of two ways: - By running a :term:`scan` against application source code which has a :class:`pyramid.view.view_config` decorator attached to a Python object as per :ref:`mapping_views_using_a_decorator_section`. - By using the :meth:`pyramid.config.Configurator.add_view` method as per :ref:`mapping_views_using_imperative_config_section`. .. index:: single: view configuration parameters .. _view_configuration_parameters: View Configuration Parameters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All forms of view configuration accept the same general types of arguments. Many arguments supplied during view configuration are :term:`view predicate` arguments. View predicate arguments used during view configuration are used to narrow the set of circumstances in which :term:`view lookup` will find a particular view callable. :term:`View predicate` attributes are an important part of view configuration that enables the :term:`view lookup` subsystem to find and invoke the appropriate view. The greater the number of predicate attributes possessed by a view's configuration, the more specific the circumstances need to be before the registered view callable will be invoked. The fewer the number of predicates which are supplied to a particular view configuration, the more likely it is that the associated view callable will be invoked. A view with five predicates will always be found and evaluated before a view with two, for example. This does not mean however, that :app:`Pyramid` "stops looking" when it finds a view registration with predicates that don't match. If one set of view predicates does not match, the "next most specific" view (if any) is consulted for predicates, and so on, until a view is found, or no view can be matched up with the request. The first view with a set of predicates all of which match the request environment will be invoked. If no view can be found with predicates which allow it to be matched up with the request, :app:`Pyramid` will return an error to the user's browser, representing a "not found" (404) page. See :ref:`changing_the_notfound_view` for more information about changing the default :term:`Not Found View`. Other view configuration arguments are non-predicate arguments. These tend to modify the response of the view callable or prevent the view callable from being invoked due to an authorization policy. The presence of non-predicate arguments in a view configuration does not narrow the circumstances in which the view callable will be invoked. .. _nonpredicate_view_args: Non-Predicate Arguments +++++++++++++++++++++++ ``permission`` The name of a :term:`permission` that the user must possess in order to invoke the :term:`view callable`. See :ref:`view_security_section` for more information about view security and permissions. If ``permission`` is not supplied, no permission is registered for this view (it's accessible by any caller). ``attr`` The view machinery defaults to using the ``__call__`` method of the :term:`view callable` (or the function itself, if the view callable is a function) to obtain a response. The ``attr`` value allows you to vary the method attribute used to obtain the response. For example, if your view was a class, and the class has a method named ``index`` and you wanted to use this method instead of the class's ``__call__`` method to return the response, you'd say ``attr="index"`` in the view configuration for the view. This is most useful when the view definition is a class. If ``attr`` is not supplied, ``None`` is used (implying the function itself if the view is a function, or the ``__call__`` callable attribute if the view is a class). ``renderer`` Denotes the :term:`renderer` implementation which will be used to construct a :term:`response` from the associated view callable's return value. .. seealso:: See also :ref:`renderers_chapter`. This is either a single string term (e.g., ``json``) or a string implying a path or :term:`asset specification` (e.g., ``templates/views.pt``) naming a :term:`renderer` implementation. If the ``renderer`` value does not contain a dot (``.``), the specified string will be used to look up a renderer implementation, and that renderer implementation will be used to construct a response from the view return value. If the ``renderer`` value contains a dot (``.``), the specified term will be treated as a path, and the filename extension of the last element in the path will be used to look up the renderer implementation, which will be passed the full path. When the renderer is a path—although a path is usually just a simple relative pathname (e.g., ``templates/foo.pt``, implying that a template named "foo.pt" is in the "templates" directory relative to the directory of the current :term:`package`)—the path can be absolute, starting with a slash on UNIX or a drive letter prefix on Windows. The path can alternatively be a :term:`asset specification` in the form ``some.dotted.package_name:relative/path``, making it possible to address template assets which live in a separate package. The ``renderer`` attribute is optional. If it is not defined, the "null" renderer is assumed (no rendering is performed and the value is passed back to the upstream :app:`Pyramid` machinery unchanged). Note that if the view callable itself returns a :term:`response` (see :ref:`the_response`), the specified renderer implementation is never called. ``http_cache`` When you supply an ``http_cache`` value to a view configuration, the ``Expires`` and ``Cache-Control`` headers of a response generated by the associated view callable are modified. The value for ``http_cache`` may be one of the following: - A nonzero integer. If it's a nonzero integer, it's treated as a number of seconds. This number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=3600`` instructs the requesting browser to 'cache this response for an hour, please'. - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` instance, it will be converted into a number of seconds, and that number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=datetime.timedelta(days=1)`` instructs the requesting browser to 'cache this response for a day, please'. - Zero (``0``). If the value is zero, the ``Cache-Control`` and ``Expires`` headers present in all responses from this view will be composed such that client browser cache (and any intermediate caches) are instructed to never cache the response. - A two-tuple. If it's a two-tuple (e.g., ``http_cache=(1, {'public':True})``), the first value in the tuple may be a nonzero integer or a ``datetime.timedelta`` instance. In either case this value will be used as the number of seconds to cache the response. The second value in the tuple must be a dictionary. The values present in the dictionary will be used as input to the ``Cache-Control`` response header. For example: ``http_cache=(3600, {'public':True})`` means 'cache for an hour, and add ``public`` to the Cache-Control header of the response'. All keys and values supported by the ``webob.cachecontrol.CacheControl`` interface may be added to the dictionary. Supplying ``{'public':True}`` is equivalent to calling ``response.cache_control.public = True``. Providing a non-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value)`` within your view's body. Providing a two-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value[0], **value[1])`` within your view's body. If you wish to avoid influencing the ``Expires`` header, and instead wish to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, i.e., ``(None, {'public':True})``. ``wrapper`` The :term:`view name` of a different :term:`view configuration` which will receive the response body of this view as the ``request.wrapped_body`` attribute of its own :term:`request`, and the :term:`response` returned by this view as the ``request.wrapped_response`` attribute of its own request. Using a wrapper makes it possible to "chain" views together to form a composite response. The response of the outermost wrapper view will be returned to the user. The wrapper view will be found as any view is found. See :ref:`view_lookup`. The "best" wrapper view will be found based on the lookup ordering. "Under the hood" this wrapper view is looked up via ``pyramid.view.render_view_to_response(context, request, 'wrapper_viewname')``. The context and request of a wrapper view is the same context and request of the inner view. If ``wrapper`` is not supplied, no wrapper view is used. ``decorator`` A :term:`dotted Python name` to a function (or the function itself) which will be used to decorate the registered :term:`view callable`. The decorator function will be called with the view callable as a single argument. The view callable it is passed will accept ``(context, request)``. The decorator must return a replacement view callable which also accepts ``(context, request)``. The ``decorator`` may also be an iterable of decorators, in which case they will be applied one after the other to the view, in reverse order. For example:: @view_config(..., decorator=(decorator2, decorator1)) def myview(request): ... Is similar to doing:: @view_config(...) @decorator2 @decorator1 def myview(request): ... All view callables in the decorator chain must return a response object implementing :class:`pyramid.interfaces.IResponse` or raise an exception: .. code-block:: python def log_timer(wrapped): def wrapper(context, request): start = time.time() response = wrapped(context, request) duration = time.time() - start response.headers['X-View-Time'] = '%.3f' % (duration,) log.info('view took %.3f seconds', duration) return response return wrapper ``mapper`` A Python object or :term:`dotted Python name` which refers to a :term:`view mapper`, or ``None``. By default it is ``None``, which indicates that the view should use the default view mapper. This plug-point is useful for Pyramid extension developers, but it's not very useful for "civilians" who are just developing stock Pyramid applications. Pay no attention to the man behind the curtain. Predicate Arguments +++++++++++++++++++ These arguments modify view lookup behavior. In general the more predicate arguments that are supplied, the more specific and narrower the usage of the configured view. ``name`` The :term:`view name` required to match this view callable. A ``name`` argument is typically only used when your application uses :term:`traversal`. Read :ref:`traversal_chapter` to understand the concept of a view name. If ``name`` is not supplied, the empty string is used (implying the default view). ``context`` An object representing a Python class of which the :term:`context` resource must be an instance *or* the :term:`interface` that the :term:`context` resource must provide in order for this view to be found and called. This predicate is true when the :term:`context` resource is an instance of the represented class or if the :term:`context` resource provides the represented interface; it is otherwise false. If ``context`` is not supplied, the value ``None``, which matches any resource, is used. ``route_name`` If ``route_name`` is supplied, the view callable will be invoked only when the named route has matched. This value must match the ``name`` of a :term:`route configuration` declaration (see :ref:`urldispatch_chapter`) that must match before this view will be called. Note that the ``route`` configuration referred to by ``route_name`` will usually have a ``*traverse`` token in the value of its ``pattern``, representing a part of the path that will be used by :term:`traversal` against the result of the route's :term:`root factory`. If ``route_name`` is not supplied, the view callable will only have a chance of being invoked if no other route was matched. This is when the request/context pair found via :term:`resource location` does not indicate it matched any configured route. ``request_type`` This value should be an :term:`interface` that the :term:`request` must provide in order for this view to be found and called. If ``request_type`` is not supplied, the value ``None`` is used, implying any request type. *This is an advanced feature, not often used by "civilians"*. ``request_method`` 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 (i.e., the ``REQUEST_METHOD`` of the WSGI environment) matches a supplied value. .. versionchanged:: 1.4 The use of ``"GET"`` also implies that the view will respond to ``"HEAD"``. If ``request_method`` is not supplied, the view will be invoked regardless of the ``REQUEST_METHOD`` of the :term:`WSGI` environment. ``request_param`` This value can be any string or a 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 any value supplied has an ``=`` sign in it, e.g., ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, *and* the value must match the right hand side of the expression (``123``) for the view to "match" the current request. If ``request_param`` is not supplied, the view will be invoked without consideration of keys and values in the ``request.params`` dictionary. ``match_param`` This param may be either a single string of the format "key=value" or a tuple containing one or more of these strings. This argument ensures that the view will only be called when the :term:`request` has key/value pairs in its :term:`matchdict` that equal those supplied in the predicate. For example, ``match_param="action=edit"`` would require the ``action`` parameter in the :term:`matchdict` match the right hand side of the expression (``edit``) for the view to "match" the current request. If the ``match_param`` is a tuple, every key/value pair must match for the predicate to pass. If ``match_param`` is not supplied, the view will be invoked without consideration of the keys and values in ``request.matchdict``. .. versionadded:: 1.2 ``containment`` This value should be a reference to a Python class or :term:`interface` that a parent object in the context resource's :term:`lineage` must provide in order for this view to be found and called. The resources in your resource tree must be "location-aware" to use this feature. If ``containment`` is not supplied, the interfaces and classes in the lineage are not considered when deciding whether or not to invoke the view callable. See :ref:`location_aware` for more information about location-awareness. ``xhr`` This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`WSGI` environment must possess an ``HTTP_X_REQUESTED_WITH`` header (i.e., ``X-Requested-With``) that has the value ``XMLHttpRequest`` for the associated view callable to be found and called. This is useful for detecting AJAX requests issued from jQuery, Prototype, and other Javascript libraries. If ``xhr`` is not specified, the ``HTTP_X_REQUESTED_WITH`` HTTP header is not taken into consideration when deciding whether or not to invoke the associated view callable. ``accept`` The value of this argument represents a match query for one or more mimetypes in the ``Accept`` HTTP request header. If this value is specified, it must be in one of the following forms: a mimetype match token in the form ``text/plain``, a wildcard mimetype match token in the form ``text/*``, or a match-all wildcard mimetype match token in the form ``*/*``. If any of the forms matches the ``Accept`` header of the request, this predicate will be true. If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not taken into consideration when deciding whether or not to invoke the associated view callable. ``header`` This value represents an HTTP header name or a header name/value pair. If ``header`` is specified, it must be a header name or a ``headername:headervalue`` pair. If ``header`` is specified without a value (a bare header name only, e.g., ``If-Modified-Since``), the view will only be invoked if the HTTP header exists with any value in the request. If ``header`` is specified, and possesses a name/value pair (e.g., ``User-Agent:Mozilla/.*``), the view will only be invoked if the HTTP header exists *and* the HTTP header matches the value requested. When the ``headervalue`` contains a ``:`` (colon), it will be considered a name/value pair (e.g., ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The value portion should be a regular expression. Whether or not the value represents a header name or a header name/value pair, the case of the header name is not significant. If ``header`` is not specified, the composition, presence, or absence of HTTP headers is not taken into consideration when deciding whether or not to invoke the associated view callable. ``path_info`` This value represents a regular expression pattern that will be tested against the ``PATH_INFO`` WSGI environment variable to decide whether or not to call the associated view callable. If the regex matches, this predicate will be ``True``. If ``path_info`` is not specified, the WSGI ``PATH_INFO`` is not taken into consideration when deciding whether or not to invoke the associated view callable. ``check_csrf`` If specified, this value should be one of ``None``, ``True``, ``False``, or a string representing the "check name". If the value is ``True`` or a string, CSRF checking will be performed. If the value is ``False`` or ``None``, CSRF checking will not be performed. If the value provided is a string, that string will be used as the "check name". If the value provided is ``True``, ``csrf_token`` will be used as the check name. If CSRF checking is performed, the checked value will be the value of ``request.params[check_name]``. This value will be compared against the value of ``request.session.get_csrf_token()``, and the check will pass if these two values are the same. If the check passes, the associated view will be permitted to execute. If the check fails, the associated view will not be permitted to execute. Note that using this feature requires a :term:`session factory` to have been configured. .. versionadded:: 1.4a2 ``physical_path`` If specified, this value should be a string or a tuple representing the :term:`physical path` of the context found via traversal for this predicate to match as true. For example, ``physical_path='/'``, ``physical_path='/a/b/c'``, or ``physical_path=('', 'a', 'b', 'c')``. This is not a path prefix match or a regex, but a whole-path match. It's useful when you want to always potentially show a view when some object is traversed to, but you can't be sure about what kind of object it will be, so you can't use the ``context`` predicate. The individual path elements between slash characters or in tuple elements should be the Unicode representation of the name of the resource and should not be encoded in any way. .. versionadded:: 1.4a3 ``effective_principals`` If specified, this value should be a :term:`principal` identifier or a sequence of principal identifiers. If the :meth:`pyramid.request.Request.effective_principals` method indicates that every principal named in the argument list is present in the current request, this predicate will return True; otherwise it will return False. For example: ``effective_principals=pyramid.security.Authenticated`` or ``effective_principals=('fred', 'group:admins')``. .. versionadded:: 1.4a4 ``custom_predicates`` If ``custom_predicates`` is specified, it must 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. Each custom predicate callable should accept two arguments, ``context`` and ``request``, and should return either ``True`` or ``False`` after doing arbitrary evaluation of the context resource and/or the request. If all callables return ``True``, the associated view callable will be considered viable for a given request. If ``custom_predicates`` is not specified, no custom predicates are used. ``predicates`` Pass a key/value pair here to use a third-party predicate registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. .. versionadded:: 1.4a1 Inverting Predicate Values ++++++++++++++++++++++++++ You can invert the meaning of any predicate value by wrapping it in a call to :class:`pyramid.config.not_`. .. code-block:: python :linenos: from pyramid.config import not_ config.add_view( 'mypackage.views.my_view', route_name='ok', request_method=not_('POST') ) The above example will ensure that the view is called if the request method is *not* ``POST``, at least if no other view is more specific. This technique of wrapping a predicate value in ``not_`` can be used anywhere predicate values are accepted: - :meth:`pyramid.config.Configurator.add_view` - :meth:`pyramid.view.view_config` .. versionadded:: 1.5 .. index:: single: view_config decorator .. _mapping_views_using_a_decorator_section: Adding View Configuration Using the ``@view_config`` Decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. warning:: Using this feature tends to slow down application startup slightly, as more work is performed at application startup to scan for view configuration declarations. For maximum startup performance, use the view configuration method described in :ref:`mapping_views_using_imperative_config_section` instead. The :class:`~pyramid.view.view_config` decorator can be used to associate :term:`view configuration` information with a function, method, or class that acts as a :app:`Pyramid` view callable. Here's an example of the :class:`~pyramid.view.view_config` decorator that lives within a :app:`Pyramid` application module ``views.py``: .. code-block:: python :linenos: from resources import MyResource from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='ok', request_method='POST', permission='read') def my_view(request): return Response('OK') Using this decorator as above replaces the need to add this imperative configuration stanza: .. code-block:: python :linenos: config.add_view('mypackage.views.my_view', route_name='ok', request_method='POST', permission='read') All arguments to ``view_config`` may be omitted. For example: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config() def my_view(request): """ My view """ return Response() Such a registration as the one directly above implies that the view name will be ``my_view``, registered with a ``context`` argument that matches any resource type, using no permission, registered against requests with any request method, request type, request param, route name, or containment. The mere existence of a ``@view_config`` decorator doesn't suffice to perform view configuration. All that the decorator does is "annotate" the function with your configuration declarations, it doesn't process them. To make :app:`Pyramid` process your :class:`pyramid.view.view_config` declarations, you *must* use the ``scan`` method of a :class:`pyramid.config.Configurator`: .. code-block:: python :linenos: # config is assumed to be an instance of the # pyramid.config.Configurator class config.scan() Please see :ref:`decorations_and_code_scanning` for detailed information about what happens when code is scanned for configuration declarations resulting from use of decorators like :class:`~pyramid.view.view_config`. See :ref:`configuration_module` for additional API arguments to the :meth:`~pyramid.config.Configurator.scan` method. For example, the method allows you to supply a ``package`` argument to better control exactly *which* code will be scanned. All arguments to the :class:`~pyramid.view.view_config` decorator mean precisely the same thing as they would if they were passed as arguments to the :meth:`pyramid.config.Configurator.add_view` method save for the ``view`` argument. Usage of the :class:`~pyramid.view.view_config` decorator is a form of :term:`declarative configuration`, while :meth:`pyramid.config.Configurator.add_view` is a form of :term:`imperative configuration`. However, they both do the same thing. .. index:: single: view_config placement .. _view_config_placement: ``@view_config`` Placement ++++++++++++++++++++++++++ A :class:`~pyramid.view.view_config` decorator can be placed in various points in your application. If your view callable is a function, it may be used as a function decorator: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='edit') def edit(request): return Response('edited!') If your view callable is a class, the decorator can also be used as a class decorator. All the arguments to the decorator are the same when applied against a class as when they are applied against a function. For example: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config(route_name='hello') class MyView(object): def __init__(self, request): self.request = request def __call__(self): return Response('hello') More than one :class:`~pyramid.view.view_config` decorator can be stacked on top of any number of others. Each decorator creates a separate view registration. For example: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response @view_config(route_name='edit') @view_config(route_name='change') def edit(request): return Response('edited!') This registers the same view under two different names. The decorator can also be used against a method of a class: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config class MyView(object): def __init__(self, request): self.request = request @view_config(route_name='hello') def amethod(self): return Response('hello') When the decorator is used against a method of a class, a view is registered for the *class*, so the class constructor must accept an argument list in one of two forms: either a single argument, ``request``, or two arguments, ``context, request``. The method which is decorated must return a :term:`response`. Using the decorator against a particular method of a class is equivalent to using the ``attr`` parameter in a decorator attached to the class itself. For example, the above registration implied by the decorator being used against the ``amethod`` method could be written equivalently as follows: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.view import view_config @view_config(attr='amethod', route_name='hello') class MyView(object): def __init__(self, request): self.request = request def amethod(self): return Response('hello') .. index:: single: add_view .. _mapping_views_using_imperative_config_section: Adding View Configuration Using :meth:`~pyramid.config.Configurator.add_view` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.add_view` method within :ref:`configuration_module` is used to configure a view "imperatively" (without a :class:`~pyramid.view.view_config` decorator). The arguments to this method are very similar to the arguments that you provide to the :class:`~pyramid.view.view_config` decorator. For example: .. code-block:: python :linenos: from pyramid.response import Response def hello_world(request): return Response('hello!') # config is assumed to be an instance of the # pyramid.config.Configurator class config.add_view(hello_world, route_name='hello') The first argument, a :term:`view callable`, is the only required argument. It must either be a Python object which is the view itself or a :term:`dotted Python name` to such an object. In the above example, the ``view callable`` is the ``hello_world`` function. When you use only :meth:`~pyramid.config.Configurator.add_view` to add view configurations, you don't need to issue a :term:`scan` in order for the view configuration to take effect. .. index:: single: view_defaults class decorator .. _view_defaults: ``@view_defaults`` Class Decorator ---------------------------------- .. versionadded:: 1.3 If you use a class as a view, you can use the :class:`pyramid.view.view_defaults` class decorator on the class to provide defaults to the view configuration information used by every ``@view_config`` decorator that decorates a method of that class. For instance, if you've got a class that has methods that represent "REST actions", all of which are mapped to the same route but different request methods, instead of this: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response class RESTView(object): def __init__(self, request): self.request = request @view_config(route_name='rest', request_method='GET') def get(self): return Response('get') @view_config(route_name='rest', request_method='POST') def post(self): return Response('post') @view_config(route_name='rest', request_method='DELETE') def delete(self): return Response('delete') You can do this: .. code-block:: python :linenos: from pyramid.view import view_defaults from pyramid.view import view_config from pyramid.response import Response @view_defaults(route_name='rest') class RESTView(object): def __init__(self, request): self.request = request @view_config(request_method='GET') def get(self): return Response('get') @view_config(request_method='POST') def post(self): return Response('post') @view_config(request_method='DELETE') def delete(self): return Response('delete') In the above example, we were able to take the ``route_name='rest'`` argument out of the call to each individual ``@view_config`` statement because we used a ``@view_defaults`` class decorator to provide the argument as a default to each view method it possessed. Arguments passed to ``@view_config`` will override any default passed to ``@view_defaults``. The ``view_defaults`` class decorator can also provide defaults to the :meth:`pyramid.config.Configurator.add_view` directive when a decorated class is passed to that directive as its ``view`` argument. For example, instead of this: .. code-block:: python :linenos: from pyramid.response import Response from pyramid.config import Configurator class RESTView(object): def __init__(self, request): self.request = request def get(self): return Response('get') def post(self): return Response('post') def delete(self): return Response('delete') def main(global_config, **settings): config = Configurator() config.add_route('rest', '/rest') config.add_view( RESTView, route_name='rest', attr='get', request_method='GET') config.add_view( RESTView, route_name='rest', attr='post', request_method='POST') config.add_view( RESTView, route_name='rest', attr='delete', request_method='DELETE') return config.make_wsgi_app() To reduce the amount of repetition in the ``config.add_view`` statements, we can move the ``route_name='rest'`` argument to a ``@view_defaults`` class decorator on the ``RESTView`` class: .. code-block:: python :linenos: from pyramid.view import view_defaults from pyramid.response import Response from pyramid.config import Configurator @view_defaults(route_name='rest') class RESTView(object): def __init__(self, request): self.request = request def get(self): return Response('get') def post(self): return Response('post') def delete(self): return Response('delete') def main(global_config, **settings): config = Configurator() config.add_route('rest', '/rest') config.add_view(RESTView, attr='get', request_method='GET') config.add_view(RESTView, attr='post', request_method='POST') config.add_view(RESTView, attr='delete', request_method='DELETE') return config.make_wsgi_app() :class:`pyramid.view.view_defaults` accepts the same set of arguments that :class:`pyramid.view.view_config` does, and they have the same meaning. Each argument passed to ``view_defaults`` provides a default for the view configurations of methods of the class it's decorating. Normal Python inheritance rules apply to defaults added via ``view_defaults``. For example: .. code-block:: python :linenos: @view_defaults(route_name='rest') class Foo(object): pass class Bar(Foo): pass The ``Bar`` class above will inherit its view defaults from the arguments passed to the ``view_defaults`` decorator of the ``Foo`` class. To prevent this from happening, use a ``view_defaults`` decorator without any arguments on the subclass: .. code-block:: python :linenos: @view_defaults(route_name='rest') class Foo(object): pass @view_defaults() class Bar(Foo): pass The ``view_defaults`` decorator only works as a class decorator; using it against a function or a method will produce nonsensical results. .. index:: single: view security pair: security; view .. _view_security_section: Configuring View Security ~~~~~~~~~~~~~~~~~~~~~~~~~ If an :term:`authorization policy` is active, any :term:`permission` attached to a :term:`view configuration` found during view lookup will be verified. This will ensure that the currently authenticated user possesses that permission against the :term:`context` resource before the view function is actually called. Here's an example of specifying a permission in a view configuration using :meth:`~pyramid.config.Configurator.add_view`: .. code-block:: python :linenos: # config is an instance of pyramid.config.Configurator config.add_route('add', '/add.html', factory='mypackage.Blog') config.add_view('myproject.views.add_entry', route_name='add', permission='add') When an :term:`authorization policy` is enabled, this view will be protected with the ``add`` permission. The view will *not be called* if the user does not possess the ``add`` permission relative to the current :term:`context`. Instead the :term:`forbidden view` result will be returned to the client as per :ref:`protecting_views`. .. index:: single: debugging not found errors single: not found error (debugging) .. _debug_notfound_section: :exc:`~pyramid.exceptions.NotFound` Errors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It's useful to be able to debug :exc:`~pyramid.exceptions.NotFound` error responses when they occur unexpectedly due to an application registry misconfiguration. To debug these errors, use the ``PYRAMID_DEBUG_NOTFOUND`` environment variable or the ``pyramid.debug_notfound`` configuration file setting. Details of why a view was not found will be printed to ``stderr``, and the browser representation of the error will include the same information. See :ref:`environment_chapter` for more information about how, and where to set these values. .. index:: single: HTTP caching .. _influencing_http_caching: Influencing HTTP Caching ------------------------ .. versionadded:: 1.1 When a non-``None`` ``http_cache`` argument is passed to a view configuration, Pyramid will set ``Expires`` and ``Cache-Control`` response headers in the resulting response, causing browsers to cache the response data for some time. See ``http_cache`` in :ref:`nonpredicate_view_args` for the allowable values and what they mean. Sometimes it's undesirable to have these headers set as the result of returning a response from a view, even though you'd like to decorate the view with a view configuration decorator that has ``http_cache``. Perhaps there's an alternative branch in your view code that returns a response that should never be cacheable, while the "normal" branch returns something that should always be cacheable. If this is the case, set the ``prevent_auto`` attribute of the ``response.cache_control`` object to a non-``False`` value. For example, the below view callable is configured with a ``@view_config`` decorator that indicates any response from the view should be cached for 3600 seconds. However, the view itself prevents caching from taking place unless there's a ``should_cache`` GET or POST variable: .. code-block:: python from pyramid.view import view_config @view_config(http_cache=3600) def view(request): response = Response() if 'should_cache' not in request.params: response.cache_control.prevent_auto = True return response Note that the ``http_cache`` machinery will overwrite or add to caching headers you set within the view itself, unless you use ``prevent_auto``. You can also turn off the effect of ``http_cache`` entirely for the duration of a Pyramid application lifetime. To do so, set the ``PYRAMID_PREVENT_HTTP_CACHE`` environment variable or the ``pyramid.prevent_http_cache`` configuration value setting to a true value. For more information, see :ref:`preventing_http_caching`. Note that setting ``pyramid.prevent_http_cache`` will have no effect on caching headers that your application code itself sets. It will only prevent caching headers that would have been set by the Pyramid HTTP caching machinery invoked as the result of the ``http_cache`` argument to view configuration. .. index:: pair: view configuration; debugging .. _debugging_view_configuration: Debugging View Configuration ---------------------------- See :ref:`displaying_matching_views` for information about how to display each of the view callables that might match for a given URL. This can be an effective way to figure out why a particular view callable is being called instead of the one you'd like to be called. pyramid-1.6/docs/narr/views.rst0000644000076500000240000005416512606630333017350 0ustar michaelstaff00000000000000.. _views_chapter: Views ===== One of the primary jobs of :app:`Pyramid` is to find and invoke a :term:`view callable` when a :term:`request` reaches your application. View callables are bits of code which do something interesting in response to a request made to your application. They are the "meat" of any interesting web application. .. note:: A :app:`Pyramid` :term:`view callable` is often referred to in conversational shorthand as a :term:`view`. In this documentation, however, we need to use less ambiguous terminology because there are significant differences between view *configuration*, the code that implements a view *callable*, and the process of view *lookup*. This chapter describes how view callables should be defined. We'll have to wait until a following chapter (entitled :ref:`view_config_chapter`) to find out how we actually tell :app:`Pyramid` to wire up view callables to particular URL patterns and other request circumstances. .. index:: single: view callables View Callables -------------- View callables are, at the risk of sounding obvious, callable Python objects. Specifically, view callables can be functions, classes, or instances that implement a ``__call__`` method (making the instance callable). View callables must, at a minimum, accept a single argument named ``request``. This argument represents a :app:`Pyramid` :term:`Request` object. A request object represents a :term:`WSGI` environment provided to :app:`Pyramid` by the upstream WSGI server. As you might expect, the request object contains everything your application needs to know about the specific HTTP request being made. A view callable's ultimate responsibility is to create a :app:`Pyramid` :term:`Response` object. This can be done by creating a :term:`Response` object in the view callable code and returning it directly or by raising special kinds of exceptions from within the body of a view callable. .. index:: single: view calling convention single: view function .. _function_as_view: Defining a View Callable as a Function -------------------------------------- One of the easiest way to define a view callable is to create a function that accepts a single argument named ``request``, and which returns a :term:`Response` object. For example, this is a "hello world" view callable implemented as a function: .. code-block:: python :linenos: from pyramid.response import Response def hello_world(request): return Response('Hello world!') .. index:: single: view calling convention single: view class .. _class_as_view: Defining a View Callable as a Class ----------------------------------- A view callable may also be represented by a Python class instead of a function. When a view callable is a class, the calling semantics are slightly different than when it is a function or another non-class callable. When a view callable is a class, the class's ``__init__`` method is called with a ``request`` parameter. As a result, an instance of the class is created. Subsequently, that instance's ``__call__`` method is invoked with no parameters. Views defined as classes must have the following traits. - an ``__init__`` method that accepts a ``request`` argument - a ``__call__`` (or other) method that accepts no parameters and which returns a response For example: .. code-block:: python :linenos: from pyramid.response import Response class MyView(object): def __init__(self, request): self.request = request def __call__(self): return Response('hello') The request object passed to ``__init__`` is the same type of request object described in :ref:`function_as_view`. If you'd like to use a different attribute than ``__call__`` to represent the method expected to return a response, you can use an ``attr`` value as part of the configuration for the view. See :ref:`view_configuration_parameters`. The same view callable class can be used in different view configuration statements with different ``attr`` values, each pointing at a different method of the class if you'd like the class to represent a collection of related view callables. .. index:: single: view response single: response .. _the_response: View Callable Responses ----------------------- A view callable may return an object that implements the :app:`Pyramid` :term:`Response` interface. The easiest way to return something that implements the :term:`Response` interface is to return a :class:`pyramid.response.Response` object instance directly. For example: .. code-block:: python :linenos: from pyramid.response import Response def view(request): return Response('OK') :app:`Pyramid` provides a range of different "exception" classes which inherit from :class:`pyramid.response.Response`. For example, an instance of the class :class:`pyramid.httpexceptions.HTTPFound` is also a valid response object because it inherits from :class:`~pyramid.response.Response`. For examples, see :ref:`http_exceptions` and :ref:`http_redirect`. .. note:: You can also return objects from view callables that aren't instances of :class:`pyramid.response.Response` in various circumstances. This can be helpful when writing tests and when attempting to share code between view callables. See :ref:`renderers_chapter` for the common way to allow for this. A much less common way to allow for view callables to return non-Response objects is documented in :ref:`using_iresponse`. .. index:: single: view exceptions .. _special_exceptions_in_callables: Using Special Exceptions in View Callables ------------------------------------------ Usually when a Python exception is raised within a view callable, :app:`Pyramid` allows the exception to propagate all the way out to the :term:`WSGI` server which invoked the application. It is usually caught and logged there. However, for convenience, a special set of exceptions exists. When one of these exceptions is raised within a view callable, it will always cause :app:`Pyramid` to generate a response. These are known as :term:`HTTP exception` objects. .. index:: single: HTTP exceptions .. _http_exceptions: HTTP Exceptions ~~~~~~~~~~~~~~~ All :mod:`pyramid.httpexceptions` classes which are documented as inheriting from the :class:`pyramid.httpexceptions.HTTPException` are :term:`http exception` objects. Instances of an HTTP exception object may either be *returned* or *raised* from within view code. In either case (return or raise) the instance will be used as the view's response. For example, the :class:`pyramid.httpexceptions.HTTPUnauthorized` exception can be raised. This will cause a response to be generated with a ``401 Unauthorized`` status: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPUnauthorized def aview(request): raise HTTPUnauthorized() An HTTP exception, instead of being raised, can alternately be *returned* (HTTP exceptions are also valid response objects): .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPUnauthorized def aview(request): return HTTPUnauthorized() A shortcut for creating an HTTP exception is the :func:`pyramid.httpexceptions.exception_response` function. This function accepts an HTTP status code and returns the corresponding HTTP exception. For example, instead of importing and constructing a :class:`~pyramid.httpexceptions.HTTPUnauthorized` response object, you can use the :func:`~pyramid.httpexceptions.exception_response` function to construct and return the same object. .. code-block:: python :linenos: from pyramid.httpexceptions import exception_response def aview(request): raise exception_response(401) This is the case because ``401`` is the HTTP status code for "HTTP Unauthorized". Therefore, ``raise exception_response(401)`` is functionally equivalent to ``raise HTTPUnauthorized()``. Documentation which maps each HTTP response code to its purpose and its associated HTTP exception object is provided within :mod:`pyramid.httpexceptions`. .. versionadded:: 1.1 The :func:`~pyramid.httpexceptions.exception_response` function. How Pyramid Uses HTTP Exceptions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTTP exceptions are meant to be used directly by application developers. However, Pyramid itself will raise two HTTP exceptions at various points during normal operations. * :exc:`~pyramid.httpexceptions.HTTPNotFound` gets raised when a view to service a request is not found. * :exc:`~pyramid.httpexceptions.HTTPForbidden` gets raised when authorization was forbidden by a security policy. If :exc:`~pyramid.httpexceptions.HTTPNotFound` is raised by Pyramid itself or within view code, the result of the :term:`Not Found View` will be returned to the user agent which performed the request. If :exc:`~pyramid.httpexceptions.HTTPForbidden` is raised by Pyramid itself within view code, the result of the :term:`Forbidden View` will be returned to the user agent which performed the request. .. index:: single: exception views .. _exception_views: Custom Exception Views ---------------------- The machinery which allows HTTP exceptions to be raised and caught by specialized views as described in :ref:`special_exceptions_in_callables` can also be used by application developers to convert arbitrary exceptions to responses. To register a view that should be called whenever a particular exception is raised from within :app:`Pyramid` view code, use the exception class (or one of its superclasses) as the :term:`context` of a view configuration which points at a view callable for which you'd like to generate a response. For example, given the following exception class in a module named ``helloworld.exceptions``: .. code-block:: python :linenos: class ValidationFailure(Exception): def __init__(self, msg): self.msg = msg You can wire a view callable to be called whenever any of your *other* code raises a ``helloworld.exceptions.ValidationFailure`` exception: .. code-block:: python :linenos: from pyramid.view import view_config from helloworld.exceptions import ValidationFailure @view_config(context=ValidationFailure) def failed_validation(exc, request): response = Response('Failed validation: %s' % exc.msg) response.status_int = 500 return response Assuming that a :term:`scan` was run to pick up this view registration, this view callable will be invoked whenever a ``helloworld.exceptions.ValidationFailure`` is raised by your application's view code. The same exception raised by a custom root factory, a custom traverser, or a custom view or route predicate is also caught and hooked. Other normal view predicates can also be used in combination with an exception view registration: .. code-block:: python :linenos: from pyramid.view import view_config from helloworld.exceptions import ValidationFailure @view_config(context=ValidationFailure, route_name='home') def failed_validation(exc, request): response = Response('Failed validation: %s' % exc.msg) response.status_int = 500 return response The above exception view names the ``route_name`` of ``home``, meaning that it will only be called when the route matched has a name of ``home``. You can therefore have more than one exception view for any given exception in the system: the "most specific" one will be called when the set of request circumstances match the view registration. The only view predicate that cannot be used successfully when creating an exception view configuration is ``name``. The name used to look up an exception view is always the empty string. Views registered as exception views which have a name will be ignored. .. note:: Normal (i.e., non-exception) views registered against a context resource type which inherits from :exc:`Exception` will work normally. When an exception view configuration is processed, *two* views are registered. One as a "normal" view, the other as an "exception" view. This means that you can use an exception as ``context`` for a normal view. Exception views can be configured with any view registration mechanism: ``@view_config`` decorator or imperative ``add_view`` styles. .. note:: Pyramid's :term:`exception view` handling logic is implemented as a tween factory function: :func:`pyramid.tweens.excview_tween_factory`. If Pyramid exception view handling is desired, and tween factories are specified via the ``pyramid.tweens`` configuration setting, the :func:`pyramid.tweens.excview_tween_factory` function must be added to the ``pyramid.tweens`` configuration setting list explicitly. If it is not present, Pyramid will not perform exception view handling. .. index:: single: view http redirect single: http redirect (from a view) .. _http_redirect: Using a View Callable to do an HTTP Redirect -------------------------------------------- You can issue an HTTP redirect by using the :class:`pyramid.httpexceptions.HTTPFound` class. Raising or returning an instance of this class will cause the client to receive a "302 Found" response. To do so, you can *return* a :class:`pyramid.httpexceptions.HTTPFound` instance. .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPFound def myview(request): return HTTPFound(location='http://example.com') Alternately, you can *raise* an HTTPFound exception instead of returning one. .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPFound def myview(request): raise HTTPFound(location='http://example.com') When the instance is raised, it is caught by the default :term:`exception response` handler and turned into a response. .. index:: single: unicode, views, and forms single: forms, views, and unicode single: views, forms, and unicode Handling Form Submissions in View Callables (Unicode and Character Set Issues) ------------------------------------------------------------------------------ Most web applications need to accept form submissions from web browsers and various other clients. In :app:`Pyramid`, form submission handling logic is always part of a :term:`view`. For a general overview of how to handle form submission data using the :term:`WebOb` API, see :ref:`webob_chapter` and `"Query and POST variables" within the WebOb documentation `_. :app:`Pyramid` defers to WebOb for its request and response implementations, and handling form submission data is a property of the request implementation. Understanding WebOb's request API is the key to understanding how to process form submission data. There are some defaults that you need to be aware of when trying to handle form submission data in a :app:`Pyramid` view. Having high-order (i.e., non-ASCII) characters in data contained within form submissions is exceedingly common, and the UTF-8 encoding is the most common encoding used on the web for character data. Since Unicode values are much saner than working with and storing bytestrings, :app:`Pyramid` configures the :term:`WebOb` request machinery to attempt to decode form submission values into Unicode from UTF-8 implicitly. This implicit decoding happens when view code obtains form field values via the ``request.params``, ``request.GET``, or ``request.POST`` APIs (see :ref:`request_module` for details about these APIs). .. note:: Many people find the difference between Unicode and UTF-8 confusing. Unicode is a standard for representing text that supports most of the world's writing systems. However, there are many ways that Unicode data can be encoded into bytes for transit and storage. UTF-8 is a specific encoding for Unicode that is backwards-compatible with ASCII. This makes UTF-8 very convenient for encoding data where a large subset of that data is ASCII characters, which is largely true on the web. UTF-8 is also the standard character encoding for URLs. As an example, let's assume that the following form page is served up to a browser client, and its ``action`` points at some :app:`Pyramid` view code: .. code-block:: xml :linenos:
The ``myview`` view code in the :app:`Pyramid` application *must* expect that the values returned by ``request.params`` will be of type ``unicode``, as opposed to type ``str``. The following will work to accept a form post from the above form: .. code-block:: python :linenos: def myview(request): firstname = request.params['firstname'] lastname = request.params['lastname'] But the following ``myview`` view code *may not* work, as it tries to decode already-decoded (``unicode``) values obtained from ``request.params``: .. code-block:: python :linenos: def myview(request): # the .decode('utf-8') will break below if there are any high-order # characters in the firstname or lastname firstname = request.params['firstname'].decode('utf-8') lastname = request.params['lastname'].decode('utf-8') For implicit decoding to work reliably, you should ensure that every form you render that posts to a :app:`Pyramid` view explicitly defines a charset encoding of UTF-8. This can be done via a response that has a ``;charset=UTF-8`` in its ``Content-Type`` header; or, as in the form above, with a ``meta http-equiv`` tag that implies that the charset is UTF-8 within the HTML ``head`` of the page containing the form. This must be done explicitly because all known browser clients assume that they should encode form data in the same character set implied by the ``Content-Type`` value of the response containing the form when subsequently submitting that form. There is no other generally accepted way to tell browser clients which charset to use to encode form data. If you do not specify an encoding explicitly, the browser client will choose to encode form data in its default character set before submitting it, which may not be UTF-8 as the server expects. If a request containing form data encoded in a non-UTF-8 ``charset`` is handled by your view code, eventually the request code accessed within your view will throw an error when it can't decode some high-order character encoded in another character set within form data, e.g., when ``request.params['somename']`` is accessed. If you are using the :class:`~pyramid.response.Response` class to generate a response, or if you use the ``render_template_*`` templating APIs, the UTF-8 ``charset`` is set automatically as the default via the ``Content-Type`` header. If you return a ``Content-Type`` header without an explicit ``charset``, a request will add a ``;charset=utf-8`` trailer to the ``Content-Type`` header value for you for response content types that are textual (e.g., ``text/html`` or ``application/xml``) as it is rendered. If you are using your own response object, you will need to ensure you do this yourself. .. note:: Only the *values* of request params obtained via ``request.params``, ``request.GET`` or ``request.POST`` are decoded to Unicode objects implicitly in the :app:`Pyramid` default configuration. The keys are still (byte) strings. .. index:: single: view calling convention .. _request_and_context_view_definitions: Alternate View Callable Argument/Calling Conventions ---------------------------------------------------- Usually view callables are defined to accept only a single argument: ``request``. However, view callables may alternately be defined as classes, functions, or any callable that accept *two* positional arguments: a :term:`context` resource as the first argument and a :term:`request` as the second argument. The :term:`context` and :term:`request` arguments passed to a view function defined in this style can be defined as follows: context The :term:`resource` object found via tree :term:`traversal` or :term:`URL dispatch`. request A :app:`Pyramid` Request object representing the current WSGI request. The following types work as view callables in this style: #. Functions that accept two arguments: ``context`` and ``request``, e.g.: .. code-block:: python :linenos: from pyramid.response import Response def view(context, request): return Response('OK') #. Classes that have an ``__init__`` method that accepts ``context, request``, and a ``__call__`` method which accepts no arguments, e.g.: .. code-block:: python :linenos: from pyramid.response import Response class view(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return Response('OK') #. Arbitrary callables that have a ``__call__`` method that accepts ``context, request``, e.g.: .. code-block:: python :linenos: from pyramid.response import Response class View(object): def __call__(self, context, request): return Response('OK') view = View() # this is the view callable This style of calling convention is most useful for :term:`traversal` based applications, where the context object is frequently used within the view callable code itself. No matter which view calling convention is used, the view code always has access to the context via ``request.context``. .. index:: single: Passing in configuration variables .. _passing_in_config_variables: Passing Configuration Variables to a View ----------------------------------------- For information on passing a variable from the configuration .ini files to a view, see :ref:`deployment_settings`. .. index:: single: Pylons-style controller dispatch Pylons-1.0-Style "Controller" Dispatch -------------------------------------- A package named :term:`pyramid_handlers` (available from PyPI) provides an analogue of :term:`Pylons`-style "controllers", which are a special kind of view class which provides more automation when your application uses :term:`URL dispatch` solely. pyramid-1.6/docs/narr/webob.rst0000644000076500000240000004456312610765056017320 0ustar michaelstaff00000000000000.. index:: single: Bicking, Ian single: WebOb .. _webob_chapter: Request and Response Objects ============================ .. note:: This chapter is adapted from a portion of the :term:`WebOb` documentation, originally written by Ian Bicking. :app:`Pyramid` uses the :term:`WebOb` package as a basis for its :term:`request` and :term:`response` object implementations. The :term:`request` object that is passed to a :app:`Pyramid` :term:`view` is an instance of the :class:`pyramid.request.Request` class, which is a subclass of :class:`webob.Request`. The :term:`response` returned from a :app:`Pyramid` :term:`view` :term:`renderer` is an instance of the :mod:`pyramid.response.Response` class, which is a subclass of the :class:`webob.Response` class. Users can also return an instance of :class:`pyramid.response.Response` directly from a view as necessary. WebOb is a project separate from :app:`Pyramid` with a separate set of authors and a fully separate `set of documentation `_. :app:`Pyramid` adds some functionality to the standard WebOb request, which is documented in the :ref:`request_module` API documentation. WebOb provides objects for HTTP requests and responses. Specifically it does this by wrapping the `WSGI `_ request environment and response status, header list, and app_iter (body) values. WebOb request and response objects provide many conveniences for parsing WSGI requests and forming WSGI responses. WebOb is a nice way to represent "raw" WSGI requests and responses. However, we won't cover that use case in this document, as users of :app:`Pyramid` don't typically need to use the WSGI-related features of WebOb directly. The `reference documentation `_ shows many examples of creating requests and using response objects in this manner, however. .. index:: single: request object single: request attributes Request ~~~~~~~ The request object is a wrapper around the `WSGI environ dictionary `_. This dictionary contains keys for each header, keys that describe the request (including the path and query string), a file-like object for the request body, and a variety of custom keys. You can always access the environ with ``req.environ``. Some of the most important and interesting attributes of a request object are below. ``req.method`` The request method, e.g., ``GET``, ``POST`` ``req.GET`` A :term:`multidict` with all the variables in the query string. ``req.POST`` A :term:`multidict` with all the variables in the request body. This only has variables if the request was a ``POST`` and it is a form submission. ``req.params`` A :term:`multidict` with a combination of everything in ``req.GET`` and ``req.POST``. ``req.body`` The contents of the body of the request. This contains the entire request body as a string. This is useful when the request is a ``POST`` that is *not* a form submission, or a request like a ``PUT``. You can also get ``req.body_file`` for a file-like object. ``req.json_body`` The JSON-decoded contents of the body of the request. See :ref:`request_json_body`. ``req.cookies`` A simple dictionary of all the cookies. ``req.headers`` A dictionary of all the headers. This dictionary is case-insensitive. ``req.urlvars`` and ``req.urlargs`` ``req.urlvars`` are the keyword parameters associated with the request URL. ``req.urlargs`` are the positional parameters. These are set by products like `Routes `_ and `Selector `_. Also for standard HTTP request headers, there are usually attributes such as ``req.accept_language``, ``req.content_length``, and ``req.user_agent``. These properties expose the *parsed* form of each header, for whatever parsing makes sense. For instance, ``req.if_modified_since`` returns a :mod:`datetime` object (or None if the header is was not provided). .. note:: Full API documentation for the :app:`Pyramid` request object is available in :ref:`request_module`. .. index:: single: request attributes (special) .. _special_request_attributes: Special Attributes Added to the Request by :app:`Pyramid` +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ In addition to the standard :term:`WebOb` attributes, :app:`Pyramid` adds special attributes to every request: ``context``, ``registry``, ``root``, ``subpath``, ``traversed``, ``view_name``, ``virtual_root``, ``virtual_root_path``, ``session``, ``matchdict``, and ``matched_route``. These attributes are documented further within the :class:`pyramid.request.Request` API documentation. .. index:: single: request URLs URLs ++++ In addition to these attributes, there are several ways to get the URL of the request and its parts. We'll show various values for an example URL ``http://localhost/app/blog?id=10``, where the application is mounted at ``http://localhost/app``. ``req.url`` The full request URL with query string, e.g., ``http://localhost/app/blog?id=10`` ``req.host`` The host information in the URL, e.g., ``localhost`` ``req.host_url`` The URL with the host, e.g., ``http://localhost`` ``req.application_url`` The URL of the application (just the ``SCRIPT_NAME`` portion of the path, not ``PATH_INFO``), e.g., ``http://localhost/app`` ``req.path_url`` The URL of the application including the ``PATH_INFO``, e.g., ``http://localhost/app/blog`` ``req.path`` The URL including ``PATH_INFO`` without the host or scheme, e.g., ``/app/blog`` ``req.path_qs`` The URL including ``PATH_INFO`` and the query string, e.g, ``/app/blog?id=10`` ``req.query_string`` The query string in the URL, e.g., ``id=10`` ``req.relative_url(url, to_application=False)`` Gives a URL relative to the current URL. If ``to_application`` is True, then resolves it relative to ``req.application_url``. .. index:: single: request methods Methods +++++++ There are methods of request objects documented in :class:`pyramid.request.Request` but you'll find that you won't use very many of them. Here are a couple that might be useful: ``Request.blank(base_url)`` Creates a new request with blank information, based at the given URL. This can be useful for subrequests and artificial requests. You can also use ``req.copy()`` to copy an existing request, or for subrequests ``req.copy_get()`` which copies the request but always turns it into a GET (which is safer to share for subrequests). ``req.get_response(wsgi_application)`` This method calls the given WSGI application with this request, and returns a :class:`pyramid.response.Response` object. You can also use this for subrequests or testing. .. index:: single: request (and text/unicode) single: unicode and text (and the request) Text (Unicode) ++++++++++++++ Many of the properties of the request object will be text values (``unicode`` under Python 2 or ``str`` under Python 3) if the request encoding/charset is provided. If it is provided, the values in ``req.POST``, ``req.GET``, ``req.params``, and ``req.cookies`` will contain text. The client *can* indicate the charset with something like ``Content-Type: application/x-www-form-urlencoded; charset=utf8``, but browsers seldom set this. You can reset the charset of an existing request with ``newreq = req.decode('utf-8')``, or during instantiation with ``Request(environ, charset='utf8')``. .. index:: single: multidict (WebOb) .. _multidict_narr: Multidict +++++++++ Several attributes of a WebOb request are multidict structures (such as ``request.GET``, ``request.POST``, and ``request.params``). A multidict is a dictionary where a key can have multiple values. The quintessential example is a query string like ``?pref=red&pref=blue``; the ``pref`` variable has two values: ``red`` and ``blue``. In a multidict, when you do ``request.GET['pref']``, you'll get back only ``"blue"`` (the last value of ``pref``). This returned result might not be expected—sometimes returning a string, and sometimes returning a list—and may be cause of frequent exceptions. If you want *all* the values back, use ``request.GET.getall('pref')``. If you want to be sure there is *one and only one* value, use ``request.GET.getone('pref')``, which will raise an exception if there is zero or more than one value for ``pref``. When you use operations like ``request.GET.items()``, you'll get back something like ``[('pref', 'red'), ('pref', 'blue')]``. All the key/value pairs will show up. Similarly ``request.GET.keys()`` returns ``['pref', 'pref']``. Multidict is a view on a list of tuples; all the keys are ordered, and all the values are ordered. API documentation for a multidict exists as :class:`pyramid.interfaces.IMultiDict`. .. index:: pair: json_body; request .. _request_json_body: Dealing with a JSON-Encoded Request Body ++++++++++++++++++++++++++++++++++++++++ .. versionadded:: 1.1 :attr:`pyramid.request.Request.json_body` is a property that returns a :term:`JSON`-decoded representation of the request body. If the request does not have a body, or the body is not a properly JSON-encoded value, an exception will be raised when this attribute is accessed. This attribute is useful when you invoke a :app:`Pyramid` view callable via, for example, jQuery's ``$.ajax`` function, which has the potential to send a request with a JSON-encoded body. Using ``request.json_body`` is equivalent to: .. code-block:: python from json import loads loads(request.body, encoding=request.charset) Here's how to construct an AJAX request in JavaScript using :term:`jQuery` that allows you to use the ``request.json_body`` attribute when the request is sent to a :app:`Pyramid` application: .. code-block:: javascript jQuery.ajax({type:'POST', url: 'http://localhost:6543/', // the pyramid server data: JSON.stringify({'a':1}), contentType: 'application/json; charset=utf-8'}); When such a request reaches a view in your application, the ``request.json_body`` attribute will be available in the view callable body. .. code-block:: javascript @view_config(renderer='string') def aview(request): print(request.json_body) return 'OK' For the above view, printed to the console will be: .. code-block:: python {u'a': 1} For bonus points, here's a bit of client-side code that will produce a request that has a body suitable for reading via ``request.json_body`` using Python's ``urllib2`` instead of a JavaScript AJAX request: .. code-block:: python import urllib2 import json json_payload = json.dumps({'a':1}) headers = {'Content-Type':'application/json; charset=utf-8'} 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 handle this is to add an extra ``view_config`` for the same route, with ``request_method`` set to ``OPTIONS``, and set the desired response header before returning. You can find examples of response headers `Access control CORS, Preflighted requests `_. .. index:: single: cleaning up after request .. _cleaning_up_after_a_request: Cleaning up after a Request +++++++++++++++++++++++++++ Sometimes it's required to perform some cleanup at the end of a request when a database connection is involved. For example, let's say you have a ``mypackage`` :app:`Pyramid` application package that uses SQLAlchemy, and you'd like the current SQLAlchemy database session to be removed after each request. Put the following in the ``mypackage.__init__`` module: .. code-block:: python :linenos: from mypackage.models import DBSession from pyramid.events import subscriber from pyramid.events import NewRequest def cleanup_callback(request): DBSession.remove() @subscriber(NewRequest) def add_cleanup_callback(event): event.request.add_finished_callback(cleanup_callback) Registering the ``cleanup_callback`` finished callback at the start of a request (by causing the ``add_cleanup_callback`` to receive a :class:`pyramid.events.NewRequest` event at the start of each request) will cause the DBSession to be removed whenever request processing has ended. Note that in the example above, for the :class:`pyramid.events.subscriber` decorator to work, the :meth:`pyramid.config.Configurator.scan` method must be called against your ``mypackage`` package during application initialization. .. note:: This is only an example. In particular, it is not necessary to cause ``DBSession.remove`` to be called in an application generated from any :app:`Pyramid` scaffold, because these all use the ``pyramid_tm`` package. The cleanup done by ``DBSession.remove`` is unnecessary when ``pyramid_tm`` :term:`middleware` is configured into the application. More Details ++++++++++++ More detail about the request object API is available as follows. - :class:`pyramid.request.Request` API documentation - `WebOb documentation `_. All methods and attributes of a ``webob.Request`` documented within the WebOb documentation will work with request objects created by :app:`Pyramid`. .. index:: single: response object Response ~~~~~~~~ The :app:`Pyramid` response object can be imported as :class:`pyramid.response.Response`. This class is a subclass of the ``webob.Response`` class. The subclass does not add or change any functionality, so the WebOb Response documentation will be completely relevant for this class as well. A response object has three fundamental parts: ``response.status`` The response code plus reason message, like ``200 OK``. To set the code without a message, use ``status_int``, i.e., ``response.status_int = 200``. ``response.headerlist`` A list of all the headers, like ``[('Content-Type', 'text/html')]``. There's a case-insensitive :term:`multidict` in ``response.headers`` that also allows you to access these same headers. ``response.app_iter`` An iterable (such as a list or generator) that will produce the content of the response. This is also accessible as ``response.body`` (a string), ``response.text`` (a unicode object, informed by ``response.charset``), and ``response.body_file`` (a file-like object; writing to it appends to ``app_iter``). Everything else in the object typically derives from this underlying state. Here are some highlights: ``response.content_type`` 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.text``. ``response.content_type_params`` is a dictionary of all the parameters. ``response.set_cookie(key, value, max_age=None, path='/', ...)`` Set a cookie. The keyword arguments control the various cookie parameters. The ``max_age`` argument is the length for the cookie to live in seconds (you may also use a timedelta object). The ``Expires`` key will also be set based on the value of ``max_age``. ``response.delete_cookie(key, path='/', domain=None)`` Delete a cookie from the client. This sets ``max_age`` to 0 and the cookie value to ``''``. ``response.cache_expires(seconds=0)`` This makes the response cacheable for the given number of seconds, or if ``seconds`` is ``0`` then the response is uncacheable (this also sets the ``Expires`` header). ``response(environ, start_response)`` The response object is a WSGI application. As an application, it acts according to how you create it. It *can* do conditional responses if you pass ``conditional_response=True`` when instantiating (or set that attribute later). It can also do HEAD and Range requests. .. index:: single: response headers Headers +++++++ Like the request, most HTTP response headers are available as properties. These are parsed, so you can do things like ``response.last_modified = os.path.getmtime(filename)``. The details are available in the :mod:`webob.response` API documentation. .. index:: single: response (creating) Instantiating the Response ++++++++++++++++++++++++++ Of course most of the time you just want to *make* a response. Generally any attribute of the response can be passed in as a keyword argument to the class, e.g.: .. code-block:: python :linenos: from pyramid.response import Response response = Response(body='hello world!', content_type='text/plain') 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 Exception Responses +++++++++++++++++++ To facilitate error responses like ``404 Not Found``, the module :mod:`pyramid.httpexceptions` contains classes for each kind of error response. These include boring but appropriate error bodies. The exceptions exposed by this module, when used under :app:`Pyramid`, should be imported from the :mod:`pyramid.httpexceptions` module. This import location contains subclasses and replacements that mirror those in the ``webob.exc`` module. Each class is named ``pyramid.httpexceptions.HTTP*``, where ``*`` is the reason for the error. For instance, :class:`pyramid.httpexceptions.HTTPNotFound` subclasses :class:`pyramid.response.Response`, so you can manipulate the instances in the same way. A typical example is: .. code-block:: python :linenos: from pyramid.httpexceptions import HTTPNotFound from pyramid.httpexceptions import HTTPMovedPermanently response = HTTPNotFound('There is no such resource') # or: response = HTTPMovedPermanently(location=new_url) More Details ++++++++++++ More details about the response object API are available in the :mod:`pyramid.response` documentation. More details about exception responses are in the :mod:`pyramid.httpexceptions` API documentation. The `WebOb documentation `_ is also useful. pyramid-1.6/docs/narr/zca.rst0000644000076500000240000002410512634676134016771 0ustar michaelstaff00000000000000.. index:: single: ZCA single: Zope Component Architecture single: zope.component single: application registry single: getSiteManager single: getUtility .. _zca_chapter: Using the Zope Component Architecture in :app:`Pyramid` ======================================================= Under the hood, :app:`Pyramid` uses a :term:`Zope Component Architecture` component registry as its :term:`application registry`. The Zope Component Architecture is referred to colloquially as the "ZCA." The ``zope.component`` API used to access data in a traditional Zope application can be opaque. For example, here is a typical "unnamed utility" lookup using the :func:`zope.component.getUtility` global API as it might appear in a traditional Zope application: .. code-block:: python :linenos: from pyramid.interfaces import ISettings from zope.component import getUtility settings = getUtility(ISettings) After this code runs, ``settings`` will be a Python dictionary. But it's unlikely that any "civilian" will be able to figure this out just by reading the code casually. When the ``zope.component.getUtility`` API is used by a developer, the conceptual load on a casual reader of code is high. While the ZCA is an excellent tool with which to build a *framework* such as :app:`Pyramid`, it is not always the best tool with which to build an *application* due to the opacity of the ``zope.component`` APIs. Accordingly, :app:`Pyramid` tends to hide the presence of the ZCA from application developers. You needn't understand the ZCA to create a :app:`Pyramid` application; its use is effectively only a framework implementation detail. However, developers who are already used to writing :term:`Zope` applications often still wish to use the ZCA while building a :app:`Pyramid` application. :app:`Pyramid` makes this possible. .. index:: single: get_current_registry single: getUtility single: getSiteManager single: ZCA global API Using the ZCA global API in a :app:`Pyramid` application -------------------------------------------------------- :term:`Zope` uses a single ZCA registry—the "global" ZCA registry—for all Zope applications that run in the same Python process, effectively making it impossible to run more than one Zope application in a single process. However, for ease of deployment, it's often useful to be able to run more than a single application per process. For example, use of a :term:`PasteDeploy` "composite" allows you to run separate individual WSGI applications in the same process, each answering requests for some URL prefix. This makes it possible to run, for example, a TurboGears application at ``/turbogears`` and a :app:`Pyramid` application at ``/pyramid``, both served up using the same :term:`WSGI` server within a single Python process. Most production Zope applications are relatively large, making it impractical due to memory constraints to run more than one Zope application per Python process. However, a :app:`Pyramid` application may be very small and consume very little memory, so it's a reasonable goal to be able to run more than one :app:`Pyramid` application per process. In order to make it possible to run more than one :app:`Pyramid` application in a single process, :app:`Pyramid` defaults to using a separate ZCA registry *per application*. While this services a reasonable goal, it causes some issues when trying to use patterns which you might use to build a typical :term:`Zope` application to build a :app:`Pyramid` application. Without special help, ZCA "global" APIs such as :func:`zope.component.getUtility` and :func:`zope.component.getSiteManager` will use the ZCA "global" registry. Therefore, these APIs will appear to fail when used in a :app:`Pyramid` application, because they'll be consulting the ZCA global registry rather than the component registry associated with your :app:`Pyramid` application. There are three ways to fix this: by disusing the ZCA global API entirely, by using :meth:`pyramid.config.Configurator.hook_zca` or by passing the ZCA global registry to the :term:`Configurator` constructor at startup time. We'll describe all three methods in this section. .. index:: single: request.registry .. _disusing_the_global_zca_api: Disusing the global ZCA API +++++++++++++++++++++++++++ ZCA "global" API functions such as ``zope.component.getSiteManager``, ``zope.component.getUtility``, :func:`zope.component.getAdapter`, and :func:`zope.component.getMultiAdapter` aren't strictly necessary. Every component registry has a method API that offers the same functionality; it can be used instead. For example, presuming the ``registry`` value below is a Zope Component Architecture component registry, the following bit of code is equivalent to ``zope.component.getUtility(IFoo)``: .. code-block:: python registry.getUtility(IFoo) The full method API is documented in the ``zope.component`` package, but it largely mirrors the "global" API almost exactly. If you are willing to disuse the "global" ZCA APIs and use the method interface of a registry instead, you need only know how to obtain the :app:`Pyramid` component registry. There are two ways of doing so: - use the :func:`pyramid.threadlocal.get_current_registry` function within :app:`Pyramid` view or resource code. This will always return the "current" :app:`Pyramid` application registry. - use the attribute of the :term:`request` object named ``registry`` in your :app:`Pyramid` view code, e.g., ``request.registry``. This is the ZCA component registry related to the running :app:`Pyramid` application. See :ref:`threadlocals_chapter` for more information about :func:`pyramid.threadlocal.get_current_registry`. .. index:: single: hook_zca (configurator method) .. _hook_zca: Enabling the ZCA global API by using ``hook_zca`` +++++++++++++++++++++++++++++++++++++++++++++++++ Consider the following bit of idiomatic :app:`Pyramid` startup code: .. code-block:: python :linenos: from pyramid.config import Configurator def app(global_settings, **settings): config = Configurator(settings=settings) config.include('some.other.package') return config.make_wsgi_app() When the ``app`` function above is run, a :term:`Configurator` is constructed. When the configurator is created, it creates a *new* :term:`application registry` (a ZCA component registry). A new registry is constructed whenever the ``registry`` argument is omitted, when a :term:`Configurator` constructor is called, or when a ``registry`` argument with a value of ``None`` is passed to a :term:`Configurator` constructor. During a request, the application registry created by the Configurator is "made current". This means calls to :func:`~pyramid.threadlocal.get_current_registry` in the thread handling the request will return the component registry associated with the application. As a result, application developers can use ``get_current_registry`` to get the registry and thus get access to utilities and such, as per :ref:`disusing_the_global_zca_api`. But they still cannot use the global ZCA API. Without special treatment, the ZCA global APIs will always return the global ZCA registry (the one in ``zope.component.globalregistry.base``). To "fix" this and make the ZCA global APIs use the "current" :app:`Pyramid` registry, you need to call :meth:`~pyramid.config.Configurator.hook_zca` within your setup code. For example: .. code-block:: python :linenos: :emphasize-lines: 5 from pyramid.config import Configurator def app(global_settings, **settings): config = Configurator(settings=settings) config.hook_zca() config.include('some.other.application') return config.make_wsgi_app() We've added a line to our original startup code, line number 5, which calls ``config.hook_zca()``. The effect of this line under the hood is that an analogue of the following code is executed: .. code-block:: python :linenos: from zope.component import getSiteManager from pyramid.threadlocal import get_current_registry getSiteManager.sethook(get_current_registry) This causes the ZCA global API to start using the :app:`Pyramid` application registry in threads which are running a :app:`Pyramid` request. Calling ``hook_zca`` is usually sufficient to "fix" the problem of being able to use the global ZCA API within a :app:`Pyramid` application. However, it also means that a Zope application that is running in the same process may start using the :app:`Pyramid` global registry instead of the Zope global registry, effectively inverting the original problem. In such a case, follow the steps in the next section, :ref:`using_the_zca_global_registry`. .. index:: single: get_current_registry single: getGlobalSiteManager single: ZCA global registry .. _using_the_zca_global_registry: Enabling the ZCA global API by using the ZCA global registry ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ You can tell your :app:`Pyramid` application to use the ZCA global registry at startup time instead of constructing a new one: .. code-block:: python :linenos: :emphasize-lines: 5-7 from zope.component import getGlobalSiteManager from pyramid.config import Configurator def app(global_settings, **settings): globalreg = getGlobalSiteManager() config = Configurator(registry=globalreg) config.setup_registry(settings=settings) config.include('some.other.application') return config.make_wsgi_app() Lines 5, 6, and 7 above are the interesting ones. Line 5 retrieves the global ZCA component registry. Line 6 creates a :term:`Configurator`, passing the global ZCA registry into its constructor as the ``registry`` argument. Line 7 "sets up" the global registry with Pyramid-specific registrations; this is code that is normally executed when a registry is constructed rather than created, but we must call it "by hand" when we pass an explicit registry. At this point, :app:`Pyramid` will use the ZCA global registry rather than creating a new application-specific registry. Since by default the ZCA global API will use this registry, things will work as you might expect in a Zope app when you use the global ZCA API. pyramid-1.6/docs/pscripts/0000755000076500000240000000000012642137501016352 5ustar michaelstaff00000000000000pyramid-1.6/docs/pscripts/index.rst0000644000076500000240000000031312642135264020214 0ustar michaelstaff00000000000000.. _pscripts_documentation: ``p*`` Scripts Documentation ============================ Command line programs (``p*`` scripts) included with :app:`Pyramid`. .. toctree:: :maxdepth: 1 :glob: * pyramid-1.6/docs/pscripts/pcreate.rst0000644000076500000240000000026712642135264020540 0ustar michaelstaff00000000000000.. index:: single: pcreate; --help .. _pcreate_script: ``pcreate`` ----------- .. program-output:: pcreate --help :prompt: :shell: .. seealso:: :ref:`creating_a_project` pyramid-1.6/docs/pscripts/pdistreport.rst0000644000076500000240000000031612642135264021467 0ustar michaelstaff00000000000000.. index:: single: pdistreport; --help .. _pdistreport_script: ``pdistreport`` --------------- .. program-output:: pdistreport --help :prompt: :shell: .. seealso:: :ref:`showing_distributions` pyramid-1.6/docs/pscripts/prequest.rst0000644000076500000240000000027412642135264020763 0ustar michaelstaff00000000000000.. index:: single: prequest; --help .. _prequest_script: ``prequest`` ------------ .. program-output:: prequest --help :prompt: :shell: .. seealso:: :ref:`invoking_a_request` pyramid-1.6/docs/pscripts/proutes.rst0000644000076500000240000000030212642135264020604 0ustar michaelstaff00000000000000.. index:: single: proutes; --help .. _proutes_script: ``proutes`` ----------- .. program-output:: proutes --help :prompt: :shell: .. seealso:: :ref:`displaying_application_routes` pyramid-1.6/docs/pscripts/pserve.rst0000644000076500000240000000027712642135264020422 0ustar michaelstaff00000000000000.. index:: single: pserve; --help .. _pserve_script: ``pserve`` ---------- .. program-output:: pserve --help :prompt: :shell: .. seealso:: :ref:`running_the_project_application` pyramid-1.6/docs/pscripts/pshell.rst0000644000076500000240000000026112642135264020376 0ustar michaelstaff00000000000000.. index:: single: pshell; --help .. _pshell_script: ``pshell`` ---------- .. program-output:: pshell --help :prompt: :shell: .. seealso:: :ref:`interactive_shell` pyramid-1.6/docs/pscripts/ptweens.rst0000644000076500000240000000026612642135264020601 0ustar michaelstaff00000000000000.. index:: single: ptweens; --help .. _ptweens_script: ``ptweens`` ----------- .. program-output:: ptweens --help :prompt: :shell: .. seealso:: :ref:`displaying_tweens` pyramid-1.6/docs/pscripts/pviews.rst0000644000076500000240000000027112642135264020425 0ustar michaelstaff00000000000000.. index:: single: pviews; --help .. _pviews_script: ``pviews`` ---------- .. program-output:: pviews --help :prompt: :shell: .. seealso:: :ref:`displaying_matching_views` pyramid-1.6/docs/python-3.png0000644000076500000240000002123212234375161016675 0ustar michaelstaff00000000000000‰PNG  IHDRÒHƒd«g pHYsììŠnýr IDATxí xUÕ™÷×¹å .Š ‚(V¦ µ-^¦µŽ¶3Ÿ±_§3Çé÷µ;ujmk­mÕÇêcm«½:Úk;ÚÒ©¢ˆ¢–Á*A. „$pÉ\Ornßï¿ÏÙ';''á$œ¥{=¬¬½×Zû]ïz×û_ï»Ö^ûà‰ÅbÆ ®\ œœ¼'÷¸û´+W’€ $W\ dA.² D—„+H®¸È‚\ eAˆ. W.\p% ¸@Ê‚]®\ ¹:àJ p”!º$\ ¸@ruÀ•@$à) BtI¸ð¶<„LÚä  {0A¹uÞðŒ†¾ <˯[“Wp~Á…¦'2=f¥јoŠÏD'GCÑpO,Ôàíénˆs´;<Ú°mÓë6ü´ ‘eŒ ªw„®¸L "’tùÝÿ³Úõ~.ó®ðxbyn,8’iÔºwäEÃÁPW纺=o©ùŸŸìƒÿLƒŒ¢[tÊ%0¢@ºêÞu_51ÿíÑ ‰g꼦¬ODY$jmÚûê{ÿýuHª0Yê”KÍeÀ•@ŠF HW}gí옿`7è t·µÔÙ½yÓØ©3ËÇL._ÔkÃBÅ-“m©â*l¯ûw^ÝÙP³ Þ»\Ë”2‚inå ¬\¹Ò×ÙÙY¼yóæfªHl§Íšsþüù9¹¹¹sé’ô×C?tlݺµõì³Ï.íîîn:pà@·D3Zý±]»×·B ’Ùöë{Ûñä]¿ýÓ7ÝÒÓ~¼Ò²LríœÑv÷dì|ò|ü©“Ï]q=2™@ôI8nXK—.]B¼åz!‰|Œš…ÄÓJn;wî€Â^¯÷ïÛÛÛóÇŒsM ŽŒ7îëyyygrKÌhc‹z'FHš==‘R 0áp´ûøaÍŠ»‰[<_u7/*ü¿^ÐinIéBžD ˆ4©ÒH@²!\KÑUĉgN%žnrëÙ²eKýj«¬¬l…B®®®¢)S¦Ì‡ÃAÀUBÙâˆè7tû…ÛþE#y9ƒÇãí¯oªøó¾Òó/[áÍË]’À“°>Ikä°Rv©Ç(‚óbb1دnFRÌÔ>ÀÀåöôôHnÄQS¨$##xAQ+ODV‰f¢\GéïñÉ“'_ Þf‰ =Õ>$G0c¦Ì:¿pÒŒ5}7 ±¬OÜYV)il7OÔ,ªp¬Vßepw¬uÊåáZò:]-¸Ö|•ÄÀ©Á2í™;wn @j`tœ|lÔˆ)Ê+ T‹CF¿¼äVxÚ²8Ø¢‘¨”átUˆŒ[À¸è¢‹¦£$YTïà!ÍȧÍBFBH©ôæ›o>MÖØ;vÜõˆ¿Ä´ºCítÊ„…Ýt1ny•ÌNKÕ›§'V^FÙtb ×ÇÅìóÌ»øFýRÀ2©§eßÅÃ3¼-ÂÕwü~¹Ç—ó€­ìˆ’éU\6 `’E¶gos'Á¨zñ¨:ýyÚõLI×¥%yóoý¿ç]CÝqľ€ëO 㜸êöÿ›)=™iÝÑ®w²¼õ—ÊÉõ5½“åq0™¦ko°ú'*²k'Vß½î_YçxÓZ)Â}ëZ\¶²SLJí<;u€HÅVG,ÊÕ“5í4jnøèœî|`û: [a1Ó¡ðDò0êß’%KÊÙ9^@ZFª÷WÜ«c¼øÛÉÎÐkTÑ"?:P¥¥¥…3fÌxGYŽRwuÃÕ]µjU^[[[å¶nûvíª¢¾µ#wñÅO_¼xqeãɳ@éóùÆB¿Œ#11ÞàkTAYFk%èåSwï—ŠÏ?ÿü¶éÓ§WÔÕÕ i}ùÞ÷¾w2Vq 3i[|µÁß!xßÀ}÷’‹é„ú’õ$ç°#9Š ÝIì຦¹¹ùÊ´¾PÖ'lÄQAí=øfB,×,xèf./&‡Ã¦52•‡ÍË‹É>#þ¤Í´zõ9hÛµ½ l ØÖ(nQ`¨· Þ¬{å%®9\—ȳSG™ƒËP[SãÂs&OíµBN0ÅuvYÁ‚+VN?ÿICÞúÔù-órôãô\S– “¸K‰@CêQšosLÅTªÐ—/_þ…²²²ëÈσÎÃШ&¶Ó.‚Q–÷ÐîϨï;vl#õ>AlæÙ6þ-''g¥ÊàG»QžÂÂÂ…Äy”‡à'Ê™³kÙ€8Ƚu¶Œ:ý‚ Î㙿ƒÆhåúý~ËjöðÔ©S7r.íž?ʃNà¥ç¨÷!ñCªu›èxbÓ¹ÐoÂ}Žè!¿Ë±–Ï™3çbêj"Iʆ{+È¥’5}ùq }胻‘ó³Ð{3‚;È·äÍ ôá+LŸåá±ÐÒ¸&š´8ùk´òÊŽN¬©5Û·í6/nÙcÖ5´šºPÔ䄺M¡×g.›¿Àüäò•æJ+ 'Mçõ-’7ǃB' Ô¶*vê(ëµ8)Àq<ׇàK Á†Co—´m­š=sî'úX$Õµ];í€r]>³ð ž—RiÆpvu¶ÁÀ.@Ñ~Èà{×VUUm¡N'Qn…\Ÿ(à{ÖYg}‰=‹û\fÊÛPˆÀ¶“¶’§ÒQ4Y1ÉÕÒÊ• û)9yV .[Ôsª+þ-…yã7¾ÇõÓÌàÿËó~YDÜÀ·***~C¾>|”•Sšv¦Æý[ éÊZM¿6ÔÖÖê}Z^,Ѳ’’YNñZÊõ—:ôï\!&ßO­X±b<ÖìûȦÀä ´ÿßÐl¢žÚc¦Ïž=û3Ȧz88ú]õ™Ã‡¿EûI7›waØ>¯~"›6¬ñóXCɺ‹(Y+õ`‰ÞƒµüýD]üÞÈ9º×9²zíä ÉmçÏg®3¿Äÿª}ôIÃ$d­£å>ÊŠ{ΛcÊþî£æóyy¦Ô3S.šo>¿nƒ¹•2ÉB2>aÄÐåwí"å3îJj}PèàC³§”­ß`1€!ê¿Jú ׇNäá‚,`R,Yçàp›€âIS‰I~°‘˜RK¹¿$š‰kˆR"gŸtjú(VD/²µ áÅ}=ƒ:¶kÁû0ez ìÇ•«ÃŠHéíIÆ9SkÆïÞ³gÏËXƒð;‹{ßĉ/H²ðr£,«Dê»ä¬˜º+'z²P{é÷,Ú·ȽÑ$I½>A4ȘúLމJÿ~´&‰Ïëí]V;jÏÉo¢zÿ$c…³ …ƒÕ_¡F0ÓZ#¬P8Üõô÷Y:³ñðú×~¤tRá|ŸÏ+%ïkÓ 'rçâÏ!h4·tjÆ ûi]ÿF̱t€ÒØñãÇ·2Sê7$øJ$Æñ¨À êj¦”“R¼cJ¨¾È…‘Es‚Èæ1‚Òס´Ö@*ÙéЧ¥¬Ü/U©ãemTI¾ä+ë‘tý¸¶õ¤ŒZ½B»3Hµ¶™G¡6[ä&ÛÖ˪ŸÁ‘T{â Çz}2ã:h“?ü†YLKê» †L{Åk<%¿ŒÂí Uƶ’âÛ€êÍCæÐÏÿáº%]-Ýóá[™Åån¥¶…±ÀDDkãL¿r „S[×¥ÙW3Ÿ¥é~®Feig¦mFY 1¸†]·(ÚîdF³×ð9ö“*ýÓ'ô4¹Hµ]\í@Î$ßÚæ&•‹#·H“Å@ô$S õ3îS¨;ލ™]{ÈA“mù¹¡k^†f1í&Nºö† ¤P㑊¼1S®]Ü õnsÇþñÎ-.*Ì•®Ñ8H4Ë8€`ÅŽd™#ÏNÂ¥‚í­Ýmê¼µPNÛnšLÞUX/Ó ”…~Ä’.ã¢5Åé$õ]–@k›±¤\X¹=´A•yhߪõ¾I.\²¤s}JÂïô\ûŸ·s²Ág&áôÎÎ ˜…vézºM 3^3O=ýœÙsUD­3>=$ I@ªþ¯n}ª!ÉNJi¯…êh:T³êêÚeI È7 €±,Q0EMk[OûoÖ5K’½°Mi?;·È  ¡+Ú¨»c»CÙiàB…~ÖÑÏ)²¼\kÛ8@ÛìcÅ´Áa[1 \vÙh§ðïùÑwÌçŽ!EtbÅxº» d/ó³—éÁ{éRsÝ…óMyu½ùÑ£¿°¾¾Í˜çá,Þ¢‘îÖ§’»u‰-pi$ÔÓ=~\A_&Ý3Y!&)˺ö;¡`—QOß,Y§ôŒåÉçâù¿}±icck¨Òà9w”3܇!Ï”(—6¬€²h“¦¬Ù:žyšüe XO_-eÂ…UŸ5¦ƒÊ‹‰E› V®µ½- 1H§>»ÍsëÌŸª™7ö×™-5õfû‘£fk#{âõŽk.X4ÏÜwó?›Áq!øËÈeï«ì™õ5V¿{ÓCÓ\z#û«ã$ç^k·PäG}¸l—L`Jºl€Ü6‡ëÖÇʤ”÷)s>3ÁpÇ¿ßS¹â‰ö{»õ¬¦:d A)•¥,¼Üx­÷<© R­ TϪ›ZþN¿G‰ÁCTkÁ3áW»®ƒ*Öku¼<çaË\›?ö‹O.Om¸ékæ>8˜ILŽ×ì.š‚+Þg-¿À\Ÿãe—1lòg•š[þíF3æÞŸšŸÓ¦t2²EÒ µÿ÷?¨n®Ú}33–¶RL–U²vXl ‘&­€‘°0}¬LˆϤräÙÏ'Ët¬~ÅÛùùÓ ÏmŠj}TKÔ/²Ú p›ÝÀ.Ý…P´¾ R;¼'ÙÃ}rƒ¡kQnÖ“¹x·º}1Nz¬£‹ÖÆý.æXÑ9ôGëÁ´vÇÓÿ³žår,i+uï¸=5AcEË›jâ.âÛ‰¸ïß>gþëÑÇÌMíV™¡ÀŒéæŸ̱ÎÝéÜ`Ú>Cà CRâ¹ÐŽ_~mMÕËO~¼ãhík¡ŽÖ#‘žž`´»'ë 9còxÙÒ06 H™Ñ ¡`7ÏrA ù븷ó©Ó¥È}0<ÞiÛUÙ±÷kÖüì_îØ÷ |í#Ê•…™m6¤ÔÖ}jžUEŸ!0K´\;CÙ`YA¹1&—ÝJ9Ör–ëáX~‹Ú“ûhÀ©µÊ€<&ªe%á„{%mÿ¯6 DmÿÕ$ÚÎNû;yÔý˜dƒÎyuœ¨¡¡¡’ºâ]î݈MnÐÎ(À³°5éi¼ì¨wUâñèÞƒfÇúWÍWñ¦:8óϺÐ~èRs#eÚ™t -äá´†´øì¨ÿÓo^ Ý3ˆjL;;ä¶ÿÇWÅ­‘Æ@V„Ô鯩OÜϽlí]v¦’\€ÁBê@è^Ê«5‘vXjˆâtX­Úyœ\~??íT ú(ÁÛA¿âsäÏ"ZÊÂY²—(l$j0¬º¸3æEä§äáÚ•Ì›7ïŸkjjnãEnôl_Ü¢Éñ¤‹ÆgíÜÊLópîÒ‚_kUd†; ¦iaÀ¬0LjîåÜÜÔK¿ð“Àÿ‡ ßå¾IòO¤>¡¸„¾|Š|„ˆ²ibè›®­>‡Lu}×ç]õæýЕ e+·¢¨ÙN&[[•v·Cô§ ¸¸øüƒÐz¸U}$†eùé<¨Z'Æ‹œe«à^“€uú›º>üÛÌgkQøk¨ïEù.œO`ù~G=y ¢p ¨º˜òÙ€HcÃý€ÀàüÛVα ÜgÎ)œ¿ pý’³ú­…üÔ#6de%Ę*á÷ë&L¸~ó8w-íë€ïãX*dUL\‰|> O¹Ôññ±àxÞ ‰rí4NïøïšBhñëX¥¿ yÏŸkŠ‹ŠÌ”ÖVs€HßÓ†aIÔhW`’"+*ÈY!?ka­oR,QŸMƒˆ_䟔´T’·eµ cIÏzL¨'RQåÑÀØ!9@‚9”Ô¹k‡kYË.\Êr. ÿ1úö7µÁ™¾¼_±o'ìKŸ‡‡yÃ8ļÝãç¥H”s¼”fÍPoMÞ>H¢ÐéÃI)=Y¤ÃW¦°Bq8}eƒ .m`‘2žÝç^õ¿wï?¬ùäGJK~üÍ÷Ü%ú {@×g †š 5¨-¸mû‰›¹ÖºO‹{­ý$H)™”ÖöýµC(åO ÊkæC´ Äm\—EÇšH¥@rGÛ­…ìF¡½(šòÄCÅ×ࢰ=(à!ú1Ê׋‰Ú^—…Ö3Và×G×q¡6µ) üA7`ø1úõÔÙAT_Õ®Ö|â/$ÚïÕ|Jñ$ªý|¢ä¢þŠwYíi!¯Ý$_ÜÚÚH¢ÍMŠiÛ"?Rx-ñ6âë-8C‹P…žÓ[§¾©}ÆEåv1 µuwWŒã,†e‘4žÀÄ­T"ÏQvà`ÛA@T soüä¿7þàë³o ø=ã«k{ä"HvÄîPR)‘A *ÀHFRÍFâAåÝÄ_úJñU®ÙšTƒ`ÓÑd 2ÑÑÀ„°03m…Lª/Eé×Oј(³77´&MÌTVªb×ÑšUõ,>HÓòš¨¯þH¾€‚îûÕ§®À¤~;Û·å¢vÔñ§çû­Ùm£\ iÛR£¾ÍÛ }Ñ3ÙôÑóý;Ì¥:ñ€5ò4·6±¬ñ†~¿q±Û1 íúsÓ+Ó?èòz˜µlIÖ–eJ€Èi¥Ð­9gä•ýãõS&?ºæHSÃÆ‹V DéÚ͚ŤØ¬Ñ6`4Ðý‚¹_fš ª‰ß_ ¸~1¬s&iðô8@àÔfÚ~&ÚÕî’èjp­àäGmR.…V´‚³ÜγӡÔOÐé×¾MKi¶ÚJÐR_œ| ÷ú_1³ùDíÒî0LjøŠuÃ&³ZòB’òLG{DÜ% óƒ7½\¿ííæ/áµ³V€ˆÉ÷JLºv^2ÕOºß#_sgז寗Œ÷ÿ\ ?óbÓÚ_ýñ˜€¤sÐΨ~6ƒú‘. µt4”— ³ˆT@ÒÏqÉÛnD¢¸’J/µÆ‰Ê³]ßÙ^*íÔ{g]]§–§Þµ~êóC¹ø;žñÞs'o@óÙ<ö×6;7o5{¡!«˜üí‰t4GH‰†"‹ÿúùŸ}çá]7TT·nKžpä„‚Í`^À;ùpC¨ê›×~ïßÝýùûˆr ú}Pf?3Ü”õÉ€ ÈáÒÌô9ý†í_‡²è›Ÿë©]<«-Ö_dÊã_B=dïyäÏœ?ÁÔÏ …MîñVsäû›'é¿6MäΦõNlùŒ˜k§™þ:þãÞ=k‰[¿üO³>týêÒë§–f–û&ù|üëÞ ‰ÿØ/ÔÐiª;<ú«?6lºûÇu¯Ã¤ÀSMÔLÝ I¹4#FL8~ ñ, 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "" pyramid-1.6/docs/quick_tour/awesome/awesome/locale/de/0000755000076500000240000000000012642137501023617 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/0000755000076500000240000000000012642137501025404 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mo0000644000076500000240000000071412520062551027400 0ustar michaelstaff00000000000000Þ•,<=€DÅHello!Project-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2011-05-12 09:14-0430 PO-Revision-Date: 2011-05-12 10:12-0330 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 Hallo!pyramid-1.6/docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po0000644000076500000240000000117112520062551027401 0ustar michaelstaff00000000000000# Translations template for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "Hallo!" pyramid-1.6/docs/quick_tour/awesome/awesome/locale/fr/0000755000076500000240000000000012642137501023636 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/0000755000076500000240000000000012642137501025423 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mo0000644000076500000240000000071512520062551027420 0ustar michaelstaff00000000000000Þ•,<=DÄHello!Project-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2011-05-12 09:14-0430 PO-Revision-Date: 2011-05-12 10:12-0330 Last-Translator: FULL NAME Language-Team: fr Plural-Forms: nplurals=2; plural=(n > 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 Bonjour!pyramid-1.6/docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po0000644000076500000240000000117312520062551027422 0ustar michaelstaff00000000000000# Translations template for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "Bonjour!" pyramid-1.6/docs/quick_tour/awesome/awesome/models.py0000644000076500000240000000013312520062551023617 0ustar michaelstaff00000000000000class MyModel(object): pass root = MyModel() def get_root(request): return root pyramid-1.6/docs/quick_tour/awesome/awesome/static/0000755000076500000240000000000012642137501023257 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/static/favicon.ico0000644000076500000240000000257612520062551025407 0ustar michaelstaff00000000000000h( " A>;)'%+¯ô=¿ö420520ÿÿÿ;96¸èú „„†µäô—àúGDA/-+LHF+©é\XUtØùååæììì,BO/Vi531WSP+®ñ·çø§§©OÏù--..-.QNK       #!!!!!!"   $$$$$$$$$$$$$$$$À€€Àpyramid-1.6/docs/quick_tour/awesome/awesome/static/logo.png0000644000076500000240000001476112520062551024733 0ustar michaelstaff00000000000000‰PNG  IHDR,P£êH{¸IDATxœíwœåÝÀ¿ÏÌìÚG/ "¢(%¢RD$Vô jT^KLb‰Æk”X’`¬˜¢hTH,Q|A1 hÆ‚X¥rw»;3ÏûÇoŽÙÙÛ^îv¾÷Ž{vwö™Ùßüú£ÈcŽ9¦]ƒm 2•;ØÕì z¬BõBké> ‡VÁPo‹-“œ°ñáÂ…sV{f‰Qé劯À:ô°Ã/U†y»vu™éT‰1 W;Ë´m¹xñüUÅžO@@9'°F›0Ù²Ì?k­­2W¬<è˜_ËYÒ¡]åQsçέ/öŒÊ ³ùc&NÜÕÐ<…¦“«uz9e€ëj ÓÐØ©üìÓ ‹=Ÿ€€r£¹ÀRuýÜoæN,¨ƒm§ @k”±ÿÝú¿øÙÊ•_¤wº²a»5úÐ ‡¡y­bN¨5`˜®ë,êÙ­ö;AºC@@á°b¿•vË Ã4$ë; %ÛA)5~ÍšÿŽ^,ö|Ê `Ô˜1{k—ñŽ”…TQJ)G묀€‚!–£ŽW–Qá:ÀJW;hôá£GîöòË/¯+ö|Ê P(}˜v]‚¬«ÔÑ”R݇‘À³ÅžO@@9`7®¶1âîéª@`¥‹R.êP¬p8ܪuuy5ç­]\íîYìy”1guìÈ:QÀkÀœ¢Í( ¯XVw…6˽^0´«AÓ«Øó(cΆ{Æf¬6‹¥µ[£”!N™c§)©4» ‹Æm?uêTã†n(´ŠZtzƶ@-PtªÙqÊ4°X ||| l-ì”sNƒÏX¸à³(–ÖÚ º…j¤ôŶ)1!ËÄ0Zƒc»ØŽƒ,ÓÀ2MT)H0­Í%K–@!–< t"}YnŸK?/Ñ\N0 XPÜöƈF´ÖtïÚ‰¡ƒû0t> èÛ.]ÚQ]Uí¸lÝæëµ›øhÅW¼»|+>[KCc„Š…aOr¹Å9o ØѦ2Áv‹m§¯7 r2»€€u<»÷ïB$ì€Îÿ1•Ø*BéxñÖg÷û<Ö IÊ () Ѱòßþ®1lsòуøÑ‰Cˆ6nCÛà†sºE·Ñ·»Å´Ÿ~›]:WbÛnÞ«•£Ë€ÿø¼ÿ†»$xÞFDý 0ü™2¸Ù3ö;à‘fì¸xBjI½¬þüx)‹ù4§òùœ| )óâ ÂûàQvhíÉÐHDz0øCl|ð)pdì=W¶å†vó"°¢¶fèní¾{%Ñȶ‚ß'MËä¨C:³èëòjò¶B@…ϸ_}á'È<Ä3¾'p7’´š. É å_…Üm EGäRPžŒ.ˆf0¸9þéøe'£Šøã_Üìÿ{wG$ÙOwà{ˆÀ½¸ŠìŠÜG!ǵ’ç™H«Ÿ:àà>àœ4Þ§ùT#çO!Bï4Dx½t—(ažÒQÛeÔ°v´« £ÜFSÙìHßÚâ¶&„ãäçÛ† ‹]ð¿[û ŸüS!:Ggøþ»ßö˜ôMŒLŽôç¿Ô„•—^À¯z̼~‹ÏXÇØïqÀ"’ «æ˜Àùˆæç§¥ÂÑÀ|’ +?ÒÕ6rãl¢"¤#&ç×@…… :ONwÓ€}[à4¥:Íu¡GÍ€Þo,ʇ#«m¨X#Øqq4a#e>~Ì®C„TsN$}]ú(ÄÓœ0ðXšûÉ”±ÀˆvâLjoh"ú!¦±÷øÆÏ!Õï§1‡Fä¼5ÿ’V{#çÛ{C‰ BÎ@|k‰|G ZßY¤÷¹ fá_`ÿð°ùÞìŠh€Mç/<•Æ{Ë;ìÄÞ篈°ntE(òR¤5TW*꺻8ÑŠ¡Šh ²Â`@oƒ.Ó„Ì<¬6áÂâtŸ±OI,°¾@"9ÿë?ù¢/Oã½ð]ŸñWwÓØO¦ #±°z¸i¿³ÑóXD@_F¼0ˆ™ñ¤îoD¾MÍý†»!¦Uóý¿ŽÜ–"š‡8ÀÎEL2/gO#*(àHµæ¬C|Š‰Í·9=ãý1¢)¥#¬AŽkâ ùn9Àß‘KÙ”åF¢„®†êJƒšö\' n‘ZÀhèÑ™íùX¹ß}«×°ŽF¹^æ!‘§D<L!^#8 S§Êîø›ƒ"ÿ^ÏŽÈqø «ß"þ¿`HTlð âdÞÇóøP¤¶s ©‡ŸÚ0Žš“ ü¸xa±ørÎf‡ùìÿJĹJ—!Ä›÷QDK››à5køIDMw .íyÓlbª.#'žÖÇŽâ2¤¨²ÁÑÅ L»Ð¡ZŽÝql\×A&¦™›ZCöçRRŒ@îà–g¼!6ÞK·‰oQ|"ðkRïüy,âhmÎDä›Ksàå>à')îã=àxÄlÙÕóØÉˆ™ŸÂ~lä"mþY4 +76Ÿ{“ìcp*©äyl$r¬ÿLa.cˆïèñ:‰…Us¢HD//89¨%ÔšHCn¤‘¾½zЫ{Wœp[6oŵ#˜* NÜ"lF„úmlÛü í*CôïÛ‡švUÔoÙŒë89¨'Ì×G“©ÎÆBÌ„yˆ9áåwÈ…Øà>ã{¦8¹Ø½ÌAÌ|Ò¸ÀgüÄÌK‡•ˆ@ñÞ½¢Ù¤rwtHüù=@raÕÄZÄœóbS܇WØD‡‹Ž?gùil`·uÌœ5‹y 0Á}ôaöÜë@.¹õ¿¬[%¤¢µÁ.ÜViØ<ñÌ6f/RL½îJæÎ›Ç³óç3wÞ<Î;ïœHcÓ‚¨ÿ”–¼¢égåG{ÄLù1’Ÿ“ÈZŒDÊRa6ñ¾…øvRañ(‰æ›)H‘·—È, àYäàåüµ¸Tù/pcš¯yÿ(ÝÈ_ï—âÒ'Í9äi/£Œý;Úu©ªq×Ýw3lŸfüAÌ#GrãÓøÁÍ÷óàUÕÔÖDíÂØ†U0ë™0XØ›é÷ü–ý÷ßoûcÕÕÕ\síÏX¿n=OÌ~’ªö‰®ñÐz§d™"s pbRmFîÚ ñÕôB¾t~ÍûšX‚äð¤zÁ®BÌ„Ó<ãÇW/̼K|õÛˆ£5Ÿ˜H’¢—÷ÈnÉ{cØÙ¯g"ÇùZ†ṳ̂ÍϦØûyµ×]‡x²þý~Ân"|—¦9—œ"&aVyH.íÛ·§WïxË" qÓM×1bü¹\pûV¶E0œã‘ÿ1 ÑA/sÈnÕ—g¼—Ñd¾èS¦ÂϤïL|úˆKˆÿL«‘¬û Î''ˆI˜ƒJÇIì|žzýUtô]® S‹`ÑyÚBÊæ­£üò±*n»ë7 ’¸á€[…'«cGSJ*Všh¤ŒãH$»{sûxÉÉñrj’× %ÞTÚ‚˜™ùf’˜èeI–ûÝŠÑîd¾`Hº7&¾ò«ÆßÜóò:ð¼Ïx¢ñýø`KAÈA¦;I#€†apÛ¯Áû›†3s~˜J#жs¿n„ߨ\1ÃæÇ—Nå Q~ÑòÉ^X—˜+9 HÇÑ»‘„ÉÃðÿr¦J"çû¡ÄGÍš3‰ø‹g!’ÿ•oüîbõä¦ È¯»Å.$NJMF¦*¼_JŠ"5MÏ.ſҡøb¶ÿɡ˧’ÒÓ=‹L÷T_ש¦wÜygv"{õ]ǃ]Â9Tü`˜Šëg: }§Ÿ–ªß7õcð-”Šõ¶îÃΙë[‘;õJä¢ü”¤K}¤Å“Èj;Í‹„;">”;}žoáoú ¾|à݈dngËç>c•øgÅ—2Ë‘•Ljïââ{œÛþƒÄ?€‰QΈeºgш.+ÆÐ¡C¸üš›¹ú—ðÈÅÛ¨íä`;¹é¢PUárϼ >·âÏ?ÿYê/ÌæØ›^_:,£8‹G|‰äMáÿ¢ÉyýÈ7)V ¹L…ÀÏ<ÛFnVö«}T´ì(U–"Ùëw!åS‰‚ܘÎ.';½E 'ÅÏi¼á “cì¤óøéƒ&Ñpå6 ípV[•ÑÀÂ7 y­Ž»¦ßA‡©ø‘WÙJJ`…’?%oÌ$ÞQ;éiäeñsMv]ÒÁï.™«RŒD9W­5Ãø#$Êy2É“N‡!7®T“nÓ&ë(a&æÔ5W_ŽÕÿ8¦>VéDQNì̶JÕÀ[‡˜úÔ.Ü|Ë ”n¡|–Ç^ZQÂbò ðoϘ…|Ñ›">Ü¡p…Îào¶´'>ã>üzgÙ Þ³µà#ÑΉHQt¢ÄÞ ¤Ø:YÐ%#¤§{V:·§ŠŠ ¦ßu;»ãøÅÿuÄÔ6†›~D°RÙ¼ÿYþ±3]5ñãǦ}²×.#Š¿êxvvÊG²á›S¨Bç&ü ’µØI—Ý|ƶ  ­(9ÑžoÄÿ¸RóØ+ד0ËŸLèܹ†˜Á;‘ \ùH áF1òµ|S´3`éòçÎêÉÙ—ßÂ)§œ˜ñIÈîè[]”0Ÿó 5;·ŸŽRügùrÌl‘ÕÛÿ ˆgRª“ȇñ,þuw…dð}¤…qÏccÒ”ß#ã'Há¸hŽ!ÍE¦!½Þ[#Ç#]Wˆ¬4é¯õ%;Šª«‘œ³)ÈÂ^“ð¤‘`NÉÚ$TJ±ió.¹ôÒÖ#¬bX–…iYر63éÓºŽ·À¼Š„óýÂü.;Vø-6ÿDò…#>K½bÚ^…„í7 šTd•˜DÜ \›ó™†;ROjÍé$DP­F΋œ«:â0‚˜ù‘Y»¢±Ìl:ŽjMc¤É Q¤•q2ı³/ï DVB¢ÈrT~ë_ÀË…N‹<4:œíc%ÉÍCœ²k®­•ýðïuÕŽÔLÄ­ˆ™ü\.'Õ„hXfæVÙS¼Óæ•)fñ³‹Áå× rãŒõ»»gš÷ó:²¬ÖÅÈú~锕|ƒ¬mx ™/Ö`àÿ™fº¼“·šDè$Ó*Ö •Ç¿¸n2– ­±óùõtÏÂSæ¨âH,øˆ¼aÔLû†ç‹áÄ_8+ŸP.x”ø²l4·ÍH}Ü}È{4°/0hጠ© ï!ÚÙÓd¿ªÌF¤hØ+p3Ýï2àÏX=-¯5 búžŽhSG!I¿{!&°·KëVÄùwä3}G½¨.]ºœ¬ ã±ëMÞzP¼½qÆýÉóÕ ±ç£<ãW >žÖB Ò𯖠8l@"¡ÉÙh+t@Ò=º²C ܆˜À_‘›>b)a¹®²U¶ÙÞåŒÆ&HÆòãâ…Õ—ø—p”2ßàß”¯œØ |ÛŠŠÎ&­[—³¼tP€ÞB °¼ôÁ1Ï»h{]  ˆå8Æ×†áFQ%ç°-y uÚkƵu,$wÉ[†ó!â È˲ô*Ça-è’XÙµU¡@ë¤Kº—·#™Ï^®#y9å…µiÓ¦M;vzWkúYEé¡¥®'YŸër¡Vçû<ö$Å/à hX®ë<§”qD ®ÒDë/**Bo{%À$$ïW¦òÒ!øzd`ÛöÓ Ý„Ò[±M ÕÜ7–s©²¨æÅø7®«G–~ ü|9Ápg£e…†övƒ HLD¡/ˆF£~K‚—"ía&á¿ì{f…nІÙ^I¯”¾Mk¦©½U°%ÙÔÓõõõo¤ÊÛ I&ôc#’-]È¥»Ê€íuJÑhô+3ê‰fd1'ÔÐèÍ®ëLq§œsŠ6 íG¼é0¯ I£/|Fmž2F;vìX‰D–‚øH[äâp8<½Ø“(2"œŒý½Y–~Ò7* çìT ‰D”ªxS)÷DPÞv¶èG"‘ÈÕ]#Õü iVwð7‚šÊ€<â[“SQQ1Yký0¨Ö×÷8_HZûÂh4:™Ö½ìx.Q‚; €øöÚqg¹RêàTÜÂå‰ÖÏØ¶}*A¶v@@ÑHØLký¾a/#>Šn…›Ri¡5ZÁ]¶mŸ‹T­‰»º®û…뺳AUkidæ×Ű͢µ~W)ÎvçnßL@@ÑI§¯ÌHÃ0.ÖZ£”Êíj¦%†Öú=¥Ôý®ë>D`” ™4ÂÚ8V)õ`Ò>6Ó¾Ó¥ÂVàS­õ«Èzt‹IÞJ6  ÀdÛ¹¯é{ÔY×­’Ö5RHÓ½­H›×/n˜ÅZ…8 þî@ᄽ}qXIEND®B`‚pyramid-1.6/docs/quick_tour/awesome/awesome/static/pylons.css0000644000076500000240000001041012520062551025306 0ustar michaelstaff00000000000000html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ vertical-align:baseline;background:transparent;} body{line-height:1;} ol,ul{list-style:none;} blockquote,q{quotes:none;} blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} /* remember to define focus styles! */ :focus{outline:0;} /* remember to highlight inserts somehow! */ ins{text-decoration:none;} del{text-decoration:line-through;} /* tables still need 'cellspacing="0"' in the markup */ table{border-collapse:collapse;border-spacing:0;} /* restyling */ sub{vertical-align:sub;font-size:smaller;line-height:normal;} sup{vertical-align:super;font-size:smaller;line-height:normal;} /* lists */ ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} li{display:list-item;} /* nested lists have no top/bottom margins */ ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} /* 2 deep unordered lists use a circle */ ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} /* 3 deep (or more) unordered lists use a square */ ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} .hidden{display:none;} p{line-height:1.5em;} h1{font-size:1.75em;/* 28px */ line-height:1.7em;font-family:helvetica,verdana;} h2{font-size:1.5em;/* 24px */ line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;/* 20px */ line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, body h2, body h3, body h4, body h5, body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;} #wrap {min-height: 100%;} #header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;} #header{background-color:#e88f00;top:0;font-size:14px;} #footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;} .header,.footer{width:700px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} #top,#bottom{width:100%;} #top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} #bottom{color:#222;background-color:#ffffff;overflow:auto;padding-bottom:80px;} .top,.bottom{width:700px;margin-right:auto;margin-left:auto;} .top{padding-top:100px;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} #left{width:325px;float:left;padding-right:25px;} #right{width:325px;float:right;padding-left:25px;} .align-left{text-align:left;} .align-right{text-align:right;} .align-center{text-align:center;} ul.links{margin:0;padding:0;} ul.links li{list-style-type:none;font-size:14px;} form{border-style:none;} fieldset{border-style:none;} input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} input[type=text]{} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} pyramid-1.6/docs/quick_tour/awesome/awesome/templates/0000755000076500000240000000000012642137501023766 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/awesome/awesome/templates/mytemplate.jinja20000644000076500000240000000632312520062551027247 0ustar michaelstaff00000000000000 The Pyramid Web Framework
Logo

Welcome to {{project}}, an application generated by
the Pyramid web framework.

{% trans %}Hello!{% endtrans %}

Request performed with {{ request.locale_name }} locale.

pyramid-1.6/docs/quick_tour/awesome/awesome/tests.py0000644000076500000240000000074512520062551023507 0ustar michaelstaff00000000000000import unittest from pyramid import testing from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('awesome') class ViewTests(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from awesome.views import my_view request = testing.DummyRequest() response = my_view(request) self.assertEqual(response['project'], 'awesome') pyramid-1.6/docs/quick_tour/awesome/awesome/views.py0000644000076500000240000000022312520062551023471 0ustar michaelstaff00000000000000from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('awesome') def my_view(request): return {'project':'awesome'} pyramid-1.6/docs/quick_tour/awesome/CHANGES.txt0000644000076500000240000000003312520062551022132 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/quick_tour/awesome/development.ini0000644000076500000240000000141412520062551023350 0ustar michaelstaff00000000000000[app:awesome] use = egg:awesome reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false debug_templates = true default_locale_name = en jinja2.directories = awesome:templates [pipeline:main] pipeline = awesome [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, awesome [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_awesome] level = DEBUG handlers = qualname = awesome [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 pyramid-1.6/docs/quick_tour/awesome/MANIFEST.in0000644000076500000240000000020212520062551022055 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include awesome *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/quick_tour/awesome/message-extraction.ini0000644000076500000240000000006512520062551024631 0ustar michaelstaff00000000000000[python: **.py] [jinja2: **.jinja2] encoding = utf-8 pyramid-1.6/docs/quick_tour/awesome/README.txt0000644000076500000240000000002212520062551022015 0ustar michaelstaff00000000000000awesome README pyramid-1.6/docs/quick_tour/awesome/setup.py0000644000076500000240000000174612520062551022047 0ustar michaelstaff00000000000000import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires=['pyramid>=1.0.2', 'pyramid_jinja2'] setup(name='awesome', version='0.0', description='awesome', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="awesome", entry_points = """\ [paste.app_factory] main = awesome:main """, paster_plugins=['pyramid'], ) pyramid-1.6/docs/quick_tour/hello_world/0000755000076500000240000000000012642137501021202 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/hello_world/app.py0000644000076500000240000000070012520062551022326 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('

Hello World!

') if __name__ == '__main__': config = Configurator() config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/jinja2/0000755000076500000240000000000012642137501020045 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/jinja2/app.py0000644000076500000240000000054512520062551021200 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_route('hello', '/howdy/{name}') config.include('pyramid_jinja2') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/jinja2/hello_world.jinja20000644000076500000240000000020012520062551023443 0ustar michaelstaff00000000000000 Hello World

Hello {{ name }}!

pyramid-1.6/docs/quick_tour/jinja2/views.py0000644000076500000240000000031512520062551021550 0ustar michaelstaff00000000000000from pyramid.view import view_config # Start View 1 @view_config(route_name='hello', renderer='hello_world.jinja2') # End View 1 def hello_world(request): return dict(name=request.matchdict['name']) pyramid-1.6/docs/quick_tour/json/0000755000076500000240000000000012642137501017641 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/json/app.py0000644000076500000240000000072112520062551020770 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_route('hello', '/howdy/{name}') config.add_route('hello_json', 'hello.json') config.add_static_view(name='static', path='static') config.include('pyramid_jinja2') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/json/hello_world.jinja20000644000076500000240000000026512520062551023252 0ustar michaelstaff00000000000000 Quick Glance

Hello {{ name }}!

pyramid-1.6/docs/quick_tour/json/hello_world.pt0000644000076500000240000000056312520062551022521 0ustar michaelstaff00000000000000 Quick Glance

Hello ${name}!

pyramid-1.6/docs/quick_tour/json/views.py0000644000076500000240000000046312520062551021350 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(route_name='hello', renderer='hello_world.pt') def hello_world(request): return dict(name=request.matchdict['name']) # Start View 1 @view_config(route_name='hello_json', renderer='json') def hello_json(request): return [1, 2, 3] # End View 1pyramid-1.6/docs/quick_tour/package/0000755000076500000240000000000012642137501020263 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/CHANGES.txt0000644000076500000240000000003312520062551022065 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/quick_tour/package/development.ini0000644000076500000240000000163412520062551023307 0ustar michaelstaff00000000000000# Start Includes [app:hello_world] pyramid.includes = pyramid_debugtoolbar # End Includes use = egg:hello_world reload_templates = true debug_authorization = false debug_notfound = false debug_routematch = false debug_templates = true default_locale_name = en jinja2.directories = hello_world:templates [pipeline:main] pipeline = hello_world [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 # Begin logging configuration # Start Sphinx Include [loggers] keys = root, hello_world [logger_hello_world] level = DEBUG handlers = qualname = hello_world # End Sphinx Include [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 pyramid-1.6/docs/quick_tour/package/hello_world/0000755000076500000240000000000012642137501022575 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/__init__.py0000644000076500000240000000213712520062551024706 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_jinja2 import renderer_factory # Start Sphinx Include 1 from pyramid.session import SignedCookieSessionFactory # End Sphinx Include 1 from hello_world.models import get_root def main(global_config, **settings): """ This function returns a WSGI application. It is usually called by the PasteDeploy framework during ``paster serve``. """ settings = dict(settings) settings.setdefault('jinja2.i18n.domain', 'hello_world') # Start Sphinx Include 2 my_session_factory = SignedCookieSessionFactory('itsaseekreet') config = Configurator(root_factory=get_root, settings=settings, session_factory=my_session_factory) # End Sphinx Include 2 config.add_translation_dirs('locale/') # Start Include config.include('pyramid_jinja2') # End Include config.add_static_view('static', 'static') config.add_view('hello_world.views.my_view', context='hello_world.models.MyModel', renderer="mytemplate.jinja2") return config.make_wsgi_app() pyramid-1.6/docs/quick_tour/package/hello_world/init.py0000644000076500000240000000211712520062551024110 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_jinja2 import renderer_factory # Start Sphinx 1 from pyramid.session import SignedCookieSessionFactory # End Sphinx 1 from hello_world.models import get_root def main(global_config, **settings): """ This function returns a WSGI application. It is usually called by the PasteDeploy framework during ``paster serve``. """ settings = dict(settings) settings.setdefault('jinja2.i18n.domain', 'hello_world') config = Configurator(root_factory=get_root, settings=settings) config.add_translation_dirs('locale/') # Start Include config.include('pyramid_jinja2') # End Include # Start Sphinx Include 2 my_session_factory = SignedCookieSessionFactory('itsaseekreet') config = Configurator(session_factory=my_session_factory) # End Sphinx Include 2 config.add_static_view('static', 'static') config.add_view('hello_world.views.my_view', context='hello_world.models.MyModel', renderer="mytemplate.jinja2") return config.make_wsgi_app() pyramid-1.6/docs/quick_tour/package/hello_world/locale/0000755000076500000240000000000012642137501024034 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/locale/de/0000755000076500000240000000000012642137501024424 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/0000755000076500000240000000000012642137501026211 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo0000644000076500000240000000071412520062551031057 0ustar michaelstaff00000000000000Þ•,<=€DÅHello!Project-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2011-05-12 09:14-0430 PO-Revision-Date: 2011-05-12 10:12-0330 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 Hallo!pyramid-1.6/docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po0000644000076500000240000000117112520062551031060 0ustar michaelstaff00000000000000# Translations template for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "Hallo!" pyramid-1.6/docs/quick_tour/package/hello_world/locale/fr/0000755000076500000240000000000012642137501024443 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/0000755000076500000240000000000012642137501026230 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo0000644000076500000240000000071512520062551031077 0ustar michaelstaff00000000000000Þ•,<=DÄHello!Project-Id-Version: PROJECT VERSION Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2011-05-12 09:14-0430 PO-Revision-Date: 2011-05-12 10:12-0330 Last-Translator: FULL NAME Language-Team: fr Plural-Forms: nplurals=2; plural=(n > 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.6 Bonjour!pyramid-1.6/docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po0000644000076500000240000000117312520062551031101 0ustar michaelstaff00000000000000# Translations template for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "Bonjour!" pyramid-1.6/docs/quick_tour/package/hello_world/locale/hello_world.pot0000644000076500000240000000116312520062551027070 0ustar michaelstaff00000000000000# Translations template for PROJECT. # Copyright (C) 2011 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2011. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2011-05-12 09:14-0330\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.6\n" msgid "Hello!" msgstr "" pyramid-1.6/docs/quick_tour/package/hello_world/models.py0000644000076500000240000000013312520062551024424 0ustar michaelstaff00000000000000class MyModel(object): pass root = MyModel() def get_root(request): return root pyramid-1.6/docs/quick_tour/package/hello_world/static/0000755000076500000240000000000012642137501024064 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/static/favicon.ico0000644000076500000240000000257612520062551026214 0ustar michaelstaff00000000000000h( " A>;)'%+¯ô=¿ö420520ÿÿÿ;96¸èú „„†µäô—àúGDA/-+LHF+©é\XUtØùååæììì,BO/Vi531WSP+®ñ·çø§§©OÏù--..-.QNK       #!!!!!!"   $$$$$$$$$$$$$$$$À€€Àpyramid-1.6/docs/quick_tour/package/hello_world/static/logo.png0000644000076500000240000001476112520062551025540 0ustar michaelstaff00000000000000‰PNG  IHDR,P£êH{¸IDATxœíwœåÝÀ¿ÏÌìÚG/ "¢(%¢RD$Vô jT^KLb‰Æk”X’`¬˜¢hTH,Q|A1 hÆ‚X¥rw»;3ÏûÇoŽÙÙÛ^îv¾÷Ž{vwö™Ùßüú£ÈcŽ9¦]ƒm 2•;ØÕì z¬BõBké> ‡VÁPo‹-“œ°ñáÂ…sV{f‰Qé劯À:ô°Ã/U†y»vu™éT‰1 W;Ë´m¹xñüUÅžO@@9'°F›0Ù²Ì?k­­2W¬<è˜_ËYÒ¡]åQsçέ/öŒÊ ³ùc&NÜÕÐ<…¦“«uz9e€ëj ÓÐØ©üìÓ ‹=Ÿ€€r£¹ÀRuýÜoæN,¨ƒm§ @k”±ÿÝú¿øÙÊ•_¤wº²a»5úÐ ‡¡y­bN¨5`˜®ë,êÙ­ö;AºC@@á°b¿•vË Ã4$ë; %ÛA)5~ÍšÿŽ^,ö|Ê `Ô˜1{k—ñŽ”…TQJ)G묀€‚!–£ŽW–Qá:ÀJW;hôá£GîöòË/¯+ö|Ê P(}˜v]‚¬«ÔÑ”R݇‘À³ÅžO@@9`7®¶1âîéª@`¥‹R.êP¬p8ܪuuy5ç­]\íîYìy”1guìÈ:QÀkÀœ¢Í( ¯XVw…6˽^0´«AÓ«Øó(cΆ{Æf¬6‹¥µ[£”!N™c§)©4» ‹Æm?uêTã†n(´ŠZtzƶ@-PtªÙqÊ4°X ||| l-ì”sNƒÏX¸à³(–ÖÚ º…j¤ôŶ)1!ËÄ0Zƒc»ØŽƒ,ÓÀ2MT)H0­Í%K–@!–< t"}YnŸK?/Ñ\N0 XPÜöƈF´ÖtïÚ‰¡ƒû0t> èÛ.]ÚQ]Uí¸lÝæëµ›øhÅW¼»|+>[KCc„Š…aOr¹Å9o ØѦ2Áv‹m§¯7 r2»€€u<»÷ïB$ì€Îÿ1•Ø*BéxñÖg÷û<Ö IÊ () Ѱòßþ®1lsòуøÑ‰Cˆ6nCÛà†sºE·Ñ·»Å´Ÿ~›]:WbÛnÞ«•£Ë€ÿø¼ÿ†»$xÞFDý 0ü™2¸Ù3ö;à‘fì¸xBjI½¬þüx)‹ù4§òùœ| )óâ ÂûàQvhíÉÐHDz0øCl|ð)pdì=W¶å†vó"°¢¶fèní¾{%Ñȶ‚ß'MËä¨C:³èëòjò¶B@…ϸ_}á'È<Ä3¾'p7’´š. É å_…Üm EGäRPžŒ.ˆf0¸9þéøe'£Šøã_Üìÿ{wG$ÙOwà{ˆÀ½¸ŠìŠÜG!ǵ’ç™H«Ÿ:àà>àœ4Þ§ùT#çO!Bï4Dx½t—(ažÒQÛeÔ°v´« £ÜFSÙìHßÚâ¶&„ãäçÛ† ‹]ð¿[û ŸüS!:Ggøþ»ßö˜ôMŒLŽôç¿Ô„•—^À¯z̼~‹ÏXÇØïqÀ"’ «æ˜Àùˆæç§¥ÂÑÀ|’ +?ÒÕ6rãl¢"¤#&ç×@…… :ONwÓ€}[à4¥:Íu¡GÍ€Þo,ʇ#«m¨X#Øqq4a#e>~Ì®C„TsN$}]ú(ÄÓœ0ðXšûÉ”±ÀˆvâLjoh"ú!¦±÷øÆÏ!Õï§1‡Fä¼5ÿ’V{#çÛ{C‰ BÎ@|k‰|G ZßY¤÷¹ fá_`ÿð°ùÞìŠh€Mç/<•Æ{Ë;ìÄÞ篈°ntE(òR¤5TW*꺻8ÑŠ¡Šh ²Â`@oƒ.Ó„Ì<¬6áÂâtŸ±OI,°¾@"9ÿë?ù¢/Oã½ð]ŸñWwÓØO¦ #±°z¸i¿³ÑóXD@_F¼0ˆ™ñ¤îoD¾MÍý†»!¦Uóý¿ŽÜ–"š‡8ÀÎEL2/gO#*(àHµæ¬C|Š‰Í·9=ãý1¢)¥#¬AŽkâ ùn9Àß‘KÙ”åF¢„®†êJƒšö\' n‘ZÀhèÑ™íùX¹ß}«×°ŽF¹^æ!‘§D<L!^#8 S§Êîø›ƒ"ÿ^ÏŽÈqø «ß"þ¿`HTlð âdÞÇóøP¤¶s ©‡ŸÚ0Žš“ ü¸xa±ørÎf‡ùìÿJĹJ—!Ä›÷QDK››à5køIDMw .íyÓlbª.#'žÖÇŽâ2¤¨²ÁÑÅ L»Ð¡ZŽÝql\×A&¦™›ZCöçRRŒ@îà–g¼!6ÞK·‰oQ|"ðkRïüy,âhmÎDä›Ksàå>à')îã=àxÄlÙÕóØÉˆ™ŸÂ~lä"mþY4 +76Ÿ{“ìcp*©äyl$r¬ÿLa.cˆïèñ:‰…Us¢HD//89¨%ÔšHCn¤‘¾½zЫ{Wœp[6oŵ#˜* NÜ"lF„úmlÛü í*CôïÛ‡švUÔoÙŒë89¨'Ì×G“©ÎÆBÌ„yˆ9áåwÈ…Øà>ã{¦8¹Ø½ÌAÌ|Ò¸ÀgüÄÌK‡•ˆ@ñÞ½¢Ù¤rwtHüù=@raÕÄZÄœóbS܇WØD‡‹Ž?gùil`·uÌœ5‹y 0Á}ôaöÜë@.¹õ¿¬[%¤¢µÁ.ÜViØ<ñÌ6f/RL½îJæÎ›Ç³óç3wÞ<Î;ïœHcÓ‚¨ÿ”–¼¢égåG{ÄLù1’Ÿ“ÈZŒDÊRa6ñ¾…øvRañ(‰æ›)H‘·—È, àYäàåüµ¸Tù/pcš¯yÿ(ÝÈ_ï—âÒ'Í9äi/£Œý;Úu©ªq×Ýw3lŸfüAÌ#GrãÓøÁÍ÷óàUÕÔÖDíÂØ†U0ë™0XØ›é÷ü–ý÷ßoûcÕÕÕ\síÏX¿n=OÌ~’ªö‰®ñÐz§d™"s pbRmFîÚ ñÕôB¾t~ÍûšX‚äð¤zÁ®BÌ„Ó<ãÇW/̼K|õÛˆ£5Ÿ˜H’¢—÷ÈnÉ{cØÙ¯g"ÇùZ†ṳ̂ÍϦØûyµ×]‡x²þý~Ân"|—¦9—œ"&aVyH.íÛ·§WïxË" qÓM×1bü¹\pûV¶E0œã‘ÿ1 ÑA/sÈnÕ—g¼—Ñd¾èS¦ÂϤïL|úˆKˆÿL«‘¬û Î''ˆI˜ƒJÇIì|žzýUtô]® S‹`ÑyÚBÊæ­£üò±*n»ë7 ’¸á€[…'«cGSJ*Všh¤ŒãH$»{sûxÉÉñrj’× %ÞTÚ‚˜™ùf’˜èeI–ûÝŠÑîd¾`Hº7&¾ò«ÆßÜóò:ð¼Ïx¢ñýø`KAÈA¦;I#€†apÛ¯Áû›†3s~˜J#жs¿n„ߨ\1ÃæÇ—Nå Q~ÑòÉ^X—˜+9 HÇÑ»‘„ÉÃðÿr¦J"çû¡ÄGÍš3‰ø‹g!’ÿ•oüîbõä¦ È¯»Å.$NJMF¦*¼_JŠ"5MÏ.ſҡøb¶ÿɡ˧’ÒÓ=‹L÷T_ש¦wÜygv"{õ]ǃ]Â9Tü`˜Šëg: }§Ÿ–ªß7õcð-”Šõ¶îÃΙë[‘;õJä¢ü”¤K}¤Å“Èj;Í‹„;">”;}žoáoú ¾|à݈dngËç>c•øgÅ—2Ë‘•Ljïââ{œÛþƒÄ?€‰QΈeºgш.+ÆÐ¡C¸üš›¹ú—ðÈÅÛ¨íä`;¹é¢PUárϼ >·âÏ?ÿYê/ÌæØ›^_:,£8‹G|‰äMáÿ¢ÉyýÈ7)V ¹L…ÀÏ<ÛFnVö«}T´ì(U–"Ùëw!åS‰‚ܘÎ.';½E 'ÅÏi¼á “cì¤óøéƒ&Ñpå6 ípV[•ÑÀÂ7 y­Ž»¦ßA‡©ø‘WÙJJ`…’?%oÌ$ÞQ;éiäeñsMv]ÒÁï.™«RŒD9W­5Ãø#$Êy2É“N‡!7®T“nÓ&ë(a&æÔ5W_ŽÕÿ8¦>VéDQNì̶JÕÀ[‡˜úÔ.Ü|Ë ”n¡|–Ç^ZQÂbò ðoϘ…|Ñ›">Ü¡p…Îào¶´'>ã>üzgÙ Þ³µà#ÑΉHQt¢ÄÞ ¤Ø:YÐ%#¤§{V:·§ŠŠ ¦ßu;»ãøÅÿuÄÔ6†›~D°RÙ¼ÿYþ±3]5ñãǦ}²×.#Š¿êxvvÊG²á›S¨Bç&ü ’µØI—Ý|ƶ  ­(9ÑžoÄÿ¸RóØ+ד0ËŸLèܹ†˜Á;‘ \ùH áF1òµ|S´3`éòçÎêÉÙ—ßÂ)§œ˜ñIÈîè[]”0Ÿó 5;·ŸŽRügùrÌl‘ÕÛÿ ˆgRª“ȇñ,þuw…dð}¤…qÏccÒ”ß#ã'Há¸hŽ!ÍE¦!½Þ[#Ç#]Wˆ¬4é¯õ%;Šª«‘œ³)ÈÂ^“ð¤‘`NÉÚ$TJ±ió.¹ôÒÖ#¬bX–…iYر63éÓºŽ·À¼Š„óýÂü.;Vø-6ÿDò…#>K½bÚ^…„í7 šTd•˜DÜ \›ó™†;ROjÍé$DP­F΋œ«:â0‚˜ù‘Y»¢±Ìl:ŽjMc¤É Q¤•q2ı³/ï DVB¢ÈrT~ë_ÀË…N‹<4:œíc%ÉÍCœ²k®­•ýðïuÕŽÔLÄ­ˆ™ü\.'Õ„hXfæVÙS¼Óæ•)fñ³‹Áå× rãŒõ»»gš÷ó:²¬ÖÅÈú~锕|ƒ¬mx ™/Ö`àÿ™fº¼“·šDè$Ó*Ö •Ç¿¸n2– ­±óùõtÏÂSæ¨âH,øˆ¼aÔLû†ç‹áÄ_8+ŸP.x”ø²l4·ÍH}Ü}È{4°/0hጠ© ï!ÚÙÓd¿ªÌF¤hØ+p3Ýï2àÏX=-¯5 búžŽhSG!I¿{!&°·KëVÄùwä3}G½¨.]ºœ¬ ã±ëMÞzP¼½qÆýÉóÕ ±ç£<ãW >žÖB Ò𯖠8l@"¡ÉÙh+t@Ò=º²C ܆˜À_‘›>b)a¹®²U¶ÙÞåŒÆ&HÆòãâ…Õ—ø—p”2ßàß”¯œØ |ÛŠŠÎ&­[—³¼tP€ÞB °¼ôÁ1Ï»h{]  ˆå8Æ×†áFQ%ç°-y uÚkƵu,$wÉ[†ó!â È˲ô*Ça-è’XÙµU¡@ë¤Kº—·#™Ï^®#y9å…µiÓ¦M;vzWkúYEé¡¥®'YŸër¡Vçû<ö$Å/à hX®ë<§”qD ®ÒDë/**Bo{%À$$ïW¦òÒ!øzd`ÛöÓ Ý„Ò[±M ÕÜ7–s©²¨æÅø7®«G–~ ü|9Ápg£e…†övƒ HLD¡/ˆF£~K‚—"ía&á¿ì{f…nІÙ^I¯”¾Mk¦©½U°%ÙÔÓõõõo¤ÊÛ I&ôc#’-]È¥»Ê€íuJÑhô+3ê‰fd1'ÔÐèÍ®ëLq§œsŠ6 íG¼é0¯ I£/|Fmž2F;vìX‰D–‚øH[äâp8<½Ø“(2"œŒý½Y–~Ò7* çìT ‰D”ªxS)÷DPÞv¶èG"‘ÈÕ]#Õü iVwð7‚šÊ€<â[“SQQ1Yký0¨Ö×÷8_HZûÂh4:™Ö½ìx.Q‚; €øöÚqg¹RêàTÜÂå‰ÖÏØ¶}*A¶v@@ÑHØLký¾a/#>Šn…›Ri¡5ZÁ]¶mŸ‹T­‰»º®û…뺳AUkidæ×Ű͢µ~W)ÎvçnßL@@ÑI§¯ÌHÃ0.ÖZ£”Êíj¦%†Öú=¥Ôý®ë>D`” ™4ÂÚ8V)õ`Ò>6Ó¾Ó¥ÂVàS­õ«Èzt‹IÞJ6  ÀdÛ¹¯é{ÔY×­’Ö5RHÓ½­H›×/n˜ÅZ…8 þî@ᄽ}qXIEND®B`‚pyramid-1.6/docs/quick_tour/package/hello_world/static/pylons.css0000644000076500000240000001041012520062551026113 0ustar michaelstaff00000000000000html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ vertical-align:baseline;background:transparent;} body{line-height:1;} ol,ul{list-style:none;} blockquote,q{quotes:none;} blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} /* remember to define focus styles! */ :focus{outline:0;} /* remember to highlight inserts somehow! */ ins{text-decoration:none;} del{text-decoration:line-through;} /* tables still need 'cellspacing="0"' in the markup */ table{border-collapse:collapse;border-spacing:0;} /* restyling */ sub{vertical-align:sub;font-size:smaller;line-height:normal;} sup{vertical-align:super;font-size:smaller;line-height:normal;} /* lists */ ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} li{display:list-item;} /* nested lists have no top/bottom margins */ ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} /* 2 deep unordered lists use a circle */ ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} /* 3 deep (or more) unordered lists use a square */ ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} .hidden{display:none;} p{line-height:1.5em;} h1{font-size:1.75em;/* 28px */ line-height:1.7em;font-family:helvetica,verdana;} h2{font-size:1.5em;/* 24px */ line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;/* 20px */ line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, body h2, body h3, body h4, body h5, body h6{font-family:"Nobile","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#144fb2;font-style:normal;} #wrap {min-height: 100%;} #header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;} #header{background-color:#e88f00;top:0;font-size:14px;} #footer{background-color:#000000;bottom:0;position: relative;margin-top:-40px;clear:both;} .header,.footer{width:700px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} #top,#bottom{width:100%;} #top{color:#888;background-color:#eee;height:300px;border-bottom:2px solid #ddd;} #bottom{color:#222;background-color:#ffffff;overflow:auto;padding-bottom:80px;} .top,.bottom{width:700px;margin-right:auto;margin-left:auto;} .top{padding-top:100px;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} #left{width:325px;float:left;padding-right:25px;} #right{width:325px;float:right;padding-left:25px;} .align-left{text-align:left;} .align-right{text-align:right;} .align-center{text-align:center;} ul.links{margin:0;padding:0;} ul.links li{list-style-type:none;font-size:14px;} form{border-style:none;} fieldset{border-style:none;} input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} input[type=text]{} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ body:before {content:"";height:100%;float:left;width:0;margin-top:-32767px;} pyramid-1.6/docs/quick_tour/package/hello_world/templates/0000755000076500000240000000000012642137501024573 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/package/hello_world/templates/mytemplate.jinja20000644000076500000240000000653412520062551030060 0ustar michaelstaff00000000000000 The Pyramid Web Framework
Logo

Welcome to {{project}}, an application generated by
the Pyramid Web Framework.

{% trans %}Hello!{% endtrans %}

Counter: {{ request.session.counter }}

Request performed with {{ request.locale_name }} locale.

pyramid-1.6/docs/quick_tour/package/hello_world/tests.py0000644000076500000240000000076012520062551024311 0ustar michaelstaff00000000000000import unittest from pyramid import testing from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('hello_world') class ViewTests(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from hello_world.views import my_view request = testing.DummyRequest() response = my_view(request) self.assertEqual(response['project'], 'hello_world') pyramid-1.6/docs/quick_tour/package/hello_world/views.py0000644000076500000240000000076512520062551024311 0ustar michaelstaff00000000000000# Start Logging 1 import logging log = logging.getLogger(__name__) # End Logging 1 from pyramid.i18n import TranslationStringFactory _ = TranslationStringFactory('hello_world') def my_view(request): # Start Logging 2 log.debug('Some Message') # End Logging 2 # Start Sphinx Include 1 session = request.session if 'counter' in session: session['counter'] += 1 else: session['counter'] = 0 # End Sphinx Include 1 return {'project': 'hello_world'} pyramid-1.6/docs/quick_tour/package/MANIFEST.in0000644000076500000240000000020612520062551022014 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include hello_world *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/quick_tour/package/message-extraction.ini0000644000076500000240000000006512520062551024564 0ustar michaelstaff00000000000000[python: **.py] [jinja2: **.jinja2] encoding = utf-8 pyramid-1.6/docs/quick_tour/package/README.txt0000644000076500000240000000002612520062551021754 0ustar michaelstaff00000000000000hello_world README pyramid-1.6/docs/quick_tour/package/setup.py0000644000076500000240000000215412520062551021774 0ustar michaelstaff00000000000000import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() # Start Requires requires = ['pyramid>=1.0.2', 'pyramid_jinja2', 'pyramid_debugtoolbar'] # End Requires setup(name='hello_world', version='0.0', description='hello_world', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="hello_world", entry_points="""\ [paste.app_factory] main = hello_world:main """, paster_plugins=['pyramid'], extras_require={ 'testing': ['nose', ], } )pyramid-1.6/docs/quick_tour/requests/0000755000076500000240000000000012642137501020543 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/requests/app.py0000644000076500000240000000123512520062551021673 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): # Some parameters from a request such as /?name=lisa url = request.url name = request.params.get('name', 'No Name Provided') body = 'URL %s with name: %s' % (url, name) return Response( content_type="text/plain", body=body ) if __name__ == '__main__': config = Configurator() config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/routing/0000755000076500000240000000000012642137501020357 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/routing/app.py0000644000076500000240000000055612520062551021514 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() # Start Route 1 config.add_route('hello', '/howdy/{first}/{last}') # End Route 1 config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/routing/views.py0000644000076500000240000000040412520062551022061 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config # Start Route 1 @view_config(route_name='hello') def hello_world(request): body = '

Hi %(first)s %(last)s!

' % request.matchdict return Response(body) # End Route 1pyramid-1.6/docs/quick_tour/sqla_demo/0000755000076500000240000000000012642137501020634 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/sqla_demo/CHANGES.txt0000644000076500000240000000003412520062551022437 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/quick_tour/sqla_demo/development.ini0000644000076500000240000000257312520062551023663 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:sqla_demo pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, sqla_demo, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_sqla_demo] level = DEBUG handlers = qualname = sqla_demo [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/quick_tour/sqla_demo/MANIFEST.in0000644000076500000240000000020412520062551022363 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include sqla_demo *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/quick_tour/sqla_demo/production.ini0000644000076500000240000000227112520062551023522 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:sqla_demo pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/sqla_demo.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, sqla_demo, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_sqla_demo] level = WARN handlers = qualname = sqla_demo [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/quick_tour/sqla_demo/README.txt0000644000076500000240000000035312520062551022330 0ustar michaelstaff00000000000000sqla_demo README ================== Getting Started --------------- - cd - $venv/bin/python setup.py develop - $venv/bin/initialize_sqla_demo_db development.ini - $venv/bin/pserve development.ini pyramid-1.6/docs/quick_tour/sqla_demo/setup.py0000644000076500000240000000217712520062551022352 0ustar michaelstaff00000000000000import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) README = open(os.path.join(here, 'README.txt')).read() CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() requires = [ 'pyramid', 'SQLAlchemy', 'transaction', 'pyramid_tm', 'pyramid_debugtoolbar', 'zope.sqlalchemy', 'waitress', ] setup(name='sqla_demo', version='0.0', description='sqla_demo', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='sqla_demo', install_requires=requires, entry_points="""\ [paste.app_factory] main = sqla_demo:main [console_scripts] initialize_sqla_demo_db = sqla_demo.scripts.initializedb:main """, ) pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/0000755000076500000240000000000012642137501022600 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/__init__.py0000644000076500000240000000106512520062551024710 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/models.py0000644000076500000240000000121612520062551024432 0ustar michaelstaff00000000000000from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() # Start Sphinx Include class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) value = Column(Integer) def __init__(self, name, value): self.name = name self.value = value # End Sphinx Include pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/scripts/0000755000076500000240000000000012642137501024267 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/scripts/__init__.py0000644000076500000240000000001212520062551026366 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py0000644000076500000240000000143412520062551027307 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, MyModel, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=1) DBSession.add(model) pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/0000755000076500000240000000000012642137501024067 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/favicon.ico0000644000076500000240000000257612520062551026217 0ustar michaelstaff00000000000000h( " A>;)'%+¯ô=¿ö420520ÿÿÿ;96¸èú „„†µäô—àúGDA/-+LHF+©é\XUtØùååæììì,BO/Vi531WSP+®ñ·çø§§©OÏù--..-.QNK       #!!!!!!"   $$$$$$$$$$$$$$$$À€€Àpyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.png0000644000076500000240000000051512520062551026402 0ustar michaelstaff00000000000000‰PNG  IHDRfÍÉÙIDAT8…“É‘Ã0í¬œZ샔%[®Ú‡Šâ58äù| X0`©‡˜Ôáÿo©„qNG@Å™Ï;mH#©¡¦8‰áÎáàKkƒÜ8FèOž-«&19çÀZ#¸ÆA¢dF\gƒ/8ñŠML—¦Gpº8ûdk躓à€éâ}œ}ºôts¾ìóœùe.¾uõ9dë=|úî÷¢­üð9àëðèè­óÉM÷êíÂþÖÓ±}ÈæÕ»æ™ëÁk>öÿ½´þÎÆÕ;«t.k~äí!;?ÀÛŸîZ¢Ôq‚–ÜYÁ:ßîw·óuËmMŽ|¬<äÈì¡ñ+3ëÝ9ðípâ-nù³V=F|˜IEND®B`‚pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.png0000644000076500000240000000031312520062551026330 0ustar michaelstaff00000000000000‰PNG  IHDR4b·Ît’IDAT8í“AÄ C“\«—›SÏn`b‹íºà)4ÁoŸàû9DRp‘hµÈ¯«×uìô½?÷Þ²‡ ”@»÷¬\SÍ=ýÌ®Š3}½÷­¾ÚOÜÕÜåo¼±FŸ5´9,×cå©ïß»'çãmZó&kÌéä… ´q~øx²Xò5†èßE3˜,óçû÷”01]îsÖIEND®B`‚pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css0000644000076500000240000000136612520062551025267 0ustar michaelstaff00000000000000* html img, * html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) );} #wrap{display:table;height:100%} pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.png0000644000076500000240000000535512520062551026351 0ustar michaelstaff00000000000000‰PNG  IHDR¬º—ƹ CiCCPICC Profilex–wTSYÀï{/½ÐB‘z MJ‘z‘^E%$B °WDWiŠ"‹".¸ºY+¢XX°/È" ¬‹«ˆŠe_ôeÿØý¾³óǜߛ;sïÜ™¹ç<(¾BQ&¬@†H"óñ`ÆÄÆ1ñÝD€ÖpyÙYAáÞ?/3u’±L Ïúuÿ¸Åò a2?›þ¥ÈËKÐBй|A6å<”Ós%Y2û$ÊôÄ4ËÑQV•qò6ÿìó…ÝdÌÏñQYÎYü ¾Œ;PÞ’# Œ¢œŸ#ä¢|eýti†å7(Ó3Ül0™]"ॠl…2EÆAyJò,NœÅÁ24O8™YËÅÂä Ó˜g´vtd3}¹é‰„Âå¥qÅ|&'3#‹+ZÀ—;Ë¢€’¬¶L´ÈöÖŽöö, ´ü_å_¿zý;ÈzûÅãeèçžAŒ®o¶o±ßl™Õ°§ÐÚìøfK, eª÷¾Ùô Ÿ@óY÷aÈæ%E"Ér²´ÌÍ͵ x²‚~•ÿéðÕóŸaÖy²ó¾ÖŽé)HâJÓ%LYQy™é™R13;‹Ë0Ybtëÿ8+­Yy˜‡ ’b=* 2¡(m·ˆ/”3EL¡èŸ:üÃfå Ã/s­æ# /± 7èù½ `hd€ÄïGW ¯} $FÙË‹Öý2÷(£ëŸõß\„~ÂÙÂd¦ÌÌ ‹`ò¤â£oB¦°€ä¨- Œ Øà Ü€ðÁ ĂŀR@ƒ\° ¬ù ì{@9¨5 4€ œÀepÜ}à>#à˜¯Á Axˆ Ñ 5H2€Ì ˆ ͇¼ @( Š… dHI¡UÐF¨*†Ê¡ƒPô#t º]…z »Ð4ý ½ƒ˜ÓaMض„Ù°;GÀ‹àdx)¼΃·Ã¥p5| n†/À×á>x~O!!# Da!l„ƒ#qH"FÖ H R4 mH'r D&·††abXgŒ/&ÃÃ,ŬÁlÔcŽ`š1˜[˜!Ì$æ#–ŠÕÀša°~Øl26›-ÁÖb›°—°}ØìkÇÀáp¾¸X\*n%nn®w׃ÆMáñx5¼ÞŒçâ%ø||þþ¾?‚C ´ 6oBADØ@(!%œ%ôF 3D¢щLä—‹ˆ5Ä6â âq†¤H2"¹"H©¤õ¤RRééé%™LÖ%;’CÉBò:r)ù8ù yˆü–¢D1¥p(ñ)e;å0å<å.å%•J5¤ºQã¨êvjõ"õõMÎBÎOŽ/·V®B®Y®W›¦°©iŠi…é 3ØÌÞLh¶Ï¬Çkîh.2¯6`QXî¬V=kÈ‚ah±Á¢Åâ¹¥¾eœåNËNËVvVéV5V÷­•¬ý­7X·Yÿicjó©°¹=—:×{îÚ¹­s_ØšÙ l÷ÛÞ±£ÙÙm¶k·û`ï`/¶o°wÐwHp¨t`ÓÙ!ìmì+ŽXGÇµŽ§ß:Ù;IœN8ýáÌrNs>ê<6Ïhž`^ͼa]®ËA—ÁùÌù óÌtÕqåºV»>vÓsã»Õºº›¸§ºsîaå!öhò˜æ8qVsÎ{"ž>žžÝ^J^‘^å^¼u½“½ë½'}ì|Vúœ÷ÅúøîôðÓôãùÕùMú;ø¯öï „”<4 ¶ÁAþA»‚,0X ZÐ ‚ý‚w? 1 Yòs(.4$´"ôI˜uت°ÎpZø’ð£á¯#<"Š"îGGJ#Û£ä£â£ê¢¦£=£‹£c,cVÇ\UƶÆáã¢âjã¦z-ܳp$Þ.>?¾‘Ñ¢e‹®.V_œ¾øÌù%Ü%'° Ñ GÞsƒ¹ÕÜ©D¿ÄÊÄI‡·—÷ŒïÆßÍ¸Š£I.IÅIcÉ.É»’ÇS\SJR&„a¹ðEªojUêtZpÚá´OéÑ鄌„ŒS"%Qš¨#S+sYfO–YV~ÖàR§¥{–NŠĵÙPö¢ìV ý™ê’K7I‡ræçTä¼ÉÊ=¹Lq™hY×rÓå[—®ð^ñýJÌJÞÊöU:«Ö¯Zí¾úàhMâšöµzkóÖŽ¬óYwd=i}Úú_6Xm(Þðjcôƶ<ͼuyÛ|6ÕçËå‹ó6;o®Ú‚Ù"ÜÒ½uîÖ²­ ø× ­ K ßoãm»öõw¥ß}Úž´½»È¾hÿÜÑŽþ®;+¯(Þ´«y7swÁîW{–ì¹Zb[Rµ—´Wºw°4°´µL¿lGÙûò”ò¾ ŠÆJÊ­•Óûøûz÷»ío¨Ò¬*¬zw@xàÎAŸƒÍÕ†Õ%‡p‡r=©‰ªéüžý}]­zmaí‡Ã¢ÃƒGÂŽtÔ9ÔÕÕ8ZT×KëÇÅ»ùƒç­ ¬†ƒŒÆÂãà¸ôøÓ~ì?p¢ý$ûdÃO?U6Ñš š¡æåÍ“-)-ƒ­±­=§üOµ·9·5ýlñóáÓ:§+Î(Ÿ):K:›wöÓ¹ç¦ÎgŸ¸|a¸}Iûý‹1ow„vt_ ¸tå²÷å‹î箸\9}Õéê©kìk-×í¯7wÙu5ýb÷KS·}wó ‡­7o¶õÌë9ÛëÚ{á–ç­Ë·ýn_ï[Ð×ÓÙg ~`ðÿÎØÝô»/îåÜ›¹¿îöAÁC…‡%4Uÿjòkã ýà™!Ï¡®Çáïó†Ÿý–ýÛû‘¼'Ô'%£Ú£uc6c§Ç½Ço>]øtäYÖ³™‰ü߯|nüü§?Üþ蚌™y!~ñéÏm/Õ^~eûª}*dêÑëŒ×3ÓoÔÞyË~Ûù.úÝèLî{üûÒ&Ú>||ð)ãÓ§¿›óüìÎçŠ pHYs  šœPIDAT(cøøñã& €ÿÿÿ‡²H#SØ4½ø¹8]E„¶A¢ÍTô¶àÄ&†ÍDˆaSB¬ë±9§%Hr3NÏ $,Â&…¨½´›IEND®B`‚pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css0000644000076500000240000001221112520062551026117 0ustar michaelstaff00000000000000html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; /* 16px */ vertical-align: baseline; background: transparent; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } :focus { outline: 0; } ins { text-decoration: none; } del { text-decoration: line-through; } table { border-collapse: collapse; border-spacing: 0; } sub { vertical-align: sub; font-size: smaller; line-height: normal; } sup { vertical-align: super; font-size: smaller; line-height: normal; } ul, menu, dir { display: block; list-style-type: disc; margin: 1em 0; padding-left: 40px; } ol { display: block; list-style-type: decimal-leading-zero; margin: 1em 0; padding-left: 40px; } li { display: list-item; } ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl { margin-top: 0; margin-bottom: 0; } ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir { list-style-type: circle; } ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir { list-style-type: square; } .hidden { display: none; } p { line-height: 1.5em; } h1 { font-size: 1.75em; line-height: 1.7em; font-family: helvetica, verdana; } h2 { font-size: 1.5em; line-height: 1.7em; font-family: helvetica, verdana; } h3 { font-size: 1.25em; line-height: 1.7em; font-family: helvetica, verdana; } h4 { font-size: 1em; line-height: 1.7em; font-family: helvetica, verdana; } html, body { width: 100%; height: 100%; } body { margin: 0; padding: 0; background-color: #fff; position: relative; font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; } a { color: #1b61d6; text-decoration: none; } a:hover { color: #e88f00; text-decoration: underline; } body h1, body h2, body h3, body h4, body h5, body h6 { font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; font-weight: 400; color: #373839; font-style: normal; } #wrap { min-height: 100%; } #header, #footer { width: 100%; color: #fff; height: 40px; position: absolute; text-align: center; line-height: 40px; overflow: hidden; font-size: 12px; vertical-align: middle; } #header { background: #000; top: 0; font-size: 14px; } #footer { bottom: 0; background: #000 url(footerbg.png) repeat-x 0 top; position: relative; margin-top: -40px; clear: both; } .header, .footer { width: 750px; margin-right: auto; margin-left: auto; } .wrapper { width: 100%; } #top, #top-small, #bottom { width: 100%; } #top { color: #000; height: 230px; background: #fff url(headerbg.png) repeat-x 0 top; position: relative; } #top-small { color: #000; height: 60px; background: #fff url(headerbg.png) repeat-x 0 top; position: relative; } #bottom { color: #222; background-color: #fff; } .top, .top-small, .middle, .bottom { width: 750px; margin-right: auto; margin-left: auto; } .top { padding-top: 40px; } .top-small { padding-top: 10px; } #middle { width: 100%; height: 100px; background: url(middlebg.png) repeat-x; border-top: 2px solid #fff; border-bottom: 2px solid #b2b2b2; } .app-welcome { margin-top: 25px; } .app-name { color: #000; font-weight: 700; } .bottom { padding-top: 50px; } #left { width: 350px; float: left; padding-right: 25px; } #right { width: 350px; float: right; padding-left: 25px; } .align-left { text-align: left; } .align-right { text-align: right; } .align-center { text-align: center; } ul.links { margin: 0; padding: 0; } ul.links li { list-style-type: none; font-size: 14px; } form { border-style: none; } fieldset { border-style: none; } input { color: #222; border: 1px solid #ccc; font-family: sans-serif; font-size: 12px; line-height: 16px; } input[type=text], input[type=password] { width: 205px; } input[type=submit] { background-color: #ddd; font-weight: 700; } /*Opera Fix*/ body:before { content: ""; height: 100%; float: left; width: 0; margin-top: -32767px; } pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.png0000644000076500000240000001560412520062551027353 0ustar michaelstaff00000000000000‰PNG  IHDRÜ27µsKIDATxœí{˜Õ÷?çTU_æ>0ÃÜp„ŠD(ÞˆšhVØ,."»hbÜUß}³kÖ˜¼‰˜ˆ»„}ˆó¬1’Å Äý0sÙ™°íÛ„ilÛÎe·Yá8yIkkûÅÀÚS=ž!|ú‘3ÂM™2%lãLU6(ÇùDm9®p”"³œÌ(¶Efa.¥’6\Åá†äŒp0,d;#lÁ'òH:J H CÒÞÇ02Iôd]ZŸÂWì(gì€4„Ó¥À?Åèé¿ùŽ]Ü|›àù\ ,g„Êr‚*'¢N*<òxéîŽs×-—pVþFîÌA ÙCx)ï<Òy'2 :GåŸÀ ü°€¿ÆU@!tÞlÞ~ ´°3 pp!ð$ðǶ栟‡"•pïsÀ€CÀ†ösÂÈ¡ ׅ턲xiDF¡®&¨ô]ëìŽ3ãÒÑÌ›y>Öá5Ì»Zò‹W È )"y~z[‚äQá#¢Ð»Ž°Š[ü¨è£^#ð°| !¸ øw7ÿ9àR kí(÷¼B šv¬ 8þŒ–rÛ2Ø §NÇq~#|*±Ÿ¬ãQ)w¨«-áþ;'aÒI¼[ð÷×·°iW˜?n1´A'ÈàyÑÿ ýÜîG?«œyñrJ8”Êê¼H?iDâ´PÐäŸîºˆª2I¬³ åHò’n>ÎKÊ8Ö¦!ídÜ Met‘èã$`#°8­l!pšdÃÝýÕÀ®“Õé§ «€ï¢Uô:©Ÿãî6]ú :d®:êêòø¦'¿”)$†ÔI ‰”Ú¢“‘ØÚÜù7ã˜òÙâm º@YÄ¢ÔU:üãM],Ó41M Ë2±,ËMX@ ‘‚ Ë:Yk8³-"ø/à{n¾˜y2:û”¢X€¶±Þ8µCäÔ†ð—x:ãæ}RÈ'á¢QÅ5Ó«¸ùújâ](倊!”ä‹J®šàÐôyÁò5‚Ád[Bÿç 3‘Ü÷õ§ð ÃýÀKh½8Ï-@pÜÔ—Zc‘T±¼7³ ù ã$£$Unj"Ú©BK\´ûúè[ºý¨´~*j´$Ú Ä2œ[áÖ‰¢m¥Þl2ÿõô瞘À(´—±­9Ä8VUdAÎ$œ‡„ä1MÌDÞ hi¤%Q€` €”cÎ.æ¾[ë0T åDAÅ@Ùhs( *;–Ï-W+.9/€4B„BAB¡¡Pˆp(D(&‡ ‡CÉ”&‘ƒçÓt¸yÏN OëÑ^̪^Ο¼ãÖ½ßW~6Úíý.0ÈC«µ€ßS|uÇ»åÝ:ÞCKáK{éÿónß«Ð/:àhßïÝck/øÎ©E«…ï»ã[öØÞÜK?ån ©d‚Üâö¹ÁmÿOÀ:·}ÅiJºœI¸®®.By…zU—I)¤“ôåµ$Ï’üÃWë(+5ˆEõ‹U @Ù(Bèû.‰c0wÏ<ô\˜Ö0 é“jÚn“R‚ÀuܤØö K7€2ôD8èÝ´£åFwÿ:àçYΟ&Àƒ¾r‰&cMØ»ù¾ã–»À"·ØY£¿®D»Ò_Îи--§cÐ/‘(Žo½à¶µxh@K¿n·Î…À³èøØO3ôp¯³íAÌ„°{î-¾²f÷?ç¶ÿS´$>íCÂ4$á~ÁûÉ%¤&‡ô•Åm˜?³Œ có‰wÛ>w•a¢dGJÀ ¦$5e‚¯]àg¯0M‘ôˆ¦lÜr¡ çÄAõ7héZ"xx¸=碥Fú€ÂèI ðZzˆ¢'tM´Ùh/>FK2ÐoûçÑRu'šð!’®ø à_ÑRèXÚÚÝ­‰žÌÀ{€•nÙW€oºc]„&b-ðÀ¢oý_ÿÏísðŠ;?Iu9a~H’loßGKi&ôCÀh•ô´CNm8SJòóóQ(—\®Äq&š|‚X¦_Ϭ«†aÇHPàDAuC¼:÷ãÄÚô>1Šna0¹\°iT9O¯-¦¤P«–yyyäå… ‡B˜–…ÿc×h,“Ù1 db®DOÆ{Ýý?¯ùŽo~̦¢Õ¾ô7û…Àùnþy’“’vŽ7¡ß¾Œ~ã§ã?2”Å€_£¥ãOÑiÚæôóÛÂh"ÍJ«ó-´:øUà³nÙͤÆ¢_,¢íÇ™À’ cê £ÉZ5þpÔwü5´:º ¸èÛÎ rªR†AAA>Ji/¥!µÄѤ8JRQä֙ŧ 'v køa°£œã`·c·´ â¸î}´àÐmÀ_6ÀËkjùŸwBäA¡`‚‚JKK1b•” î滑Æ¡WIA«rõÀ ´ªÚ9qI—´‡¥è FOÂtÂ} ­;wÐsù‘ßV‰¡%J&²õ…U$%ÞzÎoý:ÃqÐÒùw¬o+2ÔY†–Œ…èɉâ«hõÖF‡Žf¨sMîœ,Õ:Qä”p¦iR\\Œã(M0‰K:‰eš!‰Åº˜÷ùc«Dìû (×ß ´ú®ï$†–!.Ù”Û†°?øë=ÌyìZ"6í´··ÓÜÜLãæF‚ eeeÔÔTŸ,)7½) -ÅîG;ÒñÚ›Xü5ÚéáÅŸBhÂv4fhÛǤª«½¡ý‚¨GK›*’o²>Îým–òÝè—I)Z•Íä´øØ&\_«sÒ"éØÙ‚vdûîXŠz©sJ[ÂY&%%%8¶:îf¹*^KK ï°™[¯ÙM}É¢‘ˆJmL9¨x’pB¸«T\òuÇ¡®|y;w.«G!É€mÛ477³¿¹™x|À„óOª½h±­‚BÛ\¯oõÒÆqô*”ïç‡B¡©kQTT49‰Œ(**ZÞÚÚ I 1}úôð[o½%”R˜¦¹?‹¹7,#„eYÅãñ»Ð^njĒRJÛ¶¥Âoù¯³=ý]$í®löW”¤·6Û8³aI’~HïAñn7vÈéJ+`QZZ‚m;˜¦‰aìÙ³‡õë×óç¶ñÅ©qæ^! €ö8_…a$q¤ vâ K!=Ò è´áÚ±ðw—·ð“Õ•„ƒ*ÕQC2‹FûíBVJy±"ãöÛo/xê©§„ã8äåå½ßÞÞþ÷ÍÍÍp8l{±+ íy3}Ép“ ˜Ë–-;úµ¯}­;‹îCO,9~üøÙo¿ý¶QPPptݺu…hÇJâJ}ôÑ’3f„ººº¨«««B;g2EΜ9órÇqn¡P¨«®®îƒúúú=ÕÕÕÇÚÛÛ+V¬¸!‹'NœØÌQJż¶¾ûÝï6,X°€ë®»nüÊ•+g ÉG«²öO~ò“a÷ÜsiÛ6åååE---gùöOÜ[Ì›ÛòÉ ø–u¹a˜+ ”*GÛAôà ¦% ýÀ-/Ýwß}…¿üå/ó#‘£F Ì®¬¬ôŒÂô‡îßO™LóçÏ<òÈ–ÆÆÆñ;wîœÚÔÔô?Ѧ¦¦K.¸à‚??ÞB«bÞùÂ4Ͱ·HÀ0 m¦;oŒGyäÜ—^zižã8Œ3æ£'Ÿ|rÅå—_ÞŠûzÚ¼y³õ«_ýêºX, ‡Ã^ˆÁ›üƈ#ª½ÆjkkkÐö—w\râĉE–eY¶msî¹çŽF{L=Ò*Àž3g<ð@éáÇ©ªª*ß·oßL·N7ÐýôÓO}å+_1lÛ¦¸¸¸àرcUî±ØÅ_l¾ûî»Þµy÷!NK%ä˜p¡`ˆa¥ÃXµj+_y…ÖcdžI~ždñ½6UÝЭ0Œ6Ž·Ei9;÷±«¥‚ãñQXã(¯¾€Q]€yI€[7òûèÞûGÂ]pV~+£J5 ó `€aÀÿr7óžj ù¸‰e$%›”Û¶©®ªªFÇÃ|2²TÚ˲‚ÂñBÏ18ƒ9kÖ¬w.\8þøñãÅK—.]__ôСC#¤”ö¼yóÞuÛõ·-ðM,¥Å†M†É¶lÙ²KÇ!G–.]úÜ´iÓ"$ #Ž;–ˆþ;Žã¸Ç„r'!ylý¹~”Ti$mÛöëåÞêïå#Ca†!B¡P½J$ÑFCCC¡GÚººº³Ð×(`¯X±Âœ0aB<‰PPP0¹±±qFMMM­¢v¢UÚ. »¾¾~øÖ­[½0ÌiÏ)á"‘O>ù$o®[ç®›4±Å?ÞjpNíqÞú}ˆÍE4·Uc„ÏeDÍxªÎ=ŸË¯:›êš* óSÕþ‹'}ø[:£°¿ùû÷î¢i÷G¼½kÑ)ÛY¸›1åðÀuøÖ‹µØŽp=¤Ú;ª”ðTR…ž$ý ˜ Û¶mw¢cÛ¶·ìi oWçî»ïþð‰'žh>räHåo~ó›q›6m:¨”ÕÕÕ»n¿ýömô\=!DZ=õ, áľ}ûÌŒ6lXË´iÓ§µc´´´Xñx<‰G ¹m½:@@‚éäWJ©LäOyùH)½c¸õ£¾±Û¶ƒi×ãõkÔÕÕ‰ššš=åuË—/¿æ›ßüfIò'^cÆŒ¹rëÖ­ùeeeE<MÌn·Ž§æ:îõæ 9#\Ie%ï½ÿñxÓÔÝÚŽƒ)b<óëc¬Þ0‰Ë®šÅ¤K§ñűc>¬ ßm‡pÎÈ ÎYS?ÜJ¤#ÆÖ­ÛøÓ{ï²|í*>n\K@Dé6ò0¤V-…”H¥PN<}` ”R‰ Õ›„骫«™4iÒV­Zõ¥ÆÆÆqÛ·oï˜:uêz˲¼ñeퟤtM!\ ˆI)€¶¶¶Â––9bÄïÅ gÑ¢E—z„s_)dqI¦Ü>3Îñ% )QJ ¡p'ç¶íÒ»f¯Ž1sæÌ7/rG.^¼xæœ9sÖÖÖúíDV¬X1bíÚµó¼>ƒÁ`:Fè‘-%¹¶j‚€ 2 sF¸cÍÇœ²ò"ýC¬Ž“X¼W»óÙ¾¿‰ ü˜Â¢§(/+㬳΢¡¡††FMMM eeeX–•Ò®mÛ=z„ææìرƒmÛ¶±uë6öíû˜Ã‡ÐÖÞN4'f—0%–¡5Fá~­ ¤ÂQŽ7ÉNáuûí·¿³f͚Ϸµµ•ƒÁ¶¯ýëëIÚBéý'ÊôïÙYY™ª««k:|øpukkkùœ9s¾´lÙ²ÿ.//·×¯__ðï|çêµk×^#„PJ)F ’݃\÷¤]õ6‹´MW¯ÝωS…[&üª+É ¾÷\â .üà /¼°¾©©iòþýûÏ4iÒ?Í;÷…/|á ÛºººÔsÏ=7îå—_¾9‹ ·E"‘Ññx\’$˜§*§ýâñ4¡”b°H7(„Súï„Ð+ Š€â+VŒ¹çÞÿkÚ¶üM€’pPûö£±(‡¦å@ lü3+W¾Œ”’P(Dqq1UU•ÔÖÖRWW‡eYìÚ½›÷îåÀ´¶§;ÚR`¦e%<¡†!±,3é™ô–’I‰£Ç„sǶmÛ°m[2p•€Ù³güàƒnܼyód€úúú÷/»ì²#dVsE<·Çé«çÁ|iÞ¼yãÛÛÛ‡½ñÆ_<ï¼ó&‡Ãá¶£GVÄb±ð¤I“VïÙ³ç쌎D"y¤®»»;q_âñ8î1ÿ˜„mÛŽ7–X,&HµqÇ(¼ûå!eâ»m˜nÞõxu ž}öÙŸÍš5+oß¾}çµ´´4,Y²ä[?þx»ã8†mÛ!!Dlîܹß[·nÝ‘Hdt4õIŽo›ž÷ۜ괖pJ© z`¡›ŠÜF{÷ À>|øp@H©?PH,,Ö;©.{76—táC{G[š¶Ò¸y Ê•Ò00Mýí›aš¾u’Þâe\{M¦Í#œr{`„ …BÝ•••[#‘HiUUÕ>!᫦¦fÏæÍ›'K)ã·ÜrËëøÔ¥ôþóòòºªªª¶tvvTWWïÎÖÿÌ™3÷?öØc>üðÃ7îÝ»÷3‘H¤¬½½½xøðữ¾úêÕÏ>ûìÚ+®¸b¶ã8”””"Õ¶r†1bD ÊËË»ÇS$\QQQguuõ–ŽŽŽ‚ÊÊÊýn”±H)©ªªÚnšfgUU•÷)M¢üüüÎÊÊÊ-]]]ù•••“êåT€š|ôèÑ‘#GŽÜ°k×®GûÓ ô/õÚk¯ kjj*ª¨¨è¸æškƒv>Šh4*ÂápÒzýd:–^Ç0 2Ž¥:*JÛ¶q¯ÇëK‘Û·o7V­Z5¼µµÕ;vì¡n¸á š@ÑX,fwtt8¦iÆòóóÝ·ÄHÆýêeBuM øŸtd•p®Zh‘T Ð’,ä–ëoc4¡l´Qêíû€¨­­mµLëx4Ë—=ˆÖ“@é_ϳ¨õP„Éã25ï'\*Ù¼2½oÑh„â’â ýœè‚‰¬0nºé¦/=zt¤+Ý^ìϘN´ÿk¯½¶åÚk¯=HÚä5 ‚x?Ÿ ŽÊp<= ã'œ¨sÎ9ǹãŽ;"$½Ëžªk[–/..N'U6©Ö#ä3XHN)å-T  UAO%´H®ðts…[!± *‘2®¡¡Á.--iÚßÜ\•bOùåÿb@¥ÌGL™~®¿<-Ÿ$o²=Ó09~¼•˦Oû€Ìމ\@òú믿výúõÐÐа桇úà'×È6É{#\‚tô´ËÒ¥V62§ª¤Ó%šG*oëý>‡§§§_¤G8oëOôüxyãÊ+¯|mÙ²§§ç…ót°8Eí“Y÷¥ï«Dfòô8?퓟Ôvô:ή®Nº»ºwÝ{ï½ïqŠÖÞ½øâ‹åßþö·oÚ´iÓu€(,,ÜõøãÿœT;êLB5“ž¤ó“ÆŸÒ‰–t§DºARjy¤ð_eûŽ¥í MJ?áü«4R¤››â?þñ÷ÒÊ•oµ·µMËÏ×q¶LÒÍS“v˜ÌRž‰¬ú‡aµÓD¦J7Oõt¸hçÎL™2uY]]]¹—&béÒ¥UwÝu×â®®®r€¼¼¼= ,øþ•W^yøŒçtA&ÂyÛLR.pžzÙ›'ÒOºœBø$œ§z‹j yʤJú =É'Ÿyæ™ò{î¹gq8™Ÿ_€B%m-™Ý.“=ˆ—J ÏÉ"S—FL©LISÓ€6mÚ´„S#IÄ®]»¬qãÆý¼³³³fäÈ‘¯/Z´èg³gÏ>“É'N¸tâe’nqúV7=$I™$ÉÖŸ”N¶L¶›Ÿtþ­\¸páÈY´è!Ë4JJK°L+%4 ÓHÒ“\ÉV{ÔK'«ŸhRÒÞÖFSS–e=¿aÆ%………§äMçÝ“ùóçO.--íX²d‰÷·ÇÏ45²¯ëM'ôT ³IºtÂõ©fæ‚p‚äO°ù¥œ‘a¿¿Ž’Þ _}õÕ‚o|ãó[ZZf†B¡a÷ÃÔžÒ)Tž=—‰l’¤C¯ÂhkoçСCD"‘M&LøÅ믿¾zîé‰Â»/gÑ<ôvÝ*-ß›¥/ÒeS7’r°C$œ'á¼”‰\þ2ßçžþ9è•h™>WK–,±lÙ²©üŒmÛ%êáËûuA¢ghÑqœx Ø___¿áG?úÑúóÏ?ßûíÂ3u’ÿoA:á¼mÔËÞV—du¢äJÂyΓtÉ•M}Ìd³ù½’™H–-ÈžéÛ±lèÍHÿmˆhÿ{)<àm{#œ—O'WºÝ–SÂyq¸¾.‚´ã‰•æô­J¦çéGùÎ<ôóçûCºlªf¦ör?ἕÜÞ@=ˆ u¼ûk³ nýÁÉ ]ºÔÊ{Ëiü̓iWú<Òù ç»B´!r áD‘-DàÏŸñ2.§È´–Ò?tÕ1lô²MÏÓò! úç¹ì¯3[꫟AA& çå='ˆ_ÒõF¶L$êXC¤B6œH¨ SY ØŸþN*?ççüpNgŸ³}‡98°M8L2QH("i…ÊÚ4©»~|»gzf'íjvV»ª·^­éé®®ªîéíþÔ·¾õ-…eÄsÍ5×Ä»ºô´X“·H}®vÔ03ŒaÐ8Üõ;Õ1¥Q ”:f0¯*Ôv0ëL&ù|&“ÙýðÃî:Z,‹Åb±¨á®€ep\sÍ5ñLÆ™éjo¥6ꔺ˜¦E)eדÀƒR*‰1{ æicxÈdÜ?x^r‹ñ‹Åb±X† +ðFW]uÕ—èÕà]‹Qg+­æ*¥0Æ`Œîê*”R(¥0€qÝÃF™Ê¨ß×üìw¿»oãp×Ïb±X,Ëé…î#„s®»®i\Æ»ÁxÜœ¥§Àó¼a®Ùéˆxç¹cÌK(õË„›üÖc¿ýmçp×Íb±X,Ëéî§8«W¯ŽhÝx)Žú¸Vêu(ñY·Öõá!ðB2Æx 6»˜¯¶6œøþÝw?Ö5ÌU³X,‹Å2Êq†»–Ò¬^½z‚ŽÄ?­õJ©å@t¸ëdÉ¢”R ®N¥âsæÍ]°ãå—·íîJY,‹Åb½X‹û)ÊåW]{™ç¹Ÿ×J_ ÖÂ~*£”Æo†/Ä£|ã¾ûîKw,‹Åb±Œ>¬ÅýdõåW¾¯i­—yžgEû)Ž1­u›1fUÊõÚçÌšñøÎ;SÃ]/‹Åb±X,£ +ÜO-Ôe—_y‡VúËÚÑSìÀÓ‘ƒB2¦Ñ磜ö)“ç?ùÊ+Ûû†»^‹Åb±XFV¸ŸB\ºúÊÛ´V_QJµ+ÚGÆF)¥Î‹D¼‰³gÍ|dçΉᮖÅb±X,–Ñî§«/¿ò=Zó”cŒí#ƒAiçlƒš6cÚ’‡;;·Zñn±X,‹å¤±Âý`Õå—¿ÞxêkJéñÖŸ}´`PJŸ‰“Ò»vìøÝpׯb±X,ËÈÇ ÷aæÂÕWž©1wi­çZÑ>ºP œ5{Îìõ»vìØ2Üõ±X,‹Å2²±Â}¹æškân&óeí8WXÑ>:QJÇ ,;{ÖC;wî<<Üõ±X,‹Å2r±Â}™2}æÛµŠ|Òã` ì2Êc ZéIž¡cL[ë}Hc±X,‹Å2¬p&.½ôÊÀÿUZMµÖöÑü¾j~C,þRgç·»>‹Åb±XF&V¸jÆœ9wjÍ[l™Ó¥TÔY3§ÿº³³³w¸ëc±X,‹eäa…û0pÁ«kG}k­í§:Pú@ç®k‡».‹Åb±XFz¸+p:â8úv0sŒgýÚO—Åx­u#F½ó /œˆÅb±X,ˉ wN7Î;ïÒ9F¹×+ ÖÚ~:áyƘ3ŽÜ3Üõ±X,‹Å2²°÷:ãÄôÐs‡Ûl—ú/¾Õ=¦áº .¸ ‹Åb±X,–`-îuäœsΉ‚¹\iÝàyvPêéˆg<<¸(v¦/w},‹Åb±Œ¬p¯#±ÖÖY&mÎ6žÁJ=M1 `q$9+Ü-‹Åb± ë*SO2™ù`¦ZßöÑRªä‚Œw.òÍb±X,‹¥*¬Å½~hãéåJ«˜±n2#¥ªÐÚUõ¦˜×N9çœÆ}O?mcº[,‹Åb© +ÜëÄ9çœÓf±RϺɜҔç5ùåŒÁÀ¢¹ öAg-²´X,‹Å2ú±Â½N8ŽÓh0óŒ„îêŒZª²ˆW`0¿Ï@ÊõD¸Mg>V¸[,‹¥z•Ý,ƒxf–QˆîuÂÇc‘ ãÍiìÕìyiâÑÚ ­¨…P¯w™J)0F5ª’Åb±XF?qàÓÀ" Sd¿ú€ï¿`Þ¯ó¸þ§D€µÀ[€v`2I˜ Ü4Ow°\˱½N脊ÇmÃèÓÒâîy†hD3kj;^éAk(¥"Œ{-OF|ŸìïgŒÁ£=ÏL:©Œ,‹År:Öç•IÓˆö ÷‹€w#Öüqˆ`?‚ˆó€ÃÀç€Và àI`°øß~ßÁ ÷!ÇF•©™hFL#Æ ûD@ñôö¦¸ìâ¥üí'°pZо¤óùûUè_­ËVEþ ×u•­•blÿ;Åb±X,–¢ Q!MH "ïnÞ ¼„ö?ó·ýÐÊw)0iHœÊ#9ˆr-ÄZÜëD4ÑFeN·PJ)zúR,œ7‘¿ï ¦6üžO¿µ—5Fw/4DG¼ê¯ËÀ­åµ½æƒ±Öçc£”ñâ5«Ô©ƒÆ„–fÿ³Õ_oð—ÒíÆCü½ÀqàUàpØOñ®a‹ÅbLC\;3ºÇUzUã_Œ]þŽ"ÏãuÀ3þþK{ÛnÄ"¾¿~>2'ÉäA–k V¸×cÔh’íÕØD2Ø–>üîULÔBú•$ËôqûU­|õšT"N}ÜcŠQùÂ2|W £âv˜\̦ûßÇcýÏ&äeñ?µ¿”z¹xþâ"Ö>D´ï6 /’uˆ˜ŒUÉb±Œ¦ â5À,ä9sqù69Ñi©ž8ò¬Vô7®8À&äº_ <,~…üV¸×+ÜëÈHWiýh…ôžgȸ.·ß²’+VÍ'ÓÛƒç¼T”[¯H²}_#¿|TÑÚ8tu†…w¬FÈô˜pzE¾÷Ì(àMH7jóäÝŒ4¦+€‹ü^à!àgÈ‹ùÕ!(Ûb±œÚLA|ª¯,Ø>X\¼ Ti© bHy¸xòZ[ ¼~ëuZa}ÜëÉ)àk^/¿p¥}‰4¯¿d!7ßxn2çfC&1;nH°t–!xÅL)^ç¢õ¦túRªºTÞÅòLÉÅ–¼óÄü¥^Äëþ»+Ï¿!7;Ð×b9}ˆwÒ_´‡™ü-"ä-µÁAzAŸ&ïvÛ±Ööºa…{ÝH`ð0fx–*áÁ¥ ½})Î:c Ÿúèe´D!“L ÞH&`zGš?gŠŽvH¥ â9RMþô_ ?]±4¡´yé•Ô¹ßB°0 z•B)åï—ë‹7*\¶‡sà8Ò=ûMà׈EÞb±Œ~Î>TEºËç‚•µA#Ñfžzñ¾qOr†±^§V¸2Dö_{ì`èKdhiˆòÇﻘÉ›I§’(<0AhXJ“LDX>?Í7¸¸.d2¾¨.q…u*›.ï¼*,’ªÌR¹NZëŠi¤î³Œ¥†Dù¿´ ou,Ës)2Ƚ X‰u ¹^1òÅxÓ} ° 2öÈøûFcÀ…S{3ב¡ˆ(Óßï¼ú2NæØRxžÁó<Þú¦³8ï¬idzXîÏ€Ñ~yIi®½(Åã¹ï)C¤Qöôk2iDÖ7—$_‡S©ý¼07É£xcÅøÿ—lÌTÓÆñ{‚ÿ­|¯9mHø²iÀg_L‹Å2ú˜<€´“A™¢ºŒ6’À/w˜½¡í¯_žÿ,þ ñÿ<[ךž¦Xá^O©ÔÊZ¾KäY•µ|€õ©”§RоdŠË/šÇm´/Õ‡çy!˜àx%"±²Çã†;Þ˜b×[_æF¨8¦H]_w¥Šïƒ*¹¿|™¥÷—>°_(È~ô£ƒLæqèBb§—ip%ÄŠÖŠ„w›ÈÀ{p»Ÿ×Hw®Åb]ô m6”ì@è¾XdûVà#¡ï_-Øÿ§œnoµa ÷:‘œ"j´ª(&¡ãªu_HÈÄjy%´Vôö¦9cÁD>ó‘ פI%RAe±îúî2ûûV$ó¦»üÙÛ2|ükQNôb‘œx/W~©}•Ï©øõ)%èË)úbe•®—|c†Õ9¼Îüyf/ò &òH“ÿBÕH÷lb=Ÿœt‹Ÿ‡ˆùjPÈ ©ÈL6l¤Å2ºH¤˜g±Õ+Úë„îu¤˜»EwJ¡OÂÆä—WÓßU$X/Öû›+¥H&3Œçãï?—É“šHu'AûÖåÀÊL•jÊ„\ç”G²OqöB¾þþÇÏí¦ýz;„ZB$|7áíácŒÉ}/8çR×£’0?ÃÂîjðŒw: —Úü÷Ò ÷Ò=» ívÕGù2ˆê‡(Ûb±œúüx ¸¨BºÀ½XQiEØÁ©õ¤H0’“ ¹X.Ÿbyi¥Kî×JݯPÙ}á%||&í’q=>𶜿b ™Þ¤¸Çxžø·>ãùϼ¡£"â=M&i¸ñ’7^ ½ q§ —_x~ŽÒhÊH“· 0Uáº䥵ӯ Gk­ÑJe— 7­šÐä-¹¡pP8h%Ÿ*(³ðÚ>&÷“1àbaûðFànªëúŽŸB&f±X,£‡ƒÀ_P~‡n$äsu©‘ÅR'¬p&*Em f׬tü@"­”KS*oŒÉ-Åê $’.—ž;ƒ¯šI¥ð<L ÿè/›Œ ¬ðâæn”ñH{Š˜V¼÷ê ¦+™ü(1ýb½” SM„—ÜR,ÒLÁ¿øVJçÊ G“ñ×µ–%»ˆ¿eÐàIà½Àÿ¡ºÁfKK½Åb]<|qÅ;‚¸Ã$£ÀSHœ÷ïr:™H,§ÖU¦^$À89÷‹b®aÂn(åŒÿwv¿$ª~ß—H³lñD>sÇy4Ç • ´TN çLû®o€Ñ„Ý̕ր"•2Lé0Üy³Ç_~'ÂñC,šßn´ämÔºð<2ø´ûUä×/ûl)EvlRJgËö<‰°cŒ¡„›½¥2Kº |œòÏ1î¿B–Y,–ÑÃOwºyHÏš:]À¡a¬—Å2dXá^WLö3'ªË„¤|”“ʾÝʰè Êêó.Ü–L¹Œimàß³œéÓ›Hu§D˜~íá:d¨Ê&±Z‡2 ŽQN:\p¦Ë{Ö8|é?žHÞÔÙQ«ë\í¹ôObòEy òV(PA#!eÇ2\$fû<àÍÒ.®Ã w‹e4r ]øôpWÄb©ÖU¦Žä{äœ> ·«"ûú;Š”ö‡Ïù™ûSéþÇÙ–u)²=ŒÄk‡÷Ü|ç/ŸHª' x~eƒŠø“.eÝf‚™XµÔËàûËhäVÔ`4Æ7¥¸ñ¢4×®„¾¤øŽcü¥œÜ1h±Â‡|] ýñå¼Âî1¾‡zÖ¯]ƒ’|%oGòÒ¹¼ƒ‰–ÂKá€B—Yü<ý¼‚ßÔößž4ÝÀ(ïç bu_Mu¶X,‹ÅrÊb-îu$ì“s…éŸN…þï·ï¤¬Ì…å”4·—È+U%™ryÛ ¹åºyd Œçf9*M†`=ˆ#ªÕ®B1rüI™|ÿ2ˆD ¹Áãð±k7šÊœÅël ¾÷?§ð)«Ð9—¬?ð—ÉsÛQá¯è¢å„\¤‚ØBÆP*$¥e@üø7Äu¦KéÀ¶!¯‘Åb±X,C„îu#A0{°…ó‚æa(çÂRé»êç…HGUê 7ŽPB¥(¬b v»{3,š;–w¼e±ˆ!ðÐYŸö°XŸQÐuÍŒ\‡)¨“lO»0¾ÍðÁë]¶îuèê…x,—.ïS0& ÿ#”6×,!ÚÚOt wò¾ãl ûßk•/äà ­<<¿C^ Àr2ü'ðAd*îRt3±ÂÝb±X,#+܇œu·: zU~Ô…_Šì¯lu®à÷­©´Ç¸1 ÜñŽ%tŒ“î ÏkQ*ŽúW™¬…BŠØÏE!iP$“pÆxç•ÿï×"’#¡»¶”¥½ÔIî/tv ê×#Q&¯jz8Ä•¦_2Ëɳx¸¬LšÄþÁºÔÈb±X,–!À ÷:ˆZcrQPªî%¾©B³zx›*bËWR4äÆÑ/¿Âº¨À_ñ®7/àÒ×ú~íO{6«‚A©aK|Ð@ØÚNÖZžmÌ„º<™¤âÆ‹]vîpÏZˆFB5ÍZØs¶óP»¨È9…NÈÿPy×4\­°;S~š~ü‚4á²åtZñîKm8Š <-'ÜA\edÀ…Åb±X,#+ÜëDˆdµ¬ hŠ˜ÊýµBx“b2?ýõhQ”Î;¯ΛÂF„¡;áò–+gqËu3É$Ò¾?{vDlá®(ˆÿîùb8‚2:\P¿:øUE¡H»šxÔðÁë ûFxv47–䯄s,s] …{‘ߣŸp/Ù3Q覓ÿ]+…1^nÀ±µ¾×Š$þ­íXá^kÚ€9H(¾qH‹¼™õöÅ–Z)Àä·l%7à8 Gâyö#wZ€ÀT`¼ÿ]=ȹîvSݼe âb6ù}›ýí=È5î^RCPöh@#×mòû‘er¿îö’›1Úb)‹îuED¬vræðBw‹@ær”ý¡õ57œO^~…ŽÞÁÖâ:9›°_£@)ziÎ^2ŽÛß2›ˆ2¤\7dåœÀC"ݘ|!o‚m"ÜÊÅwT&Ü=*׈…Z)He ½Þ·þúǺ!í=ŠŸ²Ê¥)lÏ”s)Õ{Q˜¦Ê<ƒßW: ÂMZN–jDZ#å›K³€³(/€¢ˆP©Wè¹82­{ ¥g‹""jÕ+z "¶ ó ºÂ6/WÈc)p5°˜ø?7ÿ} ¾‘¤±¸88˜ŒUhD®KŒ`àœw0O°™„çidҮ݃¬Ç`˜ã×7°l„‰;*äÑ œ \ \èçÙŒœ{ðäK½HƒåYÄ ìNþ\Û•H$¦ó‘FC3òû+ûàà7Ôw ÉL`9ů3ȵ~©_53-× …üM¬.@ê8ùŽ“Ó]idð[Ð[ <<y‹¥(V¸×‰8É›0Hû‘Eòr1#4ôwù(eÏϯ=Љÿb;Ãù¤S†ö–8yÛ|&uÄIõ¦é7*3‹ µ&Â0Uv]a Ux»’%¢]‘?@V‘JÁÒ9†w__þ…Âõ  &³R¥Ï-lU/ÚK¡ wCjlå‰õÂtyç婵Î^3ãyYWKM¨Å‹yð¯ˆ-dzÀõÔÇBödöÇñeÒ(àKH„j¸øP™ýÿf³˜èž¼ ¸ NÅÞ!ÍÈ5Ì > X\ƒˆö¹ˆXuÊäÓZ_ˆ§$°¸ø6°uu(W_¦ôùÿòkd5—FÄs åÃ6·#–Ü%À[€—€ïßB⛄&à*¿ìóÈYö«)ûMÈX“Ÿw! §¡f rËñ ä|Ž}uˆ# ¶w ×qÒ¸¬†yˆÈÿ°¸ø&Ò£a±äa…û0 xà¹ï¡=%Ü. ˜5¸ÿäMù‘RBéµÊêcÝ23®°0/Ï3(m¸yÍL–-Cº/«]!ŸFl#”ÎOƒBár1äÃç›s2¾ˆ[°‘0‘¯;žÝá¡õ2¸T²*g9_ÎP'Ô¦(7¨U[è^ÓßÝ&´O4äŽw¬X"ªy–õQ>”Ï3ˆø½¶B>ËeÀÃUÕlð(Äâ9½Bº£ˆµ³ZëvÃ¥h*±}%ðUàµU”1PKû"亿±æW#Ô+¡+ñ2y#ðÏÀZ!ׇܥD[«¿¿Ð½dðYàfD”ro.Gziþ‚êÝ•"!UßNΪ>¢~Ÿ^ürO%šò÷1 î\ÃàcHƒ¶cy÷kð^4ÈônX,€îu#™í;¹+òq§¼•\)•5l‹ø.ÜALøÜvSDˆëP>Z+L­H¥\®»l*o½~:™dσ¼€‡…âÜ„…z˜@Ü#JÛøe«®r'ï[ã¥- rãYQdÒâ"óþkáh¯ÃK†Æ¸œ[~ýK\ÓbÖõ¢»_C©H>ÉKÿu•©1íU¤é¡¼+I/ðÄbZî…ߌÌÄúbÑ*Z€7T‘îà¹äÛëRâ8†ˆ¢°ø~b‰Ÿ2€rªa1ð~àFĽ`(Y | ¹¦ŸEzN†‚.D”—î þ¾°p¿½ójT‡ñ÷ Äm©ïEDûü•}!byÿà+HCf(¨fxÿP‡ˆ#O ÷W-9 é%Zü9°½Æù[F(V¸×‹8]«PhÇw›(ŒôB¡°TäŒó*d”»n@RQ6…ÖKä£Ã¯Á±b´·ÏãŒym¼ëMÓˆG©„ñ³ ? MpR²^èçžÅC&\R`™4ä“·.£p³>î’Wîd“?Þõzøûÿp8Þ±Hp¯ ¬ûù¦—ì‘u óH^Y÷¨à\mT™ZCºì+qÊ–àGwƒeÒ]ƒXŸwUQî`Y\\!þ› Ä주p“ßLý#D„M@Õòzà£Ô÷]t5âztCã:ÓGy×­Frç«€·".ck\¹ˆpNÿUd¿|ø;*»‡ ”vÄâþšÑù´kBÎíä»iÕ’(p+0qÁ98DåXFå|ç,5ÆÃ`ð²®ÙTC‹ÒÁ¢ý%¼M¬åÚ_—ƒ*û©•Ê?ÆÑÙ|t(_í„Öµ–cu.G+Ò76ÆûnžÍ„ñqR‰‚÷P Ô!_ßTî7P”?ƒªò…¸XÓ5íïÑ~ÃÁ -‘кïB£‚t¥’ÉKf)n]¥¥¤´ãŸÖ8y‹“[YtDg×Ç?6X‚<'o½09® ¯Hñ¼Âùˆn7T7ŽÐRc¨lír‘]I@ì~^E™Õˆê“e ¥ÝV:Ëÿ@„Q‚ò7_œÜ»aC'Úgðbädš¿ç"¢¶šß@ F¥ûìßÀЈö€YÀß#">Œ|ø"µí ˆ%ú†!Ê8|ø8C'ÚÃ\ŽüŽÇFð9í±÷a@œ îY =þݹM…Vä"2ûùcæÐz¸Œ"Vdc@+w¼a+–´és}—–{GÞë2ô%ÏâÒA9JçüÜ Î7üÝøV÷l}³ >5é”áuËa×a‡ûŸ†˜“;ŸR1é gV ׯŸ›‹É?6›¯êß;dh0¾Êž‡¢>–š2ÈXŽ.ª³Ž{ˆû㔟‰5‚XÝÊЄákD¬Ñ•XËÀ#y¤(/ÜüýmÀ'ñ7T¼€D~¹®Lš^DÜF¢mD~Ï>䯘ŒˆÓiTßÈX|øSj+†ª½¾K16±B~.âæÒè{{)r?‚œk×­ˆz%ñ 9ÿêpD›jiA¬Ò/!ƒ.GÍÀß ƒ^jüìFƤô Ïr]ÇRþyò›mÂZ|N{¬p¯É$FGDü*…ÖN^T™¢~îaÑWÌwºˆKFéÈ(ýdVp äìKz\qa×®êÀM{¾{GÈ&åZÉÖ¥´ˆwå¢ *šuÏ),_ˆ*óÖKᕃŠ-{ñþçž=ß‚khB' ³îBábJ¸Ïôû-rAÙƒÁÁ…3²¢š\Æ<«ãkÞLåaTéâyàQÊ‹Ia½X_e¾á"$¢L9º€Ÿ0ð •„eâ~ô6ÄMf0T+ðÒH”•kÉoN'küoÿýÀ!Dð$é_ÿ/{6b|Ò+R‰÷#á"Xe}«!IåÆÜdÄÒ¾ Äþ>äüÄç°¿},¹s\M.Ž}%Þ ýO¤·á‹”Ò ¼~ ñ«¢'1Ý/E VšË1ïfäÏ¡þ7âÞU-.r=ïAB­îAîã¢Áš‘hJ¯E&‘{ÅTihÖ3´¥åÄ ÷zâ¿jÄ墿_¹¿Á—¥aËp¾P,&γ‚1ä…ž‹oÄÒݯ(ÕOàö%]Îjåí×O!¢錗«C1¡®ÈùŸ‡lnx¢©@¤‡Ý`ŸÛC‘räú¨lViWÑÒ7¯R|í¿4=IñwÏ»fAzÿøÂk×½°N½e"Ø„¿ço–HB&¤k'`ª3‘(•ØHõ.IÄêþ:Ê»ªL®`h„ûeTvaØ <4ˆ¼3”ov;ˆÕúC”à‘(<ë‘ØáI?ßvÄ2^­ËH¼í~žÿ…éøÖÕBÜœö#½w#ÂøŠ ÇÅñþÒ0¨Ê ÔàN$ìe!i°ü‡¾Ô=û]$|à'!]‰Fdêï?A„b! þïÆëá"i‚²—ŒD>©&Л–T‘öTæíÈüÕ`ëý‘ó.w/oòÓ| Åù$¤d¡E¿Ëiîu#Ž Þ—Êà8N¿8îiSn›1~˜ðþÐ1yQdò ç-žç¾ß¹¿MkE*mho‹óî7MaRGŒt_&Ôp𠕨?øT“×Þï5]ä½m4Ç·¼‡-íás ¹È9„\WŒRþÌ« ”G2©X:Þ|‘æ‡ÿ#þÿŽ"/Nzî܃ÿó¯eéž r=-ðá(?¹|ƒ1ºNÄÁó{ <ãáXå~²hà*GÃèCÉ@¬}Dâ2—ãFàëä\jÁÄšZ©þn¤Û} ³X‡q ø%öïB¢ïü 7XÌ‚tEUÃ.¤2ñH&ؾ IDATþ'ç~dÆÄíˆeùfÊ7“ÏAÜž¾we†IQ>ÚÐ<äž-ü}{€BBVVjdD~ÿ'——RÙp1Ò˜)Ö“t‰Ý•ï©£ˆùH#àÏ©¯¼i =ÆÈµ/@¢ïT#ž»þ…Ò  bBæx ¹¶ÌаŒP¬p¯I0"Dµ?ÀÑ/_<‚„| X˜Ã‰| Ý;²~Ù*°i üh‹9K}n¿á-Wv°|q3©„KÎ $0‡ý×ýÊ„© Y÷p÷àðª’ö@øT©u9$å*.=vrx|“GC,×ó­cž•»xÏEá¾|W¦KL1×¥ÜáE÷9ÚÁhWœŒÁúÊœ47ï©"ÝFÄ‚>¶"⤒p_ \2ˆü˱‚Ê‘C5.3ÌB)Öpø9"HÖVÈc † 1ðÿ­{ׂ¹ˆ³-ˆEøÇÔf¼B¥A³Å,ÔG‘(,ÿRáØB^þ çq}…´íˆ Q!‡ëô­” 9q:i8Tjh^‚4’*Ý;§"1ÄÒ^MÈÌcÀ_"Q§;xú826`'ÒøªÁá–ˆîu$ø v´Æ‰8㔥ü²+…< §)6@³XxCô$ ׯjçÚKÇâe¼")Mþy+aaø°÷·)ß¿]7€v0ALJcÀ¸~Z1Š*W¢®dõrè]è…ªúÖpOi"ŽæÍçkŽðâ+Š–¥ŽÁ‰HD9Ôàe; DDëý(u .}y×™Báî_S椹±LŽ«.ƒXR3 ü‘P}åʇXÝk)¢×PÙgÿ!ªŸ\g  …i€ÿ‡X»k\žap¿O5ìB¬ø?¥¼ÛÓ Ä¾©Få¤w§±®þû Ë:Šø\Ϧò¸ˆbei´ †$Òp˜ƒôZ”c*2.d$ ÷+°•èþð•û]dpðW)îÞd9 ±Â½Ž?ÊŠˆÉ2ƒS ¬áEÅvÁ¾âô|«2…ÇøŸÉ¤aÁÌ8o¸|,G‘._!$ÒûÙŠ øÀbX»=ŒIC¦“8‚!&n¼„¬{iÀÅÆx.ö­Óžˆþ@ê*oþœF)’hÚšàª9Üý›&N$4-M'B,%LjÇâòo ÓFú]» êÉ6ò¯}±k¾Î…ÛGãyâ cŒ±Â}phD(ÿ-•géÎìÀÃ? þוfR½%{YN˜ Èl©åºc\ÄMb°ÛT YŒ!VÙZ‹öz ò¼ºLšéÈÌ¥µî.‹RóÄõèdØ…4dÿ•E:ù ð³“,ûby¿é½(…ƒÜÛ È=8RhF& «4æÄ ûû5.ÿçÈýùåçk¡Xá^gŒ18‡ˆ Í^šo9û¬çìØÅEwq—BÇ—™ZC*cÚ4o]3qcâ¤.yÖóìá`çQ ØMZ¸Ic¼Æë7ñúÀK`L÷È>¼#{}c¹KÖ0¥²sÂb?+šÐá“Úw©ñ­øAµ ôuiΜò*o=<óóÇpœP½ý뢵&‰à8±XŒææfš›šhnn¢¹¹…ÖÖ¢Ñ(±XŒx<ê­ñÆóü߯¼>|͵Öx‘‚ –0 é’¿ƒêbOw#~»ƒžFDÿ5”Có³©p_€„ ,ÇzDˆ—uá?„…ì:‰2‡“È€×rÂ]#.‘O6#÷h-ÊüOäïãœ*ÓoGÜ0jQöïßìJQˆæ"½ICÕÃ2œ‹ ­Ä}Àÿ¡ö>üby b$°œæXá^OŒ¸eh¥ˆDÒ‰&SiÒ¥ÿöD9áVw%H¬ñjCižª¼€ôP”‹[¾±¯Käýµ›½õ0"«î?Eâ«×‚4Ò蹑ò‘ˆ&"½S#E¸+äœ*M²Ô‡ŒO¨6 Ò@9|yþUнoåXá^'€ãûRƒSµ ¢½äÒåù¨‡|¹Uh)ßöÂÏ¢þÙ!7õ¾\öÚf®¾¸ 7í[¯M¯=ün×`\ŒÛ ™pbÜ^p»1^JºñKdõ«¦Ÿ!Ç(ü^TDD¸/ÜsñÛ ÂOªp¯„ç÷ÄBo 0n˜¬å=M&­ˆE ñæcì>4†'¶â½Äœ©Ê]±ÞÞ^zzz0æUœH„X4J<§µµ•¶¶6ÆËøñãioo§¹© ”4ÀLv0l~ŠXÜ# ž18§nçlÄ·y±'7©K˜bIŸŠø/Cb/CÂ>„ï#–Ì“l¸q'('ÜAüÒïâäDØ8à ÒA„ûPLúTŒß#bp¤„ŠœS&Í,¤ÑT/áþ2ƒ÷k/†‡¸§rŒõƒˆ·–f ±X í8x¾KMð»y¾{Íi6ÓùÀ7At'–ÄJÕ‡´òÑÞ†ˆŽvÄÊUMŒè0qoù$µ Ñè!×|™n¼ ‘nì“îg!Qjʱ¡‰_ŒðkF®‹L˜*ûçO roG-y€Ú¸W…Ù†X³+ ÷ç¨}/ÊAÄ‚_N¸kdvÛ‘Â%ÈdYåH nJåB€Ö‚cH¬w+ÜOs¬p´£‰D£/=7©P¾è rùv}É“üYט’~ï¾͸†¶VÍ_×θö™¾HNBº“9™Ãx™CàöŠˆÏºÅ˜‰C‘`ªÅü<ƒé¢Ñø¹ã¯÷Ï;OŸg ,õ€—ï½½çÎ=Á×EøÄ÷â¤]ˆ Pö3ۖɸd2½ôöörøða6mÞL,câĉLž<‰)“'3~ÂÚZ[¥‡EëþÑkN49Q>”ô „¿F^rµb"t./“F#!Àà}\/£üྠbý®×ÑNFþ„9ÝTÙHe—ˆZ4kíOß…÷JaLŸ§öD{‘†C%&!Úc$Äs¿”ʳÓv÷ס. qû+¹|YF9V¸×‹GÜ("Ú!‰àùÂ=4l²âàST1Wš°Ð/-ܳŸZsÅÊfæÍhÄM$Å’ž>„I€ÌqŒ×+á³29,Ö–6ÈÊHv¾…=;骂à9®‚2*óˆe?4pÕ„ÜlÂû² å`€dÂð¦×æ‘“ùå:Es…Gp5¢:lA/ÜžL$èììd×ÎÄ7v,Ó¦OcÚÔ©L:•ˆ#rÆór½–Z°qùµw#éBÂ=®¢ü3ób`1ƒ ÓØŽøÉ—ã"öêÅ‹Ô.AæØfÜäp»ÁëCŒ?þ`Ò¡Âx!á^ä0*ö±˜˜G|ܳá!}z…?à_ÌCÚshˆ¥ùë7â•ÃXû2´5T¡˜h/e!/p˜/ìÕÀqȤÓìß¿Ÿýû÷óBôñ‹;–î®.´Ö§ÓàÔ¡ä8ªð_º¸æ !ÙÞƒóRŒnA&(R9÷#ˆå¿¸H8Ì¡îþ ¹ÖS¥ÙH¸¼iþöfrâÝ -S«È·ÜÀÊZ²q«5idD%jÙ#f?Ùð`%i«°ÿTa†¿Tâ·C]‘‘߮ҽlÅXá^7 Ž#"ÚÓºLt˜Ð!a_2}‘¯ýüµvȸivï=ĘØAV/îÆé:B*‘D~„g1BÑŽXÇî*h#„?)¾-õÜ">ìRàÂí.}Iͤ¶>wCïùV G{ 1jrn7Ù(;Åcµ›2פðØB‘¯ù \Ïåð‘Ã9z×ue†\oh¯õ(Æ V§û‰Ncð1Í«eÒ^N¸+Äj>‰G—¸šòþÕ.ð+êg¥L"ƒ‰Ob1_ŒŒ—X ž‚ˆôFê'¸kEŠ¡ ;éRÝßÂPùë@å„yc…ý§ ã‘Þr$€gëP——¡iðYFV¸×tªãDˆF£"àBñÐó––p‡ÉV ¶I—õ§ö<úúúØ»o7[¶î$Õû*ÿô'šÉ­è ,Éu~~šœ«L¾› ¡Jÿmý,òÙï¿m㇊ ¬ö8(åÐÝå¼ùG¸óÚ8þ³"Aç„)bm/¡©‹¥ËÛV$¯ Á¥)Ðu½ò!n,¥8Œ¸ÄÜDVYËÉ…š(?F¬îåÜ)– ƒÇþcùŽõ)'¦62ôQ+¸œ\ÈÉZCë^‹Œ/x •d4 É=ø‡&ïá"÷rN‰16IÔp1i–£“Ú0.‡¡¾Ï<Ë)ˆîuÆ@Öâ®´&/hc%ážu“Qy"W„nnR!×u9zô(;vì`÷îÝìÙ÷*==)þþcí\x–"8EHšÐgÈÂLTšg‘/vlÈBo‚è6ÊŸ‡‹Q©d”[Î;ÈK{&óíGZÃó|òŠÌ³¾÷æý¶»¶á¼CÁ H¸½rS»A¬î¿ úÙI—"Ó”ã—Ô/L!ÈïUÏòÂDËú›×£)ÃTËÀ ‚§lšáF!=g• }o_!§ÀËÛ2œXá^GŒñ0ÆC;ÑX'kq(1ð4›]å¥Fi‘þN$B:bÇÎlÛ¶={öÐu¢ c ½ ޾™w^¯ðÒæÔôÐ0ŸøyC¾'äÚ-Û‚°™&ˆó—´çÐ÷øä5GXßÙÁ3» ÍñPxI“›Õ4ãÆ˜²OÉÜïv•ñ×C ,0ÙüÀȽpÊ¿»jƯBü‘'!>®ˆrpÁ2Hä.àbí݇¸Ä¼Ê©1Ez/5æõ”~v*dë4ª›dÆA„~¹ˆ;ˆ›L­\+ªiPx Ï5_ |i†òO›?Ê!b´ˆJ Z‰½ÔoN‹°Â½þˆhM4ÅU:$ÆC¾í û¨(ñ:ç;â8­4½}}ìØ¶Í›·°{÷n‰ÆGÓݯYå3ïj ª É4#çÑZÔ&Ï]†œŸ|v£JLå€JùŸ.½ ×|œÏ\ßÀßmåX/Ä"AY*ßÕÅø£‚ –÷=”¾X/F~”þÐy^òà(âÖ²i†M±°5+ˆ5g4¤ÓÏY+¼^tÛÒ¥K×nÚ´iƒçyËËd½páÂ…«7oÞüDèЧ o»ë®»&}ò“Ÿ\ÓÕU:Túœ9s6ýêW¿š´lÙ²Õä®›*X7oÃûÃ×YΧ>õ©e_ùÊW¢©Té÷X,ûþ÷¿ÃM7ÝöÕ¯æÉaЬ—Úf\×ÅquðàAõ¾÷½ï‚{î¹ç&×uOÚ®dÂãÿMJ'ž1:ô›÷Ckí\~ùåSxàYHCÒ -nè3X/v^L:5²ÿ~e*¸Ã544¨¾¾>¥²“SÔ„ÓëÉ2t8TaèC3VÁb)‰îuÆ“…3)X*Ìcž•]d`$A;½½½lÙ¾• 6²{÷n2™L6¯ˆVô%=ƶhþêýÌœÉFŽh/Fø•äÛi 7åN0làæôôŹxÁ!þäÊñ«8®§ˆè.Òë1Ð}ò½Ô—Ó†°{L „*⋬°ø,©¥>ƒu‡\8ÀÂÏðRl[Ñå©§žŠ¾á oè|ðÁË ÷ȸqãÞìØ±±íííAC¤Ø¯=º0•J•Œ¹­”ò®»îº½Ë–-[@mD™3f̘±*~ªx¹ª©©©±&™tGïÚµ+~ûí·_ó裮r]w@¾ÏÑh4ÙÐÐÐÛØØØÝÖÖÖÝÒÒÒÝÚÚÚÓÒÒÒ×ÐЊÅb™††† Àý÷ß¿rÿþý³Jå‹Å¢W_}õJľÒ9Šøl£óŸÿùŸÇ¿ÿýïo;zôhɃÛÚÚZ¾ð…/,ÆÌh—]2¡Ï ¹F­)Xï÷ùÅ/~1ò™Ï|Fgߥ±b³<šÊñÛAqÛð`–ºb…{1þ?ÇqˆÄb(­ ÞéY?u¥roy¥òÜ-´Ö8Ž&™L²mûv6lØÈ¶íÛH&“8ÚɦSÒð<ÃßÒÄê©^3²E{@¾gJŽ`W#穌";@5χÞÁ3ŠÞ>‡[Ï;Äó“øéºÍŽÉ^ó~e!¿ªz_9yåöGa¬¢hhhˆõõõµ“ßa1¬ >£þêH‘õbß Å¹ö?s-¶|ªÝ–·½¹¹Ù¹ñÆ·=ú裉t:]òžqãÆ%O=õÔØ+¯¼òp©4étÚ<òÈ# ’ÉdÉpmmmG®¾úê]ÈyÔB ¤¿Ç0°ôEïÚµ+zÛm·]÷Øc]ZíA Ý'NÜ?}úô=Ë–-ë\ºté¡E‹;묳NtttdÈÍÞ ïì³Ïž[N¸¤¯DpϦ×ÍÍÍãÇ)ûnmiii>ãŒ3ÎfSüú÷mÐ Ä|XÜg ¾{ï~÷»Í÷¾÷½/¼ðBÙÊŸy晓֯_?³ ÏÂÆHvŸRªÚ{o´ˆXEuúhÔ<È-#+Üëˆ1bqDbÑÕßÍ¢¸Õ]FH&Sl|é%ž~úöìÙK2™@k-dBægÏ@WŸÇ‡ojå7Eq3§ŽL¬*˜Ê`ayfBÂXhr“4‰qÌ%Jc$Íç®;ÄÁ®É<¸IÑêOi10kz¾?(¿Ô€Vå߯;u~“BŒ1‰üˆèðzð=Ä.¸à‚ùk×®-ë°xñâ™ÀÈ·œ‡{x)Õ4+çvQì{à7_k¼[o½µóË_þòÎ;v” yâĉñßùÎwμòÊ+.‘D½ð ­Ï>ûìÒr…-\¸pëš5k3zQ€êîîV7ÝtÓëÖ­»¨šZ[[¬Zµê‰n¸aãe—]öê¼yóúûÐ,…¿»J&“*NU­¼ûÎTò‘ñyžÉåþйqèx<®§L™2±’p¿è¢‹–#3 ‡{ Ý‚²ßÉNŸBzaŠ.7ÜpÃÔ_ÿú×Ý„8õ¯¡:ßõ(£Ã$fAXá^Oü׊ÖÑHÔ\šß3¬u¾hŒD"¸—Ý»wóä“kÙ¼e }½}(­ÅÊ䢧ÏcÙ¼¹%BÔä0RJÎÉÑ sˆ7óÍE\ÿšhÝ*2®2Ü{ÑCD¼èD:ÂÄÖn>yõ Öïiçx|ös—óù½ç…è,Q±‚ã_÷ eJzO Œw’Â%xᇗ8ò²‰Ù^ŠùIæ™={ö´§žzªì‹:7 ñˉî@ÄœêxãÇ×+W®|±³³s¾ëº%Ÿ¡k×®=óàÁƒutt÷Ýwß´ƒN+u|$I]qÅ/2:…ú«¿ú«åO?ýôÊJ #‘Húì³Ï~êÎ;ï|ø¦›nÚG¾Ïþ©>ûf-(×@ ãc´çU5³[`Qvè•û^ØH »ê8W\qÅÌûî»/šN—Ö¼Ó¦Më¸ûî»W­X±â(Ò& Éжà™Ð¯\ÇqT§:Ø#ê¢Å´02B[ZFV¸ŽÖDcQ_C ô}Ý#‘Z+öîÝǺuëxö¹çéêêB)…ã”6¥2í­š¿|SÇ;$û†Ø°d¯B"Ýå(ðg;íIŽÍй§—›LÓ0sŒm„¦¸îyñÏóç&ʺþP°2Â3ÓêÐc< ZÓÝ×ÀYÓŽñÑ×5ñ·÷Å1€õ„d‹Î³Â–Q _ÎZo Iç½u|^Ì$ü=<¥{Œþ<^°­œEh°‚0ô«W‘XT}á`¾‘Œwûí·o¸ÿþû/9räȤR‰^yå•?úÑf~ìc{™"ç~ÿý÷ŸYn°ä„ öèCÚÄ(´¶ÿö·¿÷ƒüà ÏóÊZÁzn¹å–_ßu×]ÇãA¨Qw=†ƒB!æò“A"\Ub"òŒ=g¶ŒR¬p¯ b ž÷hT&úS*'Û ¾¨Féíëcà xøá‡yå•W€ÜàÕbÖM¥Àõ ©´áη5qÅù’}µ²ëæŠV€ÖàD5ÚQàÈ“„cÝ.{gؾ³Ûúxig’—¶w³igо>hk„¥Ó`ñ4‡ESaé4—y“aB+´6@Ä‘r\Ò!1œLUçã½ëÓ]…t‚qð ô¥o?ïU^Ø=•_>ïÐÚ@ÖW^®;ä½[ µVÐ0(Óe/æ Æ¦L™2öÀ¾= é/¶Ã¢»Pœ~ÛÙÚ|–ÚVŒÑ"¤ë¹òÊ+.Y²dóc=VR¸§Óé¦x`‘/Üèõë×7mÚ´iQ¹BÎ?ÿüçg̘‘bôýNæë_ÿú²ýû÷Ï,—(‰¤n½õÖ_ûÛß~,8®u³ C•nBáô”ÿM#ÈŒ¹P¤`Á‚s* ÷3fLúüç?2'AÒXöƒAšÜxLø»RêHç1y>wW‘¶VX ÿiŽîuÅ€ñp"¢‘¨¸]hñ:PÈŒ§(غe+¿{ðA¶lÝJ2‘$qrÇ—Àó «×ã=×·ð‘[cdR^6ßZTZ)¢1 æ›×ÓGNxtîKòòîÏoîá™=ìÚ—áб ‡Žy44gÆôÙ,9c!7¾ã¦M›Æ¶mÛøÃžá¡í;øáÓ¯é;Âäv‡q-Š…“aùlÍkfxÌè2­Ý£­Qô¶ñ ™´ëP:§¯œ@ò›äÖq㟻î{NtðÔ%e^‡°ÛKx,jIËz."P0y–ça* æÏ¬ U%ü"+õ9ÜIF;Ð7ÝtÓ3ëÖ­;7•J5MdŒZ·nݲ§žzê±óÎ;¯‹œ¥Xýë_?ãØ±cãKÐÒÒrüï|gyG呉Z¿~}óo~ó›K*¤3çž{îSV´Ÿ¶T2>¨j\‚ZZZšZ[[çѶѼÁ·ôìkÞûÞ÷NûÖ·¾åùc~J1Ò¤IS8PrzÑT¦Ò2Š±Â½^$ÀDLvpj4ÍF# f<=~ì8>ø O<ù$ÇŽóÝbtQ {!]½KçÄøÄí 48Šd‚ú“uw‘:¥AÐ6о—]ûRlîLñâ¶^ž{©‡-;ì>àâgÌØ©,9ã Ö,=ƒåË^Â…ó™=kMMM466úâÕ¥··®®.^~y[¶nå…õxqÃFžÜ¼…‡¶Æd’4:)fUÌŸª9k¶fé4܉Ú24Ǥ¾®Q£ðüÅå«_'8%Ÿ!І§ô¥£LjKñÙ«»ù㟵²÷¸¡)\šü¨ŸzaH‹{ñþã[Üý‰°‚xÑ#‘ª,lALmF—øro¹å–_ýêWwlß¾ýŒR‰Ž;6áG?úѼóÎ;ïyrçï­[·n^&“‰–:nÑ¢E›.»ì²W©ý½QµUÔ³…ÕöwSßùÎwœ8q¢d£`̘1‡?üá?ê­É5¨&^ºëº'{ÎFk]Íß¡rjy}‡óo2p‰+‹çy'{o@U;Øu]—ÒaMÃ.ˆa"Ë—/×Ñh4YªaîÓzÝu×½ç›ßüæ#È`ß$bÍO >òÉж td8´§€ûÈX¡á˜˜Ìr a…{ þRµ–™S '*‚rËæ-Üsï}lܸÏóÐþ ÕJO&$S†±mŸ{O 3&i’=¦¬h÷ ¾âîâÈÀQE|_\—®>xõhŠ=¯$Øðr‚ /w³öù.¶ìréM(š[ÇÐÑ1™Ž‰“yÓê3Y~ÖkXqöÙÌš5‹±cÇÇK–¯µCKK ---L™2…‹.º€ÞÞ^Ž9¶íÛyöÙçxþ… lÚ´‰ßï}•_®?Dª·‹ö¸Ë’i^;ßaÑÅâiÓÆ)Æ5§iŠËùày¸žGÊázžQ~ïFØp’oDQÊГŠpÞìc|âŠ(úó\£È;¤P˜—šx©Øvå[é• ¿­Gº ­Jú†±‘|žE™4iRzõêÕÏ–î©Tªá÷¿ÿýbàü?ÿçž{®içÎ%ÝDÇÉ\zé¥/¶·¥ã$ IDAT·1¼kÉ@ò’ûóÉ'Ÿœïy^Ùîþyóæm½í¶ÛöRß&UA;Ü £Á¸«Ô¬ìêÆÅæ•=ØòÛ˜+,¯T>fÖ¬YÇz*wÖ¯_¿ xÕ…y†Ï3pÑ D|qßÉûN~O@6òoΕc9M±Â½žø¾Žvˆøî2ÝÝÝ<þÄÜÿß÷³oÿ>”ªtÕ3Ï2®áConáÚ‹#¤ËŒƒ7Fôc4"¾éDxŠDÒãða—½ûzÙ¹'Ŧí=¼´£·ö²}w†¤i¢£c3¦-áš7Ìeñ¢…,\¸€3Ï\ʼysin®MÏ]SSMMMLŸ>Õ«Vpôè¶mÝÎK›6±eÛ66oÞJgç.¾ÿÜ~Žüî­‘.N‰°xJ„ÅÓOÕLç1uœaL£¢1.ÁPÒCÚ5¸žƒA¡‚–K1ÈÄXsæq~·)Æ=ëZâPhqÏ}WßÛ?Mà¥òäú¨Ò±§%7ß|óæ_þò—Ê Rݵk×Ìgžy¦yÅŠÝ€zâ‰'&”s“éèèxeÍš5;‡¢¾mH Geß¾}Ë¥ÑZ»Ë—/ßA E¥ëº¤R)û¾óÎ{ ÕþæÕŒò,tC,u|¶WìÜsÏ=ÖÖÖväĉÊ•ñòË/ÏݰaC|éÒ¥=ôoŒ„¿GÍÕR°? ·Y87¤Œ1ÉéÓ§_¶gÏžj^¸öå2б²º‘À˜žç‹Einjfë¶-üè‡?aý‹/’N§qœ¹çLuœèñxÇu-ü¯·7àºà¹&;€S)ÐŽ%ÅÑà€IuÙ²'Ás›ºÙ¸µ‡M;’ìÚçrôD†=M­,Y²‚K¯YÎ/fñâ…Ìœ9“ñãÇ1fLæ©)cÇŽãÜóÆqîyçJ¥8qâdçÎ]lÞ²M›7óÜó/ò«û·â%ÑÚmšYã=Lv8s¦fét˜>Î0¦Iu<2ž"ãŠX7PJ“r¡!êñ—×u±÷x;ÏîV´Ä+G)½.ß1yñƒŒ1wH,nõ¤Úºôó,…yýë_påÊ•OÝ{ï½×—JtøðáÉ÷Þ{ïô+Vl܇zhN2™l*•þ‚ .xöŠ+®84$5€EvÜ)ôÚµkÛ»ººÚÊ&ÒÚ]ºtéj‰èرc‘d2Yq&ÌXÁ«ã>d÷ª ]ÙU¹¯Pþ™0û³r¥ŠŸk±–KÞþÉ“'«¹sçîØ³gÏ‚rÑŸŽ=:åK_úÒYßþö·å[¬±P¸^¸ÍŸl„FɳÜïÙ³' ¼‘ ƒSµÖjñâÅã7lØ0 ÿüH<Õ^3Ë©îuEþNµ£YûÔZ~òãŸðòŽ~Å`ÕêP ºº ç.iàsïn"7¥ˆ7*p4x‰”áx·Ç«ÓlÞÕǺ]<ób_Ns¢×E;ÍŒ0…¥+–pÁÊóX¾|óçÍ££c---Ùè7§ ±XŒ &0a–,YÂ5׈˜ïêêbÿWÙºu/¼°žuO?ÃKÛwðÔÓG0k{Q&ÍøÆ g΀e³¢,›¡™=Q1±MÓ7D# Qd ¤Ý(“Û]>{Mwü¨‰ã}Ð…<«z¿©ý·çV¬«`0rÞ3{$ Zãy^U"…‘}žåpÖ¬Y³ñÁ|]"‘(:ªçy‘‡~xÁ§>õ©{öì‰oÚ´i%,~ñx¼çª«®Ú‚¼À‡"FyÕ¿A ü½û•}àÀ†T*UÚΧ­­­R¨À ¶mÛÖT©Á⤄{ÝEJåY‘¡òq¯qT™RÇî50Ÿ…ëŶy—\rÉKO<ñĪr³$»®{ôÑGW;vì‰ööö BQοÜþ°ÿ{^Ãõÿñ—¾úê« JÕ#@k­gÏž=˜ƒÊ—>9†ió#dN¸KzÜfçž$/mëá¹Íݼ¸µ—Í»\Ò¦ñãÆÑÞ>Ÿ×œ;%‹qæ™g²dÉb-ZD{û˜ŸiýˆÅbŒ?žñãdzôŒ%¼ñ1~;f×®]³K¥ëèèØó–·¼e7¹n­©‡¸*™_OOãºnÅL‰D`ɨEÙæ¡‡šrüøñ².ä‹¥A ÷jŽ5>'YÖ Ë‚F àÞ¢s/ZT¨ŒB¥_ªì`»û¶·½mû¿þë¿8tèЬr…ìÞ½{Ñ]wÝ5÷ÓŸþô6òÿvKYÚ ëWl §ñî¾ûî+Òét+Uà‹î`6ê ¯`ò½Àš¾.91ŸÒƘ~‘vBÇYa?ŒXá^G\Ï •bݺ§ÑZûV~ï{žAo\å°kï þó›w$Ù¾»—{ú8|ššÛyÍk–³ìÂ…Üô®E,Y¼ˆyóæ2aÂÆWvðèh¡££ƒŽŽ.ºP¿öõöräèQ<Èö—_fëÖí2vË6~z÷V¼¾ŒoSÌcÁ´(ó'iMëå‚ÙšÿÙæ`tÖŠLógT Ï• )]äòzfH^žõ¤êéÝÙçY3sæÌÔE]ôÌž={JΤºoß¾™÷ÜsÏ”7N8qâDG±4Žã¸«V­úÃøñã]×uã8C)Ü+æ=VYÇ]­uYS©çyzÇŽ%ádËVû÷ïw~ðƒ¬r]·ÚîÓî!Q^–¡´]eÑÙäµ,»Ú¼ î«“)¿ò´©â’Žä’·»Ø!á/‹/îYµjÕã?ÿùÏË ÷d2Ù|×]w½aõêÕw­\¹òÅ-êÅÜhÂi¼ßÕí·ß~aggçåê=ct*•rBÇ÷‹dS°ûƒ;ÁÌÚáýnèÓ\ÿºšÐ¾lyVÔ-V¸×#"å¬ìƒ7ê$z»øéáß~áqäDÐ,\¼”w}àZÎ;÷\,˜ÇĉŒ;–ÆÆ²ƒâO›š˜ÖÔÄ´iÓ8묳èîîæØ±c:|„-[¶±ö©u<úûÇøÑ[ˆ9†ñ-Ç9†ë6¡bMî/¯{h[¿é^Ãn4¹ ˜B¿ýH´é«ÉçY ï¶ÛnÛpï½÷:~üøäb 2™LÃÃ?<§³³³ä Öæææ#7ß|óÀ"ѳ”Õ\¸O˜0¡/–eÒó¿ª3Í?Rù>_ )vLèØR|Z71G‚ucLÄ£ý¥–»Ÿ6X‹{=1¦&s™*ZÛÆÐÜâây®ëâf\v¼¼ƒíÛ¶áyžçÑØØÄøñã˜0aÂÿß޹ǹQ\ùþT·¤Ñ¼5¶gŒß0b &ĹöšÇ5á¹`‚!<Øò!Þûá³ Öönn6$$7χÜÝ% ×$!„…½fI ÄNl60ÄÆ&ÄãÏÃöxÞ3šI­®û‡ÔšR©^Ý#ÍŸ¯?²º«Nªni¤_®†ùóçÃâÅ‹aÑ¢E°páB˜5kÔÕÕA]]ÔÔ˜^³5µp]úúú ¯¯Ï[†ššš ¹¹ššš ½½º»»¡··R)B!ˆemÙPQQ!ÛË e[`YÌŸŠ÷lJŒø.µ’¼öÌwQ™`è 7ÜðÎûï¿ÿ_d) d-ËJ­^½z÷dº¼KÒ9sæ¤fÍšu´µµUz=@GGǼ‡~ø³7n|Se§À~ñÅëxà›zzzf ŽR;›b0¥ñ1)+z×EhO˜í1Ã\<Ï?Ëú/؇ÃäÞ{ïýÏ={öœÓÝÝ=OÓ_ä•W^¹þÊ+¯´^~ùå_Ù™Ÿ×½üpS± ?üáç?úè£wvtt|F}”‚QOŒDÇ*+çÇÈ—‹ì„Câž½ 9ñÞ¯„J)%‘׃Â}¼ ð–,\Á$êµ, X„æüRJfo@”J¥ ··:;»àƒöB:‰Î‡ÃaˆÅbÐÐÐõõõÐÐÐ .„ÓN; æÍ›sæÌ††¨©©H„¿¡Üäcddúúú ££Ž9ÍÍÍðñÇCkk+tvvBWWtuuÁÀÀ¤ˆeA$P( ¡P*+«À¶C`Û6+9'„€g¿M€ð¯‡ziHÈFä@f}~&~H]ÊF/¦*&y¦l”çËÝwß}ðŸþéŸö666žë·íܹsï¸ãŽ?CéÏ“÷í¨4¢”Bö–òEÏ%—\²{×®]A·’O¥RÑM›6]þÌ3Ï||ûí··ù˜Gyä´'žxâÆîîî٦㢔Bæ&›c;ælš€Î†2¹×Åúû'žo­aFûµ%Y¡¬|sQJ½Õ¨LóÎ¥~´ʈA6Z,t¥(£@®»îºc[¶lÙô/ÿò/N§•_Š©Tªâ׿þõW–.]:ïÞ{ïÝüÕ¯~µ‰ñÅŠ[>§ìÛ·/úÀüÅ–-[næïyàN›6m¥4ÔÓÓs–ð`(µ‰D„9f^¸»’:Ó_úDÇ¢³=?n토pGLb¬Þ*¥£9ÔüÇ …ŒP'„€KÓy«—X–Ô"`“PæT’‰œ€Ì]DÝ4¤Ó. $ ñàAøÓG7íB(‚šš¨¬¬„êêj¨¯¯‡E‹Á‚ `Þ¼y0wî\X°`Ìž=*++¥‘äRB)…¾¾>hoo‡––hkkƒÖÖVhii¦¦&èéé¡¡!„ÁÁ8¸Ô…p8 ‘p"‘T×Ô‚m‡ d[`Ùöh‹·ð=Íœ[/ý4ïÕ’oNܾ^b>û#"eþ!ŸjkkÓ^xáÏÃÄx¬X±b×i§6 ¥ŸÜLô›Î½óÎ;÷?õÔS-º•:úûûëׯ_sww÷ ÷ÝwßÈ?§lä.Çï~÷»ºÇ{lù¶mÛ.òukx×uíáááÉó“Ç'Áuüë©}ŸfÓ}Lú2Y)HmgÚ‘üÇܵuëÖ 8p¾I·ûöí»ðßøÆ7n|ó‹_üâ»wß}÷ÇÓ¦Mó–x-Ëo~ó›ØO~ò“O¿ùæ›+=ºÔqœ‚‹ÓÂápÿ—¾ô¥ŸoÙ²å¿Ê„;ÇqØ éL#î~~ÐùäQFâ½h»¢=€Â}œ8Ö×çN¯ªÙû—ìì>[&+gE¾KÝÜ <Ÿm¡—jFs¬fs-ÁM§ÁI§¡`ººº ñàAx÷ÝwÁ²,°m;oýôyóæÁÂ… aÉ’%°páB˜;w.TUUAEE”••e¿t"NÃÈÈ C?´¶¶À¡CM°ÿ~hjj‚#GŽ@ww7ôôô€ã8™ÕY²©A¡p"ᄘQ_™Ë)÷VðaÏSî“&{žˆ r.\9F¶–»à¦LB;`ÎùÔ_ßÜϸ§òq³fÍš?¿üòˇUi1³gÏž¥ÝÝݳEÑüÊÊÊ®d2Y)[“›Rj …`lpãë>J¸¤Û¢ömºbQö— ^ôÈ¿RéÇä"`ï»AâK&8Eû4‹Ñ¿ÿû¿~ݺu3:;;êúœùÖ[o]»k×®Õ?þxãܹs›çÎÛ^WW×W^^îô÷÷GŽ92£¹¹yþÑ£GOÇã³Òé´tÉ·3Ï<ó•»ï¾ûÃÍ›7AfC)µ²«[ù‰Ž ]qu&Â\61ЂÑvsP¸—J© £Ë*…|ðÁ¹Oýä_-þC]˜#À¤\'óY äÄ9g›—.¤œT*ánƒ÷Þ{?Å'™´›†úz˜¿`œzê©0þ|X°`Ìš5+—G_UUÑhl{4}Ôqœœ8ïë냶¶6h?rZ†Ã‡Ccc#´µµAOO¤Ói „€mÛYnA(‚h4 –Ûʤ¸°~"ÄÿªjIÎ{î9÷·»g#8ï¢;ª²ûü„æ°L".™]¹re÷§>õ©ü÷yóæí¿êª«ŽÂøœ꺮ÑÅ©¥|ÝÖ­[÷‡;v|¦¹¹Y»ÚK<mß¾ýŠ7ß|ó¢Y³f5MŸ>ýx8Néïï¯êêêšÙÛÛ;Ëqi Cmmí‘[o½õÅçž{îjY¤ŸRjŒŒ„alÇk¼$è…SàþuF¼Ž»*²ëa´Ô£é ¸¾”Ñ_…{ã7Ù¿ÿ³ßûÞ÷¾Çu÷ð ©Tªº½½ýÜöööswîÜÉöaež9sæ;ßÿþ÷_¨©©I…B¡”¦?oª:Ç¢sbU× Ó îc$1*€JæQ}Ž@¢]tÑì}úË¡~RMtâ\VžMxô ´öl´?“ãmMlˆ„#å£ÖÍæÑ»4sQlwo/ttvÂÛo¿ Žã€mÛP[[ Ó¦MƒX,Ó§O‡¹sç¢E‹ ¶¶6³ŽúÁƒpôèÑÜÅ¡===088™*…ÂagóÏ-+“Úb[£é-yË- 4?½ˆ9N‘ –‰káPEÑxOÑ6?‰¢i`jç¸Ã… Ç ¢¢‚\}õÕïïØ±ãÂd2Y¥³/++¼ì²ËÞÎ^”:^ï£~JymÂç>÷¹þûî»ïßî¿ÿþ¹###Fws§¼¥¥åŒ–––3üôU]]Ýñ·û·Ï\sÍ5-/½ôÒj•m___Æø:¸!wfU™¢ç¸Þ͸¯-1ÕM1ºk3!„fSed}™DÛó¶~øá?E£Ñ=öØc·uvvú]í%ox¦†Ó¦MÛóØcýàâ‹/îknn…B¡•ßD"á½Yq­Êmå·ƒ ÌDìƒÄui wC²Ý€*¨Œ0¯€dÄy2ö0d–=Êû©R⺂4S!n’‚À}ÈÄ©¢Nå›BæR¶eØ(ó&x@]œtÜtŽë€ÖÖVpB¡LÎ}(‚T*Éd\×…H¤ "‘p6½¥l+U÷î4J ÄÊ<{¹á¼˜&^‰"ú-Óâc‹XÃۜש‰Æˆ{!îÚµk›~øÃ6·´´h£É ‡n»í¶`tå‰Rc-QTÖ#½nݺßzë­_|ñÅS©TIn6‹ÅÚÖ­[÷ìúõë?:~ü¸Fã*ûx<^žL&!‰”&)0ú:=êíÓvÜ#îܧ‚Feµ©4¿~D:¿Ÿ·}ÿý÷7ÖÔÔüóC=ôß:;;ù‡oêëëÿðÀsíµ×vAV„Û¶íê„{öÂV~âÁ¯gÏn«Îì!²IRDNXážéÍC¯€Œ@¯€ŒH¯‚Q‘žk–}&Ì>»L™HÀ Ôé'•(ªËv˜—b!0ã׆—ùã}8’Øf_Õž?xv¥;‚P8”‹dÓ‚Qúc/ %…J:Ñ&‚6y~$õ…íEb›(ûæ·ù2‘].7žïuœòÂÝq"ºÅu]ö¦6'îœ9s:mÛN9Ž#îµµµGï½÷Þw¡p¢_Jh*•"ª ¥ÔN¥R¥ÝèOúÓmëׯïÚ¸qã•---gÕa,k¿à‚ ~ÿä“O¾vÊ)§$™UWW+oþÔÝÝ]700`•——›.?ÉCÇÕëN§Ku~©ëºÚÕ’É$+B‹Ù·÷÷.%N[àÿ³ÎÄqmÀÀq›É\2î|¿'x׬YstÕªUÿçë_ÿúÞ-[¶\ÞÝݽh¬«£Bh,Ûá…þÛÆ[UUÅ®ÕÕÕiBˆ*Ç’Éd%äŸã BÝ$˜`"âu“#Ä''„pÏþ!U0/z…Ñ<ô0òE9d-ÐÞ^WœÍ¨æu]×Í^ðI)5^yE–JC)'6}¤Ö˜¶ãŧ.o;¯œiËm2v’(µ"åDT&·Þ§dÜŠ4™<[ɘ =[gY@˜÷@ r\ÇX,6P]]}Dö~v'TSSÓ 'HŽ{²gÏžStë=¯\¹ò·+W®ìƒq~ýkjj†c±X["‘¨ÌæÿæA)%‘Hd¨ªª*ãôº}ë[ßÚ{Ë-·ø‡ø‡ó·oß¾¢½½}‰ÉÝN=lÛNÎ;÷à .¸`×í·ß¾ûòË/ïšN§Á¶íÜø/^ܺoß¾vÑšÑétÚ¶m;ÕÑÑjhhPF4UTTT¤êêê:(¥¶mÛ€t:mÇb±ã•••Þù-jŽ{]]]oUUÕ1˲\>m‡RJ\×µb±XJã^QQ‘¨­­=â8N™è½•N§íººº®P(”Оˆ®­­¬®®î „8¢ÏÇqB±X¬;‰8 ?ϲþeÑãQ___ŸúÅ/~±í7ÞøÃÓO?}æo¼±¼µµõÓÉdÒ× #‘HßÌ™3ÿxþùç¿q×]w½ùå—÷‚$ÿ|úôéÇ¢ÑhŒNús㢔†ÊËË»{zzH]]/ÔUk·‹î¨ªŠ¸‹&8ªÉXA9æ·cL3ÃÉ3Óe/õrÒ½ucÕ{T*‡‚ááaXvþ²_oyýõŸÂî(ß·o_u(~§R)kþüùCŸùÌg”) Ÿ ¬ßþö·±ë®»îôööΗUWWùå/ù¿.»ì².ß 9räHx÷îÝ5©TÊ}qRJI8v?ÿùÏ÷ÔÔÔ<ÁرcGõ¦M›Nþýïÿ©Ã‡Ÿ2888-•JU$“É0¥Ô²,+‡G"‘ÈP]]]ǼyóšW¬XñÑu×]×|î¹çä-+˜GSSStïÞ½Õ"ážJ¥¬ÚÚÚÔòåË{ËËËÿMÆãqûwÞ©íïï‰þ.Ç!±X,uþùç÷WVVýüîÝ»·òÀU¡PˆòÇH)%étÎ9çœþ“O>Yuc :;;Ão¿ývLt~2ÇÞÐÐX¶lYÀërmãÖ[nýN"‘¨…ByK#ð_ÞS0±njk÷!šÙaëÖ>ùÑ#H½lb‘«×ü Šô›œ'B$ 8t茌ŒÐÏ/ÿÜÿÛ¼yó/a w0ÿ 9Q> ­¯|å+ñÜsÏýµ*å’K.yþµ×^ûw6<Θ¼n:¶d2 ]]]ßH¾ IDATá={öT{k¢‡™g‘0¦Œêlkœ­JÈS® ¤¬¬,Gâ”Æg°Þó7÷áÕ¶y¹ Ê«°)°clMû`õ¸Ð¿'€=#_]¤:o<º±hë¡ ^—ÛÎGÛ…mEÑýÑÆÎ‚”ã@:íqgΜ٠S_¸# ßúÖ·.9vìØ•Íi§öî]wÝÕùŸ1‚ŒSnR§ ²è¼JЋ¢Ñ²2“‡*_ݤÜįllü1úúCÑ>6&T¸gsÒ dÄ8+Ô+`4=£ùèÞ¬0(çº?T^p›´ñìLÚäê¦M›–,F{»]wu-Zçgz”/ÓH$v²r~IIÑÁŠoÈkÃogl<ÿâHw¾,æÆî-)éC´ëÆ*·Ø'ÊãÖMØ¢4oƒR†tæ&UéSN9¥|è!“òíoû´wÞyç2PLä+**ޝ]»öwY›©œ"… ãû[ŒÏI™@—í›Ö‰¢î®¢Þh—Ù˜´UM4t~Eöº2Ós‡ŒqîÌ Œ,ÈêåYŽÑ†üµÑ½¼tï ^¤›þ!SÆ—Nسõ²m‘Ôçê¦M›–¨‰Å:ZÛÚ2weš°B‚\ f „¶²Üqv_æË$ï\–#¿Ù=娏²s}«¨Lzî8±.›a¤_QÇ¿†Þ±º® CÃÃ@ RV6pÞyçoŸÈ®]»*žx≓ÉdµÊð¬³Îúýƒ>¸ðuGX!éW¼«Ú˜èDv¼°•‰wQ™nÛoA¨ÈF'øuÈÆ+ª×ù@ŠDÉ„;s3#o—(dº÷ðr@EQs—«ãß(&k)²b_4kW n••­Ì=餓óæÌ>üá‡û€R\š†Ê5Uå•Ëöu¹íÒ‹E}ø±aíø¨sª¸ùÇ-ðiy'|Y¡O3?ùÇ!³®?’wŒ…ýyå–E •r >4”ºP«m9ãŒ3ø¼GdêA€~ík_»öرcŸRVVVùÆ7¾ñäßûANtDß~Ŷʯ¬IªHµ¬¡.²S‰kQ=êLÐEÜUþdçC¤Ó„`šÌØ)špÏ^@‚ÑtvùÅä u ™//ïF l¸ÓÛv™}ÖŽpul¹·ÏGØùp*uEáUÑv‘8gíEXçw^ãÖ­[Gœt:šN¥!l3©ú4+ø²­GÓ[x¡H Ûä "u¶ ›Ò’ëƒ0íˆÄ·ÄF%ºórãÎGt\D0¯^7V"ð-*3éŒáÛ*ÒiDýBÀ"  @b8Ô¥pÊÉ'ÿ©¾¾>(ܧ:äú믿ä½÷Þ»RuA*!ĹôÒK_Z³fÍ1Àh;‚ˆËg¡Jœû›DÜE¾uÂ\¶íí‹–TT=D9î )gëÛ× u“s(š˜ %"°pg„º·ÚKd³Í<³B˜}¶œ½‹À¨ð&\™(ê-ÕÀõ§²+8,I¾½*B ëÇ=ÿüóTTTvôõõÍwÝ´ðFLºˆ°jÛÛ7i«ó!²QGáÙù‘¿~MóÛƒ´5õÃþ™ú˯ïï×u! %Ï>ûìýÙ¥ñCn sÇw,ÿÿø[t7[š3gÎ7lØð&àÅÈ"¢T~¢í²r•Xgëe‘l•pæŸM¢¶ª¼zѸDÇ¢«3ó<0Ú^$Œ„;s©—úâ vÙZéÞ›’@~ô;Ï-ŒŠw>ÒͶ÷lMÄ·*z.òÁNt‚Ýø÷ÈëëÒK/íœ^?£±·¯o¾ëRH§Ó`1ë¹—:O]—³.òß ê3Åšþt}™¦Æ¶)N*LýèÒsdǧšœ3°lF†‡¡¯¿\J¡®¶¶é¢‹.jÿ~Èä€ý«¿ú«Ï?ÿüóH$b*ãP(4tà 7ü{öæ@øz#È(üwt1|ÅÎO´]U&Ì¢6"1=ñ®³5™ˆ¨ÎƒÝD¨¯/’E(Ü¡N`4ÍÅìl]® ˆE0påÀ•c+zQYÁm‚îÍ!«WµS vS1ŸëçœOŸó‡æ¦¦Ï§tÔqGÂL »‚ ÍÌ£]æ§@~[’oS'†<›üôÆ–‡8Í…µåÛ@ _O˜ ß–°õÜ8%mXÙ•jDç‰ú󎋎ˆî˯¥ïÙ Î9°ÞÞ^ æÏ›÷ÁòåË»s§"¸×\sÍ^}õÕ›‰DʘBO=õÔmßùÎwvg‹ðË A )Öß…ÎN`ÊÊdúEöìm«Ä¾N4«=¿/Š‚û¯ãdmùñªöóÊewÔEü‘îÙÔ/EÅ»+©·ï=Lfª4ÛŽÍC'PxA)û&aë)³ÏþìC8{")Í6ûÌ׳ý°vü>‹J¸óuîßüÍ×wncÛÁÎãg¦R)ˆ8‡GsÝ…Ñ\FPò6º\nv¿ _m* aNª.Ò'ŠýEÉ•ã{áD@_¯Í‹LPôçÉ‚D" GŽ´“†²²HüÒKW¿UWWçæ:O5ȶmÛªî»ï¾5»wïþK×uùûIÐÐаçG?úÑO³·eÇ×AJK©„;¿¯‹¸ËžME8€úî¤Þç k«Š°ë¢qÊ|ÉŽ'BÙèzFÅ2«´Ø„åáE«è@ԗמßA4¾Îo^E—ùòe¿lÙ²OŸ}öößmÛ¶$í¦íd* áH8¿fŶ©ÂÔ¹/_¡€e› ®Üwc“[UoÒŸ.=I˜b”Ý´m Žwv@|(.u¡®®î£»îºË[?ð&? “ 6|úg?ûÙÚãÇ:èPFõ«_}fõêÕ½€¢AŠMÏO?Â]T.‹j‹¶ƒ wQ?‘uÙDmE>Šõ½T0.Œ¶/âîEU‘e•Ðæ÷E/¾Lè{Ûgk*¸ù¶º1ËŽ u² ƒÈ‡±Ï{î¹ç÷ï¾»ë²þþþSRÉ8"‘ükÛDé)~·µ¶T&¸¾õÂ2ö”â:ÿ´¦í˜õ'jkZoÜ•«à<3Û¶!‘HÀ±£Ç í¤! 'V®\ùúœ9sR€Bn²ã}&º?þøÉ?þñÿ²±±q•ã8U&mÛ¾âŠ+þõ‘Gù°´ÃD?BPe+½*UäY'ØEeºH9_&ÚµS uU½È^´-³aÑé9Ä'£ ‰«Å([/¤|½Hà«„½ŸVdË^Èj:&ÑD…-W ~Ù¤@<Ž<®¼òÊãçœsÎkoüç÷¤Ý4ŒŒŒd„;aD Hn[d£µ•øå#˼r>oœK—Q]´É ö¼|ðَܸ ÆÊÕ³eÞøu~D©1Fç”í#7v¶ždχÇÃàà ̘QÿÁƒnx 0·}Ò³sçÎÊM›6Ííµ×>ûÑG­ŠÇãsLÛBœÏ~ö³?þùç·–rŒò ¡˜¢ÎÔWÐÈ».Ú$êÎnÝçSeDã7©ÙóÇ(k#³CŠ {q*/fÙr‹)7ÞlŽ;0mçïƒ}fÇ%zx>dëÁS-_PxlüñˆöÁ Œ'góÈ#¿~ÓM7×ÞÞ~A"9CÃ!¨ª¬5ÌÁêå_@ÉÖƒ ¾0:,¬ÒFÓ^;¹Øgv@9ž‚6>Æ/ò#<§9A.™ ä¥ÈØÐÓÛÍMMvÓPVVÖwóÍ7=»dɩÀl“rï½÷^õÇ?þqM*•ªñÕgéÒ¥/ìØ±ã9."RH)þNüF’uå~Å»êY%êE"[VçÇFg+*×#¿Œ!B¥”òeþ…‘‰T^ ³6¬ –µ#PøR‹ïSÛ¿h¬²r"/²!Ë—/øò—¿üó'Ÿüñ§’©dõðÐD"(++JižÏ°ñ;†ú@§ |ê£ÿÙza=!ò%Å¢~lçDÖ_P!L¥àãƒaxx€,Y²ä•õë×ïL‘™ XõõõºµÙ YVò¼óÎûÙ¯~õ«ç 󷎯5‚L*]à§Nu={Û²}p—•«|èìùq¨&'ªr?ítuHøå YAËG¥yD6"q.‹jëET¯kÏ— \Ô†ŸD°íT"]w|^ûè£î~õÕ×_ûè£?]ç8i2Ð?ái!…ÂñžsH ·)#™maþº(MbUÐ*O>XÞ8c¯˜(è„4HŽÉï±(s÷©¤^ô@þñúøtww¥Nš9s×£ßùÎ Ñh/H¸W]uÕÛ·o?ǘ4ˆD"+V¬xvëÖ­/—zp‚äúyj"Pev:±®²m{û~nÈ$óË߀ 8™çíE>Tðº¿çJ{Q§É‹éç d:ô;ƒ-{¤z¸ÌCæG´-j'ó#²= Æ÷È#=7cÆô÷(¥L&a`` {GÕL.¸÷œyî‘©µ!v…u£>(ç¸>ù¾ó Š2ú›ÅŒÑâÊ,È?K äólÿ¢íÂ2’7.‹ˆÆï¯Þ²,…lhiiÖÖp] ee‘Þo¼áÿ^tÑE}Þ»ø˜˜‡{ÓM7µOŸ>ýÏ`@MMÍ[n¹åÑ­[·nšcÇ>¦âƒýŽT}‡j¿S>uíLü™ÚðÛü³ªpå Øn›÷%²c÷EíUýˆÚˆöem²Q6‚+Ê o9H êH5»ÍÖS® ðh4oËæ­{oß›U²uì˜øüxÑqðÇÂŽ‹÷Ç׉lùãEõ ŽýÚk¯=ÞÙÙùÝ|ðöõõÇ ®®,+¡O»ª/úTG­umdí3Û£ö:þûS·õ•:cP/;fYõ¶7q± µµ öïߎ“‚P(4tñÅ?ùè£ßÛ“5Ç®)Bmm-œ~úéï·´´\L)Þ¨.÷œ}öÙ›6lذéšk®éLA± ÓÅìǤLT®«¼H‰cþÙT°Ëêek½‹ÊTõ<²ãôûÚàw^ ~!1°H*!*Á:Á®úe}ÈD³ªŽ_e†g'š€ð~d“Ñd@d'¢`Rt÷w´ïß¿ÿ?õ“ŸüÁ“ãq ‹Å ΤÍÉS®^zQeÖÖ5i¤¿ yåʱHÆ`*Ðue–eëºÐ|ø46ÇIeÛβeËžÞ´iÓf@¦"îW\±{Û¶m=©Tªž­°m{pöìÙ;Ö®]û↠>ª®®–}ù!"F¦ ŠõwdêGe§›.²m*ØEe*ñí•óÓ‰€èXT>T¿^x×ÔâA²÷F™EÑlÑë³4vAÚûÙç·Ùcá×yçU´Í‹}Ó2SȺuë–>ýôÓÏ´, ¢Ñ(LŸ>ÊÊÊòY{Ù¶Ê;o[7~‘å¸teõ¹}ϼmÈvÛ¶add„ææfH§Ó`[ÖÈÒ¥K7nß¾}cyy9~PMQ¬E‹}÷øñãË F;êëëÿ¸jÕª-?øÁvÕ××;=F9Á VØtBžŸp˜D¦UâY%æùœwQtÝçëÙ}Õ]Xe¾eõ²1¨ž0]¦H°ÂÀ\´äße5¨0çý©úA=¶ƒ<ƒá¾©IÐ~íÚµ+6¿úê½ñx|>@4…Xm-TUW!$wѪ0 œÙÉ/SD‹Y{‘­È_þòüeÛ˜úÓö§ic46Õ˜ø²ìäÉË»ïëëƒ?ïßÇŽË^›`œ{î¹Oÿê•W~^__ëµOqV¯^}åûï¿ñÉ'Ÿüþ…^¸ëþûïo̾®ø„ ÅC/E?~êüFÝùm•PçŸuÂ]$¦UBÚ±/Õƒm+ó+›ê¸Q´ÍáÔ‰a•°ö#ÄuÂ_ÕFe/{‰t‘ø6ä2;*¨g·ù™¾ òío{áã?úÑïìêº Ù­¬¬„X,g”ˆK‘P¦ªzAÔY&®óê} e¥0fÆDÌ«Žß­î\åìH推CCCÐÜÜ ÍÍÍ022„ˆF£í«V®üç_mÞüZ¶)~PMq:::B###dþüùºœ œRwÖÿ]ÌoËúÖ•‰„¼LÜ«"îì¾LÌ›î›s?íuc‘KÁ6Š÷âÀ w¯L'–u¢@q„¾j_46ð±¯Ú6í*[ÓöB»_ÜT÷Àß¼­©éЗÒét€P8 555‹Å ‰ä.^Í[:R”"x–•©ÚË·i™Î·Ÿú ¶Þ/–eåÎu2™„¶¶68|ø0 @:˲ܺºiïÝxãÚ?þøã{AñC©…{Ð6~¢ðºr“g?x]êŒßˆº‰°õ§:¦¼mîÅA&ÜÌÒStÂ[%úMĹÉd4u2{Þ†·õ¶½èö[¯À÷÷÷[×\sÍ•ï¾ûîÍCÃçxQöH8 µµµPUUåååyË%ÊD¼2"n(nƒâb—ew2/ˆ¨žIgaËò^Tƒã`Åz:†øÐt?ííí¹õÙ)¥‡{/^üÒ÷¿ÿýŸ_vÙe}€ ‚L$¢ïV`TիļnÛ¤L%ÖƒFÝä©2|H¨ËÒtDýðõÂcEÑ^H´-3ñò…³Wç'¯‹Èë"ð¼O¿kÉ£pŸ@XážÛ¹0 o~Ÿ·W }Ó‰n,¢gÞŽ¯“µùà "ÒEeº72;>ºaÆEÏ?ÿüU---_H$'yÂÜ[Ý$Cyy9”••A$H$¡P(A6M?ñöUâZäÇïD@æ;ˆp× {I?®ë‚ëºà¤R0<2###Ç¡¿¿††† ™L‚ãŒ.bYÖÈÌ™3·®X±âÕo~ó›ï/[¶lpýnARâGôñß­A£Id]d/›èÒITv~¢ñ¦‚ÝᆰoÑñä•¡p/žp·AœܳH”—rßÏX@`cR&Ú— ó "Þ¯ÈWú:~ü¸õì³Ïžôì³Ï^xèСUñxüôt:]ÉÞ$ÆÝ죔Êïx÷諒PJsBž­¶m{(‰khhxëâ‹/þÍý÷ßÿáé§Ÿžòê‹2A¤øùŒ6‰º‹êtb]TæW¸›lãì—žh_Ýçë³Cá^dwo[ÙÖ¥ºø‰Èƒm¾LÔ—èXLëARnª‹7âã?ŽüÝßýÝÒ>øàÌŽŽŽ3†††Nv'æ8N•ëºQÙ)Ô¶í˲†lÛî///?RWW÷çÅ‹ïùò—¿üÁwÞy|¢ˆ ‚ " MÚ˜ˆye´YQ®K=Q¥£ðé6~/^•Õ‰|ÉìEãµïÅ‚¸‹"צ‘nÐ {¿ Ϻ2ѾŸh»ªÜ„b„Œ d#ñÛ·o¯Þµk×´¶¶¶ÚžžžÊ¡¡¡òT*ʾ¾ˆBˆ …Ò‘H$‹Åâ K–,é^µjUßYg•€ÑŸ^ñCAdòô³Z—£*²­‹ÈûIQQ]PêçbS?i2¢}ÑØ…{±à…;@¡xæËDuAÄ|PŸ&Ï~ÊDûªr•Ø.vîˆ&ljÈ1úàAAÆ £“RöaZ$uFiçëuÂÞo.bîW°›ŒSÔP¸ ]*ûGË@¶Lµ ÿ‡'Ú–Õ³ðåª?f¶N4vÖÖëCV'«÷Ì,™?G2¿ø ‚|Ò`¿‹¡nb#j£î¢rÙ³·­ò&ý˜ü BVïë5¡”ïcÇ$Z$Êu¢ÝD¼‹ìEÂWõ†QM&X?¼ˆW‰oÞŸÌN'öuoNS!®úcFAO:¦ß{ňÐö&B]dg™×EÛeö¦“]™l¿Ô¿† DÂ]Ý• u€B‘Ì—±¹Âl Œ—«‰hÝD‚µWM$x¿¢ êMĹ â‚ R\tÂÛDlú­u×m넽©¨— x]{L”™Œ¶V¸ë"Ø|½ßè»LxûÁ4F„,Ú¯²åûÕû‰ºcÎ9‚ ‚”“èx1Úðõ*Qob'Ùº_áM"æºr•M`# õ K•‘‰tÕ>€\ÈËf¹¢È>ßÞ/¦¹ï²¾d¤úù(E:Š~AäD¥"p¬"Ô$òΖëD½Jä›îö¥8¿yÚ sÜ‹ƒßu¾UQj™­.²­ºèT–’Ã×ÉÆ¨»èÕô‚W•¨Ø UAäD¡TB¯Xé A½itÜÄÎä?Ñòb—P´^¸ëÒeØ}àõ{Ñ‚¬oÖ—(ÍE&ôAb£³ Sá\LÍ_€ ‚ ' ¢ `1àx w¾Ì϶*ZoM×EóA)%(àÇŠŸˆ{Q/» T–û.Ò*ÑíùQõËd¢a2)懊G©ÊBAÉLQ„£ÿ õ"?û*q.+7í¦ãQÔó‚½8¨V•Ñ•ÉêeÛì¾Làˆß(*ï•«úUWUæÙØ1ZŽ ‚ G)£ô&¢ØD¸óûªh¼iTŸ·Õõå÷<ÛcŽ{qšã®ɲ;Hû‰–³ÑvUSá,šHðu²z•O¿iC‚ ‚ ßÁc‰ÄÀñ¢2•°7ë%Ñ,(ÞÇŽJ¸ËÄ®L¼{¨D´jU]Û^µÂ¨Óœ|¾?AD8FÛAdâ*MÛ5êÎïÈû­3í[5dÑEÜMS`tmDbŸ·—µ0› ˆ„·lb¡zÓªD¶_Ñ^ŠœwAA‚SÌt Qy?i.:•½ ²è|P¿¨wJŒé:î²ra/k#Šxó‘u•æûQ tѱ™ˆlÕ˜Líe`TAA‚Q,ÁXÌ”š ©4A¢á²ö¦i6AA‘>xÂÝ4‚®«W ~U„\U¯»˜Õ$ Çd•LÓjü ;Ï‚ ‚ä# Ëw1lÇš6£Û7Mg1íc‰Þ#ã€éÅ©¼8g1Y_ÕÏR‹ª4“œz•¨– c¿åÅ~CcJ ‚ ‚ø§”Â]էߺ ÑwY™It^%ÒýDâMƆŒ#A.N•ÙùI£‘áGÀÓÖä—ÞW±Áˆ;‚ ‚ø§ÔQb¿AmL…¾i4ݯ`Wµñ;Ù˜"B(¥è£Îª¼pS;Yꊮί­¬?mLý Y*‚ ‚ rJuŸè¼wU[¿bÜÔÖO_¦¾ðFLc„¸ûÉeW!KYÑ­þ¢J‘ÙÊ-'ÉÖÉi/õ›m"~òCA©Šß(p±ú«}HûXÊMü›ˆv?¾‘ÀFÜóÊd¶ Y½[S›REØÇ3ÒQuA) ã-"ý f“v&:ˆMÐ4?6ÊrŒº'¨p}?vA„¼ŸúbµAAdòSÌèúXm‚Fáýú2ñ;Ö‹lQ´‘b wQ¹ßȺ;¿¶*‚Špï‚ òɤâÝ´ßÕ\üŠö “b¤þŠ÷± î¹rU[?é2~ËLëƒÖ•ü‚ RZ&RM ÚÖpöe×ÉwN< ÷àø½sªWL½l%Õ‹"ºSvQæXVZ‘!È ÅÛø†EA©E1#êAÚ•:Õf,ý"ã„q'à?Ú]¬›•êBÒb mŒ#‚ ȉM±ìX¢ò:¿¼”…0âÝSýDÞMËù:Q¹(/ë_„iDÝTã AAJ_ê%"ƒ¶AÆ]Ä=ÏÎÄW‘Ú™´K|¼"è©GAÉÇd[ó=ˆbçÉ›¶S{Œ¶ þLêè¶îMÅŠœ«Pý c¼n¤äoNAA&jyI{?~0?ÁˆRe‚¤ÇÈìx[!/³µ3}ƒŒ‡`Çè:‚ ‚L]ÆCtNô®A}¢`Ÿ$YUÆ« o+²½Ð~}ÊPMŠMUjA™<Œ÷÷x1û+E„¾”~‘€°9îJS_AÇPBûR1YÆ ‚ Hñ™L‚´Ôè›Ö ¶IDAT¼ië»Ìo;&Â]Ú®„öãío²÷‹ ‚ ÈÄ1Q‚³TýŽ[ÊŠõâT¸ ýL@ÛR3™Ç† ‚ ÈÄ1™éXÇV”cCÑ^|r´â=Ïß$ó… ‚ òI¤Xâ¸h"{éŠã" ù@}O“i,‚ ‚Lm&“p-éXP¤/ë8ûb2•ÆŠ ‚ È'—)%tQ˜O.¦Œ bAA¢ƒBúÄæÿpˆ®k@IEND®B`‚pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gif0000644000076500000240000000006112520062551027111 0ustar michaelstaff00000000000000GIF89a‘ÿÿÿÿÿÿ!ù,T;pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/templates/0000755000076500000240000000000012642137501024576 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt0000644000076500000240000000661212520062551027326 0ustar michaelstaff00000000000000 The Pyramid Web Framework
pyramid

Welcome to ${project}, an application generated by
the Pyramid web framework.

pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/tests.py0000644000076500000240000000156012520062551024313 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_it(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'sqla_demo') pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo/views.py0000644000076500000240000000217612520062551024312 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: # Start Sphinx Include one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() # End Sphinx Include except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'sqla_demo'} 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_sqla_demo_db" script to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """ pyramid-1.6/docs/quick_tour/sqla_demo/sqla_demo.sqlite0000644000076500000240000000600012520062551024014 0ustar michaelstaff00000000000000SQLite format 3@ -â) üDDÏktablemodelsmodelsCREATE TABLE models ( id INTEGER NOT NULL, name TEXT, value INTEGER, PRIMARY KEY (id), UNIQUE (name) )+?indexsqlite_autoindex_models_1models ÷÷ one ùù onepyramid-1.6/docs/quick_tour/static_assets/0000755000076500000240000000000012642137501021541 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/static_assets/app.py0000644000076500000240000000070612520062551022673 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_route('hello', '/howdy/{name}') # Start Static 1 config.add_static_view(name='static', path='static') # End Static 1 config.include('pyramid_jinja2') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/static_assets/hello_world.jinja20000644000076500000240000000026512520062551025152 0ustar michaelstaff00000000000000 Quick Glance

Hello {{ name }}!

pyramid-1.6/docs/quick_tour/static_assets/hello_world.pt0000644000076500000240000000056212520062551024420 0ustar michaelstaff00000000000000 Quick Glance

Hello ${name}!

pyramid-1.6/docs/quick_tour/static_assets/static/0000755000076500000240000000000012642137501023030 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/static_assets/static/app.css0000644000076500000240000000006612520062551024321 0ustar michaelstaff00000000000000body { margin: 2em; font-family: sans-serif; }pyramid-1.6/docs/quick_tour/static_assets/views.py0000644000076500000240000000025512520062551023247 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(route_name='hello', renderer='hello_world.pt') def hello_world(request): return dict(name=request.matchdict['name']) pyramid-1.6/docs/quick_tour/templating/0000755000076500000240000000000012642137501021034 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/templating/app.py0000644000076500000240000000050012520062551022156 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_route('hello', '/howdy/{name}') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/templating/hello_world.pt0000644000076500000240000000017512520062551023713 0ustar michaelstaff00000000000000 Quick Glance

Hello ${name}

pyramid-1.6/docs/quick_tour/templating/views.py0000644000076500000240000000031412520062551022536 0ustar michaelstaff00000000000000from pyramid.view import view_config # Start View 1 @view_config(route_name='hello', renderer='hello_world.pt') def hello_world(request): return dict(name=request.matchdict['name']) # End View 1pyramid-1.6/docs/quick_tour/view_classes/0000755000076500000240000000000012642137501021357 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/view_classes/app.py0000644000076500000240000000061512520062551022510 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() # Start Routes 1 config.add_route('hello', '/howdy/{name}') # End Routes 1 config.include('pyramid_jinja2') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/view_classes/delete.jinja20000644000076500000240000000020712520062551023714 0ustar michaelstaff00000000000000 Delete World

Delete {{ view.name }}!

pyramid-1.6/docs/quick_tour/view_classes/edit.jinja20000644000076500000240000000020312520062551023373 0ustar michaelstaff00000000000000 Edit World

Edit {{ view.name }}!

pyramid-1.6/docs/quick_tour/view_classes/hello.jinja20000644000076500000240000000062012520062551023554 0ustar michaelstaff00000000000000 Hello World

Hello {{ view.name }}!

pyramid-1.6/docs/quick_tour/view_classes/views.py0000644000076500000240000000163712520062551023072 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) # Start View 1 # One route, at /howdy/amy, so don't repeat on each @view_config @view_defaults(route_name='hello') class HelloWorldViews: def __init__(self, request): self.request = request # Our templates can now say {{ view.name }} self.name = request.matchdict['name'] # Retrieving /howdy/amy the first time @view_config(renderer='hello.jinja2') def hello_view(self): return dict() # Posting to /howdy/amy via the "Edit" submit button @view_config(request_param='form.edit', renderer='edit.jinja2') def edit_view(self): print('Edited') return dict() # Posting to /howdy/amy via the "Delete" submit button @view_config(request_param='form.delete', renderer='delete.jinja2') def delete_view(self): print('Deleted') return dict() # End View 1pyramid-1.6/docs/quick_tour/views/0000755000076500000240000000000012642137501020025 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tour/views/app.py0000644000076500000240000000066312520062551021161 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator if __name__ == '__main__': config = Configurator() config.add_route('home', '/') config.add_route('hello', '/howdy') config.add_route('redirect', '/goto') config.add_route('exception', '/problem') config.scan('views') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tour/views/views.py0000644000076500000240000000164112524266531021543 0ustar michaelstaff00000000000000import cgi from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from pyramid.view import view_config # First view, available at http://localhost:6543/ @view_config(route_name='home') def home_view(request): return Response('

Visit hello

') # /howdy?name=alice which links to the next view @view_config(route_name='hello') def hello_view(request): name = request.params.get('name', 'No Name') body = '

Hi %s, this redirects

' # 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 @view_config(route_name='redirect') def redirect_view(request): return HTTPFound(location="/problem") # /problem which causes a site error @view_config(route_name='exception') def exception_view(request): raise Exception() pyramid-1.6/docs/quick_tour.rst0000644000076500000240000007543612575217552017453 0ustar michaelstaff00000000000000.. _quick_tour: ===================== Quick Tour of Pyramid ===================== Pyramid lets you start small and finish big. This *Quick Tour* of Pyramid is for those who want to evaluate Pyramid, whether you are new to Python web frameworks, or a pro in a hurry. For more detailed treatment of each topic, give the :ref:`quick_tutorial` a try. Installation ============ Once you have a standard Python environment setup, getting started with Pyramid is a breeze. Unfortunately "standard" is not so simple in Python. For this Quick Tour, it means: `Python `_, a `virtual environment `_ (or `virtualenv for Python 2.7 `_), and `setuptools `_. As an example, for Python 3.3+ on Linux: .. parsed-literal:: $ pyvenv env33 $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | env33/bin/python $ env33/bin/easy_install "pyramid==\ |release|\ " For Windows: .. parsed-literal:: # Use your browser to download: # https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py c:\\> c:\\Python33\\python -m venv env33 c:\\> env33\\Scripts\\python ez_setup.py c:\\> env33\\Scripts\\easy_install "pyramid==\ |release|\ " Of course Pyramid runs fine on Python 2.6+, as do the examples in this *Quick Tour*. We're just showing Python 3 a little love (Pyramid had production support for Python 3 in October 2011). .. note:: Why ``easy_install`` and not ``pip``? Some distributions on which Pyramid depends upon have optional C extensions for performance. ``pip`` cannot install some binary Python distributions. With ``easy_install``, Windows users are able to obtain binary Python distributions, so they get the benefit of the C extensions without needing a C compiler. Also, there can be issues when ``pip`` and ``easy_install`` are used side-by-side in the same environment, so we chose to recommend ``easy_install`` for the sake of reducing the complexity of these instructions. .. seealso:: See also: :ref:`Quick Tutorial section on Requirements `, :ref:`installing_unix`, :ref:`Before You Install `, and :ref:`Installing Pyramid on a Windows System ` Hello World =========== Microframeworks have shown that learning starts best from a very small first step. Here's a tiny application in Pyramid: .. literalinclude:: quick_tour/hello_world/app.py :linenos: This simple example is easy to run. Save this as ``app.py`` and run it: .. code-block:: bash $ python ./app.py Next open http://localhost:6543/ in a browser, and you will see the ``Hello World!`` message. New to Python web programming? If so, some lines in the module merit explanation: #. *Line 10*. The ``if __name__ == '__main__':`` is Python's way of saying "Start here when running from the command line". #. *Lines 11-13*. 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 :term:`response`. #. *Lines 14-16*. Publish a :term:`WSGI` app using an HTTP server. As shown in this example, the :term:`configurator` plays a central role in Pyramid development. Building an application from loosely-coupled parts via :doc:`../narr/configuration` is a central idea in Pyramid, one that we will revisit regurlarly in this *Quick Tour*. .. seealso:: See also: :ref:`Quick Tutorial Hello World `, :ref:`firstapp_chapter`, and :ref:`Single File Tasks tutorial ` Handling web requests and responses =================================== Developing for the web means processing web requests. As this is a critical part of a web application, web developers need a robust, mature set of software for web requests. Pyramid has always fit nicely into the existing world of Python web development (virtual environments, packaging, scaffolding, one of the first to embrace Python 3, etc.). Pyramid turned to the well-regarded :term:`WebOb` Python library for request and response handling. In our example above, Pyramid hands ``hello_world`` a ``request`` that is :ref:`based on WebOb `. Let's see some features of requests and responses in action: .. literalinclude:: quick_tour/requests/app.py :pyobject: hello_world In this Pyramid view, we get the URL being visited from ``request.url``. Also, if you visited http://localhost:6543/?name=alice in a browser, the name is included in the body of the response:: URL http://localhost:6543/?name=alice with name: alice Finally, we set the response's content type and return the Response. .. seealso:: See also: :ref:`Quick Tutorial Request and Response ` and :ref:`webob_chapter` Views ===== For the examples above, the ``hello_world`` function is a "view". In Pyramid, views are the primary way to accept web requests and return responses. So far our examples place everything in one file: - the view function - its registration with the configurator - the route to map it to an URL - the WSGI application launcher Let's move the views out to their own ``views.py`` module and change the ``app.py`` to scan that module, looking for decorators that set up the views. First, our revised ``app.py``: .. literalinclude:: quick_tour/views/app.py :linenos: We added some more routes, but we also removed the view code. Our views and their registrations (via decorators) are now in a module ``views.py``, which is scanned via ``config.scan('views')``. We now have a ``views.py`` module that is focused on handling requests and responses: .. literalinclude:: quick_tour/views/views.py :linenos: We have four views, each leading to the other. If you start at http://localhost:6543/, you get a response with a link to the next view. The ``hello_view`` (available at the URL ``/howdy``) has a link to the ``redirect_view``, which issues a redirect to the final view. Earlier we saw ``config.add_view`` as one way to configure a view. This section introduces ``@view_config``. Pyramid's configuration supports :term:`imperative configuration`, such as the ``config.add_view`` in the previous example. You can also use :term:`declarative configuration`, in which a Python :term:`decorator` is placed on the line above the view. Both approaches result in the same final configuration, thus usually it is simply a matter of taste. .. seealso:: See also: :ref:`Quick Tutorial Views `, :doc:`../narr/views`, :doc:`../narr/viewconfig`, and :ref:`debugging_view_configuration` Routing ======= Writing web applications usually means sophisticated URL design. We just saw some Pyramid machinery for requests and views. Let's look at features that help in routing. Above we saw the basics of routing URLs to views in Pyramid: - Your project's "setup" code registers a route name to be used when matching part of the URL - Elsewhere a view is configured to be called for that route name .. note:: Why do this twice? Other Python web frameworks let you create a route and associate it with a view in one step. As illustrated in :ref:`routes_need_ordering`, multiple routes might match the same URL pattern. Rather than provide ways to help guess, Pyramid lets you be explicit in ordering. Pyramid also gives facilities to avoid the problem. What if we want part of the URL to be available as data in my view? This route declaration: .. literalinclude:: quick_tour/routing/app.py :start-after: Start Route 1 :end-before: End Route 1 With this, URLs such as ``/howdy/amy/smith`` will assign ``amy`` to ``first`` and ``smith`` to ``last``. We can then use this data in our view: .. literalinclude:: quick_tour/routing/views.py :start-after: Start Route 1 :end-before: End Route 1 ``request.matchdict`` contains values from the URL that match the "replacement patterns" (the curly braces) in the route declaration. This information can then be used in your view. .. seealso:: See also: :ref:`Quick Tutorial Routing `, :doc:`../narr/urldispatch`, :ref:`debug_routematch_section`, and :doc:`../narr/router` Templating ========== Ouch. We have been making our own ``Response`` and filling the response body with HTML. You usually won't embed an HTML string directly in Python, but instead, will use a templating language. Pyramid doesn't mandate a particular database system, form library, etc. It encourages replaceability. This applies equally to templating, which is fortunate: developers have strong views about template languages. That said, the Pylons Project officially supports bindings for Chameleon, Jinja2, and Mako, so in this step, let's use Chameleon. Let's add ``pyramid_chameleon``, a Pyramid :term:`add-on` which enables Chameleon as a :term:`renderer` in our Pyramid applications: .. code-block:: bash $ easy_install pyramid_chameleon With the package installed, we can include the template bindings into our configuration: .. code-block:: python config.include('pyramid_chameleon') Now lets change our views.py file: .. literalinclude:: quick_tour/templating/views.py :start-after: Start View 1 :end-before: End View 1 Ahh, that looks better. We have a view that is focused on Python code. Our ``@view_config`` decorator specifies a :term:`renderer` that points to our template file. Our view then simply returns data which is then supplied to our template: .. literalinclude:: quick_tour/templating/hello_world.pt :language: html Since our view returned ``dict(name=request.matchdict['name'])``, we can use ``name`` as a variable in our template via ``${name}``. .. seealso:: See also: :ref:`Quick Tutorial Templating `, :doc:`../narr/templates`, :ref:`debugging_templates`, and :ref:`available_template_system_bindings` Templating with ``jinja2`` ========================== We just said Pyramid doesn't prefer one templating language over another. Time to prove it. Jinja2 is a popular templating system, modeled after Django's templates. Let's add ``pyramid_jinja2``, a Pyramid :term:`add-on` which enables Jinja2 as a :term:`renderer` in our Pyramid applications: .. code-block:: bash $ easy_install pyramid_jinja2 With the package installed, we can include the template bindings into our configuration: .. code-block:: python config.include('pyramid_jinja2') The only change in our view is to point the renderer at the ``.jinja2`` file: .. literalinclude:: quick_tour/jinja2/views.py :start-after: Start View 1 :end-before: End View 1 Our Jinja2 template is very similar to our previous template: .. literalinclude:: quick_tour/jinja2/hello_world.jinja2 :language: html Pyramid's templating add-ons register a new kind of renderer into your application. The renderer registration maps to different kinds of filename extensions. In this case, changing the extension from ``.pt`` to ``.jinja2`` passed the view response through the ``pyramid_jinja2`` renderer. .. seealso:: See also: :ref:`Quick Tutorial Jinja2 `, `Jinja2 homepage `_, and :ref:`pyramid_jinja2 Overview ` Static assets ============= Of course the Web is more than just markup. You need static assets: CSS, JS, and images. Let's point our web app at a directory where Pyramid will serve some static assets. First another call to the :term:`configurator`: .. literalinclude:: quick_tour/static_assets/app.py :start-after: Start Static 1 :end-before: End Static 1 This tells our WSGI application to map requests under http://localhost:6543/static/ to files and directories inside a ``static`` directory alongside our Python module. Next make a directory named ``static``, and place ``app.css`` inside: .. literalinclude:: quick_tour/static_assets/static/app.css :language: css All we need to do now is point to it in the ```` of our Jinja2 template: .. literalinclude:: quick_tour/static_assets/hello_world.pt :language: html :start-after: Start Link 1 :end-before: End Link 1 This link presumes that our CSS is at a URL starting with ``/static/``. What if the site is later moved under ``/somesite/static/``? Or perhaps a web developer changes the arrangement on disk? Pyramid provides a helper to allow flexibility on URL generation: .. literalinclude:: quick_tour/static_assets/hello_world.pt :language: html :start-after: Start Link 2 :end-before: End Link 2 By using ``request.static_url`` to generate the full URL to the static assets, you both ensure you stay in sync with the configuration and gain refactoring flexibility later. .. seealso:: See also: :ref:`Quick Tutorial Static Assets `, :doc:`../narr/assets`, :ref:`preventing_http_caching`, and :ref:`influencing_http_caching` Returning JSON ============== Modern web apps are more than rendered HTML. Dynamic pages now use JavaScript to update the UI in the browser by requesting server data as JSON. Pyramid supports this with a JSON renderer: .. literalinclude:: quick_tour/json/views.py :start-after: Start View 1 :end-before: End View 2 This wires up a view that returns some data through the JSON :term:`renderer`, which calls Python's JSON support to serialize the data into JSON and set the appropriate HTTP headers. .. seealso:: See also: :ref:`Quick Tutorial JSON `, :ref:`views_which_use_a_renderer`, :ref:`json_renderer`, and :ref:`adding_and_overriding_renderers` View classes ============ So far our views have been simple, free-standing functions. Many times your views are related: different ways to look at or work on the same data, or a REST API that handles multiple operations. Grouping these together as a :ref:`view class ` makes sense. - Group views - Centralize some repetitive defaults - Share some state and helpers The following shows a "Hello World" example with three operations: view a form, save a change, or press the delete button: .. literalinclude:: quick_tour/view_classes/views.py :start-after: Start View 1 :end-before: End View 1 As you can see, the three views are logically grouped together. Specifically: - The first view is returned when you go to ``/howdy/amy``. This URL is mapped to the ``hello`` route that we centrally set using the optional ``@view_defaults``. - The second view is returned when the form data contains a field with ``form.edit``, such as clicking on ````. This rule is specified in the ``@view_config`` for that view. - The third view is returned when clicking on a button such as ````. Only one route is needed, stated in one place atop the view class. Also, the assignment of ``name`` is done in the ``__init__`` function. Our templates can then use ``{{ view.name }}``. Pyramid view classes, combined with built-in and custom predicates, have much more to offer: - All the same view configuration parameters as function views - One route leading to multiple views, based on information in the request or data such as ``request_param``, ``request_method``, ``accept``, ``header``, ``xhr``, ``containment``, and ``custom_predicates`` .. seealso:: See also: :ref:`Quick Tutorial View Classes `, :ref:`Quick Tutorial More View Classes `, and :ref:`class_as_view` Quick project startup with scaffolds ==================================== So far we have done all of our *Quick Tour* as a single Python file. No Python packages, no structure. Most Pyramid projects, though, aren't developed this way. To ease the process of getting started, Pyramid provides *scaffolds* that generate sample projects from templates in Pyramid and Pyramid add-ons. Pyramid's ``pcreate`` command can list the available scaffolds: .. code-block:: bash $ pcreate --list Available scaffolds: alchemy: Pyramid SQLAlchemy project using url dispatch pyramid_jinja2_starter: pyramid jinja2 starter project starter: Pyramid starter project zodb: Pyramid ZODB project using traversal The ``pyramid_jinja2`` add-on gave us a scaffold that we can use. From the parent directory of where we want our Python package to be generated, let's use that scaffold to make our project: .. code-block:: bash $ pcreate --scaffold pyramid_jinja2_starter hello_world We next use the normal Python command to set up our package for development: .. code-block:: bash $ cd hello_world $ python ./setup.py develop We are moving in the direction of a full-featured Pyramid project, with a proper setup for Python standards (packaging) and Pyramid configuration. This includes a new way of running your application: .. code-block:: bash $ pserve development.ini Let's look at ``pserve`` and configuration in more depth. .. seealso:: See also: :ref:`Quick Tutorial Scaffolds `, :ref:`project_narr`, and :doc:`../narr/scaffolding` Application running with ``pserve`` =================================== Prior to scaffolds, our project mixed a number of operational details into our code. Why should my main code care which HTTP server I want and what port number to run on? ``pserve`` is Pyramid's application runner, separating operational details from your code. When you install Pyramid, a small command program called ``pserve`` is written to your ``bin`` directory. This program is an executable Python module. It's very small, getting most of its brains via import. You can run ``pserve`` with ``--help`` to see some of its options. Doing so reveals that you can ask ``pserve`` to watch your development files and reload the server when they change: .. code-block:: bash $ pserve development.ini --reload The ``pserve`` command has a number of other options and operations. Most of the work, though, comes from your project's wiring, as expressed in the configuration file you supply to ``pserve``. Let's take a look at this configuration file. .. seealso:: See also: :ref:`what_is_this_pserve_thing` Configuration with ``.ini`` files ================================= Earlier in *Quick Tour* we first met Pyramid's configuration system. At that point we did all configuration in Python code. For example, the port number chosen for our HTTP server was right there in Python code. Our scaffold has moved this decision and more into the ``development.ini`` file: .. literalinclude:: quick_tour/package/development.ini :language: ini Let's take a quick high-level look. First the ``.ini`` file is divided into sections: - ``[app:hello_world]`` configures our WSGI app - ``[pipeline:main]`` sets up our WSGI "pipeline" - ``[server:main]`` holds our WSGI server settings - Various sections afterwards configure our Python logging system We have a few decisions made for us in this configuration: #. *Choice of web server:* ``use = egg:pyramid#wsgiref`` tells ``pserve`` to use the ``wsgiref`` server that is wrapped in the Pyramid package. #. *Port number:* ``port = 6543`` tells ``wsgiref`` to listen on port 6543. #. *WSGI app:* What package has our WSGI application in it? ``use = egg:hello_world`` in the app section tells the configuration what application to load. #. *Easier development by automatic template reloading:* In development mode, you shouldn't have to restart the server when editing a Jinja2 template. ``reload_templates = true`` sets this policy, which might be different in production. Additionally the ``development.ini`` generated by this scaffold wired up Python's standard logging. We'll now see in the console, for example, a log on every request that comes in, as well as traceback information. .. seealso:: See also: :ref:`Quick Tutorial Application Configuration `, :ref:`environment_chapter` and :doc:`../narr/paste` Easier development with ``debugtoolbar`` ======================================== As we introduce the basics, we also want to show how to be productive in development and debugging. For example, we just discussed template reloading and earlier we showed ``--reload`` for application reloading. ``pyramid_debugtoolbar`` is a popular Pyramid add-on which makes several tools available in your browser. Adding it to your project illustrates several points about configuration. First change your ``setup.py`` to say: .. literalinclude:: quick_tour/package/setup.py :start-after: Start Requires :end-before: End Requires ...and rerun your setup: .. code-block:: bash $ python ./setup.py develop The Python package ``pyramid_debugtoolbar`` is now installed into our environment. The package is a Pyramid add-on, which means we need to include its configuration into our web application. We could do this with imperative configuration, as we did above for the ``pyramid_jinja2`` add-on: .. literalinclude:: quick_tour/package/hello_world/__init__.py :start-after: Start Include :end-before: End Include Now that we have a configuration file, we can use the ``pyramid.includes`` facility and place this in our ``development.ini`` instead: .. literalinclude:: quick_tour/package/development.ini :language: ini :start-after: Start Includes :end-before: End Includes 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, there's no need to change code: you can remove it from ``pyramid.includes`` in the relevant ``.ini`` configuration file. .. seealso:: See also: :ref:`Quick Tutorial pyramid_debugtoolbar ` and :ref:`pyramid_debugtoolbar ` Unit tests and ``nose`` ======================= Yikes! We got this far and we haven't yet discussed tests. This is particularly egregious, as Pyramid has had a deep commitment to full test coverage since before its release. Our ``pyramid_jinja2_starter`` scaffold generated a ``tests.py`` module with one unit test in it. To run it, let's install the handy ``nose`` test runner by editing ``setup.py``. While we're at it, we'll throw in the ``coverage`` tool which yells at us for code that isn't tested: .. code-block:: python setup(name='hello_world', # Some lines removed... extras_require={ 'testing': ['nose', 'coverage'], } ) We changed ``setup.py`` which means we need to rerun ``python ./setup.py develop``. We can now run all our tests: .. code-block:: bash $ nosetests hello_world/tests.py . Name Stmts Miss Cover Missing --------------------------------------------------- hello_world 12 8 33% 11-23 hello_world.models 5 1 80% 8 hello_world.tests 14 0 100% hello_world.views 4 0 100% --------------------------------------------------- TOTAL 35 9 74% ---------------------------------------------------------------------- Ran 1 test in 0.931s OK Our unit test passed. What did our test look like? .. literalinclude:: quick_tour/package/hello_world/tests.py Pyramid supplies helpers for test writing, which we use in the test setup and teardown. Our one test imports the view, makes a dummy request, and sees if the view returns what we expected. .. seealso:: See also: :ref:`Quick Tutorial Unit Testing `, :ref:`Quick Tutorial Functional Testing `, and :ref:`testing_chapter` Logging ======= It's important to know what is going on inside our web application. In development we might need to collect some output. In production 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`` 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). Maybe you would like to log messages in your code? In your Python module, import and set up the logging: .. literalinclude:: quick_tour/package/hello_world/views.py :start-after: Start Logging 1 :end-before: End Logging 1 You can now, in your code, log messages: .. literalinclude:: quick_tour/package/hello_world/views.py :start-after: Start Logging 2 :end-before: End Logging 2 This will log ``Some Message`` at a ``debug`` log level to the application-configured logger in your ``development.ini``. What controls that? These sections in the configuration file: .. literalinclude:: quick_tour/package/development.ini :language: ini :start-after: Start Sphinx Include :end-before: End Sphinx Include Our application, a package named ``hello_world``, is set up as a logger and configured to log messages at a ``DEBUG`` or higher level. When you visit http://localhost:6543, your console will now show:: 2013-08-09 10:42:42,968 DEBUG [hello_world.views][MainThread] Some Message .. seealso:: See also: :ref:`Quick Tutorial Logging ` and :ref:`logging_chapter` Sessions ======== 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. 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>`. In our ``__init__.py`` we first import the kind of sessioning we want: .. literalinclude:: quick_tour/package/hello_world/__init__.py :start-after: Start Sphinx Include 1 :end-before: End Sphinx Include 1 .. warning:: As noted in the session docs, this example implementation is not intended for use in settings with security implications. Now make a "factory" and pass it to the :term:`configurator`'s ``session_factory`` argument: .. literalinclude:: quick_tour/package/hello_world/__init__.py :start-after: Start Sphinx Include 2 :end-before: End Sphinx Include 2 Pyramid's :term:`request` object now has a ``session`` attribute that we can use in our view code: .. literalinclude:: quick_tour/package/hello_world/views.py :start-after: Start Sphinx Include 1 :end-before: End Sphinx Include 1 With this, each reload will increase the counter displayed in our Jinja2 template: .. literalinclude:: quick_tour/package/hello_world/templates/mytemplate.jinja2 :language: jinja :start-after: Start Sphinx Include 1 :end-before: End Sphinx Include 1 .. seealso:: See also: :ref:`Quick Tutorial Sessions `, :ref:`sessions_chapter`, :ref:`flash_messages`, :ref:`session_module`, and :term:`pyramid_redis_sessions`. Databases ========= Web applications mean data. Data means databases. Frequently SQL databases. SQL databases frequently mean an "ORM" (object-relational mapper.) In Python, ORM usually leads to the mega-quality *SQLAlchemy*, a Python package that greatly eases working with databases. Pyramid and SQLAlchemy are great friends. That friendship includes a scaffold! .. code-block:: bash $ pcreate --scaffold alchemy sqla_demo $ cd sqla_demo $ python setup.py develop We now have a working sample SQLAlchemy application with all dependencies installed. The sample project provides a console script to initialize a SQLite database with tables. Let's run it and then start the application: .. code-block:: bash $ initialize_sqla_demo_db development.ini $ pserve development.ini The ORM eases the mapping of database structures into a programming language. SQLAlchemy uses "models" for this mapping. The scaffold generated a sample model: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/models.py :start-after: Start Sphinx Include :end-before: End Sphinx Include View code, which mediates the logic between web requests and the rest of the system, can then easily get at the data thanks to SQLAlchemy: .. literalinclude:: quick_tour/sqla_demo/sqla_demo/views.py :start-after: Start Sphinx Include :end-before: End Sphinx Include .. seealso:: See also: :ref:`Quick Tutorial Databases `, `SQLAlchemy `_, :ref:`making_a_console_script`, :ref:`bfg_sql_wiki_tutorial`, and :ref:`Application Transactions With pyramid_tm ` Forms ===== Developers have lots of opinions about web forms, and thus there are many form libraries for Python. Pyramid doesn't directly bundle a form library, but *Deform* is a popular choice for forms, along with its related *Colander* schema system. As an example, imagine we want a form that edits a wiki page. The form should have two fields on it, one of them a required title and the other a rich text editor for the body. With Deform we can express this as a Colander schema: .. code-block:: python class WikiPage(colander.MappingSchema): title = colander.SchemaNode(colander.String()) body = colander.SchemaNode( colander.String(), widget=deform.widget.RichTextWidget() ) With this in place, we can render the HTML for a form, perhaps with form data from an existing page: .. code-block:: python form = self.wiki_form.render() We'd like to handle form submission, validation, and saving: .. code-block:: python # Get the form data that was posted controls = self.request.POST.items() try: # Validate and either raise a validation error # or return deserialized data from widgets appstruct = wiki_form.validate(controls) except deform.ValidationFailure as e: # Bail out and render form with errors return dict(title=title, page=page, form=e.render()) # Change the content and redirect to the view page['title'] = appstruct['title'] page['body'] = appstruct['body'] Deform and Colander provide a very flexible combination for forms, widgets, schemas, and validation. Recent versions of Deform also include a :ref:`retail mode ` for gaining Deform features on custom forms. Also the ``deform_bootstrap`` Pyramid add-on restyles the stock Deform widgets using attractive CSS from Twitter Bootstrap and more powerful widgets from Chosen. .. seealso:: See also: :ref:`Quick Tutorial Forms `, :ref:`Deform `, :ref:`Colander `, and `deform_bootstrap `_ Conclusion ========== This *Quick Tour* covered a little about a lot. We introduced a long list of concepts in Pyramid, many of which are expanded on more fully in the Pyramid developer docs. pyramid-1.6/docs/quick_tutorial/0000755000076500000240000000000012642137501017542 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/authentication/0000755000076500000240000000000012642137501022561 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/authentication/development.ini0000644000076500000240000000030512520062551025577 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar tutorial.secret = 98zd [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/authentication/setup.py0000644000076500000240000000034612520062551024273 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/authentication/tutorial/0000755000076500000240000000000012642137501024424 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/authentication/tutorial/__init__.py0000644000076500000240000000150612520062551026534 0ustar michaelstaff00000000000000from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from pyramid.config import Configurator from .security import groupfinder def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') # Security policies authn_policy = AuthTktAuthenticationPolicy( settings['tutorial.secret'], callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_route('home', '/') config.add_route('hello', '/howdy') config.add_route('login', '/login') config.add_route('logout', '/logout') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/authentication/tutorial/home.pt0000644000076500000240000000066312575217552025740 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

Visit hello

pyramid-1.6/docs/quick_tutorial/authentication/tutorial/login.pt0000644000076500000240000000117612575217552026120 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Login



pyramid-1.6/docs/quick_tutorial/authentication/tutorial/security.py0000644000076500000240000000030212520062551026635 0ustar michaelstaff00000000000000USERS = {'editor': 'editor', 'viewer': 'viewer'} GROUPS = {'editor': ['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, [])pyramid-1.6/docs/quick_tutorial/authentication/tutorial/views.py0000644000076500000240000000346312520062551026136 0ustar michaelstaff00000000000000from pyramid.httpexceptions import HTTPFound from pyramid.security import ( remember, forget, ) from pyramid.view import ( view_config, view_defaults ) from .security import USERS @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request self.logged_in = request.authenticated_userid @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): return {'name': 'Hello View'} @view_config(route_name='login', renderer='login.pt') def login(self): request = self.request login_url = request.route_url('login') referrer = request.url if referrer == login_url: referrer = '/' # never use login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) message = 'Failed login' return dict( name='Login', message=message, url=request.application_url + '/login', came_from=came_from, login=login, password=password, ) @view_config(route_name='logout') def logout(self): request = self.request headers = forget(request) url = request.route_url('home') return HTTPFound(location=url, headers=headers) pyramid-1.6/docs/quick_tutorial/authentication.rst0000644000076500000240000000756712575217552023344 0ustar michaelstaff00000000000000============================== 20: Logins With Authentication ============================== Login views that authenticate a username/password against a list of users. Background ========== Most web applications have URLs that allow people to add/edit/delete content via a web browser. Time to add :ref:`security ` to the application. In this first step we introduce authentication. That is, logging in and logging out using Pyramid's rich facilities for pluggable user storages. In the next step we will introduce protection resources with authorization security statements. Objectives ========== - Introduce the Pyramid concepts of authentication - Create login/logout views Steps ===== #. We are going to use the view classes step as our starting point: .. code-block:: bash $ cd ..; cp -r view_classes authentication; cd authentication $ $VENV/bin/python setup.py develop #. Put the security hash in the ``authentication/development.ini`` configuration file as ``tutorial.secret`` instead of putting it in the code: .. literalinclude:: authentication/development.ini :language: ini :linenos: #. Get authentication (and for now, authorization policies) and login route into the :term:`configurator` in ``authentication/tutorial/__init__.py``: .. literalinclude:: authentication/tutorial/__init__.py :linenos: #. Create a ``authentication/tutorial/security.py`` module that can find our user information by providing an *authentication policy callback*: .. literalinclude:: authentication/tutorial/security.py :linenos: #. Update the views in ``authentication/tutorial/views.py``: .. literalinclude:: authentication/tutorial/views.py :linenos: #. Add a login template at ``authentication/tutorial/login.pt``: .. literalinclude:: authentication/tutorial/login.pt :language: html :linenos: #. Provide a login/logout box in ``authentication/tutorial/home.pt`` .. literalinclude:: authentication/tutorial/home.pt :language: html :linenos: #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in a browser. #. Click the "Log In" link. #. Submit the login form with the username ``editor`` and the password ``editor``. #. Note that the "Log In" link has changed to "Logout". #. Click the "Logout" link. Analysis ======== Unlike many web frameworks, Pyramid includes a built-in but optional security model for authentication and authorization. This security system is intended to be flexible and support many needs. In this security model, authentication (who are you) and authorization (what are you allowed to do) are not just pluggable, but de-coupled. To learn one step at a time, we provide a system that identifies users and lets them log out. In this example we chose to use the bundled :ref:`AuthTktAuthenticationPolicy ` policy. We enabled it in our configuration and provided a ticket-signing secret in our INI file. Our view class grew a login view. When you reached it via a GET, it returned a login form. When reached via POST, it processed the username and password against the "groupfinder" callable that we registered in the configuration. In our template, we fetched the ``logged_in`` value from the view class. We use this to calculate the logged-in user, if any. In the template we can then choose to show a login link to anonymous visitors or a logout link to logged-in users. Extra Credit ============ #. What is the difference between a user and a principal? #. Can I use a database behind my ``groupfinder`` to look up principals? #. Once I am logged in, does any user-centric information get jammed onto each request? Use ``import pdb; pdb.set_trace()`` to answer this. .. seealso:: See also :ref:`security_chapter`, :ref:`AuthTktAuthenticationPolicy `. pyramid-1.6/docs/quick_tutorial/authorization/0000755000076500000240000000000012642137501022442 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/authorization/development.ini0000644000076500000240000000030512520062551025460 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar tutorial.secret = 98zd [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/authorization/setup.py0000644000076500000240000000034612520062551024154 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/authorization/tutorial/0000755000076500000240000000000012642137501024305 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/authorization/tutorial/__init__.py0000644000076500000240000000160012520062551026410 0ustar michaelstaff00000000000000from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from pyramid.config import Configurator from .security import groupfinder def main(global_config, **settings): config = Configurator(settings=settings, root_factory='.resources.Root') config.include('pyramid_chameleon') # Security policies authn_policy = AuthTktAuthenticationPolicy( settings['tutorial.secret'], callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_route('home', '/') config.add_route('hello', '/howdy') config.add_route('login', '/login') config.add_route('logout', '/logout') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/authorization/tutorial/home.pt0000644000076500000240000000066312575217552025621 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

Visit hello

pyramid-1.6/docs/quick_tutorial/authorization/tutorial/login.pt0000644000076500000240000000117612575217552026001 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Login



pyramid-1.6/docs/quick_tutorial/authorization/tutorial/resources.py0000644000076500000240000000031412520062551026664 0ustar michaelstaff00000000000000from pyramid.security import Allow, Everyone class Root(object): __acl__ = [(Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit')] def __init__(self, request): passpyramid-1.6/docs/quick_tutorial/authorization/tutorial/security.py0000644000076500000240000000030212520062551026516 0ustar michaelstaff00000000000000USERS = {'editor': 'editor', 'viewer': 'viewer'} GROUPS = {'editor': ['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, [])pyramid-1.6/docs/quick_tutorial/authorization/tutorial/views.py0000644000076500000240000000362112520062551026013 0ustar michaelstaff00000000000000from pyramid.httpexceptions import HTTPFound from pyramid.security import ( remember, forget, ) from pyramid.view import ( view_config, view_defaults, forbidden_view_config ) from .security import USERS @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request self.logged_in = request.authenticated_userid @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello', permission='edit') def hello(self): return {'name': 'Hello View'} @view_config(route_name='login', renderer='login.pt') @forbidden_view_config(renderer='login.pt') def login(self): request = self.request login_url = request.route_url('login') referrer = request.url if referrer == login_url: referrer = '/' # never use login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location=came_from, headers=headers) message = 'Failed login' return dict( name='Login', message=message, url=request.application_url + '/login', came_from=came_from, login=login, password=password, ) @view_config(route_name='logout') def logout(self): request = self.request headers = forget(request) url = request.route_url('home') return HTTPFound(location=url, headers=headers) pyramid-1.6/docs/quick_tutorial/authorization.rst0000644000076500000240000000670112575217552023212 0ustar michaelstaff00000000000000=========================================== 21: Protecting Resources With Authorization =========================================== Assign security statements to resources describing the permissions required to perform an operation. Background ========== Our application has URLs that allow people to add/edit/delete content via a web browser. Time to add security to the application. Let's protect our add/edit views to require a login (username of ``editor`` and password of ``editor``). We will allow the other views to continue working without a password. Objectives ========== - Introduce the Pyramid concepts of authentication, authorization, permissions, and access control lists (ACLs) - Make a :term:`root factory` that returns an instance of our class for the top of the application - Assign security statements to our root resource - Add a permissions predicate on a view - Provide a :term:`Forbidden view` to handle visiting a URL without adequate permissions Steps ===== #. We are going to use the authentication step as our starting point: .. code-block:: bash $ cd ..; cp -r authentication authorization; cd authorization $ $VENV/bin/python setup.py develop #. Start by changing ``authorization/tutorial/__init__.py`` to specify a root factory to the :term:`configurator`: .. literalinclude:: authorization/tutorial/__init__.py :linenos: #. That means we need to implement ``authorization/tutorial/resources.py`` .. literalinclude:: authorization/tutorial/resources.py :linenos: #. Change ``authorization/tutorial/views.py`` to require the ``edit`` permission on the ``hello`` view and implement the forbidden view: .. literalinclude:: authorization/tutorial/views.py :linenos: #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in a browser. #. If you are still logged in, click the "Log Out" link. #. Visit http://localhost:6543/howdy in a browser. You should be asked to login. Analysis ======== This simple tutorial step can be boiled down to the following: - A view can require a *permission* (``edit``) - The context for our view (the ``Root``) has an access control list (ACL) - This ACL says that the ``edit`` permission is available on ``Root`` to the ``group:editors`` *principal* - The registered ``groupfinder`` answers whether a particular user (``editor``) has a particular group (``group:editors``) In summary: ``hello`` wants ``edit`` permission, ``Root`` says ``group:editors`` has ``edit`` permission. Of course, this only applies on ``Root``. Some other part of the site (a.k.a. *context*) might have a different ACL. If you are not logged in and visit ``/howdy``, you need to get shown the login screen. How does Pyramid know what is the login page to use? We explicitly told Pyramid that the ``login`` view should be used by decorating the view with ``@forbidden_view_config``. Extra Credit ============ #. Do I have to put a ``renderer`` in my ``@forbidden_view_config`` decorator? #. Perhaps you would like the experience of not having enough permissions (forbidden) to be richer. How could you change this? #. Perhaps we want to store security statements in a database and allow editing via a browser. How might this be done? #. What if we want different security statements on different kinds of objects? Or on the same kinds of objects, but in different parts of a URL hierarchy? pyramid-1.6/docs/quick_tutorial/conf.py0000644000076500000240000002175712520062551021052 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- # # Getting Started with Pyramid and REST documentation build configuration file, created by # sphinx-quickstart on Mon Aug 26 14:44:57 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Getting Started with Pyramid and REST' copyright = u'2013, Agendaless Consulting' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.0' # The full version, including alpha/beta/rc tags. release = '1.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'GettingStartedwithPyramidandRESTdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'GettingStartedwithPyramidandREST.tex', u'Getting Started with Pyramid and REST Documentation', u'Agendaless Consulting', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'gettingstartedwithpyramidandrest', u'Getting Started with Pyramid and REST Documentation', [u'Agendaless Consulting'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'GettingStartedwithPyramidandREST', u'Getting Started with Pyramid and REST Documentation', u'Agendaless Consulting', 'GettingStartedwithPyramidandREST', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'python': ( 'http://docs.python.org/2', None), 'sqla': ( 'http://docs.sqlalchemy.org/en/latest', None), 'pyramid': ( 'http://docs.pylonsproject.org/projects/pyramid/en/latest/', None), 'jinja2': ( 'http://docs.pylonsproject.org/projects/pyramid_jinja2/en/latest/', None), 'toolbar': ( 'http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/en/latest', None), 'deform': ( 'http://docs.pylonsproject.org/projects/deform/en/latest', None), 'colander': ( 'http://docs.pylonsproject.org/projects/colander/en/latest', None), 'tutorials': ( 'http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/', None), } pyramid-1.6/docs/quick_tutorial/databases/0000755000076500000240000000000012642137501021471 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/databases/development.ini0000644000076500000240000000150512575217552024527 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/sqltutorial.sqlite [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, tutorial, sqlalchemy.engine.base.Engine [logger_tutorial] level = DEBUG handlers = qualname = tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_sqlalchemy.engine.base.Engine] level = INFO handlers = qualname = sqlalchemy.engine.base.Engine [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 pyramid-1.6/docs/quick_tutorial/databases/setup.py0000644000076500000240000000060212520062551023176 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon', 'deform', 'sqlalchemy', 'pyramid_tm', 'zope.sqlalchemy' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.initialize_db:main """, )pyramid-1.6/docs/quick_tutorial/databases/sqltutorial.sqlite0000644000076500000240000003000012520062551025265 0ustar michaelstaff00000000000000SQLite format 3@ -â$ ü55ÉqtablewikipageswikipagesCREATE TABLE wikipages ( uid INTEGER NOT NULL, title TEXT, body TEXT, PRIMARY KEY (uid), UNIQUE (title) )1Eindexsqlite_autoindex_wikipages_1wikipages ÉëÉ %-asfasdfasasd

fasdfadsf

#Root

Root

çøç%asfasdfasasd Rootpyramid-1.6/docs/quick_tutorial/databases/tutorial/0000755000076500000240000000000012642137501023334 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/databases/tutorial/__init__.py0000644000076500000240000000134312520062551025443 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import DBSession, Base def main(global_config, **settings): engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings, root_factory='tutorial.models.Root') config.include('pyramid_chameleon') config.add_route('wiki_view', '/') config.add_route('wikipage_add', '/add') config.add_route('wikipage_view', '/{uid}') config.add_route('wikipage_edit', '/{uid}/edit') config.add_static_view('deform_static', 'deform:static/') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/databases/tutorial/initialize_db.py0000644000076500000240000000144212520062551026512 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from .models import ( DBSession, Page, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = Page(title='Root', body='

Root

') DBSession.add(model) pyramid-1.6/docs/quick_tutorial/databases/tutorial/models.py0000644000076500000240000000132512520062551025167 0ustar michaelstaff00000000000000from pyramid.security import Allow, Everyone from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session( sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): __tablename__ = 'wikipages' uid = Column(Integer, primary_key=True) title = Column(Text, unique=True) body = Column(Text) class Root(object): __acl__ = [(Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit')] def __init__(self, request): passpyramid-1.6/docs/quick_tutorial/databases/tutorial/tests.py0000644000076500000240000000270212575217552025063 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing def _initTestingDB(): from sqlalchemy import create_engine from .models import ( DBSession, Page, Base ) engine = create_engine('sqlite://') Base.metadata.create_all(engine) DBSession.configure(bind=engine) with transaction.manager: model = Page(title='FrontPage', body='This is the front page') DBSession.add(model) return DBSession class WikiViewTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def test_wiki_view(self): from tutorial.views import WikiViews request = testing.DummyRequest() inst = WikiViews(request) response = inst.wiki_view() self.assertEqual(response['title'], 'Wiki View') class WikiFunctionalTests(unittest.TestCase): def setUp(self): from pyramid.paster import get_app app = get_app('development.ini') from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): from .models import DBSession DBSession.remove() def test_it(self): res = self.testapp.get('/', status=200) self.assertIn(b'Wiki: View', res.body) res = self.testapp.get('/add', status=200) self.assertIn(b'Add/Edit', res.body) pyramid-1.6/docs/quick_tutorial/databases/tutorial/views.py0000644000076500000240000000570112520062551025043 0ustar michaelstaff00000000000000import colander import deform.widget from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config from .models import DBSession, Page class WikiPage(colander.MappingSchema): title = colander.SchemaNode(colander.String()) body = colander.SchemaNode( colander.String(), widget=deform.widget.RichTextWidget() ) class WikiViews(object): def __init__(self, request): self.request = request @property def wiki_form(self): schema = WikiPage() return deform.Form(schema, buttons=('submit',)) @property def reqts(self): return self.wiki_form.get_widget_resources() @view_config(route_name='wiki_view', renderer='wiki_view.pt') def wiki_view(self): pages = DBSession.query(Page).order_by(Page.title) return dict(title='Wiki View', pages=pages) @view_config(route_name='wikipage_add', renderer='wikipage_addedit.pt') def wikipage_add(self): form = self.wiki_form.render() if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = self.wiki_form.validate(controls) except deform.ValidationFailure as e: # Form is NOT valid return dict(form=e.render()) # Add a new page to the database new_title = appstruct['title'] new_body = appstruct['body'] DBSession.add(Page(title=new_title, body=new_body)) # Get the new ID and redirect page = DBSession.query(Page).filter_by(title=new_title).one() new_uid = page.uid url = self.request.route_url('wikipage_view', uid=new_uid) return HTTPFound(url) return dict(form=form) @view_config(route_name='wikipage_view', renderer='wikipage_view.pt') def wikipage_view(self): uid = int(self.request.matchdict['uid']) page = DBSession.query(Page).filter_by(uid=uid).one() return dict(page=page) @view_config(route_name='wikipage_edit', renderer='wikipage_addedit.pt') def wikipage_edit(self): uid = int(self.request.matchdict['uid']) page = DBSession.query(Page).filter_by(uid=uid).one() wiki_form = self.wiki_form if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = wiki_form.validate(controls) except deform.ValidationFailure as e: return dict(page=page, form=e.render()) # Change the content and redirect to the view page.title = appstruct['title'] page.body = appstruct['body'] url = self.request.route_url('wikipage_view', uid=uid) return HTTPFound(url) form = self.wiki_form.render(dict( uid=page.uid, title=page.title, body=page.body) ) return dict(page=page, form=form) pyramid-1.6/docs/quick_tutorial/databases/tutorial/wiki_view.pt0000644000076500000240000000053712520062551025700 0ustar michaelstaff00000000000000 Wiki: View

Wiki

Add WikiPage pyramid-1.6/docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt0000644000076500000240000000124112575217552027167 0ustar michaelstaff00000000000000 WikiPage: Add/Edit

Wiki

${structure: form}

pyramid-1.6/docs/quick_tutorial/databases/tutorial/wikipage_view.pt0000644000076500000240000000044712520062551026535 0ustar michaelstaff00000000000000 WikiPage: View Up | Edit

${page.title}

${structure: page.body}

pyramid-1.6/docs/quick_tutorial/databases.rst0000644000076500000240000001576612575217552022254 0ustar michaelstaff00000000000000.. _qtut_databases: ============================== 19: Databases Using SQLAlchemy ============================== Store/retrieve data using the SQLAlchemy ORM atop the SQLite database. Background ========== Our Pyramid-based wiki application now needs database-backed storage of pages. This frequently means a SQL database. The Pyramid community strongly supports the :ref:`SQLAlchemy ` project and its :ref:`object-relational mapper (ORM) ` as a convenient, Pythonic way to interface to databases. In this step we hook up SQLAlchemy to a SQLite database table, providing storage and retrieval for the wikipages in the previous step. .. note:: The ``alchemy`` scaffold is really helpful for getting a SQLAlchemy project going, including generation of the console script. Since we want to see all the decisions, we will forgo convenience in this tutorial and wire it up ourselves. Objectives ========== - Store pages in SQLite by using SQLAlchemy models - Use SQLAlchemy queries to list/add/view/edit pages - Provide a database-initialize command by writing a Pyramid *console script* which can be run from the command line Steps ===== #. We are going to use the forms step as our starting point: .. code-block:: bash $ cd ..; cp -r forms databases; cd databases #. We need to add some dependencies in ``databases/setup.py`` as well as an "entry point" for the command-line script: .. literalinclude:: databases/setup.py :linenos: .. note:: We aren't yet doing ``$VENV/bin/python setup.py develop`` as we will change it later. #. Our configuration file at ``databases/development.ini`` wires together some new pieces: .. literalinclude:: databases/development.ini :language: ini #. This engine configuration now needs to be read into the application through changes in ``databases/tutorial/__init__.py``: .. literalinclude:: databases/tutorial/__init__.py :linenos: #. Make a command-line script at ``databases/tutorial/initialize_db.py`` to initialize the database: .. literalinclude:: databases/tutorial/initialize_db.py :linenos: #. Since ``setup.py`` changed, we now run it: .. code-block:: bash $ $VENV/bin/python setup.py develop #. The script references some models in ``databases/tutorial/models.py``: .. literalinclude:: databases/tutorial/models.py :linenos: #. Let's run this console script, thus producing our database and table: .. code-block:: bash $ $VENV/bin/initialize_tutorial_db development.ini 2015-06-01 11:22:52,650 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2015-06-01 11:22:52,650 INFO [sqlalchemy.engine.base.Engine][MainThread] () 2015-06-01 11:22:52,651 INFO [sqlalchemy.engine.base.Engine][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2015-06-01 11:22:52,651 INFO [sqlalchemy.engine.base.Engine][MainThread] () 2015-06-01 11:22:52,652 INFO [sqlalchemy.engine.base.Engine][MainThread] PRAGMA table_info("wikipages") 2015-06-01 11:22:52,652 INFO [sqlalchemy.engine.base.Engine][MainThread] () 2015-06-01 11:22:52,653 INFO [sqlalchemy.engine.base.Engine][MainThread] CREATE TABLE wikipages ( uid INTEGER NOT NULL, title TEXT, body TEXT, PRIMARY KEY (uid), UNIQUE (title) ) 2015-06-01 11:22:52,653 INFO [sqlalchemy.engine.base.Engine][MainThread] () 2015-06-01 11:22:52,655 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT 2015-06-01 11:22:52,658 INFO [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit) 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO wikipages (title, body) VALUES (?, ?) 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] ('Root', '

Root

') 2015-06-01 11:22:52,659 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT #. With our data now driven by SQLAlchemy queries, we need to update our ``databases/tutorial/views.py``: .. literalinclude:: databases/tutorial/views.py :linenos: #. Our tests in ``databases/tutorial/tests.py`` changed to include SQLAlchemy bootstrapping: .. literalinclude:: databases/tutorial/tests.py :linenos: #. Run the tests in your package using ``nose``: .. code-block:: bash $ $VENV/bin/nosetests tutorial .. ----------------------------------------------------------------- Ran 2 tests in 1.141s OK #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in a browser. Analysis ======== Let's start with the dependencies. We made the decision to use ``SQLAlchemy`` to talk to our database. We also, though, installed ``pyramid_tm`` and ``zope.sqlalchemy``. Why? Pyramid has a strong orientation towards support for ``transactions``. Specifically, you can install a transaction manager into your application either as middleware or a Pyramid "tween". Then, just before you return the response, all transaction-aware parts of your application are executed. This means Pyramid view code usually doesn't manage transactions. If your view code or a template generates an error, the transaction manager aborts the transaction. This is a very liberating way to write code. The ``pyramid_tm`` package provides a "tween" that is configured in the ``development.ini`` configuration file. That installs it. We then need a package that makes SQLAlchemy, and thus the RDBMS transaction manager, integrate with the Pyramid transaction manager. That's what ``zope.sqlalchemy`` does. Where do we point at the location on disk for the SQLite file? In the configuration file. This lets consumers of our package change the location in a safe (non-code) way. That is, in configuration. This configuration-oriented approach isn't required in Pyramid; you can still make such statements in your ``__init__.py`` or some companion module. The ``initialize_tutorial_db`` is a nice example of framework support. You point your setup at the location of some ``[console_scripts]`` and these get generated into your virtualenv's ``bin`` directory. Our console script follows the pattern of being fed a configuration file with all the bootstrapping. It then opens SQLAlchemy and creates the root of the wiki, which also makes the SQLite file. Note the ``with transaction.manager`` part that puts the work in the scope of a transaction, as we aren't inside a web request where this is done automatically. The ``models.py`` does a little bit extra work to hook up SQLAlchemy into the Pyramid transaction manager. It then declares the model for a ``Page``. Our views have changes primarily around replacing our dummy dictionary-of-dictionaries data with proper database support: list the rows, add a row, edit a row, and delete a row. Extra Credit ============ #. Why all this code? Why can't I just type 2 lines and have magic ensue? #. Give a try at a button that deletes a wiki page. pyramid-1.6/docs/quick_tutorial/debugtoolbar/0000755000076500000240000000000012642137501022213 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/debugtoolbar/development.ini0000644000076500000240000000021612520062551025232 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/debugtoolbar/setup.py0000644000076500000240000000031612520062551023722 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/debugtoolbar/tutorial/0000755000076500000240000000000012642137501024056 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/debugtoolbar/tutorial/__init__.py0000644000076500000240000000056212520062551026167 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('

Hello World!

') def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') return config.make_wsgi_app() pyramid-1.6/docs/quick_tutorial/debugtoolbar.rst0000644000076500000240000000731612575217552022766 0ustar michaelstaff00000000000000.. _qtut_debugtoolbar: ============================================ 04: Easier Development with ``debugtoolbar`` ============================================ Error-handling and introspection using the ``pyramid_debugtoolbar`` add-on. Background ========== As we introduce the basics we also want to show how to be productive in development and debugging. For example, we just discussed template reloading and earlier we showed ``--reload`` for application reloading. ``pyramid_debugtoolbar`` is a popular Pyramid add-on which makes several tools available in your browser. Adding it to your project illustrates several points about configuration. Objectives ========== - Install and enable the toolbar to help during development - Explain Pyramid add-ons - Show how an add-on gets configured into your application Steps ===== #. First we copy the results of the previous step, as well as install the ``pyramid_debugtoolbar`` package: .. code-block:: bash $ cd ..; cp -r ini debugtoolbar; cd debugtoolbar $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install pyramid_debugtoolbar #. Our ``debugtoolbar/development.ini`` gets a configuration entry for ``pyramid.includes``: .. literalinclude:: debugtoolbar/development.ini :language: ini :linenos: #. Run the WSGI application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in your browser. See the handy toolbar on the right. 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 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, there's 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 ```` 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 `. Extra Credit ============ #. Why don't we add ``pyramid_debugtoolbar`` to the list of ``install_requires`` dependencies in ``debugtoolbar/setup.py``? #. Introduce a bug into your application: Change: .. code-block:: python def hello_world(request): return Response('

Hello World!

') to: .. code-block:: python def hello_world(request): return xResponse('

Hello World!

') Save, and visit http://localhost:6543/ again. Notice the nice traceback display. On the lowest line, click the "screen" icon to the right, and try typing the variable names ``request`` and ``Response``. What else can you discover? pyramid-1.6/docs/quick_tutorial/forms/0000755000076500000240000000000012642137501020670 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/forms/development.ini0000644000076500000240000000025612520062551023713 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/forms/setup.py0000644000076500000240000000036412520062551022402 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon', 'deform' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/forms/tutorial/0000755000076500000240000000000012642137501022533 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/forms/tutorial/__init__.py0000644000076500000240000000072712520062551024647 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('wiki_view', '/') config.add_route('wikipage_add', '/add') config.add_route('wikipage_view', '/{uid}') config.add_route('wikipage_edit', '/{uid}/edit') config.add_static_view('deform_static', 'deform:static/') config.scan('.views') return config.make_wsgi_app() pyramid-1.6/docs/quick_tutorial/forms/tutorial/tests.py0000644000076500000240000000146512520062551024252 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import WikiViews request = testing.DummyRequest() inst = WikiViews(request) response = inst.wiki_view() self.assertEqual(len(response['pages']), 3) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): testing.tearDown() def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'Wiki: View', res.body) pyramid-1.6/docs/quick_tutorial/forms/tutorial/views.py0000644000076500000240000000567612520062551024255 0ustar michaelstaff00000000000000import colander import deform.widget from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config pages = { '100': dict(uid='100', title='Page 100', body='100'), '101': dict(uid='101', title='Page 101', body='101'), '102': dict(uid='102', title='Page 102', body='102') } class WikiPage(colander.MappingSchema): title = colander.SchemaNode(colander.String()) body = colander.SchemaNode( colander.String(), widget=deform.widget.RichTextWidget() ) class WikiViews(object): def __init__(self, request): self.request = request @property def wiki_form(self): schema = WikiPage() return deform.Form(schema, buttons=('submit',)) @property def reqts(self): return self.wiki_form.get_widget_resources() @view_config(route_name='wiki_view', renderer='wiki_view.pt') def wiki_view(self): return dict(pages=pages.values()) @view_config(route_name='wikipage_add', renderer='wikipage_addedit.pt') def wikipage_add(self): form = self.wiki_form.render() if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = self.wiki_form.validate(controls) except deform.ValidationFailure as e: # Form is NOT valid return dict(form=e.render()) # Form is valid, make a new identifier and add to list last_uid = int(sorted(pages.keys())[-1]) new_uid = str(last_uid + 1) pages[new_uid] = dict( uid=new_uid, title=appstruct['title'], body=appstruct['body'] ) # Now visit new page url = self.request.route_url('wikipage_view', uid=new_uid) return HTTPFound(url) return dict(form=form) @view_config(route_name='wikipage_view', renderer='wikipage_view.pt') def wikipage_view(self): uid = self.request.matchdict['uid'] page = pages[uid] return dict(page=page) @view_config(route_name='wikipage_edit', renderer='wikipage_addedit.pt') def wikipage_edit(self): uid = self.request.matchdict['uid'] page = pages[uid] wiki_form = self.wiki_form if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = wiki_form.validate(controls) except deform.ValidationFailure as e: return dict(page=page, form=e.render()) # Change the content and redirect to the view page['title'] = appstruct['title'] page['body'] = appstruct['body'] url = self.request.route_url('wikipage_view', uid=page['uid']) return HTTPFound(url) form = wiki_form.render(page) return dict(page=page, form=form)pyramid-1.6/docs/quick_tutorial/forms/tutorial/wiki_view.pt0000644000076500000240000000053712520062551025077 0ustar michaelstaff00000000000000 Wiki: View

Wiki

Add WikiPage pyramid-1.6/docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt0000644000076500000240000000123612575217552026372 0ustar michaelstaff00000000000000 WikiPage: Add/Edit

Wiki

${structure: form}

pyramid-1.6/docs/quick_tutorial/forms/tutorial/wikipage_view.pt0000644000076500000240000000044712520062551025734 0ustar michaelstaff00000000000000 WikiPage: View Up | Edit

${page.title}

${structure: page.body}

pyramid-1.6/docs/quick_tutorial/forms.rst0000644000076500000240000001075012575217552021437 0ustar michaelstaff00000000000000.. _qtut_forms: ==================================== 18: Forms and Validation With Deform ==================================== Schema-driven, autogenerated forms with validation. Background ========== Modern web applications deal extensively with forms. Developers, though, have a wide range of philosophies about how frameworks should help them with their forms. As such, Pyramid doesn't directly bundle one particular form library. Instead there are a variety of form libraries that are easy to use in Pyramid. :ref:`Deform ` is one such library. In this step, we introduce Deform for our forms and validation. This also gives us :ref:`Colander ` for schemas and validation. Deform is getting a facelift, with styling from Twitter Bootstrap and advanced widgets from popular JavaScript projects. The work began in ``deform_bootstrap`` and is being merged into an update to Deform. Objectives ========== - Make a schema using Colander, the companion to Deform - Create a form with Deform and change our views to handle validation Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes forms; cd forms #. Let's edit ``forms/setup.py`` to declare a dependency on Deform (which then pulls in Colander as a dependency: .. literalinclude:: forms/setup.py :linenos: #. We can now install our project in development mode: .. code-block:: bash $ $VENV/bin/python setup.py develop #. Register a static view in ``forms/tutorial/__init__.py`` for Deform's CSS/JS etc. as well as our demo wikipage scenario's views: .. literalinclude:: forms/tutorial/__init__.py :linenos: #. Implement the new views, as well as the form schemas and some dummy data, in ``forms/tutorial/views.py``: .. literalinclude:: forms/tutorial/views.py :linenos: #. A template for the top of the "wiki" in ``forms/tutorial/wiki_view.pt``: .. literalinclude:: forms/tutorial/wiki_view.pt :language: html :linenos: #. Another template for adding/editing in ``forms/tutorial/wikipage_addedit.pt``: .. literalinclude:: forms/tutorial/wikipage_addedit.pt :language: html :linenos: #. Finally, a template at ``forms/tutorial/wikipage_view.pt`` for viewing a wiki page: .. literalinclude:: forms/tutorial/wikipage_view.pt :language: html :linenos: #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in a browser. Analysis ======== This step helps illustrate the utility of asset specifications for static assets. We have an outside package called Deform with static 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 packages, Pyramid provides a facility (``config.include()``) which even makes that unnecessary for consumers of a package. (Deform is not specific to Pyramid.) Our forms have rich widgets which need the static CSS and JS just mentioned. Deform has a :term:`resource registry` which allows widgets to specify which JS and CSS are needed. Our ``wikipage_addedit.pt`` template shows how we iterated over that data to generate markup that includes the needed resources. Our add and edit views use a pattern called *self-posting forms*. Meaning, the same URL is used to ``GET`` the form as is used to ``POST`` the form. The route, the view, and the template are the same whether you are walking up to it the first time or you clicked a button. Inside the view we do ``if 'submit' in self.request.params:`` to see if this form was a ``POST`` where the user clicked on a particular button ````. The form controller then follows a typical pattern: - If you are doing a GET, skip over and just return the form - If you are doing a POST, validate the form contents - If the form is invalid, bail out by re-rendering the form with the supplied ``POST`` data - If the validation succeeded, perform some action and issue a redirect via ``HTTPFound``. We are, in essence, writing our own form controller. Other Pyramid-based systems, including ``pyramid_deform``, provide a form-centric view class which automates much of this branching and routing. Extra Credit ============ #. Give a try at a button that goes to a delete view for a particular wiki page. pyramid-1.6/docs/quick_tutorial/functional_testing/0000755000076500000240000000000012642137501023441 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/functional_testing/development.ini0000644000076500000240000000021612520062551026460 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/functional_testing/setup.py0000644000076500000240000000031612520062551025150 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/functional_testing/tutorial/0000755000076500000240000000000012642137501025304 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/functional_testing/tutorial/__init__.py0000644000076500000240000000056112520062551027414 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('

Hello World!

') def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/functional_testing/tutorial/tests.py0000644000076500000240000000136012520062551027015 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_hello_world(self): from tutorial import hello_world request = testing.DummyRequest() response = hello_world(request) self.assertEqual(response.status_code, 200) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_hello_world(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hello World!

', res.body) pyramid-1.6/docs/quick_tutorial/functional_testing.rst0000644000076500000240000000374212575217552024213 0ustar michaelstaff00000000000000.. _qtut_functional_testing: =================================== 06: Functional Testing with WebTest =================================== Write end-to-end full-stack testing using ``webtest``. Background ========== Unit tests are a common and popular approach to test-driven development (TDD). In web applications, though, the templating and entire apparatus of a web site are important parts of the delivered quality. We'd like a way to test these. WebTest is a Python package that does functional testing. With WebTest you can write tests which simulate a full HTTP request against a WSGI application, then test the information in the response. For speed purposes, WebTest skips the setup/teardown of an actual HTTP server, providing tests that run fast enough to be part of TDD. Objectives ========== - Write a test which checks the contents of the returned HTML Steps ===== #. First we copy the results of the previous step, as well as install the ``webtest`` package: .. code-block:: bash $ cd ..; cp -r unit_testing functional_testing; cd functional_testing $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install webtest #. 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 $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 2 tests in 0.141s OK Analysis ======== We now have the end-to-end testing we were looking for. WebTest lets us simply extend our existing ``nose``-based test approach with functional tests that are reported in the same output. These new tests not only cover our templating, but they didn't dramatically increase the execution time of our tests. Extra Credit ============ #. Why do our functional tests use ``b''``? pyramid-1.6/docs/quick_tutorial/hello_world/0000755000076500000240000000000012642137501022054 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/hello_world/app.py0000644000076500000240000000075412520062551023211 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): print('Incoming request') return Response('

Hello World!

') if __name__ == '__main__': config = Configurator() config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever() pyramid-1.6/docs/quick_tutorial/hello_world.rst0000644000076500000240000000631312520062551022606 0ustar michaelstaff00000000000000.. _qtut_hello_world: ================================ 01: Single-File Web Applications ================================ What's the simplest way to get started in Pyramid? A single-file module. No Python packages, no ``setup.py``, no other machinery. Background ========== Microframeworks are all the rage these days. "Microframework" is a marketing term, not a technical one. They have a low mental overhead: they do so little, the only things you have to worry about are *your things*. Pyramid is special because it can act as a single-file module microframework. You can have a single Python file that can be executed directly by Python. But Pyramid also provides facilities to scale to the largest of applications. Python has a standard called :term:`WSGI` that defines how Python web applications plug into standard servers, getting passed incoming requests and returning responses. Most modern Python web frameworks obey an "MVC" (model-view-controller) application pattern, where the data in the model has a view that mediates interaction with outside systems. In this step we'll see a brief glimpse of WSGI servers, WSGI applications, requests, responses, and views. Objectives ========== - Get a running Pyramid web application, as simply as possible - Use that as a well-understood base for adding each unit of complexity - Initial exposure to WSGI apps, requests, views, and responses Steps ===== #. Make sure you have followed the steps in :doc:`requirements`. #. Starting from your workspace directory (``~/projects/quick_tutorial``), create a directory for this step: .. code-block:: bash $ mkdir hello_world; cd hello_world #. Copy the following into ``hello_world/app.py``: .. literalinclude:: hello_world/app.py :linenos: #. Run the application: .. code-block:: bash $ $VENV/bin/python app.py #. Open http://localhost:6543/ in your browser. Analysis ======== New to Python web programming? If so, some lines in module merit explanation: #. *Line 11*. The ``if __name__ == '__main__':`` is Python's way of saying "Start here when running from the command line", rather than when this module is imported. #. *Lines 12-14*. Use Pyramid's :term:`configurator` to connect :term:`view` code to a particular URL :term:`route`. #. *Lines 6-8*. Implement the view code that generates the :term:`response`. #. *Lines 15-17*. Publish a :term:`WSGI` app using an HTTP server. As shown in this example, the :term:`configurator` plays a central role in Pyramid development. Building an application from loosely-coupled parts via :ref:`configuration_narr` is a central idea in Pyramid, one that we will revisit regularly in this *Quick Tour*. Extra Credit ============ #. Why do we do this: .. code-block:: python print('Incoming request') ...instead of: .. code-block:: python print 'Incoming request' #. What happens if you return a string of HTML? A sequence of integers? #. Put something invalid, such as ``print xyz``, in the view function. Kill your ``python app.py`` with ``cntrl-c`` and restart, then reload your browser. See the exception in the console? #. The ``GI`` in ``WSGI`` stands for "Gateway Interface". What web standard is this modelled after? pyramid-1.6/docs/quick_tutorial/index.rst0000644000076500000240000000165512520062551021407 0ustar michaelstaff00000000000000.. _quick_tutorial: ========================== Quick Tutorial for Pyramid ========================== Pyramid is a web framework for Python 2 and 3. This tutorial gives a Python 3/2-compatible, high-level tour of the major features. This hands-on tutorial covers "a little about a lot": practical introductions to the most common facilities. Fun, fast-paced, and most certainly not aimed at experts of the Pyramid web framework. Contents ======== .. toctree:: :maxdepth: 1 requirements tutorial_approach scaffolds hello_world package ini debugtoolbar unit_testing functional_testing views templating view_classes request_response routing jinja2 static_assets json more_view_classes logging sessions forms databases authentication authorization Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyramid-1.6/docs/quick_tutorial/ini/0000755000076500000240000000000012642137501020321 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/ini/development.ini0000644000076500000240000000014212520062551023336 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/ini/setup.py0000644000076500000240000000031612520062551022030 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/ini/tutorial/0000755000076500000240000000000012642137501022164 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/ini/tutorial/__init__.py0000644000076500000240000000056112520062551024274 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('

Hello World!

') def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/ini.rst0000644000076500000240000001041312575217552021064 0ustar michaelstaff00000000000000.. _qtut_ini: ================================================= 03: Application Configuration with ``.ini`` Files ================================================= Use Pyramid's ``pserve`` command with a ``.ini`` configuration file for simpler, better application running. Background ========== Pyramid has a first-class concept of :ref:`configuration ` 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 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 ========== - Modify our ``setup.py`` to have an entry point telling Pyramid the location of the WSGI app - Create an application driven by a ``.ini`` file - Startup the application with Pyramid's ``pserve`` command - Move code into the package's ``__init__.py`` Steps ===== #. First we copy the results of the previous step: .. code-block:: bash $ cd ..; cp -r package ini; cd ini #. Our ``ini/setup.py`` needs a setuptools "entry point" in the ``setup()`` function: .. literalinclude:: ini/setup.py :linenos: #. We can now install our project, thus generating (or re-generating) an "egg" at ``ini/tutorial.egg-info``: .. code-block:: bash $ $VENV/bin/python setup.py develop #. Let's make a file ``ini/development.ini`` for our configuration: .. literalinclude:: ini/development.ini :language: ini :linenos: #. We can refactor our startup code from the previous step's ``app.py`` into ``ini/tutorial/__init__.py``: .. literalinclude:: ini/tutorial/__init__.py :linenos: #. Now that ``ini/tutorial/app.py`` isn't used, let's remove it: .. code-block:: bash $ rm tutorial/app.py #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/. Analysis ======== Our ``development.ini`` file is read by ``pserve`` and serves to bootstrap our application. Processing then proceeds as described in the Pyramid chapter on :ref:`application startup `: - ``pserve`` looks for ``[app:main]`` and finds ``use = egg:tutorial`` - The projects's ``setup.py`` has defined an "entry point" (lines 9-12) for the project "main" entry point of ``tutorial:main`` - The ``tutorial`` package's ``__init__`` has a ``main`` function - This function is invoked, with the values from certain ``.ini`` sections passed in The ``.ini`` file is also used for two other functions: - *Configuring the WSGI server*. ``[server:main]`` wires up the choice of which WSGI *server* for your WSGI *application*. In this case, we are using ``wsgiref`` bundled in the Python library. It also wires up the *port number*: ``port = 6543`` tells ``wsgiref`` to listen on port 6543. - *Configuring Python logging*. Pyramid uses Python standard logging, which needs a number of configuration values. The ``.ini`` serves this function. This provides the console log output that you see on startup and each request. We moved our startup code from ``app.py`` to the package's ``tutorial/__init__.py``. This isn't necessary, but it is a common style in Pyramid to take the WSGI app bootstrapping out of your module's code and put it in the package's ``__init__.py``. The ``pserve`` application runner has a number of command-line arguments and options. We are using ``--reload`` which tells ``pserve`` to watch the filesystem for changes to relevant code (Python files, the INI file, etc.) and, when something changes, restart the application. Very handy during development. Extra Credit ============ #. If you don't like configuration and/or ``.ini`` files, could you do this yourself in Python code? #. Can we have multiple ``.ini`` configuration files for a project? Why might you want to do that? #. The entry point in ``setup.py`` didn't mention ``__init__.py`` when it declared ``tutorial:main`` function. Why not? #. What is the purpose of ``**settings``? What does the ``**`` signify? .. seealso:: :ref:`project_narr`, :ref:`scaffolding_chapter`, :ref:`what_is_this_pserve_thing`, :ref:`environment_chapter`, :ref:`paste_chapter` pyramid-1.6/docs/quick_tutorial/jinja2/0000755000076500000240000000000012642137501020717 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/jinja2/development.ini0000644000076500000240000000025612520062551023742 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/jinja2/setup.py0000644000076500000240000000031612520062551022426 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/jinja2/tutorial/0000755000076500000240000000000012642137501022562 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/jinja2/tutorial/__init__.py0000644000076500000240000000044612520062551024674 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_jinja2') config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/jinja2/tutorial/home.jinja20000644000076500000240000000021412575217552024620 0ustar michaelstaff00000000000000 Quick Tutorial: {{ name }}

Hi {{ name }}

pyramid-1.6/docs/quick_tutorial/jinja2/tutorial/tests.py0000644000076500000240000000216112520062551024273 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/jinja2/tutorial/views.py0000644000076500000240000000060112520062551024263 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.jinja2') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/jinja2.rst0000644000076500000240000000500712575217552021465 0ustar michaelstaff00000000000000.. _qtut_jinja2: ============================== 12: Templating With ``jinja2`` ============================== We just said Pyramid doesn't prefer one templating language over another. Time to prove it. Jinja2 is a popular templating system, used in Flask and modeled after Django's templates. Let's add ``pyramid_jinja2``, a Pyramid :term:`add-on` which enables Jinja2 as a :term:`renderer` in our Pyramid applications. Objectives ========== - Show Pyramid's support for different templating systems - Learn about installing Pyramid add-ons Steps ===== #. 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 $ cd ..; cp -r view_classes jinja2; cd jinja2 $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install pyramid_jinja2 #. We need to include ``pyramid_jinja2`` in ``jinja2/tutorial/__init__.py``: .. literalinclude:: jinja2/tutorial/__init__.py :linenos: #. Our ``jinja2/tutorial/views.py`` simply changes its ``renderer``: .. literalinclude:: jinja2/tutorial/views.py :linenos: #. Add ``jinja2/tutorial/home.jinja2`` as a template: .. literalinclude:: jinja2/tutorial/home.jinja2 :language: html #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in your browser. Analysis ======== Getting a Pyramid add-on into Pyramid is simple. First you use normal Python package installation tools to install the add-on package into your Python. You then tell Pyramid's configurator to run the setup code in the add-on. In this case the setup code told Pyramid to make a new "renderer" available that looked for ``.jinja2`` file extensions. 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. Extra Credit ============ #. Our project now depends on ``pyramid_jinja2``. We installed that dependency manually. What is another way we could have made the association? #. 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 `_, and :ref:`pyramid_jinja2 Overview ` pyramid-1.6/docs/quick_tutorial/json/0000755000076500000240000000000012642137501020513 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/json/development.ini0000644000076500000240000000025612520062551023536 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/json/setup.py0000644000076500000240000000034612520062551022225 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/json/tutorial/0000755000076500000240000000000012642137501022356 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/json/tutorial/__init__.py0000644000076500000240000000053212520062551024464 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.add_route('hello_json', 'howdy.json') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/json/tutorial/home.pt0000644000076500000240000000020612575217552023663 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

pyramid-1.6/docs/quick_tutorial/json/tutorial/tests.py0000644000076500000240000000250612520062551024072 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) def test_hello_json(self): res = self.testapp.get('/howdy.json', status=200) self.assertIn(b'{"name": "Hello View"}', res.body) self.assertEqual(res.content_type, 'application/json') pyramid-1.6/docs/quick_tutorial/json/tutorial/views.py0000644000076500000240000000067012520062551024065 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') @view_config(route_name='hello_json', renderer='json') def hello(self): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/json.rst0000644000076500000240000000632512520062551021250 0ustar michaelstaff00000000000000.. _qtut_json: ======================================== 14: Ajax Development With JSON Renderers ======================================== Modern web apps are more than rendered HTML. Dynamic pages now use JavaScript to update the UI in the browser by requesting server data as JSON. Pyramid supports this with a *JSON renderer*. Background ========== As we saw in :doc:`templating`, view declarations can specify a renderer. Output from the view is then run through the renderer, which generates and returns the ``Response``. We first used a Chameleon renderer, then a Jinja2 renderer. Renderers aren't limited, however, to templates that generate HTML. Pyramid supplies a JSON renderer which takes Python data, serializes it to JSON, and performs some other functions such as setting the content type. In fact, you can write your own renderer (or extend a built-in renderer) containing custom logic for your unique application. Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes json; cd json $ $VENV/bin/python setup.py develop #. We add a new route for ``hello_json`` in ``json/tutorial/__init__.py``: .. literalinclude:: json/tutorial/__init__.py :linenos: #. Rather than implement a new view, we will "stack" another decorator on the ``hello`` view in ``views.py``: .. literalinclude:: json/tutorial/views.py :linenos: #. We need a new functional test at the end of ``json/tutorial/tests.py``: .. literalinclude:: json/tutorial/tests.py :linenos: #. Run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/howdy.json in your browser and you will see the resulting JSON response. Analysis ======== Earlier we changed our view functions and methods to return Python data. This change to a data-oriented view layer made test writing easier, decoupling the templating from the view logic. Since Pyramid has a JSON renderer as well as the templating renderers, it is an easy step to return JSON. In this case we kept the exact same view and arranged to return a JSON encoding of the view data. We did this by: - Adding a route to map ``/howdy.json`` to a route name - Providing a ``@view_config`` that associated that route name with an existing view - *overriding* the view defaults in the view config that mentions the ``hello_json`` route, so that when the route is matched, we use the JSON renderer rather than the ``home.pt`` template renderer that would otherwise be used. In fact, for pure Ajax-style web applications, we could re-use the existing route by using Pyramid's view predicates to match on the ``Accepts:`` header sent by modern Ajax implementation. Pyramid's JSON renderer uses the base Python JSON encoder, thus inheriting its strengths and weaknesses. For example, Python can't natively JSON encode DateTime objects. There are a number of solutions for this in Pyramid, including extending the JSON renderer with a custom renderer. .. seealso:: :ref:`views_which_use_a_renderer`, :ref:`json_renderer`, and :ref:`adding_and_overriding_renderers` pyramid-1.6/docs/quick_tutorial/logging/0000755000076500000240000000000012642137501021170 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/logging/development.ini0000644000076500000240000000116612520062551024214 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] 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 pyramid-1.6/docs/quick_tutorial/logging/setup.py0000644000076500000240000000034612520062551022702 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/logging/tutorial/0000755000076500000240000000000012642137501023033 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/logging/tutorial/__init__.py0000644000076500000240000000045112520062551025141 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/logging/tutorial/home.pt0000644000076500000240000000020612575217552024340 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

pyramid-1.6/docs/quick_tutorial/logging/tutorial/tests.py0000644000076500000240000000216112520062551024544 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/logging/tutorial/views.py0000644000076500000240000000076412520062551024546 0ustar michaelstaff00000000000000import logging log = logging.getLogger(__name__) from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): log.debug('In home view') return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): log.debug('In hello view') return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/logging.rst0000644000076500000240000000435112575217552021737 0ustar michaelstaff00000000000000.. _qtut_logging: ============================================ 16: Collecting Application Info With Logging ============================================ Capture debugging and error output from your web applications using standard Python logging. Background ========== It's important to know what is going on inside our web application. In development we might need to collect some output. In production, 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`` 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. Objectives ========== - Inspect the configuration setup used for logging - Add logging statements to your view code Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes logging; cd logging $ $VENV/bin/python setup.py develop #. Extend ``logging/tutorial/views.py`` to log a message: .. literalinclude:: logging/tutorial/views.py :linenos: #. Finally let's edit ``development.ini`` configuration file to enable logging for our Pyramid application: .. literalinclude:: logging/development.ini :language: ini #. Make sure the tests still pass: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ and http://localhost:6543/howdy in your browser. Note, both in the console and in the debug toolbar, the message that you logged. Analysis ======== In our configuration file ``development.ini``, our ``tutorial`` Python package is setup as a logger and configured to log messages at a ``DEBUG`` or higher level. When you visit http://localhost:6543 your console will now show:: 2013-08-09 10:42:42,968 DEBUG [tutorial.views][MainThread] In home view Also, if you have configured your Pyramid application to use the ``pyramid_debugtoolbar``, logging statements appear in one of its menus. .. seealso:: See also :ref:`logging_chapter`. pyramid-1.6/docs/quick_tutorial/more_view_classes/0000755000076500000240000000000012642137501023253 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/more_view_classes/development.ini0000644000076500000240000000025612520062551026276 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/more_view_classes/setup.py0000644000076500000240000000034612520062551024765 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/0000755000076500000240000000000012642137501025116 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/__init__.py0000644000076500000240000000047012520062551027225 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy/{first}/{last}') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/delete.pt0000644000076500000240000000024312575217552026736 0ustar michaelstaff00000000000000 Quick Tutorial: ${page_title}

${view.view_name} - ${page_title}

pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/edit.pt0000644000076500000240000000034512575217552026424 0ustar michaelstaff00000000000000 Quick Tutorial: ${view.view_name} - ${page_title}

${view.view_name} - ${page_title}

You submitted ${new_name}

pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/hello.pt0000644000076500000240000000066712575217552026611 0ustar michaelstaff00000000000000 Quick Tutorial: ${view.view_name} - ${page_title}

${view.view_name} - ${page_title}

Welcome, ${view.full_name}

pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/home.pt0000644000076500000240000000043512575217552026427 0ustar michaelstaff00000000000000 Quick Tutorial: ${view.view_name} - ${page_title}

${view.view_name} - ${page_title}

Go to the form.

pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/tests.py0000644000076500000240000000141412520062551026627 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['page_title']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'TutorialViews - Home View', res.body) pyramid-1.6/docs/quick_tutorial/more_view_classes/tutorial/views.py0000644000076500000240000000227212520062551026625 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(route_name='hello') class TutorialViews(object): def __init__(self, request): self.request = request self.view_name = 'TutorialViews' @property def full_name(self): first = self.request.matchdict['first'] last = self.request.matchdict['last'] return first + ' ' + last @view_config(route_name='home', renderer='home.pt') def home(self): return {'page_title': 'Home View'} # Retrieving /howdy/first/last the first time @view_config(renderer='hello.pt') def hello(self): return {'page_title': 'Hello View'} # 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 /howdy/first/last via the "Delete" submit button @view_config(request_method='POST', request_param='form.delete', renderer='delete.pt') def delete(self): print ('Deleted') return {'page_title': 'Delete View'} pyramid-1.6/docs/quick_tutorial/more_view_classes.rst0000644000076500000240000001362212575217552024023 0ustar michaelstaff00000000000000.. _qtut_more_view_classes: ========================== 15: More With View Classes ========================== Group views into a class, sharing configuration, state, and logic. Background ========== As part of its mission to help build more ambitious web applications, Pyramid provides many more features for views and view classes. The Pyramid documentation discusses views as a Python "callable". This callable can be a function, an object with an ``__call__``, or a Python class. In this last case, methods on the class can be decorated with ``@view_config`` to register the class methods with the :term:`configurator` as a view. At first, our views were simple, free-standing functions. Many times your views are related: different ways to look at or work on the same data or a REST API that handles multiple operations. Grouping these together as a :ref:`view class ` makes sense: - Group views - Centralize some repetitive defaults - Share some state and helpers Pyramid views have :ref:`view predicates ` 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: view a home page which leads to a form, save a change, and press the delete button. Objectives ========== - Group related views into a view class - Centralize configuration with class-level ``@view_defaults`` - Dispatch one route/URL to multiple views based on request data - Share states and logic between views and templates via the view class Steps ===== #. First we copy the results of the previous step: .. code-block:: bash $ cd ..; cp -r templating more_view_classes; cd more_view_classes $ $VENV/bin/python setup.py develop #. Our route in ``more_view_classes/tutorial/__init__.py`` needs some replacement patterns: .. literalinclude:: more_view_classes/tutorial/__init__.py :linenos: #. Our ``more_view_classes/tutorial/views.py`` now has a view class with several views: .. literalinclude:: more_view_classes/tutorial/views.py :linenos: #. Our primary view needs a template at ``more_view_classes/tutorial/home.pt``: .. literalinclude:: more_view_classes/tutorial/home.pt :language: html #. Ditto for our other view from the previous section at ``more_view_classes/tutorial/hello.pt``: .. literalinclude:: more_view_classes/tutorial/hello.pt :language: html #. We have an edit view that also needs a template at ``more_view_classes/tutorial/edit.pt``: .. literalinclude:: more_view_classes/tutorial/edit.pt :language: html #. And finally the delete view's template at ``more_view_classes/tutorial/delete.pt``: .. literalinclude:: more_view_classes/tutorial/delete.pt :language: html #. Our tests in ``more_view_classes/tutorial/tests.py`` fail, so let's modify them: .. literalinclude:: more_view_classes/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 2 tests in 0.248s OK #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/howdy/jane/doe in your browser. Click the ``Save`` and ``Delete`` buttons and watch the output in the console window. Analysis ======== As you can see, the four views are logically grouped together. Specifically: - We have a ``home`` view available at http://localhost:6543/ with a clickable link to the ``hello`` view. - The second view is returned when you go to ``/howdy/jane/doe``. This URL is mapped to the ``hello`` route that we centrally set using the optional ``@view_defaults``. - The third view is returned when the form is submitted with a ``POST`` method. This rule is specified in the ``@view_config`` for that view. - The fourth view is returned when clicking on a button such as ````. In this step we show, using the following information as criteria, how to decide which view to use: - Method of the HTTP request (``GET``, ``POST``, etc.) - Parameter information in the request (submitted form field names) We also centralize part of the view configuration to the class level with ``@view_defaults``, then in one view, override that default just for that one view. Finally, we put this commonality between views to work in the view class by sharing: - State assigned in ``TutorialViews.__init__`` - A computed value These are then available both in the view methods but also in the templates (e.g. ``${view.view_name}`` and ``${view.full_name}``. As a note, we made a switch in our templates on how we generate URLs. We previously hardcode the URLs, such as:: Howdy In ``home.pt`` we switched to:: form Pyramid has rich facilities to help generate URLs in a flexible, non-error-prone fashion. Extra Credit ============ #. Why could our template do ``${view.full_name}`` and not have to do ``${view.full_name()}``? #. The ``edit`` and ``delete`` views are both submitted to with ``POST``. Why does the ``edit`` view configuration not catch the ``POST`` used by ``delete``? #. We used Python ``@property`` on ``full_name``. If we reference this many times in a template or view code, it would re-compute this every time. Does Pyramid provide something that will cache the initial computation on a property? #. Can you associate more than one route with the same view? #. There is also a ``request.route_path`` API. How does this differ from ``request.route_url``? .. seealso:: :ref:`class_as_view`, `Weird Stuff You Can Do With URL Dispatch `_ pyramid-1.6/docs/quick_tutorial/package/0000755000076500000240000000000012642137501021135 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/package/setup.py0000644000076500000240000000016612520062551022647 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, )pyramid-1.6/docs/quick_tutorial/package/tutorial/0000755000076500000240000000000012642137501023000 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/package/tutorial/__init__.py0000644000076500000240000000001112520062551025076 0ustar michaelstaff00000000000000# packagepyramid-1.6/docs/quick_tutorial/package/tutorial/app.py0000644000076500000240000000075412520062551024135 0ustar michaelstaff00000000000000from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): print ('Incoming request') return Response('

Hello World!

') if __name__ == '__main__': config = Configurator() config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('0.0.0.0', 6543, app) server.serve_forever()pyramid-1.6/docs/quick_tutorial/package.rst0000644000076500000240000000700212575217552021700 0ustar michaelstaff00000000000000============================================ 02: Python Packages for Pyramid Applications ============================================ Most modern Python development is done using Python packages, an approach Pyramid puts to good use. In this step we redo "Hello World" as a minimum Python package inside a minimum Python project. Background ========== Python developers can organize a collection of modules and files into a namespaced unit called a :ref:`package `. If a directory is on ``sys.path`` and has a special file named ``__init__.py``, it is treated as a Python package. Packages can be bundled up, made available for installation, and installed through a (muddled, but improving) toolchain oriented around a ``setup.py`` file for a `setuptools project `_. Explaining it all in this tutorial will induce madness. For this tutorial, this is all you need to know: - We will have a directory for each tutorial step as a setuptools *project* - This project will contain a ``setup.py`` which injects the features of the setuptool's project machinery into the directory - In this project we will make a ``tutorial`` subdirectory into a Python *package* using an ``__init__.py`` Python module file - We will run ``python setup.py develop`` to install our project in development mode In summary: - You'll do your development in a Python *package* - That package will be part of a setuptools *project* Objectives ========== - Make a Python "package" directory with an ``__init__.py`` - Get a minimum Python "project" in place by making a ``setup.py`` - Install our ``tutorial`` project in development mode Steps ===== #. Make an area for this tutorial step: .. code-block:: bash $ cd ..; mkdir package; cd package #. In ``package/setup.py``, enter the following: .. literalinclude:: package/setup.py #. Make the new project installed for development then make a directory for the actual code: .. code-block:: bash $ $VENV/bin/python setup.py develop $ mkdir tutorial #. Enter the following into ``package/tutorial/__init__.py``: .. literalinclude:: package/tutorial/__init__.py #. Enter the following into ``package/tutorial/app.py``: .. literalinclude:: package/tutorial/app.py #. Run the WSGI application with: .. code-block:: bash $ $VENV/bin/python tutorial/app.py #. Open http://localhost:6543/ in your browser. Analysis ======== Python packages give us an organized unit of project development. Python projects, via ``setup.py``, gives us special features when our package is installed (in this case, in local development mode.) In this step we have a Python package called ``tutorial``. We use the same name in each step of the tutorial, to avoid unnecessary retyping. Above this ``tutorial`` directory we have the files that handle the packaging of this project. At the moment, all we need is a bare-bones ``setup.py``. Everything else is the same about our application. We simply made a Python package with a ``setup.py`` and installed it in development mode. Note that the way we're running the app (``python tutorial/app.py``) is a bit of an odd duck. We would never do this unless we were writing a tutorial that tries to capture how this stuff works a step at a time. It's generally a bad idea to run a Python module inside a package directly as a script. .. seealso:: :ref:`Python Packages `, `setuptools Entry Points `_ pyramid-1.6/docs/quick_tutorial/request_response/0000755000076500000240000000000012642137501023150 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/request_response/development.ini0000644000076500000240000000025612520062551026173 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/request_response/setup.py0000644000076500000240000000031612520062551024657 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/request_response/tutorial/0000755000076500000240000000000012642137501025013 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/request_response/tutorial/__init__.py0000644000076500000240000000040112520062551027114 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('home', '/') config.add_route('plain', '/plain') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/request_response/tutorial/tests.py0000644000076500000240000000267412520062551026535 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual(response.status, '302 Found') def test_plain_without_name(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.plain() self.assertIn(b'No Name Provided', response.body) def test_plain_with_name(self): from .views import TutorialViews request = testing.DummyRequest() request.GET['name'] = 'Jane Doe' inst = TutorialViews(request) response = inst.plain() self.assertIn(b'Jane Doe', response.body) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_plain_without_name(self): res = self.testapp.get('/plain', status=200) self.assertIn(b'No Name Provided', res.body) def test_plain_with_name(self): res = self.testapp.get('/plain?name=Jane%20Doe', status=200) self.assertIn(b'Jane Doe', res.body) pyramid-1.6/docs/quick_tutorial/request_response/tutorial/views.py0000644000076500000240000000112412520062551026515 0ustar michaelstaff00000000000000from pyramid.httpexceptions import HTTPFound from pyramid.response import Response from pyramid.view import view_config class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): return HTTPFound(location='/plain') @view_config(route_name='plain') def plain(self): name = self.request.params.get('name', 'No Name Provided') body = 'URL %s with name: %s' % (self.request.url, name) return Response( content_type='text/plain', body=body ) pyramid-1.6/docs/quick_tutorial/request_response.rst0000644000076500000240000000575412575217552023727 0ustar michaelstaff00000000000000.. _qtut_request_response: ======================================= 10: Handling Web Requests and Responses ======================================= Web applications handle incoming requests and return outgoing responses. Pyramid makes working with requests and responses convenient and reliable. Objectives ========== - Learn the background on Pyramid's choices for requests and responses - Grab data out of the request - Change information in the response headers Background ========== Developing for the web means processing web requests. As this is a critical part of a web application, web developers need a robust, mature set of software for web requests and returning web responses. Pyramid has always fit nicely into the existing world of Python web development (virtual environments, packaging, scaffolding, first to embrace Python 3, etc.) For request handling, Pyramid turned to the well-regarded :term:`WebOb` Python library for request and response handling. In our example above, Pyramid hands ``hello_world`` a ``request`` that is :ref:`based on WebOb `. Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes request_response; cd request_response $ $VENV/bin/python setup.py develop #. Simplify the routes in ``request_response/tutorial/__init__.py``: .. literalinclude:: request_response/tutorial/__init__.py :linenos: #. We only need one view in ``request_response/tutorial/views.py``: .. literalinclude:: request_response/tutorial/views.py :linenos: #. Update the tests in ``request_response/tutorial/tests.py``: .. literalinclude:: request_response/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in your browser. You will be redirected to http://localhost:6543/plain #. Open http://localhost:6543/plain?name=alice in your browser. Analysis ======== In this view class we have two routes and two views, with the first leading to the second by an HTTP redirect. Pyramid can :ref:`generate redirects ` by returning a special object from a view or raising a special exception. In this Pyramid view, we get the URL being visited from ``request.url``. Also, if you visited http://localhost:6543/plain?name=alice, the name is included in the body of the response:: URL http://localhost:6543/plain?name=alice with name: alice Finally, we set the response's content type and body, then return the Response. We updated the unit and functional tests to prove that our code does the redirection, but also handles sending and not sending ``/plain?name``. Extra Credit ============ #. Could we also ``raise HTTPFound(location='/plain')`` instead of returning it? If so, what's the difference? .. seealso:: :ref:`webob_chapter`, :ref:`generate redirects ` pyramid-1.6/docs/quick_tutorial/requirements.rst0000644000076500000240000002057212575217552023037 0ustar michaelstaff00000000000000.. _qtut_requirements: ============ Requirements ============ Let's get our tutorial environment setup. Most of the setup work is in standard Python development practices (install Python, make an isolated environment, and setup packaging tools.) .. note:: Pyramid encourages standard Python development practices with packaging tools, virtual environments, logging, and so on. There are many variations, implementations, and opinions across the Python community. For consistency, ease of documentation maintenance, and to minimize confusion, the Pyramid *documentation* has adopted specific conventions. This *Quick Tutorial* is based on: * **Python 3.3**. Pyramid fully supports Python 3.2+ and Python 2.6+. This tutorial uses **Python 3.3** but runs fine under Python 2.7. * **pyvenv**. We believe in virtual environments. For this tutorial, we use Python 3.3's built-in solution, the ``pyvenv`` command. For Python 2.7, you can install ``virtualenv``. * **setuptools and easy_install**. We use `setuptools `_ and its ``easy_install`` for package management. * **Workspaces, projects, and packages.** Our home directory will contain a *tutorial workspace* with our Python virtual environment(s) and *Python projects* (a directory with packaging information and *Python packages* of working code.) * **Unix commands**. Commands in this tutorial use UNIX syntax and paths. Windows users should adjust commands accordingly. .. note:: Pyramid was one of the first web frameworks to fully support Python 3 in October 2011. Steps ===== #. :ref:`install-python-3.3-or-greater` #. :ref:`create-a-project-directory-structure` #. :ref:`set-an-environment-variable` #. :ref:`create-a-virtual-environment` #. :ref:`install-setuptools-(python-packaging-tools)` #. :ref:`install-pyramid` .. _install-python-3.3-or-greater: Install Python 3.3 or greater ----------------------------- Download the latest standard Python 3.3+ release (not development release) from `python.org `_. Windows and Mac OS X users can download and run an installer. Windows users should also install the `Python for Windows extensions `_. Carefully read the ``README.txt`` file at the end of the list of builds, and follow its directions. Make sure you get the proper 32- or 64-bit build and Python version. Linux users can either use their package manager to install Python 3.3 or may `build Python 3.3 from source `_. .. _create-a-project-directory-structure: Create a project directory structure ------------------------------------ We will arrive at a directory structure of ``workspace->project->package``, with our workspace named ``quick_tutorial``. The following tree diagram shows how this will be structured and where our virtual environment will reside as we proceed through the tutorial: .. code-block:: text └── ~ └── projects └── quick_tutorial ├── env └── step_one ├── intro │ ├── __init__.py │ └── app.py └── setup.py For Linux, the commands to do so are as follows: .. code-block:: bash # Mac and Linux $ cd ~ $ mkdir -p projects/quick_tutorial $ cd projects/quick_tutorial For Windows: .. code-block:: posh # Windows c:\> cd \ c:\> mkdir projects\quick_tutorial c:\> cd projects\quick_tutorial In the above figure, your user home directory is represented by ``~``. In your home directory, all of your projects are in the ``projects`` directory. This is a general convention not specific to Pyramid that many developers use. Windows users will do well to use ``c:\`` as the location for ``projects`` in order to avoid spaces in any of the path names. Next within ``projects`` is your workspace directory, here named ``quick_tutorial``. A workspace is a common term used by integrated development environments (IDE) like PyCharm and PyDev that stores isolated Python environments (virtualenvs) and specific project files and repositories. .. _set-an-environment-variable: Set an Environment Variable --------------------------- This tutorial will refer frequently to the location of the virtual environment. We set an environment variable to save typing later. .. code-block:: bash # Mac and Linux $ export VENV=~/projects/quick_tutorial/env # Windows # TODO: This command does not work c:\> set VENV=c:\projects\quick_tutorial\env .. _create-a-virtual-environment: Create a Virtual Environment ---------------------------- .. warning:: The current state of isolated Python environments using ``pyvenv`` on Windows is suboptimal in comparison to Mac and Linux. See http://stackoverflow.com/q/15981111/95735 for a discussion of the issue and `PEP 453 `_ for a proposed resolution. ``pyvenv`` is a tool to create isolated Python 3.3 environments, each with its own Python binary and independent set of installed Python packages in its site directories. Let's create one, using the location we just specified in the environment variable. .. code-block:: bash # Mac and Linux $ pyvenv $VENV # Windows c:\> c:\Python33\python -m venv %VENV% .. seealso:: See also Python 3's :mod:`venv module `, Python 2's `virtualenv `_ package, :ref:`Installing Pyramid on a Windows System ` .. _install-setuptools-(python-packaging-tools): Install ``setuptools`` (Python packaging tools) ----------------------------------------------- The following command will download a script to install ``setuptools``, then pipe it to your environment's version of Python. .. code-block:: bash # Mac and Linux $ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python # Windows # # 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: .. code-block:: bash # Mac and Linux $ wget --no-check-certificate https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | $VENV/bin/python .. _install-pyramid: Install Pyramid --------------- We have our Python standard prerequisites out of the way. The Pyramid part is pretty easy: .. parsed-literal:: # Mac and Linux $ $VENV/bin/easy_install "pyramid==\ |release|\ " # Windows c:\\> %VENV%\\Scripts\\easy_install "pyramid==\ |release|\ " Our Python virtual environment now has the Pyramid software available. You can optionally install some of the extra Python packages used during this tutorial: .. code-block:: bash # Mac and Linux $ $VENV/bin/easy_install nose webtest deform sqlalchemy \ pyramid_chameleon pyramid_debugtoolbar waitress \ pyramid_tm zope.sqlalchemy # Windows c:\> %VENV%\Scripts\easy_install nose webtest deform sqlalchemy pyramid_chameleon pyramid_debugtoolbar waitress pyramid_tm zope.sqlalchemy .. note:: Why ``easy_install`` and not ``pip``? Pyramid encourages use of namespace packages, for which ``pip``'s support is less-than-optimal. Also, Pyramid's dependencies use some optional C extensions for performance: with ``easy_install``, Windows users can get these extensions without needing a C compiler (``pip`` does not support installing binary Windows distributions, except for ``wheels``, which are not yet available for all dependencies). .. seealso:: See also :ref:`installing_unix`. For instructions to set up your Python environment for development using Windows or Python 2, see Pyramid's :ref:`Before You Install `. See also Python 3's :mod:`venv module `, the `setuptools installation instructions `_, and `easy_install help `_. pyramid-1.6/docs/quick_tutorial/retail_forms/0000755000076500000240000000000012642137501022230 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/retail_forms/development.ini0000644000076500000240000000025612520062551025253 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/retail_forms/setup.py0000644000076500000240000000036412520062551023742 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon', 'deform' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/0000755000076500000240000000000012642137501024073 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/__init__.py0000644000076500000240000000072712520062551026207 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('wiki_view', '/') config.add_route('wikipage_add', '/add') config.add_route('wikipage_view', '/{uid}') config.add_route('wikipage_edit', '/{uid}/edit') config.add_static_view('deform_static', 'deform:static/') config.scan('.views') return config.make_wsgi_app() pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/tests.py0000644000076500000240000000146512520062551025612 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import WikiViews request = testing.DummyRequest() inst = WikiViews(request) response = inst.wiki_view() self.assertEqual(len(response['pages']), 3) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): testing.tearDown() def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'Wiki: View', res.body) pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/views.py0000644000076500000240000000566512520062551025613 0ustar michaelstaff00000000000000import colander import deform.widget from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config pages = { '100': dict(uid='100', title='Page 100', body='100'), '101': dict(uid='101', title='Page 101', body='101'), '102': dict(uid='102', title='Page 102', body='102') } class WikiPage(colander.MappingSchema): title = colander.SchemaNode(colander.String()) body = colander.SchemaNode( colander.String(), widget=deform.widget.RichTextWidget() ) class WikiViews(object): def __init__(self, request): self.request = request @property def wiki_form(self): schema = WikiPage() return deform.Form(schema, buttons=('submit',)) @property def reqts(self): return self.wiki_form.get_widget_resources() @view_config(route_name='wiki_view', renderer='wiki_view.pt') def wiki_view(self): return dict(pages=pages.values()) @view_config(route_name='wikipage_add', renderer='wikipage_addedit.pt') def wikipage_add(self): form = self.wiki_form if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = self.wiki_form.validate(controls) except deform.ValidationFailure as e: # Form is NOT valid return dict(form=e.render()) # Form is valid, make a new identifier and add to list last_uid = int(sorted(pages.keys())[-1]) new_uid = str(last_uid + 1) pages[new_uid] = dict( uid=new_uid, title=appstruct['title'], body=appstruct['body'] ) # Now visit new page url = self.request.route_url('wikipage_view', uid=new_uid) return HTTPFound(url) return dict(form=form) @view_config(route_name='wikipage_view', renderer='wikipage_view.pt') def wikipage_view(self): uid = self.request.matchdict['uid'] page = pages[uid] return dict(page=page) @view_config(route_name='wikipage_edit', renderer='wikipage_addedit.pt') def wikipage_edit(self): uid = self.request.matchdict['uid'] page = pages[uid] wiki_form = self.wiki_form if 'submit' in self.request.params: controls = self.request.POST.items() try: appstruct = wiki_form.validate(controls) except deform.ValidationFailure as e: return dict(page=page, form=e.render()) # Change the content and redirect to the view page['title'] = appstruct['title'] page['body'] = appstruct['body'] url = self.request.route_url('wikipage_view', uid=page['uid']) return HTTPFound(url) form = wiki_form.render(page) return dict(page=page, form=form)pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt0000644000076500000240000000053712520062551026437 0ustar michaelstaff00000000000000 Wiki: View

Wiki

Add WikiPage pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt0000644000076500000240000000171512520062551027717 0ustar michaelstaff00000000000000 WikiPage: Add/Edit

Wiki

${structure:field.title} *
${structure:field.serialize()}
  • ${structure:error}
pyramid-1.6/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt0000644000076500000240000000044712520062551027274 0ustar michaelstaff00000000000000 WikiPage: View Up | Edit

${page.title}

${structure: page.body}

pyramid-1.6/docs/quick_tutorial/routing/0000755000076500000240000000000012642137501021231 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/routing/development.ini0000644000076500000240000000025612520062551024254 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/routing/setup.py0000644000076500000240000000034612520062551022743 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/routing/tutorial/0000755000076500000240000000000012642137501023074 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/routing/tutorial/__init__.py0000644000076500000240000000042512520062551025203 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/howdy/{first}/{last}') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/routing/tutorial/home.pt0000644000076500000240000000025112575217552024401 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

${name}

First: ${first}, Last: ${last}

pyramid-1.6/docs/quick_tutorial/routing/tutorial/tests.py0000644000076500000240000000166012520062551024610 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() request.matchdict['first'] = 'First' request.matchdict['last'] = 'Last' inst = TutorialViews(request) response = inst.home() self.assertEqual(response['first'], 'First') self.assertEqual(response['last'], 'Last') class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/howdy/Jane/Doe', status=200) self.assertIn(b'Jane', res.body) self.assertIn(b'Doe', res.body) pyramid-1.6/docs/quick_tutorial/routing/tutorial/views.py0000644000076500000240000000070612520062551024603 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): first = self.request.matchdict['first'] last = self.request.matchdict['last'] return { 'name': 'Home View', 'first': first, 'last': last } pyramid-1.6/docs/quick_tutorial/routing.rst0000644000076500000240000000644012575217552022001 0ustar michaelstaff00000000000000.. _qtut_routing: ========================================== 11: Dispatching URLs To Views With Routing ========================================== Routing matches incoming URL patterns to view code. Pyramid's routing has a number of useful features. Background ========== Writing web applications usually means sophisticated URL design. We just saw some Pyramid machinery for requests and views. Let's look at features that help in routing. Previously we saw the basics of routing URLs to views in Pyramid. - Your project's "setup" code registers a route name to be used when matching part of the URL - Elsewhere, a view is configured to be called for that route name .. note:: Why do this twice? Other Python web frameworks let you create a route and associate it with a view in one step. As illustrated in :ref:`routes_need_ordering`, multiple routes might match the same URL pattern. Rather than provide ways to help guess, Pyramid lets you be explicit in ordering. Pyramid also gives facilities to avoid the problem. It's relatively easy to build a system that uses implicit route ordering with Pyramid too. See `The Groundhog series of screencasts `_ if you're interested in doing so. Objectives ========== - Define a route that extracts part of the URL into a Python dictionary - Use that dictionary data in a view Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes routing; cd routing $ $VENV/bin/python setup.py develop #. Our ``routing/tutorial/__init__.py`` needs a route with a replacement pattern: .. literalinclude:: routing/tutorial/__init__.py :linenos: #. We just need one view in ``routing/tutorial/views.py``: .. literalinclude:: routing/tutorial/views.py :linenos: #. We just need one view in ``routing/tutorial/home.pt``: .. literalinclude:: routing/tutorial/home.pt :language: html :linenos: #. Update ``routing/tutorial/tests.py``: .. literalinclude:: routing/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/howdy/amy/smith in your browser. Analysis ======== In ``__init__.py`` we see an important change in our route declaration: .. code-block:: python config.add_route('hello', '/howdy/{first}/{last}') With this we tell the :term:`configurator` that our URL has a "replacement pattern". With this, URLs such as ``/howdy/amy/smith`` will assign ``amy`` to ``first`` and ``smith`` to ``last``. We can then use this data in our view: .. code-block:: python self.request.matchdict['first'] self.request.matchdict['last'] ``request.matchdict`` contains values from the URL that match the "replacement patterns" (the curly braces) in the route declaration. This information can then be used anywhere in Pyramid that has access to the request. Extra Credit ============ #. What happens if you to go the URL http://localhost:6543/howdy? Is this the result that you expected? .. seealso:: `Weird Stuff You Can Do With URL Dispatch `_ pyramid-1.6/docs/quick_tutorial/scaffolds/0000755000076500000240000000000012642137501021506 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/scaffolds/CHANGES.txt0000644000076500000240000000003412520062551023311 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/quick_tutorial/scaffolds/development.ini0000644000076500000240000000211012520062551024520 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:scaffolds pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, scaffolds [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_scaffolds] level = DEBUG handlers = qualname = scaffolds [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 pyramid-1.6/docs/quick_tutorial/scaffolds/MANIFEST.in0000644000076500000240000000020412520062551023235 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include scaffolds *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/quick_tutorial/scaffolds/production.ini0000644000076500000240000000162712520062551024400 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:scaffolds pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, scaffolds [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_scaffolds] level = WARN handlers = qualname = scaffolds [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 pyramid-1.6/docs/quick_tutorial/scaffolds/README.txt0000644000076500000240000000002112520062551023172 0ustar michaelstaff00000000000000scaffolds README pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/0000755000076500000240000000000012642137501023452 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/__init__.py0000644000076500000240000000057712520062551025571 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/0000755000076500000240000000000012642137501024741 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico0000644000076500000240000000257612520062551027071 0ustar michaelstaff00000000000000h( " A>;)'%+¯ô=¿ö420520ÿÿÿ;96¸èú „„†µäô—àúGDA/-+LHF+©é\XUtØùååæììì,BO/Vi531WSP+®ñ·çø§§©OÏù--..-.QNK       #!!!!!!"   $$$$$$$$$$$$$$$$À€€Àpyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png0000644000076500000240000000051512520062551027254 0ustar michaelstaff00000000000000‰PNG  IHDRfÍÉÙIDAT8…“É‘Ã0í¬œZ샔%[®Ú‡Šâ58äù| X0`©‡˜Ôáÿo©„qNG@Å™Ï;mH#©¡¦8‰áÎáàKkƒÜ8FèOž-«&19çÀZ#¸ÆA¢dF\gƒ/8ñŠML—¦Gpº8ûdk躓à€éâ}œ}ºôts¾ìóœùe.¾uõ9dë=|úî÷¢­üð9àëðèè­óÉM÷êíÂþÖÓ±}ÈæÕ»æ™ëÁk>öÿ½´þÎÆÕ;«t.k~äí!;?ÀÛŸîZ¢Ôq‚–ÜYÁ:ßîw·óuËmMŽ|¬<äÈì¡ñ+3ëÝ9ðípâ-nù³V=F|˜IEND®B`‚pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png0000644000076500000240000000031312520062551027202 0ustar michaelstaff00000000000000‰PNG  IHDR4b·Ît’IDAT8í“AÄ C“\«—›SÏn`b‹íºà)4ÁoŸàû9DRp‘hµÈ¯«×uìô½?÷Þ²‡ ”@»÷¬\SÍ=ýÌ®Š3}½÷­¾ÚOÜÕÜåo¼±FŸ5´9,×cå©ïß»'çãmZó&kÌéä… ´q~øx²Xò5†èßE3˜,óçû÷”01]îsÖIEND®B`‚pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css0000644000076500000240000000136612520062551026141 0ustar michaelstaff00000000000000* html img, * html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) );} #wrap{display:table;height:100%} pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png0000644000076500000240000000535512520062551027223 0ustar michaelstaff00000000000000‰PNG  IHDR¬º—ƹ CiCCPICC Profilex–wTSYÀï{/½ÐB‘z MJ‘z‘^E%$B °WDWiŠ"‹".¸ºY+¢XX°/È" ¬‹«ˆŠe_ôeÿØý¾³óǜߛ;sïÜ™¹ç<(¾BQ&¬@†H"óñ`ÆÄÆ1ñÝD€ÖpyÙYAáÞ?/3u’±L Ïúuÿ¸Åò a2?›þ¥ÈËKÐBй|A6å<”Ós%Y2û$ÊôÄ4ËÑQV•qò6ÿìó…ÝdÌÏñQYÎYü ¾Œ;PÞ’# Œ¢œŸ#ä¢|eýti†å7(Ó3Ül0™]"ॠl…2EÆAyJò,NœÅÁ24O8™YËÅÂä Ó˜g´vtd3}¹é‰„Âå¥qÅ|&'3#‹+ZÀ—;Ë¢€’¬¶L´ÈöÖŽöö, ´ü_å_¿zý;ÈzûÅãeèçžAŒ®o¶o±ßl™Õ°§ÐÚìøfK, eª÷¾Ùô Ÿ@óY÷aÈæ%E"Ér²´ÌÍ͵ x²‚~•ÿéðÕóŸaÖy²ó¾ÖŽé)HâJÓ%LYQy™é™R13;‹Ë0Ybtëÿ8+­Yy˜‡ ’b=* 2¡(m·ˆ/”3EL¡èŸ:üÃfå Ã/s­æ# /± 7èù½ `hd€ÄïGW ¯} $FÙË‹Öý2÷(£ëŸõß\„~ÂÙÂd¦ÌÌ ‹`ò¤â£oB¦°€ä¨- Œ Øà Ü€ðÁ ĂŀR@ƒ\° ¬ù ì{@9¨5 4€ œÀepÜ}à>#à˜¯Á Axˆ Ñ 5H2€Ì ˆ ͇¼ @( Š… dHI¡UÐF¨*†Ê¡ƒPô#t º]…z »Ð4ý ½ƒ˜ÓaMض„Ù°;GÀ‹àdx)¼΃·Ã¥p5| n†/À×á>x~O!!# Da!l„ƒ#qH"FÖ H R4 mH'r D&·††abXgŒ/&ÃÃ,ŬÁlÔcŽ`š1˜[˜!Ì$æ#–ŠÕÀša°~Øl26›-ÁÖb›°—°}ØìkÇÀáp¾¸X\*n%nn®w׃ÆMáñx5¼ÞŒçâ%ø||þþ¾?‚C ´ 6oBADØ@(!%œ%ôF 3D¢щLä—‹ˆ5Ä6â âq†¤H2"¹"H©¤õ¤RRééé%™LÖ%;’CÉBò:r)ù8ù yˆü–¢D1¥p(ñ)e;å0å<å.å%•J5¤ºQã¨êvjõ"õõMÎBÎOŽ/·V®B®Y®W›¦°©iŠi…é 3ØÌÞLh¶Ï¬Çkîh.2¯6`QXî¬V=kÈ‚ah±Á¢Åâ¹¥¾eœåNËNËVvVéV5V÷­•¬ý­7X·Yÿicjó©°¹=—:×{îÚ¹­s_ØšÙ l÷ÛÞ±£ÙÙm¶k·û`ï`/¶o°wÐwHp¨t`ÓÙ!ìmì+ŽXGÇµŽ§ß:Ù;IœN8ýáÌrNs>ê<6Ïhž`^ͼa]®ËA—ÁùÌù óÌtÕqåºV»>vÓsã»Õºº›¸§ºsîaå!öhò˜æ8qVsÎ{"ž>žžÝ^J^‘^å^¼u½“½ë½'}ì|Vúœ÷ÅúøîôðÓôãùÕùMú;ø¯öï „”<4 ¶ÁAþA»‚,0X ZÐ ‚ý‚w? 1 Yòs(.4$´"ôI˜uت°ÎpZø’ð£á¯#<"Š"îGGJ#Û£ä£â£ê¢¦£=£‹£c,cVÇ\UƶÆáã¢âjã¦z-ܳp$Þ.>?¾‘Ñ¢e‹®.V_œ¾øÌù%Ü%'° Ñ GÞsƒ¹ÕÜ©D¿ÄÊÄI‡·—÷ŒïÆßÍ¸Š£I.IÅIcÉ.É»’ÇS\SJR&„a¹ðEªojUêtZpÚá´OéÑ鄌„ŒS"%Qš¨#S+sYfO–YV~ÖàR§¥{–NŠĵÙPö¢ìV ý™ê’K7I‡ræçTä¼ÉÊ=¹Lq™hY×rÓå[—®ð^ñýJÌJÞÊöU:«Ö¯Zí¾úàhMâšöµzkóÖŽ¬óYwd=i}Úú_6Xm(Þðjcôƶ<ͼuyÛ|6ÕçËå‹ó6;o®Ú‚Ù"ÜÒ½uîÖ²­ ø× ­ K ßoãm»öõw¥ß}Úž´½»È¾hÿÜÑŽþ®;+¯(Þ´«y7swÁîW{–ì¹Zb[Rµ—´Wºw°4°´µL¿lGÙûò”ò¾ ŠÆJÊ­•Óûøûz÷»ío¨Ò¬*¬zw@xàÎAŸƒÍÕ†Õ%‡p‡r=©‰ªéüžý}]­zmaí‡Ã¢ÃƒGÂŽtÔ9ÔÕÕ8ZT×KëÇÅ»ùƒç­ ¬†ƒŒÆÂãà¸ôøÓ~ì?p¢ý$ûdÃO?U6Ñš š¡æåÍ“-)-ƒ­±­=§üOµ·9·5ýlñóáÓ:§+Î(Ÿ):K:›wöÓ¹ç¦ÎgŸ¸|a¸}Iûý‹1ow„vt_ ¸tå²÷å‹î箸\9}Õéê©kìk-×í¯7wÙu5ýb÷KS·}wó ‡­7o¶õÌë9ÛëÚ{á–ç­Ë·ýn_ï[Ð×ÓÙg ~`ðÿÎØÝô»/îåÜ›¹¿îöAÁC…‡%4Uÿjòkã ýà™!Ï¡®Çáïó†Ÿý–ýÛû‘¼'Ô'%£Ú£uc6c§Ç½Ço>]øtäYÖ³™‰ü߯|nüü§?Üþ蚌™y!~ñéÏm/Õ^~eûª}*dêÑëŒ×3ÓoÔÞyË~Ûù.úÝèLî{üûÒ&Ú>||ð)ãÓ§¿›óüìÎçŠ pHYs  šœPIDAT(cøøñã& €ÿÿÿ‡²H#SØ4½ø¹8]E„¶A¢ÍTô¶àÄ&†ÍDˆaSB¬ë±9§%Hr3NÏ $,Â&…¨½´›IEND®B`‚pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css0000644000076500000240000001221112520062551026771 0ustar michaelstaff00000000000000html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; /* 16px */ vertical-align: baseline; background: transparent; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } :focus { outline: 0; } ins { text-decoration: none; } del { text-decoration: line-through; } table { border-collapse: collapse; border-spacing: 0; } sub { vertical-align: sub; font-size: smaller; line-height: normal; } sup { vertical-align: super; font-size: smaller; line-height: normal; } ul, menu, dir { display: block; list-style-type: disc; margin: 1em 0; padding-left: 40px; } ol { display: block; list-style-type: decimal-leading-zero; margin: 1em 0; padding-left: 40px; } li { display: list-item; } ul ul, ul ol, ul dir, ul menu, ul dl, ol ul, ol ol, ol dir, ol menu, ol dl, dir ul, dir ol, dir dir, dir menu, dir dl, menu ul, menu ol, menu dir, menu menu, menu dl, dl ul, dl ol, dl dir, dl menu, dl dl { margin-top: 0; margin-bottom: 0; } ol ul, ul ul, menu ul, dir ul, ol menu, ul menu, menu menu, dir menu, ol dir, ul dir, menu dir, dir dir { list-style-type: circle; } ol ol ul, ol ul ul, ol menu ul, ol dir ul, ol ol menu, ol ul menu, ol menu menu, ol dir menu, ol ol dir, ol ul dir, ol menu dir, ol dir dir, ul ol ul, ul ul ul, ul menu ul, ul dir ul, ul ol menu, ul ul menu, ul menu menu, ul dir menu, ul ol dir, ul ul dir, ul menu dir, ul dir dir, menu ol ul, menu ul ul, menu menu ul, menu dir ul, menu ol menu, menu ul menu, menu menu menu, menu dir menu, menu ol dir, menu ul dir, menu menu dir, menu dir dir, dir ol ul, dir ul ul, dir menu ul, dir dir ul, dir ol menu, dir ul menu, dir menu menu, dir dir menu, dir ol dir, dir ul dir, dir menu dir, dir dir dir { list-style-type: square; } .hidden { display: none; } p { line-height: 1.5em; } h1 { font-size: 1.75em; line-height: 1.7em; font-family: helvetica, verdana; } h2 { font-size: 1.5em; line-height: 1.7em; font-family: helvetica, verdana; } h3 { font-size: 1.25em; line-height: 1.7em; font-family: helvetica, verdana; } h4 { font-size: 1em; line-height: 1.7em; font-family: helvetica, verdana; } html, body { width: 100%; height: 100%; } body { margin: 0; padding: 0; background-color: #fff; position: relative; font: 16px/24px NobileRegular, "Lucida Grande", Lucida, Verdana, sans-serif; } a { color: #1b61d6; text-decoration: none; } a:hover { color: #e88f00; text-decoration: underline; } body h1, body h2, body h3, body h4, body h5, body h6 { font-family: NeutonRegular, "Lucida Grande", Lucida, Verdana, sans-serif; font-weight: 400; color: #373839; font-style: normal; } #wrap { min-height: 100%; } #header, #footer { width: 100%; color: #fff; height: 40px; position: absolute; text-align: center; line-height: 40px; overflow: hidden; font-size: 12px; vertical-align: middle; } #header { background: #000; top: 0; font-size: 14px; } #footer { bottom: 0; background: #000 url(footerbg.png) repeat-x 0 top; position: relative; margin-top: -40px; clear: both; } .header, .footer { width: 750px; margin-right: auto; margin-left: auto; } .wrapper { width: 100%; } #top, #top-small, #bottom { width: 100%; } #top { color: #000; height: 230px; background: #fff url(headerbg.png) repeat-x 0 top; position: relative; } #top-small { color: #000; height: 60px; background: #fff url(headerbg.png) repeat-x 0 top; position: relative; } #bottom { color: #222; background-color: #fff; } .top, .top-small, .middle, .bottom { width: 750px; margin-right: auto; margin-left: auto; } .top { padding-top: 40px; } .top-small { padding-top: 10px; } #middle { width: 100%; height: 100px; background: url(middlebg.png) repeat-x; border-top: 2px solid #fff; border-bottom: 2px solid #b2b2b2; } .app-welcome { margin-top: 25px; } .app-name { color: #000; font-weight: 700; } .bottom { padding-top: 50px; } #left { width: 350px; float: left; padding-right: 25px; } #right { width: 350px; float: right; padding-left: 25px; } .align-left { text-align: left; } .align-right { text-align: right; } .align-center { text-align: center; } ul.links { margin: 0; padding: 0; } ul.links li { list-style-type: none; font-size: 14px; } form { border-style: none; } fieldset { border-style: none; } input { color: #222; border: 1px solid #ccc; font-family: sans-serif; font-size: 12px; line-height: 16px; } input[type=text], input[type=password] { width: 205px; } input[type=submit] { background-color: #ddd; font-weight: 700; } /*Opera Fix*/ body:before { content: ""; height: 100%; float: left; width: 0; margin-top: -32767px; } pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png0000644000076500000240000001560412520062551030225 0ustar michaelstaff00000000000000‰PNG  IHDRÜ27µsKIDATxœí{˜Õ÷?çTU_æ>0ÃÜp„ŠD(ÞˆšhVØ,."»hbÜUß}³kÖ˜¼‰˜ˆ»„}ˆó¬1’Å Äý0sÙ™°íÛ„ilÛÎe·Yá8yIkkûÅÀÚS=ž!|ú‘3ÂM™2%lãLU6(ÇùDm9®p”"³œÌ(¶Efa.¥’6\Åá†äŒp0,d;#lÁ'òH:J H CÒÞÇ02Iôd]ZŸÂWì(gì€4„Ó¥À?Åèé¿ùŽ]Ü|›àù\ ,g„Êr‚*'¢N*<òxéîŽs×-—pVþFîÌA ÙCx)ï<Òy'2 :GåŸÀ ü°€¿ÆU@!tÞlÞ~ ´°3 pp!ð$ðǶ栟‡"•pïsÀ€CÀ†ösÂÈ¡ ׅ턲xiDF¡®&¨ô]ëìŽ3ãÒÑÌ›y>Öá5Ì»Zò‹W È )"y~z[‚äQá#¢Ð»Ž°Š[ü¨è£^#ð°| !¸ øw7ÿ9àR kí(÷¼B šv¬ 8þŒ–rÛ2Ø §NÇq~#|*±Ÿ¬ãQ)w¨«-áþ;'aÒI¼[ð÷×·°iW˜?n1´A'ÈàyÑÿ ýÜîG?«œyñrJ8”Êê¼H?iDâ´PÐäŸîºˆª2I¬³ åHò’n>ÎKÊ8Ö¦!ídÜ Met‘èã$`#°8­l!pšdÃÝýÕÀ®“Õé§ «€ï¢Uô:©Ÿãî6]ú :d®:êêòø¦'¿”)$†ÔI ‰”Ú¢“‘ØÚÜù7ã˜òÙâm º@YÄ¢ÔU:üãM],Ó41M Ë2±,ËMX@ ‘‚ Ë:Yk8³-"ø/à{n¾˜y2:û”¢X€¶±Þ8µCäÔ†ð—x:ãæ}RÈ'á¢QÅ5Ó«¸ùújâ](倊!”ä‹J®šàÐôyÁò5‚Ád[Bÿç 3‘Ü÷õ§ð ÃýÀKh½8Ï-@pÜÔ—Zc‘T±¼7³ ù ã$£$Unj"Ú©BK\´ûúè[ºý¨´~*j´$Ú Ä2œ[áÖ‰¢m¥Þl2ÿõô瞘À(´—±­9Ä8VUdAÎ$œ‡„ä1MÌDÞ hi¤%Q€` €”cÎ.æ¾[ë0T åDAÅ@Ùhs( *;–Ï-W+.9/€4B„BAB¡¡Pˆp(D(&‡ ‡CÉ”&‘ƒçÓt¸yÏN OëÑ^̪^Ο¼ãÖ½ßW~6Úíý.0ÈC«µ€ßS|uÇ»åÝ:ÞCKáK{éÿónß«Ð/:àhßïÝck/øÎ©E«…ï»ã[öØÞÜK?ån ©d‚Üâö¹ÁmÿOÀ:·}ÅiJºœI¸®®.By…zU—I)¤“ôåµ$Ï’üÃWë(+5ˆEõ‹U @Ù(Bèû.‰c0wÏ<ô\˜Ö0 é“jÚn“R‚ÀuܤØö K7€2ôD8èÝ´£åFwÿ:àçYΟ&Àƒ¾r‰&cMØ»ù¾ã–»À"·ØY£¿®D»Ò_Îи--§cÐ/‘(Žo½à¶µxh@K¿n·Î…À³èøØO3ôp¯³íAÌ„°{î-¾²f÷?ç¶ÿS´$>íCÂ4$á~ÁûÉ%¤&‡ô•Åm˜?³Œ có‰wÛ>w•a¢dGJÀ ¦$5e‚¯]àg¯0M‘ôˆ¦lÜr¡ çÄAõ7héZ"xx¸=碥Fú€ÂèI ðZzˆ¢'tM´Ùh/>FK2ÐoûçÑRu'šð!’®ø à_ÑRèXÚÚÝ­‰žÌÀ{€•nÙW€oºc]„&b-ðÀ¢oý_ÿÏísðŠ;?Iu9a~H’loßGKi&ôCÀh•ô´CNm8SJòóóQ(—\®Äq&š|‚X¦_Ϭ«†aÇHPàDAuC¼:÷ãÄÚô>1Šna0¹\°iT9O¯-¦¤P«–yyyäå… ‡B˜–…ÿc×h,“Ù1 db®DOÆ{Ýý?¯ùŽo~̦¢Õ¾ô7û…Àùnþy’“’vŽ7¡ß¾Œ~ã§ã?2”Å€_£¥ãOÑiÚæôóÛÂh"ÍJ«ó-´:øUà³nÙͤÆ¢_,¢íÇ™À’ cê £ÉZ5þpÔwü5´:º ¸èÛÎ rªR†AAA>Ji/¥!µÄѤ8JRQä֙ŧ 'v køa°£œã`·c·´ â¸î}´àÐmÀ_6ÀËkjùŸwBäA¡`‚‚JKK1b•” î滑Æ¡WIA«rõÀ ´ªÚ9qI—´‡¥è FOÂtÂ} ­;wÐsù‘ßV‰¡%J&²õ…U$%ÞzÎoý:ÃqÐÒùw¬o+2ÔY†–Œ…èɉâ«hõÖF‡Žf¨sMîœ,Õ:Qä”p¦iR\\Œã(M0‰K:‰eš!‰Åº˜÷ùc«Dìû (×ß ´ú®ï$†–!.Ù”Û†°?øë=ÌyìZ"6í´··ÓÜÜLãæF‚ eeeÔÔTŸ,)7½) -ÅîG;ÒñÚ›Xü5ÚéáÅŸBhÂv4fhÛǤª«½¡ý‚¨GK›*’o²>Îým–òÝè—I)Z•Íä´øØ&\_«sÒ"éØÙ‚vdûîXŠz©sJ[ÂY&%%%8¶:îf¹*^KK ï°™[¯ÙM}É¢‘ˆJmL9¨x’pB¸«T\òuÇ¡®|y;w.«G!É€mÛ477³¿¹™x|À„óOª½h±­‚BÛ\¯oõÒÆqô*”ïç‡B¡©kQTT49‰Œ(**ZÞÚÚ I 1}úôð[o½%”R˜¦¹?‹¹7,#„eYÅãñ»Ð^njĒRJÛ¶¥Âoù¯³=ý]$í®löW”¤·6Û8³aI’~HïAñn7vÈéJ+`QZZ‚m;˜¦‰aìÙ³‡õë×óç¶ñÅ©qæ^! €ö8_…a$q¤ vâ K!=Ò è´áÚ±ðw—·ð“Õ•„ƒ*ÕQC2‹FûíBVJy±"ãöÛo/xê©§„ã8äåå½ßÞÞþ÷ÍÍÍp8l{±+ íy3}Ép“ ˜Ë–-;úµ¯}­;‹îCO,9~üøÙo¿ý¶QPPptݺu…hÇJâJ}ôÑ’3f„ººº¨«««B;g2EΜ9órÇqn¡P¨«®®îƒúúú=ÕÕÕÇÚÛÛ+V¬¸!‹'NœØÌQJż¶¾ûÝï6,X°€ë®»nüÊ•+g ÉG«²öO~ò“a÷ÜsiÛ6åååE---gùöOÜ[Ì›ÛòÉ ø–u¹a˜+ ”*GÛAôà ¦% ýÀ-/Ýwß}…¿üå/ó#‘£F Ì®¬¬ôŒÂô‡îßO™LóçÏ<òÈ–ÆÆÆñ;wîœÚÔÔô?Ѧ¦¦K.¸à‚??ÞB«bÞùÂ4Ͱ·HÀ0 m¦;oŒGyäÜ—^zižã8Œ3æ£'Ÿ|rÅå—_ÞŠûzÚ¼y³õ«_ýêºX, ‡Ã^ˆÁ›üƈ#ª½ÆjkkkÐö—w\râĉE–eY¶msî¹çŽF{L=Ò*Àž3g<ð@éáÇ©ªª*ß·oßL·N7ÐýôÓO}å+_1lÛ¦¸¸¸àرcUî±ØÅ_l¾ûî»Þµy÷!NK%ä˜p¡`ˆa¥ÃXµj+_y…ÖcdžI~ždñ½6UÝЭ0Œ6Ž·Ei9;÷±«¥‚ãñQXã(¯¾€Q]€yI€[7òûèÞûGÂ]pV~+£J5 ó `€aÀÿr7óžj ù¸‰e$%›”Û¶©®ªªFÇÃ|2²TÚ˲‚ÂñBÏ18ƒ9kÖ¬w.\8þøñãÅK—.]__ôСC#¤”ö¼yóÞuÛõ·-ðM,¥Å†M†É¶lÙ²KÇ!G–.]úÜ´iÓ"$ #Ž;–ˆþ;Žã¸Ç„r'!ylý¹~”Ti$mÛöëåÞêïå#Ca†!B¡P½J$ÑFCCC¡GÚººº³Ð×(`¯X±Âœ0aB<‰PPP0¹±±qFMMM­¢v¢UÚ. »¾¾~øÖ­[½0ÌiÏ)á"‘O>ù$o®[ç®›4±Å?ÞjpNíqÞú}ˆÍE4·Uc„ÏeDÍxªÎ=ŸË¯:›êš* óSÕþ‹'}ø[:£°¿ùû÷î¢i÷G¼½kÑ)ÛY¸›1åðÀuøÖ‹µØŽp=¤Ú;ª”ðTR…ž$ý ˜ Û¶mw¢cÛ¶·ìi oWçî»ïþð‰'žh>räHåo~ó›q›6m:¨”ÕÕÕ»n¿ýömô\=!DZ=õ, áľ}ûÌŒ6lXË´iÓ§µc´´´Xñx<‰G ¹m½:@@‚éäWJ©LäOyùH)½c¸õ£¾±Û¶ƒi×ãõkÔÕÕ‰ššš=åuË—/¿æ›ßüfIò'^cÆŒ¹rëÖ­ùeeeE<MÌn·Ž§æ:îõæ 9#\Ie%ï½ÿñxÓÔÝÚŽƒ)b<óëc¬Þ0‰Ë®šÅ¤K§ñűc>¬ ßm‡pÎÈ ÎYS?ÜJ¤#ÆÖ­ÛøÓ{ï²|í*>n\K@Dé6ò0¤V-…”H¥PN<}` ”R‰ Õ›„骫«™4iÒV­Zõ¥ÆÆÆqÛ·oï˜:uêz˲¼ñeퟤtM!\ ˆI)€¶¶¶Â––9bÄïÅ gÑ¢E—z„s_)dqI¦Ü>3Îñ% )QJ ¡p'ç¶íÒ»f¯Ž1sæÌ7/rG.^¼xæœ9sÖÖÖúíDV¬X1bíÚµó¼>ƒÁ`:Fè‘-%¹¶j‚€ 2 sF¸cÍÇœ²ò"ýC¬Ž“X¼W»óÙ¾¿‰ ü˜Â¢§(/+㬳΢¡¡††FMMM eeeX–•Ò®mÛ=z„ææìرƒmÛ¶±uë6öíû˜Ã‡ÐÖÞN4'f—0%–¡5Fá~­ ¤ÂQŽ7ÉNáuûí·¿³f͚Ϸµµ•ƒÁ¶¯ýëëIÚBéý'ÊôïÙYY™ª««k:|øpukkkùœ9s¾´lÙ²ÿ.//·×¯__ðï|çêµk×^#„PJ)F ’݃\÷¤]õ6‹´MW¯ÝωS…[&üª+É ¾÷\â .üà /¼°¾©©iòþýûÏ4iÒ?Í;÷…/|á ÛºººÔsÏ=7îå—_¾9‹ ·E"‘Ññx\’$˜§*§ýâñ4¡”b°H7(„Súï„Ð+ Š€â+VŒ¹çÞÿkÚ¶üM€’pPûö£±(‡¦å@ lü3+W¾Œ”’P(Dqq1UU•ÔÖÖRWW‡eYìÚ½›÷îåÀ´¶§;ÚR`¦e%<¡†!±,3é™ô–’I‰£Ç„sǶmÛ°m[2p•€Ù³güàƒnܼyód€úúú÷/»ì²#dVsE<·Çé«çÁ|iÞ¼yãÛÛÛ‡½ñÆ_<ï¼ó&‡Ãá¶£GVÄb±ð¤I“VïÙ³ç쌎D"y¤®»»;q_âñ8î1ÿ˜„mÛŽ7–X,&HµqÇ(¼ûå!eâ»m˜nÞõxu ž}öÙŸÍš5+oß¾}çµ´´4,Y²ä[?þx»ã8†mÛ!!Dlîܹß[·nÝ‘Hdt4õIŽo›ž÷ۜ괖pJ© z`¡›ŠÜF{÷ À>|øp@H©?PH,,Ö;©.{76—táC{G[š¶Ò¸y Ê•Ò00Mýí›aš¾u’Þâe\{M¦Í#œr{`„ …BÝ•••[#‘HiUUÕ>!᫦¦fÏæÍ›'K)ã·ÜrËëøÔ¥ôþóòòºªªª¶tvvTWWïÎÖÿÌ™3÷?öØc>üðÃ7îÝ»÷3‘H¤¬½½½xøðữ¾úêÕÏ>ûìÚ+®¸b¶ã8”””"Õ¶r†1bD ÊËË»ÇS$\QQQguuõ–ŽŽŽ‚ÊÊÊýn”±H)©ªªÚnšfgUU•÷)M¢üüüÎÊÊÊ-]]]ù•••“êåT€š|ôèÑ‘#GŽÜ°k×®GûÓ ô/õÚk¯ kjj*ª¨¨è¸æškƒv>Šh4*ÂápÒzýd:–^Ç0 2Ž¥:*JÛ¶q¯ÇëK‘Û·o7V­Z5¼µµÕ;vì¡n¸á š@ÑX,fwtt8¦iÆòóóÝ·ÄHÆýêeBuM øŸtd•p®Zh‘T Ð’,ä–ëoc4¡l´Qêíû€¨­­mµLëx4Ë—=ˆÖ“@é_ϳ¨õP„Éã25ï'\*Ù¼2½oÑh„â’â ýœè‚‰¬0nºé¦/=zt¤+Ý^ìϘN´ÿk¯½¶åÚk¯=HÚä5 ‚x?Ÿ ŽÊp<= ã'œ¨sÎ9ǹãŽ;"$½Ëžªk[–/..N'U6©Ö#ä3XHN)å-T  UAO%´H®ðts…[!± *‘2®¡¡Á.--iÚßÜ\•bOùåÿb@¥ÌGL™~®¿<-Ÿ$o²=Ó09~¼•˦Oû€Ìމ\@òú믿výúõÐÐа桇úà'×È6É{#\‚tô´ËÒ¥V62§ª¤Ó%šG*oëý>‡§§§_¤G8oëOôüxyãÊ+¯|mÙ²§§ç…ót°8Eí“Y÷¥ï«Dfòô8?퓟Ôvô:ή®Nº»ºwÝ{ï½ïqŠÖÞ½øâ‹åßþö·oÚ´iÓu€(,,ÜõøãÿœT;êLB5“ž¤ó“ÆŸÒ‰–t§DºARjy¤ð_eûŽ¥í MJ?áü«4R¤››â?þñ÷ÒÊ•oµ·µMËÏ×q¶LÒÍS“v˜ÌRž‰¬ú‡aµÓD¦J7Oõt¸hçÎL™2uY]]]¹—&béÒ¥UwÝu×â®®®r€¼¼¼= ,øþ•W^yøŒçtA&ÂyÛLR.pžzÙ›'ÒOºœBø$œ§z‹j yʤJú =É'Ÿyæ™ò{î¹gq8™Ÿ_€B%m-™Ý.“=ˆ—J ÏÉ"S—FL©LISÓ€6mÚ´„S#IÄ®]»¬qãÆý¼³³³fäÈ‘¯/Z´èg³gÏ>“É'N¸tâe’nqúV7=$I™$ÉÖŸ”N¶L¶›Ÿtþ­\¸páÈY´è!Ë4JJK°L+%4 ÓHÒ“\ÉV{ÔK'«ŸhRÒÞÖFSS–e=¿aÆ%………§äMçÝ“ùóçO.--íX²d‰÷·ÇÏ45²¯ëM'ôT ³IºtÂõ©fæ‚p‚äO°ù¥œ‘a¿¿Ž’Þ _}õÕ‚o|ãó[ZZf†B¡a÷ÃÔžÒ)Tž=—‰l’¤C¯ÂhkoçСCD"‘M&LøÅ믿¾zîé‰Â»/gÑ<ôvÝ*-ß›¥/ÒeS7’r°C$œ'á¼”‰\þ2ßçžþ9è•h™>WK–,±lÙ²©üŒmÛ%êáËûuA¢ghÑqœx Ø___¿áG?úÑúóÏ?ßûíÂ3u’ÿoA:á¼mÔËÞV—du¢äJÂyΓtÉ•M}Ìd³ù½’™H–-ÈžéÛ±lèÍHÿmˆhÿ{)<àm{#œ—O'WºÝ–SÂyq¸¾.‚´ã‰•æô­J¦çéGùÎ<ôóçûCºlªf¦ör?ἕÜÞ@=ˆ u¼ûk³ nýÁÉ ]ºÔÊ{Ëiü̓iWú<Òù ç»B´!r áD‘-DàÏŸñ2.§È´–Ò?tÕ1lô²MÏÓò! úç¹ì¯3[꫟AA& çå='ˆ_ÒõF¶L$êXC¤B6œH¨ SY ØŸþN*?ççüpNgŸ³}‡98°M8L2QH("i…ÊÚ4©»~|»gzf'íjvV»ª·^­éé®®ªîéíþÔ·¾õ-…eÄsÍ5×Ä»ºô´X“·H}®vÔ03ŒaÐ8Üõ;Õ1¥Q ”:f0¯*Ôv0ëL&ù|&“ÙýðÃî:Z,‹Åb±¨á®€ep\sÍ5ñLÆ™éjo¥6ꔺ˜¦E)eדÀƒR*‰1{ æicxÈdÜ?x^r‹ñ‹Åb±X† +ðFW]uÕ—èÕà]‹Qg+­æ*¥0Æ`Œîê*”R(¥0€qÝÃF™Ê¨ß×üìw¿»oãp×Ïb±X,Ëé…î#„s®»®i\Æ»ÁxÜœ¥§Àó¼a®Ùéˆxç¹cÌK(õË„›üÖc¿ýmçp×Íb±X,Ëéî§8«W¯ŽhÝx)Žú¸Vêu(ñY·Öõá!ðB2Æx 6»˜¯¶6œøþÝw?Ö5ÌU³X,‹Å2Êq†»–Ò¬^½z‚ŽÄ?­õJ©å@t¸ëdÉ¢”R ®N¥âsæÍ]°ãå—·íîJY,‹Åb½X‹û)ÊåW]{™ç¹Ÿ×J_ ÖÂ~*£”Æo†/Ä£|ã¾ûîKw,‹Åb±Œ>¬ÅýdõåW¾¯i­—yžgEû)Ž1­u›1fUÊõÚçÌšñøÎ;SÃ]/‹Åb±X,£ +ÜO-Ôe—_y‡VúËÚÑSìÀÓ‘ƒB2¦Ñ磜ö)“ç?ùÊ+Ûû†»^‹Åb±XFV¸ŸB\ºúÊÛ´V_QJµ+ÚGÆF)¥Î‹D¼‰³gÍ|dçΉᮖÅb±X,–Ñî§«/¿ò=Zó”cŒí#ƒAiçlƒš6cÚ’‡;;·Zñn±X,‹å¤±Âý`Õå—¿ÞxêkJéñÖŸ}´`PJŸ‰“Ò»vìøÝpׯb±X,ËÈÇ ÷aæÂÕWž©1wi­çZÑ>ºP œ5{Îìõ»vìØ2Üõ±X,‹Å2²±Â}¹æškân&óeí8WXÑ>:QJÇ ,;{ÖC;wî<<Üõ±X,‹Å2r±Â}™2}æÛµŠ|Òã` ì2Êc ZéIž¡cL[ë}Hc±X,‹Å2¬p&.½ôÊÀÿUZMµÖöÑü¾j~C,þRgç·»>‹Åb±XF&V¸jÆœ9wjÍ[l™Ó¥TÔY3§ÿº³³³w¸ëc±X,‹eäa…û0pÁ«kG}k­í§:Pú@ç®k‡».‹Åb±XFz¸+p:â8úv0sŒgýÚO—Åx­u#F½ó /œˆÅb±X,ˉ wN7Î;ïÒ9F¹×+ ÖÚ~:áyƘ3ŽÜ3Üõ±X,‹Å2²°÷:ãÄôÐs‡Ûl—ú/¾Õ=¦áº .¸ ‹Åb±X,–`-îuäœsΉ‚¹\iÝàyvPêéˆg<<¸(v¦/w},‹Åb±Œ¬p¯#±ÖÖY&mÎ6žÁJ=M1 `q$9+Ü-‹Åb± ë*SO2™ù`¦ZßöÑRªä‚Œw.òÍb±X,‹¥*¬Å½~hãéåJ«˜±n2#¥ªÐÚUõ¦˜×N9çœÆ}O?mcº[,‹Åb© +ÜëÄ9çœÓf±RϺɜҔç5ùåŒÁÀ¢¹ öAg-²´X,‹Å2ú±Â½N8ŽÓh0óŒ„îêŒZª²ˆW`0¿Ï@ÊõD¸Mg>V¸[,‹¥z•Ý,ƒxf–QˆîuÂÇc‘ ãÍiìÕìyiâÑÚ ­¨…P¯w™J)0F5ª’Åb±XF?qàÓÀ" Sd¿ú€ï¿`Þ¯ó¸þ§D€µÀ[€v`2I˜ Ü4Ow°\˱½N脊ÇmÃèÓÒâîy†hD3kj;^éAk(¥"Œ{-OF|ŸìïgŒÁ£=ÏL:©Œ,‹År:Öç•IÓˆö ÷‹€w#Öüqˆ`?‚ˆó€ÃÀç€Và àI`°øß~ßÁ ÷!ÇF•©™hFL#Æ ûD@ñôö¦¸ìâ¥üí'°pZо¤óùûUè_­ËVEþ ×u•­•blÿ;Åb±X,–¢ Q!MH "ïnÞ ¼„ö?ó·ýÐÊw)0iHœÊ#9ˆr-ÄZÜëD4ÑFeN·PJ)zúR,œ7‘¿ï ¦6üžO¿µ—5Fw/4DG¼ê¯ËÀ­åµ½æƒ±Öçc£”ñâ5«Ô©ƒÆ„–fÿ³Õ_oð—ÒíÆCü½ÀqàUàpØOñ®a‹ÅbLC\;3ºÇUzUã_Œ]þŽ"ÏãuÀ3þþK{ÛnÄ"¾¿~>2'ÉäA–k V¸×cÔh’íÕØD2Ø–>üîULÔBú•$ËôqûU­|õšT"N}ÜcŠQùÂ2|W £âv˜\̦ûßÇcýÏ&äeñ?µ¿”z¹xþâ"Ö>D´ï6 /’uˆ˜ŒUÉb±Œ¦ â5À,ä9sqù69Ñi©ž8ò¬Vô7®8À&äº_ <,~…üV¸×+ÜëÈHWiýh…ôžgȸ.·ß²’+VÍ'ÓÛƒç¼T”[¯H²}_#¿|TÑÚ8tu†…w¬FÈô˜pzE¾÷Ì(àMH7jóäÝŒ4¦+€‹ü^à!àgÈ‹ùÕ!(Ûb±œÚLA|ª¯,Ø>X\¼ Ti© bHy¸xòZ[ ¼~ëuZa}ÜëÉ)àk^/¿p¥}‰4¯¿d!7ßxn2çfC&1;nH°t–!xÅL)^ç¢õ¦túRªºTÞÅòLÉÅ–¼óÄü¥^Äëþ»+Ï¿!7;Ð×b9}ˆwÒ_´‡™ü-"ä-µÁAzAŸ&ïvÛ±Ööºa…{ÝH`ð0fx–*áÁ¥ ½})Î:c Ÿúèe´D!“L ÞH&`zGš?gŠŽvH¥ â9RMþô_ ?]±4¡´yé•Ô¹ßB°0 z•B)åï—ë‹7*\¶‡sà8Ò=ûMà׈EÞb±Œ~Î>TEºËç‚•µA#Ñfžzñ¾qOr†±^§V¸2Dö_{ì`èKdhiˆòÇﻘÉ›I§’(<0AhXJ“LDX>?Í7¸¸.d2¾¨.q…u*›.ï¼*,’ªÌR¹NZëŠi¤î³Œ¥†Dù¿´ ou,Ës)2Ƚ X‰u ¹^1òÅxÓ} ° 2öÈøûFcÀ…S{3ב¡ˆ(Óßï¼ú2NæØRxžÁó<Þú¦³8ï¬idzXîÏ€Ñ~yIi®½(Åã¹ï)C¤Qöôk2iDÖ7—$_‡S©ý¼07É£xcÅøÿ—lÌTÓÆñ{‚ÿ­|¯9mHø²iÀg_L‹Å2ú˜<€´“A™¢ºŒ6’À/w˜½¡í¯_žÿ,þ ñÿ<[ךž¦Xá^O©ÔÊZ¾KäY•µ|€õ©”§RоdŠË/šÇm´/Õ‡çy!˜àx%"±²Çã†;Þ˜b×[_æF¨8¦H]_w¥Šïƒ*¹¿|™¥÷—>°_(È~ô£ƒLæqèBb§—ip%ÄŠÖŠ„w›ÈÀ{p»Ÿ×Hw®Åb]ô m6”ì@è¾XdûVà#¡ï_-Øÿ§œnoµa ÷:‘œ"j´ª(&¡ãªu_HÈÄjy%´Vôö¦9cÁD>ó‘ פI%RAe±îúî2ûûV$ó¦»üÙÛ2|ükQNôb‘œx/W~©}•Ï©øõ)%èË)úbe•®—|c†Õ9¼Îüyf/ò &òH“ÿBÕH÷lb=Ÿœt‹Ÿ‡ˆùjPÈ ©ÈL6l¤Å2ºH¤˜g±Õ+Úë„îu¤˜»EwJ¡OÂÆä—WÓßU$X/Öû›+¥H&3Œçãï?—É“šHu'AûÖåÀÊL•jÊ„\ç”G²OqöB¾þþÇÏí¦ýz;„ZB$|7áíácŒÉ}/8çR×£’0?ÃÂîjðŒw: —Úü÷Ò ÷Ò=» ívÕGù2ˆê‡(Ûb±œúüx ¸¨BºÀ½XQiEØÁ©õ¤H0’“ ¹X.Ÿbyi¥Kî×JݯPÙ}á%||&í’q=>𶜿b ™Þ¤¸Çxžø·>ãùϼ¡£"â=M&i¸ñ’7^ ½ q§ —_x~ŽÒhÊH“· 0Uáº䥵ӯ Gk­ÑJe— 7­šÐä-¹¡pP8h%Ÿ*(³ðÚ>&÷“1àbaûðFànªëúŽŸB&f±X,£‡ƒÀ_P~‡n$äsu©‘ÅR'¬p&*Em f׬tü@"­”KS*oŒÉ-Åê $’.—ž;ƒ¯šI¥ð<L ÿè/›Œ ¬ðâæn”ñH{Š˜V¼÷ê ¦+™ü(1ýb½” SM„—ÜR,ÒLÁ¿øVJçÊ G“ñ×µ–%»ˆ¿eÐàIà½Àÿ¡ºÁfKK½Åb]<|qÅ;‚¸Ã$£ÀSHœ÷ïr:™H,§ÖU¦^$À89÷‹b®aÂn(åŒÿwv¿$ª~ß—H³lñD>sÇy4Ç • ´TN çLû®o€Ñ„Ý̕ր"•2Lé0Üy³Ç_~'ÂñC,šßn´ämÔºð<2ø´ûUä×/ûl)EvlRJgËö<‰°cŒ¡„›½¥2Kº |œòÏ1î¿B–Y,–ÑÃOwºyHÏš:]À¡a¬—Å2dXá^WLö3'ªË„¤|”“ʾÝʰè Êêó.Ü–L¹Œimàß³œéÓ›Hu§D˜~íá:d¨Ê&±Z‡2 ŽQN:\p¦Ë{Ö8|é?žHÞÔÙQ«ë\í¹ôObòEy òV(PA#!eÇ2\$fû<àÍÒ.®Ã w‹e4r ]øôpWÄb©ÖU¦Žä{äœ> ·«"ûú;Š”ö‡Ïù™ûSéþÇÙ–u)²=ŒÄk‡÷Ü|ç/ŸHª' x~eƒŠø“.eÝf‚™XµÔËàûËhäVÔ`4Æ7¥¸ñ¢4×®„¾¤øŽcü¥œÜ1h±Â‡|] ýñå¼Âî1¾‡zÖ¯]ƒ’|%oGòÒ¹¼ƒ‰–ÂKá€B—Yü<ý¼‚ßÔößž4ÝÀ(ïç bu_Mu¶X,‹ÅrÊb-îu$ì“s…éŸN…þï·ï¤¬Ì…å”4·—È+U%™ryÛ ¹åºyd Œçf9*M†`=ˆ#ªÕ®B1rüI™|ÿ2ˆD ¹Áãð±k7šÊœÅël ¾÷?§ð)«Ð9—¬?ð—ÉsÛQá¯è¢å„\¤‚ØBÆP*$¥e@üø7Äu¦KéÀ¶!¯‘Åb±X,C„îu#A0{°…ó‚æa(çÂRé»êç…HGUê 7ŽPB¥(¬b v»{3,š;–w¼e±ˆ!ðÐYŸö°XŸQÐuÍŒ\‡)¨“lO»0¾ÍðÁë]¶îuèê…x,—.ïS0& ÿ#”6×,!ÚÚOt wò¾ãl ûßk•/äà ­<<¿C^ Àr2ü'ðAd*îRt3±ÂÝb±X,#+܇œu·: zU~Ô…_Šì¯lu®à÷­©´Ç¸1 ÜñŽ%tŒ“î ÏkQ*ŽúW™¬…BŠØÏE!iP$“pÆxç•ÿï×"’#¡»¶”¥½ÔIî/tv ê×#Q&¯jz8Ä•¦_2Ëɳx¸¬LšÄþÁºÔÈb±X,–!À ÷:ˆZcrQPªî%¾©B³zx›*bËWR4äÆÑ/¿Âº¨À_ñ®7/àÒ×ú~íO{6«‚A©aK|Ð@ØÚNÖZžmÌ„º<™¤âÆ‹]vîpÏZˆFB5ÍZØs¶óP»¨È9…NÈÿPy×4\­°;S~š~ü‚4á²åtZñîKm8Š <-'ÜA\edÀ…Åb±X,#+ÜëDˆdµ¬ hŠ˜ÊýµBx“b2?ýõhQ”Î;¯ΛÂF„¡;áò–+gqËu3É$Ò¾?{vDlá®(ˆÿîùb8‚2:\P¿:øUE¡H»šxÔðÁë ûFxv47–䯄s,s] …{‘ߣŸp/Ù3Q覓ÿ]+…1^nÀ±µ¾×Š$þ­íXá^kÚ€9H(¾qH‹¼™õöÅ–Z)Àä·l%7à8 Gâyö#wZ€ÀT`¼ÿ]=ȹîvSݼe âb6ù}›ýí=È5î^RCPöh@#×mòû‘er¿îö’›1Úb)‹îuED¬vræðBw‹@ær”ý¡õ57œO^~…ŽÞÁÖâ:9›°_£@)ziÎ^2ŽÛß2›ˆ2¤\7dåœÀC"ݘ|!o‚m"ÜÊÅwT&Ü=*׈…Z)He ½Þ·þúǺ!í=ŠŸ²Ê¥)lÏ”s)Õ{Q˜¦Ê<ƒßW: ÂMZN–jDZ#å›K³€³(/€¢ˆP©Wè¹82­{ ¥g‹""jÕ+z "¶ ó ºÂ6/WÈc)p5°˜ø?7ÿ} ¾‘¤±¸88˜ŒUhD®KŒ`àœw0O°™„çidҮ݃¬Ç`˜ã×7°l„‰;*äÑ œ \ \èçÙŒœ{ðäK½HƒåYÄ ìNþ\Û•H$¦ó‘FC3òû+ûàà7Ôw ÉL`9ů3ȵ~©_53-× …üM¬.@ê8ùŽ“Ó]idð[Ð[ <<y‹¥(V¸×‰8É›0Hû‘Eòr1#4ôwù(eÏϯ=Љÿb;Ãù¤S†ö–8yÛ|&uÄIõ¦é7*3‹ µ&Â0Uv]a Ux»’%¢]‘?@V‘JÁÒ9†w__þ…Âõ  &³R¥Ï-lU/ÚK¡ wCjlå‰õÂtyç婵Î^3ãyYWKM¨Å‹yð¯ˆ-dzÀõÔÇBödöÇñeÒ(àKH„j¸øP™ýÿf³˜èž¼ ¸ NÅÞ!ÍÈ5Ì > X\ƒˆö¹ˆXuÊäÓZ_ˆ§$°¸ø6°uu(W_¦ôùÿòkd5—FÄs åÃ6·#–Ü%À[€—€ïßB⛄&à*¿ìóÈYö«)ûMÈX“Ÿw! §¡f rËñ ä|Ž}uˆ# ¶w ×qÒ¸¬†yˆÈÿ°¸ø&Ò£a±äa…û0 xà¹ï¡=%Ü. ˜5¸ÿäMù‘RBéµÊêcÝ23®°0/Ï3(m¸yÍL–-Cº/«]!ŸFl#”ÎOƒBár1äÃç›s2¾ˆ[°‘0‘¯;žÝá¡õ2¸T²*g9_ÎP'Ô¦(7¨U[è^ÓßÝ&´O4äŽw¬X"ªy–õQ>”Ï3ˆø½¶B>ËeÀÃUÕlð(Äâ9½Bº£ˆµ³ZëvÃ¥h*±}%ðUàµU”1PKû"亿±æW#Ô+¡+ñ2y#ðÏÀZ!ׇܥD[«¿¿Ð½dðYàfD”ro.Gziþ‚êÝ•"!UßNΪ>¢~Ÿ^ürO%šò÷1 î\ÃàcHƒ¶cy÷kð^4ÈônX,€îu#™í;¹+òq§¼•\)•5l‹ø.ÜALøÜvSDˆëP>Z+L­H¥\®»l*o½~:™dσ¼€‡…âÜ„…z˜@Ü#JÛøe«®r'ï[ã¥- rãYQdÒâ"óþkáh¯ÃK†Æ¸œ[~ýK\ÓbÖõ¢»_C©H>ÉKÿu•©1íU¤é¡¼+I/ðÄbZî…ߌÌÄúbÑ*Z€7T‘îà¹äÛëRâ8†ˆ¢°ø~b‰Ÿ2€rªa1ð~àFĽ`(Y | ¹¦ŸEzN†‚.D”—î þ¾°p¿½ójT‡ñ÷ Äm©ïEDûü•}!byÿà+HCf(¨fxÿP‡ˆ#O ÷W-9 é%Zü9°½Æù[F(V¸×‹8]«PhÇw›(ŒôB¡°TäŒó*d”»n@RQ6…ÖKä£Ã¯Á±b´·ÏãŒym¼ëMÓˆG©„ñ³ ? MpR²^èçžÅC&\R`™4ä“·.£p³>î’Wîd“?Þõzøûÿp8Þ±Hp¯ ¬ûù¦—ì‘u óH^Y÷¨à\mT™ZCºì+qÊ–àGwƒeÒ]ƒXŸwUQî`Y\\!þ› Ä주p“ßLý#D„M@Õòzà£Ô÷]t5âztCã:ÓGy×­Frç«€·".ck\¹ˆpNÿUd¿|ø;*»‡ ”vÄâþšÑù´kBÎíä»iÕ’(p+0qÁ98DåXFå|ç,5ÆÃ`ð²®ÙTC‹ÒÁ¢ý%¼M¬åÚ_—ƒ*û©•Ê?ÆÑÙ|t(_í„Öµ–cu.G+Ò76ÆûnžÍ„ñqR‰‚÷P Ô!_ßTî7P”?ƒªò…¸XÓ5íïÑ~ÃÁ -‘кïB£‚t¥’ÉKf)n]¥¥¤´ãŸÖ8y‹“[YtDg×Ç?6X‚<'o½09® ¯Hñ¼Âùˆn7T7ŽÐRc¨lír‘]I@ì~^E™Õˆê“e ¥ÝV:Ëÿ@„Q‚ò7_œÜ»aC'Úgðbädš¿ç"¢¶šß@ F¥ûìßÀЈö€YÀß#">Œ|ø"µí ˆ%ú†!Ê8|ø8C'ÚÃ\ŽüŽÇFð9í±÷a@œ îY =þݹM…Vä"2ûùcæÐz¸Œ"Vdc@+w¼a+–´és}—–{GÞë2ô%ÏâÒA9JçüÜ Î7üÝøV÷l}³ >5é”áuËa×a‡ûŸ†˜“;ŸR1é gV ׯŸ›‹É?6›¯êß;dh0¾Êž‡¢>–š2ÈXŽ.ª³Ž{ˆû㔟‰5‚XÝÊЄákD¬Ñ•XËÀ#y¤(/ÜüýmÀ'ñ7T¼€D~¹®Lš^DÜF¢mD~Ï>䯘ŒˆÓiTßÈX|øSj+†ª½¾K16±B~.âæÒè{{)r?‚œk×­ˆz%ñ 9ÿêpD›jiA¬Ò/!ƒ.GÍÀß ƒ^jüìFƤô Ïr]ÇRþyò›mÂZ|N{¬p¯É$FGDü*…ÖN^T™¢~îaÑWÌwºˆKFéÈ(ýdVp äìKz\qa×®êÀM{¾{GÈ&åZÉÖ¥´ˆwå¢ *šuÏ),_ˆ*óÖKᕃŠ-{ñþçž=ß‚khB' ³îBábJ¸Ïôû-rAÙƒÁÁ…3²¢š\Æ<«ãkÞLåaTéâyàQÊ‹Ia½X_e¾á"$¢L9º€Ÿ0ð •„eâ~ô6ÄMf0T+ðÒH”•kÉoN'küoÿýÀ!Dð$é_ÿ/{6b|Ò+R‰÷#á"Xe}«!IåÆÜdÄÒ¾ Äþ>äüÄç°¿},¹s\M.Ž}%Þ ýO¤·á‹”Ò ¼~ ñ«¢'1Ý/E VšË1ïfäÏ¡þ7âÞU-.r=ïAB­îAîã¢Áš‘hJ¯E&‘{ÅTihÖ3´¥åÄ ÷zâ¿jÄ墿_¹¿Á—¥aËp¾P,&γ‚1ä…ž‹oÄÒݯ(ÕOàö%]Îjåí×O!¢錗«C1¡®ÈùŸ‡lnx¢©@¤‡Ý`ŸÛC‘räú¨lViWÑÒ7¯R|í¿4=IñwÏ»fAzÿøÂk×½°N½e"Ø„¿ço–HB&¤k'`ª3‘(•ØHõ.IÄêþ:Ê»ªL®`h„ûeTvaØ <4ˆ¼3”ov;ˆÕúC”à‘(<ë‘ØáI?ßvÄ2^­ËH¼í~žÿ…éøÖÕBÜœö#½w#ÂøŠ ÇÅñþÒ0¨Ê ÔàN$ìe!i°ü‡¾Ô=û]$|à'!]‰Fdêï?A„b! þïÆëá"i‚²—ŒD>©&Л–T‘öTæíÈüÕ`ëý‘ó.w/oòÓ| Åù$¤d¡E¿Ëiîu#Ž Þ—Êà8N¿8îiSn›1~˜ðþÐ1yQdò ç-žç¾ß¹¿MkE*mho‹óî7MaRGŒt_&Ôp𠕨?øT“×Þï5]ä½m4Ç·¼‡-íás ¹È9„\WŒRþÌ« ”G2©X:Þ|‘æ‡ÿ#þÿŽ"/Nzî܃ÿó¯eéž r=-ðá(?¹|ƒ1ºNÄÁó{ <ãáXå~²hà*GÃèCÉ@¬}Dâ2—ãFàëä\jÁÄšZ©þn¤Û} ³X‡q ø%öïB¢ïü 7XÌ‚tEUÃ.¤2ñH&ؾ IDATþ'ç~dÆÄíˆeùfÊ7“ÏAÜž¾we†IQ>ÚÐ<äž-ü}{€BBVVjdD~ÿ'——RÙp1Ò˜)Ö“t‰Ý•ï©£ˆùH#àÏ©¯¼i =ÆÈµ/@¢ïT#ž»þ…Ò  bBæx ¹¶ÌаŒP¬p¯I0"Dµ?ÀÑ/_<‚„| X˜Ã‰| Ý;²~Ù*°i üh‹9K}n¿á-Wv°|q3©„KÎ $0‡ý×ýÊ„© Y÷p÷àðª’ö@øT©u9$å*.=vrx|“GC,×ó­cž•»xÏEá¾|W¦KL1×¥ÜáE÷9ÚÁhWœŒÁúÊœ47ï©"ÝFÄ‚>¶"⤒p_ \2ˆü˱‚Ê‘C5.3ÌB)Öpø9"HÖVÈc † 1ðÿ­{ׂ¹ˆ³-ˆEøÇÔf¼B¥A³Å,ÔG‘(,ÿRáØB^þ çq}…´íˆ Q!‡ëô­” 9q:i8Tjh^‚4’*Ý;§"1ÄÒ^MÈÌcÀ_"Q§;xú826`'ÒøªÁá–ˆîu$ø v´Æ‰8㔥ü²+…< §)6@³XxCô$ ׯjçÚKÇâe¼")Mþy+aaø°÷·)ß¿]7€v0ALJcÀ¸~Z1Š*W¢®dõrè]è…ªúÖpOi"ŽæÍçkŽðâ+Š–¥ŽÁ‰HD9Ôàe; DDëý(u .}y×™Báî_S椹±LŽ«.ƒXR3 ü‘P}åʇXÝk)¢×PÙgÿ!ªŸ\g  …i€ÿ‡X»k\žap¿O5ìB¬ø?¥¼ÛÓ Ä¾©Få¤w§±®þû Ë:Šø\Ϧò¸ˆbei´ †$Òp˜ƒôZ”c*2.d$ ÷+°•èþð•û]dpðW)îÞd9 ±Â½Ž?ÊŠˆÉ2ƒS ¬áEÅvÁ¾âô|«2…ÇøŸÉ¤aÁÌ8o¸|,G‘._!$ÒûÙŠ øÀbX»=ŒIC¦“8‚!&n¼„¬{iÀÅÆx.ö­Óžˆþ@ê*oþœF)’hÚšàª9Üý›&N$4-M'B,%LjÇâòo ÓFú]» êÉ6ò¯}±k¾Î…ÛGãyâ cŒ±Â}phD(ÿ-•géÎìÀÃ? þוfR½%{YN˜ Èl©åºc\ÄMb°ÛT YŒ!VÙZ‹öz ò¼ºLšéÈÌ¥µî.‹RóÄõèdØ…4dÿ•E:ù ð³“,ûby¿é½(…ƒÜÛ È=8RhF& «4æÄ ûû5.ÿçÈýùåçk¡Xá^gŒ18‡ˆ Í^šo9û¬çìØÅEwq—BÇ—™ZC*cÚ4o]3qcâ¤.yÖóìá`çQ ØMZ¸Ic¼Æë7ñúÀK`L÷È>¼#{}c¹KÖ0¥²sÂb?+šÐá“Úw©ñ­øAµ ôuiΜò*o=<óóÇpœP½ý뢵&‰à8±XŒææfš›šhnn¢¹¹…ÖÖ¢Ñ(±XŒx<ê­ñÆóü߯¼>|͵Öx‘‚ –0 é’¿ƒêbOw#~»ƒžFDÿ5”Có³©p_€„ ,ÇzDˆ—uá?„…ì:‰2‡“È€×rÂ]#.‘O6#÷h-ÊüOäïãœ*ÓoGÜ0jQöïßìJQˆæ"½ICÕÃ2œ‹ ­Ä}Àÿ¡ö>üby b$°œæXá^OŒ¸eh¥ˆDÒ‰&SiÒ¥ÿöD9áVw%H¬ñjCižª¼€ôP”‹[¾±¯Käýµ›½õ0"«î?Eâ«×‚4Ò蹑ò‘ˆ&"½S#E¸+äœ*M²Ô‡ŒO¨6 Ò@9|yþUнoåXá^'€ãûRƒSµ ¢½äÒåù¨‡|¹Uh)ßöÂÏ¢þÙ!7õ¾\öÚf®¾¸ 7í[¯M¯=ün×`\ŒÛ ™pbÜ^p»1^JºñKdõ«¦Ÿ!Ç(ü^TDD¸/ÜsñÛ ÂOªp¯„ç÷ÄBo 0n˜¬å=M&­ˆE ñæcì>4†'¶â½Äœ©Ê]±ÞÞ^zzz0æUœH„X4J<§µµ•¶¶6ÆËøñãioo§¹© ”4ÀLv0l~ŠXÜ# ž18§nçlÄ·y±'7©K˜bIŸŠø/Cb/CÂ>„ï#–Ì“l¸q'('ÜAüÒïâäDØ8à ÒA„ûPLúTŒß#bp¤„ŠœS&Í,¤ÑT/áþ2ƒ÷k/†‡¸§rŒõƒˆ·–f ±X í8x¾KMð»y¾{Íi6ÓùÀ7At'–ÄJÕ‡´òÑÞ†ˆŽvÄÊUMŒè0qoù$µ Ñè!×|™n¼ ‘nì“îg!Qjʱ¡‰_ŒðkF®‹L˜*ûçO roG-y€Ú¸W…Ù†X³+ ÷ç¨}/ÊAÄ‚_N¸kdvÛ‘Â%ÈdYåH nJåB€Ö‚cH¬w+ÜOs¬p´£‰D£/=7©P¾è rùv}É“üYט’~ï¾͸†¶VÍ_×θö™¾HNBº“9™Ãx™CàöŠˆÏºÅ˜‰C‘`ªÅü<ƒé¢Ñø¹ã¯÷Ï;OŸg ,õ€—ï½½çÎ=Á×EøÄ÷â¤]ˆ Pö3ۖɸd2½ôöörøða6mÞL,câĉLž<‰)“'3~ÂÚZ[¥‡EëþÑkN49Q>”ô „¿F^rµb"t./“F#!Àà}\/£üྠbý®×ÑNFþ„9ÝTÙHe—ˆZ4kíOß…÷JaLŸ§öD{‘†C%&!Úc$Äs¿”ʳÓv÷ס. qû+¹|YF9V¸×‹GÜ("Ú!‰àùÂ=4l²âàST1Wš°Ð/-ܳŸZsÅÊfæÍhÄM$Å’ž>„I€ÌqŒ×+á³29,Ö–6ÈÊHv¾…=;骂à9®‚2*óˆe?4pÕ„ÜlÂû² å`€dÂð¦×æ‘“ùå:Es…Gp5¢:lA/ÜžL$èììd×ÎÄ7v,Ó¦OcÚÔ©L:•ˆ#rÆór½–Z°qùµw#éBÂ=®¢ü3ób`1ƒ ÓØŽøÉ—ã"öêÅ‹Ô.AæØfÜäp»ÁëCŒ?þ`Ò¡Âx!á^ä0*ö±˜˜G|ܳá!}z…?à_ÌCÚshˆ¥ùë7â•ÃXû2´5T¡˜h/e!/p˜/ìÕÀqȤÓìß¿Ÿýû÷óBôñ‹;–î®.´Ö§ÓàÔ¡ä8ªð_º¸æ !ÙÞƒóRŒnA&(R9÷#ˆå¿¸H8Ì¡îþ ¹ÖS¥ÙH¸¼iþöfrâÝ -S«È·ÜÀÊZ²q«5idD%jÙ#f?Ùð`%i«°ÿTa†¿Tâ·C]‘‘߮ҽlÅXá^7 Ž#"ÚÓºLt˜Ð!a_2}‘¯ýüµvȸivï=ĘØAV/îÆé:B*‘D~„g1BÑŽXÇî*h#„?)¾-õÜ">ìRàÂí.}Iͤ¶>wCïùV G{ 1jrn7Ù(;Åcµ›2פðØB‘¯ù \Ïåð‘Ã9z×ue†\oh¯õ(Æ V§û‰Ncð1Í«eÒ^N¸+Äj>‰G—¸šòþÕ.ð+êg¥L"ƒ‰Ob1_ŒŒ—X ž‚ˆôFê'¸kEŠ¡ ;éRÝßÂPùë@å„yc…ý§ ã‘Þr$€gëP——¡iðYFV¸×tªãDˆF£"àBñÐó––p‡ÉV ¶I—õ§ö<úúúØ»o7[¶î$Õû*ÿô'šÉ­è ,Éu~~šœ«L¾› ¡Jÿmý,òÙï¿m㇊ ¬ö8(åÐÝå¼ùG¸óÚ8þ³"Aç„)bm/¡©‹¥ËÛV$¯ Á¥)Ðu½ò!n,¥8Œ¸ÄÜDVYËÉ…š(?F¬îåÜ)– ƒÇþcùŽõ)'¦62ôQ+¸œ\ÈÉZCë^‹Œ/x •d4 É=ø‡&ïá"÷rN‰16IÔp1i–£“Ú0.‡¡¾Ï<Ë)ˆîuÆ@Öâ®´&/hc%ážu“Qy"W„nnR!×u9zô(;vì`÷îÝìÙ÷*==)þþcí\x–"8EHšÐgÈÂLTšg‘/vlÈBo‚è6ÊŸ‡‹Q©d”[Î;ÈK{&óíGZÃó|òŠÌ³¾÷æý¶»¶á¼CÁ H¸½rS»A¬î¿ úÙI—"Ó”ã—Ô/L!ÈïUÏòÂDËú›×£)ÃTËÀ ‚§lšáF!=g• }o_!§ÀËÛ2œXá^GŒñ0ÆC;ÑX'kq(1ð4›]å¥Fi‘þN$B:bÇÎlÛ¶={öÐu¢ c ½ ޾™w^¯ðÒæÔôÐ0ŸøyC¾'äÚ-Û‚°™&ˆó—´çÐ÷øä5GXßÙÁ3» ÍñPxI“›Õ4ãÆ˜²OÉÜïv•ñ×C ,0ÙüÀȽpÊ¿»jƯBü‘'!>®ˆrpÁ2Hä.àbí݇¸Ä¼Ê©1Ez/5æõ”~v*dë4ª›dÆA„~¹ˆ;ˆ›L­\+ªiPx Ï5_ |i†òO›?Ê!b´ˆJ Z‰½ÔoN‹°Â½þˆhM4ÅU:$ÆC¾í û¨(ñ:ç;â8­4½}}ìØ¶Í›·°{÷n‰ÆGÓݯYå3ïj ª É4#çÑZÔ&Ï]†œŸ|v£JLå€JùŸ.½ ×|œÏ\ßÀßmåX/Ä"AY*ßÕÅø£‚ –÷=”¾X/F~”þÐy^òà(âÖ²i†M±°5+ˆ5g4¤ÓÏY+¼^tÛÒ¥K×nÚ´iƒçyËËd½páÂ…«7oÞüDèЧ o»ë®»&}ò“Ÿ\ÓÕU:Túœ9s6ýêW¿š´lÙ²Õä®›*X7oÃûÃ×YΧ>õ©e_ùÊW¢©Té÷X,ûþ÷¿ÃM7ÝöÕ¯æÉaЬ—Úf\×ÅquðàAõ¾÷½ï‚{î¹ç&×uOÚ®dÂãÿMJ'ž1:ô›÷Ckí\~ùåSxàYHCÒ -nè3X/v^L:5²ÿ~e*¸Ã544¨¾¾>¥²“SÔ„ÓëÉ2t8TaèC3VÁb)‰îuÆ“…3)X*Ìcž•]d`$A;½½½lÙ¾• 6²{÷n2™L6¯ˆVô%=ƶhþêýÌœÉFŽh/Fø•äÛi 7åN0làæôôŹxÁ!þäÊñ«8®§ˆè.Òë1Ð}ò½Ô—Ó†°{L „*⋬°ø,©¥>ƒu‡\8ÀÂÏðRl[Ñå©§žŠ¾á oè|ðÁË ÷ȸqãÞìØ±±íííAC¤Ø¯=º0•J•Œ¹­”ò®»îº½Ë–-[@mD™3f̘±*~ªx¹ª©©©±&™tGïÚµ+~ûí·_ó裮r]w@¾ÏÑh4ÙÐÐÐÛØØØÝÖÖÖÝÒÒÒÝÚÚÚÓÒÒÒ×ÐЊÅb™††† Àý÷ß¿rÿþý³Jå‹Å¢W_}õJľÒ9Šøl£óŸÿùŸÇ¿ÿýïo;zôhɃÛÚÚZ¾ð…/,ÆÌh—]2¡Ï ¹F­)Xï÷ùÅ/~1ò™Ï|Fgߥ±b³<šÊñÛAqÛð`–ºb…{1þ?ÇqˆÄb(­ ÞéY?u¥roy¥òÜ-´Ö8Ž&™L²mûv6lØÈ¶íÛH&“8ÚɦSÒð<ÃßÒÄê©^3²E{@¾gJŽ`W#穌";@5χÞÁ3ŠÞ>‡[Ï;Äó“øéºÍŽÉ^ó~e!¿ªz_9yåöGa¬¢hhhˆõõõµ“ßa1¬ >£þêH‘õbß Å¹ö?s-¶|ªÝ–·½¹¹Ù¹ñÆ·=ú裉t:]òžqãÆ%O=õÔØ+¯¼òp©4étÚ<òÈ# ’ÉdÉpmmmG®¾úê]ÈyÔB ¤¿Ç0°ôEïÚµ+zÛm·]÷Øc]ZíA Ý'NÜ?}úô=Ë–-ë\ºté¡E‹;묳NtttdÈÍÞ ïì³Ïž[N¸¤¯DpϦ×ÍÍÍãÇ)ûnmiii>ãŒ3ÎfSüú÷mÐ Ä|XÜg ¾{ï~÷»Í÷¾÷½/¼ðBÙÊŸy晓֯_?³ ÏÂÆHvŸRªÚ{o´ˆXEuúhÔ<È-#+Üëˆ1bqDbÑÕßÍ¢¸Õ]FH&Sl|é%ž~úöìÙK2™@k-dBægÏ@WŸÇ‡ojå7Eq3§ŽL¬*˜Ê`ayfBÂXhr“4‰qÌ%Jc$Íç®;ÄÁ®É<¸IÑêOi10kz¾?(¿Ô€Vå߯;u~“BŒ1‰üˆèðzð=Ä.¸à‚ùk×®-ë°xñâ™ÀÈ·œ‡{x)Õ4+çvQì{à7_k¼[o½µóË_þòÎ;v” yâĉñßùÎwμòÊ+.‘D½ð ­Ï>ûìÒr…-\¸pëš5k3zQ€êîîV7ÝtÓëÖ­»¨šZ[[¬Zµê‰n¸aãe—]öê¼yóúûÐ,…¿»J&“*NU­¼ûÎTò‘ñyžÉåþйqèx<®§L™2±’p¿è¢‹–#3 ‡{ Ý‚²ßÉNŸBzaŠ.7ÜpÃÔ_ÿú×Ý„8õ¯¡:ßõ(£Ã$fAXá^Oü׊ÖÑHÔ\šß3¬u¾hŒD"¸—Ý»wóä“kÙ¼e }½}(­ÅÊ䢧ÏcÙ¼¹%BÔä0RJÎÉÑ sˆ7óÍE\ÿšhÝ*2®2Ü{ÑCD¼èD:ÂÄÖn>yõ Öïiçx|ös—óù½ç…è,Q±‚ã_÷ eJzO Œw’Â%xᇗ8ò²‰Ù^ŠùIæ™={ö´§žzªì‹:7 ñˉî@ÄœêxãÇ×+W®|±³³s¾ëº%Ÿ¡k×®=óàÁƒutt÷Ýwß´ƒN+u|$I]qÅ/2:…ú«¿ú«åO?ýôÊJ #‘Húì³Ï~êÎ;ï|ø¦›nÚG¾Ïþ©>ûf-(×@ ãc´çU5³[`Qvè•û^ØH »ê8W\qÅÌûî»/šN—Ö¼Ó¦Më¸ûî»W­X±â(Ò& Éжà™Ð¯\ÇqT§:Ø#ê¢Å´02B[ZFV¸ŽÖDcQ_C ô}Ý#‘Z+öîÝǺuëxö¹çéêêB)…ã”6¥2í­š¿|SÇ;$û†Ø°d¯B"Ýå(ðg;íIŽÍй§—›LÓ0sŒm„¦¸îyñÏóç&ʺþP°2Â3ÓêÐc< ZÓÝ×ÀYÓŽñÑ×5ñ·÷Å1€õ„d‹Î³Â–Q _ÎZo Iç½u|^Ì$ü=<¥{Œþ<^°­œEh°‚0ô«W‘XT}á`¾‘Œwûí·o¸ÿþû/9räȤR‰^yå•?úÑf~ìc{™"ç~ÿý÷ŸYn°ä„ öèCÚÄ(´¶ÿö·¿÷ƒüà ÏóÊZÁzn¹å–_ßu×]ÇãA¨Qw=†ƒB!æò“A"\Ub"òŒ=g¶ŒR¬p¯ b ž÷hT&úS*'Û ¾¨Féíëcà xøá‡yå•W€ÜàÕbÖM¥Àõ ©´áη5qÅù’}µ²ëæŠV€ÖàD5ÚQàÈ“„cÝ.{gؾ³Ûúxig’—¶w³igо>hk„¥Ó`ñ4‡ESaé4—y“aB+´6@Ä‘r\Ò!1œLUçã½ëÓ]…t‚qð ô¥o?ïU^Ø=•_>ïÐÚ@ÖW^®;ä½[ µVÐ0(Óe/æ Æ¦L™2öÀ¾= é/¶Ã¢»Pœ~ÛÙÚ|–ÚVŒÑ"¤ë¹òÊ+.Y²dóc=VR¸§Óé¦x`‘/Üèõë×7mÚ´iQ¹BÎ?ÿüçg̘‘bôýNæë_ÿú²ýû÷Ï,—(‰¤n½õÖ_ûÛß~,8®u³ C•nBáô”ÿM#ÈŒ¹P¤`Á‚s* ÷3fLúüç?2'AÒXöƒAšÜxLø»RêHç1y>wW‘¶VX ÿiŽîuÅ€ñp"¢‘¨¸]hñ:PÈŒ§(غe+¿{ðA¶lÝJ2‘$qrÇ—Àó «×ã=×·ð‘[cdR^6ßZTZ)¢1 æ›×ÓGNxtîKòòîÏoîá™=ìÚ—áб ‡Žy44gÆôÙ,9c!7¾ã¦M›Æ¶mÛøÃžá¡í;øáÓ¯é;Âäv‡q-Š…“aùlÍkfxÌè2­Ý£­Qô¶ñ ™´ëP:§¯œ@ò›äÖq㟻î{NtðÔ%e^‡°ÛKx,jIËz."P0y–ça* æÏ¬ U%ü"+õ9ÜIF;Ð7ÝtÓ3ëÖ­;7•J5MdŒZ·nݲ§žzê±óÎ;¯‹œ¥Xýë_?ãØ±cãKÐÒÒrüï|gyG呉Z¿~}óo~ó›K*¤3çž{îSV´Ÿ¶T2>¨j\‚ZZZšZ[[çѶѼÁ·ôìkÞûÞ÷NûÖ·¾åùc~J1Ò¤IS8PrzÑT¦Ò2Š±Â½^$ÀDLvpj4ÍF# f<=~ì8>ø O<ù$ÇŽóÝbtQ {!]½KçÄøÄí 48Šd‚ú“uw‘:¥AÐ6о—]ûRlîLñâ¶^ž{©‡-;ì>àâgÌØ©,9ã Ö,=ƒåË^Â…ó™=kMMM466úâÕ¥··®®.^~y[¶nå…õxqÃFžÜ¼…‡¶Æd’4:)fUÌŸª9k¶fé4܉Ú24Ǥ¾®Q£ðüÅå«_'8%Ÿ!І§ô¥£LjKñÙ«»ù㟵²÷¸¡)\šü¨ŸzaH‹{ñþã[Üý‰°‚xÑ#‘ª,lALmF—øro¹å–_ýêWwlß¾ýŒR‰Ž;6áG?úѼóÎ;ïyrçï­[·n^&“‰–:nÑ¢E›.»ì²W©ý½QµUÔ³…ÕöwSßùÎwœ8q¢d£`̘1‡?üá?ê­É5¨&^ºëº'{ÎFk]Íß¡rjy}‡óo2p‰+‹çy'{o@U;Øu]—ÒaMÃ.ˆa"Ë—/×Ñh4YªaîÓzÝu×½ç›ßüæ#È`ß$bÍO >òÉж td8´§€ûÈX¡á˜˜Ìr a…{ þRµ–™S '*‚rËæ-Üsï}lܸÏóÐþ ÕJO&$S†±mŸ{O 3&i’=¦¬h÷ ¾âîâÈÀQE|_\—®>xõhŠ=¯$Øðr‚ /w³öù.¶ìréM(š[ÇÐÑ1™Ž‰“yÓê3Y~ÖkXqöÙÌš5‹±cÇÇK–¯µCKK ---L™2…‹.º€ÞÞ^Ž9¶íÛyöÙçxþ… lÚ´‰ßï}•_®?Dª·‹ö¸Ë’i^;ßaÑÅâiÓÆ)Æ5§iŠËùày¸žGÊázžQ~ïFØp’oDQÊГŠpÞìc|âŠ(úó\£È;¤P˜—šx©Øvå[é• ¿­Gº ­Jú†±‘|žE™4iRzõêÕÏ–î©Tªá÷¿ÿýbàü?ÿçž{®içÎ%ÝDÇÉ\zé¥/¶·¥ã$ IDAT·1¼kÉ@ò’ûóÉ'Ÿœïy^Ùîþyóæm½í¶ÛöRß&UA;Ü £Á¸«Ô¬ìêÆÅæ•=ØòÛ˜+,¯T>fÖ¬YÇz*wÖ¯_¿ xÕ…y†Ï3pÑ D|qßÉûN~O@6òoΕc9M±Â½žø¾Žvˆøî2ÝÝÝ<þÄÜÿß÷³oÿ>”ªtÕ3Ï2®áConáÚ‹#¤ËŒƒ7Fôc4"¾éDxŠDÒãða—½ûzÙ¹'Ŧí=¼´£·ö²}w†¤i¢£c3¦-áš7Ìeñ¢…,\¸€3Ï\ʼysin®MÏ]SSMMMLŸ>Õ«Vpôè¶mÝÎK›6±eÛ66oÞJgç.¾ÿÜ~Žüî­‘.N‰°xJ„ÅÓOÕLç1uœaL£¢1.ÁPÒCÚ5¸žƒA¡‚–K1ÈÄXsæq~·)Æ=ëZâPhqÏ}WßÛ?Mà¥òäú¨Ò±§%7ß|óæ_þò—Ê Rݵk×Ìgžy¦yÅŠÝ€zâ‰'&”s“éèèxeÍš5;‡¢¾mH Geß¾}Ë¥ÑZ»Ë—/ßA E¥ëº¤R)û¾óÎ{ ÕþæÕŒò,tC,u|¶WìÜsÏ=ÖÖÖväĉÊ•ñòË/ÏݰaC|éÒ¥=ôoŒ„¿GÍÕR°? ·Y87¤Œ1ÉéÓ§_¶gÏžj^¸öå2б²º‘À˜žç‹Einjfë¶-üè‡?aý‹/’N§qœ¹çLuœèñxÇu-ü¯·7àºà¹&;€S)ÐŽ%ÅÑà€IuÙ²'Ás›ºÙ¸µ‡M;’ìÚçrôD†=M­,Y²‚K¯YÎ/fñâ…Ìœ9“ñãÇ1fLæ©)cÇŽãÜóÆqîyçJ¥8qâdçÎ]lÞ²M›7óÜó/ò«û·â%ÑÚmšYã=Lv8s¦fét˜>Î0¦Iu<2ž"ãŠX7PJ“r¡!êñ—×u±÷x;ÏîV´Ä+G)½.ß1yñƒŒ1wH,nõ¤Úºôó,…yýë_påÊ•OÝ{ï½×—JtøðáÉ÷Þ{ïô+Vl܇zhN2™l*•þ‚ .xöŠ+®84$5€EvÜ)ôÚµkÛ»ººÚÊ&ÒÚ]ºtéj‰èرc‘d2Yq&ÌXÁ«ã>d÷ª ]ÙU¹¯Pþ™0û³r¥ŠŸk±–KÞþÉ“'«¹sçîØ³gÏ‚rÑŸŽ=:åK_úÒYßþö·å[¬±P¸^¸ÍŸl„FɳÜïÙ³' ¼‘ ƒSµÖjñâÅã7lØ0 ÿüH<Õ^3Ë©îuEþNµ£YûÔZ~òãŸðòŽ~Å`ÕêP ºº ç.iàsïn"7¥ˆ7*p4x‰”áx·Ç«ÓlÞÕǺ]<ób_Ns¢×E;ÍŒ0…¥+–pÁÊóX¾|óçÍ££c---Ùè7§ ±XŒ &0a–,YÂ5׈˜ïêêbÿWÙºu/¼°žuO?ÃKÛwðÔÓG0k{Q&ÍøÆ g΀e³¢,›¡™=Q1±MÓ7D# Qd ¤Ý(“Û]>{Mwü¨‰ã}Ð…<«z¿©ý·çV¬«`0rÞ3{$ Zãy^U"…‘}žåpÖ¬Y³ñÁ|]"‘(:ªçy‘‡~xÁ§>õ©{öì‰oÚ´i%,~ñx¼çª«®Ú‚¼À‡"FyÕ¿A ü½û•}àÀ†T*UÚΧ­­­R¨À ¶mÛÖT©Á⤄{ÝEJåY‘¡òq¯qT™RÇî50Ÿ…ëŶy—\rÉKO<ñĪr³$»®{ôÑGW;vì‰ööö BQοÜþ°ÿ{^Ãõÿñ—¾úê« JÕ#@k­gÏž=˜ƒÊ—>9†ió#dN¸KzÜfçž$/mëá¹Íݼ¸µ—Í»\Ò¦ñãÆÑÞ>Ÿ×œ;%‹qæ™g²dÉb-ZD{û˜ŸiýˆÅbŒ?žñãdzôŒ%¼ñ1~;f×®]³K¥ëèèØó–·¼e7¹n­©‡¸*™_OOãºnÅL‰D`ɨEÙæ¡‡šrüøñ².ä‹¥A ÷jŽ5>'YÖ Ë‚F àÞ¢s/ZT¨ŒB¥_ªì`»û¶·½mû¿þë¿8tèЬr…ìÞ½{Ñ]wÝ5÷ÓŸþô6òÿvKYÚ ëWl §ñî¾ûî+Òét+Uà‹î`6ê ¯`ò½Àš¾.91ŸÒƘ~‘vBÇYa?ŒXá^G\Ï •bݺ§ÑZûV~ï{žAo\å°kï þó›w$Ù¾»—{ú8|ššÛyÍk–³ìÂ…Üô®E,Y¼ˆyóæ2aÂÆWvðèh¡££ƒŽŽ.ºP¿öõöräèQ<Èö—_fëÖí2vË6~z÷V¼¾ŒoSÌcÁ´(ó'iMëå‚ÙšÿÙæ`tÖŠLógT Ï• )]äòzfH^žõ¤êéÝÙçY3sæÌÔE]ôÌž={JΤºoß¾™÷ÜsÏ”7N8qâDG±4Žã¸«V­úÃøñã]×uã8C)Ü+æ=VYÇ]­uYS©çyzÇŽ%ádËVû÷ïw~ðƒ¬r]·ÚîÓî!Q^–¡´]eÑÙäµ,»Ú¼ î«“)¿ò´©â’Žä’·»Ø!á/‹/îYµjÕã?ÿùÏË ÷d2Ù|×]w½aõêÕw­\¹òÅ-êÅÜhÂi¼ßÕí·ß~aggçåê=ct*•rBÇ÷‹dS°ûƒ;ÁÌÚáýnèÓ\ÿºšÐ¾lyVÔ-V¸×#"å¬ìƒ7ê$z»øéáß~áqäDÐ,\¼”w}àZÎ;÷\,˜ÇĉŒ;–ÆÆ²ƒâO›š˜ÖÔÄ´iÓ8묳èîîæØ±c:|„-[¶±ö©u<úûÇøÑ[ˆ9†ñ-Ç9†ë6¡bMî/¯{h[¿é^Ãn4¹ ˜B¿ýH´é«ÉçY ï¶ÛnÛpï½÷:~üøäb 2™LÃÃ?<§³³³ä Öæææ#7ß|óÀ"ѳ”Õ\¸O˜0¡/–eÒó¿ª3Í?Rù>_ )vLèØR|Z71G‚ucLÄ£ý¥–»Ÿ6X‹{=1¦&s™*ZÛÆÐÜâây®ëâf\v¼¼ƒíÛ¶áyžçÑØØÄøñã˜0aÂÿß޹ǹQ\ùþT·¤Ñ¼5¶gŒß0b &ĹöšÇ5á¹`‚!<Øò!Þûá³ Öönn6$$7χÜÝ% ×$!„…½fI ÄNl60ÄÆ&ÄãÏÃöxÞ3šI­®û‡ÔšR©^Ý#ÍŸ¯?²º«Nªni¤_®†ùóçÃâÅ‹aÑ¢E°páB˜5kÔÕÕA]]ÔÔ˜^³5µp]úúú ¯¯Ï[†ššš ¹¹ššš ½½º»»¡··R)B!ˆemÙPQQ!ÛË e[`YÌŸŠ÷lJŒø.µ’¼öÌwQ™`è 7ÜðÎûï¿ÿ_d) d-ËJ­^½z÷dº¼KÒ9sæ¤fÍšu´µµUz=@GGǼ‡~ø³7n|Se§À~ñÅëxà›zzzf ŽR;›b0¥ñ1)+z×EhO˜í1Ã\<Ï?Ëú/؇ÃäÞ{ïýÏ={öœÓÝÝ=OÓ_ä•W^¹þÊ+¯´^~ùå_Ù™Ÿ×½üpS± ?üáç?úè£wvtt|F}”‚QOŒDÇ*+çÇÈ—‹ì„Câž½ 9ñÞ¯„J)%‘׃Â}¼ ð–,\Á$êµ, X„æüRJfo@”J¥ ··:;»àƒöB:‰Î‡ÃaˆÅbÐÐÐõõõÐÐÐ .„ÓN; æÍ›sæÌ††¨©©H„¿¡Üäcddúúú ££Ž9ÍÍÍðñÇCkk+tvvBWWtuuÁÀÀ¤ˆeA$P( ¡P*+«À¶C`Û6+9'„€g¿M€ð¯‡ziHÈFä@f}~&~H]ÊF/¦*&y¦l”çËÝwß}ðŸþéŸö666žë·íܹsï¸ãŽ?CéÏ“÷í¨4¢”Bö–òEÏ%—\²{×®]A·’O¥RÑM›6]þÌ3Ï||ûí··ù˜Gyä´'žxâÆîîî٦㢔Bæ&›c;ælš€Î†2¹×Åúû'žo­aFûµ%Y¡¬|sQJ½Õ¨LóÎ¥~´ʈA6Z,t¥(£@®»îºc[¶lÙô/ÿò/N§•_Š©Tªâ׿þõW–.]:ïÞ{ïÝüÕ¯~µ‰ñÅŠ[>§ìÛ·/úÀüÅ–-[næïyàN›6m¥4ÔÓÓs–ð`(µ‰D„9f^¸»’:Ó_úDÇ¢³=?n토pGLb¬Þ*¥£9ÔüÇ …ŒP'„€KÓy«—X–Ô"`“PæT’‰œ€Ì]DÝ4¤Ó. $ ñàAøÓG7íB(‚šš¨¬¬„êêj¨¯¯‡E‹Á‚ `Þ¼y0wî\X°`Ìž=*++¥‘äRB)…¾¾>hoo‡––hkkƒÖÖVhii¦¦&èéé¡¡!„ÁÁ8¸Ô…p8 ‘p"‘T×Ô‚m‡ d[`Ùöh‹·ð=Íœ[/ý4ïÕ’oNܾ^b>û#"eþ!ŸjkkÓ^xáÏÃÄx¬X±b×i§6 ¥ŸÜLô›Î½óÎ;÷?õÔS-º•:úûûëׯ_sww÷ ÷ÝwßÈ?§lä.Çï~÷»ºÇ{lù¶mÛ.òukx×uíáááÉó“Ç'Áuüë©}ŸfÓ}Lú2Y)HmgÚ‘üÇܵuëÖ 8p¾I·ûöí»ðßøÆ7n|ó‹_üâ»wß}÷ÇÓ¦Mó–x-Ëo~ó›ØO~ò“O¿ùæ›+=ºÔqœ‚‹ÓÂápÿ—¾ô¥ŸoÙ²å¿Ê„;ÇqØ éL#î~~ÐùäQFâ½h»¢=€Â}œ8Ö×çN¯ªÙû—ìì>[&+gE¾KÝÜ <Ÿm¡—jFs¬fs-ÁM§ÁI§¡`ººº ñàAx÷ÝwÁ²,°m;oýôyóæÁÂ… aÉ’%°páB˜;w.TUUAEE”••e¿t"NÃÈÈ C?´¶¶À¡CM°ÿ~hjj‚#GŽ@ww7ôôô€ã8™ÕY²©A¡p"ᄘQ_™Ë)÷VðaÏSî“&{žˆ r.\9F¶–»à¦LB;`ÎùÔ_ßÜϸ§òq³fÍš?¿üòˇUi1³gÏž¥ÝÝݳEÑüÊÊÊ®d2Y)[“›Rj …`lpãë>J¸¤Û¢ömºbQö— ^ôÈ¿RéÇä"`ï»AâK&8Eû4‹Ñ¿ÿû¿~ݺu3:;;êúœùÖ[o]»k×®Õ?þxãܹs›çÎÛ^WW×W^^îô÷÷GŽ92£¹¹yþÑ£GOÇã³Òé´tÉ·3Ï<ó•»ï¾ûÃÍ›7AfC)µ²«[ù‰Ž ]qu&Â\61ЂÑvsP¸—J© £Ë*…|ðÁ¹Oýä_-þC]˜#À¤\'óY äÄ9g›—.¤œT*ánƒ÷Þ{?Å'™´›†úz˜¿`œzê©0þ|X°`Ìš5+—G_UUÑhl{4}Ôqœœ8ïë냶¶6h?rZ†Ã‡Ccc#´µµAOO¤Ói „€mÛYnA(‚h4 –Ûʤ¸°~"ÄÿªjIÎ{î9÷·»g#8ï¢;ª²ûü„æ°L".™]¹re÷§>õ©ü÷yóæí¿êª«ŽÂøœ꺮ÑÅ©¥|ÝÖ­[÷‡;v|¦¹¹Y»ÚK<mß¾ýŠ7ß|ó¢Y³f5MŸ>ýx8Néïï¯êêêšÙÛÛ;Ëqi Cmmí‘[o½õÅçž{îjY¤ŸRjŒŒ„alÇk¼$è…SàþuF¼Ž»*²ëa´Ô£é ¸¾”Ñ_…{ã7Ù¿ÿ³ßûÞ÷¾Çu÷ð ©Tªº½½ýÜöööswîÜÉöaež9sæ;ßÿþ÷_¨©©I…B¡”¦?oª:Ç¢sbU× Ó îc$1*€JæQ}Ž@¢]tÑì}úË¡~RMtâ\VžMxô ´öl´?“ãmMlˆ„#å£ÖÍæÑ»4sQlwo/ttvÂÛo¿ Žã€mÛP[[ Ó¦MƒX,Ó§O‡¹sç¢E‹ ¶¶6³ŽúÁƒpôèÑÜÅ¡===088™*…ÂagóÏ-+“Úb[£é-yË- 4?½ˆ9N‘ –‰káPEÑxOÑ6?‰¢i`jç¸Ã… Ç ¢¢‚\}õÕïïØ±ãÂd2Y¥³/++¼ì²ËÞÎ^”:^ï£~JymÂç>÷¹þûî»ïßî¿ÿþ¹###Fws§¼¥¥åŒ–––3üôU]]Ýñ·û·Ï\sÍ5-/½ôÒj•m___Æø:¸!wfU™¢ç¸Þ͸¯-1ÕM1ºk3!„fSed}™DÛó¶~øá?E£Ñ=öØc·uvvú]í%ox¦†Ó¦MÛóØcýàâ‹/îknn…B¡•ßD"á½Yq­Êmå·ƒ ÌDìƒÄui wC²Ý€*¨Œ0¯€dÄy2ö0d–=Êû©R⺂4S!n’‚À}ÈÄ©¢Nå›BæR¶eØ(ó&x@]œtÜtŽë€ÖÖVpB¡LÎ}(‚T*Éd\×…H¤ "‘p6½¥l+U÷î4J ÄÊ<{¹á¼˜&^‰"ú-Óâc‹XÃۜש‰Æˆ{!îÚµk›~øÃ6·´´h£É ‡n»í¶`tå‰Rc-QTÖ#½nݺßzë­_|ñÅS©TIn6‹ÅÚÖ­[÷ìúõë?:~ü¸Fã*ûx<^žL&!‰”&)0ú:=êíÓvÜ#îܧ‚Feµ©4¿~D:¿Ÿ·}ÿý÷7ÖÔÔüóC=ôß:;;ù‡oêëëÿðÀsíµ×vAV„Û¶íê„{öÂV~âÁ¯gÏn«Îì!²IRDNXážéÍC¯€Œ@¯€ŒH¯‚Q‘žk–}&Ì>»L™HÀ Ôé'•(ªËv˜—b!0ã׆—ùã}8’Øf_Õž?xv¥;‚P8”‹dÓ‚Qúc/ %…J:Ñ&‚6y~$õ…íEb›(ûæ·ù2‘].7žïuœòÂÝq"ºÅu]ö¦6'îœ9s:mÛN9Ž#îµµµGï½÷Þw¡p¢_Jh*•"ª ¥ÔN¥R¥ÝèOúÓmëׯïÚ¸qã•---gÕa,k¿à‚ ~ÿä“O¾vÊ)§$™UWW+oþÔÝÝ]700`•——›.?ÉCÇÕëN§Ku~©ëºÚÕ’É$+B‹Ù·÷÷.%N[àÿ³ÎÄqmÀÀq›É\2î|¿'x׬YstÕªUÿçë_ÿúÞ-[¶\ÞÝݽh¬«£Bh,Ûá…þÛÆ[UUÅ®ÕÕÕiBˆ*Ç’Éd%äŸã BÝ$˜`"âu“#Ä''„pÏþ!U0/z…Ñ<ô0òE9d-ÐÞ^WœÍ¨æu]×Í^ðI)5^yE–JC)'6}¤Ö˜¶ãŧ.o;¯œiËm2v’(µ"åDT&·Þ§dÜŠ4™<[ɘ =[gY@˜÷@ r\ÇX,6P]]}Dö~v'TSSÓ 'HŽ{²gÏžStë=¯\¹ò·+W®ìƒq~ýkjj†c±X["‘¨ÌæÿæA)%‘Hd¨ªª*ãôº}ë[ßÚ{Ë-·ø‡ø‡ó·oß¾¢½½}‰ÉÝN=lÛNÎ;÷à .¸`×í·ß¾ûòË/ïšN§Á¶íÜø/^ܺoß¾vÑšÑétÚ¶m;ÕÑÑjhhPF4UTTT¤êêê:(¥¶mÛ€t:mÇb±ã•••Þù-jŽ{]]]oUUÕ1˲\>m‡RJ\×µb±XJã^QQ‘¨­­=â8N™è½•N§íººº®P(”Оˆ®­­¬®®î „8¢ÏÇqB±X¬;‰8 ?ϲþeÑãQ___ŸúÅ/~±í7ÞøÃÓO?}æo¼±¼µµõÓÉdÒ× #‘HßÌ™3ÿxþùç¿q×]w½ùå—÷‚$ÿ|úôéÇ¢ÑhŒNús㢔†ÊËË»{zzH]]/ÔUk·‹î¨ªŠ¸‹&8ªÉXA9æ·cL3ÃÉ3Óe/õrÒ½ucÕ{T*‡‚ááaXvþ²_oyýõŸÂî(ß·o_u(~§R)kþüùCŸùÌg”) Ÿ ¬ßþö·±ë®»îôööΗUWWùå/ù¿.»ì².ß 9räHx÷îÝ5©TÊ}qRJI8v?ÿùÏ÷ÔÔÔ<ÁرcGõ¦M›Nþýïÿ©Ã‡Ÿ2888-•JU$“É0¥Ô²,+‡G"‘ÈP]]]ǼyóšW¬XñÑu×]×|î¹çä-+˜GSSStïÞ½Õ"ážJ¥¬ÚÚÚÔòåË{ËËËÿMÆãqûwÞ©íïï‰þ.Ç!±X,uþùç÷WVVýüîÝ»·òÀU¡PˆòÇH)%étÎ9çœþ“O>Yuc :;;Ão¿ývLt~2ÇÞÐÐX¶lYÀërmãÖ[nýN"‘¨…ByK#ð_ÞS0±njk÷!šÙaëÖ>ùÑ#H½lb‘«×ü Šô›œ'B$ 8t茌ŒÐÏ/ÿÜÿÛ¼yó/a w0ÿ 9Q> ­¯|å+ñÜsÏýµ*å’K.yþµ×^ûw6<Θ¼n:¶d2 ]]]ßH¾ IDATá={öT{k¢‡™g‘0¦Œêlkœ­JÈS® ¤¬¬,Gâ”Æg°Þó7÷áÕ¶y¹ Ê«°)°clMû`õ¸Ð¿'€=#_]¤:o<º±hë¡ ^—ÛÎGÛ…mEÑýÑÆÎ‚”ã@:íqgΜ٠S_¸# ßúÖ·.9vìØ•Íi§öî]wÝÕùŸ1‚ŒSnR§ ²è¼JЋ¢Ñ²2“‡*_ݤÜįllü1úúCÑ>6&T¸gsÒ dÄ8+Ô+`4=£ùèÞ¬0(çº?T^p›´ñìLÚäê¦M›–,F{»]wu-Zçgz”/ÓH$v²r~IIÑÁŠoÈkÃogl<ÿâHw¾,æÆî-)éC´ëÆ*·Ø'ÊãÖMØ¢4oƒR†tæ&UéSN9¥|è!“òíoû´wÞyç2PLä+**ޝ]»öwY›©œ"… ãû[ŒÏI™@—í›Ö‰¢î®¢Þh—Ù˜´UM4t~Eöº2Ós‡ŒqîÌ Œ,ÈêåYŽÑ†üµÑ½¼tï ^¤›þ!SÆ—Nسõ²m‘Ôçê¦M›–¨‰Å:ZÛÚ2weš°B‚\ f „¶²Üqv_æË$ï\–#¿Ù=娏²s}«¨Lzî8±.›a¤_QÇ¿†Þ±º® CÃÃ@ RV6pÞyçoŸÈ®]»*žx≓ÉdµÊð¬³Îúýƒ>¸ðuGX!éW¼«Ú˜èDv¼°•‰wQ™nÛoA¨ÈF'øuÈÆ+ª×ù@ŠDÉ„;s3#o—(dº÷ðr@EQs—«ãß(&k)²b_4kW n••­Ì=餓óæÌ>üá‡û€R\š†Ê5Uå•Ëöu¹íÒ‹E}ø±aíø¨sª¸ùÇ-ðiy'|Y¡O3?ùÇ!³®?’wŒ…ýyå–E •r >4”ºP«m9ãŒ3ø¼GdêA€~ík_»öرcŸRVVVùÆ7¾ñäßûANtDß~Ŷʯ¬IªHµ¬¡.²S‰kQ=êLÐEÜUþdçC¤Ó„`šÌØ)špÏ^@‚ÑtvùÅä u ™//ïF l¸ÓÛv™}ÖŽpul¹·ÏGØùp*uEáUÑv‘8gíEXçw^ãÖ­[Gœt:šN¥!l3©ú4+ø²­GÓ[x¡H Ûä "u¶ ›Ò’ëƒ0íˆÄ·ÄF%ºórãÎGt\D0¯^7V"ð-*3éŒáÛ*ÒiDýBÀ"  @b8Ô¥pÊÉ'ÿ©¾¾>(ܧ:äú믿ä½÷Þ»RuA*!ĹôÒK_Z³fÍ1Àh;‚ˆËg¡Jœû›DÜE¾uÂ\¶íí‹–TT=D9î )gëÛ× u“s(š˜ %"°pg„º·ÚKd³Í<³B˜}¶œ½‹À¨ð&\™(ê-ÕÀõ§²+8,I¾½*B ëÇ=ÿüóTTTvôõõÍwÝ´ðFLºˆ°jÛÛ7i«ó!²QGáÙù‘¿~MóÛƒ´5õÃþ™ú˯ïï×u! %Ï>ûìýÙ¥ñCn sÇw,ÿÿø[t7[š3gÎ7lØð&àÅÈ"¢T~¢í²r•Xgëe‘l•pæŸM¢¶ª¼zѸDÇ¢«3ó<0Ú^$Œ„;s©—úâ vÙZéÞ›’@~ô;Ï-ŒŠw>ÒͶ÷lMÄ·*z.òÁNt‚Ýø÷ÈëëÒK/íœ^?£±·¯o¾ëRH§Ó`1ë¹—:O]—³.òß ê3Åšþt}™¦Æ¶)N*LýèÒsdǧšœ3°lF†‡¡¯¿\J¡®¶¶é¢‹.jÿ~Èä€ý«¿ú«Ï?ÿüóH$b*ãP(4tà 7ü{öæ@øz#È(üwt1|ÅÎO´]U&Ì¢6"1=ñ®³5™ˆ¨ÎƒÝD¨¯/’E(Ü¡N`4ÍÅìl]® ˆE0påÀ•c+zQYÁm‚îÍ!«WµS vS1ŸëçœOŸó‡æ¦¦Ï§tÔqGÂL »‚ ÍÌ£]æ§@~[’oS'†<›üôÆ–‡8Í…µåÛ@ _O˜ ß–°õÜ8%mXÙ•jDç‰ú󎋎ˆî˯¥ïÙ Î9°ÞÞ^ æÏ›÷ÁòåË»s§"¸×\sÍ^}õÕ›‰DʘBO=õÔmßùÎwvg‹ðË A )Öß…ÎN`ÊÊdúEöìm«Ä¾N4«=¿/Š‚û¯ãdmùñªöóÊewÔEü‘îÙÔ/EÅ»+©·ï=Lfª4ÛŽÍC'PxA)û&aë)³ÏþìC8{")Í6ûÌ׳ý°vü>‹J¸óuîßüÍ×wncÛÁÎãg¦R)ˆ8‡GsÝ…Ñ\FPò6º\nv¿ _m* aNª.Ò'ŠýEÉ•ã{áD@_¯Í‹LPôçÉ‚D" GŽ´“†²²HüÒKW¿UWWçæ:O5ȶmÛªî»ï¾5»wïþK×uùûIÐÐаçG?úÑO³·eÇ×AJK©„;¿¯‹¸ËžME8€úî¤Þç k«Š°ë¢qÊ|ÉŽ'BÙèzFÅ2«´Ø„åáE«è@ԗמßA4¾Îo^E—ùòe¿lÙ²OŸ}öößmÛ¶$í¦íd* áH8¿fŶ©ÂÔ¹/_¡€e› ®Üwc“[UoÒŸ.=I˜b”Ý´m Žwv@|(.u¡®®î£»îºË[?ð&? “ 6|úg?ûÙÚãÇ:èPFõ«_}fõêÕ½€¢AŠMÏO?Â]T.‹j‹¶ƒ wQ?‘uÙDmE>Šõ½T0.Œ¶/âîEU‘e•Ðæ÷E/¾Lè{Ûgk*¸ù¶º1ËŽ u² ƒÈ‡±Ï{î¹ç÷ï¾»ë²þþþSRÉ8"‘ükÛDé)~·µ¶T&¸¾õÂ2ö”â:ÿ´¦í˜õ'jkZoÜ•«à<3Û¶!‘HÀ±£Ç í¤! 'V®\ùúœ9sR€Bn²ã}&º?þøÉ?þñÿ²±±q•ã8U&mÛ¾âŠ+þõ‘Gù°´ÃD?BPe+½*UäY'ØEeºH9_&ÚµS uU½È^´-³aÑé9Ä'£ ‰«Å([/¤|½Hà«„½ŸVdË^Èj:&ÑD…-W ~Ù¤@<Ž<®¼òÊãçœsÎkoüç÷¤Ý4ŒŒŒd„;aD Hn[d£µ•øå#˼r>oœK—Q]´É ö¼|ðَܸ ÆÊÕ³eÞøu~D©1Fç”í#7v¶ždχÇÃàà ̘QÿÁƒnx 0·}Ò³sçÎÊM›6Ííµ×>ûÑG­ŠÇãsLÛBœÏ~ö³?þùç·–rŒò ¡˜¢ÎÔWÐÈ».Ú$êÎnÝçSeDã7©ÙóÇ(k#³CŠ {q*/fÙr‹)7ÞlŽ;0mçïƒ}fÇ%zx>dëÁS-_PxlüñˆöÁ Œ'góÈ#¿~ÓM7×ÞÞ~A"9CÃ!¨ª¬5ÌÁêå_@ÉÖƒ ¾0:,¬ÒFÓ^;¹Øgv@9ž‚6>Æ/ò#<§9A.™ ä¥ÈØÐÓÛÍMMvÓPVVÖwóÍ7=»dɩÀl“rï½÷^õÇ?þqM*•ªñÕgéÒ¥/ìØ±ã9."RH)þNüF’uå~Å»êY%êE"[VçÇFg+*×#¿Œ!B¥”òeþ…‘‰T^ ³6¬ –µ#PøR‹ïSÛ¿h¬²r"/²!Ë—/øò—¿üó'Ÿüñ§’©dõðÐD"(++JižÏ°ñ;†ú@§ |ê£ÿÙza=!ò%Å¢~lçDÖ_P!L¥àãƒaxx€,Y²ä•õë×ïL‘™ XõõõºµÙ YVò¼óÎûÙ¯~õ«ç 󷎯5‚L*]à§Nu={Û²}p—•«|èìùq¨&'ªr?ítuHøå YAËG¥yD6"q.‹jëET¯kÏ— \Ô†ŸD°íT"]w|^ûè£î~õÕ×_ûè£?]ç8i2Ð?ái!…ÂñžsH ·)#™maþº(MbUÐ*O>XÞ8c¯˜(è„4HŽÉï±(s÷©¤^ô@þñúøtww¥Nš9s×£ßùÎ Ñh/H¸W]uÕÛ·o?ǘ4ˆD"+V¬xvëÖ­/—zp‚äúyj"Pev:±®²m{û~nÈ$óË߀ 8™çíE>Tðº¿çJ{Q§É‹éç d:ô;ƒ-{¤z¸ÌCæG´-j'ó#²= Æ÷È#=7cÆô÷(¥L&a`` {GÕL.¸÷œyî‘©µ!v…u£>(ç¸>ù¾ó Š2ú›ÅŒÑâÊ,È?K äólÿ¢íÂ2’7.‹ˆÆï¯Þ²,…lhiiÖÖp] ee‘Þo¼áÿ^tÑE}Þ»ø˜˜‡{ÓM7µOŸ>ýÏ`@MMÍ[n¹åÑ­[·nšcÇ>¦âƒýŽT}‡j¿S>uíLü™ÚðÛü³ªpå Øn›÷%²c÷EíUýˆÚˆöem²Q6‚+Ê o9H êH5»ÍÖS® ðh4oËæ­{oß›U²uì˜øüxÑqðÇÂŽ‹÷Ç׉lùãEõ ŽýÚk¯=ÞÙÙùÝ|ðöõõÇ ®®,+¡O»ª/úTG­umdí3Û£ö:þûS·õ•:cP/;fYõ¶7q± µµ öïߎ“‚P(4tñÅ?ùè£ßÛ“5Ç®)Bmm-œ~úéï·´´\L)Þ¨.÷œ}öÙ›6lذéšk®éLA± ÓÅìǤLT®«¼H‰cþÙT°Ëêek½‹ÊTõ<²ãôûÚàw^ ~!1°H*!*Á:Á®úe}ÈD³ªŽ_e†g'š€ð~d“Ñd@d'¢`Rt÷w´ïß¿ÿ?õ“ŸüÁ“ãq ‹Å ΤÍÉS®^zQeÖÖ5i¤¿ yåʱHÆ`*Ðue–eëºÐ|ø46ÇIeÛβeËžÞ´iÓf@¦"îW\±{Û¶m=©Tªž­°m{pöìÙ;Ö®]û↠>ª®®–}ù!"F¦ ŠõwdêGe§›.²m*ØEe*ñí•óÓ‰€èXT>T¿^x×ÔâA²÷F™EÑlÑë³4vAÚûÙç·Ùcá×yçU´Í‹}Ó2SȺuë–>ýôÓÏ´, ¢Ñ(LŸ>ÊÊÊòY{Ù¶Ê;o[7~‘å¸teõ¹}ϼmÈvÛ¶add„ææfH§Ó`[ÖÈÒ¥K7nß¾}cyy9~PMQ¬E‹}÷øñãË F;êëëÿ¸jÕª-?øÁvÕ××;=F9Á VØtBžŸp˜D¦UâY%æùœwQtÝçëÙ}Õ]Xe¾eõ²1¨ž0]¦H°ÂÀ\´äße5¨0çý©úA=¶ƒ<ƒá¾©IÐ~íÚµ+6¿úê½ñx|>@4…Xm-TUW!$wѪ0 œÙÉ/SD‹Y{‘­È_þòüeÛ˜úÓö§ic46Õ˜ø²ìäÉË»ïëëƒ?ïßÇŽË^›`œ{î¹Oÿê•W~^__ëµOqV¯^}åûï¿ñÉ'Ÿüþ…^¸ëþûïo̾®ø„ ÅC/E?~êüFÝùm•PçŸuÂ]$¦UBÚ±/Õƒm+ó+›ê¸Q´ÍáÔ‰a•°ö#ÄuÂ_ÕFe/{‰t‘ø6ä2;*¨g·ù™¾ òío{áã?úÑïìêº Ù­¬¬„X,g”ˆK‘P¦ªzAÔY&®óê} e¥0fÆDÌ«Žß­î\åìH推CCCÐÜÜ ÍÍÍ022„ˆF£í«V®üç_mÞüZ¶)~PMq:::B###dþüùºœ œRwÖÿ]ÌoËúÖ•‰„¼LÜ«"îì¾LÌ›î›s?íuc‘KÁ6Š÷âÀ w¯L'–u¢@q„¾j_46ð±¯Ú6í*[ÓöB»_ÜT÷Àß¼­©éЗÒét€P8 555‹Å ‰ä.^Í[:R”"x–•©ÚË·i™Î·Ÿú ¶Þ/–eåÎu2™„¶¶68|ø0 @:˲ܺºiïÝxãÚ?þøã{AñC©…{Ð6~¢ðºr“g?x]êŒßˆº‰°õ§:¦¼mîÅA&ÜÌÒStÂ[%úMĹÉd4u2{Þ†·õ¶½èö[¯À÷÷÷[×\sÍ•ï¾ûîÍCÃçxQöH8 µµµPUUåååyË%ÊD¼2"n(nƒâb—ew2/ˆ¨žIgaËò^Tƒã`Åz:†øÐt?ííí¹õÙ)¥‡{/^üÒ÷¿ÿýŸ_vÙe}€ ‚L$¢ïV`TիļnÛ¤L%ÖƒFÝä©2|H¨ËÒtDýðõÂcEÑ^H´-3ñò…³Wç'¯‹Èë"ð¼O¿kÉ£pŸ@XážÛ¹0 o~Ÿ·W }Ó‰n,¢gÞŽ¯“µùà "ÒEeº72;>ºaÆEÏ?ÿüU---_H$'yÂÜ[Ý$Cyy9”••A$H$¡P(A6M?ñöUâZäÇïD@æ;ˆp× {I?®ë‚ëºà¤R0<2###Ç¡¿¿††† ™L‚ãŒ.bYÖÈÌ™3·®X±âÕo~ó›ï/[¶lpýnARâGôñß­A£Id]d/›èÒITv~¢ñ¦‚ÝᆰoÑñä•¡p/žp·AœܳH”—rßÏX@`cR&Ú— ó "Þ¯ÈWú:~ü¸õì³Ïžôì³Ï^xèСUñxüôt:]ÉÞ$ÆÝ죔Êïx÷諒PJsBž­¶m{(‰khhxëâ‹/þÍý÷ßÿáé§Ÿžòê‹2A¤øùŒ6‰º‹êtb]TæW¸›lãì—žh_Ýçë³Cá^dwo[ÙÖ¥ºø‰Èƒm¾LÔ—èXLëARnª‹7âã?ŽüÝßýÝÒ>øàÌŽŽŽ3†††Nv'æ8N•ëºQÙ)Ô¶í˲†lÛî///?RWW÷çÅ‹ïùò—¿üÁwÞy|¢ˆ ‚ " MÚ˜ˆye´YQ®K=Q¥£ðé6~/^•Õ‰|ÉìEãµïÅ‚¸‹"צ‘nÐ {¿ Ϻ2ѾŸh»ªÜ„b„Œ d#ñÛ·o¯Þµk×´¶¶¶ÚžžžÊ¡¡¡òT*ʾ¾ˆBˆ …Ò‘H$‹Åâ K–,é^µjUßYg•€ÑŸ^ñCAdòô³Z—£*²­‹ÈûIQQ]PêçbS?i2¢}ÑØ…{±à…;@¡xæËDuAÄ|PŸ&Ï~ÊDûªr•Ø.vîˆ&ljÈ1úàAAÆ £“RöaZ$uFiçëuÂÞo.bîW°›ŒSÔP¸ ]*ûGË@¶Lµ ÿ‡'Ú–Õ³ðåª?f¶N4vÖÖëCV'«÷Ì,™?G2¿ø ‚|Ò`¿‹¡nb#j£î¢rÙ³·­ò&ý˜ü BVïë5¡”ïcÇ$Z$Êu¢ÝD¼‹ìEÂWõ†QM&X?¼ˆW‰oÞŸÌN'öuoNS!®úcFAO:¦ß{ňÐö&B]dg™×EÛeö¦“]™l¿Ô¿† DÂ]Ý• u€B‘Ì—±¹Âl Œ—«‰hÝD‚µWM$x¿¢ êMĹ â‚ R\tÂÛDlú­u×m넽©¨— x]{L”™Œ¶V¸ë"Ø|½ßè»LxûÁ4F„,Ú¯²åûÕû‰ºcÎ9‚ ‚”“èx1Úðõ*Qob'Ùº_áM"æºr•M`# õ K•‘‰tÕ>€\ÈËf¹¢È>ßÞ/¦¹ï²¾d¤úù(E:Š~AäD¥"p¬"Ô$òΖëD½Jä›îö¥8¿yÚ sÜ‹ƒßu¾UQj™­.²­ºèT–’Ã×ÉÆ¨»èÕô‚W•¨Ø UAäD¡TB¯Xé A½itÜÄÎä?Ñòb—P´^¸ëÒeØ}àõ{Ñ‚¬oÖ—(ÍE&ôAb£³ Sá\LÍ_€ ‚ ' ¢ `1àx w¾Ì϶*ZoM×EóA)%(àÇŠŸˆ{Q/» T–û.Ò*ÑíùQõËd¢a2)懊G©ÊBAÉLQ„£ÿ õ"?û*q.+7í¦ãQÔó‚½8¨V•Ñ•ÉêeÛì¾Làˆß(*ï•«úUWUæÙØ1ZŽ ‚ G)£ô&¢ØD¸óûªh¼iTŸ·Õõå÷<ÛcŽ{qšã®ɲ;Hû‰–³ÑvUSá,šHðu²z•O¿iC‚ ‚ ßÁc‰ÄÀñ¢2•°7ë%Ñ,(ÞÇŽJ¸ËÄ®L¼{¨D´jU]Û^µÂ¨Óœ|¾?AD8FÛAdâ*MÛ5êÎïÈû­3í[5dÑEÜMS`tmDbŸ·—µ0› ˆ„·lb¡zÓªD¶_Ñ^ŠœwAA‚SÌt Qy?i.:•½ ²è|P¿¨wJŒé:î²ra/k#Šxó‘u•æûQ tѱ™ˆlÕ˜Líe`TAA‚Q,ÁXÌ”š ©4A¢á²ö¦i6AA‘>xÂÝ4‚®«W ~U„\U¯»˜Õ$ Çd•LÓjü ;Ï‚ ‚ä# Ëw1lÇš6£Û7Mg1íc‰Þ#ã€éÅ©¼8g1Y_ÕÏR‹ª4“œz•¨– c¿åÅ~CcJ ‚ ‚ø§”Â]էߺ ÑwY™It^%ÒýDâMƆŒ#A.N•ÙùI£‘áGÀÓÖä—ÞW±Áˆ;‚ ‚ø§ÔQb¿AmL…¾i4ݯ`Wµñ;Ù˜"B(¥è£Îª¼pS;Yꊮί­¬?mLý Y*‚ ‚ rJuŸè¼wU[¿bÜÔÖO_¦¾ðFLc„¸ûÉeW!KYÑ­þ¢J‘ÙÊ-'ÉÖÉi/õ›m"~òCA©Šß(p±ú«}HûXÊMü›ˆv?¾‘ÀFÜóÊd¶ Y½[S›REØÇ3ÒQuA) ã-"ý f“v&:ˆMÐ4?6ÊrŒº'¨p}?vA„¼ŸúbµAAdòSÌèúXm‚Fáýú2ñ;Ö‹lQ´‘b wQ¹ßȺ;¿¶*‚Špï‚ òɤâÝ´ßÕ\üŠö “b¤þŠ÷± î¹rU[?é2~ËLëƒÖ•ü‚ RZ&RM ÚÖpöe×ÉwN< ÷àø½sªWL½l%Õ‹"ºSvQæXVZ‘!È ÅÛø†EA©E1#êAÚ•:Õf,ý"ã„q'à?Ú]¬›•êBÒb mŒ#‚ ȉM±ìX¢ò:¿¼”…0âÝSýDÞMËù:Q¹(/ë_„iDÝTã AAJ_ê%"ƒ¶AÆ]Ä=ÏÎÄW‘Ú™´K|¼"è©GAÉÇd[ó=ˆbçÉ›¶S{Œ¶ þLêè¶îMÅŠœ«Pý c¼n¤äoNAA&jyI{?~0?ÁˆRe‚¤ÇÈìx[!/³µ3}ƒŒ‡`Çè:‚ ‚L]ÆCtNô®A}¢`Ÿ$YUÆ« o+²½Ð~}ÊPMŠMUjA™<Œ÷÷x1û+E„¾”~‘€°9îJS_AÇPBûR1YÆ ‚ Hñ™L‚´Ôè›Ö ¶IDAT¼ië»Ìo;&Â]Ú®„öãío²÷‹ ‚ ÈÄ1Q‚³TýŽ[ÊŠõâT¸ ýL@ÛR3™Ç† ‚ ÈÄ1™éXÇV”cCÑ^|r´â=Ïß$ó… ‚ òI¤Xâ¸h"{éŠã" ù@}O“i,‚ ‚Lm&“p-éXP¤/ë8ûb2•ÆŠ ‚ È'—)%tQ˜O.¦Œ bAA¢ƒBúÄæÿpˆ®k@IEND®B`‚pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif0000644000076500000240000000006112520062551027763 0ustar michaelstaff00000000000000GIF89a‘ÿÿÿÿÿÿ!ù,T;pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/templates/0000755000076500000240000000000012642137501025450 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt0000644000076500000240000000643612520062551030204 0ustar michaelstaff00000000000000 The Pyramid Web Framework
pyramid

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/tests.py0000644000076500000240000000060212520062551025161 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'scaffolds') pyramid-1.6/docs/quick_tutorial/scaffolds/scaffolds/views.py0000644000076500000240000000024512520062551025157 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): return {'project': 'scaffolds'} pyramid-1.6/docs/quick_tutorial/scaffolds/setup.py0000644000076500000240000000204512520062551023216 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'waitress', ] setup(name='scaffolds', version='0.0', description='scaffolds', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="scaffolds", entry_points="""\ [paste.app_factory] main = scaffolds:main """, ) pyramid-1.6/docs/quick_tutorial/scaffolds.rst0000644000076500000240000000522712520062551022243 0ustar michaelstaff00000000000000.. _qtut_scaffolds: ============================================= Prelude: Quick Project Startup with Scaffolds ============================================= To ease the process of getting started, Pyramid provides *scaffolds* that generate sample projects from templates in Pyramid and Pyramid add-ons. Background ========== We're going to cover a lot in this tutorial, focusing on one topic at a time and writing everything from scratch. As a warmup, though, it sure would be nice to see some pixels on a screen. Like other web development frameworks, Pyramid provides a number of "scaffolds" that generate working Python, template, and CSS code for sample applications. In this step we'll use a built-in scaffold to let us preview a Pyramid application, before starting from scratch on Step 1. Objectives ========== - Use Pyramid's ``pcreate`` command to list scaffolds and make a new project - Start up a Pyramid application and visit it in a web browser Steps ===== #. Pyramid's ``pcreate`` command can list the available scaffolds: .. code-block:: bash $ $VENV/bin/pcreate --list Available scaffolds: alchemy: Pyramid SQLAlchemy project using url dispatch starter: Pyramid starter project zodb: Pyramid ZODB project using traversal #. Tell ``pcreate`` to use the ``starter`` scaffold to make our project: .. code-block:: bash $ $VENV/bin/pcreate --scaffold starter scaffolds #. Use normal Python development to setup our project for development: .. code-block:: bash $ cd scaffolds $ $VENV/bin/python setup.py develop #. Startup the application by pointing Pyramid's ``pserve`` command at the project's (generated) configuration file: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload On startup, ``pserve`` logs some output: .. code-block:: bash Starting subprocess with file monitor Starting server in PID 72213. Starting HTTP server on http://0.0.0.0:6543 #. Open http://localhost:6543/ in your browser. Analysis ======== Rather than starting from scratch, ``pcreate`` can make getting a Python project containing a Pyramid application a quick matter. Pyramid ships with a few scaffolds. But installing a Pyramid add-on can give you new scaffolds from that add-on. ``pserve`` is Pyramid's application runner, separating operational details from your code. When you install Pyramid, a small command program called ``pserve`` is written to your ``bin`` directory. This program is an executable Python module. It is passed a configuration file (in this case, ``development.ini``.) pyramid-1.6/docs/quick_tutorial/sessions/0000755000076500000240000000000012642137501021410 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/sessions/development.ini0000644000076500000240000000025612520062551024433 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/sessions/setup.py0000644000076500000240000000034612520062551023122 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/sessions/tutorial/0000755000076500000240000000000012642137501023253 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/sessions/tutorial/__init__.py0000644000076500000240000000075412520062551025367 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.session import SignedCookieSessionFactory def main(global_config, **settings): my_session_factory = SignedCookieSessionFactory( 'itsaseekreet') config = Configurator(settings=settings, session_factory=my_session_factory) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app() pyramid-1.6/docs/quick_tutorial/sessions/tutorial/home.pt0000644000076500000240000000024412575217552024562 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

Count: ${view.counter}

pyramid-1.6/docs/quick_tutorial/sessions/tutorial/tests.py0000644000076500000240000000216112520062551024764 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/sessions/tutorial/views.py0000644000076500000240000000114412520062551024757 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @property def counter(self): session = self.request.session if 'counter' in session: session['counter'] += 1 else: session['counter'] = 1 return session['counter'] @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/sessions.rst0000644000076500000240000000575612575217552022171 0ustar michaelstaff00000000000000.. _qtut_sessions: ================================= 17: Transient Data Using Sessions ================================= Store and retrieve non-permanent data in Pyramid sessions. Background ========== 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. 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 ========== - Make a session factory using a built-in, simple Pyramid sessioning system - Change our code to use a session Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes sessions; cd sessions $ $VENV/bin/python setup.py develop #. Our ``sessions/tutorial/__init__.py`` needs a choice of session factory to get registered with the :term:`configurator`: .. literalinclude:: sessions/tutorial/__init__.py :linenos: #. Our views in ``sessions/tutorial/views.py`` can now use ``request.session``: .. literalinclude:: sessions/tutorial/views.py :linenos: #. The template at ``sessions/tutorial/home.pt`` can display the value: .. literalinclude:: sessions/tutorial/home.pt :language: html :linenos: #. Make sure the tests still pass: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ and http://localhost:6543/howdy in your browser. As you reload and switch between those URLs, note that the counter increases and is *not* specific to the URL. #. Restart the application and revisit the page. Note that counter still increases from where it left off. Analysis ======== Pyramid's :term:`request` object now has a ``session`` attribute that we can use in our view code. It acts like a dictionary. Since all the views are using the same counter, we made the counter a Python property at the view class level. With this, each reload will increase the counter displayed in our template. In web development, "flash messages" are notes for the user that need to appear on a screen after a future web request. For example, when you add an item using a form ``POST``, the site usually issues a second HTTP Redirect web request to view the new item. You might want a message to appear after that second web request saying "Your item was added." You can't just return it in the web response for the POST, as it will be tossed out during the second web request. Flash messages are a technique where messages can be stored between requests, using sessions, then removed when they finally get displayed. .. seealso:: :ref:`sessions_chapter`, :ref:`flash_messages`, and :ref:`session_module`. pyramid-1.6/docs/quick_tutorial/static_assets/0000755000076500000240000000000012642137501022413 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/static_assets/development.ini0000644000076500000240000000025612520062551025436 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/static_assets/setup.py0000644000076500000240000000034612520062551024125 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/0000755000076500000240000000000012642137501024256 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/__init__.py0000644000076500000240000000055312520062551026367 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.add_static_view(name='static', path='tutorial:static') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/home.pt0000644000076500000240000000034612575217552025570 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/static/0000755000076500000240000000000012642137501025545 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/static/app.css0000644000076500000240000000006612520062551027036 0ustar michaelstaff00000000000000body { margin: 2em; font-family: sans-serif; }pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/tests.py0000644000076500000240000000216112520062551025767 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/static_assets/tutorial/views.py0000644000076500000240000000057512520062551025771 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/static_assets.rst0000644000076500000240000000502512575217552023161 0ustar michaelstaff00000000000000.. _qtut_static_assets: ========================================== 13: CSS/JS/Images Files With Static Assets ========================================== Of course the Web is more than just markup. You need static assets: CSS, JS, and images. Let's point our web app at a directory where Pyramid will serve some static assets. Objectives ========== - Publish a directory of static assets at a URL - Use Pyramid to help generate URLs to files in that directory Steps ===== #. First we copy the results of the ``view_classes`` step: .. code-block:: bash $ cd ..; cp -r view_classes static_assets; cd static_assets $ $VENV/bin/python setup.py develop #. We add a call ``config.add_static_view`` in ``static_assets/tutorial/__init__.py``: .. literalinclude:: static_assets/tutorial/__init__.py :linenos: #. We can add a CSS link in the ```` of our template at ``static_assets/tutorial/home.pt``: .. literalinclude:: static_assets/tutorial/home.pt :language: html #. Add a CSS file at ``static_assets/tutorial/static/app.css``: .. literalinclude:: static_assets/tutorial/static/app.css :language: css #. Make sure we haven't broken any existing code by running the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ in your browser and note the new font. Analysis ======== We changed our WSGI application to map requests under http://localhost:6543/static/ to files and directories inside a ``static`` directory inside our ``tutorial`` package. This directory contained ``app.css``. We linked to the CSS in our template. We could have hard-coded this link to ``/static/app.css``. But what if the site is later moved under ``/somesite/static/``? Or perhaps the web developer changes the arrangement on disk? Pyramid gives a helper that provides flexibility on URL generation: .. code-block:: html ${request.static_url('tutorial:static/app.css')} This matches the ``path='tutorial:static'`` in our ``config.add_static_view`` registration. By using ``request.static_url`` to generate the full URL to the static assets, you both ensure you stay in sync with the configuration and gain refactoring flexibility later. Extra Credit ============ #. There is also a ``request.static_path`` API. How does this differ from ``request.static_url``? .. seealso:: :ref:`assets_chapter`, :ref:`preventing_http_caching`, and :ref:`influencing_http_caching` pyramid-1.6/docs/quick_tutorial/templating/0000755000076500000240000000000012642137501021706 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/templating/development.ini0000644000076500000240000000025612520062551024731 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/templating/setup.py0000644000076500000240000000035012520062551023413 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/quick_tutorial/templating/tutorial/0000755000076500000240000000000012642137501023551 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/templating/tutorial/__init__.py0000644000076500000240000000045112520062551025657 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/templating/tutorial/home.pt0000644000076500000240000000020612575217552025056 0ustar michaelstaff00000000000000 Quick Tutorial: ${name}

Hi ${name}

pyramid-1.6/docs/quick_tutorial/templating/tutorial/tests.py0000644000076500000240000000214012520062551025257 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import home request = testing.DummyRequest() response = home(request) # Our view now returns data self.assertEqual('Home View', response['name']) def test_hello(self): from .views import hello request = testing.DummyRequest() response = hello(request) # Our view now returns data self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/templating/tutorial/views.py0000644000076500000240000000046712520062551025264 0ustar michaelstaff00000000000000from pyramid.view import view_config # First view, available at http://localhost:6543/ @view_config(route_name='home', renderer='home.pt') def home(request): return {'name': 'Home View'} # /howdy @view_config(route_name='hello', renderer='home.pt') def hello(request): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/templating.rst0000644000076500000240000000652312575217552022460 0ustar michaelstaff00000000000000.. _qtut_templating: =================================== 08: HTML Generation With Templating =================================== Most web frameworks don't embed HTML in programming code. Instead, they pass data into a templating system. In this step we look at the basics of using HTML templates in Pyramid. Background ========== Ouch. We have been making our own ``Response`` and filling the response body with HTML. You usually won't embed an HTML string directly in Python, but instead, will use a templating language. Pyramid doesn't mandate a particular database system, form library, etc. It encourages replaceability. This applies equally to templating, which is fortunate: developers have strong views about template languages. As of Pyramid 1.5a2, Pyramid doesn't even bundle a template language! It does, however, have strong ties to Jinja2, Mako, and Chameleon. In this step we see how to add ``pyramid_chameleon`` to your project, then change your views to use templating. Objectives ========== - Enable the ``pyramid_chameleon`` Pyramid add-on - Generate HTML from template files - Connect the templates as "renderers" for view code - Change the view code to simply return data Steps ===== #. Let's begin by using the previous package as a starting point for a new project: .. code-block:: bash $ cd ..; cp -r views templating; cd templating #. This step depends on ``pyramid_chameleon``, so add it as a dependency in ``templating/setup.py``: .. literalinclude:: templating/setup.py :linenos: #. Now we can activate the development-mode distribution: .. code-block:: bash $ $VENV/bin/python setup.py develop #. We need to connect ``pyramid_chameleon`` as a renderer by making a call in the setup of ``templating/tutorial/__init__.py``: .. literalinclude:: templating/tutorial/__init__.py :linenos: #. Our ``templating/tutorial/views.py`` no longer has HTML in it: .. literalinclude:: templating/tutorial/views.py :linenos: #. Instead we have ``templating/tutorial/home.pt`` as a template: .. literalinclude:: templating/tutorial/home.pt :language: html #. For convenience, change ``templating/development.ini`` to reload templates automatically with ``pyramid.reload_templates``: .. literalinclude:: templating/development.ini :language: ini #. Our unit tests in ``templating/tutorial/tests.py`` can focus on data: .. literalinclude:: templating/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 4 tests in 0.141s OK #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ and http://localhost:6543/howdy in your browser. Analysis ======== Ahh, that looks better. We have a view that is focused on Python code. Our ``@view_config`` decorator specifies a :term:`renderer` that points to our template file. Our view then simply returns data which is then supplied to our template. Note that we used the same template for both views. Note the effect on testing. We can focus on having a data-oriented contract with our view code. .. seealso:: :ref:`templates_chapter`, :ref:`debugging_templates`, and :ref:`available_template_system_bindings`. pyramid-1.6/docs/quick_tutorial/tutorial_approach.rst0000644000076500000240000000253512520062551024016 0ustar michaelstaff00000000000000================= Tutorial Approach ================= This tutorial uses conventions to keep the introduction focused and concise. Details, references, and deeper discussions are mentioned in "See also" notes. .. seealso:: This is an example "See also" note. This "Getting Started" tutorial is broken into independent steps, starting with the smallest possible "single file WSGI app" example. Each of these steps introduce a topic and a very small set of concepts via working code. The steps each correspond to a directory in this repo, where each step/topic/directory is a Python package. To successfully run each step:: $ cd request_response $ $VENV/bin/python setup.py develop ...and repeat for each step you would like to work on. In most cases we will start with the results of an earlier step. Directory Tree ============== As we develop our tutorial our directory tree will resemble the structure below:: quicktutorial/ request_response/ development.ini setup.py tutorial/ __init__.py home.pt tests.py views.py Each of the first-level directories (e.g. ``request_response``) is a *Python project* (except, as noted, the ``hello_world`` step.) The ``tutorial`` directory is a *Python package*. At the end of each step, we copy a previous directory into a new directory to use as a starting point. pyramid-1.6/docs/quick_tutorial/unit_testing/0000755000076500000240000000000012642137501022256 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/unit_testing/development.ini0000644000076500000240000000021612520062551025275 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/unit_testing/setup.py0000644000076500000240000000031612520062551023765 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/unit_testing/tutorial/0000755000076500000240000000000012642137501024121 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/unit_testing/tutorial/__init__.py0000644000076500000240000000056112520062551026231 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('

Hello World!

') def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/unit_testing/tutorial/tests.py0000644000076500000240000000063212520062551025633 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_hello_world(self): from tutorial import hello_world request = testing.DummyRequest() response = hello_world(request) self.assertEqual(response.status_code, 200) pyramid-1.6/docs/quick_tutorial/unit_testing.rst0000644000076500000240000001016712575217552023027 0ustar michaelstaff00000000000000.. _qtut_unit_testing: =========================== 05: Unit Tests and ``nose`` =========================== Provide unit testing for our project's Python code. Background ========== As the mantra says, "Untested code is broken code." The Python community has had a long culture of writing test scripts which ensure that your code works correctly as you write it and maintain it in the future. Pyramid has always had a deep commitment to testing, with 100% test coverage from the earliest pre-releases. Python includes a :ref:`unit testing framework ` in its standard library. Over the years a number of Python projects, such as `nose `_, have extended this framework with alternative test runners that provide more convenience and functionality. The Pyramid developers use ``nose``, which we'll thus use in this tutorial. Don't worry, this tutorial won't be pedantic about "test-driven development" (TDD). We'll do just enough to ensure that, in each step, we haven't majorly broken the code. As you're writing your code you might find this more convenient than changing to your browser constantly and clicking reload. We'll also leave discussion of `coverage `_ for another section. Objectives ========== - Write unit tests that ensure the quality of our code - Install a Python package (``nose``) which helps in our testing Steps ===== #. First we copy the results of the previous step, as well as install the ``nose`` package: .. code-block:: bash $ cd ..; cp -r debugtoolbar unit_testing; cd unit_testing $ $VENV/bin/python setup.py develop $ $VENV/bin/easy_install nose #. Now we write a simple unit test in ``unit_testing/tutorial/tests.py``: .. literalinclude:: unit_testing/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 1 test in 0.141s OK Analysis ======== Our ``tests.py`` imports the Python standard unit testing framework. To make writing Pyramid-oriented tests more convenient, Pyramid supplies some ``pyramid.testing`` helpers which we use in the test setup and teardown. Our one test imports the view, makes a dummy request, and sees if the view returns what we expected. The ``tests.TutorialViewTests.test_hello_world`` test is a small example of a unit test. First, we import the view inside each test. Why not import at the top, like in normal Python code? Because imports can cause effects that break a test. We'd like our tests to be in *units*, hence the name *unit* testing. Each test should isolate itself to the correct degree. Our test then makes a fake incoming web request, then calls our Pyramid view. We test the HTTP status code on the response to make sure it matches our expectations. Note that our use of ``pyramid.testing.setUp()`` and ``pyramid.testing.tearDown()`` aren't actually necessary here; they are only necessary when your test needs to make use of the ``config`` object (it's a Configurator) to add stuff to the configuration state before calling the view. Extra Credit ============ #. Change the test to assert that the response status code should be ``404`` (meaning, not found.) Run ``nosetests`` again. Read the error report and see if you can decipher what it is telling you. #. As a more realistic example, put the ``tests.py`` back as you found it and put an error in your view, such as a reference to a non-existing variable. Run the tests and see how this is more convenient than reloading your browser and going back to your code. #. Finally, for the most realistic test, read about Pyramid ``Response`` objects and see how to change the response code. Run the tests and see how testing confirms the "contract" that your code claims to support. #. How could we add a unit test assertion to test the HTML value of the response body? #. Why do we import the ``hello_world`` view function *inside* the ``test_hello_world`` method instead of at the top of the module? .. seealso:: See also :ref:`testing_chapter` pyramid-1.6/docs/quick_tutorial/view_classes/0000755000076500000240000000000012642137501022231 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/view_classes/development.ini0000644000076500000240000000025612520062551025254 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/view_classes/setup.py0000644000076500000240000000034612520062551023743 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', 'pyramid_chameleon' ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/view_classes/tutorial/0000755000076500000240000000000012642137501024074 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/view_classes/tutorial/__init__.py0000644000076500000240000000045112520062551026202 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/view_classes/tutorial/home.pt0000644000076500000240000000020112520062551025357 0ustar michaelstaff00000000000000 Quick Tour: ${name}

Hi ${name}

pyramid-1.6/docs/quick_tutorial/view_classes/tutorial/tests.py0000644000076500000240000000216112520062551025605 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.home() self.assertEqual('Home View', response['name']) def test_hello(self): from .views import TutorialViews request = testing.DummyRequest() inst = TutorialViews(request) response = inst.hello() self.assertEqual('Hello View', response['name']) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'

Hi Home View', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'

Hi Hello View', res.body) pyramid-1.6/docs/quick_tutorial/view_classes/tutorial/views.py0000644000076500000240000000057412520062551025606 0ustar michaelstaff00000000000000from pyramid.view import ( view_config, view_defaults ) @view_defaults(renderer='home.pt') class TutorialViews: def __init__(self, request): self.request = request @view_config(route_name='home') def home(self): return {'name': 'Home View'} @view_config(route_name='hello') def hello(self): return {'name': 'Hello View'} pyramid-1.6/docs/quick_tutorial/view_classes.rst0000644000076500000240000000505012575217552022775 0ustar michaelstaff00000000000000.. _qtut_view_classes: ====================================== 09: Organizing Views With View Classes ====================================== Change our view functions to be methods on a view class, then move some declarations to the class level. Background ========== So far our views have been simple, free-standing functions. Many times your views are related: different ways to look at or work on the same data or a REST API that handles multiple operations. Grouping these together as a :ref:`view class ` makes sense: - Group views - Centralize some repetitive defaults - Share some state and helpers In this step we just do the absolute minimum to convert the existing views to a view class. In a later tutorial step we'll examine view classes in depth. Objectives ========== - Group related views into a view class - Centralize configuration with class-level ``@view_defaults`` Steps ===== #. First we copy the results of the previous step: .. code-block:: bash $ cd ..; cp -r templating view_classes; cd view_classes $ $VENV/bin/python setup.py develop #. Our ``view_classes/tutorial/views.py`` now has a view class with our two views: .. literalinclude:: view_classes/tutorial/views.py :linenos: #. Our unit tests in ``view_classes/tutorial/tests.py`` don't run, so let's modify them to import the view class and make an instance before getting a response: .. literalinclude:: view_classes/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 4 tests in 0.141s OK #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ and http://localhost:6543/howdy in your browser. Analysis ======== To ease the transition to view classes, we didn't introduce any new functionality. We simply changed the view functions to methods on a view class, then updated the tests. In our ``TutorialViews`` view class you can see that our two view classes are logically grouped together as methods on a common class. Since the two views shared the same template, we could move that to a ``@view_defaults`` decorator at the class level. The tests needed to change. Obviously we needed to import the view class. But you can also see the pattern in the tests of instantiating the view class with the dummy request first, then calling the view method being tested. .. seealso:: :ref:`class_as_view` pyramid-1.6/docs/quick_tutorial/views/0000755000076500000240000000000012642137501020677 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/views/development.ini0000644000076500000240000000021612520062551023716 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 pyramid-1.6/docs/quick_tutorial/views/setup.py0000644000076500000240000000031612520062551022406 0ustar michaelstaff00000000000000from setuptools import setup requires = [ 'pyramid', ] setup(name='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main """, )pyramid-1.6/docs/quick_tutorial/views/tutorial/0000755000076500000240000000000012642137501022542 5ustar michaelstaff00000000000000pyramid-1.6/docs/quick_tutorial/views/tutorial/__init__.py0000644000076500000240000000040112520062551024643 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): config = Configurator(settings=settings) config.add_route('home', '/') config.add_route('hello', '/howdy') config.scan('.views') return config.make_wsgi_app()pyramid-1.6/docs/quick_tutorial/views/tutorial/tests.py0000644000076500000240000000214612520062551024256 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TutorialViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_home(self): from .views import home request = testing.DummyRequest() response = home(request) self.assertEqual(response.status_code, 200) self.assertIn(b'Visit', response.body) def test_hello(self): from .views import hello request = testing.DummyRequest() response = hello(request) self.assertEqual(response.status_code, 200) self.assertIn(b'Go back', response.body) class TutorialFunctionalTests(unittest.TestCase): def setUp(self): from tutorial import main app = main({}) from webtest import TestApp self.testapp = TestApp(app) def test_home(self): res = self.testapp.get('/', status=200) self.assertIn(b'Visit', res.body) def test_hello(self): res = self.testapp.get('/howdy', status=200) self.assertIn(b'Go back', res.body) pyramid-1.6/docs/quick_tutorial/views/tutorial/views.py0000644000076500000240000000057012520062551024250 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config # First view, available at http://localhost:6543/ @view_config(route_name='home') def home(request): return Response('Visit hello') # /howdy @view_config(route_name='hello') def hello(request): return Response('Go back home') pyramid-1.6/docs/quick_tutorial/views.rst0000644000076500000240000000642712575217552021454 0ustar michaelstaff00000000000000.. _qtut_views: ================================= 07: Basic Web Handling With Views ================================= Organize a views module with decorators and multiple views. Background ========== For the examples so far, the ``hello_world`` function is a "view". In Pyramid, views are the primary way to accept web requests and return responses. So far our examples place everything in one file: - The view function - Its registration with the configurator - The route to map it to a URL - The WSGI application launcher Let's move the views out to their own ``views.py`` module and change our startup code to scan that module, looking for decorators that setup the views. Let's also add a second view and update our tests. Objectives ========== - Views in a module that is scanned by the configurator - Decorators that do declarative configuration Steps ===== #. Let's begin by using the previous package as a starting point for a new distribution, then making it active: .. code-block:: bash $ cd ..; cp -r functional_testing views; cd views $ $VENV/bin/python setup.py develop #. Our ``views/tutorial/__init__.py`` gets a lot shorter: .. literalinclude:: views/tutorial/__init__.py :linenos: #. Let's add a module ``views/tutorial/views.py`` that is focused on handling requests and responses: .. literalinclude:: views/tutorial/views.py :linenos: #. Update the tests to cover the two new views: .. literalinclude:: views/tutorial/tests.py :linenos: #. Now run the tests: .. code-block:: bash $ $VENV/bin/nosetests tutorial . ---------------------------------------------------------------------- Ran 4 tests in 0.141s OK #. Run your Pyramid application with: .. code-block:: bash $ $VENV/bin/pserve development.ini --reload #. Open http://localhost:6543/ and http://localhost:6543/howdy in your browser. Analysis ======== We added some more URLs, but we also removed the view code from the application startup code in ``tutorial/__init__.py``. Our views, and their view registrations (via decorators) are now in a module ``views.py`` which is scanned via ``config.scan('.views')``. We have 2 views, each leading to the other. If you start at http://localhost:6543/, you get a response with a link to the next view. The ``hello`` view (available at the URL ``/howdy``) has a link back to the first view. This step also shows that the name appearing in the URL, the name of the "route" that maps a URL to a view, and the name of the view, can all be different. More on routes later. Earlier we saw ``config.add_view`` as one way to configure a view. This section introduces ``@view_config``. Pyramid's configuration supports :term:`imperative configuration`, such as the ``config.add_view`` in the previous example. You can also use :term:`declarative configuration`, in which a Python :term:`python:decorator` is placed on the line above the view. Both approaches result in the same final configuration, thus usually, it is simply a matter of taste. Extra Credit ============ #. What does the dot in ``.views`` signify? #. Why might ``assertIn`` be a better choice in testing the text in responses than ``assertEqual``? .. seealso:: :ref:`views_chapter`, :ref:`view_config_chapter`, and :ref:`debugging_view_configuration` pyramid-1.6/docs/remake0000755000076500000240000000006412342763132015677 0ustar michaelstaff00000000000000make clean html SPHINXBUILD=../env/bin/sphinx-build pyramid-1.6/docs/tutorials/0000755000076500000240000000000012642137501016531 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/.gitignore0000644000076500000240000000000612234375161020520 0ustar michaelstaff00000000000000env*/ pyramid-1.6/docs/tutorials/modwsgi/0000755000076500000240000000000012642137501020202 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/modwsgi/index.rst0000644000076500000240000001215112520062551022040 0ustar michaelstaff00000000000000.. _modwsgi_tutorial: Running a :app:`Pyramid` Application under ``mod_wsgi`` ========================================================== :term:`mod_wsgi` is an Apache module developed by Graham Dumpleton. It allows :term:`WSGI` programs to be served using the Apache web server. This guide will outline broad steps that can be used to get a :app:`Pyramid` application running under Apache via ``mod_wsgi``. This particular tutorial was developed under Apple's Mac OS X platform (Snow Leopard, on a 32-bit Mac), but the instructions should be largely the same for all systems, delta specific path information for commands and files. .. note:: Unfortunately these instructions almost certainly won't work for deploying a :app:`Pyramid` application on a Windows system using ``mod_wsgi``. If you have experience with :app:`Pyramid` and ``mod_wsgi`` on Windows systems, please help us document this experience by submitting documentation to the `Pylons-devel maillist `_. #. The tutorial assumes you have Apache already installed on your system. If you do not, install Apache 2.X for your platform in whatever manner makes sense. #. Once you have Apache installed, install ``mod_wsgi``. Use the (excellent) `installation instructions `_ for your platform into your system's Apache installation. #. Install :term:`virtualenv` into the Python which mod_wsgi will run using the ``easy_install`` program. .. code-block:: text $ sudo /usr/bin/easy_install-2.6 virtualenv This command may need to be performed as the root user. #. Create a :term:`virtualenv` which we'll use to install our application. .. code-block:: text $ cd ~ $ mkdir modwsgi $ cd modwsgi $ /usr/local/bin/virtualenv env #. Install :app:`Pyramid` into the newly created virtualenv: .. code-block:: text $ cd ~/modwsgi/env $ $VENV/bin/easy_install pyramid #. Create and install your :app:`Pyramid` application. For the purposes of this tutorial, we'll just be using the ``pyramid_starter`` application as a baseline application. Substitute your existing :app:`Pyramid` application as necessary if you already have one. .. code-block:: text $ cd ~/modwsgi/env $ $VENV/bin/pcreate -s starter myapp $ cd myapp $ $VENV/bin/python setup.py install #. Within the virtualenv directory (``~/modwsgi/env``), create a script named ``pyramid.wsgi``. Give it these contents: .. code-block:: python from pyramid.paster import get_app, setup_logging ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini' setup_logging(ini_path) application = get_app(ini_path, 'main') The first argument to ``get_app`` is the project configuration file name. It's best to use the ``production.ini`` file provided by your scaffold, as it contains settings appropriate for production. The second is the name of the section within the .ini file that should be loaded by ``mod_wsgi``. The assignment to the name ``application`` is important: mod_wsgi requires finding such an assignment when it opens the file. The call to ``setup_logging`` initializes the standard library's `logging` module to allow logging within your application. See :ref:`logging_config`. There is no need to make the ``pyramid.wsgi`` script executable. However, you'll need to make sure that *two* users have access to change into the ``~/modwsgi/env`` directory: your current user (mine is ``chrism`` and the user that Apache will run as often named ``apache`` or ``httpd``). Make sure both of these users can "cd" into that directory. #. Edit your Apache configuration and add some stuff. I happened to create a file named ``/etc/apache2/other/modwsgi.conf`` on my own system while installing Apache, so this stuff went in there. .. code-block:: apache # Use only 1 Python sub-interpreter. Multiple sub-interpreters # play badly with C extensions. See # http://stackoverflow.com/a/10558360/209039 WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \ python-path=/Users/chrism/modwsgi/env/lib/python2.6/site-packages WSGIScriptAlias /myapp /Users/chrism/modwsgi/env/pyramid.wsgi WSGIProcessGroup pyramid Order allow,deny Allow from all #. Restart Apache .. code-block:: text $ sudo /usr/sbin/apachectl restart #. Visit ``http://localhost/myapp`` in a browser. You should see the sample application rendered in your browser. :term:`mod_wsgi` has many knobs and a great variety of deployment modes. This is just one representation of how you might use it to serve up a :app:`Pyramid` application. See the `mod_wsgi configuration documentation `_ for more in-depth configuration information. pyramid-1.6/docs/tutorials/wiki/0000755000076500000240000000000012642137501017474 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/authorization.rst0000644000076500000240000003162312575217552023145 0ustar michaelstaff00000000000000.. _wiki_adding_authorization: ==================== Adding authorization ==================== :app:`Pyramid` provides facilities for :term:`authentication` and ::term:`authorization`. We'll make use of both features to provide security :to our application. Our application currently allows anyone with access to :the server to view, edit, and add pages to our wiki. We'll change that to :allow only people who are members of a *group* named ``group:editors`` to add :and edit wiki pages but we'll continue allowing anyone with access to the :server to view pages. We will also add a login page and a logout link on all the pages. The login page will be shown when a user is denied access to any of the views that require permission, instead of a default "403 Forbidden" page. We will implement the access control with the following steps: * Add users and groups (``security.py``, a new module). * Add an :term:`ACL` (``models.py``). * Add an :term:`authentication policy` and an :term:`authorization policy` (``__init__.py``). * Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). Then we will add the login and logout feature: * Add ``login`` and ``logout`` views (``views.py``). * Add a login template (``login.pt``). * Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). * Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``). Access control -------------- Add users and groups ~~~~~~~~~~~~~~~~~~~~ Create a new ``tutorial/tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py :linenos: :language: python The ``groupfinder`` function accepts a userid and a request and returns one of these values: - If the userid exists in the system, it will return a sequence of group identifiers (or an empty sequence if the user isn't a member of any groups). - If the userid *does not* exist in the system, it will return ``None``. For example, ``groupfinder('editor', request )`` returns ``['group:editor']``, ``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin', request)`` returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. In a production system, user and group data will most often come from a database, but here we use "dummy" data to represent user and groups sources. Add an ACL ~~~~~~~~~~ Open ``tutorial/tutorial/models.py`` and add the following import statement at the head: .. literalinclude:: src/authorization/tutorial/models.py :lines: 4-7 :linenos: :language: python Add the following lines to the ``Wiki`` class: .. literalinclude:: src/authorization/tutorial/models.py :lines: 9-13 :linenos: :lineno-start: 9 :emphasize-lines: 4-5 :language: python We import :data:`~pyramid.security.Allow`, an action that means that permission is allowed, and :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests. Both are used in the :term:`ACE` entries that make up the ACL. The ACL is a list that needs to be named `__acl__` and be an attribute of a class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry allows any user the `view` permission. The second entry allows the ``group:editors`` principal the `edit` permission. The ``Wiki`` class that contains the ACL is the :term:`resource` constructor for the :term:`root` resource, which is a ``Wiki`` instance. The ACL is provided to each view in the :term:`context` of the request as the ``context`` attribute. It's only happenstance that we're assigning this ACL at class scope. An ACL can be attached to an object *instance* too; this is how "row level security" can be achieved in :app:`Pyramid` applications. We actually need only *one* ACL for the entire system, however, because our security requirements are simple, so this feature is not demonstrated. See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. Add authentication and authorization policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/__init__.py`` and add the highlighted import statements: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 1-8 :linenos: :emphasize-lines: 4-5,8 :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 18-23 :linenos: :lineno-start: 18 :emphasize-lines: 1-3,5-6 :language: python Only the highlighted lines need to be added. We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth ticket that may be included in the request. We are also enabling an ``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or *deny* outcome for a view. Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string representing an encryption key used by the "authentication ticket" machinery represented by this policy: it is required. The ``callback`` is the ``groupfinder()`` function that we created before. Add permission declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: .. literalinclude:: src/authorization/tutorial/views.py :lines: 50-52 :emphasize-lines: 2-3 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 70-72 :emphasize-lines: 2-3 :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py :lines: 23-24 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 28-29 :emphasize-lines: 1-2 :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. This allows anyone to invoke these two views. We are done with the changes needed to control access. The changes that follow will add the login and logout feature. Login, logout ------------- Add login and logout views ~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. We'll also add a ``logout`` view callable to our application and provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. Add the following import statements to the head of ``tutorial/tutorial/views.py``: .. literalinclude:: src/authorization/tutorial/views.py :lines: 6-17 :emphasize-lines: 1-12 :language: python All the highlighted lines need to be added or edited. :meth:`~pyramid.view.forbidden_view_config` will be used to customize the default 403 Forbidden page. :meth:`~pyramid.security.remember` and :meth:`~pyramid.security.forget` help to create and expire an auth ticket cookie. Now add the ``login`` and ``logout`` views at the end of the file: .. literalinclude:: src/authorization/tutorial/views.py :lines: 82-116 :linenos: :lineno-start: 82 :language: python ``login()`` has two decorators: - a ``@view_config`` decorator which associates it with the ``login`` route and makes it visible when we visit ``/login``, - a ``@forbidden_view_config`` decorator which turns it into a :term:`forbidden view`. ``login()`` will be invoked when a user tries to execute a view callable for which they lack authorization. For example, if a user has not logged in and tries to add or edit a Wiki page, they will be shown the login form before being allowed to continue. The order of these two :term:`view configuration` decorators is unimportant. ``logout()`` is decorated with a ``@view_config`` decorator which associates it with the ``logout`` route. It will be invoked when we visit ``/logout``. Add the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create ``tutorial/tutorial/templates/login.pt`` with the following content: .. literalinclude:: src/authorization/tutorial/templates/login.pt :language: html The above template is referenced in the login view that we just added in ``views.py``. Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to the return value of ``view_page()``, ``edit_page()``, and ``add_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py :lines: 47-48 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 67-68 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 75-77 :emphasize-lines: 2-3 :language: python Only the highlighted lines need to be added or edited. The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if the user is not authenticated, or a userid if the user is authenticated. Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/templates/edit.pt`` and ``tutorial/tutorial/templates/view.pt`` and add the following code as indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt :lines: 34-38 :emphasize-lines: 3-5 :language: html The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id. The link will invoke the logout view. The above element will not be included if ``logged_in`` is ``None``, such as when a user is not authenticated. Reviewing our changes --------------------- Our ``tutorial/tutorial/__init__.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: :emphasize-lines: 4-5,8,18-20,22-23 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/models.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/models.py :linenos: :emphasize-lines: 4-7,12-13 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/views.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/views.py :linenos: :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: :emphasize-lines: 36-38 :language: html Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/templates/view.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: :emphasize-lines: 36-38 :language: html Only the highlighted lines need to be added or edited. Viewing the application in a browser ------------------------------------ We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). Launch a browser and visit each of the following URLs, checking that the result is as expected: - http://localhost:6543/ invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource. It is executable by any user. - http://localhost:6543/FrontPage invokes the ``view_page`` view of the ``FrontPage`` Page resource. This is because it's the :term:`default view` (a view without a ``name``) for ``Page`` resources. It is executable by any user. - http://localhost:6543/FrontPage/edit_page invokes the edit view for the FrontPage object. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. - http://localhost:6543/add_page/SomePageName invokes the add view for a page. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. - After logging in (as a result of hitting an edit or add page and submitting the login form with the ``editor`` credentials), we'll see a Logout link in the upper right hand corner. When we click it, we're logged out, and redirected back to the front page. pyramid-1.6/docs/tutorials/wiki/background.rst0000644000076500000240000000130412520062551022340 0ustar michaelstaff00000000000000========== Background ========== This version of the :app:`Pyramid` wiki tutorial presents a :app:`Pyramid` application that uses technologies which will be familiar to someone with :term:`Zope` experience. It uses :term:`ZODB` as a persistence mechanism and :term:`traversal` to map URLs to code. It can also be followed by people without any prior Python web framework experience. To code along with this tutorial, the developer will need a UNIX machine with development tools (Mac OS X with XCode, any Linux or BSD variant, etc.) *or* a Windows system of any kind. .. warning:: This tutorial has been written for Python 2. It is unlikely to work without modification under Python 3. Have fun! pyramid-1.6/docs/tutorials/wiki/basiclayout.rst0000644000076500000240000002052712575217552022565 0ustar michaelstaff00000000000000============ Basic Layout ============ The starter files generated by the ``zodb`` scaffold are very basic, but they provide a good orientation for the high-level patterns common to most :term:`traversal`-based (and :term:`ZODB`-based) :app:`Pyramid` projects. Application configuration with ``__init__.py`` ---------------------------------------------- A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python package. We use ``__init__.py`` both as a marker, indicating the directory in which it's contained is a package, and to contain application configuration code. When you run the application using the ``pserve`` command using the ``development.ini`` generated configuration file, the application configuration points at a Setuptools *entry point* described as ``egg:tutorial``. In our application, because the application's ``setup.py`` file says so, this entry point happens to be the ``main`` function within the file named ``__init__.py``. Let's take a look at the code and describe what it does: .. literalinclude:: src/basiclayout/tutorial/__init__.py :linenos: :language: py #. *Lines 1-3*. Perform some dependency imports. #. *Lines 6-8*. Define a :term:`root factory` for our Pyramid application. #. *Line 11*. ``__init__.py`` defines a function named ``main``. #. *Line 14*. We construct a :term:`Configurator` with a root factory and the settings keywords parsed by :term:`PasteDeploy`. The root factory is named ``root_factory``. #. *Line 15*. Include support for the :term:`Chameleon` template rendering bindings, allowing us to use the ``.pt`` templates. #. *Line 16*. Register a "static view", which answers requests whose URL paths start with ``/static``, using the :meth:`pyramid.config.Configurator.add_static_view` method. This statement registers a view that will serve up static assets, such as CSS and image files, for us, in this case, at ``http://localhost:6543/static/`` and below. The first argument is the "name" ``static``, which indicates that the URL path prefix of the view will be ``/static``. The second argument of this tag is the "path", which is a relative :term:`asset specification`, so it finds the resources it should serve within the ``static`` directory inside the ``tutorial`` package. Alternatively the scaffold could have used an *absolute* asset specification as the path (``tutorial:static``). #. *Line 17*. Perform a :term:`scan`. A scan will find :term:`configuration decoration`, such as view configuration decorators (e.g., ``@view_config``) in the source code of the ``tutorial`` package and will take actions based on these decorators. We don't pass any arguments to :meth:`~pyramid.config.Configurator.scan`, which implies that the scan should take place in the current package (in this case, ``tutorial``). The scaffold could have equivalently said ``config.scan('tutorial')``, but it chose to omit the package name argument. #. *Line 18*. Use the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application. Resources and models with ``models.py`` --------------------------------------- :app:`Pyramid` uses the word :term:`resource` to describe objects arranged hierarchically in a :term:`resource tree`. This tree is consulted by :term:`traversal` to map URLs to code. In this application, the resource tree represents the site structure, but it *also* represents the :term:`domain model` of the application, because each resource is a node stored persistently in a :term:`ZODB` database. The ``models.py`` file is where the ``zodb`` scaffold put the classes that implement our resource objects, each of which also happens to be a domain model object. Here is the source for ``models.py``: .. literalinclude:: src/basiclayout/tutorial/models.py :linenos: :language: py #. *Lines 4-5*. The ``MyModel`` :term:`resource` class is implemented here. Instances of this class are capable of being persisted in :term:`ZODB` because the class inherits from the :class:`persistent.mapping.PersistentMapping` class. The ``__parent__`` and ``__name__`` are important parts of the :term:`traversal` protocol. By default, have these as ``None`` indicating that this is the :term:`root` object. #. *Lines 8-14*. ``appmaker`` is used to return the *application root* object. It is called on *every request* to the :app:`Pyramid` application. It also performs bootstrapping by *creating* an application root (inside the ZODB root object) if one does not already exist. It is used by the ``root_factory`` we've defined in our ``__init__.py``. Bootstrapping is done by first seeing if the database has the persistent application root. If not, we make an instance, store it, and commit the transaction. We then return the application root object. Views With ``views.py`` ----------------------- Our scaffold generated a default ``views.py`` on our behalf. It contains a single view, which is used to render the page shown when you visit the URL ``http://localhost:6543/``. Here is the source for ``views.py``: .. literalinclude:: src/basiclayout/tutorial/views.py :linenos: :language: py Let's try to understand the components in this module: #. *Lines 1-2*. Perform some dependency imports. #. *Line 5*. Use the :func:`pyramid.view.view_config` :term:`configuration decoration` to perform a :term:`view configuration` registration. This view configuration registration will be activated when the application is started. It will be activated by virtue of it being found as the result of a :term:`scan` (when Line 14 of ``__init__.py`` is run). The ``@view_config`` decorator accepts a number of keyword arguments. We use two keyword arguments here: ``context`` and ``renderer``. The ``context`` argument signifies that the decorated view callable should only be run when :term:`traversal` finds the ``tutorial.models.MyModel`` :term:`resource` to be the :term:`context` of a request. In English, this means that when the URL ``/`` is visited, because ``MyModel`` is the root model, this view callable will be invoked. The ``renderer`` argument names an :term:`asset specification` of ``templates/mytemplate.pt``. This asset specification points at a :term:`Chameleon` template which lives in the ``mytemplate.pt`` file within the ``templates`` directory of the ``tutorial`` package. And indeed if you look in the ``templates`` directory of this package, you'll see a ``mytemplate.pt`` template file, which renders the default home page of the generated project. This asset specification is *relative* (to the view.py's current package). Alternatively we could have used the absolute asset specification ``tutorial:templates/mytemplate.pt``, but chose to use the relative version. Since this call to ``@view_config`` doesn't pass a ``name`` argument, the ``my_view`` function which it decorates represents the "default" view callable used when the context is of the type ``MyModel``. #. *Lines 6-7*. We define a :term:`view callable` named ``my_view``, which we decorated in the step above. This view callable is a *function* we write generated by the ``zodb`` scaffold that is given a ``request`` and which returns a dictionary. The ``mytemplate.pt`` :term:`renderer` named by the asset specification in the step above will convert this dictionary to a :term:`response` on our behalf. The function returns the dictionary ``{'project':'tutorial'}``. This dictionary is used by the template named by the ``mytemplate.pt`` asset specification to fill in certain values on the page. Configuration in ``development.ini`` ------------------------------------ The ``development.ini`` (in the tutorial :term:`project` directory, as opposed to the tutorial :term:`package` directory) looks like this: .. literalinclude:: src/basiclayout/development.ini :language: ini Note the existence of a ``[app:main]`` section which specifies our WSGI application. Our ZODB database settings are specified as the ``zodbconn.uri`` setting within this section. This value, and the other values within this section, are passed as ``**settings`` to the ``main`` function we defined in ``__init__.py`` when the server is started via ``pserve``. pyramid-1.6/docs/tutorials/wiki/definingmodels.rst0000644000076500000240000001016112575217552023226 0ustar michaelstaff00000000000000========================= Defining the Domain Model ========================= The first change we'll make to our stock pcreate-generated application will be to define two :term:`resource` constructors, one representing a wiki page, and another representing the wiki as a mapping of wiki page names to page objects. We'll do this inside our ``models.py`` file. Because we're using :term:`ZODB` to represent our :term:`resource tree`, each of these resource constructors represents a :term:`domain model` object, so we'll call these constructors "model constructors". Both our Page and Wiki constructors will be class objects. A single instance of the "Wiki" class will serve as a container for "Page" objects, which will be instances of the "Page" class. Delete the database ------------------- In the next step, we're going to remove the ``MyModel`` Python model class from our ``models.py`` file. Since this class is referred to within our persistent storage (represented on disk as a file named ``Data.fs``), we'll have strange things happen the next time we want to visit the application in a browser. Remove the ``Data.fs`` from the ``tutorial`` directory before proceeding any further. It's always fine to do this as long as you don't care about the content of the database; the database itself will be recreated as necessary. Edit ``models.py`` ------------------ .. note:: There is nothing special about the filename ``models.py``. A project may have many models throughout its codebase in arbitrarily named files. Files implementing models often have ``model`` in their filenames or they may live in a Python subpackage of your application package named ``models``, but this is only by convention. Open ``tutorial/tutorial/models.py`` file and edit it to look like the following: .. literalinclude:: src/models/tutorial/models.py :linenos: :language: python The first thing we want to do is remove the ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a sample and we're not going to use it. Then, we'll add a ``Wiki`` class. We want it to inherit from the :class:`persistent.mapping.PersistentMapping` class because it provides mapping behavior, and it makes sure that our Wiki page is stored as a "first-class" persistent object in our ZODB database. Our ``Wiki`` class should have two attributes set to ``None`` at class scope: ``__parent__`` and ``__name__``. If a model has a ``__parent__`` attribute of ``None`` in a traversal-based :app:`Pyramid` application, it means that it's the :term:`root` model. The ``__name__`` of the root model is also always ``None``. Then we'll add a ``Page`` class. This class should inherit from the :class:`persistent.Persistent` class. We'll also give it an ``__init__`` method that accepts a single parameter named ``data``. This parameter will contain the :term:`reStructuredText` body representing the wiki page content. Note that ``Page`` objects don't have an initial ``__name__`` or ``__parent__`` attribute. All objects in a traversal graph must have a ``__name__`` and a ``__parent__`` attribute. We don't specify these here because both ``__name__`` and ``__parent__`` will be set by a :term:`view` function when a Page is added to our Wiki mapping. As a last step, we want to change the ``appmaker`` function in our ``models.py`` file so that the :term:`root` :term:`resource` of our application is a Wiki instance. We'll also slot a single page object (the front page) into the Wiki within the ``appmaker``. This will provide :term:`traversal` a :term:`resource tree` to work against when it attempts to resolve URLs to resources. View the application in a browser --------------------------------- We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the application successfully. If you try to start the application (See :ref:`wiki-start-the-application`), you'll wind up with a Python traceback on your console that ends with this exception: .. code-block:: text ImportError: cannot import name MyModel This will also happen if you attempt to run the tests. pyramid-1.6/docs/tutorials/wiki/definingviews.rst0000644000076500000240000004156312575217552023112 0ustar michaelstaff00000000000000============== Defining Views ============== A :term:`view callable` in a :term:`traversal` -based :app:`Pyramid` application is typically a simple Python function that accepts two parameters: :term:`context` and :term:`request`. A view callable is assumed to return a :term:`response` object. .. note:: A :app:`Pyramid` view can also be defined as callable which accepts *only* a :term:`request` argument. You'll see this one-argument pattern used in other :app:`Pyramid` tutorials and applications. Either calling convention will work in any :app:`Pyramid` application; the calling conventions can be used interchangeably as necessary. In :term:`traversal` based applications, URLs are mapped to a context :term:`resource`, and since our :term:`resource tree` also represents our application's "domain model", we're often interested in the context because it represents the persistent storage of our application. For this reason, in this tutorial we define views as callables that accept ``context`` in the callable argument list. If you do need the ``context`` within a view function that only takes the request as a single argument, you can obtain it via ``request.context``. We're going to define several :term:`view callable` functions, then wire them into :app:`Pyramid` using some :term:`view configuration`. Declaring Dependencies in Our ``setup.py`` File =============================================== The view code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" application was generated by the ``pcreate`` command; it doesn't know about our custom application requirements. We need to add a dependency on the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. Open ``tutorial/setup.py`` and edit it to look like the following: .. literalinclude:: src/views/setup.py :linenos: :emphasize-lines: 20 :language: python Only the highlighted line needs to be added. Running ``setup.py develop`` ============================ Since a new software dependency was added, you will need to run ``python setup.py develop`` again inside the root of the ``tutorial`` package to obtain and register the newly added dependency distribution. Make sure your current working directory is the root of the project (the directory in which ``setup.py`` lives) and execute the following command. On UNIX: .. code-block:: text $ cd tutorial $ $VENV/bin/python setup.py develop On Windows: .. code-block:: text c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop Success executing this command will end with a line to the console something like:: Finished processing dependencies for tutorial==0.0 Adding view functions in ``views.py`` ===================================== It's time for a major change. Open ``tutorial/tutorial/views.py`` and edit it to look like the following: .. literalinclude:: src/views/tutorial/views.py :linenos: :language: python We added some imports and created a regular expression to find "WikiWords". We got rid of the ``my_view`` view function and its decorator that was added when we originally rendered the ``zodb`` scaffold. It was only an example and isn't relevant to our application. Then we added four :term:`view callable` functions to our ``views.py`` module: * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. * ``view_page()`` - Displays an individual page. * ``add_page()`` - Allows the user to add a page. * ``edit_page()`` - Allows the user to edit a page. We'll describe each one briefly in the following sections. .. note:: There is nothing special about the filename ``views.py``. A project may have many view callables throughout its codebase in arbitrarily named files. Files implementing view callables often have ``view`` in their filenames (or may live in a Python subpackage of your application package named ``views``), but this is only by convention. The ``view_wiki`` view function ------------------------------- Following is the code for the ``view_wiki`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 12-14 :lineno-start: 12 :linenos: :language: python .. note:: In our code, we use an *import* that is *relative* to our package named ``tutorial``, meaning we can omit the name of the package in the ``import`` and ``context`` statements. In our narrative, however, we refer to a *class* and thus we use the *absolute* form, meaning that the name of the package is included. ``view_wiki()`` is the :term:`default view` that gets called when a request is made to the root URL of our wiki. It always redirects to an URL which represents the path to our "FrontPage". We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Wiki`` as its context. This means that when a Wiki resource is the context and no :term:`view name` exists in the request, then this view will be used. The view configuration associated with ``view_wiki`` does not use a ``renderer`` because the view callable always returns a :term:`response` object rather than a dictionary. No renderer is necessary when a view returns a response object. The ``view_wiki`` view callable always redirects to the URL of a Page resource named "FrontPage". To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement the :class:`pyramid.interfaces.IResponse` interface, like :class:`pyramid.response.Response` does). It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page resource (i.e., ``http://localhost:6543/FrontPage``), and uses it as the "location" of the ``HTTPFound`` response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- Here is the code for the ``view_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 16-33 :lineno-start: 16 :linenos: :language: python The ``view_page`` function is configured to respond as the default view of a Page resource. We provide it with a ``@view_config`` decorator which names the class ``tutorial.models.Page`` as its context. This means that when a Page resource is the context, and no :term:`view name` exists in the request, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/view.pt`` template file as a ``renderer``. The ``view_page`` function generates the :term:`reStructuredText` body of a page (stored as the ``data`` attribute of the context passed to the view; the context will be a ``Page`` resource) as HTML. Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each WikiWord match found in the content. If the wiki (our page's ``__parent__``) already contains a page with the matched WikiWord name, the ``check`` function generates a view link to be used as the substitution value and returns it. If the wiki does not already contain a page with the matched WikiWord name, the function generates an "add" link as the substitution value and returns it. As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for WikiWords based on the content of our current page resource. We then generate an edit URL because it's easier to do here than in the template, and we wrap up a number of arguments in a dictionary and return it. The arguments we wrap into a dictionary include ``page``, ``content``, and ``edit_url``. As a result, the *template* associated with this view callable (via ``renderer=`` in its configuration) will be able to use these names to perform various rendering tasks. The template associated with this view callable will be a template which lives in ``templates/view.pt``. Note the contrast between this view callable and the ``view_wiki`` view callable. In the ``view_wiki`` view callable, we unconditionally return a :term:`response` object. In the ``view_page`` view callable, we return a *dictionary*. It is *always* fine to return a :term:`response` object from a :app:`Pyramid` view. Returning a dictionary is allowed only when there is a :term:`renderer` associated with the view callable in the view configuration. The ``add_page`` view function ------------------------------ Here is the code for the ``add_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 35-50 :lineno-start: 35 :linenos: :language: python The ``add_page`` function is configured to respond when the context resource is a Wiki and the :term:`view name` is ``add_page``. We provide it with a ``@view_config`` decorator which names the string ``add_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Wiki`` as its context, and the renderer named ``templates/edit.pt``. This means that when a Wiki resource is the context, and a :term:`view name` named ``add_page`` exists as the result of traversal, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. We share the same template between add and edit views, thus ``edit.pt`` instead of ``add.pt``. The ``add_page`` function will be invoked when a user clicks on a WikiWord which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. It also acts as a handler for the form that is generated when we want to add a page resource. The ``context`` of the ``add_page`` view is always a Wiki resource (*not* a Page resource). The request :term:`subpath` in :app:`Pyramid` is the sequence of names that are found *after* the :term:`view name` in the URL segments given in the ``PATH_INFO`` of the WSGI request as the result of :term:`traversal`. If our add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``, the :term:`subpath` will be a tuple: ``('SomeName',)``. The add view takes the zeroth element of the subpath (the wiki page name), and aliases it to the name attribute in order to know the name of the page we're trying to add. If the view rendering is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), the view renders a template. To do so, it generates a "save url" which the template uses as the form post URL during rendering. We're lazy here, so we're trying to use the same template (``templates/edit.pt``) for the add view as well as the page edit view. To do so, we create a dummy Page resource object in order to satisfy the edit form's desire to have *some* page object exposed as ``page``, and we'll render the template to a response. If the view rendering *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), we grab the page body from the form data, create a Page object using the name in the subpath and the page body, and save it into "our context" (the Wiki) using the ``__setitem__`` method of the context. We then redirect back to the ``view_page`` view (the default view for a page) for the newly created page. The ``edit_page`` view function ------------------------------- Here is the code for the ``edit_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 52-60 :lineno-start: 52 :linenos: :language: python The ``edit_page`` function is configured to respond when the context is a Page resource and the :term:`view name` is ``edit_page``. We provide it with a ``@view_config`` decorator which names the string ``edit_page`` as its :term:`view name` (via ``name=``), the class ``tutorial.models.Page`` as its context, and the renderer named ``templates/edit.pt``. This means that when a Page resource is the context, and a :term:`view name` exists as the result of traversal named ``edit_page``, this view will be used. We inform :app:`Pyramid` this view will use the ``templates/edit.pt`` template file as a ``renderer``. The ``edit_page`` function will be invoked when a user clicks the "Edit this Page" button on the view form. It renders an edit form but it also acts as the form post view callable for the form it renders. The ``context`` of the ``edit_page`` view will *always* be a Page resource (never a Wiki resource). If the view execution is *not* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``False``), the view simply renders the edit form, passing the page resource, and a ``save_url`` which will be used as the action of the generated form. If the view execution *is* a result of a form submission (if the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameter and sets it as the ``data`` attribute of the page context. It then redirects to the default view of the context (the page), which will always be the ``view_page`` view. Adding templates ================ The ``view_page``, ``add_page`` and ``edit_page`` views that we've added reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT` template. These templates will live in the ``templates`` directory of our tutorial package. Chameleon templates must have a ``.pt`` extension to be recognized as such. The ``view.pt`` template ------------------------ Create ``tutorial/tutorial/templates/view.pt`` and add the following content: .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html This template is used by ``view_page()`` for displaying a single wiki page. It includes: - A ``div`` element that is replaced with the ``content`` value provided by the view (lines 36-38). ``content`` contains HTML, so the ``structure`` keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.) - A link that points at the "edit" URL which invokes the ``edit_page`` view for the page being viewed (lines 40-42). The ``edit.pt`` template ------------------------ Create ``tutorial/tutorial/templates/edit.pt`` and add the following content: .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: html This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. It displays a page containing a form that includes: - A 10 row by 60 column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (line 45). - A submit button that has the name ``form.submitted`` (line 48). The form POSTs back to the ``save_url`` argument supplied by the view (line 43). The view will use the ``body`` and ``form.submitted`` values. .. note:: Our templates use a ``request`` object that none of our tutorial views return in their dictionary. ``request`` is one of several names that are available "by default" in a template when a template renderer is used. See :ref:`renderer_system_values` for information about other names that are available by default when a template is used as a renderer. Static assets ------------- Our templates name static assets, including CSS and images. We don't need to create these files within our package's ``static`` directory because they were provided at the time we created the project. As an example, the CSS file will be accessed via ``http://localhost:6543/static/theme.css`` by virtue of the call to the ``add_static_view`` directive we've made in the ``__init__.py`` file. Any number and type of static assets can be placed in this directory (or subdirectories) and are just referred to by URL or by using the convenience method ``static_url``, e.g., ``request.static_url(':static/foo.css')`` within templates. Viewing the application in a browser ==================================== We can finally examine our application in a browser (See :ref:`wiki-start-the-application`). Launch a browser and visit each of the following URLs, checking that the result is as expected: - http://localhost:6543/ invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource. - http://localhost:6543/FrontPage/ invokes the ``view_page`` view of the front page resource. This is because it's the :term:`default view` (a view without a ``name``) for Page resources. - http://localhost:6543/FrontPage/edit_page invokes the edit view for the ``FrontPage`` Page resource. - http://localhost:6543/add_page/SomePageName invokes the add view for a Page. - To generate an error, visit http://localhost:6543/add_page which will generate an ``IndexErrorr: tuple index out of range`` error. You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. pyramid-1.6/docs/tutorials/wiki/design.rst0000644000076500000240000002141212575217552021511 0ustar michaelstaff00000000000000========== Design ========== Following is a quick overview of the design of our wiki application, to help us understand the changes that we will be making as we work through the tutorial. Overall ------- We choose to use :term:`reStructuredText` markup in the wiki text. Translation from reStructuredText to HTML is provided by the widely used ``docutils`` Python module. We will add this module in the dependency list on the project ``setup.py`` file. Models ------ The root resource named ``Wiki`` will be a mapping of wiki page names to page resources. The page resources will be instances of a *Page* class and they store the text content. URLs like ``/PageName`` will be traversed using Wiki[ *PageName* ] => page, and the context that results is the page resource of an existing page. To add a page to the wiki, a new instance of the page resource is created and its name and reference are added to the Wiki mapping. A page named ``FrontPage`` containing the text *This is the front page*, will be created when the storage is initialized, and will be used as the wiki home page. Views ----- There will be three views to handle the normal operations of adding, editing, and viewing wiki pages, plus one view for the wiki front page. Two templates will be used, one for viewing, and one for both adding and editing wiki pages. The default templating systems in :app:`Pyramid` are :term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. Mako is a non-XML-based templating language. Because we had to pick one, we chose Chameleon for this tutorial. Security -------- We'll eventually be adding security to our application. The components we'll use to do this are below. - USERS, a dictionary mapping :term:`userids ` to their corresponding passwords. - GROUPS, a dictionary mapping :term:`userids ` to a list of groups to which they belong. - ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS. It will be provided in a new ``security.py`` file. - An :term:`ACL` is attached to the root :term:`resource`. Each row below details an :term:`ACE`: +----------+----------------+----------------+ | Action | Principal | Permission | +==========+================+================+ | Allow | Everyone | View | +----------+----------------+----------------+ | Allow | group:editors | Edit | +----------+----------------+----------------+ - Permission declarations are added to the views to assert the security policies as each request is handled. Two additional views and one template will handle the login and logout tasks. Summary ------- The URL, context, actions, template and permission associated to each view are listed in the following table: +----------------------+-------------+-----------------+-----------------------+------------+------------+ | URL | View | Context | Action | Template | Permission | | | | | | | | +======================+=============+=================+=======================+============+============+ | / | view_wiki | Wiki | Redirect to | | | | | | | /FrontPage | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ | /PageName | view_page | Page | Display existing | view.pt | view | | | [1]_ | | page [2]_ | | | | | | | | | | | | | | | | | | | | | | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ | /PageName/edit_page | edit_page | Page | Display edit form | edit.pt | edit | | | | | with existing | | | | | | | content. | | | | | | | | | | | | | | If the form was | | | | | | | submitted, redirect | | | | | | | to /PageName | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ | /add_page/PageName | add_page | Wiki | Create the page | edit.pt | edit | | | | | *PageName* in | | | | | | | storage, display | | | | | | | the edit form | | | | | | | without content. | | | | | | | | | | | | | | If the form was | | | | | | | submitted, | | | | | | | redirect to | | | | | | | /PageName | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ | /login | login | Wiki, | Display login form. | login.pt | | | | | Forbidden [3]_ | | | | | | | | If the form was | | | | | | | submitted, | | | | | | | authenticate. | | | | | | | | | | | | | | - If authentication | | | | | | | succeeds, | | | | | | | redirect to the | | | | | | | page that we | | | | | | | came from. | | | | | | | | | | | | | | - If authentication | | | | | | | fails, display | | | | | | | login form with | | | | | | | "login failed" | | | | | | | message. | | | | | | | | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ | /logout | logout | Wiki | Redirect to | | | | | | | /FrontPage | | | +----------------------+-------------+-----------------+-----------------------+------------+------------+ .. [1] This is the default view for a Page context when there is no view name. .. [2] Pyramid will return a default 404 Not Found page if the page *PageName* does not exist yet. .. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. pyramid-1.6/docs/tutorials/wiki/distributing.rst0000644000076500000240000000246312575217552022754 0ustar michaelstaff00000000000000============================= Distributing Your Application ============================= Once your application works properly, you can create a "tarball" from it by using the ``setup.py sdist`` command. The following commands assume your current working directory is the ``tutorial`` package we've created and that the parent directory of the ``tutorial`` package is a virtualenv representing a :app:`Pyramid` environment. On UNIX: .. code-block:: text $ $VENV/bin/python setup.py sdist On Windows: .. code-block:: text c:\pyramidtut> %VENV%\Scripts\python setup.py sdist The output of such a command will be something like: .. code-block:: text running sdist # .. more output .. creating dist tar -cf dist/tutorial-0.0.tar tutorial-0.0 gzip -f9 dist/tutorial-0.0.tar removing 'tutorial-0.0' (and everything under it) Note that this command creates a tarball in the "dist" subdirectory named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the ``easy_install`` command directly at it. Or you can upload it to `PyPI `_ and share it with the rest of the world, where it can be downloaded via ``easy_install`` remotely like any other package people download from PyPI. pyramid-1.6/docs/tutorials/wiki/index.rst0000644000076500000240000000150412575217552021347 0ustar michaelstaff00000000000000.. _bfg_wiki_tutorial: ZODB + Traversal Wiki Tutorial ============================== This tutorial introduces a :term:`ZODB` and :term:`traversal`-based :app:`Pyramid` application to a developer familiar with Python. It will be most familiar to developers with previous :term:`Zope` experience. When the is finished, the developer will have created a basic Wiki application with authentication. For cut and paste purposes, the source code for all stages of this tutorial can be browsed on GitHub at `docs/tutorials/wiki/src `_, which corresponds to the same location if you have Pyramid sources. .. toctree:: :maxdepth: 2 background design installation basiclayout definingmodels definingviews authorization tests distributing pyramid-1.6/docs/tutorials/wiki/installation.rst0000644000076500000240000001734312575217552022751 0ustar michaelstaff00000000000000============ Installation ============ Before you begin ================ This tutorial assumes that you have already followed the steps in :ref:`installing_chapter`, except **do not create a virtualenv or install Pyramid**. Thereby you will satisfy the following requirements. * Python interpreter is installed on your operating system * :term:`setuptools` or :term:`distribute` is installed * :term:`virtualenv` is installed Create directory to contain the project --------------------------------------- We need a workspace for our project files. On UNIX ^^^^^^^ .. code-block:: text $ mkdir ~/pyramidtut On Windows ^^^^^^^^^^ .. code-block:: text c:\> mkdir pyramidtut Create and use a virtual Python environment ------------------------------------------- Next let's create a `virtualenv` workspace for our project. We will use the `VENV` environment variable instead of the absolute path of the virtual environment. On UNIX ^^^^^^^ .. code-block:: text $ export VENV=~/pyramidtut $ virtualenv $VENV New python executable in /home/foo/env/bin/python Installing setuptools.............done. On Windows ^^^^^^^^^^ .. code-block:: text c:\> set VENV=c:\pyramidtut Versions of Python use different paths, so you will need to adjust the path to the command for your Python version. Python 2.7: .. code-block:: text c:\> c:\Python27\Scripts\virtualenv %VENV% Python 3.2: .. code-block:: text c:\> c:\Python32\Scripts\virtualenv %VENV% Install Pyramid and tutorial dependencies into the virtual Python environment ----------------------------------------------------------------------------- On UNIX ^^^^^^^ .. code-block:: text $ $VENV/bin/easy_install docutils pyramid_tm pyramid_zodbconn \ pyramid_debugtoolbar nose coverage On Windows ^^^^^^^^^^ .. code-block:: text c:\> %VENV%\Scripts\easy_install docutils pyramid_tm pyramid_zodbconn \ pyramid_debugtoolbar nose coverage Change Directory to Your Virtual Python Environment --------------------------------------------------- Change directory to the ``pyramidtut`` directory. On UNIX ^^^^^^^ .. code-block:: text $ cd pyramidtut On Windows ^^^^^^^^^^ .. code-block:: text c:\> cd pyramidtut .. _making_a_project: Making a project ================ Your next step is to create a project. For this tutorial, we will use the :term:`scaffold` named ``zodb``, which generates an application that uses :term:`ZODB` and :term:`traversal`. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We will use `pcreate`—a script that comes with Pyramid to quickly and easily generate scaffolds, usually with a single command—to create the scaffold for our project. By passing `zodb` into the `pcreate` command, the script creates the files needed to use ZODB. By passing in our application name `tutorial`, the script inserts that application name into all the required files. The below instructions assume your current working directory is "pyramidtut". On UNIX ------- .. code-block:: text $ $VENV/bin/pcreate -s zodb tutorial On Windows ---------- .. code-block:: text c:\pyramidtut> %VENV%\Scripts\pcreate -s zodb tutorial .. note:: If you are using Windows, the ``zodb`` scaffold may not deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. .. _installing_project_in_dev_mode_zodb: Installing the project in development mode ========================================== In order to do development on the project easily, you must "register" the project as a development egg in your workspace using the ``setup.py develop`` command. In order to do so, cd to the `tutorial` directory you created in :ref:`making_a_project`, and run the ``setup.py develop`` command using the virtualenv Python interpreter. On UNIX ------- .. code-block:: text $ cd tutorial $ $VENV/bin/python setup.py develop On Windows ---------- .. code-block:: text c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop The console will show `setup.py` checking for packages and installing missing packages. Success executing this command will show a line like the following:: Finished processing dependencies for tutorial==0.0 .. _running_tests: Run the tests ============= After you've installed the project in development mode, you may run the tests for the project. On UNIX ------- .. code-block:: text $ $VENV/bin/python setup.py test -q On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q For a successful test run, you should see output that ends like this:: . ---------------------------------------------------------------------- Ran 1 test in 0.094s OK Expose test coverage information ================================ You can run the ``nosetests`` command to see test coverage information. This runs the tests in the same way that ``setup.py test`` does but provides additional "coverage" information, exposing which lines of your project are "covered" (or not covered) by the tests. On UNIX ------- .. code-block:: text $ $VENV/bin/nosetests --cover-package=tutorial --cover-erase --with-coverage On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \ --cover-erase --with-coverage If successful, you will see output something like this:: . Name Stmts Miss Cover Missing -------------------------------------------------- tutorial.py 12 7 42% 7-8, 14-18 tutorial/models.py 10 6 40% 9-14 tutorial/views.py 4 0 100% -------------------------------------------------- TOTAL 26 13 50% ---------------------------------------------------------------------- Ran 1 test in 0.392s OK Looks like our package doesn't quite have 100% test coverage. .. _wiki-start-the-application: Start the application ===================== Start the application. On UNIX ------- .. code-block:: text $ $VENV/bin/pserve development.ini --reload On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload .. note:: Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections. If successful, you will see something like this on your console:: Starting subprocess with file monitor Starting server in PID 95736. serving on http://0.0.0.0:6543 This means the server is ready to accept requests. Visit the application in a browser ================================== In a browser, visit `http://localhost:6543/ `_. You will see the generated application's default page. One thing you'll notice is the "debug toolbar" icon on right hand side of the page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. Decisions the ``zodb`` scaffold has made for you ================================================ Creating a project using the ``zodb`` scaffold makes the following assumptions: - you are willing to use :term:`ZODB` as persistent storage - you are willing to use :term:`traversal` to map URLs to code .. note:: :app:`Pyramid` supports any persistent storage mechanism (e.g., a SQL database or filesystem files). It also supports an additional mechanism to map URLs to code (:term:`URL dispatch`). However, for the purposes of this tutorial, we'll only be using traversal and ZODB. pyramid-1.6/docs/tutorials/wiki/NOTE-relocatable.txt0000644000076500000240000000075412520062551023260 0ustar michaelstaff00000000000000We specifically use relative package references where possible so this demo works even if the user names their package (in the '$VENV/bin/pcreate -s zodb ...' step) something other than 'tutorial'. Specifically: - use relative imports - use plain relative URLs for resources (like stylesheets and images) in page templates. Direct uses of the package name, like in __init__.py 'config.scan()' statements, are already adjusted by the paster/pcreate, so we don't have to worry about them. pyramid-1.6/docs/tutorials/wiki/src/0000755000076500000240000000000012642137501020263 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/authorization/0000755000076500000240000000000012642137501023163 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/authorization/CHANGES.txt0000644000076500000240000000003412234375161024774 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki/src/authorization/development.ini0000644000076500000240000000227312517346416026222 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/authorization/MANIFEST.in0000644000076500000240000000020312234375161024717 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki/src/authorization/production.ini0000644000076500000240000000203612517346416026063 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/authorization/README.txt0000644000076500000240000000002312234375161024657 0ustar michaelstaff00000000000000tutorial README pyramid-1.6/docs/tutorials/wiki/src/authorization/setup.py0000644000076500000240000000217312575217552024712 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', 'docutils', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="tutorial", entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/0000755000076500000240000000000012642137501025026 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/__init__.py0000644000076500000240000000167212520062551027142 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from .models import appmaker from .security import groupfinder def root_factory(request): conn = get_connection(request) return appmaker(conn.root()) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/models.py0000644000076500000240000000142212517346416026672 0ustar michaelstaff00000000000000from persistent import Persistent from persistent.mapping import PersistentMapping from pyramid.security import ( Allow, Everyone, ) class Wiki(PersistentMapping): __name__ = None __parent__ = None __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] class Page(Persistent): def __init__(self, data): self.data = data def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = Wiki() frontpage = Page('This is the front page') app_root['FrontPage'] = frontpage frontpage.__name__ = 'FrontPage' frontpage.__parent__ = app_root zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root'] pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/security.py0000644000076500000240000000030012234375161027243 0ustar michaelstaff00000000000000USERS = {'editor':'editor', 'viewer':'viewer'} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/static/0000755000076500000240000000000012642137501026315 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552031274 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png0000644000076500000240000003114512575217552030506 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/static/theme.css0000644000076500000240000000556612575217552030157 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css0000644000076500000240000000442512575217552030732 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/templates/0000755000076500000240000000000012642137501027024 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt0000644000076500000240000000552512575217552030337 0ustar michaelstaff00000000000000 ${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Editing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt0000644000076500000240000000552212575217552030517 0ustar michaelstaff00000000000000 Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Login

pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt0000644000076500000240000000562412575217552031573 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt0000644000076500000240000000524512575217552030363 0ustar michaelstaff00000000000000 ${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/tests.py0000644000076500000240000001017212517346416026553 0ustar michaelstaff00000000000000import unittest from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Page return Page def _makeOne(self, data=u'some data'): return self._getTargetClass()(data=data) def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') class WikiModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Wiki return Wiki def _makeOne(self): return self._getTargetClass()() def test_it(self): wiki = self._makeOne() self.assertEqual(wiki.__parent__, None) self.assertEqual(wiki.__name__, None) class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): from .models import appmaker return appmaker(zodb_root) def test_it(self): root = {} self._callFUT(root) self.assertEqual(root['app_root']['FrontPage'].data, 'This is the front page') class ViewWikiTests(unittest.TestCase): def test_it(self): from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import view_page return view_page(context, request) def test_it(self): wiki = testing.DummyResource() wiki['IDoExist'] = testing.DummyResource() context = testing.DummyResource(data='Hello CruelWorld IDoExist') context.__parent__ = wiki context.__name__ = 'thepage' request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/thepage/edit_page') class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() request.subpath = ['AnotherPage'] info = self._callFUT(context, request) self.assertEqual(info['page'].data,'') self.assertEqual( info['save_url'], request.resource_url(context, 'add_page', 'AnotherPage')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') self.assertEqual(page.__parent__, context) class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual(info['save_url'], request.resource_url(context, 'edit_page')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) response = self._callFUT(context, request) self.assertEqual(response.location, 'http://example.com/') self.assertEqual(context.data, 'Hello yo!') pyramid-1.6/docs/tutorials/wiki/src/authorization/tutorial/views.py0000644000076500000240000000734012520062551026536 0ustar michaelstaff00000000000000from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound from pyramid.view import ( view_config, forbidden_view_config, ) from pyramid.security import ( remember, forget, ) from .security import USERS from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) @view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ def check(match): word = match.group(1) if word in wiki: page = wiki[word] view_url = request.resource_url(page) return '%s' % (view_url, word) else: add_url = request.application_url + '/add_page/' + word return '%s' % (add_url, word) content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') return dict(page = context, content = content, edit_url = edit_url, logged_in = request.authenticated_userid) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): pagename = request.subpath[0] if 'form.submitted' in request.params: body = request.params['body'] page = Page(body) page.__name__ = pagename page.__parent__ = context context[pagename] = page return HTTPFound(location = request.resource_url(page)) save_url = request.resource_url(context, 'add_page', pagename) page = Page('') page.__name__ = pagename page.__parent__ = context return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] return HTTPFound(location = request.resource_url(context)) return dict(page=context, save_url=request.resource_url(context, 'edit_page'), logged_in=request.authenticated_userid) @view_config(context='.models.Wiki', name='login', renderer='templates/login.pt') @forbidden_view_config(renderer='templates/login.pt') def login(request): login_url = request.resource_url(request.context, 'login') referrer = request.url if referrer == login_url: referrer = '/' # never use the login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location = came_from, headers = headers) message = 'Failed login' return dict( message = message, url = request.application_url + '/login', came_from = came_from, login = login, password = password, ) @view_config(context='.models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location = request.resource_url(request.context), headers = headers) pyramid-1.6/docs/tutorials/wiki/src/basiclayout/0000755000076500000240000000000012642137501022602 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/basiclayout/CHANGES.txt0000644000076500000240000000003412234375161024413 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki/src/basiclayout/development.ini0000644000076500000240000000227312517346416025641 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/basiclayout/MANIFEST.in0000644000076500000240000000020312234375161024336 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki/src/basiclayout/production.ini0000644000076500000240000000203612517346416025502 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/basiclayout/README.txt0000644000076500000240000000002312234375161024276 0ustar michaelstaff00000000000000tutorial README pyramid-1.6/docs/tutorials/wiki/src/basiclayout/setup.py0000644000076500000240000000215312575217552024327 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="tutorial", entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/0000755000076500000240000000000012642137501024445 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py0000644000076500000240000000104212520062551026550 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_zodbconn import get_connection from .models import appmaker def root_factory(request): conn = get_connection(request) return appmaker(conn.root()) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/models.py0000644000076500000240000000052412517346416026313 0ustar michaelstaff00000000000000from persistent.mapping import PersistentMapping class MyModel(PersistentMapping): __parent__ = __name__ = None def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = MyModel() zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root'] pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/static/0000755000076500000240000000000012642137501025734 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552030713 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png0000644000076500000240000003114512575217552030125 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css0000644000076500000240000000556612575217552027576 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css0000644000076500000240000000442512575217552030351 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/templates/0000755000076500000240000000000012642137501026443 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt0000644000076500000240000000562412575217552031212 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/tests.py0000644000076500000240000000060012517346416026165 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') pyramid-1.6/docs/tutorials/wiki/src/basiclayout/tutorial/views.py0000644000076500000240000000027612517346416026171 0ustar michaelstaff00000000000000from pyramid.view import view_config from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): return {'project': 'tutorial'} pyramid-1.6/docs/tutorials/wiki/src/models/0000755000076500000240000000000012642137501021546 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/models/CHANGES.txt0000644000076500000240000000003312234375161023356 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki/src/models/development.ini0000644000076500000240000000227312517346416024605 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/models/MANIFEST.in0000644000076500000240000000020312234375161023302 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki/src/models/production.ini0000644000076500000240000000203612517346416024446 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/models/README.txt0000644000076500000240000000002312234375161023242 0ustar michaelstaff00000000000000tutorial README pyramid-1.6/docs/tutorials/wiki/src/models/setup.py0000644000076500000240000000215312575217552023273 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="tutorial", entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/0000755000076500000240000000000012642137501023411 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/__init__.py0000644000076500000240000000104212520062551025514 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_zodbconn import get_connection from .models import appmaker def root_factory(request): conn = get_connection(request) return appmaker(conn.root()) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/models.py0000644000076500000240000000116512234375161025254 0ustar michaelstaff00000000000000from persistent import Persistent from persistent.mapping import PersistentMapping class Wiki(PersistentMapping): __name__ = None __parent__ = None class Page(Persistent): def __init__(self, data): self.data = data def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = Wiki() frontpage = Page('This is the front page') app_root['FrontPage'] = frontpage frontpage.__name__ = 'FrontPage' frontpage.__parent__ = app_root zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root'] pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/static/0000755000076500000240000000000012642137501024700 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027657 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/static/pyramid.png0000644000076500000240000003114512575217552027071 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/static/theme.css0000644000076500000240000000556612575217552026542 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/static/theme.min.css0000644000076500000240000000442512575217552027315 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/templates/0000755000076500000240000000000012642137501025407 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt0000644000076500000240000000562412575217552030156 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/tests.py0000644000076500000240000000306312520062551025124 0ustar michaelstaff00000000000000import unittest from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Page return Page def _makeOne(self, data=u'some data'): return self._getTargetClass()(data=data) def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') class WikiModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Wiki return Wiki def _makeOne(self): return self._getTargetClass()() def test_it(self): wiki = self._makeOne() self.assertEqual(wiki.__parent__, None) self.assertEqual(wiki.__name__, None) class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): from .models import appmaker return appmaker(zodb_root) def test_no_app_root(self): root = {} self._callFUT(root) self.assertEqual(root['app_root']['FrontPage'].data, 'This is the front page') def test_w_app_root(self): app_root = object() root = {'app_root': app_root} self._callFUT(root) self.assertTrue(root['app_root'] is app_root) class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], 'tutorial') pyramid-1.6/docs/tutorials/wiki/src/models/tutorial/views.py0000644000076500000240000000027612517346416025135 0ustar michaelstaff00000000000000from pyramid.view import view_config from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): return {'project': 'tutorial'} pyramid-1.6/docs/tutorials/wiki/src/tests/0000755000076500000240000000000012642137501021425 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/tests/CHANGES.txt0000644000076500000240000000003412234375161023236 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki/src/tests/development.ini0000644000076500000240000000227312517346416024464 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/tests/MANIFEST.in0000644000076500000240000000020312234375161023161 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki/src/tests/production.ini0000644000076500000240000000203612517346416024325 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/tests/README.txt0000644000076500000240000000002312234375161023121 0ustar michaelstaff00000000000000tutorial README pyramid-1.6/docs/tutorials/wiki/src/tests/setup.py0000644000076500000240000000222512575217552023152 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', 'docutils', 'WebTest', # add this ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="tutorial", entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/0000755000076500000240000000000012642137501023270 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/__init__.py0000644000076500000240000000167212520062551025404 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_zodbconn import get_connection from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from .models import appmaker from .security import groupfinder def root_factory(request): conn = get_connection(request) return appmaker(conn.root()) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/models.py0000644000076500000240000000142212517346416025134 0ustar michaelstaff00000000000000from persistent import Persistent from persistent.mapping import PersistentMapping from pyramid.security import ( Allow, Everyone, ) class Wiki(PersistentMapping): __name__ = None __parent__ = None __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] class Page(Persistent): def __init__(self, data): self.data = data def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = Wiki() frontpage = Page('This is the front page') app_root['FrontPage'] = frontpage frontpage.__name__ = 'FrontPage' frontpage.__parent__ = app_root zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root'] pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/security.py0000644000076500000240000000030012234375161025505 0ustar michaelstaff00000000000000USERS = {'editor':'editor', 'viewer':'viewer'} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/static/0000755000076500000240000000000012642137501024557 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027536 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/static/pyramid.png0000644000076500000240000003114512575217552026750 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/static/theme.css0000644000076500000240000000556612575217552026421 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css0000644000076500000240000000442512575217552027174 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/templates/0000755000076500000240000000000012642137501025266 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt0000644000076500000240000000552512575217552026601 0ustar michaelstaff00000000000000 ${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Editing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/templates/login.pt0000644000076500000240000000552212575217552026761 0ustar michaelstaff00000000000000 Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Login

pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt0000644000076500000240000000562412575217552030035 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/templates/view.pt0000644000076500000240000000524512575217552026625 0ustar michaelstaff00000000000000 ${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/tests.py0000644000076500000240000001737712520062551025020 0ustar michaelstaff00000000000000import unittest from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Page return Page def _makeOne(self, data=u'some data'): return self._getTargetClass()(data=data) def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') class WikiModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Wiki return Wiki def _makeOne(self): return self._getTargetClass()() def test_it(self): wiki = self._makeOne() self.assertEqual(wiki.__parent__, None) self.assertEqual(wiki.__name__, None) class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): from .models import appmaker return appmaker(zodb_root) def test_it(self): root = {} self._callFUT(root) self.assertEqual(root['app_root']['FrontPage'].data, 'This is the front page') class ViewWikiTests(unittest.TestCase): def test_it(self): from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import view_page return view_page(context, request) def test_it(self): wiki = testing.DummyResource() wiki['IDoExist'] = testing.DummyResource() context = testing.DummyResource(data='Hello CruelWorld IDoExist') context.__parent__ = wiki context.__name__ = 'thepage' request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/thepage/edit_page') class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() request.subpath = ['AnotherPage'] info = self._callFUT(context, request) self.assertEqual(info['page'].data,'') self.assertEqual( info['save_url'], request.resource_url(context, 'add_page', 'AnotherPage')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') self.assertEqual(page.__parent__, context) class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual(info['save_url'], request.resource_url(context, 'edit_page')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) response = self._callFUT(context, request) self.assertEqual(response.location, 'http://example.com/') self.assertEqual(context.data, 'Hello yo!') class FunctionalTests(unittest.TestCase): viewer_login = '/login?login=viewer&password=viewer' \ '&came_from=FrontPage&form.submitted=Login' viewer_wrong_login = '/login?login=viewer&password=incorrect' \ '&came_from=FrontPage&form.submitted=Login' editor_login = '/login?login=editor&password=editor' \ '&came_from=FrontPage&form.submitted=Login' def setUp(self): import tempfile import os.path from . import main self.tmpdir = tempfile.mkdtemp() dbpath = os.path.join( self.tmpdir, 'test.db') uri = 'file://' + dbpath settings = { 'zodbconn.uri' : uri , 'pyramid.includes': ['pyramid_zodbconn', 'pyramid_tm'] } app = main({}, **settings) self.db = app.registry._zodb_databases[''] from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): import shutil self.db.close() shutil.rmtree( self.tmpdir ) def test_root(self): res = self.testapp.get('/', status=302) self.assertEqual(res.location, 'http://localhost/FrontPage') def test_FrontPage(self): res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'FrontPage' in res.body) def test_unexisting_page(self): res = self.testapp.get('/SomePage', status=404) self.assertTrue(b'Not Found' in res.body) def test_successful_log_in(self): res = self.testapp.get( self.viewer_login, status=302) self.assertEqual(res.location, 'http://localhost/FrontPage') def test_failed_log_in(self): res = self.testapp.get( self.viewer_wrong_login, status=200) self.assertTrue(b'login' in res.body) def test_logout_link_present_when_logged_in(self): res = self.testapp.get( self.viewer_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'Logout' in res.body) def test_logout_link_not_present_after_logged_out(self): res = self.testapp.get( self.viewer_login, status=302) res = self.testapp.get('/FrontPage', status=200) res = self.testapp.get('/logout', status=302) self.assertTrue(b'Logout' not in res.body) def test_anonymous_user_cannot_edit(self): res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Login' in res.body) def test_anonymous_user_cannot_add(self): res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Login' in res.body) def test_viewer_user_cannot_edit(self): res = self.testapp.get( self.viewer_login, status=302) res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Login' in res.body) def test_viewer_user_cannot_add(self): res = self.testapp.get( self.viewer_login, status=302) res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Login' in res.body) def test_editors_member_user_can_edit(self): res = self.testapp.get( self.editor_login, status=302) res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Editing' in res.body) def test_editors_member_user_can_add(self): res = self.testapp.get( self.editor_login, status=302) res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Editing' in res.body) def test_editors_member_user_can_view(self): res = self.testapp.get( self.editor_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'FrontPage' in res.body) pyramid-1.6/docs/tutorials/wiki/src/tests/tutorial/views.py0000644000076500000240000000734012520062551025000 0ustar michaelstaff00000000000000from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound from pyramid.view import ( view_config, forbidden_view_config, ) from pyramid.security import ( remember, forget, ) from .security import USERS from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(context='.models.Wiki', permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) @view_config(context='.models.Page', renderer='templates/view.pt', permission='view') def view_page(context, request): wiki = context.__parent__ def check(match): word = match.group(1) if word in wiki: page = wiki[word] view_url = request.resource_url(page) return '%s' % (view_url, word) else: add_url = request.application_url + '/add_page/' + word return '%s' % (add_url, word) content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') return dict(page = context, content = content, edit_url = edit_url, logged_in = request.authenticated_userid) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', permission='edit') def add_page(context, request): pagename = request.subpath[0] if 'form.submitted' in request.params: body = request.params['body'] page = Page(body) page.__name__ = pagename page.__parent__ = context context[pagename] = page return HTTPFound(location = request.resource_url(page)) save_url = request.resource_url(context, 'add_page', pagename) page = Page('') page.__name__ = pagename page.__parent__ = context return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', permission='edit') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] return HTTPFound(location = request.resource_url(context)) return dict(page=context, save_url=request.resource_url(context, 'edit_page'), logged_in=request.authenticated_userid) @view_config(context='.models.Wiki', name='login', renderer='templates/login.pt') @forbidden_view_config(renderer='templates/login.pt') def login(request): login_url = request.resource_url(request.context, 'login') referrer = request.url if referrer == login_url: referrer = '/' # never use the login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location = came_from, headers = headers) message = 'Failed login' return dict( message = message, url = request.application_url + '/login', came_from = came_from, login = login, password = password, ) @view_config(context='.models.Wiki', name='logout') def logout(request): headers = forget(request) return HTTPFound(location = request.resource_url(request.context), headers = headers) pyramid-1.6/docs/tutorials/wiki/src/views/0000755000076500000240000000000012642137501021420 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/views/CHANGES.txt0000644000076500000240000000002712234375161023233 0ustar michaelstaff000000000000000.1 Initial version pyramid-1.6/docs/tutorials/wiki/src/views/development.ini0000644000076500000240000000227312517346416024457 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/views/MANIFEST.in0000644000076500000240000000020312234375161023154 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki/src/views/production.ini0000644000076500000240000000203612517346416024320 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [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 pyramid-1.6/docs/tutorials/wiki/src/views/README.txt0000644000076500000240000000002312234375161023114 0ustar michaelstaff00000000000000tutorial README pyramid-1.6/docs/tutorials/wiki/src/views/setup.py0000644000076500000240000000217312575217552023147 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', 'docutils', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="tutorial", entry_points="""\ [paste.app_factory] main = tutorial:main """, ) pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/0000755000076500000240000000000012642137501023263 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/__init__.py0000644000076500000240000000104212520062551025366 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid_zodbconn import get_connection from .models import appmaker def root_factory(request): conn = get_connection(request) return appmaker(conn.root()) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=root_factory, settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/models.py0000644000076500000240000000116512234375161025126 0ustar michaelstaff00000000000000from persistent import Persistent from persistent.mapping import PersistentMapping class Wiki(PersistentMapping): __name__ = None __parent__ = None class Page(Persistent): def __init__(self, data): self.data = data def appmaker(zodb_root): if not 'app_root' in zodb_root: app_root = Wiki() frontpage = Page('This is the front page') app_root['FrontPage'] = frontpage frontpage.__name__ = 'FrontPage' frontpage.__parent__ = app_root zodb_root['app_root'] = app_root import transaction transaction.commit() return zodb_root['app_root'] pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/static/0000755000076500000240000000000012642137501024552 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027531 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/static/pyramid.png0000644000076500000240000003114512575217552026743 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/static/theme.css0000644000076500000240000000556612575217552026414 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/static/theme.min.css0000644000076500000240000000442512575217552027167 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/templates/0000755000076500000240000000000012642137501025261 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/templates/edit.pt0000644000076500000240000000527412575217552026575 0ustar michaelstaff00000000000000 ${page.__name__} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Editing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt0000644000076500000240000000562412575217552030030 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/templates/view.pt0000644000076500000240000000501012575217552026606 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)
Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/tests.py0000644000076500000240000001022512517346416025007 0ustar michaelstaff00000000000000import unittest from pyramid import testing class PageModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Page return Page def _makeOne(self, data=u'some data'): return self._getTargetClass()(data=data) def test_constructor(self): instance = self._makeOne() self.assertEqual(instance.data, u'some data') class WikiModelTests(unittest.TestCase): def _getTargetClass(self): from .models import Wiki return Wiki def _makeOne(self): return self._getTargetClass()() def test_it(self): wiki = self._makeOne() self.assertEqual(wiki.__parent__, None) self.assertEqual(wiki.__name__, None) class AppmakerTests(unittest.TestCase): def _callFUT(self, zodb_root): from .models import appmaker return appmaker(zodb_root) def test_it(self): root = {} self._callFUT(root) self.assertEqual(root['app_root']['FrontPage'].data, 'This is the front page') class ViewWikiTests(unittest.TestCase): def test_it(self): from .views import view_wiki context = testing.DummyResource() request = testing.DummyRequest() response = view_wiki(context, request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import view_page return view_page(context, request) def test_it(self): wiki = testing.DummyResource() wiki['IDoExist'] = testing.DummyResource() context = testing.DummyResource(data='Hello CruelWorld IDoExist') context.__parent__ = wiki context.__name__ = 'thepage' request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/thepage/edit_page') class AddPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import add_page return add_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() request.subpath = ['AnotherPage'] info = self._callFUT(context, request) self.assertEqual(info['page'].data,'') self.assertEqual( info['save_url'], request.resource_url(context, 'add_page', 'AnotherPage')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.subpath = ['AnotherPage'] self._callFUT(context, request) page = context['AnotherPage'] self.assertEqual(page.data, 'Hello yo!') self.assertEqual(page.__name__, 'AnotherPage') self.assertEqual(page.__parent__, context) class EditPageTests(unittest.TestCase): def _callFUT(self, context, request): from .views import edit_page return edit_page(context, request) def test_it_notsubmitted(self): context = testing.DummyResource() request = testing.DummyRequest() info = self._callFUT(context, request) self.assertEqual(info['page'], context) self.assertEqual(info['save_url'], request.resource_url(context, 'edit_page')) def test_it_submitted(self): context = testing.DummyResource() request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) response = self._callFUT(context, request) self.assertEqual(response.location, 'http://example.com/') self.assertEqual(context.data, 'Hello yo!') pyramid-1.6/docs/tutorials/wiki/src/views/tutorial/views.py0000644000076500000240000000415612517346416025010 0ustar michaelstaff00000000000000from docutils.core import publish_parts import re from pyramid.httpexceptions import HTTPFound from pyramid.view import view_config from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(context='.models.Wiki') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) @view_config(context='.models.Page', renderer='templates/view.pt') def view_page(context, request): wiki = context.__parent__ def check(match): word = match.group(1) if word in wiki: page = wiki[word] view_url = request.resource_url(page) return '%s' % (view_url, word) else: add_url = request.application_url + '/add_page/' + word return '%s' % (add_url, word) content = publish_parts(context.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') return dict(page = context, content = content, edit_url = edit_url) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt') def add_page(context, request): pagename = request.subpath[0] if 'form.submitted' in request.params: body = request.params['body'] page = Page(body) page.__name__ = pagename page.__parent__ = context context[pagename] = page return HTTPFound(location = request.resource_url(page)) save_url = request.resource_url(context, 'add_page', pagename) page = Page('') page.__name__ = pagename page.__parent__ = context return dict(page = page, save_url = save_url) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt') def edit_page(context, request): if 'form.submitted' in request.params: context.data = request.params['body'] return HTTPFound(location = request.resource_url(context)) return dict(page=context, save_url=request.resource_url(context, 'edit_page')) pyramid-1.6/docs/tutorials/wiki/tests.rst0000644000076500000240000000571612575217552021413 0ustar michaelstaff00000000000000============ Adding Tests ============ We will now add tests for the models and the views and a few functional tests in ``tests.py``. Tests ensure that an application works, and that it continues to work when changes are made in the future. Test the models =============== We write tests for the ``model`` classes and the ``appmaker``. Changing ``tests.py``, we'll write a separate test class for each ``model`` class, and we'll write a test class for the ``appmaker``. To do so, we'll retain the ``tutorial.tests.ViewTests`` class that was generated as part of the ``zodb`` scaffold. We'll add three test classes: one for the ``Page`` model named ``PageModelTests``, one for the ``Wiki`` model named ``WikiModelTests``, and one for the appmaker named ``AppmakerTests``. Test the views ============== We'll modify our ``tests.py`` file, adding tests for each view function we added previously. As a result, we'll *delete* the ``ViewTests`` class that the ``zodb`` scaffold provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views. Functional tests ================ We'll test the whole application, covering security aspects that are not tested in the unit tests, like logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. View the results of all our edits to ``tests.py`` ================================================= Open the ``tutorial/tests.py`` module, and edit it such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests.py :linenos: :language: python Running the tests ================= We can run these tests by using ``setup.py test`` in the same way we did in :ref:`running_tests`. However, first we must edit our ``setup.py`` to include a dependency on WebTest, which we've used in our ``tests.py``. Change the ``requires`` list in ``setup.py`` to include ``WebTest``. .. literalinclude:: src/tests/setup.py :linenos: :language: python :lines: 11-22 :emphasize-lines: 11 After we've added a dependency on WebTest in ``setup.py``, we need to run ``setup.py develop`` to get WebTest installed into our virtualenv. Assuming our shell's current working directory is the "tutorial" distribution directory: On UNIX: .. code-block:: text $ $VENV/bin/python setup.py develop On Windows: .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop Once that command has completed successfully, we can run the tests themselves: On UNIX: .. code-block:: text $ $VENV/bin/python setup.py test -q On Windows: .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q The expected result should look like the following: .. code-block:: text ......... ---------------------------------------------------------------------- Ran 23 tests in 1.653s OK pyramid-1.6/docs/tutorials/wiki2/0000755000076500000240000000000012642137501017556 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/authorization.rst0000644000076500000240000003371512575217552023233 0ustar michaelstaff00000000000000.. _wiki2_adding_authorization: ==================== Adding authorization ==================== :app:`Pyramid` provides facilities for :term:`authentication` and :term:`authorization`. We'll make use of both features to provide security to our application. Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki. We'll change that to allow only people who are members of a *group* named ``group:editors`` to add and edit wiki pages but we'll continue allowing anyone with access to the server to view pages. We will also add a login page and a logout link on all the pages. The login page will be shown when a user is denied access to any of the views that require permission, instead of a default "403 Forbidden" page. We will implement the access control with the following steps: * Add users and groups (``security.py``, a new module). * Add an :term:`ACL` (``models.py`` and ``__init__.py``). * Add an :term:`authentication policy` and an :term:`authorization policy` (``__init__.py``). * Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). Then we will add the login and logout feature: * Add routes for /login and /logout (``__init__.py``). * Add ``login`` and ``logout`` views (``views.py``). * Add a login template (``login.pt``). * Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). * Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``). Access control -------------- Add users and groups ~~~~~~~~~~~~~~~~~~~~ Create a new ``tutorial/tutorial/security.py`` module with the following content: .. literalinclude:: src/authorization/tutorial/security.py :linenos: :language: python The ``groupfinder`` function accepts a userid and a request and returns one of these values: - If the userid exists in the system, it will return a sequence of group identifiers (or an empty sequence if the user isn't a member of any groups). - If the userid *does not* exist in the system, it will return ``None``. For example, ``groupfinder('editor', request )`` returns ``['group:editor']``, ``groupfinder('viewer', request)`` returns ``[]``, and ``groupfinder('admin', request)`` returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy` "callback" that will provide the :term:`principal` or principals for a user. In a production system, user and group data will most often come from a database, but here we use "dummy" data to represent user and groups sources. Add an ACL ~~~~~~~~~~ Open ``tutorial/tutorial/models.py`` and add the following import statement at the head: .. literalinclude:: src/authorization/tutorial/models.py :lines: 1-4 :linenos: :language: python Add the following class definition at the end: .. literalinclude:: src/authorization/tutorial/models.py :lines: 33-37 :linenos: :lineno-start: 33 :language: python We import :data:`~pyramid.security.Allow`, an action that means that permission is allowed, and :data:`~pyramid.security.Everyone`, a special :term:`principal` that is associated to all requests. Both are used in the :term:`ACE` entries that make up the ACL. The ACL is a list that needs to be named `__acl__` and be an attribute of a class. We define an :term:`ACL` with two :term:`ACE` entries: the first entry allows any user the `view` permission. The second entry allows the ``group:editors`` principal the `edit` permission. The ``RootFactory`` class that contains the ACL is a :term:`root factory`. We need to associate it to our :app:`Pyramid` application, so the ACL is provided to each view in the :term:`context` of the request as the ``context`` attribute. Open ``tutorial/tutorial/__init__.py`` and add a ``root_factory`` parameter to our :term:`Configurator` constructor, that points to the class we created above: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 24-25 :linenos: :emphasize-lines: 2 :lineno-start: 16 :language: python Only the highlighted line needs to be added. We are now providing the ACL to the application. See :ref:`assigning_acls` for more information about what an :term:`ACL` represents. .. note:: Although we don't use the functionality here, the ``factory`` used to create route contexts may differ per-route as opposed to globally. See the ``factory`` argument to :meth:`pyramid.config.Configurator.add_route` for more info. Add authentication and authorization policies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/__init__.py`` and add the highlighted import statements: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 1-7 :linenos: :emphasize-lines: 2-3,7 :language: python Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 21-27 :linenos: :lineno-start: 21 :emphasize-lines: 1-3,6-7 :language: python Only the highlighted lines need to be added. We are enabling an ``AuthTktAuthenticationPolicy``, which is based in an auth ticket that may be included in the request. We are also enabling an ``ACLAuthorizationPolicy``, which uses an ACL to determine the *allow* or *deny* outcome for a view. Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string representing an encryption key used by the "authentication ticket" machinery represented by this policy: it is required. The ``callback`` is the ``groupfinder()`` function that we created before. Add permission declarations ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/views.py`` and add a ``permission='edit'`` parameter to the ``@view_config`` decorators for ``add_page()`` and ``edit_page()``: .. literalinclude:: src/authorization/tutorial/views.py :lines: 60-61 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 75-76 :emphasize-lines: 1-2 :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. The result is that only users who possess the ``edit`` permission at the time of the request may invoke those two views. Add a ``permission='view'`` parameter to the ``@view_config`` decorator for ``view_wiki()`` and ``view_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py :lines: 30-31 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 36-37 :emphasize-lines: 1-2 :language: python Only the highlighted lines, along with their preceding commas, need to be edited and added. This allows anyone to invoke these two views. We are done with the changes needed to control access. The changes that follow will add the login and logout feature. Login, logout ------------- Add routes for /login and /logout ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Go back to ``tutorial/tutorial/__init__.py`` and add these two routes as highlighted: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 30-33 :emphasize-lines: 2-3 :language: python .. note:: The preceding lines must be added *before* the following ``view_page`` route definition: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 33 :language: python This is because ``view_page``'s route definition uses a catch-all "replacement marker" ``/{pagename}`` (see :ref:`route_pattern_syntax`) which will catch any route that was not already caught by any route listed above it in ``__init__.py``. Hence, for ``login`` and ``logout`` views to have the opportunity of being matched (or "caught"), they must be above ``/{pagename}``. Add login and logout views ~~~~~~~~~~~~~~~~~~~~~~~~~~ We'll add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. We'll also add a ``logout`` view callable to our application and provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. Add the following import statements to the head of ``tutorial/tutorial/views.py``: .. literalinclude:: src/authorization/tutorial/views.py :lines: 9-19 :emphasize-lines: 1-11 :language: python All the highlighted lines need to be added or edited. :meth:`~pyramid.view.forbidden_view_config` will be used to customize the default 403 Forbidden page. :meth:`~pyramid.security.remember` and :meth:`~pyramid.security.forget` help to create and expire an auth ticket cookie. Now add the ``login`` and ``logout`` views at the end of the file: .. literalinclude:: src/authorization/tutorial/views.py :lines: 91-123 :language: python ``login()`` has two decorators: - a ``@view_config`` decorator which associates it with the ``login`` route and makes it visible when we visit ``/login``, - a ``@forbidden_view_config`` decorator which turns it into a :term:`forbidden view`. ``login()`` will be invoked when a user tries to execute a view callable for which they lack authorization. For example, if a user has not logged in and tries to add or edit a Wiki page, they will be shown the login form before being allowed to continue. The order of these two :term:`view configuration` decorators is unimportant. ``logout()`` is decorated with a ``@view_config`` decorator which associates it with the ``logout`` route. It will be invoked when we visit ``/logout``. Add the ``login.pt`` Template ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create ``tutorial/tutorial/templates/login.pt`` with the following content: .. literalinclude:: src/authorization/tutorial/templates/login.pt :language: html The above template is referenced in the login view that we just added in ``views.py``. Return a ``logged_in`` flag to the renderer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/views.py`` again. Add a ``logged_in`` parameter to the return value of ``view_page()``, ``edit_page()``, and ``add_page()`` as follows: .. literalinclude:: src/authorization/tutorial/views.py :lines: 57-58 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 72-73 :emphasize-lines: 1-2 :language: python .. literalinclude:: src/authorization/tutorial/views.py :lines: 85-89 :emphasize-lines: 3-4 :language: python Only the highlighted lines need to be added or edited. The :meth:`pyramid.request.Request.authenticated_userid` will be ``None`` if the user is not authenticated, or a userid if the user is authenticated. Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Open ``tutorial/tutorial/templates/edit.pt`` and ``tutorial/tutorial/templates/view.pt`` and add the following code as indicated by the highlighted lines. .. literalinclude:: src/authorization/tutorial/templates/edit.pt :lines: 34-38 :emphasize-lines: 3-5 :language: html The attribute ``tal:condition="logged_in"`` will make the element be included when ``logged_in`` is any user id. The link will invoke the logout view. The above element will not be included if ``logged_in`` is ``None``, such as when a user is not authenticated. Reviewing our changes --------------------- Our ``tutorial/tutorial/__init__.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/__init__.py :linenos: :emphasize-lines: 2-3,7,21-23,25-27,31-32 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/models.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/models.py :linenos: :emphasize-lines: 1-4,33-37 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/views.py`` will look like this when we're done: .. literalinclude:: src/authorization/tutorial/views.py :linenos: :emphasize-lines: 9-11,14-19,25,31,37,58,61,73,76,88,91-117,119-123 :language: python Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/templates/edit.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: :emphasize-lines: 36-38 :language: html Only the highlighted lines need to be added or edited. Our ``tutorial/tutorial/templates/view.pt`` template will look like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: :emphasize-lines: 36-38 :language: html Only the highlighted lines need to be added or edited. Viewing the application in a browser ------------------------------------ We can finally examine our application in a browser (See :ref:`wiki2-start-the-application`). Launch a browser and visit each of the following URLs, checking that the result is as expected: - http://localhost:6543/ invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` page object. It is executable by any user. - http://localhost:6543/FrontPage invokes the ``view_page`` view of the ``FrontPage`` page object. - http://localhost:6543/FrontPage/edit_page invokes the edit view for the FrontPage object. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. - http://localhost:6543/add_page/SomePageName invokes the add view for a page. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the credentials with the username ``editor``, password ``editor`` will display the edit page form. - After logging in (as a result of hitting an edit or add page and submitting the login form with the ``editor`` credentials), we'll see a Logout link in the upper right hand corner. When we click it, we're logged out, and redirected back to the front page. pyramid-1.6/docs/tutorials/wiki2/background.rst0000644000076500000240000000123512575217552022442 0ustar michaelstaff00000000000000========== Background ========== This version of the :app:`Pyramid` wiki tutorial presents a :app:`Pyramid` application that uses technologies which will be familiar to someone with SQL database experience. It uses :term:`SQLAlchemy` as a persistence mechanism and :term:`url dispatch` to map URLs to code. It can also be followed by people without any prior Python web framework experience. To code along with this tutorial, the developer will need a UNIX machine with development tools (Mac OS X with XCode, any Linux or BSD variant, etc) *or* a Windows system of any kind. .. note:: This tutorial runs on both Python 2 and 3 without modification. Have fun! pyramid-1.6/docs/tutorials/wiki2/basiclayout.rst0000644000076500000240000002345312621241570022635 0ustar michaelstaff00000000000000============ Basic Layout ============ The starter files generated by the ``alchemy`` scaffold are very basic, but they provide a good orientation for the high-level patterns common to most :term:`URL dispatch`-based :app:`Pyramid` projects. Application configuration with ``__init__.py`` ---------------------------------------------- A directory on disk can be turned into a Python :term:`package` by containing an ``__init__.py`` file. Even if empty, this marks a directory as a Python package. We use ``__init__.py`` both as a marker, indicating the directory in which it's contained is a package, and to contain application configuration code. Open ``tutorial/tutorial/__init__.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/__init__.py :linenos: :language: py Let's go over this piece-by-piece. First, we need some imports to support later code: .. literalinclude:: src/basiclayout/tutorial/__init__.py :end-before: main :linenos: :language: py ``__init__.py`` defines a function named ``main``. Here is the entirety of the ``main`` function we've defined in our ``__init__.py``: .. literalinclude:: src/basiclayout/tutorial/__init__.py :pyobject: main :linenos: :language: py When you invoke the ``pserve development.ini`` command, the ``main`` function above is executed. It accepts some settings and returns a :term:`WSGI` application. (See :ref:`startup_chapter` for more about ``pserve``.) The main function first creates a :term:`SQLAlchemy` database engine using :func:`sqlalchemy.engine_from_config` from the ``sqlalchemy.`` prefixed settings in the ``development.ini`` file's ``[app:main]`` section. This will be a URI (something like ``sqlite://``): .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 13 :language: py ``main`` then initializes our SQLAlchemy session object, passing it the engine: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 14 :language: py ``main`` subsequently initializes our SQLAlchemy declarative ``Base`` object, assigning the engine we created to the ``bind`` attribute of it's ``metadata`` object. This allows table definitions done imperatively (instead of declaratively, via a class statement) to work. We won't use any such tables in our application, but if you add one later, long after you've forgotten about this tutorial, you won't be left scratching your head when it doesn't work. .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 15 :language: py The next step of ``main`` is to construct a :term:`Configurator` object: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 16 :language: py ``settings`` is passed to the Configurator as a keyword argument with the dictionary values passed as the ``**settings`` argument. This will be a dictionary of settings parsed from the ``.ini`` file, which contains deployment-related values such as ``pyramid.reload_templates``, ``db_string``, etc. Next, include :term:`Chameleon` templating bindings so that we can use renderers with the ``.pt`` extension within our project. .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 17 :language: py ``main`` now calls :meth:`pyramid.config.Configurator.add_static_view` with two arguments: ``static`` (the name), and ``static`` (the path): .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 18 :language: py This registers a static resource view which will match any URL that starts with the prefix ``/static`` (by virtue of the first argument to ``add_static_view``). This will serve up static resources for us from within the ``static`` directory of our ``tutorial`` package, in this case, via ``http://localhost:6543/static/`` and below (by virtue of the second argument to ``add_static_view``). With this declaration, we're saying that any URL that starts with ``/static`` should go to the static view; any remainder of its path (e.g. the ``/foo`` in ``/static/foo``) will be used to compose a path to a static file resource, such as a CSS file. Using the configurator ``main`` also registers a :term:`route configuration` via the :meth:`pyramid.config.Configurator.add_route` method that will be used when the URL is ``/``: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 19 :language: py Since this route has a ``pattern`` equaling ``/`` it is the route that will be matched when the URL ``/`` is visited, e.g. ``http://localhost:6543/``. ``main`` next calls the ``scan`` method of the configurator (:meth:`pyramid.config.Configurator.scan`), which will recursively scan our ``tutorial`` package, looking for ``@view_config`` (and other special) decorators. When it finds a ``@view_config`` decorator, a view configuration will be registered, which will allow one of our application URLs to be mapped to some code. .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 20 :language: py Finally, ``main`` is finished configuring things, so it uses the :meth:`pyramid.config.Configurator.make_wsgi_app` method to return a :term:`WSGI` application: .. literalinclude:: src/basiclayout/tutorial/__init__.py :lines: 21 :language: py View declarations via ``views.py`` ---------------------------------- The main function of a web framework is mapping each URL pattern to code (a :term:`view callable`) that is executed when the requested URL matches the corresponding :term:`route`. Our application uses the :meth:`pyramid.view.view_config` decorator to perform this mapping. Open ``tutorial/tutorial/views.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/views.py :linenos: :language: py The important part here is that the ``@view_config`` decorator associates the function it decorates (``my_view``) with a :term:`view configuration`, consisting of: * a ``route_name`` (``home``) * a ``renderer``, which is a template from the ``templates`` subdirectory of the package. When the pattern associated with the ``home`` view is matched during a request, ``my_view()`` will be executed. ``my_view()`` returns a dictionary; the renderer will use the ``templates/mytemplate.pt`` template to create a response based on the values in the dictionary. Note that ``my_view()`` accepts a single argument named ``request``. This is the standard call signature for a Pyramid :term:`view callable`. Remember in our ``__init__.py`` when we executed the :meth:`pyramid.config.Configurator.scan` method ``config.scan()``? The purpose of calling the scan method was to find and process this ``@view_config`` decorator in order to create a view configuration within our application. Without being processed by ``scan``, the decorator effectively does nothing. ``@view_config`` is inert without being detected via a :term:`scan`. The sample ``my_view()`` created by the scaffold uses a ``try:`` and ``except:`` clause to detect if there is a problem accessing the project database and provide an alternate error response. That response will include the text shown at the end of the file, which will be displayed in the browser to inform the user about possible actions to take to solve the problem. Content Models with ``models.py`` --------------------------------- In a SQLAlchemy-based application, a *model* object is an object composed by querying the SQL database. The ``models.py`` file is where the ``alchemy`` scaffold put the classes that implement our models. Open ``tutorial/tutorial/models.py``. It should already contain the following: .. literalinclude:: src/basiclayout/tutorial/models.py :linenos: :language: py Let's examine this in detail. First, we need some imports to support later code: .. literalinclude:: src/basiclayout/tutorial/models.py :end-before: DBSession :linenos: :language: py Next we set up a SQLAlchemy ``DBSession`` object: .. literalinclude:: src/basiclayout/tutorial/models.py :lines: 17 :language: py ``scoped_session`` and ``sessionmaker`` are standard SQLAlchemy helpers. ``scoped_session`` allows us to access our database connection globally. ``sessionmaker`` creates a database session object. We pass to ``sessionmaker`` the ``extension=ZopeTransactionExtension()`` extension option in order to allow the system to automatically manage database transactions. With ``ZopeTransactionExtension`` activated, our application will automatically issue a transaction commit after every request unless an exception is raised, in which case the transaction will be aborted. We also need to create a declarative ``Base`` object to use as a base class for our model: .. literalinclude:: src/basiclayout/tutorial/models.py :lines: 18 :language: py Our model classes will inherit from this ``Base`` class so they can be associated with our particular database connection. To give a simple example of a model class, we define one named ``MyModel``: .. literalinclude:: src/basiclayout/tutorial/models.py :pyobject: MyModel :linenos: :language: py Our example model does not require an ``__init__`` method because SQLAlchemy supplies for us a default constructor if one is not already present, which accepts keyword arguments of the same name as that of the mapped attributes. .. note:: Example usage of MyModel: .. code-block:: python johnny = MyModel(name="John Doe", value=10) The ``MyModel`` class has a ``__tablename__`` attribute. This informs SQLAlchemy which table to use to store the data representing instances of this class. The Index import and the Index object creation is not required for this tutorial, and will be removed in the next step. That's about all there is to it regarding models, views, and initialization code in our stock application. pyramid-1.6/docs/tutorials/wiki2/definingmodels.rst0000644000076500000240000001274612575217552023323 0ustar michaelstaff00000000000000========================= Defining the Domain Model ========================= The first change we'll make to our stock ``pcreate``-generated application will be to define a :term:`domain model` constructor representing a wiki page. We'll do this inside our ``models.py`` file. Edit ``models.py`` ------------------ .. note:: There is nothing special about the filename ``models.py``. A project may have many models throughout its codebase in arbitrarily named files. Files implementing models often have ``model`` in their filenames or they may live in a Python subpackage of your application package named ``models``, but this is only by convention. Open ``tutorial/tutorial/models.py`` file and edit it to look like the following: .. literalinclude:: src/models/tutorial/models.py :linenos: :language: py :emphasize-lines: 20-22,24,25 The highlighted lines are the ones that need to be changed, as well as removing lines that reference ``Index``. The first thing we've done is remove the stock ``MyModel`` class from the generated ``models.py`` file. The ``MyModel`` class is only a sample and we're not going to use it. Then, we added a ``Page`` class. Because this is a SQLAlchemy application, this class inherits from an instance of :func:`sqlalchemy.ext.declarative.declarative_base`. .. literalinclude:: src/models/tutorial/models.py :pyobject: Page :linenos: :language: python As you can see, our ``Page`` class has a class level attribute ``__tablename__`` which equals the string ``'pages'``. This means that SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our ``Page`` class will also have class-level attributes named ``id``, ``name`` and ``data`` (all instances of :class:`sqlalchemy.schema.Column`). These will map to columns in the ``pages`` table. The ``id`` attribute will be the primary key in the table. The ``name`` attribute will be a text attribute, each value of which needs to be unique within the column. The ``data`` attribute is a text attribute that will hold the body of each page. Changing ``scripts/initializedb.py`` ------------------------------------ We haven't looked at the details of this file yet, but within the ``scripts`` directory of your ``tutorial`` package is a file named ``initializedb.py``. Code in this file is executed whenever we run the ``initialize_tutorial_db`` command, as we did in the installation step of this tutorial. Since we've changed our model, we need to make changes to our ``initializedb.py`` script. In particular, we'll replace our import of ``MyModel`` with one of ``Page`` and we'll change the very end of the script to create a ``Page`` rather than a ``MyModel`` and add it to our ``DBSession``. Open ``tutorial/tutorial/scripts/initializedb.py`` and edit it to look like the following: .. literalinclude:: src/models/tutorial/scripts/initializedb.py :linenos: :language: python :emphasize-lines: 14,31,36 Only the highlighted lines need to be changed, as well as removing the lines referencing ``pyramid.scripts.common`` and ``options`` under the ``main`` function. Installing the project and re-initializing the database ------------------------------------------------------- Because our model has changed, in order to reinitialize the database, we need to rerun the ``initialize_tutorial_db`` command to pick up the changes you've made to both the models.py file and to the initializedb.py file. See :ref:`initialize_db_wiki2` for instructions. Success will look something like this:: 2015-05-24 15:34:14,542 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2015-05-24 15:34:14,542 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2015-05-24 15:34:14,543 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("pages") 2015-05-24 15:34:14,544 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2015-05-24 15:34:14,544 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE TABLE pages ( id INTEGER NOT NULL, name TEXT, data TEXT, PRIMARY KEY (id), UNIQUE (name) ) 2015-05-24 15:34:14,545 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2015-05-24 15:34:14,546 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2015-05-24 15:34:14,548 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) 2015-05-24 15:34:14,549 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO pages (name, data) VALUES (?, ?) 2015-05-24 15:34:14,549 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('FrontPage', 'This is the front page') 2015-05-24 15:34:14,550 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT View the application in a browser --------------------------------- We can't. At this point, our system is in a "non-runnable" state; we'll need to change view-related files in the next chapter to be able to start the application successfully. If you try to start the application (See :ref:`wiki2-start-the-application`), you'll wind up with a Python traceback on your console that ends with this exception: .. code-block:: text ImportError: cannot import name MyModel This will also happen if you attempt to run the tests. pyramid-1.6/docs/tutorials/wiki2/definingviews.rst0000644000076500000240000003574612575217552023202 0ustar michaelstaff00000000000000============== Defining Views ============== A :term:`view callable` in a :app:`Pyramid` application is typically a simple Python function that accepts a single parameter named :term:`request`. A view callable is assumed to return a :term:`response` object. The request object has a dictionary as an attribute named ``matchdict``. A ``matchdict`` maps the placeholders in the matching URL ``pattern`` to the substrings of the path in the :term:`request` URL. For instance, if a call to :meth:`pyramid.config.Configurator.add_route` has the pattern ``/{one}/{two}``, and a user visits ``http://example.com/foo/bar``, our pattern would be matched against ``/foo/bar`` and the ``matchdict`` would look like ``{'one':'foo', 'two':'bar'}``. Declaring Dependencies in Our ``setup.py`` File =============================================== The view code in our application will depend on a package which is not a dependency of the original "tutorial" application. The original "tutorial" application was generated by the ``pcreate`` command; it doesn't know about our custom application requirements. We need to add a dependency on the ``docutils`` package to our ``tutorial`` package's ``setup.py`` file by assigning this dependency to the ``requires`` parameter in the ``setup()`` function. Open ``tutorial/setup.py`` and edit it to look like the following: .. literalinclude:: src/views/setup.py :linenos: :emphasize-lines: 20 :language: python Only the highlighted line needs to be added. Running ``setup.py develop`` ============================ Since a new software dependency was added, you will need to run ``python setup.py develop`` again inside the root of the ``tutorial`` package to obtain and register the newly added dependency distribution. Make sure your current working directory is the root of the project (the directory in which ``setup.py`` lives) and execute the following command. On UNIX: .. code-block:: text $ cd tutorial $ $VENV/bin/python setup.py develop On Windows: .. code-block:: text c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop Success executing this command will end with a line to the console something like:: Finished processing dependencies for tutorial==0.0 Adding view functions in ``views.py`` ===================================== It's time for a major change. Open ``tutorial/tutorial/views.py`` and edit it to look like the following: .. literalinclude:: src/views/tutorial/views.py :linenos: :language: python :emphasize-lines: 1-7,14,16-72 The highlighted lines need to be added or edited. We added some imports and created a regular expression to find "WikiWords". We got rid of the ``my_view`` view function and its decorator that was added when we originally rendered the ``alchemy`` scaffold. It was only an example and isn't relevant to our application. Then we added four :term:`view callable` functions to our ``views.py`` module: * ``view_wiki()`` - Displays the wiki itself. It will answer on the root URL. * ``view_page()`` - Displays an individual page. * ``add_page()`` - Allows the user to add a page. * ``edit_page()`` - Allows the user to edit a page. We'll describe each one briefly in the following sections. .. note:: There is nothing special about the filename ``views.py``. A project may have many view callables throughout its codebase in arbitrarily named files. Files implementing view callables often have ``view`` in their filenames (or may live in a Python subpackage of your application package named ``views``), but this is only by convention. The ``view_wiki`` view function ------------------------------- Following is the code for the ``view_wiki`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 20-24 :lineno-start: 20 :linenos: :language: python ``view_wiki()`` is the :term:`default view` that gets called when a request is made to the root URL of our wiki. It always redirects to an URL which represents the path to our "FrontPage". The ``view_wiki`` view callable always redirects to the URL of a Page resource named "FrontPage". To do so, it returns an instance of the :class:`pyramid.httpexceptions.HTTPFound` class (instances of which implement the :class:`pyramid.interfaces.IResponse` interface, like :class:`pyramid.response.Response` does). It uses the :meth:`pyramid.request.Request.route_url` API to construct an URL to the ``FrontPage`` page (i.e., ``http://localhost:6543/FrontPage``), and uses it as the "location" of the ``HTTPFound`` response, forming an HTTP redirect. The ``view_page`` view function ------------------------------- Here is the code for the ``view_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 25-45 :lineno-start: 25 :linenos: :language: python ``view_page()`` is used to display a single page of our wiki. It renders the :term:`reStructuredText` body of a page (stored as the ``data`` attribute of a ``Page`` model object) as HTML. Then it substitutes an HTML anchor for each *WikiWord* reference in the rendered HTML using a compiled regular expression. The curried function named ``check`` is used as the first argument to ``wikiwords.sub``, indicating that it should be called to provide a value for each WikiWord match found in the content. If the wiki already contains a page with the matched WikiWord name, ``check()`` generates a view link to be used as the substitution value and returns it. If the wiki does not already contain a page with the matched WikiWord name, ``check()`` generates an "add" link as the substitution value and returns it. As a result, the ``content`` variable is now a fully formed bit of HTML containing various view and add links for WikiWords based on the content of our current page object. We then generate an edit URL because it's easier to do here than in the template, and we return a dictionary with a number of arguments. The fact that ``view_page()`` returns a dictionary (as opposed to a :term:`response` object) is a cue to :app:`Pyramid` that it should try to use a :term:`renderer` associated with the view configuration to render a response. In our case, the renderer used will be the ``templates/view.pt`` template, as indicated in the ``@view_config`` decorator that is applied to ``view_page()``. The ``add_page`` view function ------------------------------ Here is the code for the ``add_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 47-58 :lineno-start: 47 :linenos: :language: python ``add_page()`` is invoked when a user clicks on a *WikiWord* which isn't yet represented as a page in the system. The ``check`` function within the ``view_page`` view generates URLs to this view. ``add_page()`` also acts as a handler for the form that is generated when we want to add a page object. The ``matchdict`` attribute of the request passed to the ``add_page()`` view will have the values we need to construct URLs and find model objects. The ``matchdict`` will have a ``'pagename'`` key that matches the name of the page we'd like to add. If our add view is invoked via, e.g., ``http://localhost:6543/add_page/SomeName``, the value for ``'pagename'`` in the ``matchdict`` will be ``'SomeName'``. If the view execution *is* a result of a form submission (i.e., the expression ``'form.submitted' in request.params`` is ``True``), we grab the page body from the form data, create a Page object with this page body and the name taken from ``matchdict['pagename']``, and save it into the database using ``DBSession.add``. We then redirect back to the ``view_page`` view for the newly created page. If the view execution is *not* a result of a form submission (i.e., the expression ``'form.submitted' in request.params`` is ``False``), the view callable renders a template. To do so, it generates a ``save_url`` which the template uses as the form post URL during rendering. We're lazy here, so we're going to use the same template (``templates/edit.pt``) for the add view as well as the page edit view. To do so we create a dummy Page object in order to satisfy the edit form's desire to have *some* page object exposed as ``page``. :app:`Pyramid` will render the template associated with this view to a response. The ``edit_page`` view function ------------------------------- Here is the code for the ``edit_page`` view function and its decorator: .. literalinclude:: src/views/tutorial/views.py :lines: 60-72 :lineno-start: 60 :linenos: :language: python ``edit_page()`` is invoked when a user clicks the "Edit this Page" button on the view form. It renders an edit form but it also acts as the handler for the form it renders. The ``matchdict`` attribute of the request passed to the ``edit_page`` view will have a ``'pagename'`` key matching the name of the page the user wants to edit. If the view execution *is* a result of a form submission (i.e., the expression ``'form.submitted' in request.params`` is ``True``), the view grabs the ``body`` element of the request parameters and sets it as the ``data`` attribute of the page object. It then redirects to the ``view_page`` view of the wiki page. If the view execution is *not* a result of a form submission (i.e., the expression ``'form.submitted' in request.params`` is ``False``), the view simply renders the edit form, passing the page object and a ``save_url`` which will be used as the action of the generated form. Adding templates ================ The ``view_page``, ``add_page`` and ``edit_page`` views that we've added reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT` template. These templates will live in the ``templates`` directory of our tutorial package. Chameleon templates must have a ``.pt`` extension to be recognized as such. The ``view.pt`` template ------------------------ Create ``tutorial/tutorial/templates/view.pt`` and add the following content: .. literalinclude:: src/views/tutorial/templates/view.pt :linenos: :language: html This template is used by ``view_page()`` for displaying a single wiki page. It includes: - A ``div`` element that is replaced with the ``content`` value provided by the view (lines 36-38). ``content`` contains HTML, so the ``structure`` keyword is used to prevent escaping it (i.e., changing ">" to ">", etc.) - A link that points at the "edit" URL which invokes the ``edit_page`` view for the page being viewed (lines 40-42). The ``edit.pt`` template ------------------------ Create ``tutorial/tutorial/templates/edit.pt`` and add the following content: .. literalinclude:: src/views/tutorial/templates/edit.pt :linenos: :language: html This template is used by ``add_page()`` and ``edit_page()`` for adding and editing a wiki page. It displays a page containing a form that includes: - A 10 row by 60 column ``textarea`` field named ``body`` that is filled with any existing page data when it is rendered (line 45). - A submit button that has the name ``form.submitted`` (line 48). The form POSTs back to the ``save_url`` argument supplied by the view (line 43). The view will use the ``body`` and ``form.submitted`` values. .. note:: Our templates use a ``request`` object that none of our tutorial views return in their dictionary. ``request`` is one of several names that are available "by default" in a template when a template renderer is used. See :ref:`renderer_system_values` for information about other names that are available by default when a template is used as a renderer. Static Assets ------------- Our templates name static assets, including CSS and images. We don't need to create these files within our package's ``static`` directory because they were provided at the time we created the project. As an example, the CSS file will be accessed via ``http://localhost:6543/static/theme.css`` by virtue of the call to the ``add_static_view`` directive we've made in the ``__init__.py`` file. Any number and type of static assets can be placed in this directory (or subdirectories) and are just referred to by URL or by using the convenience method ``static_url``, e.g., ``request.static_url(':static/foo.css')`` within templates. Adding Routes to ``__init__.py`` ================================ The ``__init__.py`` file contains :meth:`pyramid.config.Configurator.add_route` calls which serve to add routes to our application. First, we’ll get rid of the existing route created by the template using the name ``'home'``. It’s only an example and isn’t relevant to our application. We then need to add four calls to ``add_route``. Note that the *ordering* of these declarations is very important. ``route`` declarations are matched in the order they're found in the ``__init__.py`` file. #. Add a declaration which maps the pattern ``/`` (signifying the root URL) to the route named ``view_wiki``. It maps to our ``view_wiki`` view callable by virtue of the ``@view_config`` attached to the ``view_wiki`` view function indicating ``route_name='view_wiki'``. #. Add a declaration which maps the pattern ``/{pagename}`` to the route named ``view_page``. This is the regular view for a page. It maps to our ``view_page`` view callable by virtue of the ``@view_config`` attached to the ``view_page`` view function indicating ``route_name='view_page'``. #. Add a declaration which maps the pattern ``/add_page/{pagename}`` to the route named ``add_page``. This is the add view for a new page. It maps to our ``add_page`` view callable by virtue of the ``@view_config`` attached to the ``add_page`` view function indicating ``route_name='add_page'``. #. Add a declaration which maps the pattern ``/{pagename}/edit_page`` to the route named ``edit_page``. This is the edit view for a page. It maps to our ``edit_page`` view callable by virtue of the ``@view_config`` attached to the ``edit_page`` view function indicating ``route_name='edit_page'``. As a result of our edits, the ``__init__.py`` file should look something like: .. literalinclude:: src/views/tutorial/__init__.py :linenos: :emphasize-lines: 19-22 :language: python The highlighted lines are the ones that need to be added or edited. Viewing the application in a browser ==================================== We can finally examine our application in a browser (See :ref:`wiki2-start-the-application`). Launch a browser and visit each of the following URLs, checking that the result is as expected: - http://localhost:6543/ invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` page object. - http://localhost:6543/FrontPage invokes the ``view_page`` view of the front page object. - http://localhost:6543/FrontPage/edit_page invokes the edit view for the front page object. - http://localhost:6543/add_page/SomePageName invokes the add view for a page. - To generate an error, visit http://localhost:6543/foobars/edit_page which will generate a ``NoResultFound: No row was found for one()`` error. You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. pyramid-1.6/docs/tutorials/wiki2/design.rst0000644000076500000240000001761512575217552021605 0ustar michaelstaff00000000000000========== Design ========== Following is a quick overview of the design of our wiki application, to help us understand the changes that we will be making as we work through the tutorial. Overall ------- We choose to use :term:`reStructuredText` markup in the wiki text. Translation from reStructuredText to HTML is provided by the widely used ``docutils`` Python module. We will add this module in the dependency list on the project ``setup.py`` file. Models ------ We'll be using a SQLite database to hold our wiki data, and we'll be using :term:`SQLAlchemy` to access the data in this database. Within the database, we define a single table named `pages`, whose elements will store the wiki pages. There are two columns: `name` and `data`. URLs like ``/PageName`` will try to find an element in the table that has a corresponding name. To add a page to the wiki, a new row is created and the text is stored in `data`. A page named ``FrontPage`` containing the text *This is the front page*, will be created when the storage is initialized, and will be used as the wiki home page. Views ----- There will be three views to handle the normal operations of adding, editing, and viewing wiki pages, plus one view for the wiki front page. Two templates will be used, one for viewing, and one for both adding and editing wiki pages. The default templating systems in :app:`Pyramid` are :term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, which is an XML-based templating language. Mako is a non-XML-based templating language. Because we had to pick one, we chose Chameleon for this tutorial. Security -------- We'll eventually be adding security to our application. The components we'll use to do this are below. - USERS, a dictionary mapping :term:`userids ` to their corresponding passwords. - GROUPS, a dictionary mapping :term:`userids ` to a list of groups to which they belong. - ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS. It will be provided in a new ``security.py`` file. - An :term:`ACL` is attached to the root :term:`resource`. Each row below details an :term:`ACE`: +----------+----------------+----------------+ | Action | Principal | Permission | +==========+================+================+ | Allow | Everyone | View | +----------+----------------+----------------+ | Allow | group:editors | Edit | +----------+----------------+----------------+ - Permission declarations are added to the views to assert the security policies as each request is handled. Two additional views and one template will handle the login and logout tasks. Summary ------- The URL, actions, template and permission associated to each view are listed in the following table: +----------------------+-----------------------+-------------+------------+------------+ | URL | Action | View | Template | Permission | | | | | | | +======================+=======================+=============+============+============+ | / | Redirect to | view_wiki | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+------------+------------+ | /PageName | Display existing | view_page | view.pt | view | | | page [2]_ | [1]_ | | | | | | | | | | | | | | | | | | | | | +----------------------+-----------------------+-------------+------------+------------+ | /PageName/edit_page | Display edit form | edit_page | edit.pt | edit | | | with existing | | | | | | content. | | | | | | | | | | | | If the form was | | | | | | submitted, redirect | | | | | | to /PageName | | | | +----------------------+-----------------------+-------------+------------+------------+ | /add_page/PageName | Create the page | add_page | edit.pt | edit | | | *PageName* in | | | | | | storage, display | | | | | | the edit form | | | | | | without content. | | | | | | | | | | | | If the form was | | | | | | submitted, | | | | | | redirect to | | | | | | /PageName | | | | +----------------------+-----------------------+-------------+------------+------------+ | /login | Display login form, | login | login.pt | | | | Forbidden [3]_ | | | | | | | | | | | | If the form was | | | | | | submitted, | | | | | | authenticate. | | | | | | | | | | | | - If authentication | | | | | | succeeds, | | | | | | redirect to the | | | | | | page that we | | | | | | came from. | | | | | | | | | | | | - If authentication | | | | | | fails, display | | | | | | login form with | | | | | | "login failed" | | | | | | message. | | | | | | | | | | +----------------------+-----------------------+-------------+------------+------------+ | /logout | Redirect to | logout | | | | | /FrontPage | | | | +----------------------+-----------------------+-------------+------------+------------+ .. [1] This is the default view for a Page context when there is no view name. .. [2] Pyramid will return a default 404 Not Found page if the page *PageName* does not exist yet. .. [3] ``pyramid.exceptions.Forbidden`` is reached when a user tries to invoke a view that is not authorized by the authorization policy. pyramid-1.6/docs/tutorials/wiki2/distributing.rst0000644000076500000240000000246312575217552023036 0ustar michaelstaff00000000000000============================= Distributing Your Application ============================= Once your application works properly, you can create a "tarball" from it by using the ``setup.py sdist`` command. The following commands assume your current working directory is the ``tutorial`` package we've created and that the parent directory of the ``tutorial`` package is a virtualenv representing a :app:`Pyramid` environment. On UNIX: .. code-block:: text $ $VENV/bin/python setup.py sdist On Windows: .. code-block:: text c:\pyramidtut> %VENV%\Scripts\python setup.py sdist The output of such a command will be something like: .. code-block:: text running sdist # .. more output .. creating dist tar -cf dist/tutorial-0.0.tar tutorial-0.0 gzip -f9 dist/tutorial-0.0.tar removing 'tutorial-0.0' (and everything under it) Note that this command creates a tarball in the "dist" subdirectory named ``tutorial-0.0.tar.gz``. You can send this file to your friends to show them your cool new application. They should be able to install it by pointing the ``easy_install`` command directly at it. Or you can upload it to `PyPI `_ and share it with the rest of the world, where it can be downloaded via ``easy_install`` remotely like any other package people download from PyPI. pyramid-1.6/docs/tutorials/wiki2/index.rst0000644000076500000240000000143712575217552021436 0ustar michaelstaff00000000000000.. _bfg_sql_wiki_tutorial: SQLAlchemy + URL Dispatch Wiki Tutorial ======================================= This tutorial introduces a :term:`SQLAlchemy` and :term:`url dispatch`-based :app:`Pyramid` application to a developer familiar with Python. When the tutorial is finished, the developer will have created a basic Wiki application with authentication. For cut and paste purposes, the source code for all stages of this tutorial can be browsed on GitHub at `docs/tutorials/wiki2/src `_, which corresponds to the same location if you have Pyramid sources. .. toctree:: :maxdepth: 2 background design installation basiclayout definingmodels definingviews authorization tests distributing pyramid-1.6/docs/tutorials/wiki2/installation.rst0000644000076500000240000002651512575217552023034 0ustar michaelstaff00000000000000============ Installation ============ Before you begin ================ This tutorial assumes that you have already followed the steps in :ref:`installing_chapter`, except **do not create a virtualenv or install Pyramid**. Thereby you will satisfy the following requirements. * Python interpreter is installed on your operating system * :term:`setuptools` or :term:`distribute` is installed * :term:`virtualenv` is installed Create directory to contain the project --------------------------------------- We need a workspace for our project files. On UNIX ^^^^^^^ .. code-block:: text $ mkdir ~/pyramidtut On Windows ^^^^^^^^^^ .. code-block:: text c:\> mkdir pyramidtut Create and use a virtual Python environment ------------------------------------------- Next let's create a `virtualenv` workspace for our project. We will use the `VENV` environment variable instead of the absolute path of the virtual environment. On UNIX ^^^^^^^ .. code-block:: text $ export VENV=~/pyramidtut $ virtualenv $VENV New python executable in /home/foo/env/bin/python Installing setuptools.............done. On Windows ^^^^^^^^^^ .. code-block:: text c:\> set VENV=c:\pyramidtut Versions of Python use different paths, so you will need to adjust the path to the command for your Python version. Python 2.7: .. code-block:: text c:\> c:\Python27\Scripts\virtualenv %VENV% Python 3.2: .. code-block:: text c:\> c:\Python32\Scripts\virtualenv %VENV% Install Pyramid into the virtual Python environment --------------------------------------------------- On UNIX ^^^^^^^ .. code-block:: text $ $VENV/bin/easy_install pyramid On Windows ^^^^^^^^^^ .. code-block:: text c:\> %VENV%\Scripts\easy_install pyramid Install SQLite3 and its development packages -------------------------------------------- If you used a package manager to install your Python or if you compiled your Python from source, then you must install SQLite3 and its development packages. If you downloaded your Python as an installer from python.org, then you already have it installed and can proceed to the next section :ref:`sql_making_a_project`.. If you need to install the SQLite3 packages, then, for example, using the Debian system and apt-get, the command would be the following: .. code-block:: text $ sudo apt-get install libsqlite3-dev Change directory to your virtual Python environment --------------------------------------------------- Change directory to the ``pyramidtut`` directory. On UNIX ^^^^^^^ .. code-block:: text $ cd pyramidtut On Windows ^^^^^^^^^^ .. code-block:: text c:\> cd pyramidtut .. _sql_making_a_project: Making a project ================ Your next step is to create a project. For this tutorial we will use the :term:`scaffold` named ``alchemy`` which generates an application that uses :term:`SQLAlchemy` and :term:`URL dispatch`. :app:`Pyramid` supplies a variety of scaffolds to generate sample projects. We will use `pcreate`—a script that comes with Pyramid to quickly and easily generate scaffolds, usually with a single command—to create the scaffold for our project. By passing `alchemy` into the `pcreate` command, the script creates the files needed to use SQLAlchemy. By passing in our application name `tutorial`, the script inserts that application name into all the required files. For example, `pcreate` creates the ``initialize_tutorial_db`` in the ``pyramidtut/bin`` directory. The below instructions assume your current working directory is "pyramidtut". On UNIX ------- .. code-block:: text $ $VENV/bin/pcreate -s alchemy tutorial On Windows ---------- .. code-block:: text c:\pyramidtut> %VENV%\Scripts\pcreate -s alchemy tutorial .. note:: If you are using Windows, the ``alchemy`` scaffold may not deal gracefully with installation into a location that contains spaces in the path. If you experience startup problems, try putting both the virtualenv and the project into directories that do not contain spaces in their paths. .. _installing_project_in_dev_mode: Installing the project in development mode ========================================== In order to do development on the project easily, you must "register" the project as a development egg in your workspace using the ``setup.py develop`` command. In order to do so, cd to the `tutorial` directory you created in :ref:`sql_making_a_project`, and run the ``setup.py develop`` command using the virtualenv Python interpreter. On UNIX ------- .. code-block:: text $ cd tutorial $ $VENV/bin/python setup.py develop On Windows ---------- .. code-block:: text c:\pyramidtut> cd tutorial c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop The console will show `setup.py` checking for packages and installing missing packages. Success executing this command will show a line like the following:: Finished processing dependencies for tutorial==0.0 .. _sql_running_tests: Run the tests ============= After you've installed the project in development mode, you may run the tests for the project. On UNIX ------- .. code-block:: text $ $VENV/bin/python setup.py test -q On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q For a successful test run, you should see output that ends like this:: . ---------------------------------------------------------------------- Ran 1 test in 0.094s OK Expose test coverage information ================================ You can run the ``nosetests`` command to see test coverage information. This runs the tests in the same way that ``setup.py test`` does but provides additional "coverage" information, exposing which lines of your project are "covered" (or not covered) by the tests. To get this functionality working, we'll need to install the ``nose`` and ``coverage`` packages into our ``virtualenv``: On UNIX ------- .. code-block:: text $ $VENV/bin/easy_install nose coverage On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\easy_install nose coverage Once ``nose`` and ``coverage`` are installed, we can actually run the coverage tests. On UNIX ------- .. code-block:: text $ $VENV/bin/nosetests --cover-package=tutorial --cover-erase --with-coverage On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\nosetests --cover-package=tutorial \ --cover-erase --with-coverage If successful, you will see output something like this:: . Name Stmts Miss Cover Missing --------------------------------------------------- tutorial.py 13 9 31% 13-21 tutorial/models.py 12 0 100% tutorial/scripts.py 0 0 100% tutorial/views.py 11 0 100% --------------------------------------------------- TOTAL 36 9 75% ---------------------------------------------------------------------- Ran 2 tests in 0.643s OK Looks like our package doesn't quite have 100% test coverage. .. _initialize_db_wiki2: Initializing the database ========================= We need to use the ``initialize_tutorial_db`` :term:`console script` to initialize our database. Type the following command, making sure you are still in the ``tutorial`` directory (the directory with a ``development.ini`` in it): On UNIX ------- .. code-block:: text $ $VENV/bin/initialize_tutorial_db development.ini On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\initialize_tutorial_db development.ini The output to your console should be something like this:: 2015-05-23 16:49:49,609 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2015-05-23 16:49:49,609 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1192][MainThread] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1193][MainThread] () 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] PRAGMA table_info("models") 2015-05-23 16:49:49,610 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2015-05-23 16:49:49,612 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE TABLE models ( id INTEGER NOT NULL, name TEXT, value INTEGER, PRIMARY KEY (id) ) 2015-05-23 16:49:49,612 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] CREATE UNIQUE INDEX my_index ON models (name) 2015-05-23 16:49:49,613 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] () 2015-05-23 16:49:49,614 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT 2015-05-23 16:49:49,616 INFO [sqlalchemy.engine.base.Engine:646][MainThread] BEGIN (implicit) 2015-05-23 16:49:49,617 INFO [sqlalchemy.engine.base.Engine:1097][MainThread] INSERT INTO models (name, value) VALUES (?, ?) 2015-05-23 16:49:49,617 INFO [sqlalchemy.engine.base.Engine:1100][MainThread] ('one', 1) 2015-05-23 16:49:49,618 INFO [sqlalchemy.engine.base.Engine:686][MainThread] COMMIT Success! You should now have a ``tutorial.sqlite`` file in your current working directory. This will be a SQLite database with a single table defined in it (``models``). .. _wiki2-start-the-application: Start the application ===================== Start the application. On UNIX ------- .. code-block:: text $ $VENV/bin/pserve development.ini --reload On Windows ---------- .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\pserve development.ini --reload .. note:: Your OS firewall, if any, may pop up a dialog asking for authorization to allow python to accept incoming network connections. If successful, you will see something like this on your console:: Starting subprocess with file monitor Starting server in PID 8966. Starting HTTP server on http://0.0.0.0:6543 This means the server is ready to accept requests. Visit the application in a browser ================================== In a browser, visit `http://localhost:6543/ `_. You will see the generated application's default page. One thing you'll notice is the "debug toolbar" icon on right hand side of the page. You can read more about the purpose of the icon at :ref:`debug_toolbar`. It allows you to get information about your application while you develop. Decisions the ``alchemy`` scaffold has made for you ================================================================= Creating a project using the ``alchemy`` scaffold makes the following assumptions: - you are willing to use :term:`SQLAlchemy` as a database access tool - you are willing to use :term:`URL dispatch` to map URLs to code - you want to use ``ZopeTransactionExtension`` and ``pyramid_tm`` to scope sessions to requests .. note:: :app:`Pyramid` supports any persistent storage mechanism (e.g., object database or filesystem files). It also supports an additional mechanism to map URLs to code (:term:`traversal`). However, for the purposes of this tutorial, we'll only be using URL dispatch and SQLAlchemy. pyramid-1.6/docs/tutorials/wiki2/src/0000755000076500000240000000000012642137501020345 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/0000755000076500000240000000000012642137501023245 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/CHANGES.txt0000644000076500000240000000003412234375161025056 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki2/src/authorization/development.ini0000644000076500000240000000256612517346416026311 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/authorization/MANIFEST.in0000644000076500000240000000020312234375161025001 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki2/src/authorization/production.ini0000644000076500000240000000202412517346416026142 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/authorization/README.txt0000644000076500000240000000035112520062551024737 0ustar michaelstaff00000000000000tutorial README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_tutorial_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/docs/tutorials/wiki2/src/authorization/setup.py0000644000076500000240000000230412520062551024753 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', 'docutils', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.scripts.initializedb:main """, ) pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/0000755000076500000240000000000012642137501025110 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/__init__.py0000644000076500000240000000250012520062551027213 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config from tutorial.security import groupfinder from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(settings=settings, root_factory='tutorial.models.RootFactory') config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/models.py0000644000076500000240000000145312520062551026745 0ustar michaelstaff00000000000000from pyramid.security import ( Allow, Everyone, ) from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ __tablename__ = 'pages' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) data = Column(Text) class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] def __init__(self, request): pass pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/scripts/0000755000076500000240000000000012642137501026577 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py0000644000076500000240000000001212234375161030704 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py0000644000076500000240000000146212520062551031620 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, Page, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/security.py0000644000076500000240000000030012234375161027325 0ustar michaelstaff00000000000000USERS = {'editor':'editor', 'viewer':'viewer'} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/static/0000755000076500000240000000000012642137501026377 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552031356 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid.png0000644000076500000240000003114512575217552030570 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.css0000644000076500000240000000556612575217552030241 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css0000644000076500000240000000442512575217552031014 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/templates/0000755000076500000240000000000012642137501027106 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt0000644000076500000240000000551012575217552030413 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Editing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt0000644000076500000240000000552212575217552030601 0ustar michaelstaff00000000000000 Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Login

pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt0000644000076500000240000000563212575217552031654 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt0000644000076500000240000000523512575217552030444 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Logout

Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/tests.py0000644000076500000240000001140112520062551026616 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing def _initTestingDB(): from sqlalchemy import create_engine from tutorial.models import ( DBSession, Page, Base ) engine = create_engine('sqlite://') Base.metadata.create_all(engine) DBSession.configure(bind=engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) return DBSession def _registerRoutes(config): config.add_route('view_page', '{pagename}') config.add_route('edit_page', '{pagename}/edit_page') config.add_route('add_page', 'add_page/{pagename}') class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() self.session = _initTestingDB() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_wiki return view_wiki(request) def test_it(self): _registerRoutes(self.config) request = testing.DummyRequest() response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_page return view_page(request) def test_it(self): from tutorial.models import Page request = testing.DummyRequest() request.matchdict['pagename'] = 'IDoExist' page = Page(name='IDoExist', data='Hello CruelWorld IDoExist') self.session.add(page) _registerRoutes(self.config) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/IDoExist/edit_page') class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import add_page return add_page(request) def test_it_notsubmitted(self): _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'AnotherPage'} info = self._callFUT(request) self.assertEqual(info['page'].data,'') self.assertEqual(info['save_url'], 'http://example.com/add_page/AnotherPage') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'AnotherPage'} self._callFUT(request) page = self.session.query(Page).filter_by(name='AnotherPage').one() self.assertEqual(page.data, 'Hello yo!') class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import edit_page return edit_page(request) def test_it_notsubmitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual(info['save_url'], 'http://example.com/abc/edit_page') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/abc') self.assertEqual(page.data, 'Hello yo!') pyramid-1.6/docs/tutorials/wiki2/src/authorization/tutorial/views.py0000644000076500000240000001006212520062551026613 0ustar michaelstaff00000000000000import re from docutils.core import publish_parts from pyramid.httpexceptions import ( HTTPFound, HTTPNotFound, ) from pyramid.view import ( view_config, forbidden_view_config, ) from pyramid.security import ( remember, forget, ) from .security import USERS from .models import ( DBSession, Page, ) # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(route_name='view_wiki', permission='view') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) @view_config(route_name='view_page', renderer='templates/view.pt', permission='view') def view_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).first() if page is None: return HTTPNotFound('No such page') def check(match): word = match.group(1) exists = DBSession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) return '%s' % (view_url, word) else: add_url = request.route_url('add_page', pagename=word) return '%s' % (add_url, word) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) return dict(page=page, content=content, edit_url=edit_url, logged_in=request.authenticated_userid) @view_config(route_name='add_page', renderer='templates/edit.pt', permission='edit') def add_page(request): pagename = request.matchdict['pagename'] if 'form.submitted' in request.params: body = request.params['body'] page = Page(name=pagename, data=body) DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) save_url = request.route_url('add_page', pagename=pagename) page = Page(name='', data='') return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @view_config(route_name='edit_page', renderer='templates/edit.pt', permission='edit') def edit_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).one() if 'form.submitted' in request.params: page.data = request.params['body'] DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) return dict( page=page, save_url=request.route_url('edit_page', pagename=pagename), logged_in=request.authenticated_userid ) @view_config(route_name='login', renderer='templates/login.pt') @forbidden_view_config(renderer='templates/login.pt') def login(request): login_url = request.route_url('login') referrer = request.url if referrer == login_url: referrer = '/' # never use the login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location = came_from, headers = headers) message = 'Failed login' return dict( message = message, url = request.application_url + '/login', came_from = came_from, login = login, password = password, ) @view_config(route_name='logout') def logout(request): headers = forget(request) return HTTPFound(location = request.route_url('view_wiki'), headers = headers) pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/0000755000076500000240000000000012642137501022664 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/CHANGES.txt0000644000076500000240000000003412234375161024475 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/development.ini0000644000076500000240000000256612517346416025730 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/MANIFEST.in0000644000076500000240000000020312234375161024420 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/production.ini0000644000076500000240000000226412517346416025567 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/README.txt0000644000076500000240000000035112520062551024356 0ustar michaelstaff00000000000000tutorial README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_tutorial_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/setup.py0000644000076500000240000000226412520062551024377 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.scripts.initializedb:main """, ) pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/0000755000076500000240000000000012642137501024527 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py0000644000076500000240000000113512520062551026635 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/models.py0000644000076500000240000000112012606630333026357 0ustar michaelstaff00000000000000from sqlalchemy import ( Column, Integer, Text, Index, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) value = Column(Integer) Index('my_index', MyModel.name, unique=True, mysql_length=255) pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/0000755000076500000240000000000012642137501026216 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py0000644000076500000240000000001212234375161030323 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py0000644000076500000240000000143412517346416031251 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, MyModel, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=1) DBSession.add(model) pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/static/0000755000076500000240000000000012642137501026016 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552030775 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid.png0000644000076500000240000003114512575217552030207 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.css0000644000076500000240000000556612575217552027660 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css0000644000076500000240000000442512575217552030433 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/0000755000076500000240000000000012642137501026525 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt0000644000076500000240000000563212575217552031273 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py0000644000076500000240000000155712517346416026263 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_it(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') pyramid-1.6/docs/tutorials/wiki2/src/basiclayout/tutorial/views.py0000644000076500000240000000210012517346416026237 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'tutorial'} conn_err_msg = """\ Pyramid is having a problem using your SQL database. The problem might be caused by one of the following things: 1. You may need to run the "initialize_tutorial_db" script to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """ pyramid-1.6/docs/tutorials/wiki2/src/models/0000755000076500000240000000000012642137501021630 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/models/CHANGES.txt0000644000076500000240000000003412234375161023441 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki2/src/models/development.ini0000644000076500000240000000256612517346416024674 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/models/MANIFEST.in0000644000076500000240000000020312234375161023364 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki2/src/models/production.ini0000644000076500000240000000202412517346416024525 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/models/README.txt0000644000076500000240000000035112520062551023322 0ustar michaelstaff00000000000000tutorial README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_tutorial_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/docs/tutorials/wiki2/src/models/setup.py0000644000076500000240000000226412520062551023343 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.scripts.initializedb:main """, ) pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/0000755000076500000240000000000012642137501023473 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/__init__.py0000644000076500000240000000113512520062551025601 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/models.py0000644000076500000240000000110312520062551025320 0ustar michaelstaff00000000000000from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ __tablename__ = 'pages' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) data = Column(Text) pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/scripts/0000755000076500000240000000000012642137501025162 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py0000644000076500000240000000001212234375161027267 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py0000644000076500000240000000146212520062551030203 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, Page, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/static/0000755000076500000240000000000012642137501024762 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027741 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/static/pyramid.png0000644000076500000240000003114512575217552027153 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/static/theme.css0000644000076500000240000000556612575217552026624 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css0000644000076500000240000000442512575217552027377 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/templates/0000755000076500000240000000000012642137501025471 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt0000644000076500000240000000563212575217552030237 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/tests.py0000644000076500000240000000155712517346416025227 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing from .models import DBSession class TestMyView(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_it(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'tutorial') pyramid-1.6/docs/tutorials/wiki2/src/models/tutorial/views.py0000644000076500000240000000210012517346416025203 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': 'tutorial'} conn_err_msg = """\ Pyramid is having a problem using your SQL database. The problem might be caused by one of the following things: 1. You may need to run the "initialize_tutorial_db" script to initialize your database tables. Check your virtual environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """ pyramid-1.6/docs/tutorials/wiki2/src/tests/0000755000076500000240000000000012642137501021507 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/tests/CHANGES.txt0000644000076500000240000000003412234375161023320 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki2/src/tests/development.ini0000644000076500000240000000256612517346416024553 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/tests/MANIFEST.in0000644000076500000240000000020312234375161023243 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki2/src/tests/production.ini0000644000076500000240000000202412517346416024404 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/tests/README.txt0000644000076500000240000000035112520062551023201 0ustar michaelstaff00000000000000tutorial README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_tutorial_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/docs/tutorials/wiki2/src/tests/setup.py0000644000076500000240000000233612520062551023222 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', 'docutils', 'WebTest', # add this ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.scripts.initializedb:main """, ) pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/0000755000076500000240000000000012642137501023352 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/__init__.py0000644000076500000240000000250012520062551025455 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy from sqlalchemy import engine_from_config from tutorial.security import groupfinder from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine authn_policy = AuthTktAuthenticationPolicy( 'sosecret', callback=groupfinder, hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config = Configurator(settings=settings, root_factory='tutorial.models.RootFactory') config.include('pyramid_chameleon') config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('login', '/login') config.add_route('logout', '/logout') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/models.py0000644000076500000240000000145312520062551025207 0ustar michaelstaff00000000000000from pyramid.security import ( Allow, Everyone, ) from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ __tablename__ = 'pages' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) data = Column(Text) class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit') ] def __init__(self, request): pass pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/scripts/0000755000076500000240000000000012642137501025041 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py0000644000076500000240000000001212234375161027146 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py0000644000076500000240000000146212520062551030062 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, Page, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/security.py0000644000076500000240000000030012234375161025567 0ustar michaelstaff00000000000000USERS = {'editor':'editor', 'viewer':'viewer'} GROUPS = {'editor':['group:editors']} def groupfinder(userid, request): if userid in USERS: return GROUPS.get(userid, []) pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/static/0000755000076500000240000000000012642137501024641 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027620 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/static/pyramid.png0000644000076500000240000003114512575217552027032 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/static/theme.css0000644000076500000240000000556612575217552026503 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css0000644000076500000240000000442512575217552027256 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/templates/0000755000076500000240000000000012642137501025350 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt0000644000076500000240000000557112575217552026664 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Editing Page Name Goes Here

You can return to the FrontPage.

Logout

pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt0000644000076500000240000000366412520062551027033 0ustar michaelstaff00000000000000 Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)
pyramid
Login


pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt0000644000076500000240000000563212575217552030116 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt0000644000076500000240000000531612575217552026706 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)
Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

Logout

pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/tests.py0000644000076500000240000001757612520062551025103 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing def _initTestingDB(): from sqlalchemy import create_engine from tutorial.models import ( DBSession, Page, Base ) engine = create_engine('sqlite://') Base.metadata.create_all(engine) DBSession.configure(bind=engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) return DBSession def _registerRoutes(config): config.add_route('view_page', '{pagename}') config.add_route('edit_page', '{pagename}/edit_page') config.add_route('add_page', 'add_page/{pagename}') class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request): from tutorial.views import view_wiki return view_wiki(request) def test_it(self): _registerRoutes(self.config) request = testing.DummyRequest() response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_page return view_page(request) def test_it(self): from tutorial.models import Page request = testing.DummyRequest() request.matchdict['pagename'] = 'IDoExist' page = Page(name='IDoExist', data='Hello CruelWorld IDoExist') self.session.add(page) _registerRoutes(self.config) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/IDoExist/edit_page') class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import add_page return add_page(request) def test_it_notsubmitted(self): _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'AnotherPage'} info = self._callFUT(request) self.assertEqual(info['page'].data,'') self.assertEqual(info['save_url'], 'http://example.com/add_page/AnotherPage') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'AnotherPage'} self._callFUT(request) page = self.session.query(Page).filter_by(name='AnotherPage').one() self.assertEqual(page.data, 'Hello yo!') class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import edit_page return edit_page(request) def test_it_notsubmitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual(info['save_url'], 'http://example.com/abc/edit_page') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/abc') self.assertEqual(page.data, 'Hello yo!') class FunctionalTests(unittest.TestCase): viewer_login = '/login?login=viewer&password=viewer' \ '&came_from=FrontPage&form.submitted=Login' viewer_wrong_login = '/login?login=viewer&password=incorrect' \ '&came_from=FrontPage&form.submitted=Login' editor_login = '/login?login=editor&password=editor' \ '&came_from=FrontPage&form.submitted=Login' def setUp(self): from tutorial import main settings = { 'sqlalchemy.url': 'sqlite://'} app = main({}, **settings) from webtest import TestApp self.testapp = TestApp(app) _initTestingDB() def tearDown(self): del self.testapp from tutorial.models import DBSession DBSession.remove() def test_root(self): res = self.testapp.get('/', status=302) self.assertEqual(res.location, 'http://localhost/FrontPage') def test_FrontPage(self): res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'FrontPage' in res.body) def test_unexisting_page(self): self.testapp.get('/SomePage', status=404) def test_successful_log_in(self): res = self.testapp.get(self.viewer_login, status=302) self.assertEqual(res.location, 'http://localhost/FrontPage') def test_failed_log_in(self): res = self.testapp.get(self.viewer_wrong_login, status=200) self.assertTrue(b'login' in res.body) def test_logout_link_present_when_logged_in(self): self.testapp.get(self.viewer_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'Logout' in res.body) def test_logout_link_not_present_after_logged_out(self): self.testapp.get(self.viewer_login, status=302) self.testapp.get('/FrontPage', status=200) res = self.testapp.get('/logout', status=302) self.assertTrue(b'Logout' not in res.body) def test_anonymous_user_cannot_edit(self): res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Login' in res.body) def test_anonymous_user_cannot_add(self): res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Login' in res.body) def test_viewer_user_cannot_edit(self): self.testapp.get(self.viewer_login, status=302) res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Login' in res.body) def test_viewer_user_cannot_add(self): self.testapp.get(self.viewer_login, status=302) res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Login' in res.body) def test_editors_member_user_can_edit(self): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/FrontPage/edit_page', status=200) self.assertTrue(b'Editing' in res.body) def test_editors_member_user_can_add(self): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/add_page/NewPage', status=200) self.assertTrue(b'Editing' in res.body) def test_editors_member_user_can_view(self): self.testapp.get(self.editor_login, status=302) res = self.testapp.get('/FrontPage', status=200) self.assertTrue(b'FrontPage' in res.body) pyramid-1.6/docs/tutorials/wiki2/src/tests/tutorial/views.py0000644000076500000240000001005512520062551025057 0ustar michaelstaff00000000000000import re from docutils.core import publish_parts from pyramid.httpexceptions import ( HTTPFound, HTTPNotFound, ) from pyramid.view import ( view_config, forbidden_view_config, ) from pyramid.security import ( remember, forget, ) from .security import USERS from .models import ( DBSession, Page, ) # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(route_name='view_wiki', permission='view') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) @view_config(route_name='view_page', renderer='templates/view.pt', permission='view') def view_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).first() if page is None: return HTTPNotFound('No such page') def check(match): word = match.group(1) exists = DBSession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) return '%s' % (view_url, word) else: add_url = request.route_url('add_page', pagename=word) return '%s' % (add_url, word) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) return dict(page=page, content=content, edit_url=edit_url, logged_in=request.authenticated_userid) @view_config(route_name='add_page', renderer='templates/edit.pt', permission='edit') def add_page(request): pagename = request.matchdict['pagename'] if 'form.submitted' in request.params: body = request.params['body'] page = Page(name=pagename, data=body) DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) save_url = request.route_url('add_page', pagename=pagename) page = Page(name='', data='') return dict(page=page, save_url=save_url, logged_in=request.authenticated_userid) @view_config(route_name='edit_page', renderer='templates/edit.pt', permission='edit') def edit_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).one() if 'form.submitted' in request.params: page.data = request.params['body'] DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) return dict( page=page, save_url=request.route_url('edit_page', pagename=pagename), logged_in=request.authenticated_userid ) @view_config(route_name='login', renderer='templates/login.pt') @forbidden_view_config(renderer='templates/login.pt') def login(request): login_url = request.route_url('login') referrer = request.url if referrer == login_url: referrer = '/' # never use the login form itself as came_from came_from = request.params.get('came_from', referrer) message = '' login = '' password = '' if 'form.submitted' in request.params: login = request.params['login'] password = request.params['password'] if USERS.get(login) == password: headers = remember(request, login) return HTTPFound(location = came_from, headers = headers) message = 'Failed login' return dict( message = message, url = request.application_url + '/login', came_from = came_from, login = login, password = password, ) @view_config(route_name='logout') def logout(request): headers = forget(request) return HTTPFound(location = request.route_url('view_wiki'), headers = headers) pyramid-1.6/docs/tutorials/wiki2/src/views/0000755000076500000240000000000012642137501021502 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/views/CHANGES.txt0000644000076500000240000000003412234375161023313 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/docs/tutorials/wiki2/src/views/development.ini0000644000076500000240000000256612517346416024546 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:tutorial pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_tutorial] level = DEBUG handlers = qualname = tutorial [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/views/MANIFEST.in0000644000076500000240000000020312234375161023236 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include tutorial *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/docs/tutorials/wiki2/src/views/production.ini0000644000076500000240000000202412517346416024377 0ustar michaelstaff00000000000000[app:main] use = egg:tutorial pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, tutorial, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_tutorial] level = WARN handlers = qualname = tutorial [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [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 pyramid-1.6/docs/tutorials/wiki2/src/views/README.txt0000644000076500000240000000035112520062551023174 0ustar michaelstaff00000000000000tutorial README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_tutorial_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/docs/tutorials/wiki2/src/views/setup.py0000644000076500000240000000230412520062551023210 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', 'docutils', ] setup(name='tutorial', version='0.0', description='tutorial', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='tutorial', install_requires=requires, entry_points="""\ [paste.app_factory] main = tutorial:main [console_scripts] initialize_tutorial_db = tutorial.scripts.initializedb:main """, ) pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/0000755000076500000240000000000012642137501023345 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/__init__.py0000644000076500000240000000140712520062551025455 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('view_wiki', '/') config.add_route('view_page', '/{pagename}') config.add_route('add_page', '/add_page/{pagename}') config.add_route('edit_page', '/{pagename}/edit_page') config.scan() return config.make_wsgi_app() pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/models.py0000644000076500000240000000110312520062551025172 0ustar michaelstaff00000000000000from sqlalchemy import ( Column, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class Page(Base): """ The SQLAlchemy declarative model class for a Page object. """ __tablename__ = 'pages' id = Column(Integer, primary_key=True) name = Column(Text, unique=True) data = Column(Text) pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/scripts/0000755000076500000240000000000012642137501025034 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py0000644000076500000240000000001212234375161027141 0ustar michaelstaff00000000000000# package pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py0000644000076500000240000000146212520062551030055 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from ..models import ( DBSession, Page, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s \n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) != 2: usage(argv) config_uri = argv[1] setup_logging(config_uri) settings = get_appsettings(config_uri) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/static/0000755000076500000240000000000012642137501024634 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/static/pyramid-16x16.png0000644000076500000240000000244712575217552027613 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/static/pyramid.png0000644000076500000240000003114512575217552027025 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/static/theme.css0000644000076500000240000000556612575217552026476 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css0000644000076500000240000000451012575217552027244 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/templates/0000755000076500000240000000000012642137501025343 5ustar michaelstaff00000000000000pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt0000644000076500000240000000525712575217552026660 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)

Editing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt0000644000076500000240000000563212575217552030111 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/templates/view.pt0000644000076500000240000000500412575217552026673 0ustar michaelstaff00000000000000 ${page.name} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)
Page text goes here.

Edit this page

Viewing Page Name Goes Here

You can return to the FrontPage.

pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/tests.py0000644000076500000240000001140112520062551025053 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing def _initTestingDB(): from sqlalchemy import create_engine from tutorial.models import ( DBSession, Page, Base ) engine = create_engine('sqlite://') Base.metadata.create_all(engine) DBSession.configure(bind=engine) with transaction.manager: model = Page(name='FrontPage', data='This is the front page') DBSession.add(model) return DBSession def _registerRoutes(config): config.add_route('view_page', '{pagename}') config.add_route('edit_page', '{pagename}/edit_page') config.add_route('add_page', 'add_page/{pagename}') class ViewWikiTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() self.session = _initTestingDB() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_wiki return view_wiki(request) def test_it(self): _registerRoutes(self.config) request = testing.DummyRequest() response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/FrontPage') class ViewPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import view_page return view_page(request) def test_it(self): from tutorial.models import Page request = testing.DummyRequest() request.matchdict['pagename'] = 'IDoExist' page = Page(name='IDoExist', data='Hello CruelWorld IDoExist') self.session.add(page) _registerRoutes(self.config) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual( info['content'], '
\n' '

Hello ' 'CruelWorld ' '' 'IDoExist' '

\n
\n') self.assertEqual(info['edit_url'], 'http://example.com/IDoExist/edit_page') class AddPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import add_page return add_page(request) def test_it_notsubmitted(self): _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'AnotherPage'} info = self._callFUT(request) self.assertEqual(info['page'].data,'') self.assertEqual(info['save_url'], 'http://example.com/add_page/AnotherPage') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'AnotherPage'} self._callFUT(request) page = self.session.query(Page).filter_by(name='AnotherPage').one() self.assertEqual(page.data, 'Hello yo!') class EditPageTests(unittest.TestCase): def setUp(self): self.session = _initTestingDB() self.config = testing.setUp() def tearDown(self): self.session.remove() testing.tearDown() def _callFUT(self, request): from tutorial.views import edit_page return edit_page(request) def test_it_notsubmitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest() request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) info = self._callFUT(request) self.assertEqual(info['page'], page) self.assertEqual(info['save_url'], 'http://example.com/abc/edit_page') def test_it_submitted(self): from tutorial.models import Page _registerRoutes(self.config) request = testing.DummyRequest({'form.submitted':True, 'body':'Hello yo!'}) request.matchdict = {'pagename':'abc'} page = Page(name='abc', data='hello') self.session.add(page) response = self._callFUT(request) self.assertEqual(response.location, 'http://example.com/abc') self.assertEqual(page.data, 'Hello yo!') pyramid-1.6/docs/tutorials/wiki2/src/views/tutorial/views.py0000644000076500000240000000506512575217552025074 0ustar michaelstaff00000000000000import cgi import re from docutils.core import publish_parts from pyramid.httpexceptions import ( HTTPFound, HTTPNotFound, ) from pyramid.view import view_config from .models import ( DBSession, Page, ) # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") @view_config(route_name='view_wiki') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) @view_config(route_name='view_page', renderer='templates/view.pt') def view_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).first() if page is None: return HTTPNotFound('No such page') def check(match): word = match.group(1) exists = DBSession.query(Page).filter_by(name=word).all() if exists: view_url = request.route_url('view_page', pagename=word) return '%s' % (view_url, cgi.escape(word)) else: add_url = request.route_url('add_page', pagename=word) return '%s' % (add_url, cgi.escape(word)) content = publish_parts(page.data, writer_name='html')['html_body'] content = wikiwords.sub(check, content) edit_url = request.route_url('edit_page', pagename=pagename) return dict(page=page, content=content, edit_url=edit_url) @view_config(route_name='add_page', renderer='templates/edit.pt') def add_page(request): pagename = request.matchdict['pagename'] if 'form.submitted' in request.params: body = request.params['body'] page = Page(name=pagename, data=body) DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) save_url = request.route_url('add_page', pagename=pagename) page = Page(name='', data='') return dict(page=page, save_url=save_url) @view_config(route_name='edit_page', renderer='templates/edit.pt') def edit_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).one() if 'form.submitted' in request.params: page.data = request.params['body'] DBSession.add(page) return HTTPFound(location = request.route_url('view_page', pagename=pagename)) return dict( page=page, save_url = request.route_url('edit_page', pagename=pagename), ) pyramid-1.6/docs/tutorials/wiki2/tests.rst0000644000076500000240000000520112575217552021462 0ustar michaelstaff00000000000000============ Adding Tests ============ We will now add tests for the models and the views and a few functional tests in ``tests.py``. Tests ensure that an application works, and that it continues to work when changes are made in the future. Test the models =============== To test the model class ``Page`` we'll add a new ``PageModelTests`` class to our ``tests.py`` file that was generated as part of the ``alchemy`` scaffold. Test the views ============== We'll modify our ``tests.py`` file, adding tests for each view function we added previously. As a result, we'll *delete* the ``ViewTests`` class that the ``alchemy`` scaffold provided, and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, ``view_page``, ``add_page``, and ``edit_page`` views. Functional tests ================ We'll test the whole application, covering security aspects that are not tested in the unit tests, like logging in, logging out, checking that the ``viewer`` user cannot add or edit pages, but the ``editor`` user can, and so on. View the results of all our edits to ``tests.py`` ================================================= Open the ``tutorial/tests.py`` module, and edit it such that it appears as follows: .. literalinclude:: src/tests/tutorial/tests.py :linenos: :language: python Running the tests ================= We can run these tests by using ``setup.py test`` in the same way we did in :ref:`running_tests`. However, first we must edit our ``setup.py`` to include a dependency on WebTest, which we've used in our ``tests.py``. Change the ``requires`` list in ``setup.py`` to include ``WebTest``. .. literalinclude:: src/tests/setup.py :linenos: :language: python :lines: 11-22 :emphasize-lines: 11 After we've added a dependency on WebTest in ``setup.py``, we need to run ``setup.py develop`` to get WebTest installed into our virtualenv. Assuming our shell's current working directory is the "tutorial" distribution directory: On UNIX: .. code-block:: text $ $VENV/bin/python setup.py develop On Windows: .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop Once that command has completed successfully, we can run the tests themselves: On UNIX: .. code-block:: text $ $VENV/bin/python setup.py test -q On Windows: .. code-block:: text c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py test -q The expected result should look like the following: .. code-block:: text ...................... ---------------------------------------------------------------------- Ran 21 tests in 2.700s OK pyramid-1.6/docs/whatsnew-1.0.rst0000644000076500000240000006047512520062551017402 0ustar michaelstaff00000000000000What's New In Pyramid 1.0 ========================= This article explains the new features in Pyramid version 1.0 as compared to its predecessor, :mod:`repoze.bfg` 1.3. It also documents backwards incompatibilities between the two versions and deprecations added to Pyramid 1.0, as well as software dependency changes and notable documentation additions. Major Feature Additions ----------------------- The major feature additions in Pyramid 1.0 are: - New name and branding association with the Pylons Project. - BFG conversion script - Scaffold improvements - Terminology changes - Better platform compatibility and support - Direct built-in support for the Mako templating language. - Built-in support for sessions. - Updated URL dispatch features - Better imperative extensibility - ZCML externalized - Better support for global template variables during rendering - View mappers - Testing system improvements - Authentication support improvements - Documentation improvements New Name and Branding ~~~~~~~~~~~~~~~~~~~~~ The name of ``repoze.bfg`` has been changed to Pyramid. The project is also now a subproject of a new entity, "The Pylons Project". The Pylons Project is the project name for a collection of web-framework-related technologies. Pyramid was the first package in the Pylons Project. Other packages to the collection have been added over time, such as support packages useful for Pylons 1 users as well as ex-Zope users. Pyramid is the successor to both :mod:`repoze.bfg` and :term:`Pylons` version 1. The Pyramid codebase is derived almost entirely from :mod:`repoze.bfg` with some changes made for the sake of Pylons 1 compatibility. Pyramid is technically backwards incompatible with :mod:`repoze.bfg`, as it has a new package name, so older imports from the ``repoze.bfg`` module will fail if you do nothing to your existing :mod:`repoze.bfg` application. However, you won't have to do much to use your existing BFG applications on Pyramid. There's automation which will change most of your import statements and ZCML declarations. See http://docs.pylonsproject.org/projects/pyramid/current/tutorials/bfg/index.html for upgrade instructions. Pylons 1 users will need to do more work to use Pyramid, as Pyramid shares no "DNA" with Pylons. It is hoped that over time documentation and upgrade code will be developed to help Pylons 1 users transition to Pyramid more easily. :mod:`repoze.bfg` version 1.3 will be its last major release. Minor updates will be made for critical bug fixes. Pylons version 1 will continue to see maintenance releases, as well. The Repoze project will continue to exist. Repoze will be able to regain its original focus: bringing Zope technologies to WSGI. The popularity of :mod:`repoze.bfg` as its own web framework hindered this goal. We hope that people are attracted at first by the spirit of cooperation demonstrated by the Pylons Project and the merging of development communities. It takes humility to sacrifice a little sovereignty and work together. The opposite, forking or splintering of projects, is much more common in the open source world. We feel there is a limited amount of oxygen in the space of "top-tier" Python web frameworks and we don’t do the Python community a service by over-crowding. By merging the :mod:`repoze.bfg` and the philosophically-similar Pylons communities, both gain an expanded audience and a stronger chance of future success. BFG Conversion Script ~~~~~~~~~~~~~~~~~~~~~ The ``bfg2pyramid`` conversion script performs a mostly automated conversion of an existing :mod:`repoze.bfg` application to Pyramid. The process is described in "Converting a BFG Application to Pyramid". Scaffold Improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The scaffolds now have much nicer CSS and graphics. - The ``development.ini``, generated by all scaffolds, is now configured to use the WebError interactive exception debugger by default. - All scaffolds have been normalized: each now uses the name ``main`` to represent the function that returns a WSGI application, and each now has roughly the same shape of development.ini style. - All preexisting scaffolds now use "imperative" configuration (``starter``, ``routesalchemy``, ``alchemy``, ``zodb``) instead of ZCML configuration. - The ``pyramid_zodb``, ``routesalchemy`` and ``pyramid_alchemy`` scaffolds now use a default "commit veto" hook when configuring the ``repoze.tm2`` transaction manager in ``development.ini``. This prevents a transaction from being committed when the response status code is within the 400 or 500 ranges. .. seealso:: See also http://docs.repoze.org/tm2/#using-a-commit-veto. Terminology Changes ~~~~~~~~~~~~~~~~~~~ - The Pyramid concept previously known as "model" is now known as "resource". As a result, the following API renames have been made. Backwards compatibility shims for the old names have been left in place in all cases:: pyramid.url.model_url -> pyramid.url.resource_url pyramid.traversal.find_model -> pyramid.url.find_resource pyramid.traversal.model_path -> pyramid.traversal.resource_path pyramid.traversal.model_path_tuple -> pyramid.traversal.resource_path_tuple pyramid.traversal.ModelGraphTraverser -> pyramid.traversal.ResourceTreeTraverser pyramid.config.Configurator.testing_models -> pyramid.config.Configurator.testing_resources pyramid.testing.registerModels -> pyramid.testing.registerResources pyramid.testing.DummyModel -> pyramid.testing.DummyResource - All documentation which previously referred to "model" now refers to "resource". - The ``starter`` scaffold now has a ``resources.py`` module instead of a ``models.py`` module. - Positional argument names of various APIs have been changed from ``model`` to ``resource``. - The Pyramid concept previously known as "resource" is now known as "asset". As a result, the following API changes were made. Backwards compatibility shims have been left in place as necessary:: pyramid.config.Configurator.absolute_resource_spec -> pyramid.config.Configurator.absolute_asset_spec pyramid.config.Configurator.override_resource -> pyramid.config.Configurator.override_asset - The (non-API) module previously known as ``pyramid.resource`` is now known as ``pyramid.asset``. - All docs that previously referred to "resource specification" now refer to "asset specification". - The setting previously known as ``BFG_RELOAD_RESOURCES`` (envvar) or ``reload_resources`` (config file) is now known, respectively, as ``PYRAMID_RELOAD_ASSETS`` and ``reload_assets``. Better Platform Compatibility and Support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We've made Pyramid's test suite pass on both Jython and PyPy. However, Chameleon doesn't work on either, so you'll need to use Mako or Jinja2 templates on these platforms. Sessions ~~~~~~~~ Pyramid now has built-in sessioning support, documented in :ref:`sessions_chapter`. The sessioning implementation is pluggable. It also provides flash messaging and cross-site-scripting prevention features. Using ``request.session`` now returns a (dictionary-like) session object if a :term:`session factory` has been configured. A new argument to the Configurator constructor exists: ``session_factory`` and a new method on the configurator exists: :meth:`pyramid.config.Configurator.set_session_factory`. Mako ~~~~ In addition to Chameleon templating, Pyramid now also provides built-in support for :term:`Mako` templating. See :ref:`available_template_system_bindings` for more information. URL Dispatch ~~~~~~~~~~~~ - URL Dispatch now allows for replacement markers to be located anywhere in the pattern, instead of immediately following a ``/``. - URL Dispatch now uses the form ``{marker}`` to denote a replace marker in the route pattern instead of ``:marker``. The old colon-style marker syntax is still accepted for backwards compatibility. The new format allows a regular expression for that marker location to be used instead of the default ``[^/]+``, for example ``{marker:\d+}`` is now valid to require the marker to be digits. - Addded a new API :func:`pyramid.url.current_route_url`, which computes a URL based on the "current" route (if any) and its matchdict values. - Added a ``paster proute`` command which displays a summary of the routing table. See the narrative documentation section entitled :ref:`displaying_application_routes`. - Added ``debug_routematch`` configuration setting (settable in your ``.ini`` file) that logs matched routes including the matchdict and predicates. - Add a :func:`pyramid.url.route_path` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling :func:`pyramid.url.route_url` with the argument ``_app_url`` equal to the empty string. - Add a :meth:`pyramid.request.Request.route_path` API. This is a convenience method of the request which calls :func:`pyramid.url.route_url`. - Added class vars ``matchdict`` and ``matched_route`` to :class:`pyramid.request.Request`. Each is set to ``None`` when a route isn't matched during a request. ZCML Externalized ~~~~~~~~~~~~~~~~~ - The ``load_zcml`` method of a Configurator has been removed from the Pyramid core. Loading ZCML is now a feature of the :term:`pyramid_zcml` package, which can be downloaded from PyPI. Documentation for the package should be available via http://docs.pylonsproject.org/projects/pyramid_zcml/en/latest/, which describes how to add a configuration statement to your ``main`` block to reobtain this method. You will also need to add an ``install_requires`` dependency upon the ``pyramid_zcml`` distribution to your ``setup.py`` file. - The "Declarative Configuration" narrative chapter has been removed (it was moved to the ``pyramid_zcml`` package). - Most references to ZCML in narrative chapters have been removed or redirected to ``pyramid_zcml`` locations. - The ``starter_zcml`` paster scaffold has been moved to the ``pyramid_zcml`` package. Imperative Two-Phase Configuration ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To support application extensibility, the :app:`Pyramid` :term:`Configurator`, by default, now detects configuration conflicts and allows you to include configuration imperatively from other packages or modules. It also, by default, performs configuration in two separate phases. This allows you to ignore relative configuration statement ordering in some circumstances. See :ref:`advconfig_narr` for more information. The :meth:`pyramid.config.Configurator.add_directive` allows framework extenders to add methods to the configurator, which allows extenders to avoid subclassing a Configurator just to add methods. See :ref:`add_directive` for more info. Surrounding application configuration with ``config.begin()`` and ``config.end()`` is no longer necessary. All scaffolds have been changed to no longer call these functions. Better Support for Global Template Variables During Rendering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A new event type named :class:`pyramid.interfaces.IBeforeRender` is now sent as an event before a renderer is invoked. Applications may now subscribe to the ``IBeforeRender`` event type in order to introspect the and modify the set of renderer globals before they are passed to a renderer. The event object iself has a dictionary-like interface that can be used for this purpose. For example:: from repoze.events import subscriber from pyramid.interfaces import IRendererGlobalsEvent @subscriber(IRendererGlobalsEvent) def add_global(event): event['mykey'] = 'foo' View Mappers ~~~~~~~~~~~~ A "view mapper" subsystem has been extracted, which allows framework extenders to control how view callables are constructed and called. This feature is not useful for "civilians", only for extension writers. See :ref:`using_a_view_mapper` for more information. Testing Support Improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :func:`pyramid.testing.setUp` and :func:`pyramid.testing.tearDown` APIs have been undeprecated. They are now the canonical setup and teardown APIs for test configuration, replacing "direct" creation of a Configurator. This is a change designed to provide a facade that will protect against any future Configurator deprecations. Authentication Support Improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The :class:`pyramid.interfaces.IAuthenticationPolicy` interface now specifies an ``unauthenticated_userid`` method. This method supports an important optimization required by people who are using persistent storages which do not support object caching and whom want to create a "user object" as a request attribute. - A new API has been added to the :mod:`pyramid.security` module named ``unauthenticated_userid``. This API function calls the ``unauthenticated_userid`` method of the effective security policy. - The class :class:`pyramid.authentication.AuthTktCookieHelper` is now an API. This class can be used by third-party authentication policy developers to help in the mechanics of authentication cookie-setting. - The :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now accepts a ``tokens`` parameter via :func:`pyramid.security.remember`. The value must be a sequence of strings. Tokens are placed into the auth_tkt "tokens" field and returned in the auth_tkt cookie. - Added a ``wild_domain`` argument to :class:`pyramid.authentication.AuthTktAuthenticationPolicy`, which defaults to ``True``. If it is set to ``False``, the feature of the policy which sets a cookie with a wilcard domain will be turned off. Documentation Improvements ~~~~~~~~~~~~~~~~~~~~~~~~~~ - Casey Duncan, a good friend, and an excellent technical writer has given us the gift of professionally editing the entire Pyramid documentation set. Any faults in the documentation are the development team's, and all improvements are his. - The "Resource Location and View Lookup" chapter has been replaced with a variant of Rob Miller's "Much Ado About Traversal" (originally published at http://blog.nonsequitarian.org/2010/much-ado-about-traversal/). - Many users have contributed documentation fixes and improvements including Ben Bangert, Blaise Laflamme, Rob Miller, Mike Orr, Carlos de la Guardia, Paul Everitt, Tres Seaver, John Shipman, Marius Gedminas, Chris Rossi, Joachim Krebs, Xavier Spriet, Reed O'Brien, William Chambers, Charlie Choiniere, and Jamaludin Ahmad. Minor Feature Additions ----------------------- - The ``settings`` dictionary passed to the Configurator is now available as ``config.registry.settings`` in configuration code and ``request.registry.settings`` in view code). - :meth:`pyramid.config.Configurator.add_view` now accepts a ``decorator`` keyword argument, a callable which will decorate the view callable before it is added to the registry. - Allow static renderer provided during view registration to be overridden at request time via a request attribute named ``override_renderer``, which should be the name of a previously registered renderer. Useful to provide "omnipresent" RPC using existing rendered views. - If a resource implements a ``__resource_url__`` method, it will be called as the result of invoking the :func:`pyramid.url.resource_url` function to generate a URL, overriding the default logic. See :ref:`generating_the_url_of_a_resource` for more information. - The name ``registry`` is now available in a ``pshell`` environment by default. It is the application registry object. - Added support for json on Google App Engine by catching :exc:`NotImplementedError` and importing ``simplejson`` from ``django.utils``. - Added the :mod:`pyramid.httpexceptions` module, which is a facade for the ``webob.exc`` module. - New class: :class:`pyramid.response.Response`. This is a pure facade for ``webob.Response`` (old code need not change to use this facade, it's existence is mostly for vanity and documentation-generation purposes). - The request now has a new attribute: ``tmpl_context`` for benefit of Pylons users. - New API methods for :class:`pyramid.request.Request`: ``model_url``, ``route_url``, and ``static_url``. These are simple passthroughs for their respective functions in :mod:`pyramid.url`. Backwards Incompatibilities --------------------------- - When a :class:`pyramid.exceptions.Forbidden` error is raised, its status code now ``403 Forbidden``. It was previously ``401 Unauthorized``, for backwards compatibility purposes with :mod:`repoze.bfg`. This change will cause problems for users of Pyramid with :mod:`repoze.who`, which intercepts ``401 Unauthorized`` by default, but allows ``403 Forbidden`` to pass through. Those deployments will need to configure :mod:`repoze.who` to also react to ``403 Forbidden``. To do so, use a repoze.who ``challenge_decider`` that looks like this:: import zope.interface from repoze.who.interfaces import IChallengeDecider def challenge_decider(environ, status, headers): return status.startswith('403') or status.startswith('401') zope.interface.directlyProvides(challenge_decider, IChallengeDecider) - The ``paster bfgshell`` command is now known as ``paster pshell``. - There is no longer an ``IDebugLogger`` object registered as a named utility with the name ``repoze.bfg.debug``. - These deprecated APIs have been removed: ``pyramid.testing.registerViewPermission``, ``pyramid.testing.registerRoutesMapper``, ``pyramid.request.get_request``, ``pyramid.security.Unauthorized``, ``pyramid.view.view_execution_permitted``, ``pyramid.view.NotFound`` - The Venusian "category" for all built-in Venusian decorators (e.g. ``subscriber`` and ``view_config``/``bfg_view``) is now ``pyramid`` instead of ``bfg``. - The ``pyramid.renderers.rendered_response`` function removed; use :func:`pyramid.renderers.render_to_response` instead. - Renderer factories now accept a *renderer info object* rather than an absolute resource specification or an absolute path. The object has the following attributes: ``name`` (the ``renderer=`` value), ``package`` (the 'current package' when the renderer configuration statement was found), ``type``: the renderer type, ``registry``: the current registry, and ``settings``: the deployment settings dictionary. Third-party ``repoze.bfg`` renderer implementations that must be ported to Pyramid will need to account for this. This change was made primarily to support more flexible Mako template rendering. - The presence of the key ``repoze.bfg.message`` in the WSGI environment when an exception occurs is now deprecated. Instead, code which relies on this environ value should use the ``exception`` attribute of the request (e.g. ``request.exception[0]``) to retrieve the message. - The values ``bfg_localizer`` and ``bfg_locale_name`` kept on the request during internationalization for caching purposes were never APIs. These however have changed to ``localizer`` and ``locale_name``, respectively. - The default ``cookie_name`` value of the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now defaults to ``auth_tkt`` (it used to default to ``repoze.bfg.auth_tkt``). - The :func:`pyramid.testing.zcml_configure` API has been removed. It had been advertised as removed since :mod:`repoze.bfg` 1.2a1, but hadn't actually been. - All environment variables which used to be prefixed with ``BFG_`` are now prefixed with ``PYRAMID_`` (e.g. ``BFG_DEBUG_NOTFOUND`` is now ``PYRAMID_DEBUG_NOTFOUND``) - Since the :class:`pyramid.interfaces.IAuthenticationPolicy` interface now specifies that a policy implementation must implement an ``unauthenticated_userid`` method, all third-party custom authentication policies now must implement this method. It, however, will only be called when the global function named :func:`pyramid.security.unauthenticated_userid` is invoked, so if you're not invoking that, you will not notice any issues. - The ``configure_zcml`` setting within the deployment settings (within ``**settings`` passed to a Pyramid ``main`` function) has ceased to have any meaning. - The ``make_app`` function has been removed from the :mod:`pyramid.router` module. It continues life within the ``pyramid_zcml`` package. This leaves the :mod:`pyramid.router` module without any API functions. Deprecations and Behavior Differences ------------------------------------- - :class:`pyramid.configuration.Configurator` is now deprecated. Use :class:`pyramid.config.Configurator`, passing its constructor ``autocommit=True`` instead. The :class:`pyramid.configuration.Configurator` alias will live for a long time, as every application uses it, but its import now issues a deprecation warning. The :class:`pyramid.config.Configurator` class has the same API as the :class:`pyramid.configuration.Configurator` class, which it means to replace, except by default it is a *non-autocommitting* configurator. The now-deprecated ``pyramid.configuration.Configurator`` will autocommit every time a configuration method is called. The :mod:`pyramid.configuration` module remains, but it is deprecated. Use :mod:`pyramid.config` instead. - The :func:`pyramid.settings.get_settings` API is now deprecated. Use ``pyramid.threadlocals.get_current_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). - The decorator previously known as ``pyramid.view.bfg_view`` is now known most formally as :class:`pyramid.view.view_config` in docs and scaffolds. - Obtaining the ``settings`` object via ``registry.{get|query}Utility(ISettings)`` is now deprecated. Instead, obtain the ``settings`` object via the ``registry.settings`` attribute. A backwards compatibility shim was added to the registry object to register the settings object as an ISettings utility when ``setattr(registry, 'settings', foo)`` is called, but it will be removed in a later release. - Obtaining the ``settings`` object via :func:`pyramid.settings.get_settings` is now deprecated. Obtain it instead as the ``settings`` attribute of the registry now (obtain the registry via :func:`pyramid.threadlocal.get_registry` or as ``request.registry``). Dependency Changes ------------------ - Depend on Venusian >= 0.5 (for scanning conflict exception decoration). Documentation Enhancements -------------------------- - Added a :mod:`pyramid.httpexceptions` API documentation chapter. - Added a :mod:`pyramid.session` API documentation chapter. - Added an API chapter for the :mod:`pyramid.response` module. - Added a :ref:`sessions_chapter` narrative documentation chapter. - All documentation which previously referred to ``webob.Response`` now uses :class:`pyramid.response.Response` instead. - The documentation has been overhauled to use imperative configuration, moving declarative configuration (ZCML) explanations to an external package, :term:`pyramid_zcml`. - Removed ``zodbsessions`` tutorial chapter. It's still useful, but we now have a SessionFactory abstraction which competes with it, and maintaining documentation on both ways to do it is a distraction. - Added an example of ``WebTest`` functional testing to the testing narrative chapter at :ref:`functional_tests`. - Extended the Resources chapter with examples of calls to resource-related APIs. - Add "Pyramid Provides More Than One Way to Do It" to Design Defense documentation. - The (weak) "Converting a CMF Application to Pyramid" tutorial has been removed from the tutorials section. It was moved to the ``pyramid_tutorials`` Github repository at http://docs.pylonsproject.org/projects/pyramid_tutorials/dev/. - Moved "Using ZODB With ZEO" and "Using repoze.catalog Within Pyramid" tutorials out of core documentation and into the Pyramid Tutorials site (http://docs.pylonsproject.org/projects/pyramid_tutorials/dev/). - Removed API documentation for deprecated ``pyramid.testing`` APIs named ``registerDummySecurityPolicy``, ``registerResources``, ``registerModels``, ``registerEventListener``, ``registerTemplateRenderer``, ``registerDummyRenderer``, ``registerView``, ``registerUtility``, ``registerAdapter``, ``registerSubscriber``, ``registerRoute``, and ``registerSettings``. pyramid-1.6/docs/whatsnew-1.1.rst0000644000076500000240000007554512520062551017407 0ustar michaelstaff00000000000000What's New In Pyramid 1.1 ========================= This article explains the new features in Pyramid version 1.1 as compared to its predecessor, :app:`Pyramid` 1.0. It also documents backwards incompatibilities between the two versions and deprecations added to Pyramid 1.1, as well as software dependency changes and notable documentation additions. Terminology Changes ------------------- The term "template" used by the Pyramid documentation used to refer to both "paster templates" and "rendered templates" (templates created by a rendering engine. i.e. Mako, Chameleon, Jinja, etc.). "Paster templates" will now be referred to as "scaffolds", whereas the name for "rendered templates" will remain as "templates." Major Feature Additions ----------------------- The major feature additions in Pyramid 1.1 are: - Support for the ``request.response`` attribute. - New views introspection feature: ``paster pviews``. - Support for "static" routes. - Default HTTP exception view. - ``http_cache`` view configuration parameter causes Pyramid to set HTTP caching headers. - Features that make it easier to write scripts that work in a :app:`Pyramid` environment. ``request.response`` ~~~~~~~~~~~~~~~~~~~~ - Instances of the :class:`pyramid.request.Request` class now have a ``response`` attribute. The object passed to a view callable as ``request`` is an instance of :class:`pyramid.request.Request`. ``request.response`` is an instance of the class :class:`pyramid.response.Response`. View callables that are configured with a :term:`renderer` will return this response object to the Pyramid router. Therefore, code in a renderer-using view callable can set response attributes such as ``request.response.content_type`` (before they return, e.g. a dictionary to the renderer) and this will influence the HTTP return value of the view callable. ``request.response`` can also be used in view callable code that is not configured to use a renderer. For example, a view callable might do ``request.response.body = '123'; return request.response``. However, the response object that is produced by ``request.response`` must be *returned* when a renderer is not in play in order to have any effect on the HTTP response (it is not a "global" response, and modifications to it are not somehow merged into a separately returned response object). The ``request.response`` object is lazily created, so its introduction does not negatively impact performance. ``paster pviews`` ~~~~~~~~~~~~~~~~~ - A new paster command named ``paster pviews`` was added. This command prints a summary of potentially matching views for a given path. See the section entitled :ref:`displaying_matching_views` for more information. Static Routes ~~~~~~~~~~~~~ - The ``add_route`` method of the Configurator now accepts a ``static`` argument. If this argument is ``True``, the added route will never be considered for matching when a request is handled. Instead, it will only be useful for URL generation via ``route_url`` and ``route_path``. See the section entitled :ref:`static_route_narr` for more information. Default HTTP Exception View ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - A default exception view for the interface :class:`pyramid.interfaces.IExceptionResponse` is now registered by default. This means that an instance of any exception class imported from :mod:`pyramid.httpexceptions` (such as ``HTTPFound``) can now be raised from within view code; when raised, this exception view will render the exception to a response. To allow for configuration of this feature, the :term:`Configurator` now accepts an additional keyword argument named ``exceptionresponse_view``. By default, this argument is populated with a default exception view function that will be used when an HTTP exception is raised. When ``None`` is passed for this value, an exception view for HTTP exceptions will not be registered. Passing ``None`` returns the behavior of raising an HTTP exception to that of Pyramid 1.0 (the exception will propagate to :term:`middleware` and to the WSGI server). ``http_cache`` ~~~~~~~~~~~~~~ A new value ``http_cache`` can be used as a :term:`view configuration` parameter. When you supply an ``http_cache`` value to a view configuration, the ``Expires`` and ``Cache-Control`` headers of a response generated by the associated view callable are modified. The value for ``http_cache`` may be one of the following: - A nonzero integer. If it's a nonzero integer, it's treated as a number of seconds. This number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=3600`` instructs the requesting browser to 'cache this response for an hour, please'. - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` instance, it will be converted into a number of seconds, and that number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=datetime.timedelta(days=1)`` instructs the requesting browser to 'cache this response for a day, please'. - Zero (``0``). If the value is zero, the ``Cache-Control`` and ``Expires`` headers present in all responses from this view will be composed such that client browser cache (and any intermediate caches) are instructed to never cache the response. - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, {'public':True})``), the first value in the tuple may be a nonzero integer or a ``datetime.timedelta`` instance; in either case this value will be used as the number of seconds to cache the response. The second value in the tuple must be a dictionary. The values present in the dictionary will be used as input to the ``Cache-Control`` response header. For example: ``http_cache=(3600, {'public':True})`` means 'cache for an hour, and add ``public`` to the Cache-Control header of the response'. All keys and values supported by the ``webob.cachecontrol.CacheControl`` interface may be added to the dictionary. Supplying ``{'public':True}`` is equivalent to calling ``response.cache_control.public = True``. Providing a non-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value)`` within your view's body. Providing a two-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value[0], **value[1])`` within your view's body. If you wish to avoid influencing, the ``Expires`` header, and instead wish to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. The environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and configuration file value ``prevent_http_cache`` are synonymous and allow you to prevent HTTP cache headers from being set by Pyramid's ``http_cache`` machinery globally in a process. see :ref:`influencing_http_caching` and :ref:`preventing_http_caching`. Easier Scripting Writing ~~~~~~~~~~~~~~~~~~~~~~~~ A new API function :func:`pyramid.paster.bootstrap` has been added to make writing scripts that need to work under Pyramid environment easier, e.g.: .. code-block:: python from pyramid.paster import bootstrap info = bootstrap('/path/to/my/development.ini') request = info['request'] print request.route_url('myroute') See :ref:`writing_a_script` for more details. Minor Feature Additions ----------------------- - It is now possible to invoke ``paster pshell`` even if the paste ini file section name pointed to in its argument is not actually a Pyramid WSGI application. The shell will work in a degraded mode, and will warn the user. See "The Interactive Shell" in the "Creating a Pyramid Project" narrative documentation section. - The ``paster pshell``, ``paster pviews``, and ``paster proutes`` commands each now under the hood uses :func:`pyramid.paster.bootstrap`, which makes it possible to supply an ``.ini`` file without naming the "right" section in the file that points at the actual Pyramid application. Instead, you can generally just run ``paster {pshell|proutes|pviews} development.ini`` and it will do mostly the right thing. - It is now possible to add a ``[pshell]`` section to your application's .ini configuration file, which influences the global names available to a pshell session. See :ref:`extending_pshell`. - The :meth:`pyramid.config.Configurator.scan` method has grown a ``**kw`` argument. ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object created by Pyramid. (See the :term:`Venusian` documentation for more information about ``Scanner``). - New request property: ``json_body``. This property will return the JSON-decoded variant of the request body. If the request body is not well-formed JSON, this property will raise an exception. - A `JSONP `_ renderer. See :ref:`jsonp_renderer` for more details. - New authentication policy: :class:`pyramid.authentication.SessionAuthenticationPolicy`, which uses a session to store credentials. - A function named :func:`pyramid.httpexceptions.exception_response` is a shortcut that can be used to create HTTP exception response objects using an HTTP integer status code. - Integers and longs passed as ``elements`` to :func:`pyramid.url.resource_url` or :meth:`pyramid.request.Request.resource_url` e.g. ``resource_url(context, request, 1, 2)`` (``1`` and ``2`` are the ``elements``) will now be converted implicitly to strings in the result. Previously passing integers or longs as elements would cause a TypeError. - ``pyramid_alchemy`` scaffold now uses ``query.get`` rather than ``query.filter_by`` to take better advantage of identity map caching. - ``pyramid_alchemy`` scaffold now has unit tests. - Added a :func:`pyramid.i18n.make_localizer` API. - An exception raised by a :class:`pyramid.events.NewRequest` event subscriber can now be caught by an exception view. - It is now possible to get information about why Pyramid raised a Forbidden exception from within an exception view. The ``ACLDenied`` object returned by the ``permits`` method of each stock authorization policy (:meth:`pyramid.interfaces.IAuthorizationPolicy.permits`) is now attached to the Forbidden exception as its ``result`` attribute. Therefore, if you've created a Forbidden exception view, you can see the ACE, ACL, permission, and principals involved in the request as eg. ``context.result.permission``, ``context.result.acl``, etc within the logic of the Forbidden exception view. - Don't explicitly prevent the ``timeout`` from being lower than the ``reissue_time`` when setting up an :class:`pyramid.authentication.AuthTktAuthenticationPolicy` (previously such a configuration would raise a ``ValueError``, now it's allowed, although typically nonsensical). Allowing the nonsensical configuration made the code more understandable and required fewer tests. - The :class:`pyramid.request.Request` class now has a ``ResponseClass`` attribute which points at :class:`pyramid.response.Response`. - The :class:`pyramid.response.Response` class now has a ``RequestClass`` interface which points at :class:`pyramid.request.Request`. - It is now possible to return an arbitrary object from a Pyramid view callable even if a renderer is not used, as long as a suitable adapter to :class:`pyramid.interfaces.IResponse` is registered for the type of the returned object by using the new :meth:`pyramid.config.Configurator.add_response_adapter` API. See the section in the Hooks chapter of the documentation entitled :ref:`using_iresponse`. - The Pyramid router will now, by default, call the ``__call__`` method of response objects when returning a WSGI response. This means that, among other things, the ``conditional_response`` feature response objects inherited from WebOb will now behave properly. - New method named :meth:`pyramid.request.Request.is_response`. This method should be used instead of the :func:`pyramid.view.is_response` function, which has been deprecated. - :class:`pyramid.exceptions.NotFound` is now just an alias for :class:`pyramid.httpexceptions.HTTPNotFound`. - :class:`pyramid.exceptions.Forbidden` is now just an alias for :class:`pyramid.httpexceptions.HTTPForbidden`. - Added ``mako.preprocessor`` config file parameter; allows for a Mako preprocessor to be specified as a Python callable or Python dotted name. See https://github.com/Pylons/pyramid/pull/183 for rationale. - New API class: :class:`pyramid.static.static_view`. This supersedes the (now deprecated) :class:`pyramid.view.static` class. :class:`pyramid.static.static_view`, by default, serves up documents as the result of the request's ``path_info``, attribute rather than it's ``subpath`` attribute (the inverse was true of :class:`pyramid.view.static`, and still is). :class:`pyramid.static.static_view` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. - A new api function :func:`pyramid.scripting.prepare` has been added. It is a lower-level analogue of :func:`pyramid.paster.bootstrap` that accepts a request and a registry instead of a config file argument, and is used for the same purpose: .. code-block:: python from pyramid.scripting import prepare info = prepare(registry=myregistry) request = info['request'] print request.route_url('myroute') - A new API function :func:`pyramid.scripting.make_request` has been added. The resulting request will have a ``registry`` attribute. It is meant to be used in conjunction with :func:`pyramid.scripting.prepare` and/or :func:`pyramid.paster.bootstrap` (both of which accept a request as an argument): .. code-block:: python from pyramid.scripting import make_request request = make_request('/') - New API attribute :attr:`pyramid.config.global_registries` is an iterable object that contains references to every Pyramid registry loaded into the current process via :meth:`pyramid.config.Configurator.make_wsgi_app`. It also has a ``last`` attribute containing the last registry loaded. This is used by the scripting machinery, and is available for introspection. - Added the :attr:`pyramid.renderers.null_renderer` object as an API. The null renderer is an object that can be used in advanced integration cases as input to the view configuration ``renderer=`` argument. When the null renderer is used as a view renderer argument, Pyramid avoids converting the view callable result into a Response object. This is useful if you want to reuse the view configuration and lookup machinery outside the context of its use by the Pyramid router. (This feature was added for consumption by the ``pyramid_rpc`` package, which uses view configuration and lookup outside the context of a router in exactly this way.) Backwards Incompatibilities --------------------------- - Pyramid no longer supports Python 2.4. Python 2.5 or better is required to run Pyramid 1.1+. Pyramid, however, does not work under any version of Python 3 yet. - The Pyramid router now, by default, expects response objects returned from view callables to implement the :class:`pyramid.interfaces.IResponse` interface. Unlike the Pyramid 1.0 version of this interface, objects which implement IResponse now must define a ``__call__`` method that accepts ``environ`` and ``start_response``, and which returns an ``app_iter`` iterable, among other things. Previously, it was possible to return any object which had the three WebOb ``app_iter``, ``headerlist``, and ``status`` attributes as a response, so this is a backwards incompatibility. It is possible to get backwards compatibility back by registering an adapter to IResponse from the type of object you're now returning from view callables. See the section in the Hooks chapter of the documentation entitled :ref:`using_iresponse`. - The :class:`pyramid.interfaces.IResponse` interface is now much more extensive. Previously it defined only ``app_iter``, ``status`` and ``headerlist``; now it is basically intended to directly mirror the ``webob.Response`` API, which has many methods and attributes. - The :mod:`pyramid.httpexceptions` classes named ``HTTPFound``, ``HTTPMultipleChoices``, ``HTTPMovedPermanently``, ``HTTPSeeOther``, ``HTTPUseProxy``, and ``HTTPTemporaryRedirect`` now accept ``location`` as their first positional argument rather than ``detail``. This means that you can do, e.g. ``return pyramid.httpexceptions.HTTPFound('http://foo')`` rather than ``return pyramid.httpexceptions.HTTPFound(location='http//foo')`` (the latter will of course continue to work). - The pyramid Router attempted to set a value into the key ``environ['repoze.bfg.message']`` when it caught a view-related exception for backwards compatibility with applications written for :mod:`repoze.bfg` during error handling. It did this by using code that looked like so:: # "why" is an exception object try: msg = why[0] except: msg = '' environ['repoze.bfg.message'] = msg Use of the value ``environ['repoze.bfg.message']`` was docs-deprecated in Pyramid 1.0. Our standing policy is to not remove features after a deprecation for two full major releases, so this code was originally slated to be removed in Pyramid 1.2. However, computing the ``repoze.bfg.message`` value was the source of at least one bug found in the wild (https://github.com/Pylons/pyramid/issues/199), and there isn't a foolproof way to both preserve backwards compatibility and to fix the bug. Therefore, the code which sets the value has been removed in this release. Code in exception views which relies on this value's presence in the environment should now use the ``exception`` attribute of the request (e.g. ``request.exception[0]``) to retrieve the message instead of relying on ``request.environ['repoze.bfg.message']``. Deprecations and Behavior Differences ------------------------------------- .. note:: Under Python 2.7+, it's necessary to pass the Python interpreter the correct warning flags to see deprecation warnings emitted by Pyramid when porting your application from an older version of Pyramid. Use the ``PYTHONWARNINGS`` environment variable with the value ``all`` in the shell you use to invoke ``paster serve`` to see these warnings, e.g. on UNIX, ``PYTHONWARNINGS=all $VENV/bin/paster serve development.ini``. Python 2.5 and 2.6 show deprecation warnings by default, so this is unnecessary there. All deprecation warnings are emitted to the console. - The :class:`pyramid.view.static` class has been deprecated in favor of the newer :class:`pyramid.static.static_view` class. A deprecation warning is raised when it is used. You should replace it with a reference to :class:`pyramid.static.static_view` with the ``use_subpath=True`` argument. - The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands now take a single argument in the form ``/path/to/config.ini#sectionname`` rather than the previous 2-argument spelling ``/path/to/config.ini sectionname``. ``#sectionname`` may be omitted, in which case ``#main`` is assumed. - The default Mako renderer is now configured to escape all HTML in expression tags. This is intended to help prevent XSS attacks caused by rendering unsanitized input from users. To revert this behavior in user's templates, they need to filter the expression through the 'n' filter:: ${ myhtml | n }. See https://github.com/Pylons/pyramid/issues/193. - Deprecated all assignments to ``request.response_*`` attributes (for example ``request.response_content_type = 'foo'`` is now deprecated). Assignments and mutations of assignable request attributes that were considered by the framework for response influence are now deprecated: ``response_content_type``, ``response_headerlist``, ``response_status``, ``response_charset``, and ``response_cache_for``. Instead of assigning these to the request object for later detection by the rendering machinery, users should use the appropriate API of the Response object created by accessing ``request.response`` (e.g. code which does ``request.response_content_type = 'abc'`` should be changed to ``request.response.content_type = 'abc'``). - Passing view-related parameters to :meth:`pyramid.config.Configurator.add_route` is now deprecated. Previously, a view was permitted to be connected to a route using a set of ``view*`` parameters passed to the ``add_route`` method of the Configurator. This was a shorthand which replaced the need to perform a subsequent call to ``add_view``. For example, it was valid (and often recommended) to do:: config.add_route('home', '/', view='mypackage.views.myview', view_renderer='some/renderer.pt') Passing ``view*`` arguments to ``add_route`` is now deprecated in favor of connecting a view to a predefined route via :meth:`pyramid.config.Configurator.add_view` using the route's ``route_name`` parameter. As a result, the above example should now be spelled:: config.add_route('home', '/') config.add_view('mypackage.views.myview', route_name='home', renderer='some/renderer.pt') This deprecation was done to reduce confusion observed in IRC, as well as to (eventually) reduce documentation burden. A deprecation warning is now issued when any view-related parameter is passed to ``add_route``. .. seealso:: See also `issue #164 on GitHub `_. - Passing an ``environ`` dictionary to the ``__call__`` method of a "traverser" (e.g. an object that implements :class:`pyramid.interfaces.ITraverser` such as an instance of :class:`pyramid.traversal.ResourceTreeTraverser`) as its ``request`` argument now causes a deprecation warning to be emitted. Consumer code should pass a ``request`` object instead. The fact that passing an environ dict is permitted has been documentation-deprecated since ``repoze.bfg`` 1.1, and this capability will be removed entirely in a future version. - The following (undocumented, dictionary-like) methods of the :class:`pyramid.request.Request` object have been deprecated: ``__contains__``, ``__delitem__``, ``__getitem__``, ``__iter__``, ``__setitem__``, ``get``, ``has_key``, ``items``, ``iteritems``, ``itervalues``, ``keys``, ``pop``, ``popitem``, ``setdefault``, ``update``, and ``values``. Usage of any of these methods will cause a deprecation warning to be emitted. These methods were added for internal compatibility in ``repoze.bfg`` 1.1 (code that currently expects a request object expected an environ object in BFG 1.0 and before). In a future version, these methods will be removed entirely. - A custom request factory is now required to return a request object that has a ``response`` attribute (or "reified"/lazy property) if the request is meant to be used in a view that uses a renderer. This ``response`` attribute should be an instance of the class :class:`pyramid.response.Response`. - The JSON and string renderer factories now assign to ``request.response.content_type`` rather than ``request.response_content_type``. - Each built-in renderer factory now determines whether it should change the content type of the response by comparing the response's content type against the response's default content type; if the content type is the default content type (usually ``text/html``), the renderer changes the content type (to ``application/json`` or ``text/plain`` for JSON and string renderers respectively). - The :func:`pyramid.wsgi.wsgiapp2` now uses a slightly different method of figuring out how to "fix" ``SCRIPT_NAME`` and ``PATH_INFO`` for the downstream application. As a result, those values may differ slightly from the perspective of the downstream application (for example, ``SCRIPT_NAME`` will now never possess a trailing slash). - Previously, :class:`pyramid.request.Request` inherited from :class:`webob.request.Request` and implemented ``__getattr__``, ``__setattr__`` and ``__delattr__`` itself in order to override "adhoc attr" WebOb behavior where attributes of the request are stored in the environ. Now, :class:`pyramid.request.Request` inherits from (the more recent) :class:`webob.request.BaseRequest` instead of :class:`webob.request.Request`, which provides the same behavior. :class:`pyramid.request.Request` no longer implements its own ``__getattr__``, ``__setattr__`` or ``__delattr__`` as a result. - Deprecated :func:`pyramid.view.is_response` function in favor of (newly-added) :meth:`pyramid.request.Request.is_response` method. Determining if an object is truly a valid response object now requires access to the registry, which is only easily available as a request attribute. The :func:`pyramid.view.is_response` function will still work until it is removed, but now may return an incorrect answer under some (very uncommon) circumstances. - :class:`pyramid.response.Response` is now a *subclass* of ``webob.response.Response`` (in order to directly implement the :class:`pyramid.interfaces.IResponse` interface, to speed up response generation). - The "exception response" objects importable from ``pyramid.httpexceptions`` (e.g. ``HTTPNotFound``) are no longer just import aliases for classes that actually live in ``webob.exc``. Instead, we've defined our own exception classes within the module that mirror and emulate the ``webob.exc`` exception response objects almost entirely. See :ref:`http_exception_hierarchy` in the Design Defense chapter for more information. - When visiting a URL that represented a static view which resolved to a subdirectory, the ``index.html`` of that subdirectory would not be served properly. Instead, a redirect to ``/subdir`` would be issued. This has been fixed, and now visiting a subdirectory that contains an ``index.html`` within a static view returns the index.html properly. .. seealso:: See also `issue #67 on GitHub `_. - Deprecated the ``pyramid.config.Configurator.set_renderer_globals_factory`` method and the ``renderer_globals`` Configurator constructor parameter. Users should convert code using this feature to use a BeforeRender event. See the section :ref:`beforerender_event` in the Hooks chapter. - In Pyramid 1.0, the :class:`pyramid.events.subscriber` directive behaved contrary to the documentation when passed more than one interface object to its constructor. For example, when the following listener was registered:: @subscriber(IFoo, IBar) def expects_ifoo_events_and_ibar_events(event): print event The Events chapter docs claimed that the listener would be registered and listening for both ``IFoo`` and ``IBar`` events. Instead, it registered an "object event" subscriber which would only be called if an IObjectEvent was emitted where the object interface was ``IFoo`` and the event interface was ``IBar``. The behavior now matches the documentation. If you were relying on the buggy behavior of the 1.0 ``subscriber`` directive in order to register an object event subscriber, you must now pass a sequence to indicate you'd like to register a subscriber for an object event. e.g.:: @subscriber([IFoo, IBar]) def expects_object_event(object, event): print object, event - In 1.0, if a :class:`pyramid.events.BeforeRender` event subscriber added a value via the ``__setitem__`` or ``update`` methods of the event object with a key that already existed in the renderer globals dictionary, a ``KeyError`` was raised. With the deprecation of the "add_renderer_globals" feature of the configurator, there was no way to override an existing value in the renderer globals dictionary that already existed. Now, the event object will overwrite an older value that is already in the globals dictionary when its ``__setitem__`` or ``update`` is called (as well as the new ``setdefault`` method), just like a plain old dictionary. As a result, for maximum interoperability with other third-party subscribers, if you write an event subscriber meant to be used as a BeforeRender subscriber, your subscriber code will now need to (using ``.get`` or ``__contains__`` of the event object) ensure no value already exists in the renderer globals dictionary before setting an overriding value. - The :meth:`pyramid.config.Configurator.add_route` method allowed two routes with the same route to be added without an intermediate call to :meth:`pyramid.config.Configurator.commit`. If you now receive a ``ConfigurationError`` at startup time that appears to be ``add_route`` related, you'll need to either a) ensure that all of your route names are unique or b) call ``config.commit()`` before adding a second route with the name of a previously added name or c) use a Configurator that works in ``autocommit`` mode. Dependency Changes ------------------ - Pyramid now depends on :term:`WebOb` >= 1.0.2 as tests depend on the bugfix in that release: "Fix handling of WSGI environs with missing ``SCRIPT_NAME``". (Note that in reality, everyone should probably be using 1.0.4 or better though, as WebOb 1.0.2 and 1.0.3 were effectively brownbag releases.) Documentation Enhancements -------------------------- - Added a section entitled :ref:`writing_a_script` to the "Command-Line Pyramid" chapter. - The :ref:`bfg_wiki_tutorial` was updated slightly. - The :ref:`bfg_sql_wiki_tutorial` was updated slightly. - Made :class:`pyramid.interfaces.IAuthenticationPolicy` and :class:`pyramid.interfaces.IAuthorizationPolicy` public interfaces, and they are now referred to within the :mod:`pyramid.authentication` and :mod:`pyramid.authorization` API docs. - Render the function definitions for each exposed interface in :mod:`pyramid.interfaces`. - Add missing docs reference to :meth:`pyramid.config.Configurator.set_view_mapper` and refer to it within the documentation section entitled :ref:`using_a_view_mapper`. - Added section to the "Environment Variables and ``.ini`` File Settings" chapter in the narrative documentation section entitled :ref:`adding_a_custom_setting`. - Added documentation for a :term:`multidict` as :class:`pyramid.interfaces.IMultiDict`. - Added a section to the "URL Dispatch" narrative chapter regarding the new "static" route feature entitled :ref:`static_route_narr`. - Added API docs for :func:`pyramid.httpexceptions.exception_response`. - Added :ref:`http_exceptions` section to Views narrative chapter including a description of :func:`pyramid.httpexceptions.exception_response`. - Added API docs for :class:`pyramid.authentication.SessionAuthenticationPolicy`. pyramid-1.6/docs/whatsnew-1.2.rst0000644000076500000240000003225112520062551017373 0ustar michaelstaff00000000000000What's New In Pyramid 1.2 ========================= This article explains the new features in :app:`Pyramid` version 1.2 as compared to its predecessor, :app:`Pyramid` 1.1. It also documents backwards incompatibilities between the two versions and deprecations added to Pyramid 1.2, as well as software dependency changes and notable documentation additions. Major Feature Additions ----------------------- The major feature additions in Pyramid 1.2 follow. Debug Toolbar ~~~~~~~~~~~~~ The scaffolding packages that come with Pyramid now include a debug toolbar component which can be used to interactively debug an application. See :ref:`debug_toolbar` for more information. ``route_prefix`` Argument to include ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :meth:`pyramid.config.Configurator.include` method now accepts a ``route_prefix`` argument. This argument allows you to compose URL dispatch applications together from disparate packages. See :ref:`route_prefix` for more information. Tweens ~~~~~~ A :term:`tween` is used to wrap the Pyramid router's primary request handling function. This is a feature that can be used by Pyramid framework extensions, to provide, for example, view timing support and can provide a convenient place to hang bookkeeping code. Tweens are a little like :term:`WSGI` :term:`middleware`, but have access to Pyramid functionality such as renderers and a full-featured request object. To support this feature, a new configurator directive exists named :meth:`pyramid.config.Configurator.add_tween`. This directive adds a "tween". Tweens are further described in :ref:`registering_tweens`. A new paster command now exists: ``paster ptweens``. This command prints the current tween configuration for an application. See the section entitled :ref:`displaying_tweens` for more info. Scaffolding Changes ~~~~~~~~~~~~~~~~~~~ - All scaffolds now use the ``pyramid_tm`` package rather than the ``repoze.tm2`` :term:`middleware` to manage transaction management. - The ZODB scaffold now uses the ``pyramid_zodbconn`` package rather than the ``repoze.zodbconn`` package to provide ZODB integration. - All scaffolds now use the ``pyramid_debugtoolbar`` package rather than the ``WebError`` package to provide interactive debugging features. - Projects created via a scaffold no longer depend on the ``WebError`` package at all; configuration in the ``production.ini`` file which used to require its ``error_catcher`` :term:`middleware` has been removed. Configuring error catching / email sending is now the domain of the ``pyramid_exclog`` package (see http://docs.pylonsproject.org/projects/pyramid_exclog/dev/). - All scaffolds now send the ``cache_max_age`` parameter to the ``add_static_view`` method. Minor Feature Additions ----------------------- - The ``[pshell]`` section in an ini configuration file now treats a ``setup`` key as a dotted name that points to a callable that is passed the bootstrap environment. It can mutate the environment as necessary during a ``paster pshell`` session. This feature is described in :ref:`writing_a_script`. - A new configuration setting named ``pyramid.includes`` is now available. It is described in :ref:`including_packages`. - Added a :data:`pyramid.security.NO_PERMISSION_REQUIRED` constant for use in ``permission=`` statements to view configuration. This constant has a value of the string ``__no_permission_required__``. This string value was previously referred to in documentation; now the documentation uses the constant. - Added a decorator-based way to configure a response adapter: :class:`pyramid.response.response_adapter`. This decorator has the same use as :meth:`pyramid.config.Configurator.add_response_adapter` but it's declarative. - The :class:`pyramid.events.BeforeRender` event now has an attribute named ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. - The Pyramid debug logger now uses the standard logging configuration (usually set up by Paste as part of startup). This means that output from e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the normal logging channels. The logger name of the debug logger will be the package name of the *caller* of the Configurator's constructor. - A new attribute is available on request objects: ``exc_info``. Its value will be ``None`` until an exception is caught by the Pyramid router, after which it will be the result of ``sys.exc_info()``. - :class:`pyramid.testing.DummyRequest` now implements the ``add_finished_callback`` and ``add_response_callback`` methods implemented by :class:`pyramid.request.Request`. - New methods of the :class:`pyramid.config.Configurator` class: :meth:`~pyramid.config.Configurator.set_authentication_policy` and :meth:`~pyramid.config.Configurator.set_authorization_policy`. These are meant to be consumed mostly by add-on authors who wish to offer packages which register security policies. - New Configurator method: :meth:`pyramid.config.Configurator.set_root_factory`, which can set the root factory after the Configurator has been constructed. - Pyramid no longer eagerly commits some default configuration statements at :term:`Configurator` construction time, which permits values passed in as constructor arguments (e.g. ``authentication_policy`` and ``authorization_policy``) to override the same settings obtained via the :meth:`pyramid.config.Configurator.include` method. - Better Mako rendering exceptions; the template line which caused the error is now shown when a Mako rendering raises an exception. - New request methods: :meth:`~pyramid.request.Request.current_route_url`, :meth:`~pyramid.request.Request.current_route_path`, and :meth:`~pyramid.request.Request.static_path`. - New functions in the :mod:`pyramid.url` module: :func:`~pyramid.url.current_route_path` and :func:`~pyramid.url.static_path`. - The :meth:`pyramid.request.Request.static_url` API (and its brethren :meth:`pyramid.request.Request.static_path`, :func:`pyramid.url.static_url`, and :func:`pyramid.url.static_path`) now accept an absolute filename as a "path" argument. This will generate a URL to an asset as long as the filename is in a directory which was previously registered as a static view. Previously, trying to generate a URL to an asset using an absolute file path would raise a ValueError. - The :class:`~pyramid.authentication.RemoteUserAuthenticationPolicy`, :class:`~pyramid.authentication.AuthTktAuthenticationPolicy`, and :class:`~pyramid.authentication.SessionAuthenticationPolicy` constructors now accept an additional keyword argument named ``debug``. By default, this keyword argument is ``False``. When it is ``True``, debug information will be sent to the Pyramid debug logger (usually on stderr) when the ``authenticated_userid`` or ``effective_principals`` method is called on any of these policies. The output produced can be useful when trying to diagnose authentication-related problems. - New view predicate: ``match_param``. Example: a view added via ``config.add_view(aview, match_param='action=edit')`` will be called only when the ``request.matchdict`` has a value inside it named ``action`` with a value of ``edit``. - Support an ``onerror`` keyword argument to :meth:`pyramid.config.Configurator.scan`. This argument is passed to :meth:`venusian.Scanner.scan` to influence error behavior when an exception is raised during scanning. - The ``request_method`` predicate argument to :meth:`pyramid.config.Configurator.add_view` and :meth:`pyramid.config.Configurator.add_route` is now permitted to be a tuple of HTTP method names. Previously it was restricted to being a string representing a single HTTP method name. - Undeprecated ``pyramid.traversal.find_model``, ``pyramid.traversal.model_path``, ``pyramid.traversal.model_path_tuple``, and ``pyramid.url.model_url``, which were all deprecated in Pyramid 1.0. There's just not much cost to keeping them around forever as aliases to their renamed ``resource_*`` prefixed functions. - Undeprecated ``pyramid.view.bfg_view``, which was deprecated in Pyramid 1.0. This is a low-cost alias to ``pyramid.view.view_config`` which we'll just keep around forever. - Route pattern replacement marker names can now begin with an underscore. See https://github.com/Pylons/pyramid/issues/276. Deprecations ------------ - All Pyramid-related :term:`deployment settings` (e.g. ``debug_all``, ``debug_notfound``) are now meant to be prefixed with the prefix ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The old non-prefixed settings will continue to work indefinitely but supplying them may print a deprecation warning. All scaffolds and tutorials have been changed to use prefixed settings. - The :term:`deployment settings` dictionary now raises a deprecation warning when you attempt to access its values via ``__getattr__`` instead of via ``__getitem__``. Backwards Incompatibilities --------------------------- - If a string is passed as the ``debug_logger`` parameter to a :term:`Configurator`, that string is considered to be the name of a global Python logger rather than a dotted name to an instance of a logger. - The :meth:`pyramid.config.Configurator.include` method now accepts only a single ``callable`` argument. A *sequence* of callables used to be permitted. If you are passing more than one ``callable`` to :meth:`pyramid.config.Configurator.include`, it will break. You now must now instead make a separate call to the method for each callable. - It may be necessary to more strictly order configuration route and view statements when using an "autocommitting" :term:`Configurator`. In the past, it was possible to add a view which named a route name before adding a route with that name when you used an autocommitting configurator. For example: .. code-block:: python config = Configurator(autocommit=True) config.add_view('my.pkg.someview', route_name='foo') config.add_route('foo', '/foo') The above will raise an exception when the view attempts to add itself. Now you must add the route before adding the view: .. code-block:: python config = Configurator(autocommit=True) config.add_route('foo', '/foo') config.add_view('my.pkg.someview', route_name='foo') This won't effect "normal" users, only people who have legacy BFG codebases that used an autommitting configurator and possibly tests that use the configurator API (the configurator returned by :func:`pyramid.testing.setUp` is an autocommitting configurator). The right way to get around this is to use a default non-autocommitting configurator, which does not have these directive ordering requirements: .. code-block:: python config = Configurator() config.add_view('my.pkg.someview', route_name='foo') config.add_route('foo', '/foo') The above will work fine. - The :meth:`pyramid.config.Configurator.add_route` directive no longer returns a route object. This change was required to make route vs. view configuration processing work properly. Behavior Differences -------------------- - An ETag header is no longer set when serving a static file. A Last-Modified header is set instead. - Static file serving no longer supports the ``wsgi.file_wrapper`` extension. - Instead of returning a ``403 Forbidden`` error when a static file is served that cannot be accessed by the Pyramid process' user due to file permissions, an IOError (or similar) will be raised. Documentation Enhancements -------------------------- - Narrative and API documentation which used the ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` functions in the :mod:`pyramid.url` package have now been changed to use eponymous methods of the request instead. - Added a section entitled :ref:`route_prefix` to the "URL Dispatch" narrative documentation chapter. - Added a new module to the API docs: :mod:`pyramid.tweens`. - Added a :ref:`registering_tweens` section to the "Hooks" narrative chapter. - Added a :ref:`displaying_tweens` section to the "Command-Line Pyramid" narrative chapter. - Added documentation for :ref:`explicit_tween_config` and :ref:`including_packages` to the "Environment Variables and ``.ini`` Files Settings" chapter. - Added a :ref:`logging_chapter` chapter to the narrative docs. - All tutorials now use - The ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` methods of the :class:`pyramid.request.Request` rather than the function variants imported from ``pyramid.url``. - The ZODB wiki tutorial now uses the ``pyramid_zodbconn`` package rather than the ``repoze.zodbconn`` package to provide ZODB integration. - Added :ref:`what_makes_pyramid_unique` to the Introduction narrative chapter. Dependency Changes ------------------ - Pyramid now relies on PasteScript >= 1.7.4. This version contains a feature important for allowing flexible logging configuration. - Pyramid now requires Venusian 1.0a1 or better to support the ``onerror`` keyword argument to :meth:`pyramid.config.Configurator.scan`. - The ``zope.configuration`` package is no longer a dependency. pyramid-1.6/docs/whatsnew-1.3.rst0000644000076500000240000006226312520062551017402 0ustar michaelstaff00000000000000What's New In Pyramid 1.3 ========================= This article explains the new features in :app:`Pyramid` version 1.3 as compared to its predecessor, :app:`Pyramid` 1.2. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.3, as well as software dependency changes and notable documentation additions. Major Feature Additions ----------------------- The major feature additions in Pyramid 1.3 follow. Python 3 Compatibility ~~~~~~~~~~~~~~~~~~~~~~ .. image:: python-3.png Pyramid continues to run on Python 2, but Pyramid is now also Python 3 compatible. To use Pyramid under Python 3, Python 3.2 or better is required. Many Pyramid add-ons are already Python 3 compatible. For example, ``pyramid_debugtoolbar``, ``pyramid_jinja2``, ``pyramid_exclog``, ``pyramid_tm``, ``pyramid_mailer``, and ``pyramid_handlers`` are all Python 3-ready. But other add-ons are known to work only under Python 2. Also, some scaffolding dependencies (particularly ZODB) do not yet work under Python 3. Please be patient as we gain full ecosystem support for Python 3. You can see more details about ongoing porting efforts at https://github.com/Pylons/pyramid/wiki/Python-3-Porting . Python 3 compatibility required dropping some package dependencies and support for older Python versions and platforms. See the "Backwards Incompatibilities" section below for more information. The ``paster`` Command Has Been Replaced ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We've replaced the ``paster`` command with Pyramid-specific analogues. Why? The libraries that supported the ``paster`` command named ``Paste`` and ``PasteScript`` do not run under Python 3, and we were unwilling to port and maintain them ourselves. As a result, we've had to make some changes. Previously (in Pyramid 1.0, 1.1 and 1.2), you created a Pyramid application using ``paster create``, like so:: $ $VENV/bin/paster create -t pyramid_starter foo In 1.3, you're now instead required to create an application using ``pcreate`` like so:: $ $VENV/bin/pcreate -s starter foo ``pcreate`` is required to be used for internal Pyramid scaffolding; externally distributed scaffolding may allow for both ``pcreate`` and/or ``paster create``. In previous Pyramid versions, you ran a Pyramid application like so:: $ $VENV/bin/paster serve development.ini Instead, you now must use the ``pserve`` command in 1.3:: $ $VENV/bin/pserve development.ini The ``ini`` configuration file format supported by Pyramid has not changed. As a result, Python 2-only users can install PasteScript manually and use ``paster serve`` instead if they like. However, using ``pserve`` will work under both Python 2 and Python 3. Analogues of ``paster pshell``, ``paster pviews``, ``paster request`` and ``paster ptweens`` also exist under the respective console script names ``pshell``, ``pviews``, ``prequest`` and ``ptweens``. ``paste.httpserver`` replaced by ``waitress`` in Scaffolds ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Because the ``paste.httpserver`` server we used previously in scaffolds is not Python 3 compatible, we've made the default WSGI server used by Pyramid scaffolding the :term:`waitress` server. The waitress server is both Python 2 and Python 3 compatible. Once you create a project from a scaffold, its ``development.ini`` and ``production.ini`` will have the following line:: use = egg:waitress#main Instead of this (which was the default in older versions):: use = egg:Paste#http .. note:: ``paste.httpserver`` "helped" by converting header values that were Unicode into strings, which was a feature that subverted the :term:`WSGI` specification. The ``waitress`` server, on the other hand implements the WSGI spec more fully. This specifically may affect you if you are modifying headers on your responses. The following error might be an indicator of this problem: **AssertionError: Header values must be strings, please check the type of the header being returned.** A common case would be returning Unicode headers instead of string headers. Compatibility Helper Library ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A new :mod:`pyramid.compat` module was added which provides Python 2/3 straddling support for Pyramid add-ons and development environments. Introspection ~~~~~~~~~~~~~ A configuration introspection system was added; see :ref:`using_introspection` and :ref:`introspection` for more information on using the introspection system as a developer. The latest release of the pyramid debug toolbar (0.9.7+) provides an "Introspection" panel that exposes introspection information to a Pyramid application developer. New APIs were added to support introspection :attr:`pyramid.registry.Introspectable`, :attr:`pyramid.config.Configurator.introspector`, :attr:`pyramid.config.Configurator.introspectable`, :attr:`pyramid.registry.Registry.introspector`. ``@view_defaults`` Decorator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you use a class as a view, you can use the new :class:`pyramid.view.view_defaults` class decorator on the class to provide defaults to the view configuration information used by every ``@view_config`` decorator that decorates a method of that class. For instance, if you've got a class that has methods that represent "REST actions", all which are mapped to the same route, but different request methods, instead of this: .. code-block:: python :linenos: from pyramid.view import view_config from pyramid.response import Response class RESTView(object): def __init__(self, request): self.request = request @view_config(route_name='rest', request_method='GET') def get(self): return Response('get') @view_config(route_name='rest', request_method='POST') def post(self): return Response('post') @view_config(route_name='rest', request_method='DELETE') def delete(self): return Response('delete') You can do this: .. code-block:: python :linenos: from pyramid.view import view_defaults from pyramid.view import view_config from pyramid.response import Response @view_defaults(route_name='rest') class RESTView(object): def __init__(self, request): self.request = request @view_config(request_method='GET') def get(self): return Response('get') @view_config(request_method='POST') def post(self): return Response('post') @view_config(request_method='DELETE') def delete(self): return Response('delete') This also works for imperative view configurations that involve a class. See :ref:`view_defaults` for more information. Extending a Request without Subclassing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is now possible to extend a :class:`pyramid.request.Request` object with property descriptors without having to create a custom request factory. The new method :meth:`pyramid.config.Configurator.set_request_property` provides an entry point for addons to register properties which will be added to each request. New properties may be reified, effectively caching the return value for the lifetime of the instance. Common use-cases for this would be to get a database connection for the request or identify the current user. The new method :meth:`pyramid.request.Request.set_property` has been added, as well, but the configurator method should be preferred as it provides conflict detection and consistency in the lifetime of the properties. Not Found and Forbidden View Helpers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Not Found helpers: - New API: :meth:`pyramid.config.Configurator.add_notfound_view`. This is a wrapper for :meth:`pyramid.config.Configurator.add_view` which provides support for an "append_slash" feature as well as doing the right thing when it comes to permissions (a Not Found View should always be public). It should be preferred over calling ``add_view`` directly with ``context=HTTPNotFound`` as was previously recommended. - New API: :class:`pyramid.view.notfound_view_config`. This is a decorator constructor like :class:`pyramid.view.view_config` that calls :meth:`pyramid.config.Configurator.add_notfound_view` when scanned. It should be preferred over using ``pyramid.view.view_config`` with ``context=HTTPNotFound`` as was previously recommended. Forbidden helpers: - New API: :meth:`pyramid.config.Configurator.add_forbidden_view`. This is a wrapper for :meth:`pyramid.config.Configurator.add_view` which does the right thing about permissions. It should be preferred over calling ``add_view`` directly with ``context=HTTPForbidden`` as was previously recommended. - New API: :class:`pyramid.view.forbidden_view_config`. This is a decorator constructor like :class:`pyramid.view.view_config` that calls :meth:`pyramid.config.Configurator.add_forbidden_view` when scanned. It should be preferred over using ``pyramid.view.view_config`` with ``context=HTTPForbidden`` as was previously recommended. Minor Feature Additions ----------------------- - New APIs: :class:`pyramid.path.AssetResolver` and :class:`pyramid.path.DottedNameResolver`. The former can be used to resolve an :term:`asset specification` to an API that can be used to read the asset's data, the latter can be used to resolve a :term:`dotted Python name` to a module or a package. - A ``mako.directories`` setting is no longer required to use Mako templates Rationale: Mako template renderers can be specified using an absolute asset spec. An entire application can be written with such asset specs, requiring no ordered lookup path. - ``bpython`` interpreter compatibility in ``pshell``. See :ref:`ipython_or_bpython` for more information. - Added :func:`pyramid.paster.get_appsettings` API function. This function returns the settings defined within an ``[app:...]`` section in a PasteDeploy ``ini`` file. - Added :func:`pyramid.paster.setup_logging` API function. This function sets up Python logging according to the logging configuration in a PasteDeploy ``ini`` file. - Configuration conflict reporting is reported in a more understandable way ("Line 11 in file..." vs. a repr of a tuple of similar info). - We allow extra keyword arguments to be passed to the :meth:`pyramid.config.Configurator.action` method. - Responses generated by Pyramid's :class:`pyramid.static.static_view` now use a ``wsgi.file_wrapper`` (see http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling) when one is provided by the web server. - The :meth:`pyramid.config.Configurator.scan` method can be passed an ``ignore`` argument, which can be a string, a callable, or a list consisting of strings and/or callables. This feature allows submodules, subpackages, and global objects from being scanned. See http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for more information about how to use the ``ignore`` argument to ``scan``. - Add :meth:`pyramid.config.Configurator.add_traverser` API method. See :ref:`changing_the_traverser` for more information. This is not a new feature, it just provides an API for adding a traverser without needing to use the ZCA API. - Add :meth:`pyramid.config.Configurator.add_resource_url_adapter` API method. See :ref:`changing_resource_url` for more information. This is not a new feature, it just provides an API for adding a resource url adapter without needing to use the ZCA API. - Better error messages when a view callable returns a value that cannot be converted to a response (for example, when a view callable returns a dictionary without a renderer defined, or doesn't return any value at all). The error message now contains information about the view callable itself as well as the result of calling it. - Better error message when a .pyc-only module is ``config.include`` -ed. This is not permitted due to error reporting requirements, and a better error message is shown when it is attempted. Previously it would fail with something like "AttributeError: 'NoneType' object has no attribute 'rfind'". - The system value ``req`` is now supplied to renderers as an alias for ``request``. This means that you can now, for example, in a template, do ``req.route_url(...)`` instead of ``request.route_url(...)``. This is purely a change to reduce the amount of typing required to use request methods and attributes from within templates. The value ``request`` is still available too, this is just an alternative. - A new interface was added: :class:`pyramid.interfaces.IResourceURL`. An adapter implementing its interface can be used to override resource URL generation when :meth:`pyramid.request.Request.resource_url` is called. This interface replaces the now-deprecated ``pyramid.interfaces.IContextURL`` interface. - The dictionary passed to a resource's ``__resource_url__`` method (see :ref:`overriding_resource_url_generation`) now contains an ``app_url`` key, representing the application URL generated during :meth:`pyramid.request.Request.resource_url`. It represents a potentially customized URL prefix, containing potentially custom scheme, host and port information passed by the user to ``request.resource_url``. It should be used instead of ``request.application_url`` where necessary. - The :meth:`pyramid.request.Request.resource_url` API now accepts these arguments: ``app_url``, ``scheme``, ``host``, and ``port``. The app_url argument can be used to replace the URL prefix wholesale during url generation. The ``scheme``, ``host``, and ``port`` arguments can be used to replace the respective default values of ``request.application_url`` partially. - A new API named :meth:`pyramid.request.Request.resource_path` now exists. It works like :meth:`pyramid.request.Request.resource_url` but produces a relative URL rather than an absolute one. - The :meth:`pyramid.request.Request.route_url` API now accepts these arguments: ``_app_url``, ``_scheme``, ``_host``, and ``_port``. The ``_app_url`` argument can be used to replace the URL prefix wholesale during url generation. The ``_scheme``, ``_host``, and ``_port`` arguments can be used to replace the respective default values of ``request.application_url`` partially. - New APIs: :class:`pyramid.response.FileResponse` and :class:`pyramid.response.FileIter`, for usage in views that must serve files "manually". Backwards Incompatibilities --------------------------- - Pyramid no longer runs on Python 2.5. This includes the most recent release of Jython and the Python 2.5 version of Google App Engine. The reason? We could not easily "straddle" Python 2 and 3 versions and support Python 2 versions older than Python 2.6. You will need Python 2.6 or better to run this version of Pyramid. If you need to use Python 2.5, you should use the most recent 1.2.X release of Pyramid. - The names of available scaffolds have changed and the flags supported by ``pcreate`` are different than those that were supported by ``paster create``. For example, ``pyramid_alchemy`` is now just ``alchemy``. - The ``paster`` command is no longer the documented way to create projects, start the server, or run debugging commands. To create projects from scaffolds, ``paster create`` is replaced by the ``pcreate`` console script. To serve up a project, ``paster serve`` is replaced by the ``pserve`` console script. New console scripts named ``pshell``, ``pviews``, ``proutes``, and ``ptweens`` do what their ``paster `` equivalents used to do. All relevant narrative documentation has been updated. Rationale: the Paste and PasteScript packages do not run under Python 3. - The default WSGI server run as the result of ``pserve`` from newly rendered scaffolding is now the ``waitress`` WSGI server instead of the ``paste.httpserver`` server. Rationale: the Paste and PasteScript packages do not run under Python 3. - The ``pshell`` command (see "paster pshell") no longer accepts a ``--disable-ipython`` command-line argument. Instead, it accepts a ``-p`` or ``--python-shell`` argument, which can be any of the values ``python``, ``ipython`` or ``bpython``. - Removed the ``pyramid.renderers.renderer_from_name`` function. It has been deprecated since Pyramid 1.0, and was never an API. - To use ZCML with versions of Pyramid >= 1.3, you will need ``pyramid_zcml`` version >= 0.8 and ``zope.configuration`` version >= 3.8.0. The ``pyramid_zcml`` package version 0.8 is backwards compatible all the way to Pyramid 1.0, so you won't be warned if you have older versions installed and upgrade Pyramid itself "in-place"; it may simply break instead (particularly if you use ZCML's ``includeOverrides`` directive). - String values passed to :meth:`pyramid.request.Request.route_url` or :meth:`pyramid.request.Request.route_path` that are meant to replace "remainder" matches will now be URL-quoted except for embedded slashes. For example:: config.add_route('remain', '/foo*remainder') request.route_path('remain', remainder='abc / def') # -> '/foo/abc%20/%20def' Previously string values passed as remainder replacements were tacked on untouched, without any URL-quoting. But this doesn't really work logically if the value passed is Unicode (raw unicode cannot be placed in a URL or in a path) and it is inconsistent with the rest of the URL generation machinery if the value is a string (it won't be quoted unless by the caller). Some folks will have been relying on the older behavior to tack on query string elements and anchor portions of the URL; sorry, you'll need to change your code to use the ``_query`` and/or ``_anchor`` arguments to ``route_path`` or ``route_url`` to do this now. - If you pass a bytestring that contains non-ASCII characters to :meth:`pyramid.config.Configurator.add_route` as a pattern, it will now fail at startup time. Use Unicode instead. - The ``path_info`` route and view predicates now match against ``request.upath_info`` (Unicode) rather than ``request.path_info`` (indeterminate value based on Python 3 vs. Python 2). This has to be done to normalize matching on Python 2 and Python 3. - The ``match_param`` view predicate no longer accepts a dict. This will have no negative affect because the implementation was broken for dict-based arguments. - The ``pyramid.interfaces.IContextURL`` interface has been deprecated. People have been instructed to use this to register a resource url adapter in the "Hooks" chapter to use to influence :meth:`pyramid.request.Request.resource_url` URL generation for resources found via custom traversers since Pyramid 1.0. The interface still exists and registering an adapter using it as documented in older versions still works, but this interface will be removed from the software after a few major Pyramid releases. You should replace it with an equivalent :class:`pyramid.interfaces.IResourceURL` adapter, registered using the new :meth:`pyramid.config.Configurator.add_resource_url_adapter` API. A deprecation warning is now emitted when a ``pyramid.interfaces.IContextURL`` adapter is found when :meth:`pyramid.request.Request.resource_url` is called. - Remove ``pyramid.config.Configurator.with_context`` class method. It was never an API, it is only used by ``pyramid_zcml`` and its functionality has been moved to that package's latest release. This means that you'll need to use the 0.9.2 or later release of ``pyramid_zcml`` with this release of Pyramid. - The older deprecated ``set_notfound_view`` Configurator method is now an alias for the new ``add_notfound_view`` Configurator method. Likewise, the older deprecated ``set_forbidden_view`` is now an alias for the new ``add_forbidden_view`` Configurator method. This has the following impact: the ``context`` sent to views with a ``(context, request)`` call signature registered via the ``set_notfound_view`` or ``set_forbidden_view`` will now be an exception object instead of the actual resource context found. Use ``request.context`` to get the actual resource context. It's also recommended to disuse ``set_notfound_view`` in favor of ``add_notfound_view``, and disuse ``set_forbidden_view`` in favor of ``add_forbidden_view`` despite the aliasing. Deprecations ------------ - The API documentation for ``pyramid.view.append_slash_notfound_view`` and ``pyramid.view.AppendSlashNotFoundViewFactory`` was removed. These names still exist and are still importable, but they are no longer APIs. Use ``pyramid.config.Configurator.add_notfound_view(append_slash=True)`` or ``pyramid.view.notfound_view_config(append_slash=True)`` to get the same behavior. - The ``set_forbidden_view`` and ``set_notfound_view`` methods of the Configurator were removed from the documentation. They have been deprecated since Pyramid 1.1. - All references to the ``tmpl_context`` request variable were removed from the docs. Its existence in Pyramid is confusing for people who were never Pylons users. It was added as a porting convenience for Pylons users in Pyramid 1.0, but it never caught on because the Pyramid rendering system is a lot different than Pylons' was, and alternate ways exist to do what it was designed to offer in Pylons. It will continue to exist "forever" but it will not be recommended or mentioned in the docs. - Remove references to do-nothing ``pyramid.debug_templates`` setting in all Pyramid-provided .ini files. This setting previously told Chameleon to render better exceptions; now Chameleon always renders nice exceptions regardless of the value of this setting. Known Issues ------------ - As of this writing (the release of Pyramid 1.3b2), if you attempt to install a Pyramid project that used the ``alchemy`` scaffold via ``setup.py develop`` on Python 3.2, it will quit with an installation error while trying to install ``Pygments``. If this happens, please just rerun the ``setup.py develop`` command again, and it will complete successfully. This is due to a minor bug in SQLAlchemy 0.7.5 under Python 3, and has been fixed in a later SQLAlchemy release. Keep an eye on http://www.sqlalchemy.org/trac/ticket/2421 Documentation Enhancements -------------------------- - The :ref:`bfg_sql_wiki_tutorial` has been updated. It now uses ``@view_config`` decorators and an explicit database population script. - Minor updates to the :ref:`bfg_wiki_tutorial`. - A narrative documentation chapter named :ref:`extconfig_narr` was added; it describes how to add a custom :term:`configuration directive`, and how use the :meth:`pyramid.config.Configurator.action` method within custom directives. It also describes how to add :term:`introspectable` objects. - A narrative documentation chapter named :ref:`using_introspection` was added. It describes how to query the introspection system. - Added an API docs chapter for :mod:`pyramid.scaffolds`. - Added a narrative docs chapter named :ref:`scaffolding_chapter`. - Added a description of the ``prequest`` command-line script at :ref:`invoking_a_request`. - Added a section to the "Command-Line Pyramid" chapter named :ref:`making_a_console_script`. - Removed the "Running Pyramid on Google App Engine" tutorial from the main docs. It survives on in the Cookbook (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/deployment/gae.html). Rationale: it provides the correct info for the Python 2.5 version of GAE only, and this version of Pyramid does not support Python 2.5. - Updated the :ref:`changing_the_forbidden_view` section, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_forbidden_view`` or ``forbidden_view_config``. - Updated the :ref:`changing_the_notfound_view` section, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_notfound_view`` or ``notfound_view_config``. - Updated the :ref:`redirecting_to_slash_appended_routes` section, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_notfound_view`` or ``notfound_view_config`` - Updated all tutorials to use ``pyramid.view.forbidden_view_config`` rather than ``pyramid.view.view_config`` with an HTTPForbidden context. Dependency Changes ------------------ - Pyramid no longer depends on the ``zope.component`` package, except as a testing dependency. - Pyramid now depends on the following package versions: zope.interface>=3.8.0, WebOb>=1.2dev, repoze.lru>=0.4, zope.deprecation>=3.5.0, translationstring>=0.4 for Python 3 compatibility purposes. It also, as a testing dependency, depends on WebTest>=1.3.1 for the same reason. - Pyramid no longer depends on the ``Paste`` or ``PasteScript`` packages. These packages are not Python 3 compatible. - Depend on ``venusian`` >= 1.0a3 to provide scan ``ignore`` support. Scaffolding Changes ------------------- - Rendered scaffolds have now been changed to be more relocatable (fewer mentions of the package name within files in the package). - The ``routesalchemy`` scaffold has been renamed ``alchemy``, replacing the older (traversal-based) ``alchemy`` scaffold (which has been retired). - The ``alchemy`` and ``starter`` scaffolds are Python 3 compatible. - The ``starter`` scaffold now uses URL dispatch by default. pyramid-1.6/docs/whatsnew-1.4.rst0000644000076500000240000004162612520062551017403 0ustar michaelstaff00000000000000What's New In Pyramid 1.4 ========================= This article explains the new features in :app:`Pyramid` version 1.4 as compared to its predecessor, :app:`Pyramid` 1.3. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.4, as well as software dependency changes and notable documentation additions. Major Feature Additions ----------------------- The major feature additions in Pyramid 1.4 follow. Third-Party Predicates ~~~~~~~~~~~~~~~~~~~~~~~ - Third-party custom view, route, and subscriber predicates can now be added for use by view authors via :meth:`pyramid.config.Configurator.add_view_predicate`, :meth:`pyramid.config.Configurator.add_route_predicate` and :meth:`pyramid.config.Configurator.add_subscriber_predicate`. So, for example, doing this:: config.add_view_predicate('abc', my.package.ABCPredicate) Might allow a view author to do this in an application that configured that predicate:: @view_config(abc=1) Similar features exist for :meth:`pyramid.config.Configurator.add_route`, and :meth:`pyramid.config.Configurator.add_subscriber`. See :ref:`registering_thirdparty_predicates` for more information. Easy Custom JSON Serialization ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Views can now return custom objects which will be serialized to JSON by a JSON renderer by defining a ``__json__`` method on the object's class. This method should return values natively serializable by ``json.dumps`` (such as ints, lists, dictionaries, strings, and so forth). See :ref:`json_serializing_custom_objects` for more information. The JSON renderer now also allows for the definition of custom type adapters to convert unknown objects to JSON serializations, in case you can't add a ``__json__`` method to returned objects. Partial Mako and Chameleon Template Renderings ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The Mako renderer now supports using a def name in an asset spec. When the def name is present in the asset spec, the system will render the template named def within the template instead of rendering the entire template. An example asset spec which names a def is ``package:path/to/template#defname.mako``. This will render the def named ``defname`` inside the ``template.mako`` template instead of rendering the entire template. The old way of returning a tuple in the form ``('defname', {})`` from the view is supported for backward compatibility. - The Chameleon ZPT renderer now supports using a macro name in an asset spec. When the macro name is present in the asset spec, the system will render the macro listed as a ``define-macro`` and return the result instead of rendering the entire template. An example asset spec: ``package:path/to/template#macroname.pt``. This will render the macro defined as ``macroname`` within the ``template.pt`` template instead of the entire template. Subrequest Support ~~~~~~~~~~~~~~~~~~ - Developers may invoke a subrequest by using the :meth:`pyramid.request.Request.invoke_subrequest` API. This allows a developer to obtain a response from one view callable by issuing a subrequest from within a different view callable. See :ref:`subrequest_chapter` for more information. Minor Feature Additions ----------------------- - :class:`pyramid.authentication.AuthTktAuthenticationPolicy` has been updated to support newer hashing algorithms such as ``sha512``. Existing applications should consider updating if possible for improved security over the default md5 hashing. - :meth:`pyramid.config.Configurator.add_directive` now accepts arbitrary callables like partials or objects implementing ``__call__`` which don't have ``__name__`` and ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621 and https://github.com/Pylons/pyramid/pull/647. - As of this release, the ``request_method`` view/route predicate, when used, will also imply that ``HEAD`` is implied when you use ``GET``. For example, using ``@view_config(request_method='GET')`` is equivalent to using ``@view_config(request_method=('GET', 'HEAD'))``. Using ``@view_config(request_method=('GET', 'POST')`` is equivalent to using ``@view_config(request_method=('GET', 'HEAD', 'POST')``. This is because HEAD is a variant of GET that omits the body, and WebOb has special support to return an empty body when a HEAD is used. - :meth:`pyramid.config.Configurator.add_request_method` has been introduced to support extending request objects with arbitrary callables. This method expands on the now documentation-deprecated :meth:`pyramid.config.Configurator.set_request_property` by supporting methods as well as properties. This method also causes less code to be executed at request construction time than :meth:`~pyramid.config.Configurator.set_request_property`. - The static view machinery now raises rather than returns :class:`pyramid.httpexceptions.HTTPNotFound` and :class:`pyramid.httpexceptions.HTTPMovedPermanently` exceptions, so these can be caught by the Not Found View (and other exception views). - When there is a predicate mismatch exception (seen when no view matches for a given request due to predicates not working), the exception now contains a textual description of the predicate which didn't match. - An :meth:`pyramid.config.Configurator.add_permission` directive method was added to the Configurator. This directive registers a free-standing permission introspectable into the Pyramid introspection system. Frameworks built atop Pyramid can thus use the ``permissions`` introspectable category data to build a comprehensive list of permissions supported by a running system. Before this method was added, permissions were already registered in this introspectable category as a side effect of naming them in an :meth:`pyramid.config.Configurator.add_view` call, this method just makes it possible to arrange for a permission to be put into the ``permissions`` introspectable category without naming it along with an associated view. Here's an example of usage of ``add_permission``:: config = Configurator() config.add_permission('view') - The :func:`pyramid.session.UnencryptedCookieSessionFactoryConfig` function now accepts ``signed_serialize`` and ``signed_deserialize`` hooks which may be used to influence how the sessions are marshalled (by default this is done with HMAC+pickle). - :class:`pyramid.testing.DummyRequest` now supports methods supplied by the ``pyramid.util.InstancePropertyMixin`` class such as ``set_property``. - Request properties and methods added via :meth:`pyramid.config.Configurator.add_request_method` or :meth:`pyramid.config.Configurator.set_request_property` are now available to tweens. - Request properties and methods added via :meth:`pyramid.config.Configurator.add_request_method` or :meth:`pyramid.config.Configurator.set_request_property` are now available in the request object returned from :func:`pyramid.paster.bootstrap`. - ``request.context`` of environment request during :func:`pyramid.paster.bootstrap` is now the root object if a context isn't already set on a provided request. - :class:`pyramid.decorator.reify` is now an API, and was added to the API documentation. - Added the :func:`pyramid.testing.testConfig` context manager, which can be used to generate a configurator in a test, e.g. ``with testing.testConfig(...):``. - A new :func:`pyramid.session.check_csrf_token` convenience API function was added. - A ``check_csrf`` view predicate was added. For example, you can now do ``config.add_view(someview, check_csrf=True)``. When the predicate is checked, if the ``csrf_token`` value in ``request.params`` matches the csrf token in the request's session, the view will be permitted to execute. Otherwise, it will not be permitted to execute. - Add ``Base.metadata.bind = engine`` to ``alchemy`` scaffold, so that tables defined imperatively will work. - Comments with references to documentation sections placed in scaffold ``.ini`` files. - Allow multiple values to be specified to the ``request_param`` view/route predicate as a sequence. Previously only a single string value was allowed. See https://github.com/Pylons/pyramid/pull/705 - Added an HTTP Basic authentication policy at :class:`pyramid.authentication.BasicAuthAuthenticationPolicy`. - The :meth:`pyramid.config.Configurator.testing_securitypolicy` method now returns the policy object it creates. - The DummySecurityPolicy created by :meth:`pyramid.config.Configurator.testing_securitypolicy` now sets a ``forgotten`` value on the policy (the value ``True``) when its ``forget`` method is called. - The DummySecurityPolicy created by :meth:`pyramid.config.Configurator.testing_securitypolicy` now sets a ``remembered`` value on the policy, which is the value of the ``principal`` argument it's called with when its ``remember`` method is called. - New ``physical_path`` view predicate. If specified, this value should be a string or a tuple representing the physical traversal path of the context found via traversal for this predicate to match as true. For example: ``physical_path='/'`` or ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``. It's useful when you want to always potentially show a view when some object is traversed to, but you can't be sure about what kind of object it will be, so you can't use the ``context`` predicate. - Added an ``effective_principals`` route and view predicate. - Do not allow the userid returned from the :func:`pyramid.security.authenticated_userid` or the userid that is one of the list of principals returned by :func:`pyramid.security.effective_principals` to be either of the strings ``system.Everyone`` or ``system.Authenticated`` when any of the built-in authorization policies that live in :mod:`pyramid.authentication` are in use. These two strings are reserved for internal usage by Pyramid and they will no longer be accepted as valid userids. - Allow a ``_depth`` argument to :class:`pyramid.view.view_config`, which will permit limited composition reuse of the decorator by other software that wants to provide custom decorators that are much like view_config. - Allow an iterable of decorators to be passed to :meth:`pyramid.config.Configurator.add_view`. This allows views to be wrapped by more than one decorator without requiring combining the decorators yourself. - :func:`pyramid.security.view_execution_permitted` used to return `True` if no view could be found. It now raises a :exc:`TypeError` exception in that case, as it doesn't make sense to assert that a nonexistent view is execution-permitted. See https://github.com/Pylons/pyramid/issues/299. - Small microspeed enhancement which anticipates that a :class:`pyramid.response.Response` object is likely to be returned from a view. Some code is shortcut if the class of the object returned by a view is this class. A similar microoptimization was done to :func:`pyramid.request.Request.is_response`. - Make it possible to use variable arguments on all ``p*`` commands (``pserve``, ``pshell``, ``pviews``, etc) in the form ``a=1 b=2`` so you can fill in values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini http_port=8080``. - In order to allow people to ignore unused arguments to subscriber callables and to normalize the relationship between event subscribers and subscriber predicates, we now allow both subscribers and subscriber predicates to accept only a single ``event`` argument even if they've been subscribed for notifications that involve multiple interfaces. Backwards Incompatibilities --------------------------- - The Pyramid router no longer adds the values ``bfg.routes.route`` or ``bfg.routes.matchdict`` to the request's WSGI environment dictionary. These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven minor releases ago). If your code depended on these values, use ``request.matched_route`` and ``request.matchdict`` instead. - It is no longer possible to pass an environ dictionary directly to ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka ``ModelGraphTraverser.__call__``). Instead, you must pass a request object. Passing an environment instead of a request has generated a deprecation warning since Pyramid 1.1. - Pyramid will no longer work properly if you use the ``webob.request.LegacyRequest`` as a request factory. Instances of the LegacyRequest class have a ``request.path_info`` which return a string. This Pyramid release assumes that ``request.path_info`` will unconditionally be Unicode. - The functions from ``pyramid.chameleon_zpt`` and ``pyramid.chameleon_text`` named ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response`` have been removed. These have issued a deprecation warning upon import since Pyramid 1.0. Use :func:`pyramid.renderers.get_renderer`, ``pyramid.renderers.get_renderer().implementation()``, :func:`pyramid.renderers.render` or :func:`pyramid.renderers.render_to_response` respectively instead of these functions. - The ``pyramid.configuration`` module was removed. It had been deprecated since Pyramid 1.0 and printed a deprecation warning upon its use. Use :mod:`pyramid.config` instead. - The ``pyramid.paster.PyramidTemplate`` API was removed. It had been deprecated since Pyramid 1.1 and issued a warning on import. If your code depended on this, adjust your code to import :class:`pyramid.scaffolds.PyramidTemplate` instead. - The ``pyramid.settings.get_settings()`` API was removed. It had been printing a deprecation warning since Pyramid 1.0. If your code depended on this API, use ``pyramid.threadlocal.get_current_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). - These APIs from the ``pyramid.testing`` module were removed. They have been printing deprecation warnings since Pyramid 1.0: * ``registerDummySecurityPolicy``, use :meth:`pyramid.config.Configurator.testing_securitypolicy` instead. * ``registerResources`` (aka ``registerModels``), use :meth:`pyramid.config.Configurator.testing_resources` instead. * ``registerEventListener``, use :meth:`pyramid.config.Configurator.testing_add_subscriber` instead. * ``registerTemplateRenderer`` (aka ``registerDummyRenderer``), use :meth:`pyramid.config.Configurator.testing_add_renderer` instead. * ``registerView``, use :meth:`pyramid.config.Configurator.add_view` instead. * ``registerUtility``, use :meth:`pyramid.config.Configurator.registry.registerUtility` instead. * ``registerAdapter``, use :meth:`pyramid.config.Configurator.registry.registerAdapter` instead. * ``registerSubscriber``, use :meth:`pyramid.config.Configurator.add_subscriber` instead. * ``registerRoute``, use :meth:`pyramid.config.Configurator.add_route` instead. * ``registerSettings``, use :meth:`pyramid.config.Configurator.add_settings` instead. - In Pyramid 1.3 and previous, the ``__call__`` method of a Response object returned by a view was invoked before any finished callbacks were executed. As of this release, the ``__call__`` method of a Response object is invoked *after* finished callbacks are executed. This is in support of the :meth:`pyramid.request.Request.invoke_subrequest` feature. Deprecations ------------ - The :meth:`pyramid.config.Configurator.set_request_property` directive has been documentation-deprecated. The method remains usable but the more featureful :meth:`pyramid.config.Configurator.add_request_method` should be used in its place (it has all of the same capabilities but can also extend the request object with methods). - :class:`pyramid.authentication.AuthTktAuthenticationPolicy` will emit a deprecation warning if an application is using the policy without explicitly passing a ``hashalg`` argument. This is because the default is "md5" which is considered theoretically subject to collision attacks. If you really want "md5" then you must specify it explicitly to get rid of the warning. Documentation Enhancements -------------------------- - Added an :ref:`upgrading_chapter` chapter to the narrative documentation. It describes how to cope with deprecations and removals of Pyramid APIs and how to show Pyramid-generated deprecation warnings while running tests and while running a server. - Added a :ref:`subrequest_chapter` chapter to the narrative documentation. - All of the tutorials that use :class:`pyramid.authentication.AuthTktAuthenticationPolicy` now explicitly pass ``sha512`` as a ``hashalg`` argument. - Many cleanups and improvements to narrative and API docs. Dependency Changes ------------------ - Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on 1.2dev+). This is to ensure that we obtain a version of WebOb that returns ``request.path_info`` as text. pyramid-1.6/docs/whatsnew-1.5.rst0000644000076500000240000005632212642137112017404 0ustar michaelstaff00000000000000What's New In Pyramid 1.5 ========================= This article explains the new features in :app:`Pyramid` version 1.5 as compared to its predecessor, :app:`Pyramid` 1.4. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.5, as well as software dependency changes and notable documentation additions. Major Backwards Incompatibilities --------------------------------- - Pyramid no longer depends on or configures the Mako and Chameleon templating system renderers by default. Disincluding these templating systems by default means that the Pyramid core has fewer dependencies and can run on future platforms without immediate concern for the compatibility of its templating add-ons. It also makes maintenance slightly more effective, as different people can maintain the templating system add-ons that they understand and care about without needing commit access to the Pyramid core, and it allows users who just don't want to see any packages they don't use come along for the ride when they install Pyramid. This means that upon upgrading to Pyramid 1.5a2+, projects that use either of these templating systems will see a traceback that ends something like this when their application attempts to render a Chameleon or Mako template:: ValueError: No such renderer factory .pt Or:: ValueError: No such renderer factory .mako Or:: ValueError: No such renderer factory .mak Support for Mako templating has been moved into an add-on package named ``pyramid_mako``, and support for Chameleon templating has been moved into an add-on package named ``pyramid_chameleon``. These packages are drop-in replacements for the old built-in support for these templating langauges. All you have to do is install them and make them active in your configuration to register renderer factories for ``.pt`` and/or ``.mako`` (or ``.mak``) to make your application work again. To re-add support for Chameleon and/or Mako template renderers into your existing projects, follow the below steps. If you depend on Mako templates: * Make sure the ``pyramid_mako`` package is installed. One way to do this is by adding ``pyramid_mako`` to the ``install_requires`` section of your package's ``setup.py`` file and afterwards rerunning ``setup.py develop``:: setup( #... install_requires=[ 'pyramid_mako', # new dependency 'pyramid', #... ], ) * Within the portion of your application which instantiates a Pyramid :class:`~pyramid.config.Configurator` (often the ``main()`` function in your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_mako`` includeme:: config = Configurator(.....) config.include('pyramid_mako') If you depend on Chameleon templates: * Make sure the ``pyramid_chameleon`` package is installed. One way to do this is by adding ``pyramid_chameleon`` to the ``install_requires`` section of your package's ``setup.py`` file and afterwards rerunning ``setup.py develop``:: setup( #... install_requires=[ 'pyramid_chameleon', # new dependency 'pyramid', #... ], ) * Within the portion of your application which instantiates a Pyramid :class:`~pyramid.config.Configurator` (often the ``main()`` function in your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_chameleon`` includeme:: config = Configurator(.....) config.include('pyramid_chameleon') Note that it's also fine to install these packages into *older* Pyramids for forward compatibility purposes. Even if you don't upgrade to Pyramid 1.5 immediately, performing the above steps in a Pyramid 1.4 installation is perfectly fine, won't cause any difference, and will give you forward compatibility when you eventually do upgrade to Pyramid 1.5. With the removal of Mako and Chameleon support from the core, some unit tests that use the ``pyramid.renderers.render*`` methods may begin to fail. If any of your unit tests are invoking either ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response()`` with either Mako or Chameleon templates then the ``pyramid.config.Configurator`` instance in effect during the unit test should be also be updated to include the addons, as shown above. For example:: class ATest(unittest.TestCase): def setUp(self): self.config = pyramid.testing.setUp() self.config.include('pyramid_mako') def test_it(self): result = pyramid.renderers.render('mypkg:templates/home.mako', {}) Or:: class ATest(unittest.TestCase): def setUp(self): self.config = pyramid.testing.setUp() self.config.include('pyramid_chameleon') def test_it(self): result = pyramid.renderers.render('mypkg:templates/home.pt', {}) - If you're using the Pyramid debug toolbar, when you upgrade Pyramid to 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to at least version 1.0.8, as older toolbar versions are not compatible with Pyramid 1.5a2+ due to the removal of Mako support from the core. It's fine to use this newer version of the toolbar code with older Pyramids too. Feature Additions ----------------- The feature additions in Pyramid 1.5 follow. - Python 3.4 compatibility. - Add ``pdistreport`` script, which prints the Python version in use, the Pyramid version in use, and the version number and location of all Python distributions currently installed. - Add the ability to invert the result of any view, route, or subscriber predicate value using the ``not_`` class. For example: .. code-block:: python from pyramid.config import not_ @view_config(route_name='myroute', request_method=not_('POST')) def myview(request): ... The above example will ensure that the view is called if the request method is not POST, at least if no other view is more specific. The :class:`pyramid.config.not_` class can be used against any value that is a predicate value passed in any of these contexts: - :meth:`pyramid.config.Configurator.add_view` - :meth:`pyramid.config.Configurator.add_route` - :meth:`pyramid.config.Configurator.add_subscriber` - :meth:`pyramid.view.view_config` - :meth:`pyramid.events.subscriber` - View lookup will now search for valid views based on the inheritance hierarchy of the context. It tries to find views based on the most specific context first, and upon predicate failure, will move up the inheritance chain to test views found by the super-type of the context. In the past, only the most specific type containing views would be checked and if no matching view could be found then a PredicateMismatch would be raised. Now predicate mismatches don't hide valid views registered on super-types. Here's an example that now works: .. code-block:: python class IResource(Interface): ... @view_config(context=IResource) def get(context, request): ... @view_config(context=IResource, request_method='POST') def post(context, request): ... @view_config(context=IResource, request_method='DELETE') def delete(context, request): ... @implementer(IResource) class MyResource: ... @view_config(context=MyResource, request_method='POST') def override_post(context, request): ... Previously the override_post view registration would hide the get and delete views in the context of MyResource -- leading to a predicate mismatch error when trying to use GET or DELETE methods. Now the views are found and no predicate mismatch is raised. See https://github.com/Pylons/pyramid/pull/786 and https://github.com/Pylons/pyramid/pull/1004 and https://github.com/Pylons/pyramid/pull/1046 - ``scripts/prequest.py`` (aka the ``prequest`` console script): added support for submitting ``PUT`` and ``PATCH`` requests. See https://github.com/Pylons/pyramid/pull/1033. add support for submitting ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify basic authentication credentials in the request via a ``--login`` argument to the script. See https://github.com/Pylons/pyramid/pull/1039. - The :meth:`pyramid.config.Configurator.add_route` method now supports being called with an external URL as pattern. See https://github.com/Pylons/pyramid/issues/611 and the documentation section :ref:`external_route_narr`. - :class:`pyramid.authorization.ACLAuthorizationPolicy` supports ``__acl__`` as a callable. This removes the ambiguity between the potential ``AttributeError`` that would be raised on the ``context`` when the property was not defined and the ``AttributeError`` that could be raised from any user-defined code within a dynamic property. It is recommended to define a dynamic ACL as a callable to avoid this ambiguity. See https://github.com/Pylons/pyramid/issues/735. - Allow a protocol-relative URL (e.g. ``//example.com/images``) to be passed to :meth:`pyramid.config.Configurator.add_static_view`. This allows externally-hosted static URLs to be generated based on the current protocol. - The :class:`pyramid.authentication.AuthTktAuthenticationPolicy` class has two new options to configure its domain usage: * ``parent_domain``: if set the authentication cookie is set on the parent domain. This is useful if you have multiple sites sharing the same domain. * ``domain``: if provided the cookie is always set for this domain, bypassing all usual logic. See https://github.com/Pylons/pyramid/pull/1028, https://github.com/Pylons/pyramid/pull/1072 and https://github.com/Pylons/pyramid/pull/1078. - The :class:`pyramid.authentication.AuthTktPolicy` now supports IPv6 addresses when using the ``include_ip=True`` option. This is possibly incompatible with alternative ``auth_tkt`` implementations, as the specification does not define how to properly handle IPv6. See https://github.com/Pylons/pyramid/issues/831. - Make it possible to use variable arguments via :func:`pyramid.paster.get_appsettings`. This also allowed the generated ``initialize_db`` script from the ``alchemy`` scaffold to grow support for options in the form ``a=1 b=2`` so you can fill in values in a parameterized ``.ini`` file, e.g. ``initialize_myapp_db etc/development.ini a=1 b=2``. See https://github.com/Pylons/pyramid/pull/911 - The ``request.session.check_csrf_token()`` method and the ``check_csrf`` view predicate now take into account the value of the HTTP header named ``X-CSRF-Token`` (as well as the ``csrf_token`` form parameter, which they always did). The header is tried when the form parameter does not exist. - You can now generate "hybrid" urldispatch/traversal URLs more easily by using the new ``route_name``, ``route_kw`` and ``route_remainder_name`` arguments to :meth:`~pyramid.request.Request.resource_url` and :meth:`~pyuramid.request.Request.resource_path`. See :ref:`generating_hybrid_urls`. - A new http exception superclass named :class:`~pyramid.httpexceptions.HTTPSuccessful` was added. You can use this class as the ``context`` of an exception view to catch all 200-series "exceptions" (e.g. "raise HTTPOk"). This also allows you to catch *only* the :class:`~pyramid.httpexceptions.HTTPOk` exception itself; previously this was impossible because a number of other exceptions (such as ``HTTPNoContent``) inherited from ``HTTPOk``, but now they do not. - It is now possible to escape double braces in Pyramid scaffolds (unescaped, these represent replacement values). You can use ``\{\{a\}\}`` to represent a "bare" ``{{a}}``. See https://github.com/Pylons/pyramid/pull/862 - Add ``localizer`` and ``locale_name`` properties (reified) to :class:`pyramid.request.Request`. See https://github.com/Pylons/pyramid/issues/508. Note that the :func:`pyramid.i18n.get_localizer` and :func:`pyramid.i18n.get_locale_name` functions now simply look up these properties on the request. - The ``pserve`` command now takes a ``-v`` (or ``--verbose``) flag and a ``-q`` (or ``--quiet``) flag. Output from running ``pserve`` can be controlled using these flags. ``-v`` can be specified multiple times to increase verbosity. ``-q`` sets verbosity to ``0`` unconditionally. The default verbosity level is ``1``. - The ``alchemy`` scaffold tests now provide better coverage. See https://github.com/Pylons/pyramid/pull/1029 - Users can now provide dotted Python names to as the ``factory`` argument the Configurator methods named :meth:`~pyramid.config.Configurator.add_view_predicate`, :meth:`~pyramid.config.Configurator.add_route_predicate` and :meth:`~pyramid.config.Configurator.add_subscriber_predicate`. Instead of passing the predicate factory directly, you can pass a dotted name which refers to the factory. - :func:`pyramid.path.package_name` no longer thows an exception when resolving the package name for namespace packages that have no ``__file__`` attribute. - An authorization API has been added as a method of the request: :meth:`pyramid.request.Request.has_permission`. It is a method-based alternative to the :func:`pyramid.security.has_permission` API and works exactly the same. The older API is now deprecated. - Property API attributes have been added to the request for easier access to authentication data: :attr:`pyramid.request.Request.authenticated_userid`, :attr:`pyramid.request.Request.unauthenticated_userid`, and :attr:`pyramid.request.Request.effective_principals`. These are analogues, respectively, of :func:`pyramid.security.authenticated_userid`, :func:`pyramid.security.unauthenticated_userid`, and :func:`pyramid.security.effective_principals`. They operate exactly the same, except they are attributes of the request instead of functions accepting a request. They are properties, so they cannot be assigned to. The older function-based APIs are now deprecated. - Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run directly, allowing custom arguments to be sent to the python interpreter at runtime. For example:: python -3 -m pyramid.scripts.pserve development.ini - Added a specific subclass of :class:`pyramid.httpexceptions.HTTPBadRequest` named :class:`pyramid.exceptions.BadCSRFToken` which will now be raised in response to failures in the ``check_csrf_token`` view predicate. See https://github.com/Pylons/pyramid/pull/1149 - Added a new ``SignedCookieSessionFactory`` which is very similar to the ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on signing content. The custom serializer arguments to this function should only focus on serializing, unlike its predecessor which required the serializer to also perform signing. See https://github.com/Pylons/pyramid/pull/1142 . Note that cookies generated using ``SignedCookieSessionFactory`` are not compatible with cookies generated using ``UnencryptedCookieSessionFactory``, so existing user session data will be destroyed if you switch to it. - Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie factory that can be used by framework implementors to create their own session implementations. It provides a reusable API which focuses strictly on providing a dictionary-like object that properly handles renewals, timeouts, and conformance with the ``ISession`` API. See https://github.com/Pylons/pyramid/pull/1142 - We no longer eagerly clear ``request.exception`` and ``request.exc_info`` in the exception view tween. This makes it possible to inspect exception information within a finished callback. See https://github.com/Pylons/pyramid/issues/1223. Other Backwards Incompatibilities --------------------------------- - Modified the :meth:`~pyramid.request.Reuqest.current_route_url` method. The method previously returned the URL without the query string by default, it now does attach the query string unless it is overriden. - The :meth:`~pyramid.request.Request.route_url` and :meth:`~pyramid.request.Request.route_path` APIs no longer quote ``/`` to ``%2F`` when a replacement value contains a ``/``. This was pointless, as WSGI servers always unquote the slash anyway, and Pyramid never sees the quoted value. - It is no longer possible to set a ``locale_name`` attribute of the request, nor is it possible to set a ``localizer`` attribute of the request. These 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 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. - The :class:`pyramid.interfaces.IResourceURL` interface has now grown two new attributes: ``virtual_path_tuple`` and ``physical_path_tuple``. These should be the tuple form of the resource's path (physical and virtual). - Removed the ``request.response_*`` varying attributes (such as``request.response_headers``) . These attributes had been deprecated since Pyramid 1.1, and as per the deprecation policy, have now been removed. - ``request.response`` will no longer be mutated when using the :func:`pyramid.renderers.render` API. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``), but this is only necessary when the renderer is generating a response; it was a bug when it was done as a side effect of calling :func:`pyramid.renderers.render`. - Removed the ``bfg2pyramid`` fixer script. - The :class:`pyramid.events.NewResponse` event is now sent **after** response callbacks are executed. It previously executed before response callbacks were executed. Rationale: it's more useful to be able to inspect the response after response callbacks have done their jobs instead of before. - Removed the class named ``pyramid.view.static`` that had been deprecated since Pyramid 1.1. Instead use :class:`pyramid.static.static_view` with the ``use_subpath=True`` argument. - Removed the ``pyramid.view.is_response`` function that had been deprecated since Pyramid 1.1. Use the :meth:`pyramid.request.Request.is_response` method instead. - Removed the ability to pass the following arguments to :meth:`pyramid.config.Configurator.add_route`: ``view``, ``view_context``. ``view_for``, ``view_permission``, ``view_renderer``, and ``view_attr``. Using these arguments had been deprecated since Pyramid 1.1. Instead of passing view-related arguments to ``add_route``, use a separate call to :meth:`pyramid.config.Configurator.add_view` to associate a view with a route using its ``route_name`` argument. Note that this impacts the :meth:`pyramid.config.Configurator.add_static_view` function too, because it delegates to``add_route``. - Removed the ability to influence and query a :class:`pyramid.request.Request` object as if it were a dictionary. Previously it was possible to use methods like ``__getitem__``, ``get``, ``items``, and other dictlike methods to access values in the WSGI environment. This behavior had been deprecated since Pyramid 1.1. Use methods of ``request.environ`` (a real dictionary) instead. - Removed ancient backwards compatibily hack in ``pyramid.traversal.DefaultRootFactory`` which populated the ``__dict__`` of the factory with the matchdict values for compatibility with BFG 0.9. - The ``renderer_globals_factory`` argument to the :class:`pyramid.config.Configurator` constructor and the coresponding argument to :meth:`~pyramid.config.Configurator.setup_registry` has been removed. The ``set_renderer_globals_factory`` method of :class:`~pyramid.config.Configurator` has also been removed. The (internal) ``pyramid.interfaces.IRendererGlobals`` interface was also removed. These arguments, methods and interfaces had been deprecated since 1.1. Use a ``BeforeRender`` event subscriber as documented in the "Hooks" chapter of the Pyramid narrative documentation instead of providing renderer globals values to the configurator. - The key/values in the ``_query`` parameter of :meth:`pyramid.request.Request.route_url` and the ``query`` parameter of :meth:`pyramid.request.Request.resource_url` (and their variants), used to encode a value of ``None`` as the string ``'None'``, leaving the resulting query string to be ``a=b&key=None``. The value is now dropped in this situation, leaving a query string of ``a=b&key=``. See https://github.com/Pylons/pyramid/issues/1119 Deprecations ------------ - Returning a ``("defname", dict)`` tuple from a view which has a Mako renderer is now deprecated. Instead you should use the renderer spelling ``foo#defname.mak`` in the view configuration definition and return a dict only. - The :meth:`pyramid.config.Configurator.set_request_property` method now issues a deprecation warning when used. It had been docs-deprecated in 1.4 but did not issue a deprecation warning when used. - :func:`pyramid.security.has_permission` is now deprecated in favor of using :meth:`pyramid.request.Request.has_permission`. - The :func:`pyramid.security.authenticated_userid`, :func:`pyramid.security.unauthenticated_userid`, and :func:`pyramid.security.effective_principals` functions have been deprecated. Use :attr:`pyramid.request.Request.authenticated_userid`, :attr:`pyramid.request.Request.unauthenticated_userid` and :attr:`pyramid.request.Request.effective_principals` instead. - Deprecate the ``pyramid.interfaces.ITemplateRenderer`` interface. It was ill-defined and became unused when Mako and Chameleon template bindings were split into their own packages. - The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been deprecated and is superseded by the ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies generated by the ``UnencryptedCookieSessionFactoryConfig`` are compatible with cookies generated by old releases, cookies generated by the SignedCookieSessionFactory are not. See https://github.com/Pylons/pyramid/pull/1142 Documentation Enhancements -------------------------- - A new documentation chapter named :ref:`quick_tour` was added. It describes starting out with Pyramid from a high level. - Added a :ref:`quick_tutorial` to go with the Quick Tour - Many other enhancements. Scaffolding Enhancements ------------------------ - All scaffolds have a new HTML + CSS theme. - Updated docs and scaffolds to keep in step with new 2.0 release of ``Lingua``. This included removing all ``setup.cfg`` files from scaffolds and documentation environments. Dependency Changes ------------------ - Pyramid no longer depends upon ``Mako`` or ``Chameleon``. - Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile`` from 1.3+). pyramid-1.6/docs/whatsnew-1.6.rst0000644000076500000240000002571512642137120017406 0ustar michaelstaff00000000000000What's New in Pyramid 1.6 ========================= This article explains the new features in :app:`Pyramid` version 1.6 as compared to its predecessor, :app:`Pyramid` 1.5. It also documents backwards incompatibilities between the two versions and deprecations added to :app:`Pyramid` 1.6, as well as software dependency changes and notable documentation additions. Backwards Incompatibilities --------------------------- - IPython and BPython support have been removed from pshell in the core. To continue using them on Pyramid 1.6+, you must install the binding packages explicitly. One way to do this is by adding ``pyramid_ipython`` (or ``pyramid_bpython``) to the ``install_requires`` section of your package's ``setup.py`` file, then re-running ``setup.py develop``:: setup( #... install_requires=[ 'pyramid_ipython', # new dependency 'pyramid', #... ], ) - ``request.response`` will no longer be mutated when using the :func:`~pyramid.renderers.render_to_response` API. It is now necessary to pass in a ``response=`` argument to :func:`~pyramid.renderers.render_to_response` if you wish to supply the renderer with a custom response object. If you do not pass one, then a response object will be created using the current response factory. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``). However, when invoking ``render_to_response``, it is not expected that the response object being returned would be the same one used later in the request. The response object returned from ``render_to_response`` is now explicitly different from ``request.response``. This does not change the API of a renderer. See https://github.com/Pylons/pyramid/pull/1563 Feature Additions ----------------- - Python 3.5 and pypy3 compatibility. - ``pserve --reload`` will no longer crash on syntax errors. See https://github.com/Pylons/pyramid/pull/2044 - Cache busting for static resources has been added and is available via a new :meth:`pyramid.config.Configurator.add_cache_buster` API. Core APIs are shipped for both cache busting via query strings and via asset manifests for integrating into custom asset pipelines. See https://github.com/Pylons/pyramid/pull/1380 and https://github.com/Pylons/pyramid/pull/1583 and https://github.com/Pylons/pyramid/pull/2171 - Assets can now be overidden by an absolute path on the filesystem when using the :meth:`~pyramid.config.Configurator.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 :meth:`~pyramid.request.Request.static_url` API and :meth:`~pyramid.config.Configurator.add_static_view`. Previously it was not possible to use :meth:`~pyramid.config.Configurator.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 :meth:`~pyramid.config.Configurator.set_response_factory` and the ``response_factory`` keyword argument to the constructor of :class:`~pyramid.config.Configurator` for defining a factory that will return a custom ``Response`` class. See https://github.com/Pylons/pyramid/pull/1499 - Added :attr:`pyramid.config.Configurator.root_package` attribute and init parameter to assist with includible packages that wish to resolve resources relative to the package in which the configurator was created. This is especially useful for add-ons that need to load asset specs from settings, in which case it 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 - Overall improvments for the ``proutes`` command. Added ``--format`` and ``--glob`` arguments to the command, introduced the ``method`` column for displaying available request methods, and improved the ``view`` output by showing the module instead of just ``__repr__``. See https://github.com/Pylons/pyramid/pull/1488 - ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 - Support keyword-only arguments and function annotations in views in Python 3. See https://github.com/Pylons/pyramid/pull/1556 - The ``append_slash`` argument of :meth:`~pyramid.config.Configurator.add_notfound_view()` will now accept anything that implements the :class:`~pyramid.interfaces.IResponse` interface and will use that as the response class instead of the default :class:`~pyramid.httpexceptions.HTTPFound`. See https://github.com/Pylons/pyramid/pull/1610 - The :class:`~pyramid.config.Configurator` has grown the ability to allow actions to call other actions during a commit cycle. This enables much more logic to be placed into actions, such as the ability to invoke other actions or group them for improved conflict detection. We have also exposed and documented the configuration phases that Pyramid uses in order to further assist in building conforming add-ons. See https://github.com/Pylons/pyramid/pull/1513 - Allow an iterator to be returned from a renderer. Previously it was only possible to return bytes or unicode. See https://github.com/Pylons/pyramid/pull/1417 - Improve robustness to timing attacks in the :class:`~pyramid.authentication.AuthTktCookieHelper` and the :class:`~pyramid.session.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 - Improve the readability of the ``pcreate`` shell script output. See https://github.com/Pylons/pyramid/pull/1453 - 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 :meth:`~pyramid.config.Configurator.add_notfound_view` and :meth:`~pyramid.config.Configurator.add_forbidden_view` See https://github.com/Pylons/pyramid/issues/494 - 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 - 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 - ``pcreate`` when run without a scaffold argument will now print information on the missing flag, as well as a list of available scaffolds. See https://github.com/Pylons/pyramid/pull/1566 and https://github.com/Pylons/pyramid/issues/1297 - ``pcreate`` will now ask for confirmation if invoked with an argument for a project name that already exists or is importable in the current environment. See https://github.com/Pylons/pyramid/issues/1357 and https://github.com/Pylons/pyramid/pull/1837 - Add :func:`pyramid.request.apply_request_extensions` function which can be used in testing to apply any request extensions configured via ``config.add_request_method``. Previously it was only possible to test the extensions by going through Pyramid's router. See https://github.com/Pylons/pyramid/pull/1581 - Make it possible to subclass ``pyramid.request.Request`` and also use ``pyramid.request.Request.add_request.method``. See https://github.com/Pylons/pyramid/issues/1529 - Additional shells for ``pshell`` can now be registered as entry points. See https://github.com/Pylons/pyramid/pull/1891 and https://github.com/Pylons/pyramid/pull/2012 - The variables injected into ``pshell`` are now displayed with their docstrings instead of the default ``str(obj)`` when possible. See https://github.com/Pylons/pyramid/pull/1929 Deprecations ------------ - The ``pserve`` command's daemonization features, as well as ``--monitor-restart``, have been deprecated. This includes the ``[start,stop,restart,status]`` subcommands, as well as the ``--daemon``, ``--stop-daemon``, ``--pid-file``, ``--status``, ``--user``, and ``--group`` flags. See https://github.com/Pylons/pyramid/pull/2120 and https://github.com/Pylons/pyramid/pull/2189 and https://github.com/Pylons/pyramid/pull/1641 Please use a real process manager in the future instead of relying on ``pserve`` to daemonize itself. Many options exist, including your operating system's services, such as Systemd or Upstart, as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/1641 and https://github.com/Pylons/pyramid/pull/2120 - The ``principal`` argument to :func:`pyramid.security.remember` was renamed to ``userid``. Using ``principal`` as the argument name still works and will continue to work for the next few releases, but a deprecation warning is printed. Scaffolding Enhancements ------------------------ - Added line numbers to the log formatters in the scaffolds to assist with debugging. See https://github.com/Pylons/pyramid/pull/1326 - Updated scaffold generating machinery to return the version of :app:`Pyramid` and its documentation for use in scaffolds. Updated ``starter``, ``alchemy`` and ``zodb`` templates to have links to correctly versioned documentation, and to reflect which :app:`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. Documentation Enhancements -------------------------- - Removed logging configuration from Quick Tutorial ``ini`` files, except for scaffolding- and logging-related chapters, to avoid needing to explain it too early. - Improve and clarify the documentation on what :app:`Pyramid` defines as a ``principal`` and a ``userid`` in its security APIs. See https://github.com/Pylons/pyramid/pull/1399 - Moved the documentation for ``accept`` on :meth:`pyramid.config.Configurator.add_view` to no longer be part of the predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. See https://github.com/Pylons/pyramid/pull/1487 for this PR. - Clarify a previously-implied detail of the ``ISession.invalidate`` API documentation. - Add documentation of command line programs (``p*`` scripts). See https://github.com/Pylons/pyramid/pull/2191 pyramid-1.6/hacking-tox.ini0000644000076500000240000000065012520062551016466 0ustar michaelstaff00000000000000[tox] envlist = env27,ttw27-create,ttw27-install [testenv:env27] envdir = env27 basepython = python2.7 usedevelop = True deps = setuptools-git commands = python setup.py dev [testenv:ttw27-create] envdir = env27 usedevelop = True changedir = env27 commands = pcreate -s starter hacking [testenv:ttw27-install] envdir = env27 usedevelop = True changedir = env27/hacking commands = python setup.py develop pyramid-1.6/HACKING.txt0000644000076500000240000002044312642135264015367 0ustar michaelstaff00000000000000Hacking on Pyramid ================== Here are some guidelines for hacking on Pyramid. Using a Development Checkout ---------------------------- You'll have to create a development environment to hack on Pyramid, using a Pyramid checkout. You can either do this by hand, or if you have ``tox`` installed (it's on PyPI), you can use tox to set up a working development environment. Each installation method is described below. By Hand +++++++ - While logged into your GitHub account, navigate to the Pyramid repo on GitHub. https://github.com/Pylons/pyramid - Fork and clone the Pyramid repository to your GitHub account by clicking the "Fork" button. - Clone your fork of Pyramid from your GitHub account to your local computer, substituting your account username and specifying the destination as "hack-on-pyramid". $ cd ~ $ git clone git@github.com:USERNAME/pyramid.git hack-on-pyramid $ cd hack-on-pyramid # Configure remotes such that you can pull changes from the Pyramid # repository into your local repository. $ 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 Now your local repo is set up such that you will push changes to your GitHub repo, from which you can submit a pull request. - Create a virtualenv in which to install Pyramid: $ cd ~/hack-on-pyramid $ virtualenv -ppython2.7 env Note that very old versions of virtualenv (virtualenv versions below, say, 1.10 or thereabouts) require you to pass a ``--no-site-packages`` flag to get a completely isolated environment. You can choose which Python version you want to use by passing a ``-p`` flag to ``virtualenv``. For example, ``virtualenv -ppython2.7`` chooses the Python 2.7 interpreter to be installed. From here on in within these instructions, the ``~/hack-on-pyramid/env`` virtual environment you created above will be referred to as ``$VENV``. To use the instructions in the steps that follow literally, use the ``export VENV=~/hack-on-pyramid/env`` command. - Install ``setuptools-git`` into the virtualenv (for good measure, as we're using git to do version control): $ $VENV/bin/easy_install setuptools-git - Install Pyramid from the checkout into the virtualenv using ``setup.py dev``. ``setup.py dev`` is an alias for "setup.py develop" which also installs testing requirements such as nose and coverage. Running ``setup.py dev`` *must* be done while the current working directory is the ``pyramid`` checkout directory: $ cd ~/hack-on-pyramid $ $VENV/bin/python setup.py dev - Optionally create a new Pyramid project using ``pcreate``: $ cd $VENV $ bin/pcreate -s starter starter - ...and install the new project (also using ``setup.py develop``) into the virtualenv: $ cd $VENV/starter $ $VENV/bin/python setup.py develop Using Tox +++++++++ Alternatively, if you already have ``tox`` installed, there is an easier way to get going. - Create a new directory somewhere and ``cd`` to it: $ mkdir ~/hack-on-pyramid $ cd ~/hack-on-pyramid - Check out a read-only copy of the Pyramid source: $ git clone git://github.com/Pylons/pyramid.git . (alternately, create a writeable fork on GitHub and check that out). Since Pyramid is a framework and not an application, it can be convenient to work against a sample application, preferably in its own virtualenv. A quick way to achieve this is to (ab-)use ``tox`` (http://tox.readthedocs.org/en/latest/) with a custom configuration file that's part of the checkout: tox -c hacking-tox.ini This will create a python-2.7 based virtualenv named ``env27`` (Pyramid's ``.gitconfig` ignores all top-level folders that start with ``env`` specifically 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 --------------- In order to add a feature to Pyramid: - The feature must be documented in both the API and narrative documentation (in ``docs/``). - The feature must work fully on the following CPython versions: 2.6, 2.7, 3.2, 3.3, 3.4, and 3.5 on both UNIX and Windows. - The feature must work on the latest version of PyPy and PyPy3. - The feature must not cause installation or runtime failure on App Engine. If it doesn't cause installation or runtime failure, but doesn't actually *work* on these platforms, that caveat should be spelled out in the documentation. - The feature must not depend on any particular persistence layer (filesystem, SQL, etc). - The feature must not add unnecessary dependencies (where "unnecessary" is of course subjective, but new dependencies should be discussed). The above requirements are relaxed for scaffolding dependencies. If a scaffold has an install-time dependency on something that doesn't work on a particular platform, that caveat should be spelled out clearly in *its* documentation (within its ``docs/`` directory). Coding Style ------------ - PEP8 compliance. Whitespace rules are relaxed: not necessary to put 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. - Please do not remove trailing whitespace. Configure your editor to reduce diff noise. See https://github.com/Pylons/pyramid/issues/788 for more. Running Tests -------------- - To run all tests for Pyramid on a single Python version, run ``nosetests`` from your development virtualenv (See *Using a Development Checkout* above). - To run individual tests (i.e. during development) you can use a regular expression with the ``-t`` parameter courtesy of the `nose-selecttests `_ plugin that's been installed (along with nose itself) via ``python setup.py dev``. The easiest usage is to simply provide the verbatim name of the test you're working on. - To run the full set of Pyramid tests on all platforms, install ``tox`` (http://codespeak.net/~hpk/tox/) into a system Python. The ``tox`` console script will be installed into the scripts location for that Python. While ``cd``'ed to the Pyramid checkout root directory (it contains ``tox.ini``), invoke the ``tox`` console script. This will read the ``tox.ini`` file and execute the tests on multiple Python versions and platforms; while it runs, it creates a virtualenv for each version/platform combination. For example:: $ sudo /usr/bin/easy_install tox $ cd ~/hack-on-pyramid/ $ /usr/bin/tox - The tests can also be run using ``pytest`` (http://pytest.org/). This is intended as a convenience for people who are more used or fond of ``pytest``. Run the tests like so:: $ $VENV/bin/easy_install pytest $ $VENV/bin/py.test --strict pyramid/ - Functional tests related to the "scaffolds" (starter, zodb, alchemy) which create a virtualenv, install the scaffold package and its dependencies, start a server, and hit a URL on the server can be run like so:: $ ./scaffoldtests.sh Alternately:: $ tox -e{py26,py27,py32,py33,py34,py35,pypy,pypy3}-scaffolds, Test Coverage ------------- - The codebase *must* have 100% test statement coverage after each commit. You can test coverage via ``./coverage.sh`` (which itself just executes ``tox -epy2-cover,py3-cover,coverage``). Documentation Coverage and Building HTML Documentation ------------------------------------------------------ If you fix a bug, and the bug requires an API or behavior modification, all documentation in this package which references that API or behavior must be changed to reflect the bug fix, ideally in the same commit that fixes the bug or adds the feature. To build and review docs, use the following steps. 1. In the main Pyramid checkout directory, run ``./builddocs.sh`` (which just turns around and runs ``tox -e docs``):: $ ./builddocs.sh 2. Open the ``docs/_build/html/index.html`` file to see the resulting HTML rendering. Change Log ---------- - Feature additions and bugfixes must be added to the ``CHANGES.txt`` file in the prevailing style. Changelog entries should be long and descriptive, not cryptic. Other developers should be able to know what your changelog entry means. pyramid-1.6/HISTORY.txt0000644000076500000240000057254512642135264015503 0ustar michaelstaff000000000000001.5 (2014-04-08) ================ - Python 3.4 compatibility. - Avoid crash in ``pserve --reload`` under Py3k, when iterating over possibly mutated ``sys.modules``. - ``UnencryptedCookieSessionFactoryConfig`` failed if the secret contained higher order characters. See https://github.com/Pylons/pyramid/issues/1246 - Fixed a bug in ``UnencryptedCookieSessionFactoryConfig`` and ``SignedCookieSessionFactory`` where ``timeout=None`` would cause a new session to always be created. Also in ``SignedCookieSessionFactory`` a ``reissue_time=None`` would cause an exception when modifying the session. See https://github.com/Pylons/pyramid/issues/1247 - Updated docs and scaffolds to keep in step with new 2.0 release of ``Lingua``. This included removing all ``setup.cfg`` files from scaffolds and documentation environments. 1.5b1 (2014-02-08) ================== Features -------- - We no longer eagerly clear ``request.exception`` and ``request.exc_info`` in the exception view tween. This makes it possible to inspect exception information within a finished callback. See https://github.com/Pylons/pyramid/issues/1223. 1.5a4 (2014-01-28) ================== Features -------- - Updated scaffolds with new theme, fixed documentation and sample project. Bug Fixes --------- - Depend on a newer version of WebOb so that we pull in some crucial bug-fixes that were showstoppers for functionality in Pyramid. - Add a trailing semicolon to the JSONP response. This fixes JavaScript syntax errors for old IE versions. See https://github.com/Pylons/pyramid/pull/1205 - Fix a memory leak when the configurator's ``set_request_property`` method was used or when the configurator's ``add_request_method`` method was used with the ``property=True`` attribute. See https://github.com/Pylons/pyramid/issues/1212 . 1.5a3 (2013-12-10) ================== Features -------- - An authorization API has been added as a method of the request: ``request.has_permission``. ``request.has_permission`` is a method-based alternative to the ``pyramid.security.has_permission`` API and works exactly the same. The older API is now deprecated. - Property API attributes have been added to the request for easier access to authentication data: ``request.authenticated_userid``, ``request.unauthenticated_userid``, and ``request.effective_principals``. These are analogues, respectively, of ``pyramid.security.authenticated_userid``, ``pyramid.security.unauthenticated_userid``, and ``pyramid.security.effective_principals``. They operate exactly the same, except they are attributes of the request instead of functions accepting a request. They are properties, so they cannot be assigned to. The older function-based APIs are now deprecated. - Pyramid's console scripts (``pserve``, ``pviews``, etc) can now be run directly, allowing custom arguments to be sent to the python interpreter at runtime. For example:: python -3 -m pyramid.scripts.pserve development.ini - Added a specific subclass of ``HTTPBadRequest`` named ``pyramid.exceptions.BadCSRFToken`` which will now be raised in response to failures in ``check_csrf_token``. See https://github.com/Pylons/pyramid/pull/1149 - Added a new ``SignedCookieSessionFactory`` which is very similar to the ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on signing content. The custom serializer arguments to this function should only focus on serializing, unlike its predecessor which required the serializer to also perform signing. See https://github.com/Pylons/pyramid/pull/1142 . Note that cookies generated using ``SignedCookieSessionFactory`` are not compatible with cookies generated using ``UnencryptedCookieSessionFactory``, so existing user session data will be destroyed if you switch to it. - Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie factory that can be used by framework implementors to create their own session implementations. It provides a reusable API which focuses strictly on providing a dictionary-like object that properly handles renewals, timeouts, and conformance with the ``ISession`` API. See https://github.com/Pylons/pyramid/pull/1142 - The anchor argument to ``pyramid.request.Request.route_url`` and ``pyramid.request.Request.resource_url`` and their derivatives will now be escaped via URL quoting to ensure minimal conformance. See https://github.com/Pylons/pyramid/pull/1183 - Allow sending of ``_query`` and ``_anchor`` options to ``pyramid.request.Request.static_url`` when an external URL is being generated. See https://github.com/Pylons/pyramid/pull/1183 - You can now send a string as the ``_query`` argument to ``pyramid.request.Request.route_url`` and ``pyramid.request.Request.resource_url`` and their derivatives. When a string is sent instead of a list or dictionary. it is URL-quoted however it does not need to be in ``k=v`` form. This is useful if you want to be able to use a different query string format than ``x-www-form-urlencoded``. See https://github.com/Pylons/pyramid/pull/1183 - ``pyramid.testing.DummyRequest`` now has a ``domain`` attribute to match the new WebOb 1.3 API. Its value is ``example.com``. Bug Fixes --------- - Fix the ``pcreate`` script so that when the target directory name ends with a slash it does not produce a non-working project directory structure. Previously saying ``pcreate -s starter /foo/bar/`` produced different output than saying ``pcreate -s starter /foo/bar``. The former did not work properly. - Fix the ``principals_allowed_by_permission`` method of ``ACLAuthorizationPolicy`` so it anticipates a callable ``__acl__`` on resources. Previously it did not try to call the ``__acl__`` if it was callable. - The ``pviews`` script did not work when a url required custom request methods in order to perform traversal. Custom methods and descriptors added via ``pyramid.config.Configurator.add_request_method`` will now be present, allowing traversal to continue. See https://github.com/Pylons/pyramid/issues/1104 - Remove unused ``renderer`` argument from ``Configurator.add_route``. - Allow the ``BasicAuthenticationPolicy`` to work with non-ascii usernames and passwords. The charset is not passed as part of the header and different browsers alternate between UTF-8 and Latin-1, so the policy now attempts to decode with UTF-8 first, and will fallback to Latin-1. See https://github.com/Pylons/pyramid/pull/1170 - The ``@view_defaults`` now apply to notfound and forbidden views that are defined as methods of a decorated class. See https://github.com/Pylons/pyramid/issues/1173 Documentation ------------- - Added a "Quick Tutorial" to go with the Quick Tour - Removed mention of ``pyramid_beaker`` from docs. Beaker is no longer maintained. Point people at ``pyramid_redis_sessions`` instead. - Add documentation for ``pyramid.interfaces.IRendererFactory`` and ``pyramid.interfaces.IRenderer``. Backwards Incompatibilities --------------------------- - The key/values in the ``_query`` parameter of ``request.route_url`` and the ``query`` parameter of ``request.resource_url`` (and their variants), used to encode a value of ``None`` as the string ``'None'``, leaving the resulting query string to be ``a=b&key=None``. The value is now dropped in this situation, leaving a query string of ``a=b&key=``. See https://github.com/Pylons/pyramid/issues/1119 Deprecations ------------ - Deprecate the ``pyramid.interfaces.ITemplateRenderer`` interface. It was ill-defined and became unused when Mako and Chameleon template bindings were split into their own packages. - The ``pyramid.session.UnencryptedCookieSessionFactoryConfig`` API has been deprecated and is superseded by the ``pyramid.session.SignedCookieSessionFactory``. Note that while the cookies generated by the ``UnencryptedCookieSessionFactoryConfig`` are compatible with cookies generated by old releases, cookies generated by the SignedCookieSessionFactory are not. See https://github.com/Pylons/pyramid/pull/1142 - The ``pyramid.security.has_permission`` API is now deprecated. Instead, use the newly-added ``has_permission`` method of the request object. - The ``pyramid.security.effective_principals`` API is now deprecated. Instead, use the newly-added ``effective_principals`` attribute of the request object. - The ``pyramid.security.authenticated_userid`` API is now deprecated. Instead, use the newly-added ``authenticated_userid`` attribute of the request object. - The ``pyramid.security.unauthenticated_userid`` API is now deprecated. Instead, use the newly-added ``unauthenticated_userid`` attribute of the request object. Dependencies ------------ - Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile`` from 1.3+). 1.5a2 (2013-09-22) ================== Features -------- - Users can now provide dotted Python names to as the ``factory`` argument the Configurator methods named ``add_{view,route,subscriber}_predicate`` (instead of passing the predicate factory directly, you can pass a dotted name which refers to the factory). Bug Fixes --------- - Fix an exception in ``pyramid.path.package_name`` when resolving the package name for namespace packages that had no ``__file__`` attribute. Backwards Incompatibilities --------------------------- - Pyramid no longer depends on or configures the Mako and Chameleon templating system renderers by default. Disincluding these templating systems by default means that the Pyramid core has fewer dependencies and can run on future platforms without immediate concern for the compatibility of its templating add-ons. It also makes maintenance slightly more effective, as different people can maintain the templating system add-ons that they understand and care about without needing commit access to the Pyramid core, and it allows users who just don't want to see any packages they don't use come along for the ride when they install Pyramid. This means that upon upgrading to Pyramid 1.5a2+, projects that use either of these templating systems will see a traceback that ends something like this when their application attempts to render a Chameleon or Mako template:: ValueError: No such renderer factory .pt Or:: ValueError: No such renderer factory .mako Or:: ValueError: No such renderer factory .mak Support for Mako templating has been moved into an add-on package named ``pyramid_mako``, and support for Chameleon templating has been moved into an add-on package named ``pyramid_chameleon``. These packages are drop-in replacements for the old built-in support for these templating langauges. All you have to do is install them and make them active in your configuration to register renderer factories for ``.pt`` and/or ``.mako`` (or ``.mak``) to make your application work again. To re-add support for Chameleon and/or Mako template renderers into your existing projects, follow the below steps. If you depend on Mako templates: * Make sure the ``pyramid_mako`` package is installed. One way to do this is by adding ``pyramid_mako`` to the ``install_requires`` section of your package's ``setup.py`` file and afterwards rerunning ``setup.py develop``:: setup( #... install_requires=[ 'pyramid_mako', # new dependency 'pyramid', #... ], ) * Within the portion of your application which instantiates a Pyramid ``pyramid.config.Configurator`` (often the ``main()`` function in your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_mako`` includeme:: config = Configurator(.....) config.include('pyramid_mako') If you depend on Chameleon templates: * Make sure the ``pyramid_chameleon`` package is installed. One way to do this is by adding ``pyramid_chameleon`` to the ``install_requires`` section of your package's ``setup.py`` file and afterwards rerunning ``setup.py develop``:: setup( #... install_requires=[ 'pyramid_chameleon', # new dependency 'pyramid', #... ], ) * Within the portion of your application which instantiates a Pyramid ``~pyramid.config.Configurator`` (often the ``main()`` function in your project's ``__init__.py`` file), tell Pyramid to include the ``pyramid_chameleon`` includeme:: config = Configurator(.....) config.include('pyramid_chameleon') Note that it's also fine to install these packages into *older* Pyramids for forward compatibility purposes. Even if you don't upgrade to Pyramid 1.5 immediately, performing the above steps in a Pyramid 1.4 installation is perfectly fine, won't cause any difference, and will give you forward compatibility when you eventually do upgrade to Pyramid 1.5. With the removal of Mako and Chameleon support from the core, some unit tests that use the ``pyramid.renderers.render*`` methods may begin to fail. If any of your unit tests are invoking either ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response()`` with either Mako or Chameleon templates then the ``pyramid.config.Configurator`` instance in effect during the unit test should be also be updated to include the addons, as shown above. For example:: class ATest(unittest.TestCase): def setUp(self): self.config = pyramid.testing.setUp() self.config.include('pyramid_mako') def test_it(self): result = pyramid.renderers.render('mypkg:templates/home.mako', {}) Or:: class ATest(unittest.TestCase): def setUp(self): self.config = pyramid.testing.setUp() self.config.include('pyramid_chameleon') def test_it(self): result = pyramid.renderers.render('mypkg:templates/home.pt', {}) - If you're using the Pyramid debug toolbar, when you upgrade Pyramid to 1.5a2+, you'll also need to upgrade the ``pyramid_debugtoolbar`` package to at least version 1.0.8, as older toolbar versions are not compatible with Pyramid 1.5a2+ due to the removal of Mako support from the core. It's fine to use this newer version of the toolbar code with older Pyramids too. - Removed the ``request.response_*`` varying attributes. These attributes have been deprecated since Pyramid 1.1, and as per the deprecation policy, have now been removed. - ``request.response`` will no longer be mutated when using the ``pyramid.renderers.render()`` API. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``), but this is only necessary when the renderer is generating a response; it was a bug when it was done as a side effect of calling ``pyramid.renderers.render()``. - Removed the ``bfg2pyramid`` fixer script. - The ``pyramid.events.NewResponse`` event is now sent **after** response callbacks are executed. It previously executed before response callbacks were executed. Rationale: it's more useful to be able to inspect the response after response callbacks have done their jobs instead of before. - Removed the class named ``pyramid.view.static`` that had been deprecated since Pyramid 1.1. Instead use ``pyramid.static.static_view`` with ``use_subpath=True`` argument. - Removed the ``pyramid.view.is_response`` function that had been deprecated since Pyramid 1.1. Use the ``pyramid.request.Request.is_response`` method instead. - Removed the ability to pass the following arguments to ``pyramid.config.Configurator.add_route``: ``view``, ``view_context``. ``view_for``, ``view_permission``, ``view_renderer``, and ``view_attr``. Using these arguments had been deprecated since Pyramid 1.1. Instead of passing view-related arguments to ``add_route``, use a separate call to ``pyramid.config.Configurator.add_view`` to associate a view with a route using its ``route_name`` argument. Note that this impacts the ``pyramid.config.Configurator.add_static_view`` function too, because it delegates to ``add_route``. - Removed the ability to influence and query a ``pyramid.request.Request`` object as if it were a dictionary. Previously it was possible to use methods like ``__getitem__``, ``get``, ``items``, and other dictlike methods to access values in the WSGI environment. This behavior had been deprecated since Pyramid 1.1. Use methods of ``request.environ`` (a real dictionary) instead. - Removed ancient backwards compatibily hack in ``pyramid.traversal.DefaultRootFactory`` which populated the ``__dict__`` of the factory with the matchdict values for compatibility with BFG 0.9. - The ``renderer_globals_factory`` argument to the ``pyramid.config.Configurator` constructor and its ``setup_registry`` method has been removed. The ``set_renderer_globals_factory`` method of ``pyramid.config.Configurator`` has also been removed. The (internal) ``pyramid.interfaces.IRendererGlobals`` interface was also removed. These arguments, methods and interfaces had been deprecated since 1.1. Use a ``BeforeRender`` event subscriber as documented in the "Hooks" chapter of the Pyramid narrative documentation instead of providing renderer globals values to the configurator. Deprecations ------------ - The ``pyramid.config.Configurator.set_request_property`` method now issues a deprecation warning when used. It had been docs-deprecated in 1.4 but did not issue a deprecation warning when used. 1.5a1 (2013-08-30) ================== Features -------- - A new http exception subclass named ``pyramid.httpexceptions.HTTPSuccessful`` was added. You can use this class as the ``context`` of an exception view to catch all 200-series "exceptions" (e.g. "raise HTTPOk"). This also allows you to catch *only* the ``HTTPOk`` exception itself; previously this was impossible because a number of other exceptions (such as ``HTTPNoContent``) inherited from ``HTTPOk``, but now they do not. - You can now generate "hybrid" urldispatch/traversal URLs more easily by using the new ``route_name``, ``route_kw`` and ``route_remainder_name`` arguments to ``request.resource_url`` and ``request.resource_path``. See the new section of the "Combining Traversal and URL Dispatch" documentation chapter entitled "Hybrid URL Generation". - It is now possible to escape double braces in Pyramid scaffolds (unescaped, these represent replacement values). You can use ``\{\{a\}\}`` to represent a "bare" ``{{a}}``. See https://github.com/Pylons/pyramid/pull/862 - Add ``localizer`` and ``locale_name`` properties (reified) to the request. See https://github.com/Pylons/pyramid/issues/508. Note that the ``pyramid.i18n.get_localizer`` and ``pyramid.i18n.get_locale_name`` functions now simply look up these properties on the request. - Add ``pdistreport`` script, which prints the Python version in use, the Pyramid version in use, and the version number and location of all Python distributions currently installed. - Add the ability to invert the result of any view, route, or subscriber predicate using the ``not_`` class. For example:: from pyramid.config import not_ @view_config(route_name='myroute', request_method=not_('POST')) def myview(request): ... The above example will ensure that the view is called if the request method is not POST (at least if no other view is more specific). The ``pyramid.config.not_`` class can be used against any value that is a predicate value passed in any of these contexts: - ``pyramid.config.Configurator.add_view`` - ``pyramid.config.Configurator.add_route`` - ``pyramid.config.Configurator.add_subscriber`` - ``pyramid.view.view_config`` - ``pyramid.events.subscriber`` - ``scripts/prequest.py``: add support for submitting ``PUT`` and ``PATCH`` requests. See https://github.com/Pylons/pyramid/pull/1033. add support for submitting ``OPTIONS`` and ``PROPFIND`` requests, and allow users to specify basic authentication credentials in the request via a ``--login`` argument to the script. See https://github.com/Pylons/pyramid/pull/1039. - ``ACLAuthorizationPolicy`` supports ``__acl__`` as a callable. This removes the ambiguity between the potential ``AttributeError`` that would be raised on the ``context`` when the property was not defined and the ``AttributeError`` that could be raised from any user-defined code within a dynamic property. It is recommended to define a dynamic ACL as a callable to avoid this ambiguity. See https://github.com/Pylons/pyramid/issues/735. - Allow a protocol-relative URL (e.g. ``//example.com/images``) to be passed to ``pyramid.config.Configurator.add_static_view``. This allows externally-hosted static URLs to be generated based on the current protocol. - The ``AuthTktAuthenticationPolicy`` has two new options to configure its domain usage: * ``parent_domain``: if set the authentication cookie is set on the parent domain. This is useful if you have multiple sites sharing the same domain. * ``domain``: if provided the cookie is always set for this domain, bypassing all usual logic. See https://github.com/Pylons/pyramid/pull/1028, https://github.com/Pylons/pyramid/pull/1072 and https://github.com/Pylons/pyramid/pull/1078. - The ``AuthTktAuthenticationPolicy`` now supports IPv6 addresses when using the ``include_ip=True`` option. This is possibly incompatible with alternative ``auth_tkt`` implementations, as the specification does not define how to properly handle IPv6. See https://github.com/Pylons/pyramid/issues/831. - Make it possible to use variable arguments via ``pyramid.paster.get_appsettings``. This also allowed the generated ``initialize_db`` script from the ``alchemy`` scaffold to grow support for options in the form ``a=1 b=2`` so you can fill in values in a parameterized ``.ini`` file, e.g. ``initialize_myapp_db etc/development.ini a=1 b=2``. See https://github.com/Pylons/pyramid/pull/911 - The ``request.session.check_csrf_token()`` method and the ``check_csrf`` view predicate now take into account the value of the HTTP header named ``X-CSRF-Token`` (as well as the ``csrf_token`` form parameter, which they always did). The header is tried when the form parameter does not exist. - View lookup will now search for valid views based on the inheritance hierarchy of the context. It tries to find views based on the most specific context first, and upon predicate failure, will move up the inheritance chain to test views found by the super-type of the context. In the past, only the most specific type containing views would be checked and if no matching view could be found then a PredicateMismatch would be raised. Now predicate mismatches don't hide valid views registered on super-types. Here's an example that now works:: class IResource(Interface): ... @view_config(context=IResource) def get(context, request): ... @view_config(context=IResource, request_method='POST') def post(context, request): ... @view_config(context=IResource, request_method='DELETE') def delete(context, request): ... @implementer(IResource) class MyResource: ... @view_config(context=MyResource, request_method='POST') def override_post(context, request): ... Previously the override_post view registration would hide the get and delete views in the context of MyResource -- leading to a predicate mismatch error when trying to use GET or DELETE methods. Now the views are found and no predicate mismatch is raised. See https://github.com/Pylons/pyramid/pull/786 and https://github.com/Pylons/pyramid/pull/1004 and https://github.com/Pylons/pyramid/pull/1046 - The ``pserve`` command now takes a ``-v`` (or ``--verbose``) flag and a ``-q`` (or ``--quiet``) flag. Output from running ``pserve`` can be controlled using these flags. ``-v`` can be specified multiple times to increase verbosity. ``-q`` sets verbosity to ``0`` unconditionally. The default verbosity level is ``1``. - The ``alchemy`` scaffold tests now provide better coverage. See https://github.com/Pylons/pyramid/pull/1029 - The ``pyramid.config.Configurator.add_route`` method now supports being called with an external URL as pattern. See https://github.com/Pylons/pyramid/issues/611 and the documentation section in the "URL Dispatch" chapter entitled "External Routes" for more information. Bug Fixes --------- - It was not possible to use ``pyramid.httpexceptions.HTTPException`` as the ``context`` of an exception view as very general catchall for http-related exceptions when you wanted that exception view to override the default exception view. See https://github.com/Pylons/pyramid/issues/985 - When the ``pyramid.reload_templates`` setting was true, and a Chameleon template was reloaded, and the renderer specification named a macro (e.g. ``foo#macroname.pt``), renderings of the template after the template was reloaded due to a file change would produce the entire template body instead of just a rendering of the macro. See https://github.com/Pylons/pyramid/issues/1013. - Fix an obscure problem when combining a virtual root with a route with a ``*traverse`` in its pattern. Now the traversal path generated in such a configuration will be correct, instead of an element missing a leading slash. - Fixed a Mako renderer bug returning a tuple with a previous defname value in some circumstances. See https://github.com/Pylons/pyramid/issues/1037 for more information. - Make the ``pyramid.config.assets.PackageOverrides`` object implement the API for ``__loader__`` objects specified in PEP 302. Proxies to the ``__loader__`` set by the importer, if present; otherwise, raises ``NotImplementedError``. This makes Pyramid static view overrides work properly under Python 3.3 (previously they would not). See https://github.com/Pylons/pyramid/pull/1015 for more information. - ``mako_templating``: added defensive workaround for non-importability of ``mako`` due to upstream ``markupsafe`` dropping Python 3.2 support. Mako templating will no longer work under the combination of MarkupSafe 0.17 and Python 3.2 (although the combination of MarkupSafe 0.17 and Python 3.3 or any supported Python 2 version will work OK). - Spaces and dots may now be in mako renderer template paths. This was broken when support for the new makodef syntax was added in 1.4a1. See https://github.com/Pylons/pyramid/issues/950 - ``pyramid.debug_authorization=true`` will now correctly print out ``Allowed`` for views registered with ``NO_PERMISSION_REQUIRED`` instead of invoking the ``permits`` method of the authorization policy. See https://github.com/Pylons/pyramid/issues/954 - Pyramid failed to install on some systems due to being packaged with some test files containing higher order characters in their names. These files have now been removed. See https://github.com/Pylons/pyramid/issues/981 - ``pyramid.testing.DummyResource`` didn't define ``__bool__``, so code under Python 3 would use ``__len__`` to find truthiness; this usually caused an instance of DummyResource to be "falsy" instead of "truthy". See https://github.com/Pylons/pyramid/pull/1032 - The ``alchemy`` scaffold would break when the database was MySQL during tables creation. See https://github.com/Pylons/pyramid/pull/1049 - The ``current_route_url`` method now attaches the query string to the URL by default. See https://github.com/Pylons/pyramid/issues/1040 - Make ``pserve.cherrypy_server_runner`` Python 3 compatible. See https://github.com/Pylons/pyramid/issues/718 Backwards Incompatibilities --------------------------- - Modified the ``current_route_url`` method in pyramid.Request. The method previously returned the URL without the query string by default, it now does attach the query string unless it is overriden. - The ``route_url`` and ``route_path`` APIs no longer quote ``/`` to ``%2F`` when a replacement value contains a ``/``. This was pointless, as WSGI servers always unquote the slash anyway, and Pyramid never sees the quoted value. - It is no longer possible to set a ``locale_name`` attribute of the request, nor is it possible to set a ``localizer`` attribute of the request. These are now "reified" properties that look up a locale name and localizer respectively using the machinery described in the "Internationalization" chapter of the documentation. - 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 ``request.resource_url`` or ``request.resource_path``. Previously the virtual root path would not have trailing slashes stripped, which would influence URL generation. - The ``pyramid.interfaces.IResourceURL`` interface has now grown two new attributes: ``virtual_path_tuple`` and ``physical_path_tuple``. These should be the tuple form of the resource's path (physical and virtual). 1.4 (2012-12-18) ================ Docs ---- - Fix functional tests in the ZODB tutorial 1.4b3 (2012-12-10) ================== - Packaging release only, no code changes. 1.4b2 was a brownbag release due to missing directories in the tarball. 1.4b2 (2012-12-10) ================== Docs ---- - Scaffolding is now PEP-8 compliant (at least for a brief shining moment). - Tutorial improvements. Backwards Incompatibilities --------------------------- - Modified the ``_depth`` argument to ``pyramid.view.view_config`` to accept a value relative to the invocation of ``view_config`` itself. Thus, when it was previously expecting a value of ``1`` or greater, to reflect that the caller of ``view_config`` is 1 stack frame away from ``venusian.attach``, this implementation detail is now hidden. - Modified the ``_backframes`` argument to ``pyramid.util.action_method`` in a similar way to the changes described to ``_depth`` above. This argument remains undocumented, but might be used in the wild by some insane person. 1.4b1 (2012-11-21) ================== Features -------- - Small microspeed enhancement which anticipates that a ``pyramid.response.Response`` object is likely to be returned from a view. Some code is shortcut if the class of the object returned by a view is this class. A similar microoptimization was done to ``pyramid.request.Request.is_response``. - Make it possible to use variable arguments on ``p*`` commands (``pserve``, ``pshell``, ``pviews``, etc) in the form ``a=1 b=2`` so you can fill in values in parameterized ``.ini`` file, e.g. ``pshell etc/development.ini http_port=8080``. See https://github.com/Pylons/pyramid/pull/714 - A somewhat advanced and obscure feature of Pyramid event handlers is their ability to handle "multi-interface" notifications. These notifications have traditionally presented multiple objects to the subscriber callable. For instance, if an event was sent by code like this:: registry.notify(event, context) In the past, in order to catch such an event, you were obligated to write and register an event subscriber that mentioned both the event and the context in its argument list:: @subscriber([SomeEvent, SomeContextType]) def asubscriber(event, context): pass In many subscriber callables registered this way, it was common for the logic in the subscriber callable to completely ignore the second and following arguments (e.g. ``context`` in the above example might be ignored), because they usually existed as attributes of the event anyway. You could usually get the same value by doing ``event.context`` or similar. The fact that you needed to put an extra argument which you usually ignored in the subscriber callable body was only a minor annoyance until we added "subscriber predicates", used to narrow the set of circumstances under which a subscriber will be executed, in a prior 1.4 alpha release. Once those were added, the annoyance was escalated, because subscriber predicates needed to accept the same argument list and arity as the subscriber callables that they were configured against. So, for example, if you had these two subscriber registrations in your code:: @subscriber([SomeEvent, SomeContextType]) def asubscriber(event, context): pass @subscriber(SomeOtherEvent) def asubscriber(event): pass And you wanted to use a subscriber predicate:: @subscriber([SomeEvent, SomeContextType], mypredicate=True) def asubscriber1(event, context): pass @subscriber(SomeOtherEvent, mypredicate=True) def asubscriber2(event): pass If an existing ``mypredicate`` subscriber predicate had been written in such a way that it accepted only one argument in its ``__call__``, you could not use it against a subscription which named more than one interface in its subscriber interface list. Similarly, if you had written a subscriber predicate that accepted two arguments, you couldn't use it against a registration that named only a single interface type. For example, if you created this predicate:: class MyPredicate(object): # portions elided... def __call__(self, event): return self.val == event.context.foo It would not work against a multi-interface-registered subscription, so in the above example, when you attempted to use it against ``asubscriber1``, it would fail at runtime with a TypeError, claiming something was attempting to call it with too many arguments. To hack around this limitation, you were obligated to design the ``mypredicate`` predicate to expect to receive in its ``__call__`` either a single ``event`` argument (a SomeOtherEvent object) *or* a pair of arguments (a SomeEvent object and a SomeContextType object), presumably by doing something like this:: class MyPredicate(object): # portions elided... def __call__(self, event, context=None): return self.val == event.context.foo This was confusing and bad. In order to allow people to ignore unused arguments to subscriber callables and to normalize the relationship between event subscribers and subscriber predicates, we now allow both subscribers and subscriber predicates to accept only a single ``event`` argument even if they've been subscribed for notifications that involve multiple interfaces. Subscribers and subscriber predicates that accept only one argument will receive the first object passed to ``notify``; this is typically (but not always) the event object. The other objects involved in the subscription lookup will be discarded. You can now write an event subscriber that accepts only ``event`` even if it subscribes to multiple interfaces:: @subscriber([SomeEvent, SomeContextType]) def asubscriber(event): # this will work! This prevents you from needing to match the subscriber callable parameters to the subscription type unnecessarily, especially when you don't make use of any argument in your subscribers except for the event object itself. Note, however, that if the event object is not the first object in the call to ``notify``, you'll run into trouble. For example, if notify is called with the context argument first:: registry.notify(context, event) You won't be able to take advantage of the event-only feature. It will "work", but the object received by your event handler won't be the event object, it will be the context object, which won't be very useful:: @subscriber([SomeContextType, SomeEvent]) def asubscriber(event): # bzzt! you'll be getting the context here as ``event``, and it'll # be useless Existing multiple-argument subscribers continue to work without issue, so you should continue use those if your system notifies using multiple interfaces and the first interface is not the event interface. For example:: @subscriber([SomeContextType, SomeEvent]) def asubscriber(context, event): # this will still work! The event-only feature makes it possible to use a subscriber predicate that accepts only a request argument within both multiple-interface subscriber registrations and single-interface subscriber registrations. You needn't make slightly different variations of predicates depending on the subscription type arguments. Instead, just write all your subscriber predicates so they only accept ``event`` in their ``__call__`` and they'll be useful across all registrations for subscriptions that use an event as their first argument, even ones which accept more than just ``event``. However, the same caveat applies to predicates as to subscriber callables: if you're subscribing to a multi-interface event, and the first interface is not the event interface, the predicate won't work properly. In such a case, you'll need to match the predicate ``__call__`` argument ordering and composition to the ordering of the interfaces. For example, if the registration for the subscription uses ``[SomeContext, SomeEvent]``, you'll need to reflect that in the ordering of the parameters of the predicate's ``__call__`` method:: def __call__(self, context, event): return event.request.path.startswith(self.val) tl;dr: 1) When using multi-interface subscriptions, always use the event type as the first subscription registration argument and 2) When 1 is true, use only ``event`` in your subscriber and subscriber predicate parameter lists, no matter how many interfaces the subscriber is notified with. This combination will result in the maximum amount of reusability of subscriber predicates and the least amount of thought on your part. Drink responsibly. Bug Fixes --------- - A failure when trying to locate the attribute ``__text__`` on route and view predicates existed when the ``debug_routematch`` setting was true or when the ``pviews`` command was used. See https://github.com/Pylons/pyramid/pull/727 Documentation ------------- - Sync up tutorial source files with the files that are rendered by the scaffold that each uses. 1.4a4 (2012-11-14) ================== Features -------- - ``pyramid.authentication.AuthTktAuthenticationPolicy`` has been updated to support newer hashing algorithms such as ``sha512``. Existing applications should consider updating if possible for improved security over the default md5 hashing. - Added an ``effective_principals`` route and view predicate. - Do not allow the userid returned from the ``authenticated_userid`` or the userid that is one of the list of principals returned by ``effective_principals`` to be either of the strings ``system.Everyone`` or ``system.Authenticated`` when any of the built-in authorization policies that live in ``pyramid.authentication`` are in use. These two strings are reserved for internal usage by Pyramid and they will not be accepted as valid userids. - Slightly better debug logging from ``pyramid.authentication.RepozeWho1AuthenticationPolicy``. - ``pyramid.security.view_execution_permitted`` used to return ``True`` if no view could be found. It now raises a ``TypeError`` exception in that case, as it doesn't make sense to assert that a nonexistent view is execution-permitted. See https://github.com/Pylons/pyramid/issues/299. - Allow a ``_depth`` argument to ``pyramid.view.view_config``, which will permit limited composition reuse of the decorator by other software that wants to provide custom decorators that are much like view_config. - Allow an iterable of decorators to be passed to ``pyramid.config.Configurator.add_view``. This allows views to be wrapped by more than one decorator without requiring combining the decorators yourself. Bug Fixes --------- - In the past if a renderer returned ``None``, the body of the resulting response would be set explicitly to the empty string. Instead, now, the body is left unchanged, which allows the renderer to set a body itself by using e.g. ``request.response.body = b'foo'``. The body set by the renderer will be unmolested on the way out. See https://github.com/Pylons/pyramid/issues/709 - In uncommon cases, the ``pyramid_excview_tween_factory`` might have inadvertently raised a ``KeyError`` looking for ``request_iface`` as an attribute of the request. It no longer fails in this case. See https://github.com/Pylons/pyramid/issues/700 - Be more tolerant of potential error conditions in ``match_param`` and ``physical_path`` predicate implementations; instead of raising an exception, return False. - ``pyramid.view.render_view`` was not functioning properly under Python 3.x due to a byte/unicode discrepancy. See https://github.com/Pylons/pyramid/issues/721 Deprecations ------------ - ``pyramid.authentication.AuthTktAuthenticationPolicy`` will emit a warning if an application is using the policy without explicitly passing a ``hashalg`` argument. This is because the default is "md5" which is considered theoretically subject to collision attacks. If you really want "md5" then you must specify it explicitly to get rid of the warning. Documentation ------------- - All of the tutorials that use ``pyramid.authentication.AuthTktAuthenticationPolicy`` now explicitly pass ``sha512`` as a ``hashalg`` argument. Internals --------- - Move ``TopologicalSorter`` from ``pyramid.config.util`` to ``pyramid.util``, move ``CyclicDependencyError`` from ``pyramid.config.util`` to ``pyramid.exceptions``, rename ``Singleton`` to ``Sentinel`` and move from ``pyramid.config.util`` to ``pyramid.util``; this is in an effort to move that stuff that may be an API one day out of ``pyramid.config.util``, because that package should never be imported from non-Pyramid code. TopologicalSorter is still not an API, but may become one. - Get rid of shady monkeypatching of ``pyramid.request.Request`` and ``pyramid.response.Response`` done within the ``__init__.py`` of Pyramid. Webob no longer relies on this being done. Instead, the ResponseClass attribute of the Pyramid Request class is assigned to the Pyramid response class; that's enough to satisfy WebOb and behave as it did before with the monkeypatching. 1.4a3 (2012-10-26) ================== Bug Fixes --------- - The match_param predicate's text method was fixed to sort its values. Part of https://github.com/Pylons/pyramid/pull/705 - 1.4a ``pyramid.scripting.prepare`` behaved differently than 1.3 series function of same name. In particular, if passed a request, it would not set the ``registry`` attribute of the request like 1.3 did. A symptom would be that passing a request to ``pyramid.paster.bootstrap`` (which uses the function) that did not have a ``registry`` attribute could assume that the registry would be attached to the request by Pyramid. This assumption could be made in 1.3, but not in 1.4. The assumption can now be made in 1.4 too (a registry is attached to a request passed to bootstrap or prepare). - When registering a view configuration that named a Chameleon ZPT renderer with a macro name in it (e.g. ``renderer='some/template#somemacro.pt``) as well as a view configuration without a macro name in it that pointed to the same template (e.g. ``renderer='some/template.pt'``), internal caching could confuse the two, and your code might have rendered one instead of the other. Features -------- - Allow multiple values to be specified to the ``request_param`` view/route predicate as a sequence. Previously only a single string value was allowed. See https://github.com/Pylons/pyramid/pull/705 - Comments with references to documentation sections placed in scaffold ``.ini`` files. - Added an HTTP Basic authentication policy at ``pyramid.authentication.BasicAuthAuthenticationPolicy``. - The Configurator ``testing_securitypolicy`` method now returns the policy object it creates. - The Configurator ``testing_securitypolicy`` method accepts two new arguments: ``remember_result`` and ``forget_result``. If supplied, these values influence the result of the policy's ``remember`` and ``forget`` methods, respectively. - The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a ``forgotten`` value on the policy (the value ``True``) when its ``forget`` method is called. - The DummySecurityPolicy created by ``testing_securitypolicy`` now sets a ``remembered`` value on the policy, which is the value of the ``principal`` argument it's called with when its ``remember`` method is called. - New ``physical_path`` view predicate. If specified, this value should be a string or a tuple representing the physical traversal path of the context found via traversal for this predicate to match as true. For example: ``physical_path='/'`` or ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``. This is not a path prefix match or a regex, it's a whole-path match. It's useful when you want to always potentially show a view when some object is traversed to, but you can't be sure about what kind of object it will be, so you can't use the ``context`` predicate. The individual path elements inbetween slash characters or in tuple elements should be the Unicode representation of the name of the resource and should not be encoded in any way. 1.4a2 (2012-09-27) ================== Bug Fixes --------- - When trying to determine Mako defnames and Chameleon macro names in asset specifications, take into account that the filename may have a hyphen in it. See https://github.com/Pylons/pyramid/pull/692 Features -------- - A new ``pyramid.session.check_csrf_token`` convenience function was added. - A ``check_csrf`` view predicate was added. For example, you can now do ``config.add_view(someview, check_csrf=True)``. When the predicate is checked, if the ``csrf_token`` value in ``request.params`` matches the CSRF token in the request's session, the view will be permitted to execute. Otherwise, it will not be permitted to execute. - Add ``Base.metadata.bind = engine`` to alchemy template, so that tables defined imperatively will work. Documentation ------------- - update wiki2 SQLA tutorial with the changes required after inserting ``Base.metadata.bind = engine`` into the alchemy scaffold. 1.4a1 (2012-09-16) ================== Bug Fixes --------- - Forward port from 1.3 branch: When no authentication policy was configured, a call to ``pyramid.security.effective_principals`` would unconditionally return the empty list. This was incorrect, it should have unconditionally returned ``[Everyone]``, and now does. - Explicit url dispatch regexes can now contain colons. https://github.com/Pylons/pyramid/issues/629 - On at least one 64-bit Ubuntu system under Python 3.2, using the ``view_config`` decorator caused a ``RuntimeError: dictionary changed size during iteration`` exception. It no longer does. See https://github.com/Pylons/pyramid/issues/635 for more information. - In Mako Templates lookup, check if the uri is already adjusted and bring it back to an asset spec. Normally occurs with inherited templates or included components. https://github.com/Pylons/pyramid/issues/606 https://github.com/Pylons/pyramid/issues/607 - In Mako Templates lookup, check for absolute uri (using mako directories) when mixing up inheritance with asset specs. https://github.com/Pylons/pyramid/issues/662 - HTTP Accept headers were not being normalized causing potentially conflicting view registrations to go unnoticed. Two views that only differ in the case ('text/html' vs. 'text/HTML') will now raise an error. https://github.com/Pylons/pyramid/pull/620 - Forward-port from 1.3 branch: when registering multiple views with an ``accept`` predicate in a Pyramid application runing under Python 3, you might have received a ``TypeError: unorderable types: function() < function()`` exception. Features -------- - Python 3.3 compatibility. - Configurator.add_directive now accepts arbitrary callables like partials or objects implementing ``__call__`` which dont have ``__name__`` and ``__doc__`` attributes. See https://github.com/Pylons/pyramid/issues/621 and https://github.com/Pylons/pyramid/pull/647. - Third-party custom view, route, and subscriber predicates can now be added for use by view authors via ``pyramid.config.Configurator.add_view_predicate``, ``pyramid.config.Configurator.add_route_predicate`` and ``pyramid.config.Configurator.add_subscriber_predicate``. So, for example, doing this:: config.add_view_predicate('abc', my.package.ABCPredicate) Might allow a view author to do this in an application that configured that predicate:: @view_config(abc=1) Similar features exist for ``add_route``, and ``add_subscriber``. See "Adding A Third Party View, Route, or Subscriber Predicate" in the Hooks chapter for more information. Note that changes made to support the above feature now means that only actions registered using the same "order" can conflict with one another. It used to be the case that actions registered at different orders could potentially conflict, but to my knowledge nothing ever depended on this behavior (it was a bit silly). - Custom objects can be made easily JSON-serializable in Pyramid by defining a ``__json__`` method on the object's class. This method should return values natively serializable by ``json.dumps`` (such as ints, lists, dictionaries, strings, and so forth). - The JSON renderer now allows for the definition of custom type adapters to convert unknown objects to JSON serializations. - As of this release, the ``request_method`` predicate, when used, will also imply that ``HEAD`` is implied when you use ``GET``. For example, using ``@view_config(request_method='GET')`` is equivalent to using ``@view_config(request_method=('GET', 'HEAD'))``. Using ``@view_config(request_method=('GET', 'POST')`` is equivalent to using ``@view_config(request_method=('GET', 'HEAD', 'POST')``. This is because HEAD is a variant of GET that omits the body, and WebOb has special support to return an empty body when a HEAD is used. - ``config.add_request_method`` has been introduced to support extending request objects with arbitrary callables. This method expands on the previous ``config.set_request_property`` by supporting methods as well as properties. This method now causes less code to be executed at request construction time than ``config.set_request_property`` in version 1.3. - Don't add a ``?`` to URLs generated by ``request.resource_url`` if the ``query`` argument is provided but empty. - Don't add a ``?`` to URLs generated by ``request.route_url`` if the ``_query`` argument is provided but empty. - The static view machinery now raises (rather than returns) ``HTTPNotFound`` and ``HTTPMovedPermanently`` exceptions, so these can be caught by the Not Found View (and other exception views). - The Mako renderer now supports a def name in an asset spec. When the def name is present in the asset spec, the system will render the template def within the template and will return the result. An example asset spec is ``package:path/to/template#defname.mako``. This will render the def named ``defname`` inside the ``template.mako`` template instead of rendering the entire template. The old way of returning a tuple in the form ``('defname', {})`` from the view is supported for backward compatibility, - The Chameleon ZPT renderer now accepts a macro name in an asset spec. When the macro name is present in the asset spec, the system will render the macro listed as a ``define-macro`` and return the result instead of rendering the entire template. An example asset spec: ``package:path/to/template#macroname.pt``. This will render the macro defined as ``macroname`` within the ``template.pt`` template instead of the entire templae. - When there is a predicate mismatch exception (seen when no view matches for a given request due to predicates not working), the exception now contains a textual description of the predicate which didn't match. - An ``add_permission`` directive method was added to the Configurator. This directive registers a free-standing permission introspectable into the Pyramid introspection system. Frameworks built atop Pyramid can thus use the ``permissions`` introspectable category data to build a comprehensive list of permissions supported by a running system. Before this method was added, permissions were already registered in this introspectable category as a side effect of naming them in an ``add_view`` call, this method just makes it possible to arrange for a permission to be put into the ``permissions`` introspectable category without naming it along with an associated view. Here's an example of usage of ``add_permission``:: config = Configurator() config.add_permission('view') - The ``UnencryptedCookieSessionFactoryConfig`` now accepts ``signed_serialize`` and ``signed_deserialize`` hooks which may be used to influence how the sessions are marshalled (by default this is done with HMAC+pickle). - ``pyramid.testing.DummyRequest`` now supports methods supplied by the ``pyramid.util.InstancePropertyMixin`` class such as ``set_property``. - Request properties and methods added via ``config.set_request_property`` or ``config.add_request_method`` are now available to tweens. - Request properties and methods added via ``config.set_request_property`` or ``config.add_request_method`` are now available in the request object returned from ``pyramid.paster.bootstrap``. - ``request.context`` of environment request during ``bootstrap`` is now the root object if a context isn't already set on a provided request. - The ``pyramid.decorator.reify`` function is now an API, and was added to the API documentation. - Added the ``pyramid.testing.testConfig`` context manager, which can be used to generate a configurator in a test, e.g. ``with testing.testConfig(...):``. - Users can now invoke a subrequest from within view code using a new ``request.invoke_subrequest`` API. Deprecations ------------ - The ``pyramid.config.Configurator.set_request_property`` has been documentation-deprecated. The method remains usable but the more featureful ``pyramid.config.Configurator.add_request_method`` should be used in its place (it has all of the same capabilities but can also extend the request object with methods). Backwards Incompatibilities --------------------------- - The Pyramid router no longer adds the values ``bfg.routes.route`` or ``bfg.routes.matchdict`` to the request's WSGI environment dictionary. These values were docs-deprecated in ``repoze.bfg`` 1.0 (effectively seven minor releases ago). If your code depended on these values, use request.matched_route and request.matchdict instead. - It is no longer possible to pass an environ dictionary directly to ``pyramid.traversal.ResourceTreeTraverser.__call__`` (aka ``ModelGraphTraverser.__call__``). Instead, you must pass a request object. Passing an environment instead of a request has generated a deprecation warning since Pyramid 1.1. - Pyramid will no longer work properly if you use the ``webob.request.LegacyRequest`` as a request factory. Instances of the LegacyRequest class have a ``request.path_info`` which return a string. This Pyramid release assumes that ``request.path_info`` will unconditionally be Unicode. - The functions from ``pyramid.chameleon_zpt`` and ``pyramid.chameleon_text`` named ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response`` have been removed. These have issued a deprecation warning upon import since Pyramid 1.0. Use ``pyramid.renderers.get_renderer()``, ``pyramid.renderers.get_renderer().implementation()``, ``pyramid.renderers.render()`` or ``pyramid.renderers.render_to_response`` respectively instead of these functions. - The ``pyramid.configuration`` module was removed. It had been deprecated since Pyramid 1.0 and printed a deprecation warning upon its use. Use ``pyramid.config`` instead. - The ``pyramid.paster.PyramidTemplate`` API was removed. It had been deprecated since Pyramid 1.1 and issued a warning on import. If your code depended on this, adjust your code to import ``pyramid.scaffolds.PyramidTemplate`` instead. - The ``pyramid.settings.get_settings()`` API was removed. It had been printing a deprecation warning since Pyramid 1.0. If your code depended on this API, use ``pyramid.threadlocal.get_current_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). - These APIs from the ``pyramid.testing`` module were removed. They have been printing deprecation warnings since Pyramid 1.0: * ``registerDummySecurityPolicy``, use ``pyramid.config.Configurator.testing_securitypolicy`` instead. * ``registerResources`` (aka ``registerModels``, use ``pyramid.config.Configurator.testing_resources`` instead. * ``registerEventListener``, use ``pyramid.config.Configurator.testing_add_subscriber`` instead. * ``registerTemplateRenderer`` (aka `registerDummyRenderer``), use ``pyramid.config.Configurator.testing_add_template`` instead. * ``registerView``, use ``pyramid.config.Configurator.add_view`` instead. * ``registerUtility``, use ``pyramid.config.Configurator.registry.registerUtility`` instead. * ``registerAdapter``, use ``pyramid.config.Configurator.registry.registerAdapter`` instead. * ``registerSubscriber``, use ``pyramid.config.Configurator.add_subscriber`` instead. * ``registerRoute``, use ``pyramid.config.Configurator.add_route`` instead. * ``registerSettings``, use ``pyramid.config.Configurator.add_settings`` instead. - In Pyramid 1.3 and previous, the ``__call__`` method of a Response object was invoked before any finished callbacks were executed. As of this release, the ``__call__`` method of a Response object is invoked *after* finished callbacks are executed. This is in support of the ``request.invoke_subrequest`` feature. - The 200-series exception responses named ``HTTPCreated``, ``HTTPAccepted``, ``HTTPNonAuthoritativeInformation``, ``HTTPNoContent``, ``HTTPResetContent``, and ``HTTPPartialContent`` in ``pyramid.httpexceptions`` no longer inherit from ``HTTPOk``. Instead they inherit from a new base class named ``HTTPSuccessful``. This will have no effect on you unless you've registered an exception view for ``HTTPOk`` and expect that exception view to catch all the aforementioned exceptions. Documentation ------------- - Added an "Upgrading Pyramid" chapter to the narrative documentation. It describes how to cope with deprecations and removals of Pyramid APIs and how to show Pyramid-generated deprecation warnings while running tests and while running a server. - Added a "Invoking a Subrequest" chapter to the documentation. It describes how to use the new ``request.invoke_subrequest`` API. Dependencies ------------ - Pyramid now requires WebOb 1.2b3+ (the prior Pyramid release only relied on 1.2dev+). This is to ensure that we obtain a version of WebOb that returns ``request.path_info`` as text. 1.3 (2012-03-21) ================ Bug Fixes --------- - When ``pyramid.wsgi.wsgiapp2`` calls the downstream WSGI app, the app's environ will no longer have (deprecated and potentially misleading) ``bfg.routes.matchdict`` or ``bfg.routes.route`` keys in it. A symptom of this bug would be a ``wsgiapp2``-wrapped Pyramid app finding the wrong view because it mistakenly detects that a route was matched when, in fact, it was not. - The fix for issue https://github.com/Pylons/pyramid/issues/461 (which made it possible for instance methods to be used as view callables) introduced a backwards incompatibility when methods that declared only a request argument were used. See https://github.com/Pylons/pyramid/issues/503 1.3b3 (2012-03-17) ================== Bug Fixes --------- - ``config.add_view()`` raised AttributeError involving ``__text__``. See https://github.com/Pylons/pyramid/issues/461 - Remove references to do-nothing ``pyramid.debug_templates`` setting in all Pyramid-provided ``.ini`` files. This setting previously told Chameleon to render better exceptions; now Chameleon always renders nice exceptions regardless of the value of this setting. Scaffolds --------- - The ``alchemy`` scaffold now shows an informative error message in the browser if the person creating the project forgets to run the initialization script. - The ``alchemy`` scaffold initialization script is now called ``initialize__db`` instead of ``populate_``. Documentation ------------- - Wiki tutorials improved due to collaboration at PyCon US 2012 sprints. 1.3b2 (2012-03-02) ================== Bug Fixes --------- - The method ``pyramid.request.Request.partial_application_url`` is no longer in the API docs. It was meant to be a private method; its publication in the documentation as an API method was a mistake, and it has been renamed to something private. - When a static view was registered using an absolute filesystem path on Windows, the ``request.static_url`` function did not work to generate URLs to its resources. Symptom: "No static URL definition matching c:\\foo\\bar\\baz". - Make all tests pass on Windows XP. - Bug in ACL authentication checking on Python 3: the ``permits`` and ``principals_allowed_by_permission`` method of ``pyramid.authorization.ACLAuthenticationPolicy`` could return an inappropriate ``True`` value when a permission on an ACL was a string rather than a sequence, and then only if the ACL permission string was a substring of the ``permission`` value passed to the function. This bug effects no Pyramid deployment under Python 2; it is a bug that exists only in deployments running on Python 3. It has existed since Pyramid 1.3a1. This bug was due to the presence of an ``__iter__`` attribute on strings under Python 3 which is not present under strings in Python 2. 1.3b1 (2012-02-26) ================== Bug Fixes --------- - ``pyramid.config.Configurator.with_package`` didn't work if the Configurator was an old-style ``pyramid.configuration.Configurator`` instance. - Pyramid authorization policies did not show up in the introspector. Deprecations ------------ - All references to the ``tmpl_context`` request variable were removed from the docs. Its existence in Pyramid is confusing for people who were never Pylons users. It was added as a porting convenience for Pylons users in Pyramid 1.0, but it never caught on because the Pyramid rendering system is a lot different than Pylons' was, and alternate ways exist to do what it was designed to offer in Pylons. It will continue to exist "forever" but it will not be recommended or mentioned in the docs. 1.3a9 (2012-02-22) ================== Features -------- - Add an ``introspection`` boolean to the Configurator constructor. If this is ``True``, actions registered using the Configurator will be registered with the introspector. If it is ``False``, they won't. The default is ``True``. Setting it to ``False`` during action processing will prevent introspection for any following registration statements, and setting it to ``True`` will start them up again. This addition is to service a requirement that the debug toolbar's own views and methods not show up in the introspector. - New API: ``pyramid.config.Configurator.add_notfound_view``. This is a wrapper for ``pyramid.Config.configurator.add_view`` which provides easy append_slash support and does the right thing about permissions. It should be preferred over calling ``add_view`` directly with ``context=HTTPNotFound`` as was previously recommended. - New API: ``pyramid.view.notfound_view_config``. This is a decorator constructor like ``pyramid.view.view_config`` that calls ``pyramid.config.Configurator.add_notfound_view`` when scanned. It should be preferred over using ``pyramid.view.view_config`` with ``context=HTTPNotFound`` as was previously recommended. - New API: ``pyramid.config.Configurator.add_forbidden_view``. This is a wrapper for ``pyramid.Config.configurator.add_view`` which does the right thing about permissions. It should be preferred over calling ``add_view`` directly with ``context=HTTPForbidden`` as was previously recommended. - New API: ``pyramid.view.forbidden_view_config``. This is a decorator constructor like ``pyramid.view.view_config`` that calls ``pyramid.config.Configurator.add_forbidden_view`` when scanned. It should be preferred over using ``pyramid.view.view_config`` with ``context=HTTPForbidden`` as was previously recommended. - New APIs: ``pyramid.response.FileResponse`` and ``pyramid.response.FileIter``, for usage in views that must serve files "manually". Backwards Incompatibilities --------------------------- - Remove ``pyramid.config.Configurator.with_context`` class method. It was never an API, it is only used by ``pyramid_zcml`` and its functionality has been moved to that package's latest release. This means that you'll need to use the 0.9.2 or later release of ``pyramid_zcml`` with this release of Pyramid. - The ``introspector`` argument to the ``pyramid.config.Configurator`` constructor API has been removed. It has been replaced by the boolean ``introspection`` flag. - The ``pyramid.registry.noop_introspector`` API object has been removed. - The older deprecated ``set_notfound_view`` Configurator method is now an alias for the new ``add_notfound_view`` Configurator method. Likewise, the older deprecated ``set_forbidden_view`` is now an alias for the new ``add_forbidden_view``. This has the following impact: the ``context`` sent to views with a ``(context, request)`` call signature registered via the ``set_notfound_view`` or ``set_forbidden_view`` will now be an exception object instead of the actual resource context found. Use ``request.context`` to get the actual resource context. It's also recommended to disuse ``set_notfound_view`` in favor of ``add_notfound_view``, and disuse ``set_forbidden_view`` in favor of ``add_forbidden_view`` despite the aliasing. Deprecations ------------ - The API documentation for ``pyramid.view.append_slash_notfound_view`` and ``pyramid.view.AppendSlashNotFoundViewFactory`` was removed. These names still exist and are still importable, but they are no longer APIs. Use ``pyramid.config.Configurator.add_notfound_view(append_slash=True)`` or ``pyramid.view.notfound_view_config(append_slash=True)`` to get the same behavior. - The ``set_forbidden_view`` and ``set_notfound_view`` methods of the Configurator were removed from the documentation. They have been deprecated since Pyramid 1.1. Bug Fixes --------- - The static file response object used by ``config.add_static_view`` opened the static file twice, when it only needed to open it once. - The AppendSlashNotFoundViewFactory used request.path to match routes. This was wrong because request.path contains the script name, and this would cause it to fail in circumstances where the script name was not empty. It should have used request.path_info, and now does. Documentation ------------- - Updated the "Creating a Not Found View" section of the "Hooks" chapter, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_notfound_view`` or ``notfound_view_config``. - Updated the "Creating a Not Forbidden View" section of the "Hooks" chapter, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_forbidden_view`` or ``forbidden_view_config``. - Updated the "Redirecting to Slash-Appended Routes" section of the "URL Dispatch" chapter, replacing explanations of registering a view using ``add_view`` or ``view_config`` with ones using ``add_notfound_view`` or ``notfound_view_config`` - Updated all tutorials to use ``pyramid.view.forbidden_view_config`` rather than ``pyramid.view.view_config`` with an HTTPForbidden context. 1.3a8 (2012-02-19) ================== Features -------- - The ``scan`` method of a ``Configurator`` can be passed an ``ignore`` argument, which can be a string, a callable, or a list consisting of strings and/or callables. This feature allows submodules, subpackages, and global objects from being scanned. See http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for more information about how to use the ``ignore`` argument to ``scan``. - Better error messages when a view callable returns a value that cannot be converted to a response (for example, when a view callable returns a dictionary without a renderer defined, or doesn't return any value at all). The error message now contains information about the view callable itself as well as the result of calling it. - Better error message when a .pyc-only module is ``config.include`` -ed. This is not permitted due to error reporting requirements, and a better error message is shown when it is attempted. Previously it would fail with something like "AttributeError: 'NoneType' object has no attribute 'rfind'". - Add ``pyramid.config.Configurator.add_traverser`` API method. See the Hooks narrative documentation section entitled "Changing the Traverser" for more information. This is not a new feature, it just provides an API for adding a traverser without needing to use the ZCA API. - Add ``pyramid.config.Configurator.add_resource_url_adapter`` API method. See the Hooks narrative documentation section entitled "Changing How pyramid.request.Request.resource_url Generates a URL" for more information. This is not a new feature, it just provides an API for adding a resource url adapter without needing to use the ZCA API. - The system value ``req`` is now supplied to renderers as an alias for ``request``. This means that you can now, for example, in a template, do ``req.route_url(...)`` instead of ``request.route_url(...)``. This is purely a change to reduce the amount of typing required to use request methods and attributes from within templates. The value ``request`` is still available too, this is just an alternative. - A new interface was added: ``pyramid.interfaces.IResourceURL``. An adapter implementing its interface can be used to override resource URL generation when ``request.resource_url`` is called. This interface replaces the now-deprecated ``pyramid.interfaces.IContextURL`` interface. - The dictionary passed to a resource's ``__resource_url__`` method (see "Overriding Resource URL Generation" in the "Resources" chapter) now contains an ``app_url`` key, representing the application URL generated during ``request.resource_url``. It represents a potentially customized URL prefix, containing potentially custom scheme, host and port information passed by the user to ``request.resource_url``. It should be used instead of ``request.application_url`` where necessary. - The ``request.resource_url`` API now accepts these arguments: ``app_url``, ``scheme``, ``host``, and ``port``. The app_url argument can be used to replace the URL prefix wholesale during url generation. The ``scheme``, ``host``, and ``port`` arguments can be used to replace the respective default values of ``request.application_url`` partially. - A new API named ``request.resource_path`` now exists. It works like ``request.resource_url`` but produces a relative URL rather than an absolute one. - The ``request.route_url`` API now accepts these arguments: ``_app_url``, ``_scheme``, ``_host``, and ``_port``. The ``_app_url`` argument can be used to replace the URL prefix wholesale during url generation. The ``_scheme``, ``_host``, and ``_port`` arguments can be used to replace the respective default values of ``request.application_url`` partially. Backwards Incompatibilities --------------------------- - The ``pyramid.interfaces.IContextURL`` interface has been deprecated. People have been instructed to use this to register a resource url adapter in the "Hooks" chapter to use to influence ``request.resource_url`` URL generation for resources found via custom traversers since Pyramid 1.0. The interface still exists and registering such an adapter still works, but this interface will be removed from the software after a few major Pyramid releases. You should replace it with an equivalent ``pyramid.interfaces.IResourceURL`` adapter, registered using the new ``pyramid.config.Configurator.add_resource_url_adapter`` API. A deprecation warning is now emitted when a ``pyramid.interfaces.IContextURL`` adapter is found when ``request.resource_url`` is called. Documentation ------------- - Don't create a ``session`` instance in SQLA Wiki tutorial, use raw ``DBSession`` instead (this is more common in real SQLA apps). Scaffolding ----------- - Put ``pyramid.includes`` targets within ini files in scaffolds on separate lines in order to be able to tell people to comment out only the ``pyramid_debugtoolbar`` line when they want to disable the toolbar. Dependencies ------------ - Depend on ``venusian`` >= 1.0a3 to provide scan ``ignore`` support. Internal -------- - Create a "MakoRendererFactoryHelper" that provides customizable settings key prefixes. Allows settings prefixes other than "mako." to be used to create different factories that don't use the global mako settings. This will be useful for the debug toolbar, which can currently be sabotaged by someone using custom mako configuration settings. 1.3a7 (2012-02-07) ================== Features -------- - More informative error message when a ``config.include`` cannot find an ``includeme``. See https://github.com/Pylons/pyramid/pull/392. - Internal: catch unhashable discriminators early (raise an error instead of allowing them to find their way into resolveConflicts). - The `match_param` view predicate now accepts a string or a tuple. This replaces the broken behavior of accepting a dict. See https://github.com/Pylons/pyramid/issues/425 for more information. Bug Fixes --------- - The process will now restart when ``pserve`` is used with the ``--reload`` flag when the ``development.ini`` file (or any other .ini file in use) is changed. See https://github.com/Pylons/pyramid/issues/377 and https://github.com/Pylons/pyramid/pull/411 - The ``prequest`` script would fail when used against URLs which did not return HTML or text. See https://github.com/Pylons/pyramid/issues/381 Backwards Incompatibilities --------------------------- - The `match_param` view predicate no longer accepts a dict. This will have no negative affect because the implementation was broken for dict-based arguments. Documentation ------------- - Add a traversal hello world example to the narrative docs. 1.3a6 (2012-01-20) ================== Features -------- - New API: ``pyramid.config.Configurator.set_request_property``. Add lazy property descriptors to a request without changing the request factory. This method provides conflict detection and is the suggested way to add properties to a request. - Responses generated by Pyramid's ``static_view`` now use a ``wsgi.file_wrapper`` (see http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling) when one is provided by the web server. Bug Fixes --------- - Views registered with an ``accept`` could not be overridden correctly with a different view that had the same predicate arguments. See https://github.com/Pylons/pyramid/pull/404 for more information. - When using a dotted name for a ``view`` argument to ``Configurator.add_view`` that pointed to a class with a ``view_defaults`` decorator, the view defaults would not be applied. See https://github.com/Pylons/pyramid/issues/396 . - Static URL paths were URL-quoted twice. See https://github.com/Pylons/pyramid/issues/407 . 1.3a5 (2012-01-09) ================== Bug Fixes --------- - The ``pyramid.view.view_defaults`` decorator did not work properly when more than one view relied on the defaults being different for configuration conflict resolution. See https://github.com/Pylons/pyramid/issues/394. Backwards Incompatibilities --------------------------- - The ``path_info`` route and view predicates now match against ``request.upath_info`` (Unicode) rather than ``request.path_info`` (indeterminate value based on Python 3 vs. Python 2). This has to be done to normalize matching on Python 2 and Python 3. 1.3a4 (2012-01-05) ================== Features -------- - New API: ``pyramid.request.Request.set_property``. Add lazy property descriptors to a request without changing the request factory. New properties may be reified, effectively caching the value for the lifetime of the instance. Common use-cases for this would be to get a database connection for the request or identify the current user. - Use the ``waitress`` WSGI server instead of ``wsgiref`` in scaffolding. Bug Fixes --------- - The documentation of ``pyramid.events.subscriber`` indicated that using it as a decorator with no arguments like this:: @subscriber() def somefunc(event): pass Would register ``somefunc`` to receive all events sent via the registry, but this was untrue. Instead, it would receive no events at all. This has now been fixed and the code matches the documentation. See also https://github.com/Pylons/pyramid/issues/386 - Literal portions of route patterns were not URL-quoted when ``route_url`` or ``route_path`` was used to generate a URL or path. - The result of ``route_path`` or ``route_url`` might have been ``unicode`` or ``str`` depending on the input. It is now guaranteed to always be ``str``. - URL matching when the pattern contained non-ASCII characters in literal parts was indeterminate. Now the pattern supplied to ``add_route`` is assumed to be either: a ``unicode`` value, or a ``str`` value that contains only ASCII characters. If you now want to match the path info from a URL that contains high order characters, you can pass the Unicode representation of the decoded path portion in the pattern. - When using a ``traverse=`` route predicate, traversal would fail with a URLDecodeError if there were any high-order characters in the traversal pattern or in the matched dynamic segments. - Using a dynamic segment named ``traverse`` in a route pattern like this:: config.add_route('trav_route', 'traversal/{traverse:.*}') Would cause a ``UnicodeDecodeError`` when the route was matched and the matched portion of the URL contained any high-order characters. See https://github.com/Pylons/pyramid/issues/385 . - When using a ``*traverse`` stararg in a route pattern, a URL that matched that possessed a ``@@`` in its name (signifying a view name) would be inappropriately quoted by the traversal machinery during traversal, resulting in the view not being found properly. See https://github.com/Pylons/pyramid/issues/382 and https://github.com/Pylons/pyramid/issues/375 . Backwards Incompatibilities --------------------------- - String values passed to ``route_url`` or ``route_path`` that are meant to replace "remainder" matches will now be URL-quoted except for embedded slashes. For example:: config.add_route('remain', '/foo*remainder') request.route_path('remain', remainder='abc / def') # -> '/foo/abc%20/%20def' Previously string values passed as remainder replacements were tacked on untouched, without any URL-quoting. But this doesn't really work logically if the value passed is Unicode (raw unicode cannot be placed in a URL or in a path) and it is inconsistent with the rest of the URL generation machinery if the value is a string (it won't be quoted unless by the caller). Some folks will have been relying on the older behavior to tack on query string elements and anchor portions of the URL; sorry, you'll need to change your code to use the ``_query`` and/or ``_anchor`` arguments to ``route_path`` or ``route_url`` to do this now. - If you pass a bytestring that contains non-ASCII characters to ``add_route`` as a pattern, it will now fail at startup time. Use Unicode instead. 1.3a3 (2011-12-21) ================== Features -------- - Added a ``prequest`` script (along the lines of ``paster request``). It is documented in the "Command-Line Pyramid" chapter in the section entitled "Invoking a Request". - Add undocumented ``__discriminator__`` API to derived view callables. e.g. ``adapters.lookup(...).__discriminator__(context, request)``. It will be used by superdynamic systems that require the discriminator to be used for introspection after manual view lookup. Bug Fixes --------- - Normalized exit values and ``-h`` output for all ``p*`` scripts (``pviews``, ``proutes``, etc). Documentation ------------- - Added a section named "Making Your Script into a Console Script" in the "Command-Line Pyramid" chapter. - Removed the "Running Pyramid on Google App Engine" tutorial from the main docs. It survives on in the Cookbook (http://docs.pylonsproject.org/projects/pyramid_cookbook/en/latest/deployment/gae.html). Rationale: it provides the correct info for the Python 2.5 version of GAE only, and this version of Pyramid does not support Python 2.5. 1.3a2 (2011-12-14) ================== Features -------- - New API: ``pyramid.view.view_defaults``. If you use a class as a view, you can use the new ``view_defaults`` class decorator on the class to provide defaults to the view configuration information used by every ``@view_config`` decorator that decorates a method of that class. It also works against view configurations involving a class made imperatively. - Added a backwards compatibility knob to ``pcreate`` to emulate ``paster create`` handling for the ``--list-templates`` option. - Changed scaffolding machinery around a bit to make it easier for people who want to have extension scaffolds that can work across Pyramid 1.0.X, 1.1.X, 1.2.X and 1.3.X. See the new "Creating Pyramid Scaffolds" chapter in the narrative documentation for more info. Documentation ------------- - Added documentation to "View Configuration" narrative documentation chapter about ``view_defaults`` class decorator. - Added API docs for ``view_defaults`` class decorator. - Added an API docs chapter for ``pyramid.scaffolds``. - Added a narrative docs chapter named "Creating Pyramid Scaffolds". Backwards Incompatibilities --------------------------- - The ``template_renderer`` method of ``pyramid.scaffolds.PyramidScaffold`` was renamed to ``render_template``. If you were overriding it, you're a bad person, because it wasn't an API before now. But we're nice so we're letting you know. 1.3a1 (2011-12-09) ================== Features -------- - Python 3.2 compatibility. - New ``pyramid.compat`` module and API documentation which provides Python 2/3 straddling support for Pyramid add-ons and development environments. - A ``mako.directories`` setting is no longer required to use Mako templates Rationale: Mako template renderers can be specified using an absolute asset spec. An entire application can be written with such asset specs, requiring no ordered lookup path. - ``bpython`` interpreter compatibility in ``pshell``. See the "Command-Line Pyramid" narrative docs chapter for more information. - Added ``get_appsettings`` API function to the ``pyramid.paster`` module. This function returns the settings defined within an ``[app:...]`` section in a PasteDeploy ini file. - Added ``setup_logging`` API function to the ``pyramid.paster`` module. This function sets up Python logging according to the logging configuration in a PasteDeploy ini file. - Configuration conflict reporting is reported in a more understandable way ("Line 11 in file..." vs. a repr of a tuple of similar info). - A configuration introspection system was added; see the narrative documentation chapter entitled "Pyramid Configuration Introspection" for more information. New APIs: ``pyramid.registry.Introspectable``, ``pyramid.config.Configurator.introspector``, ``pyramid.config.Configurator.introspectable``, ``pyramid.registry.Registry.introspector``. - Allow extra keyword arguments to be passed to the ``pyramid.config.Configurator.action`` method. - New APIs: ``pyramid.path.AssetResolver`` and ``pyramid.path.DottedNameResolver``. The former can be used to resolve asset specifications, the latter can be used to resolve dotted names to modules or packages. Bug Fixes --------- - Make test suite pass on 32-bit systems; closes #286. closes #306. See also https://github.com/Pylons/pyramid/issues/286 - The ``pyramid.view.view_config`` decorator did not accept a ``match_params`` predicate argument. See https://github.com/Pylons/pyramid/pull/308 - The AuthTktCookieHelper could potentially generate Unicode headers inappropriately when the ``tokens`` argument to remember was used. See https://github.com/Pylons/pyramid/pull/314. - The AuthTktAuthenticationPolicy did not use a timing-attack-aware string comparator. See https://github.com/Pylons/pyramid/pull/320 for more info. - The DummySession in ``pyramid.testing`` now generates a new CSRF token if one doesn't yet exist. - ``request.static_url`` now generates URL-quoted URLs when fed a ``path`` argument which contains characters that are unsuitable for URLs. See https://github.com/Pylons/pyramid/issues/349 for more info. - Prevent a scaffold rendering from being named ``site`` (conflicts with Python internal site.py). - Support for using instances as targets of the ``pyramid.wsgi.wsgiapp`` and ``pryramid.wsgi.wsgiapp2`` functions. See https://github.com/Pylons/pyramid/pull/370 for more info. Backwards Incompatibilities --------------------------- - Pyramid no longer runs on Python 2.5 (which includes the most recent release of Jython and the Python 2.5 version of GAE as of this writing). - The ``paster`` command is no longer the documented way to create projects, start the server, or run debugging commands. To create projects from scaffolds, ``paster create`` is replaced by the ``pcreate`` console script. To serve up a project, ``paster serve`` is replaced by the ``pserve`` console script. New console scripts named ``pshell``, ``pviews``, ``proutes``, and ``ptweens`` do what their ``paster `` equivalents used to do. Rationale: the Paste and PasteScript packages do not run under Python 3. - The default WSGI server run as the result of ``pserve`` from newly rendered scaffolding is now the ``wsgiref`` WSGI server instead of the ``paste.httpserver`` server. Rationale: Rationale: the Paste and PasteScript packages do not run under Python 3. - The ``pshell`` command (see "paster pshell") no longer accepts a ``--disable-ipython`` command-line argument. Instead, it accepts a ``-p`` or ``--python-shell`` argument, which can be any of the values ``python``, ``ipython`` or ``bpython``. - Removed the ``pyramid.renderers.renderer_from_name`` function. It has been deprecated since Pyramid 1.0, and was never an API. - To use ZCML with versions of Pyramid >= 1.3, you will need ``pyramid_zcml`` version >= 0.8 and ``zope.configuration`` version >= 3.8.0. The ``pyramid_zcml`` package version 0.8 is backwards compatible all the way to Pyramid 1.0, so you won't be warned if you have older versions installed and upgrade Pyramid "in-place"; it may simply break instead. Dependencies ------------ - Pyramid no longer depends on the ``zope.component`` package, except as a testing dependency. - Pyramid now depends on a zope.interface>=3.8.0, WebOb>=1.2dev, repoze.lru>=0.4, zope.deprecation>=3.5.0, translationstring>=0.4 (for Python 3 compatibility purposes). It also, as a testing dependency, depends on WebTest>=1.3.1 for the same reason. - Pyramid no longer depends on the Paste or PasteScript packages. Documentation ------------- - The SQLAlchemy Wiki tutorial has been updated. It now uses ``@view_config`` decorators and an explicit database population script. - Minor updates to the ZODB Wiki tutorial. - A narrative documentation chapter named "Extending Pyramid Configuration" was added; it describes how to add a new directive, and how use the ``pyramid.config.Configurator.action`` method within custom directives. It also describes how to add introspectable objects. - A narrative documentation chapter named "Pyramid Configuration Introspection" was added. It describes how to query the introspection system. Scaffolds --------- - Rendered scaffolds have now been changed to be more relocatable (fewer mentions of the package name within files in the package). - The ``routesalchemy`` scaffold has been renamed ``alchemy``, replacing the older (traversal-based) ``alchemy`` scaffold (which has been retired). - The ``starter`` scaffold now uses URL dispatch by default. 1.2 (2011-09-12) ================ Features -------- - Route pattern replacement marker names can now begin with an underscore. See https://github.com/Pylons/pyramid/issues/276. 1.2b3 (2011-09-11) ================== Bug Fixes --------- - The route prefix was not taken into account when a static view was added in an "include". See https://github.com/Pylons/pyramid/issues/266 . 1.2b2 (2011-09-08) ================== Bug Fixes --------- - The 1.2b1 tarball was a brownbag (particularly for Windows users) because it contained filenames with stray quotation marks in inappropriate places. We depend on ``setuptools-git`` to produce release tarballs, and when it was run to produce the 1.2b1 tarball, it didn't yet cope well with files present in git repositories with high-order characters in their filenames. Documentation ------------- - Minor tweaks to the "Introduction" narrative chapter example app and wording. 1.2b1 (2011-09-08) ================== Bug Fixes --------- - Sometimes falling back from territory translations (``de_DE``) to language translations (``de``) would not work properly when using a localizer. See https://github.com/Pylons/pyramid/issues/263 - The static file serving machinery could not serve files that started with a ``.`` (dot) character. - Static files with high-order (super-ASCII) characters in their names could not be served by a static view. The static file serving machinery inappropriately URL-quoted path segments in filenames when asking for files from the filesystem. - Within ``pyramid.traversal.traversal_path`` , canonicalize URL segments from UTF-8 to Unicode before checking whether a segment matches literally one of ``.``, the empty string, or ``..`` in case there's some sneaky way someone might tunnel those strings via UTF-8 that don't match the literals before decoded. Documentation ------------- - Added a "What Makes Pyramid Unique" section to the Introduction narrative chapter. 1.2a6 (2011-09-06) ================== Bug Fixes --------- - AuthTktAuthenticationPolicy with a ``reissue_time`` interfered with logout. See https://github.com/Pylons/pyramid/issues/262. Internal -------- - Internalize code previously depended upon as imports from the ``paste.auth`` module (futureproof). - Replaced use of ``paste.urlparser.StaticURLParser`` with a derivative of Chris Rossi's "happy" static file serving code (futureproof). - Fixed test suite; on some systems tests would fail due to indeterminate test run ordering and a double-push-single-pop of a shared test variable. Behavior Differences -------------------- - An ETag header is no longer set when serving a static file. A Last-Modified header is set instead. - Static file serving no longer supports the ``wsgi.file_wrapper`` extension. - Instead of returning a ``403 Forbidden`` error when a static file is served that cannot be accessed by the Pyramid process' user due to file permissions, an IOError (or similar) will be raised. Scaffolds --------- - All scaffolds now send the ``cache_max_age`` parameter to the ``add_static_view`` method. 1.2a5 (2011-09-04) ================== Bug Fixes --------- - The ``route_prefix`` of a configurator was not properly taken into account when registering routes in certain circumstances. See https://github.com/Pylons/pyramid/issues/260 Dependencies ------------ - The ``zope.configuration`` package is no longer a dependency. 1.2a4 (2011-09-02) ================== Features -------- - Support an ``onerror`` keyword argument to ``pyramid.config.Configurator.scan()``. This onerror keyword argument is passed to ``venusian.Scanner.scan()`` to influence error behavior when an exception is raised during scanning. - The ``request_method`` predicate argument to ``pyramid.config.Configurator.add_view`` and ``pyramid.config.Configurator.add_route`` is now permitted to be a tuple of HTTP method names. Previously it was restricted to being a string representing a single HTTP method name. - Undeprecated ``pyramid.traversal.find_model``, ``pyramid.traversal.model_path``, ``pyramid.traversal.model_path_tuple``, and ``pyramid.url.model_url``, which were all deprecated in Pyramid 1.0. There's just not much cost to keeping them around forever as aliases to their renamed ``resource_*`` prefixed functions. - Undeprecated ``pyramid.view.bfg_view``, which was deprecated in Pyramid 1.0. This is a low-cost alias to ``pyramid.view.view_config`` which we'll just keep around forever. Dependencies ------------ - Pyramid now requires Venusian 1.0a1 or better to support the ``onerror`` keyword argument to ``pyramid.config.Configurator.scan``. 1.2a3 (2011-08-29) ================== Bug Fixes --------- - Pyramid did not properly generate static URLs using ``pyramid.url.static_url`` when passed a caller-package relative path due to a refactoring done in 1.2a1. - The ``settings`` object emitted a deprecation warning any time ``__getattr__`` was called upon it. However, there are legitimate situations in which ``__getattr__`` is called on arbitrary objects (e.g. ``hasattr``). Now, the ``settings`` object only emits the warning upon successful lookup. Internal -------- - Use ``config.with_package`` in view_config decorator rather than manufacturing a new renderer helper (cleanup). 1.2a2 (2011-08-27) ================== Bug Fixes --------- - When a ``renderers=`` argument is not specified to the Configurator constructor, eagerly register and commit the default renderer set. This permits the overriding of the default renderers, which was broken in 1.2a1 without a commit directly after Configurator construction. - Mako rendering exceptions had the wrong value for an error message. - An include could not set a root factory successfully because the Configurator constructor unconditionally registered one that would be treated as if it were "the word of the user". Features -------- - A session factory can now be passed in using the dotted name syntax. 1.2a1 (2011-08-24) ================== Features -------- - The ``[pshell]`` section in an ini configuration file now treats a ``setup`` key as a dotted name that points to a callable that is passed the bootstrap environment. It can mutate the environment as necessary for great justice. - A new configuration setting named ``pyramid.includes`` is now available. It is described in the "Environment Variables and ``.ini`` Files Settings" narrative documentation chapter. - Added a ``route_prefix`` argument to the ``pyramid.config.Configurator.include`` method. This argument allows you to compose URL dispatch applications together. See the section entitled "Using a Route Prefix to Compose Applications" in the "URL Dispatch" narrative documentation chapter. - Added a ``pyramid.security.NO_PERMISSION_REQUIRED`` constant for use in ``permission=`` statements to view configuration. This constant has a value of the string ``__no_permission_required__``. This string value was previously referred to in documentation; now the documentation uses the constant. - Added a decorator-based way to configure a response adapter: ``pyramid.response.response_adapter``. This decorator has the same use as ``pyramid.config.Configurator.add_response_adapter`` but it's declarative. - The ``pyramid.events.BeforeRender`` event now has an attribute named ``rendering_val``. This can be used to introspect the value returned by a view in a BeforeRender subscriber. - New configurator directive: ``pyramid.config.Configurator.add_tween``. This directive adds a "tween". A "tween" is used to wrap the Pyramid router's primary request handling function. This is a feature may be used by Pyramid framework extensions, to provide, for example, view timing support and as a convenient place to hang bookkeeping code. Tweens are further described in the narrative docs section in the Hooks chapter, named "Registering Tweens". - New paster command ``paster ptweens``, which prints the current "tween" configuration for an application. See the section entitled "Displaying Tweens" in the Command-Line Pyramid chapter of the narrative documentation for more info. - The Pyramid debug logger now uses the standard logging configuration (usually set up by Paste as part of startup). This means that output from e.g. ``debug_notfound``, ``debug_authorization``, etc. will go to the normal logging channels. The logger name of the debug logger will be the package name of the *caller* of the Configurator's constructor. - A new attribute is available on request objects: ``exc_info``. Its value will be ``None`` until an exception is caught by the Pyramid router, after which it will be the result of ``sys.exc_info()``. - ``pyramid.testing.DummyRequest`` now implements the ``add_finished_callback`` and ``add_response_callback`` methods. - New methods of the ``pyramid.config.Configurator`` class: ``set_authentication_policy`` and ``set_authorization_policy``. These are meant to be consumed mostly by add-on authors. - New Configurator method: ``set_root_factory``. - Pyramid no longer eagerly commits some default configuration statements at Configurator construction time, which permits values passed in as constructor arguments (e.g. ``authentication_policy`` and ``authorization_policy``) to override the same settings obtained via an "include". - Better Mako rendering exceptions via ``pyramid.mako_templating.MakoRenderingException`` - New request methods: ``current_route_url``, ``current_route_path``, and ``static_path``. - New functions in ``pyramid.url``: ``current_route_path`` and ``static_path``. - The ``pyramid.request.Request.static_url`` API (and its brethren ``pyramid.request.Request.static_path``, ``pyramid.url.static_url``, and ``pyramid.url.static_path``) now accept an asbolute filename as a "path" argument. This will generate a URL to an asset as long as the filename is in a directory which was previously registered as a static view. Previously, trying to generate a URL to an asset using an absolute file path would raise a ValueError. - The ``RemoteUserAuthenticationPolicy ``, ``AuthTktAuthenticationPolicy``, and ``SessionAuthenticationPolicy`` constructors now accept an additional keyword argument named ``debug``. By default, this keyword argument is ``False``. When it is ``True``, debug information will be sent to the Pyramid debug logger (usually on stderr) when the ``authenticated_userid`` or ``effective_principals`` method is called on any of these policies. The output produced can be useful when trying to diagnose authentication-related problems. - New view predicate: ``match_param``. Example: a view added via ``config.add_view(aview, match_param='action=edit')`` will be called only when the ``request.matchdict`` has a value inside it named ``action`` with a value of ``edit``. Internal -------- - The Pyramid "exception view" machinery is now implemented as a "tween" (``pyramid.tweens.excview_tween_factory``). - WSGIHTTPException (HTTPFound, HTTPNotFound, etc) now has a new API named "prepare" which renders the body and content type when it is provided with a WSGI environ. Required for debug toolbar. - Once ``__call__`` or ``prepare`` is called on a WSGIHTTPException, the body will be set, and subsequent calls to ``__call__`` will always return the same body. Delete the body attribute to rerender the exception body. - Previously the ``pyramid.events.BeforeRender`` event *wrapped* a dictionary (it addressed it as its ``_system`` attribute). Now it *is* a dictionary (it inherits from ``dict``), and it's the value that is passed to templates as a top-level dictionary. - The ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` functions in the ``pyramid.url`` package now delegate to a method on the request they've been passed, instead of the other way around. The pyramid.request.Request object now inherits from a mixin named pyramid.url.URLMethodsMixin to make this possible, and all url/path generation logic is embedded in this mixin. - Refactor ``pyramid.config`` into a package. - Removed the ``_set_security_policies`` method of the Configurator. - Moved the ``StaticURLInfo`` class from ``pyramid.static`` to ``pyramid.config.views``. - Move the ``Settings`` class from ``pyramid.settings`` to ``pyramid.config.settings``. - Move the ``OverrideProvider``, ``PackageOverrides``, ``DirectoryOverride``, and ``FileOverride`` classes from ``pyramid.asset`` to ``pyramid.config.assets``. Deprecations ------------ - All Pyramid-related deployment settings (e.g. ``debug_all``, ``debug_notfound``) are now meant to be prefixed with the prefix ``pyramid.``. For example: ``debug_all`` -> ``pyramid.debug_all``. The old non-prefixed settings will continue to work indefinitely but supplying them may eventually print a deprecation warning. All scaffolds and tutorials have been changed to use prefixed settings. - The ``settings`` dictionary now raises a deprecation warning when you attempt to access its values via ``__getattr__`` instead of via ``__getitem__``. Backwards Incompatibilities --------------------------- - If a string is passed as the ``debug_logger`` parameter to a Configurator, that string is considered to be the name of a global Python logger rather than a dotted name to an instance of a logger. - The ``pyramid.config.Configurator.include`` method now accepts only a single ``callable`` argument (a sequence of callables used to be permitted). If you are passing more than one ``callable`` to ``pyramid.config.Configurator.include``, it will break. You now must now instead make a separate call to the method for each callable. This change was introduced to support the ``route_prefix`` feature of include. - It may be necessary to more strictly order configuration route and view statements when using an "autocommitting" Configurator. In the past, it was possible to add a view which named a route name before adding a route with that name when you used an autocommitting configurator. For example:: config = Configurator(autocommit=True) config.add_view('my.pkg.someview', route_name='foo') config.add_route('foo', '/foo') The above will raise an exception when the view attempts to add itself. Now you must add the route before adding the view:: config = Configurator(autocommit=True) config.add_route('foo', '/foo') config.add_view('my.pkg.someview', route_name='foo') This won't effect "normal" users, only people who have legacy BFG codebases that used an autommitting configurator and possibly tests that use the configurator API (the configurator returned by ``pyramid.testing.setUp`` is an autocommitting configurator). The right way to get around this is to use a non-autocommitting configurator (the default), which does not have these directive ordering requirements. - The ``pyramid.config.Configurator.add_route`` directive no longer returns a route object. This change was required to make route vs. view configuration processing work properly. Documentation ------------- - Narrative and API documentation which used the ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` functions in the ``pyramid.url`` package have now been changed to use eponymous methods of the request instead. - Added a section entitled "Using a Route Prefix to Compose Applications" to the "URL Dispatch" narrative documentation chapter. - Added a new module to the API docs: ``pyramid.tweens``. - Added a "Registering Tweens" section to the "Hooks" narrative chapter. - Added a "Displaying Tweens" section to the "Command-Line Pyramid" narrative chapter. - Added documentation for the ``pyramid.tweens`` and ``pyramid.includes`` configuration settings to the "Environment Variables and ``.ini`` Files Settings" chapter. - Added a Logging chapter to the narrative docs (based on the Pylons logging docs, thanks Phil). - Added a Paste chapter to the narrative docs (moved content from the Project chapter). - Added the ``pyramid.interfaces.IDict`` interface representing the methods of a dictionary, for documentation purposes only (IMultiDict and IBeforeRender inherit from it). - All tutorials now use - The ``route_url``, ``route_path``, ``resource_url``, ``static_url``, and ``current_route_url`` methods of the request rather than the function variants imported from ``pyramid.url``. - The ZODB wiki tutorial now uses the ``pyramid_zodbconn`` package rather than the ``repoze.zodbconn`` package to provide ZODB integration. Dependency Changes ------------------ - Pyramid now relies on PasteScript >= 1.7.4. This version contains a feature important for allowing flexible logging configuration. Scaffolds ---------- - All scaffolds now use the ``pyramid_tm`` package rather than the ``repoze.tm2`` middleware to manage transaction management. - The ZODB scaffold now uses the ``pyramid_zodbconn`` package rather than the ``repoze.zodbconn`` package to provide ZODB integration. - All scaffolds now use the ``pyramid_debugtoolbar`` package rather than the ``WebError`` package to provide interactive debugging features. - Projects created via a scaffold no longer depend on the ``WebError`` package at all; configuration in the ``production.ini`` file which used to require its ``error_catcher`` middleware has been removed. Configuring error catching / email sending is now the domain of the ``pyramid_exclog`` package (see http://docs.pylonsproject.org/projects/pyramid_exclog/en/latest/). Bug Fixes --------- - Fixed an issue with the default renderer not working at certain times. See https://github.com/Pylons/pyramid/issues/249 1.1 (2011-07-22) ================ Features -------- - Added the ``pyramid.renderers.null_renderer`` object as an API. The null renderer is an object that can be used in advanced integration cases as input to the view configuration ``renderer=`` argument. When the null renderer is used as a view renderer argument, Pyramid avoids converting the view callable result into a Response object. This is useful if you want to reuse the view configuration and lookup machinery outside the context of its use by the Pyramid router. This feature was added for consumption by the ``pyramid_rpc`` package, which uses view configuration and lookup outside the context of a router in exactly this way. ``pyramid_rpc`` has been broken under 1.1 since 1.1b1; adding it allows us to make it work again. - Change all scaffolding templates that point to docs.pylonsproject.org to use ``/projects/pyramid/current`` rather than ``/projects/pyramid/dev``. Internals --------- - Remove ``compat`` code that served only the purpose of providing backwards compatibility with Python 2.4. - Add a deprecation warning for non-API function ``pyramid.renderers.renderer_from_name`` which has seen use in the wild. - Add a ``clone`` method to ``pyramid.renderers.RendererHelper`` for use by the ``pyramid.view.view_config`` decorator. Documentation ------------- - Fixed two typos in wiki2 (SQLA + URL Dispatch) tutorial. - Reordered chapters in narrative section for better new user friendliness. - Added more indexing markers to sections in documentation. 1.1b4 (2011-07-18) ================== Documentation ------------- - Added a section entitled "Writing a Script" to the "Command-Line Pyramid" chapter. Backwards Incompatibilities --------------------------- - We added the ``pyramid.scripting.make_request`` API too hastily in 1.1b3. It has been removed. Sorry for any inconvenience. Use the ``pyramid.request.Request.blank`` API instead. Features -------- - The ``paster pshell``, ``paster pviews``, and ``paster proutes`` commands each now under the hood uses ``pyramid.paster.bootstrap``, which makes it possible to supply an ``.ini`` file without naming the "right" section in the file that points at the actual Pyramid application. Instead, you can generally just run ``paster {pshell|proutes|pviews} development.ini`` and it will do mostly the right thing. Bug Fixes --------- - Omit custom environ variables when rendering a custom exception template in ``pyramid.httpexceptions.WSGIHTTPException._set_default_attrs``; stringifying thse may trigger code that should not be executed; see https://github.com/Pylons/pyramid/issues/239 1.1b3 (2011-07-15) ================== Features -------- - Fix corner case to ease semifunctional testing of views: create a new rendererinfo to clear out old registry on a rescan. See https://github.com/Pylons/pyramid/pull/234. - New API class: ``pyramid.static.static_view``. This supersedes the deprecated ``pyramid.view.static`` class. ``pyramid.static.static_view`` by default serves up documents as the result of the request's ``path_info``, attribute rather than it's ``subpath`` attribute (the inverse was true of ``pyramid.view.static``, and still is). ``pyramid.static.static_view`` exposes a ``use_subpath`` flag for use when you want the static view to behave like the older deprecated version. - A new API function ``pyramid.paster.bootstrap`` has been added to make writing scripts that bootstrap a Pyramid environment easier, e.g.:: from pyramid.paster import bootstrap info = bootstrap('/path/to/my/development.ini') request = info['request'] print request.route_url('myroute') - A new API function ``pyramid.scripting.prepare`` has been added. It is a lower-level analogue of ``pyramid.paster.boostrap`` that accepts a request and a registry instead of a config file argument, and is used for the same purpose:: from pyramid.scripting import prepare info = prepare(registry=myregistry) request = info['request'] print request.route_url('myroute') - A new API function ``pyramid.scripting.make_request`` has been added. The resulting request will have a ``registry`` attribute. It is meant to be used in conjunction with ``pyramid.scripting.prepare`` and/or ``pyramid.paster.bootstrap`` (both of which accept a request as an argument):: from pyramid.scripting import make_request request = make_request('/') - New API attribute ``pyramid.config.global_registries`` is an iterable object that contains references to every Pyramid registry loaded into the current process via ``pyramid.config.Configurator.make_app``. It also has a ``last`` attribute containing the last registry loaded. This is used by the scripting machinery, and is available for introspection. Deprecations ------------ - The ``pyramid.view.static`` class has been deprecated in favor of the newer ``pyramid.static.static_view`` class. A deprecation warning is raised when it is used. You should replace it with a reference to ``pyramid.static.static_view`` with the ``use_subpath=True`` argument. Bug Fixes --------- - Without a mo-file loaded for the combination of domain/locale, ``pyramid.i18n.Localizer.pluralize`` run using that domain/locale combination raised an inscrutable "translations object has no attr 'plural'" error. Now, instead it "works" (it uses a germanic pluralization by default). It's nonsensical to try to pluralize something without translations for that locale/domain available, but this behavior matches the behavior of ``pyramid.i18n.Localizer.translate`` so it's at least consistent; see https://github.com/Pylons/pyramid/issues/235. 1.1b2 (2011-07-13) ================== Features -------- - New environment setting ``PYRAMID_PREVENT_HTTP_CACHE`` and new configuration file value ``prevent_http_cache``. These are synomymous and allow you to prevent HTTP cache headers from being set by Pyramid's ``http_cache`` machinery globally in a process. see the "Influencing HTTP Caching" section of the "View Configuration" narrative chapter and the detailed documentation for this setting in the "Environment Variables and Configuration Settings" narrative chapter. Behavior Changes ---------------- - Previously, If a ``BeforeRender`` event subscriber added a value via the ``__setitem__`` or ``update`` methods of the event object with a key that already existed in the renderer globals dictionary, a ``KeyError`` was raised. With the deprecation of the "add_renderer_globals" feature of the configurator, there was no way to override an existing value in the renderer globals dictionary that already existed. Now, the event object will overwrite an older value that is already in the globals dictionary when its ``__setitem__`` or ``update`` is called (as well as the new ``setdefault`` method), just like a plain old dictionary. As a result, for maximum interoperability with other third-party subscribers, if you write an event subscriber meant to be used as a BeforeRender subscriber, your subscriber code will now need to (using ``.get`` or ``__contains__`` of the event object) ensure no value already exists in the renderer globals dictionary before setting an overriding value. Bug Fixes --------- - The ``Configurator.add_route`` method allowed two routes with the same route to be added without an intermediate ``config.commit()``. If you now receive a ``ConfigurationError`` at startup time that appears to be ``add_route`` related, you'll need to either a) ensure that all of your route names are unique or b) call ``config.commit()`` before adding a second route with the name of a previously added name or c) use a Configurator that works in ``autocommit`` mode. - The ``pyramid_routesalchemy`` and ``pyramid_alchemy`` scaffolds inappropriately used ``DBSession.rollback()`` instead of ``transaction.abort()`` in one place. - We now clear ``request.response`` before we invoke an exception view; an exception view will be working with a request.response that has not been touched by any code prior to the exception. - Views associated with routes with spaces in the route name may not have been looked up correctly when using Pyramid with ``zope.interface`` 3.6.4 and better. See https://github.com/Pylons/pyramid/issues/232. Documentation ------------- - Wiki2 (SQLAlchemy + URL Dispatch) tutorial ``models.initialize_sql`` didn't match the ``pyramid_routesalchemy`` scaffold function of the same name; it didn't get synchronized when it was changed in the scaffold. - New documentation section in View Configuration narrative chapter: "Influencing HTTP Caching". 1.1b1 (2011-07-10) ================== Features -------- - It is now possible to invoke ``paster pshell`` even if the paste ini file section name pointed to in its argument is not actually a Pyramid WSGI application. The shell will work in a degraded mode, and will warn the user. See "The Interactive Shell" in the "Creating a Pyramid Project" narrative documentation section. - ``paster pshell`` now offers more built-in global variables by default (including ``app`` and ``settings``). See "The Interactive Shell" in the "Creating a Pyramid Project" narrative documentation section. - It is now possible to add a ``[pshell]`` section to your application's .ini configuration file, which influences the global names available to a pshell session. See "Extending the Shell" in the "Creating a Pyramid Project" narrative documentation chapter. - The ``config.scan`` method has grown a ``**kw`` argument. ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object created by Pyramid. (See the Venusian documentation for more information about ``Scanner``). - New request property: ``json_body``. This property will return the JSON-decoded variant of the request body. If the request body is not well-formed JSON, this property will raise an exception. - A new value ``http_cache`` can be used as a view configuration parameter. When you supply an ``http_cache`` value to a view configuration, the ``Expires`` and ``Cache-Control`` headers of a response generated by the associated view callable are modified. The value for ``http_cache`` may be one of the following: - A nonzero integer. If it's a nonzero integer, it's treated as a number of seconds. This number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=3600`` instructs the requesting browser to 'cache this response for an hour, please'. - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` instance, it will be converted into a number of seconds, and that number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=datetime.timedelta(days=1)`` instructs the requesting browser to 'cache this response for a day, please'. - Zero (``0``). If the value is zero, the ``Cache-Control`` and ``Expires`` headers present in all responses from this view will be composed such that client browser cache (and any intermediate caches) are instructed to never cache the response. - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, {'public':True})``), the first value in the tuple may be a nonzero integer or a ``datetime.timedelta`` instance; in either case this value will be used as the number of seconds to cache the response. The second value in the tuple must be a dictionary. The values present in the dictionary will be used as input to the ``Cache-Control`` response header. For example: ``http_cache=(3600, {'public':True})`` means 'cache for an hour, and add ``public`` to the Cache-Control header of the response'. All keys and values supported by the ``webob.cachecontrol.CacheControl`` interface may be added to the dictionary. Supplying ``{'public':True}`` is equivalent to calling ``response.cache_control.public = True``. Providing a non-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value)`` within your view's body. Providing a two-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value[0], **value[1])`` within your view's body. If you wish to avoid influencing, the ``Expires`` header, and instead wish to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. Bug Fixes --------- - Framework wrappers of the original view (such as http_cached and so on) relied on being able to trust that the response they were receiving was an IResponse. It wasn't always, because the response was resolved by the router instead of early in the view wrapping process. This has been fixed. Documentation ------------- - Added a section in the "Webob" chapter named "Dealing With A JSON-Encoded Request Body" (usage of ``request.json_body``). Behavior Changes ---------------- - The ``paster pshell``, ``paster proutes``, and ``paster pviews`` commands now take a single argument in the form ``/path/to/config.ini#sectionname`` rather than the previous 2-argument spelling ``/path/to/config.ini sectionname``. ``#sectionname`` may be omitted, in which case ``#main`` is assumed. 1.1a4 (2011-07-01) ================== Bug Fixes --------- - ``pyramid.testing.DummyRequest`` now raises deprecation warnings when attributes deprecated for ``pyramid.request.Request`` are accessed (like ``response_content_type``). This is for the benefit of folks running unit tests which use DummyRequest instead of a "real" request, so they know things are deprecated without necessarily needing a functional test suite. - The ``pyramid.events.subscriber`` directive behaved contrary to the documentation when passed more than one interface object to its constructor. For example, when the following listener was registered:: @subscriber(IFoo, IBar) def expects_ifoo_events_and_ibar_events(event): print event The Events chapter docs claimed that the listener would be registered and listening for both ``IFoo`` and ``IBar`` events. Instead, it registered an "object event" subscriber which would only be called if an IObjectEvent was emitted where the object interface was ``IFoo`` and the event interface was ``IBar``. The behavior now matches the documentation. If you were relying on the buggy behavior of the 1.0 ``subscriber`` directive in order to register an object event subscriber, you must now pass a sequence to indicate you'd like to register a subscriber for an object event. e.g.:: @subscriber([IFoo, IBar]) def expects_object_event(object, event): print object, event Features -------- - Add JSONP renderer (see "JSONP renderer" in the Renderers chapter of the documentation). Deprecations ------------ - Deprecated the ``set_renderer_globals_factory`` method of the Configurator and the ``renderer_globals`` Configurator constructor parameter. Documentation ------------- - The Wiki and Wiki2 tutorial "Tests" chapters each had two bugs: neither did told the user to depend on WebTest, and 2 tests failed in each as the result of changes to Pyramid itself. These issues have been fixed. - Move 1.0.X CHANGES.txt entries to HISTORY.txt. 1.1a3 (2011-06-26) ================== Features -------- - Added ``mako.preprocessor`` config file parameter; allows for a Mako preprocessor to be specified as a Python callable or Python dotted name. See https://github.com/Pylons/pyramid/pull/183 for rationale. Bug fixes --------- - Pyramid would raise an AttributeError in the Configurator when attempting to set a ``__text__`` attribute on a custom predicate that was actually a classmethod. See https://github.com/Pylons/pyramid/pull/217 . - Accessing or setting deprecated response_* attrs on request (e.g. ``response_content_type``) now issues a deprecation warning at access time rather than at rendering time. 1.1a2 (2011-06-22) ================== Bug Fixes --------- - 1.1a1 broke Akhet by not providing a backwards compatibility import shim for ``pyramid.paster.PyramidTemplate``. Now one has been added, although a deprecation warning is emitted when Akhet imports it. - If multiple specs were provided in a single call to ``config.add_translation_dirs``, the directories were inserted into the beginning of the directory list in the wrong order: they were inserted in the reverse of the order they were provided in the ``*specs`` list (items later in the list were added before ones earlier in the list). This is now fixed. Backwards Incompatibilities --------------------------- - The pyramid Router attempted to set a value into the key ``environ['repoze.bfg.message']`` when it caught a view-related exception for backwards compatibility with applications written for ``repoze.bfg`` during error handling. It did this by using code that looked like so:: # "why" is an exception object try: msg = why[0] except: msg = '' environ['repoze.bfg.message'] = msg Use of the value ``environ['repoze.bfg.message']`` was docs-deprecated in Pyramid 1.0. Our standing policy is to not remove features after a deprecation for two full major releases, so this code was originally slated to be removed in Pyramid 1.2. However, computing the ``repoze.bfg.message`` value was the source of at least one bug found in the wild (https://github.com/Pylons/pyramid/issues/199), and there isn't a foolproof way to both preserve backwards compatibility and to fix the bug. Therefore, the code which sets the value has been removed in this release. Code in exception views which relies on this value's presence in the environment should now use the ``exception`` attribute of the request (e.g. ``request.exception[0]``) to retrieve the message instead of relying on ``request.environ['repoze.bfg.message']``. 1.1a1 (2011-06-20) ================== Documentation ------------- - The term "template" used to refer to both "paster templates" and "rendered templates" (templates created by a rendering engine. i.e. Mako, Chameleon, Jinja, etc.). "Paster templates" will now be refered to as "scaffolds", whereas the name for "rendered templates" will remain as "templates." - The ``wiki`` (ZODB+Traversal) tutorial was updated slightly. - The ``wiki2`` (SQLA+URL Dispatch) tutorial was updated slightly. - Make ``pyramid.interfaces.IAuthenticationPolicy`` and ``pyramid.interfaces.IAuthorizationPolicy`` public interfaces, and refer to them within the ``pyramid.authentication`` and ``pyramid.authorization`` API docs. - Render the function definitions for each exposed interface in ``pyramid.interfaces``. - Add missing docs reference to ``pyramid.config.Configurator.set_view_mapper`` and refer to it within Hooks chapter section named "Using a View Mapper". - Added section to the "Environment Variables and ``.ini`` File Settings" chapter in the narrative documentation section entitled "Adding a Custom Setting". - Added documentation for a "multidict" (e.g. the API of ``request.POST``) as interface API documentation. - Added a section to the "URL Dispatch" narrative chapter regarding the new "static" route feature. - Added "What's New in Pyramid 1.1" to HTML rendering of documentation. - Added API docs for ``pyramid.authentication.SessionAuthenticationPolicy``. - Added API docs for ``pyramid.httpexceptions.exception_response``. - Added "HTTP Exceptions" section to Views narrative chapter including a description of ``pyramid.httpexceptions.exception_response``. Features -------- - Add support for language fallbacks: when trying to translate for a specific territory (such as ``en_GB``) fall back to translations for the language (ie ``en``). This brings the translation behaviour in line with GNU gettext and fixes partially translated texts when using C extensions. - New authentication policy: ``pyramid.authentication.SessionAuthenticationPolicy``, which uses a session to store credentials. - Accessing the ``response`` attribute of a ``pyramid.request.Request`` object (e.g. ``request.response`` within a view) now produces a new ``pyramid.response.Response`` object. This feature is meant to be used mainly when a view configured with a renderer needs to set response attributes: all renderers will use the Response object implied by ``request.response`` as the response object returned to the router. ``request.response`` can also be used by code in a view that does not use a renderer, however the response object that is produced by ``request.response`` must be returned when a renderer is not in play (it is not a "global" response). - Integers and longs passed as ``elements`` to ``pyramid.url.resource_url`` or ``pyramid.request.Request.resource_url`` e.g. ``resource_url(context, request, 1, 2)`` (``1`` and ``2`` are the ``elements``) will now be converted implicitly to strings in the result. Previously passing integers or longs as elements would cause a TypeError. - ``pyramid_alchemy`` paster template now uses ``query.get`` rather than ``query.filter_by`` to take better advantage of identity map caching. - ``pyramid_alchemy`` paster template now has unit tests. - Added ``pyramid.i18n.make_localizer`` API (broken out from ``get_localizer`` guts). - An exception raised by a NewRequest event subscriber can now be caught by an exception view. - It is now possible to get information about why Pyramid raised a Forbidden exception from within an exception view. The ``ACLDenied`` object returned by the ``permits`` method of each stock authorization policy (``pyramid.interfaces.IAuthorizationPolicy.permits``) is now attached to the Forbidden exception as its ``result`` attribute. Therefore, if you've created a Forbidden exception view, you can see the ACE, ACL, permission, and principals involved in the request as eg. ``context.result.permission``, ``context.result.acl``, etc within the logic of the Forbidden exception view. - Don't explicitly prevent the ``timeout`` from being lower than the ``reissue_time`` when setting up an ``AuthTktAuthenticationPolicy`` (previously such a configuration would raise a ``ValueError``, now it's allowed, although typically nonsensical). Allowing the nonsensical configuration made the code more understandable and required fewer tests. - A new paster command named ``paster pviews`` was added. This command prints a summary of potentially matching views for a given path. See the section entitled "Displaying Matching Views for a Given URL" in the "View Configuration" chapter of the narrative documentation for more information. - The ``add_route`` method of the Configurator now accepts a ``static`` argument. If this argument is ``True``, the added route will never be considered for matching when a request is handled. Instead, it will only be useful for URL generation via ``route_url`` and ``route_path``. See the section entitled "Static Routes" in the URL Dispatch narrative chapter for more information. - A default exception view for the context ``pyramid.interfaces.IExceptionResponse`` is now registered by default. This means that an instance of any exception response class imported from ``pyramid.httpexceptions`` (such as ``HTTPFound``) can now be raised from within view code; when raised, this exception view will render the exception to a response. - A function named ``pyramid.httpexceptions.exception_response`` is a shortcut that can be used to create HTTP exception response objects using an HTTP integer status code. - The Configurator now accepts an additional keyword argument named ``exceptionresponse_view``. By default, this argument is populated with a default exception view function that will be used when a response is raised as an exception. When ``None`` is passed for this value, an exception view for responses will not be registered. Passing ``None`` returns the behavior of raising an HTTP exception to that of Pyramid 1.0 (the exception will propagate to middleware and to the WSGI server). - The ``pyramid.request.Request`` class now has a ``ResponseClass`` interface which points at ``pyramid.response.Response``. - The ``pyramid.response.Response`` class now has a ``RequestClass`` interface which points at ``pyramid.request.Request``. - It is now possible to return an arbitrary object from a Pyramid view callable even if a renderer is not used, as long as a suitable adapter to ``pyramid.interfaces.IResponse`` is registered for the type of the returned object by using the new ``pyramid.config.Configurator.add_response_adapter`` API. See the section in the Hooks chapter of the documentation entitled "Changing How Pyramid Treats View Responses". - The Pyramid router will now, by default, call the ``__call__`` method of WebOb response objects when returning a WSGI response. This means that, among other things, the ``conditional_response`` feature of WebOb response objects will now behave properly. - New method named ``pyramid.request.Request.is_response``. This method should be used instead of the ``pyramid.view.is_response`` function, which has been deprecated. Bug Fixes --------- - URL pattern markers used in URL dispatch are permitted to specify a custom regex. For example, the pattern ``/{foo:\d+}`` means to match ``/12345`` (foo==12345 in the match dictionary) but not ``/abc``. However, custom regexes in a pattern marker which used squiggly brackets did not work. For example, ``/{foo:\d{4}}`` would fail to match ``/1234`` and ``/{foo:\d{1,2}}`` would fail to match ``/1`` or ``/11``. One level of inner squiggly brackets is now recognized so that the prior two patterns given as examples now work. See also https://github.com/Pylons/pyramid/issues/#issue/123. - Don't send port numbers along with domain information in cookies set by AuthTktCookieHelper (see https://github.com/Pylons/pyramid/issues/131). - ``pyramid.url.route_path`` (and the shortcut ``pyramid.request.Request.route_url`` method) now include the WSGI SCRIPT_NAME at the front of the path if it is not empty (see https://github.com/Pylons/pyramid/issues/135). - ``pyramid.testing.DummyRequest`` now has a ``script_name`` attribute (the empty string). - Don't quote ``:@&+$,`` symbols in ``*elements`` passed to ``pyramid.url.route_url`` or ``pyramid.url.resource_url`` (see https://github.com/Pylons/pyramid/issues#issue/141). - Include SCRIPT_NAME in redirects issued by ``pyramid.view.append_slash_notfound_view`` (see https://github.com/Pylons/pyramid/issues#issue/149). - Static views registered with ``config.add_static_view`` which also included a ``permission`` keyword argument would not work as expected, because ``add_static_view`` also registered a route factory internally. Because a route factory was registered internally, the context checked by the Pyramid permission machinery never had an ACL. ``add_static_view`` no longer registers a route with a factory, so the default root factory will be used. - ``config.add_static_view`` now passes extra keyword arguments it receives to ``config.add_route`` (calling add_static_view is mostly logically equivalent to adding a view of the type ``pyramid.static.static_view`` hooked up to a route with a subpath). This makes it possible to pass e.g., ``factory=`` to ``add_static_view`` to protect a particular static view with a custom ACL. - ``testing.DummyRequest`` used the wrong registry (the global registry) as ``self.registry`` if a dummy request was created *before* ``testing.setUp`` was executed (``testing.setUp`` pushes a local registry onto the threadlocal stack). Fixed by implementing ``registry`` as a property for DummyRequest instead of eagerly assigning an attribute. See also https://github.com/Pylons/pyramid/issues/165 - When visiting a URL that represented a static view which resolved to a subdirectory, the ``index.html`` of that subdirectory would not be served properly. Instead, a redirect to ``/subdir`` would be issued. This has been fixed, and now visiting a subdirectory that contains an ``index.html`` within a static view returns the index.html properly. See also https://github.com/Pylons/pyramid/issues/67. - Redirects issued by a static view did not take into account any existing ``SCRIPT_NAME`` (such as one set by a url mapping composite). Now they do. - The ``pyramid.wsgi.wsgiapp2`` decorator did not take into account the ``SCRIPT_NAME`` in the origin request. - The ``pyramid.wsgi.wsgiapp2`` decorator effectively only worked when it decorated a view found via traversal; it ignored the ``PATH_INFO`` that was part of a url-dispatch-matched view. Deprecations ------------ - Deprecated all assignments to ``request.response_*`` attributes (for example ``request.response_content_type = 'foo'`` is now deprecated). Assignments and mutations of assignable request attributes that were considered by the framework for response influence are now deprecated: ``response_content_type``, ``response_headerlist``, ``response_status``, ``response_charset``, and ``response_cache_for``. Instead of assigning these to the request object for later detection by the rendering machinery, users should use the appropriate API of the Response object created by accessing ``request.response`` (e.g. code which does ``request.response_content_type = 'abc'`` should be changed to ``request.response.content_type = 'abc'``). - Passing view-related parameters to ``pyramid.config.Configurator.add_route`` is now deprecated. Previously, a view was permitted to be connected to a route using a set of ``view*`` parameters passed to the ``add_route`` method of the Configurator. This was a shorthand which replaced the need to perform a subsequent call to ``add_view``. For example, it was valid (and often recommended) to do:: config.add_route('home', '/', view='mypackage.views.myview', view_renderer='some/renderer.pt') Passing ``view*`` arguments to ``add_route`` is now deprecated in favor of connecting a view to a predefined route via ``Configurator.add_view`` using the route's ``route_name`` parameter. As a result, the above example should now be spelled:: config.add_route('home', '/') config.add_view('mypackage.views.myview', route_name='home') renderer='some/renderer.pt') This deprecation was done to reduce confusion observed in IRC, as well as to (eventually) reduce documentation burden (see also https://github.com/Pylons/pyramid/issues/164). A deprecation warning is now issued when any view-related parameter is passed to ``Configurator.add_route``. - Passing an ``environ`` dictionary to the ``__call__`` method of a "traverser" (e.g. an object that implements ``pyramid.interfaces.ITraverser`` such as an instance of ``pyramid.traversal.ResourceTreeTraverser``) as its ``request`` argument now causes a deprecation warning to be emitted. Consumer code should pass a ``request`` object instead. The fact that passing an environ dict is permitted has been documentation-deprecated since ``repoze.bfg`` 1.1, and this capability will be removed entirely in a future version. - The following (undocumented, dictionary-like) methods of the ``pyramid.request.Request`` object have been deprecated: ``__contains__``, ``__delitem__``, ``__getitem__``, ``__iter__``, ``__setitem__``, ``get``, ``has_key``, ``items``, ``iteritems``, ``itervalues``, ``keys``, ``pop``, ``popitem``, ``setdefault``, ``update``, and ``values``. Usage of any of these methods will cause a deprecation warning to be emitted. These methods were added for internal compatibility in ``repoze.bfg`` 1.1 (code that currently expects a request object expected an environ object in BFG 1.0 and before). In a future version, these methods will be removed entirely. - Deprecated ``pyramid.view.is_response`` function in favor of (newly-added) ``pyramid.request.Request.is_response`` method. Determining if an object is truly a valid response object now requires access to the registry, which is only easily available as a request attribute. The ``pyramid.view.is_response`` function will still work until it is removed, but now may return an incorrect answer under some (very uncommon) circumstances. Behavior Changes ---------------- - The default Mako renderer is now configured to escape all HTML in expression tags. This is intended to help prevent XSS attacks caused by rendering unsanitized input from users. To revert this behavior in user's templates, they need to filter the expression through the 'n' filter. For example, ${ myhtml | n }. See https://github.com/Pylons/pyramid/issues/193. - A custom request factory is now required to return a request object that has a ``response`` attribute (or "reified"/lazy property) if they the request is meant to be used in a view that uses a renderer. This ``response`` attribute should be an instance of the class ``pyramid.response.Response``. - The JSON and string renderer factories now assign to ``request.response.content_type`` rather than ``request.response_content_type``. - Each built-in renderer factory now determines whether it should change the content type of the response by comparing the response's content type against the response's default content type; if the content type is the default content type (usually ``text/html``), the renderer changes the content type (to ``application/json`` or ``text/plain`` for JSON and string renderers respectively). - The ``pyramid.wsgi.wsgiapp2`` now uses a slightly different method of figuring out how to "fix" ``SCRIPT_NAME`` and ``PATH_INFO`` for the downstream application. As a result, those values may differ slightly from the perspective of the downstream application (for example, ``SCRIPT_NAME`` will now never possess a trailing slash). - Previously, ``pyramid.request.Request`` inherited from ``webob.request.Request`` and implemented ``__getattr__``, ``__setattr__`` and ``__delattr__`` itself in order to overidde "adhoc attr" WebOb behavior where attributes of the request are stored in the environ. Now, ``pyramid.request.Request`` object inherits from (the more recent) ``webob.request.BaseRequest`` instead of ``webob.request.Request``, which provides the same behavior. ``pyramid.request.Request`` no longer implements its own ``__getattr__``, ``__setattr__`` or ``__delattr__`` as a result. - ``pyramid.response.Response`` is now a *subclass* of ``webob.response.Response`` (in order to directly implement the ``pyramid.interfaces.IResponse`` interface). - The "exception response" objects importable from ``pyramid.httpexceptions`` (e.g. ``HTTPNotFound``) are no longer just import aliases for classes that actually live in ``webob.exc``. Instead, we've defined our own exception classes within the module that mirror and emulate the ``webob.exc`` exception response objects almost entirely. See the "Design Defense" doc section named "Pyramid Uses its Own HTTP Exception Classes" for more information. Backwards Incompatibilities --------------------------- - Pyramid no longer supports Python 2.4. Python 2.5 or better is required to run Pyramid 1.1+. - The Pyramid router now, by default, expects response objects returned from view callables to implement the ``pyramid.interfaces.IResponse`` interface. Unlike the Pyramid 1.0 version of this interface, objects which implement IResponse now must define a ``__call__`` method that accepts ``environ`` and ``start_response``, and which returns an ``app_iter`` iterable, among other things. Previously, it was possible to return any object which had the three WebOb ``app_iter``, ``headerlist``, and ``status`` attributes as a response, so this is a backwards incompatibility. It is possible to get backwards compatibility back by registering an adapter to IResponse from the type of object you're now returning from view callables. See the section in the Hooks chapter of the documentation entitled "Changing How Pyramid Treats View Responses". - The ``pyramid.interfaces.IResponse`` interface is now much more extensive. Previously it defined only ``app_iter``, ``status`` and ``headerlist``; now it is basically intended to directly mirror the ``webob.Response`` API, which has many methods and attributes. - The ``pyramid.httpexceptions`` classes named ``HTTPFound``, ``HTTPMultipleChoices``, ``HTTPMovedPermanently``, ``HTTPSeeOther``, ``HTTPUseProxy``, and ``HTTPTemporaryRedirect`` now accept ``location`` as their first positional argument rather than ``detail``. This means that you can do, e.g. ``return pyramid.httpexceptions.HTTPFound('http://foo')`` rather than ``return pyramid.httpexceptions.HTTPFound(location='http//foo')`` (the latter will of course continue to work). Dependencies ------------ - Pyramid now depends on WebOb >= 1.0.2 as tests depend on the bugfix in that release: "Fix handling of WSGI environs with missing ``SCRIPT_NAME``". (Note that in reality, everyone should probably be using 1.0.4 or better though, as WebOb 1.0.2 and 1.0.3 were effectively brownbag releases.) 1.0 (2011-01-30) ================ Documentation ------------- - Fixed bug in ZODB Wiki tutorial (missing dependency on ``docutils`` in "models" step within ``setup.py``). - Removed API documentation for ``pyramid.testing`` APIs named ``registerDummySecurityPolicy``, ``registerResources``, ``registerModels``, ``registerEventListener``, ``registerTemplateRenderer``, ``registerDummyRenderer``, ``registerView``, ``registerUtility``, ``registerAdapter``, ``registerSubscriber``, ``registerRoute``, and ``registerSettings``. - Moved "Using ZODB With ZEO" and "Using repoze.catalog Within Pyramid" tutorials out of core documentation and into the Pyramid Tutorials site (http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/). - Changed "Cleaning up After a Request" section in the URL Dispatch chapter to use ``request.add_finished_callback`` instead of jamming an object with a ``__del__`` into the WSGI environment. - Remove duplication of ``add_route`` API documentation from URL Dispatch narrative chapter. - Remove duplication of API and narrative documentation in ``pyramid.view.view_config`` API docs by pointing to ``pyramid.config.add_view`` documentation and narrative chapter documentation. - Removed some API documentation duplicated in narrative portions of documentation - Removed "Overall Flow of Authentication" from SQLAlchemy + URL Dispatch wiki tutorial due to print space concerns (moved to Pyramid Tutorials site). Bug Fixes --------- - Deprecated-since-BFG-1.2 APIs from ``pyramid.testing`` now properly emit deprecation warnings. - Added ``egg:repoze.retry#retry`` middleware to the WSGI pipeline in ZODB templates (retry ZODB conflict errors which occur in normal operations). - Removed duplicate implementations of ``is_response``. Two competing implementations existed: one in ``pyramid.config`` and one in ``pyramid.view``. Now the one defined in ``pyramid.view`` is used internally by ``pyramid.config`` and continues to be advertised as an API. 1.0b3 (2011-01-28) ================== Bug Fixes --------- - Use © instead of copyright symbol in paster templates / tutorial templates for the benefit of folks who cutnpaste and save to a non-UTF8 format. - ``pyramid.view.append_slash_notfound_view`` now preserves GET query parameters across redirects. Documentation ------------- - Beef up documentation related to ``set_default_permission``: explicitly mention that default permissions also protect exception views. - Paster templates and tutorials now use spaces instead of tabs in their HTML templates. 1.0b2 (2011-01-24) ================== Bug Fixes --------- - The ``production.ini`` generated by all paster templates now have an effective logging level of WARN, which prevents e.g. SQLAlchemy statement logging and other inappropriate output. - The ``production.ini`` of the ``pyramid_routesalchemy`` and ``pyramid_alchemy`` paster templates did not have a ``sqlalchemy`` logger section, preventing ``paster serve production.ini`` from working. - The ``pyramid_routesalchemy`` and ``pyramid_alchemy`` paster templates used the ``{{package}}`` variable in a place where it should have used the ``{{project}}`` variable, causing applications created with uppercase letters e.g. ``paster create -t pyramid_routesalchemy Dibbus`` to fail to start when ``paster serve development.ini`` was used against the result. See https://github.com/Pylons/pyramid/issues/#issue/107 - The ``render_view`` method of ``pyramid.renderers.RendererHelper`` passed an incorrect value into the renderer for ``renderer_info``. It now passes an instance of ``RendererHelper`` instead of a dictionary, which is consistent with other usages. See https://github.com/Pylons/pyramid/issues#issue/106 - A bug existed in the ``pyramid.authentication.AuthTktCookieHelper`` which would break any usage of an AuthTktAuthenticationPolicy when one was configured to reissue its tokens (``reissue_time`` < ``timeout`` / ``max_age``). Symptom: ``ValueError: ('Invalid token %r', '')``. See https://github.com/Pylons/pyramid/issues#issue/108. 1.0b1 (2011-01-21) ================== Features -------- - The AuthTktAuthenticationPolicy now accepts a ``tokens`` parameter via ``pyramid.security.remember``. The value must be a sequence of strings. Tokens are placed into the auth_tkt "tokens" field and returned in the auth_tkt cookie. - Add ``wild_domain`` argument to AuthTktAuthenticationPolicy, which defaults to ``True``. If it is set to ``False``, the feature of the policy which sets a cookie with a wildcard domain will be turned off. - Add a ``MANIFEST.in`` file to each paster template. See https://github.com/Pylons/pyramid/issues#issue/95 Bug Fixes --------- - ``testing.setUp`` now adds a ``settings`` attribute to the registry (both when it's passed a registry without any settings and when it creates one). - The ``testing.setUp`` function now takes a ``settings`` argument, which should be a dictionary. Its values will subsequently be available on the returned ``config`` object as ``config.registry.settings``. Documentation ------------- - Added "What's New in Pyramid 1.0" chapter to HTML rendering of documentation. - Merged caseman-master narrative editing branch, many wording fixes and extensions. - Fix deprecated example showing ``chameleon_zpt`` API call in testing narrative chapter. - Added "Adding Methods to the Configurator via ``add_directive``" section to Advanced Configuration narrative chapter. - Add docs for ``add_finished_callback``, ``add_response_callback``, ``route_path``, ``route_url``, and ``static_url`` methods to ``pyramid.request.Request`` API docs. - Add (minimal) documentation about using I18N within Mako templates to "Internationalization and Localization" narrative chapter. - Move content of "Forms" chapter back to "Views" chapter; I can't think of a better place to put it. - Slightly improved interface docs for ``IAuthorizationPolicy``. - Minimally explain usage of custom regular expressions in URL dispatch replacement markers within URL Dispatch chapter. Deprecations ------------- - Using the ``pyramid.view.bfg_view`` alias for ``pyramid.view.view_config`` (a backwards compatibility shim) now issues a deprecation warning. Backwards Incompatibilities --------------------------- - Using ``testing.setUp`` now registers an ISettings utility as a side effect. Some test code which queries for this utility after ``testing.setUp`` via queryAdapter will expect a return value of ``None``. This code will need to be changed. - When a ``pyramid.exceptions.Forbidden`` error is raised, its status code now ``403 Forbidden``. It was previously ``401 Unauthorized``, for backwards compatibility purposes with ``repoze.bfg``. This change will cause problems for users of Pyramid with ``repoze.who``, which intercepts ``401 Unauthorized`` by default, but allows ``403 Forbidden`` to pass through. Those deployments will need to configure ``repoze.who`` to also react to ``403 Forbidden``. - The default value for the ``cookie_on_exception`` parameter to ``pyramid.session.UnencyrptedCookieSessionFactory`` is now ``True``. This means that when view code causes an exception to be raised, and the session has been mutated, a cookie will be sent back in the response. Previously its default value was ``False``. Paster Templates ---------------- - The ``pyramid_zodb``, ``pyramid_routesalchemy`` and ``pyramid_alchemy`` paster templates now use a default "commit veto" hook when configuring the ``repoze.tm2`` transaction manager in ``development.ini``. This prevents a transaction from being committed when the response status code is within the 400 or 500 ranges. See also http://docs.repoze.org/tm2/#using-a-commit-veto. 1.0a10 (2011-01-18) =================== Bug Fixes --------- - URL dispatch now properly handles a ``.*`` or ``*`` appearing in a regex match when used inside brackets. Resolves issue #90. Backwards Incompatibilities --------------------------- - The ``add_handler`` method of a Configurator has been removed from the Pyramid core. Handlers are now a feature of the ``pyramid_handlers`` package, which can be downloaded from PyPI. Documentation for the package should be available via http://docs.pylonsproject.org/projects/pyramid_handlers/en/latest/, which describes how to add a configuration statement to your ``main`` block to reobtain this method. You will also need to add an ``install_requires`` dependency upon ``pyramid_handlers`` to your ``setup.py`` file. - The ``load_zcml`` method of a Configurator has been removed from the Pyramid core. Loading ZCML is now a feature of the ``pyramid_zcml`` package, which can be downloaded from PyPI. Documentation for the package should be available via http://docs.pylonsproject.org/projects/pyramid_zcml/en/latest/, which describes how to add a configuration statement to your ``main`` block to reobtain this method. You will also need to add an ``install_requires`` dependency upon ``pyramid_zcml`` to your ``setup.py`` file. - The ``pyramid.includes`` subpackage has been removed. ZCML files which use include the package ``pyramid.includes`` (e.g. ````) now must include the ``pyramid_zcml`` package instead (e.g. ````). - The ``pyramid.view.action`` decorator has been removed from the Pyramid core. Handlers are now a feature of the ``pyramid_handlers`` package. It should now be imported from ``pyramid_handlers`` e.g. ``from pyramid_handlers import action``. - The ``handler`` ZCML directive has been removed. It is now a feature of the ``pyramid_handlers`` package. - The ``pylons_minimal``, ``pylons_basic`` and ``pylons_sqla`` paster templates were removed. Use ``pyramid_sqla`` (available from PyPI) as a generic replacement for Pylons-esque development. - The ``make_app`` function has been removed from the ``pyramid.router`` module. It continues life within the ``pyramid_zcml`` package. This leaves the ``pyramid.router`` module without any API functions. - The ``configure_zcml`` setting within the deployment settings (within ``**settings`` passed to a Pyramid ``main`` function) has ceased to have any meaning. Features -------- - ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` have been undeprecated. They are now the canonical setup and teardown APIs for test configuration, replacing "direct" creation of a Configurator. This is a change designed to provide a facade that will protect against any future Configurator deprecations. - Add ``charset`` attribute to ``pyramid.testing.DummyRequest`` (unconditionally ``UTF-8``). - Add ``add_directive`` method to configurator, which allows framework extenders to add methods to the configurator (ala ZCML directives). - When ``Configurator.include`` is passed a *module* as an argument, it defaults to attempting to find and use a callable named ``includeme`` within that module. This makes it possible to use ``config.include('some.module')`` rather than ``config.include('some.module.somefunc')`` as long as the include function within ``some.module`` is named ``includeme``. - The ``bfg2pyramid`` script now converts ZCML include tags that have ``repoze.bfg.includes`` as a package attribute to the value ``pyramid_zcml``. For example, ```` will be converted to ````. Paster Templates ---------------- - All paster templates now use ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` rather than creating a Configurator "by hand" within their ``tests.py`` module, as per decision in features above. - The ``starter_zcml`` paster template has been moved to the ``pyramid_zcml`` package. Documentation ------------- - The wiki and wiki2 tutorials now use ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` rather than creating a Configurator "by hand", as per decision in features above. - The "Testing" narrative chapter now explains ``pyramid.testing.setUp`` and ``pyramid.testing.tearDown`` instead of Configurator creation and ``Configurator.begin()`` and ``Configurator.end()``. - Document the ``request.override_renderer`` attribute within the narrative "Renderers" chapter in a section named "Overriding A Renderer at Runtime". - The "Declarative Configuration" narrative chapter has been removed (it was moved to the ``pyramid_zcml`` package). - Most references to ZCML in narrative chapters have been removed or redirected to ``pyramid_zcml`` locations. Deprecations ------------ - Deprecation warnings related to import of the following API functions were added: ``pyramid.traversal.find_model``, ``pyramid.traversal.model_path``, ``pyramid.traversal.model_path_tuple``, ``pyramid.url.model_url``. The instructions emitted by the deprecation warnings instruct the developer to change these method spellings to their ``resource`` equivalents. This is a consequence of the mass concept rename of "model" to "resource" performed in 1.0a7. 1.0a9 (2011-01-08) ================== Bug Fixes --------- - The ``proutes`` command tried too hard to resolve the view for printing, resulting in exceptions when an exceptional root factory was encountered. Instead of trying to resolve the view, if it cannot, it will now just print ````. - The `self` argument was included in new methods of the ``ISession`` interface signature, causing ``pyramid_beaker`` tests to fail. - Readd ``pyramid.traversal.model_path_tuple`` as an alias for ``pyramid.traversal.resource_path_tuple`` for backwards compatibility. Features -------- - Add a new API ``pyramid.url.current_route_url``, which computes a URL based on the "current" route (if any) and its matchdict values. - ``config.add_view`` now accepts a ``decorator`` keyword argument, a callable which will decorate the view callable before it is added to the registry. - If a handler class provides an ``__action_decorator__`` attribute (usually a classmethod or staticmethod), use that as the decorator for each view registration for that handler. - The ``pyramid.interfaces.IAuthenticationPolicy`` interface now specifies an ``unauthenticated_userid`` method. This method supports an important optimization required by people who are using persistent storages which do not support object caching and whom want to create a "user object" as a request attribute. - A new API has been added to the ``pyramid.security`` module named ``unauthenticated_userid``. This API function calls the ``unauthenticated_userid`` method of the effective security policy. - An ``unauthenticated_userid`` method has been added to the dummy authentication policy returned by ``pyramid.config.Configurator.testing_securitypolicy``. It returns the same thing as that the dummy authentication policy's ``authenticated_userid`` method. - The class ``pyramid.authentication.AuthTktCookieHelper`` is now an API. This class can be used by third-party authentication policy developers to help in the mechanics of authentication cookie-setting. - New constructor argument to Configurator: ``default_view_mapper``. Useful to create systems that have alternate view calling conventions. A view mapper allows objects that are meant to be used as view callables to have an arbitrary argument list and an arbitrary result. The object passed as ``default_view_mapper`` should implement the ``pyramid.interfaces.IViewMapperFactory`` interface. - add a ``set_view_mapper`` API to Configurator. Has the same result as passing ``default_view_mapper`` to the Configurator constructor. - ``config.add_view`` now accepts a ``mapper`` keyword argument, which should either be ``None``, a string representing a Python dotted name, or an object which is an ``IViewMapperFactory``. This feature is not useful for "civilians", only for extension writers. - Allow static renderer provided during view registration to be overridden at request time via a request attribute named ``override_renderer``, which should be the name of a previously registered renderer. Useful to provide "omnipresent" RPC using existing rendered views. - Instances of ``pyramid.testing.DummyRequest`` now have a ``session`` object, which is mostly a dictionary, but also implements the other session API methods for flash and CSRF. Backwards Incompatibilities --------------------------- - Since the ``pyramid.interfaces.IAuthenticationPolicy`` interface now specifies that a policy implementation must implement an ``unauthenticated_userid`` method, all third-party custom authentication policies now must implement this method. It, however, will only be called when the global function named ``pyramid.security.unauthenticated_userid`` is invoked, so if you're not invoking that, you will not notice any issues. - ``pyramid.interfaces.ISession.get_csrf_token`` now mandates that an implementation should return a *new* token if one doesn't already exist in the session (previously it would return None). The internal sessioning implementation has been changed. Documentation ------------- - The (weak) "Converting a CMF Application to Pyramid" tutorial has been removed from the tutorials section. It was moved to the ``pyramid_tutorials`` Github repository. - The "Resource Location and View Lookup" chapter has been replaced with a variant of Rob Miller's "Much Ado About Traversal" (originally published at http://blog.nonsequitarian.org/2010/much-ado-about-traversal/). - Many minor wording tweaks and refactorings (merged Casey Duncan's docs fork, in which he is working on general editing). - Added (weak) description of new view mapper feature to Hooks narrative chapter. - Split views chapter into 2: View Callables and View Configuration. - Reorder Renderers and Templates chapters after View Callables but before View Configuration. - Merge Session Objects, Cross-Site Request Forgery, and Flash Messaging chapter into a single Sessions chapter. - The Wiki and Wiki2 tutorials now have much nicer CSS and graphics. Internals --------- - The "view derivation" code is now factored into a set of classes rather than a large number of standalone functions (a side effect of the view mapper refactoring). - The ``pyramid.renderer.RendererHelper`` class has grown a ``render_view`` method, which is used by the default view mapper (a side effect of the view mapper refactoring). - The object passed as ``renderer`` to the "view deriver" is now an instance of ``pyramid.renderers.RendererHelper`` rather than a dictionary (a side effect of view mapper refactoring). - The class used as the "page template" in ``pyramid.chameleon_text`` was removed, in preference to using a Chameleon-inbuilt version. - A view callable wrapper registered in the registry now contains an ``__original_view__`` attribute which references the original view callable (or class). - The (non-API) method of all internal authentication policy implementations previously named ``_get_userid`` is now named ``unauthenticated_userid``, promoted to an API method. If you were overriding this method, you'll now need to override it as ``unauthenticated_userid`` instead. - Remove (non-API) function of config.py named _map_view. 1.0a8 (2010-12-27) ================== Bug Fixes --------- - The name ``registry`` was not available in the ``paster pshell`` environment under IPython. Features -------- - If a resource implements a ``__resource_url__`` method, it will be called as the result of invoking the ``pyramid.url.resource_url`` function to generate a URL, overriding the default logic. See the new "Generating The URL Of A Resource" section within the Resources narrative chapter. - Added flash messaging, as described in the "Flash Messaging" narrative documentation chapter. - Added CSRF token generation, as described in the narrative chapter entitled "Preventing Cross-Site Request Forgery Attacks". - Prevent misunderstanding of how the ``view`` and ``view_permission`` arguments to add_route work by raising an exception during configuration if view-related arguments exist but no ``view`` argument is passed. - Add ``paster proute`` command which displays a summary of the routing table. See the narrative documentation section within the "URL Dispatch" chapter entitled "Displaying All Application Routes". Paster Templates ---------------- - The ``pyramid_zodb`` Paster template no longer employs ZCML. Instead, it is based on scanning. Documentation ------------- - Added "Generating The URL Of A Resource" section to the Resources narrative chapter (includes information about overriding URL generation using ``__resource_url__``). - Added "Generating the Path To a Resource" section to the Resources narrative chapter. - Added "Finding a Resource by Path" section to the Resources narrative chapter. - Added "Obtaining the Lineage of a Resource" to the Resources narrative chapter. - Added "Determining if a Resource is In The Lineage of Another Resource" to Resources narrative chapter. - Added "Finding the Root Resource" to Resources narrative chapter. - Added "Finding a Resource With a Class or Interface in Lineage" to Resources narrative chapter. - Added a "Flash Messaging" narrative documentation chapter. - Added a narrative chapter entitled "Preventing Cross-Site Request Forgery Attacks". - Changed the "ZODB + Traversal Wiki Tutorial" based on changes to ``pyramid_zodb`` Paster template. - Added "Advanced Configuration" narrative chapter which documents how to deal with configuration conflicts, two-phase configuration, ``include`` and ``commit``. - Fix API documentation rendering for ``pyramid.view.static`` - Add "Pyramid Provides More Than One Way to Do It" to Design Defense documentation. - Changed "Static Assets" narrative chapter: clarify that ``name`` represents a prefix unless it's a URL, added an example of a root-relative static view fallback for URL dispatch, added an example of creating a simple view that returns the body of a file. - Move ZCML usage in Hooks chapter to Declarative Configuration chapter. - Merge "Static Assets" chapter into the "Assets" chapter. - Added narrative documentation section within the "URL Dispatch" chapter entitled "Displaying All Application Routes" (for ``paster proutes`` command). 1.0a7 (2010-12-20) ================== Terminology Changes ------------------- - The Pyramid concept previously known as "model" is now known as "resource". As a result: - The following API changes have been made:: pyramid.url.model_url -> pyramid.url.resource_url pyramid.traversal.find_model -> pyramid.url.find_resource pyramid.traversal.model_path -> pyramid.traversal.resource_path pyramid.traversal.model_path_tuple -> pyramid.traversal.resource_path_tuple pyramid.traversal.ModelGraphTraverser -> pyramid.traversal.ResourceTreeTraverser pyramid.config.Configurator.testing_models -> pyramid.config.Configurator.testing_resources pyramid.testing.registerModels -> pyramid.testing.registerResources pyramid.testing.DummyModel -> pyramid.testing.DummyResource - All documentation which previously referred to "model" now refers to "resource". - The ``starter`` and ``starter_zcml`` paster templates now have a ``resources.py`` module instead of a ``models.py`` module. - Positional argument names of various APIs have been changed from ``model`` to ``resource``. Backwards compatibility shims have been left in place in all cases. They will continue to work "forever". - The Pyramid concept previously known as "resource" is now known as "asset". As a result: - The (non-API) module previously known as ``pyramid.resource`` is now known as ``pyramid.asset``. - All docs that previously referred to "resource specification" now refer to "asset specification". - The following API changes were made:: pyramid.config.Configurator.absolute_resource_spec -> pyramid.config.Configurator.absolute_asset_spec pyramid.config.Configurator.override_resource -> pyramid.config.Configurator.override_asset - The ZCML directive previously known as ``resource`` is now known as ``asset``. - The setting previously known as ``BFG_RELOAD_RESOURCES`` (envvar) or ``reload_resources`` (config file) is now known, respectively, as ``PYRAMID_RELOAD_ASSETS`` and ``reload_assets``. Backwards compatibility shims have been left in place in all cases. They will continue to work "forever". Bug Fixes --------- - Make it possible to succesfully run all tests via ``nosetests`` command directly (rather than indirectly via ``python setup.py nosetests``). - When a configuration conflict is encountered during scanning, the conflict exception now shows the decorator information that caused the conflict. Features -------- - Added ``debug_routematch`` configuration setting that logs matched routes (including the matchdict and predicates). - The name ``registry`` is now available in a ``pshell`` environment by default. It is the application registry object. Environment ----------- - All environment variables which used to be prefixed with ``BFG_`` are now prefixed with ``PYRAMID_`` (e.g. ``BFG_DEBUG_NOTFOUND`` is now ``PYRAMID_DEBUG_NOTFOUND``) Documentation ------------- - Added "Debugging Route Matching" section to the urldispatch narrative documentation chapter. - Added reference to ``PYRAMID_DEBUG_ROUTEMATCH`` envvar and ``debug_routematch`` config file setting to the Environment narrative docs chapter. - Changed "Project" chapter slightly to expand on use of ``paster pshell``. - Direct Jython users to Mako rather than Jinja2 in "Install" narrative chapter. - Many changes to support terminological renaming of "model" to "resource" and "resource" to "asset". - Added an example of ``WebTest`` functional testing to the testing narrative chapter. - Rearranged chapter ordering by popular demand (URL dispatch first, then traversal). Put hybrid chapter after views chapter. - Split off "Renderers" as its own chapter from "Views" chapter in narrative documentation. Paster Templates ---------------- - Added ``debug_routematch = false`` to all paster templates. Dependencies ------------ - Depend on Venusian >= 0.5 (for scanning conflict exception decoration). 1.0a6 (2010-12-15) ================== Bug Fixes --------- - 1.0a5 introduced a bug when ``pyramid.config.Configurator.scan`` was used without a ``package`` argument (e.g. ``config.scan()`` as opposed to ``config.scan('packagename')``. The symptoms were: lots of deprecation warnings printed to the console about imports of deprecated Pyramid functions and classes and non-detection of view callables decorated with ``view_config`` decorators. This has been fixed. - Tests now pass on Windows (no bugs found, but a few tests in the test suite assumed UNIX path segments in filenames). Documentation ------------- - If you followed it to-the-letter, the ZODB+Traversal Wiki tutorial would instruct you to run a test which would fail because the view callable generated by the ``pyramid_zodb`` tutorial used a one-arg view callable, but the test in the sample code used a two-arg call. - Updated ZODB+Traversal tutorial setup.py of all steps to match what's generated by ``pyramid_zodb``. - Fix reference to ``repoze.bfg.traversalwrapper`` in "Models" chapter (point at ``pyramid_traversalwrapper`` instead). 1.0a5 (2010-12-14) ================== Features -------- - Add a ``handler`` ZCML directive. This directive does the same thing as ``pyramid.configuration.add_handler``. - A new module named ``pyramid.config`` was added. It subsumes the duties of the older ``pyramid.configuration`` module. - The new ``pyramid.config.Configurator` class has API methods that the older ``pyramid.configuration.Configurator`` class did not: ``with_context`` (a classmethod), ``include``, ``action``, and ``commit``. These methods exist for imperative application extensibility purposes. - The ``pyramid.testing.setUp`` function now accepts an ``autocommit`` keyword argument, which defaults to ``True``. If it is passed ``False``, the Config object returned by ``setUp`` will be a non-autocommiting Config object. - Add logging configuration to all paster templates. - ``pyramid_alchemy``, ``pyramid_routesalchemy``, and ``pylons_sqla`` paster templates now use idiomatic SQLAlchemy configuration in their respective ``.ini`` files and Python code. - ``pyramid.testing.DummyRequest`` now has a class variable, ``query_string``, which defaults to the empty string. - Add support for json on GAE by catching NotImplementedError and importing simplejson from django.utils. - The Mako renderer now accepts a resource specification for ``mako.module_directory``. - New boolean Mako settings variable ``mako.strict_undefined``. See `Mako Context Variables `_ for its meaning. Dependencies ------------ - Depend on Mako 0.3.6+ (we now require the ``strict_undefined`` feature). Bug Fixes --------- - When creating a Configurator from within a ``paster pshell`` session, you were required to pass a ``package`` argument although ``package`` is not actually required. If you didn't pass ``package``, you would receive an error something like ``KeyError: '__name__'`` emanating from the ``pyramid.path.caller_module`` function. This has now been fixed. - The ``pyramid_routesalchemy`` paster template's unit tests failed (``AssertionError: 'SomeProject' != 'someproject'``). This is fixed. - Make default renderer work (renderer factory registered with no name, which is active for every view unless the view names a specific renderer). - The Mako renderer did not properly turn the ``mako.imports``, ``mako.default_filters``, and ``mako.imports`` settings into lists. - The Mako renderer did not properly convert the ``mako.error_handler`` setting from a dotted name to a callable. Documentation ------------- - Merged many wording, readability, and correctness changes to narrative documentation chapters from https://github.com/caseman/pyramid (up to and including "Models" narrative chapter). - "Sample Applications" section of docs changed to note existence of Cluegun, Shootout and Virginia sample applications, ported from their repoze.bfg origin packages. - SQLAlchemy+URLDispatch tutorial updated to integrate changes to ``pyramid_routesalchemy`` template. - Add ``pyramid.interfaces.ITemplateRenderer`` interface to Interfaces API chapter (has ``implementation()`` method, required to be used when getting at Chameleon macros). - Add a "Modifying Package Structure" section to the project narrative documentation chapter (explain turning a module into a package). - Documentation was added for the new ``handler`` ZCML directive in the ZCML section. Deprecations ------------ - ``pyramid.configuration.Configurator`` is now deprecated. Use ``pyramid.config.Configurator``, passing its constructor ``autocommit=True`` instead. The ``pyramid.configuration.Configurator`` alias will live for a long time, as every application uses it, but its import now issues a deprecation warning. The ``pyramid.config.Configurator`` class has the same API as ``pyramid.configuration.Configurator`` class, which it means to replace, except by default it is a *non-autocommitting* configurator. The now-deprecated ``pyramid.configuration.Configurator`` will autocommit every time a configuration method is called. The ``pyramid.configuration`` module remains, but it is deprecated. Use ``pyramid.config`` instead. 1.0a4 (2010-11-21) ================== Features -------- - URL Dispatch now allows for replacement markers to be located anywhere in the pattern, instead of immediately following a ``/``. - URL Dispatch now uses the form ``{marker}`` to denote a replace marker in the route pattern instead of ``:marker``. The old colon-style marker syntax is still accepted for backwards compatibility. The new format allows a regular expression for that marker location to be used instead of the default ``[^/]+``, for example ``{marker:\d+}`` is now valid to require the marker to be digits. - Add a ``pyramid.url.route_path`` API, allowing folks to generate relative URLs. Calling ``route_path`` is the same as calling ``pyramid.url.route_url`` with the argument ``_app_url`` equal to the empty string. - Add a ``pyramid.request.Request.route_path`` API. This is a convenience method of the request which calls ``pyramid.url.route_url``. - Make test suite pass on Jython (requires PasteScript trunk, presumably to be 1.7.4). - Make test suite pass on PyPy (Chameleon doesn't work). - Surrounding application configuration with ``config.begin()`` and ``config.end()`` is no longer necessary. All paster templates have been changed to no longer call these functions. - Fix configurator to not convert ``ImportError`` to ``ConfigurationError`` if the import that failed was unrelated to the import requested via a dotted name when resolving dotted names (such as view dotted names). Documentation ------------- - SQLAlchemy+URLDispatch and ZODB+Traversal tutorials have been updated to not call ``config.begin()`` or ``config.end()``. Bug Fixes --------- - Add deprecation warnings to import of ``pyramid.chameleon_text`` and ``pyramid.chameleon_zpt`` of ``get_renderer``, ``get_template``, ``render_template``, and ``render_template_to_response``. - Add deprecation warning for import of ``pyramid.zcml.zcml_configure`` and ``pyramid.zcml.file_configure``. - The ``pyramid_alchemy`` paster template had a typo, preventing an import from working. - Fix apparent failures when calling ``pyramid.traversal.find_model(root, path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is (erroneously) a Unicode object. The user is meant to pass these APIs a string object, never a Unicode object. In practice, however, users indeed pass Unicode. Because the string that is passed must be ASCII encodeable, now, if they pass a Unicode object, its data is eagerly converted to an ASCII string rather than being passed along to downstream code as a convenience to the user and to prevent puzzling second-order failures from cropping up (all failures will occur within ``pyramid.traversal.traverse`` rather than later down the line as the result of calling e.g. ``traversal_path``). Backwards Incompatibilities --------------------------- - The ``pyramid.testing.zcml_configure`` API has been removed. It had been advertised as removed since repoze.bfg 1.2a1, but hadn't actually been. Deprecations ------------ - The ``pyramid.settings.get_settings`` API is now deprecated. Use ``pyramid.threadlocals.get_current_registry().settings`` instead or use the ``settings`` attribute of the registry available from the request (``request.registry.settings``). Documentation ------------- - Removed ``zodbsessions`` tutorial chapter. It's still useful, but we now have a SessionFactory abstraction which competes with it, and maintaining documentation on both ways to do it is a distraction. Internal -------- - Replace Twill with WebTest in internal integration tests (avoid deprecation warnings generated by Twill). 1.0a3 (2010-11-16) ================== Features -------- - Added Mako TemplateLookup settings for ``mako.error_handler``, ``mako.default_filters``, and ``mako.imports``. - Normalized all paster templates: each now uses the name ``main`` to represent the function that returns a WSGI application, each now uses WebError, each now has roughly the same shape of development.ini style. - Added class vars ``matchdict`` and ``matched_route`` to ``pyramid.request.Request``. Each is set to ``None``. - New API method: ``pyramid.settings.asbool``. - New API methods for ``pyramid.request.Request``: ``model_url``, ``route_url``, and ``static_url``. These are simple passthroughs for their respective functions in ``pyramid.url``. - The ``settings`` object which used to be available only when ``request.settings.get_settings`` was called is now available as ``registry.settings`` (e.g. ``request.registry.settings`` in view code). Bug Fixes --------- - The pylons_* paster templates erroneously used the ``{squiggly}`` routing syntax as the pattern supplied to ``add_route``. This style of routing is not supported. They were replaced with ``:colon`` style route patterns. - The pylons_* paster template used the same string (``your_app_secret_string``) for the ``session.secret`` setting in the generated ``development.ini``. This was a security risk if left unchanged in a project that used one of the templates to produce production applications. It now uses a randomly generated string. Documentation ------------- - ZODB+traversal wiki (``wiki``) tutorial updated due to changes to ``pyramid_zodb`` paster template. - SQLAlchemy+urldispach wiki (``wiki2``) tutorial updated due to changes to ``pyramid_routesalchemy`` paster template. - Documented the ``matchdict`` and ``matched_route`` attributes of the request object in the Request API documentation. Deprecations ------------ - Obtaining the ``settings`` object via ``registry.{get|query}Utility(ISettings)`` is now deprecated. Instead, obtain the ``settings`` object via the ``registry.settings`` attribute. A backwards compatibility shim was added to the registry object to register the settings object as an ISettings utility when ``setattr(registry, 'settings', foo)`` is called, but it will be removed in a later release. - Obtaining the ``settings`` object via ``pyramid.settings.get_settings`` is now deprecated. Obtain it as the ``settings`` attribute of the registry now (obtain the registry via ``pyramid.threadlocal.get_registry`` or as ``request.registry``). Behavior Differences -------------------- - Internal: ZCML directives no longer call get_current_registry() if there's a ``registry`` attribute on the ZCML context (kill off use of threadlocals). - Internal: Chameleon template renderers now accept two arguments: ``path`` and ``lookup``. ``Lookup`` will be an instance of a lookup class which supplies (late-bound) arguments for debug, reload, and translate. Any third-party renderers which use (the non-API) function ``pyramid.renderers.template_renderer_factory`` will need to adjust their implementations to obey the new callback argument list. This change was to kill off inappropriate use of threadlocals. 1.0a2 (2010-11-09) ================== Documentation ------------- - All references to events by interface (e.g. ``pyramid.interfaces.INewRequest``) have been changed to reference their concrete classes (e.g. ``pyramid.events.NewRequest``) in documentation about making subscriptions. - All references to Pyramid-the-application were changed from mod-`pyramid` to app-`Pyramid`. A custom role setting was added to ``docs/conf.py`` to allow for this. (internal) 1.0a1 (2010-11-05) ================== Features (delta from BFG 1.3) ------------------------------- - Mako templating renderer supports resource specification format for template lookups and within Mako templates. Absolute filenames must be used in Pyramid to avoid this lookup process. - Add ``pyramid.httpexceptions`` module, which is a facade for the ``webob.exc`` module. - Direct built-in support for the Mako templating language. - A new configurator method exists: ``add_handler``. This method adds a Pylons-style "view handler" (such a thing used to be called a "controller" in Pylons 1.0). - New argument to configurator: ``session_factory``. - New method on configurator: ``set_session_factory`` - Using ``request.session`` now returns a (dictionary-like) session object if a session factory has been configured. - The request now has a new attribute: ``tmpl_context`` for benefit of Pylons users. - The decorator previously known as ``pyramid.view.bfg_view`` is now known most formally as ``pyramid.view.view_config`` in docs and paster templates. An import of ``pyramid.view.bfg_view``, however, will continue to work "forever". - New API methods in ``pyramid.session``: ``signed_serialize`` and ``signed_deserialize``. - New interface: ``pyramid.interfaces.IRendererInfo``. An object of this type is passed to renderer factory constructors (see "Backwards Incompatibilities"). - New event type: ``pyramid.interfaces.IBeforeRender``. An object of this type is sent as an event before a renderer is invoked (but after the application-level renderer globals factory added via ``pyramid.configurator.configuration.set_renderer_globals_factory``, if any, has injected its own keys). Applications may now subscribe to the ``IBeforeRender`` event type in order to introspect the and modify the set of renderer globals before they are passed to a renderer. The event object iself has a dictionary-like interface that can be used for this purpose. For example:: from repoze.events import subscriber from pyramid.interfaces import IRendererGlobalsEvent @subscriber(IRendererGlobalsEvent) def add_global(event): event['mykey'] = 'foo' If a subscriber attempts to add a key that already exist in the renderer globals dictionary, a ``KeyError`` is raised. This limitation is due to the fact that subscribers cannot be ordered relative to each other. The set of keys added to the renderer globals dictionary by all subscribers and app-level globals factories must be unique. - New class: ``pyramid.response.Response``. This is a pure facade for ``webob.Response`` (old code need not change to use this facade, it's existence is mostly for vanity and documentation-generation purposes). - All preexisting paster templates (except ``zodb``) now use "imperative" configuration (``starter``, ``routesalchemy``, ``alchemy``). - A new paster template named ``pyramid_starter_zcml`` exists, which uses declarative configuration. Documentation (delta from BFG 1.3) ----------------------------------- - Added a ``pyramid.httpexceptions`` API documentation chapter. - Added a ``pyramid.session`` API documentation chapter. - Added a ``Session Objects`` narrative documentation chapter. - Added an API chapter for the ``pyramid.personality`` module. - Added an API chapter for the ``pyramid.response`` module. - All documentation which previously referred to ``webob.Response`` now uses ``pyramid.response.Response`` instead. - The documentation has been overhauled to use imperative configuration, moving declarative configuration (ZCML) explanations to a separate narrative chapter ``declarative.rst``. - The ZODB Wiki tutorial was updated to take into account changes to the ``pyramid_zodb`` paster template. - The SQL Wiki tutorial was updated to take into account changes to the ``pyramid_routesalchemy`` paster template. Backwards Incompatibilities (with BFG 1.3) ------------------------------------------ - There is no longer an ``IDebugLogger`` registered as a named utility with the name ``repoze.bfg.debug``. - The logger which used to have the name of ``repoze.bfg.debug`` now has the name ``pyramid.debug``. - The deprecated API ``pyramid.testing.registerViewPermission`` has been removed. - The deprecated API named ``pyramid.testing.registerRoutesMapper`` has been removed. - The deprecated API named ``pyramid.request.get_request`` was removed. - The deprecated API named ``pyramid.security.Unauthorized`` was removed. - The deprecated API named ``pyramid.view.view_execution_permitted`` was removed. - The deprecated API named ``pyramid.view.NotFound`` was removed. - The ``bfgshell`` paster command is now named ``pshell``. - The Venusian "category" for all built-in Venusian decorators (e.g. ``subscriber`` and ``view_config``/``bfg_view``) is now ``pyramid`` instead of ``bfg``. - ``pyramid.renderers.rendered_response`` function removed; use ``render_pyramid.renderers.render_to_response`` instead. - Renderer factories now accept a *renderer info object* rather than an absolute resource specification or an absolute path. The object has the following attributes: ``name`` (the ``renderer=`` value), ``package`` (the 'current package' when the renderer configuration statement was found), ``type``: the renderer type, ``registry``: the current registry, and ``settings``: the deployment settings dictionary. Third-party ``repoze.bfg`` renderer implementations that must be ported to Pyramid will need to account for this. This change was made primarily to support more flexible Mako template rendering. - The presence of the key ``repoze.bfg.message`` in the WSGI environment when an exception occurs is now deprecated. Instead, code which relies on this environ value should use the ``exception`` attribute of the request (e.g. ``request.exception[0]``) to retrieve the message. - The values ``bfg_localizer`` and ``bfg_locale_name`` kept on the request during internationalization for caching purposes were never APIs. These however have changed to ``localizer`` and ``locale_name``, respectively. - The default ``cookie_name`` value of the ``authtktauthenticationpolicy`` ZCML now defaults to ``auth_tkt`` (it used to default to ``repoze.bfg.auth_tkt``). - The default ``cookie_name`` value of the ``pyramid.authentication.AuthTktAuthenticationPolicy`` constructor now defaults to ``auth_tkt`` (it used to default to ``repoze.bfg.auth_tkt``). - The ``request_type`` argument to the ``view`` ZCML directive, the ``pyramid.configuration.Configurator.add_view`` method, or the ``pyramid.view.view_config`` decorator (nee ``bfg_view``) is no longer permitted to be one of the strings ``GET``, ``HEAD``, ``PUT``, ``POST`` or ``DELETE``, and now must always be an interface. Accepting the method-strings as ``request_type`` was a backwards compatibility strategy servicing repoze.bfg 1.0 applications. Use the ``request_method`` parameter instead to specify that a view a string request-method predicate. pyramid-1.6/LICENSE.txt0000644000076500000240000001632712234375161015412 0ustar michaelstaff00000000000000The majority of the code in Pyramid is supplied under this license: A copyright notice accompanies this license document that identifies the copyright holders. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Portions of the code in Pyramid are supplied under the ZPL (headers within individiual files indicate that these portions are licensed under the ZPL): Zope Public License (ZPL) Version 2.1 ------------------------------------- A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. The documentation portion of Pyramid (the rendered contents of the "docs" directory of a software distribution or checkout) is supplied under the Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License as described by http://creativecommons.org/licenses/by-nc-sa/3.0/us/ Internationalization Code in ``pyramid.i18n`` is supplied under the following license: Copyright (C) 2007 Edgewall Software All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Portions of the code marked as "stolen from Paste" are provided under the following license: Copyright (c) 2006-2007 Ian Bicking and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pyramid-1.6/PKG-INFO0000644000076500000240000005722412642137501014662 0ustar michaelstaff00000000000000Metadata-Version: 1.1 Name: pyramid Version: 1.6 Summary: The Pyramid Web Framework, a Pylons project Home-page: http://docs.pylonsproject.org/en/latest/docs/pyramid.html Author: Chris McDonough, Agendaless Consulting Author-email: pylons-discuss@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: Pyramid ======= .. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.6-branch :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: Master Documentation Status .. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/ :alt: Latest Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg :target: https://webchat.freenode.net/?channels=pyramid :alt: IRC Freenode 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. Pyramid is produced by the `Pylons Project `_. Support and Documentation ------------------------- See the `Pylons Project website `_ to view documentation, report bugs, and obtain support. Developing and Contributing --------------------------- See ``HACKING.txt`` and ``contributing.md`` for guidelines for running tests, adding features, coding style, and updating documentation when developing in or contributing to Pyramid. License ------- Pyramid is offered under the BSD-derived `Repoze Public License `_. Authors ------- Pyramid is made available by `Agendaless Consulting `_ and a team of contributors. 1.6 (2016-01-03) ================ Deprecations ------------ - Continue removal of ``pserve`` daemon/process management features by deprecating ``--user`` and ``--group`` options. See https://github.com/Pylons/pyramid/pull/2190 1.6b3 (2015-12-17) ================== Backward Incompatibilities -------------------------- - Remove the ``cachebust`` option from ``config.add_static_view``. See ``config.add_cache_buster`` for the new way to attach cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 - Modify the ``pyramid.interfaces.ICacheBuster`` API to be a simple callable instead of an object with ``match`` and ``pregenerate`` methods. Cache busters are now focused solely on generation. Matching has been dropped. Note this affects usage of ``pyramid.static.QueryStringCacheBuster`` and ``pyramid.static.ManifestCacheBuster``. See https://github.com/Pylons/pyramid/pull/2186 Features -------- - Add a new ``config.add_cache_buster`` API for attaching cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 Bug Fixes --------- - Ensure that ``IAssetDescriptor.abspath`` always returns an absolute path. There were cases depending on the process CWD that a relative path would be returned. See https://github.com/Pylons/pyramid/issues/2188 1.6b2 (2015-10-15) ================== Features -------- - Allow asset specifications to be supplied to ``pyramid.static.ManifestCacheBuster`` instead of requiring a filesystem path. 1.6b1 (2015-10-15) ================== Backward Incompatibilities -------------------------- - IPython and BPython support have been removed from pshell in the core. To continue using them on Pyramid 1.6+ you must install the binding packages explicitly:: $ pip install pyramid_ipython or $ pip install pyramid_bpython - Remove default cache busters introduced in 1.6a1 including ``PathSegmentCacheBuster``, ``PathSegmentMd5CacheBuster``, and ``QueryStringMd5CacheBuster``. See https://github.com/Pylons/pyramid/pull/2116 Features -------- - Additional shells for ``pshell`` can now be registered as entrypoints. See https://github.com/Pylons/pyramid/pull/1891 and https://github.com/Pylons/pyramid/pull/2012 - The variables injected into ``pshell`` are now displayed with their docstrings instead of the default ``str(obj)`` when possible. See https://github.com/Pylons/pyramid/pull/1929 - Add new ``pyramid.static.ManifestCacheBuster`` for use with external asset pipelines as well as examples of common usages in the narrative. See https://github.com/Pylons/pyramid/pull/2116 - Fix ``pserve --reload`` to not crash on syntax errors!!! See https://github.com/Pylons/pyramid/pull/2125 - Fix an issue when user passes unparsed strings to ``pyramid.session.CookieSession`` and ``pyramid.authentication.AuthTktCookieHelper`` for time related parameters ``timeout``, ``reissue_time``, ``max_age`` that expect an integer value. See https://github.com/Pylons/pyramid/pull/2050 Bug Fixes --------- - ``pyramid.httpexceptions.HTTPException`` now defaults to ``520 Unknown Error`` instead of ``None None`` to conform with changes in WebOb 1.5. See https://github.com/Pylons/pyramid/pull/1865 - ``pshell`` will now preserve the capitalization of variables in the ``[pshell]`` section of the INI file. This makes exposing classes to the shell a little more straightfoward. See https://github.com/Pylons/pyramid/pull/1883 - Fixed usage of ``pserve --monitor-restart --daemon`` which would fail in horrible ways. See https://github.com/Pylons/pyramid/pull/2118 - Explicitly prevent ``pserve --reload --daemon`` from being used. It's never been supported but would work and fail in weird ways. See https://github.com/Pylons/pyramid/pull/2119 - Fix an issue on Windows when running ``pserve --reload`` in which the process failed to fork because it could not find the pserve script to run. See https://github.com/Pylons/pyramid/pull/2138 Deprecations ------------ - Deprecate ``pserve --monitor-restart`` in favor of user's using a real process manager such as Systemd or Upstart as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/2120 1.6a2 (2015-06-30) ================== Bug Fixes --------- - Ensure that ``pyramid.httpexceptions.exception_response`` returns the appropriate "concrete" class for ``400`` and ``500`` status codes. See https://github.com/Pylons/pyramid/issues/1832 - Fix an infinite recursion bug introduced in 1.6a1 when ``pyramid.view.render_view_to_response`` was called directly or indirectly. See https://github.com/Pylons/pyramid/issues/1643 - Further fix the JSONP renderer by prefixing the returned content with a comment. This should mitigate attacks from Flash (See CVE-2014-4671). See https://github.com/Pylons/pyramid/pull/1649 - Allow periods and brackets (``[]``) in the JSONP callback. The original fix was overly-restrictive and broke Angular. See https://github.com/Pylons/pyramid/pull/1649 1.6a1 (2015-04-15) ================== Features -------- - pcreate will now ask for confirmation if invoked with an argument for a project name that already exists or is importable in the current environment. See https://github.com/Pylons/pyramid/issues/1357 and https://github.com/Pylons/pyramid/pull/1837 - Make it possible to subclass ``pyramid.request.Request`` and also use ``pyramid.request.Request.add_request.method``. See https://github.com/Pylons/pyramid/issues/1529 - The ``pyramid.config.Configurator`` has grown the ability to allow actions to call other actions during a commit-cycle. This enables much more logic to be placed into actions, such as the ability to invoke other actions or group them for improved conflict detection. We have also exposed and documented the config phases that Pyramid uses in order to further assist in building conforming addons. See https://github.com/Pylons/pyramid/pull/1513 - Add ``pyramid.request.apply_request_extensions`` function which can be used in testing to apply any request extensions configured via ``config.add_request_method``. Previously it was only possible to test the extensions by going through Pyramid's router. See https://github.com/Pylons/pyramid/pull/1581 - pcreate when run without a scaffold argument will now print information on the missing flag, as well as a list of available scaffolds. See https://github.com/Pylons/pyramid/pull/1566 and https://github.com/Pylons/pyramid/issues/1297 - Added support / testing for 'pypy3' under Tox and Travis. See https://github.com/Pylons/pyramid/pull/1469 - Automate code coverage metrics across py2 and py3 instead of just py2. See https://github.com/Pylons/pyramid/pull/1471 - Cache busting for static resources has been added and is available via a new argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. Core APIs are shipped for both cache busting via query strings and path segments and may be extended to fit into custom asset pipelines. See https://github.com/Pylons/pyramid/pull/1380 and https://github.com/Pylons/pyramid/pull/1583 - 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 - Allow an iterator to be returned from a renderer. Previously it was only possible to return bytes or unicode. See https://github.com/Pylons/pyramid/pull/1417 - ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 - Overall improvments for the ``proutes`` command. Added ``--format`` and ``--glob`` arguments to the command, introduced the ``method`` column for displaying available request methods, and improved the ``view`` output by showing the module instead of just ``__repr__``. See https://github.com/Pylons/pyramid/pull/1488 - Support keyword-only arguments and function annotations in views in Python 3. See https://github.com/Pylons/pyramid/pull/1556 - ``request.response`` will no longer be mutated when using the ``pyramid.renderers.render_to_response()`` API. It is now necessary to pass in a ``response=`` argument to ``render_to_response`` if you wish to supply the renderer with a custom response object for it to use. If you do not pass one then a response object will be created using the application's ``IResponseFactory``. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``). However, when invoking ``render_to_response`` it is not expected that the response object being returned would be the same one used later in the request. The response object returned from ``render_to_response`` is now explicitly different from ``request.response``. This does not change the API of a renderer. See https://github.com/Pylons/pyramid/pull/1563 - The ``append_slash`` argument of ```Configurator().add_notfound_view()`` will now accept anything that implements the ``IResponse`` interface and will use that as the response class instead of the default ``HTTPFound``. See https://github.com/Pylons/pyramid/pull/1610 Bug Fixes --------- - The JSONP renderer created JavaScript code in such a way that a callback variable could be used to arbitrarily inject javascript into the response object. https://github.com/Pylons/pyramid/pull/1627 - Work around an issue where ``pserve --reload`` would leave terminal echo disabled if it reloaded during a pdb session. See https://github.com/Pylons/pyramid/pull/1577, https://github.com/Pylons/pyramid/pull/1592 - ``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 - Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no valid request object. In this case it will not wrap the object in a callback and thus behave just like the ``pyramid.renderers.JSON`` renderer. See https://github.com/Pylons/pyramid/pull/1561 - Prevent "parameters to load are deprecated" ``DeprecationWarning`` from setuptools>=11.3. See https://github.com/Pylons/pyramid/pull/1541 - Avoiding sharing the ``IRenderer`` objects across threads when attached to a view using the `renderer=` argument. These renderers were instantiated at time of first render and shared between requests, causing potentially subtle effects like `pyramid.reload_templates = true` failing to work in `pyramid_mako`. See https://github.com/Pylons/pyramid/pull/1575 and https://github.com/Pylons/pyramid/issues/1268 - Avoiding timing attacks against CSRF tokens. See https://github.com/Pylons/pyramid/pull/1574 - ``request.finished_callbacks`` and ``request.response_callbacks`` now default to an iterable instead of ``None``. It may be checked for a length of 0. This was the behavior in 1.5. Deprecations ------------ - The ``pserve`` command's daemonization features have been deprecated. This includes the ``[start,stop,restart,status]`` subcommands as well as the ``--daemon``, ``--stop-server``, ``--pid-file``, and ``--status`` flags. Please use a real process manager in the future instead of relying on the ``pserve`` to daemonize itself. Many options exist including your Operating System's services such as Systemd or Upstart, as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/1641 - 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 ---- - Moved the documentation for ``accept`` on ``Configurator.add_view`` to no longer be part of the predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. See https://github.com/Pylons/pyramid/pull/1487 for this PR - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too early. - 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 - Add documentation of command line programs (``p*`` scripts). See https://github.com/Pylons/pyramid/pull/2191 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. - You can now run the scaffolding func tests via ``tox py2-scaffolds`` and ``tox py3-scaffolds``. Keywords: web wsgi pylons pyramid Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Framework :: Pyramid Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: License :: Repoze Public License pyramid-1.6/pyramid/0000755000076500000240000000000012642137501015220 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/__init__.py0000644000076500000240000000001212517346416017332 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/asset.py0000644000076500000240000000253012234375161016714 0ustar michaelstaff00000000000000import os import pkg_resources from pyramid.compat import string_types from pyramid.path import ( package_path, package_name, ) def resolve_asset_spec(spec, pname='__main__'): if pname and not isinstance(pname, string_types): pname = pname.__name__ # as package if os.path.isabs(spec): return None, spec filename = spec if ':' in spec: pname, filename = spec.split(':', 1) elif pname is None: pname, filename = None, spec return pname, filename def asset_spec_from_abspath(abspath, package): """ Try to convert an absolute path to a resource in a package to a resource specification if possible; otherwise return the absolute path. """ if getattr(package, '__name__', None) == '__main__': return abspath pp = package_path(package) + os.path.sep if abspath.startswith(pp): relpath = abspath[len(pp):] return '%s:%s' % (package_name(package), relpath.replace(os.path.sep, '/')) return abspath # bw compat only; use pyramid.path.AssetDescriptor.abspath() instead def abspath_from_asset_spec(spec, pname='__main__'): if pname is None: return spec pname, filename = resolve_asset_spec(spec, pname) if pname is None: return filename return pkg_resources.resource_filename(pname, filename) pyramid-1.6/pyramid/authentication.py0000644000076500000240000012410112642137120020605 0ustar michaelstaff00000000000000import binascii from codecs import utf_8_decode from codecs import utf_8_encode import hashlib import base64 import re import time as time_mod import warnings from zope.interface import implementer from webob.cookies import CookieProfile from pyramid.compat import ( long, text_type, binary_type, url_unquote, url_quote, bytes_, ascii_native_, native_, ) from pyramid.interfaces import ( IAuthenticationPolicy, IDebugLogger, ) from pyramid.security import ( Authenticated, Everyone, ) from pyramid.util import strings_differ VALID_TOKEN = re.compile(r"^[A-Za-z][A-Za-z0-9+_-]*$") class CallbackAuthenticationPolicy(object): """ Abstract class """ debug = False callback = None def _log(self, msg, methodname, request): logger = request.registry.queryUtility(IDebugLogger) if logger: cls = self.__class__ classname = cls.__module__ + '.' + cls.__name__ methodname = classname + '.' + methodname logger.debug(methodname + ': ' + msg) def _clean_principal(self, princid): if princid in (Authenticated, Everyone): princid = None return princid def authenticated_userid(self, request): """ Return the authenticated userid or ``None``. If no callback is registered, this will be the same as ``unauthenticated_userid``. If a ``callback`` is registered, this will return the userid if and only if the callback returns a value that is not ``None``. """ debug = self.debug userid = self.unauthenticated_userid(request) if userid is None: debug and self._log( 'call to unauthenticated_userid returned None; returning None', 'authenticated_userid', request) return None if self._clean_principal(userid) is None: debug and self._log( ('use of userid %r is disallowed by any built-in Pyramid ' 'security policy, returning None' % userid), 'authenticated_userid' , request) return None if self.callback is None: debug and self._log( 'there was no groupfinder callback; returning %r' % (userid,), 'authenticated_userid', request) return userid callback_ok = self.callback(userid, request) if callback_ok is not None: # is not None! debug and self._log( 'groupfinder callback returned %r; returning %r' % ( callback_ok, userid), 'authenticated_userid', request ) return userid debug and self._log( 'groupfinder callback returned None; returning None', 'authenticated_userid', request ) def effective_principals(self, request): """ A list of effective principals derived from request. This will return a list of principals including, at least, :data:`pyramid.security.Everyone`. If there is no authenticated userid, or the ``callback`` returns ``None``, this will be the only principal: .. code-block:: python return [Everyone] If the ``callback`` does not return ``None`` and an authenticated userid is found, then the principals will include :data:`pyramid.security.Authenticated`, the ``authenticated_userid`` and the list of principals returned by the ``callback``: .. code-block:: python extra_principals = callback(userid, request) return [Everyone, Authenticated, userid] + extra_principals """ debug = self.debug effective_principals = [Everyone] userid = self.unauthenticated_userid(request) if userid is None: debug and self._log( 'unauthenticated_userid returned %r; returning %r' % ( userid, effective_principals), 'effective_principals', request ) return effective_principals if self._clean_principal(userid) is None: debug and self._log( ('unauthenticated_userid returned disallowed %r; returning %r ' 'as if it was None' % (userid, effective_principals)), 'effective_principals', request ) return effective_principals if self.callback is None: debug and self._log( 'groupfinder callback is None, so groups is []', 'effective_principals', request) groups = [] else: groups = self.callback(userid, request) debug and self._log( 'groupfinder callback returned %r as groups' % (groups,), 'effective_principals', request) if groups is None: # is None! debug and self._log( 'returning effective principals: %r' % ( effective_principals,), 'effective_principals', request ) return effective_principals effective_principals.append(Authenticated) effective_principals.append(userid) effective_principals.extend(groups) debug and self._log( 'returning effective principals: %r' % ( effective_principals,), 'effective_principals', request ) return effective_principals @implementer(IAuthenticationPolicy) class RepozeWho1AuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which obtains data from the :mod:`repoze.who` 1.X WSGI 'API' (the ``repoze.who.identity`` key in the WSGI environment). Constructor Arguments ``identifier_name`` Default: ``auth_tkt``. The :mod:`repoze.who` plugin name that performs remember/forget. Optional. ``callback`` Default: ``None``. A callback passed the :mod:`repoze.who` identity and the :term:`request`, expected to return ``None`` if the user represented by the identity doesn't exist or a sequence of principal identifiers (possibly empty) representing groups if the user does exist. If ``callback`` is None, the userid will be assumed to exist with no group principals. Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ def __init__(self, identifier_name='auth_tkt', callback=None): self.identifier_name = identifier_name self.callback = callback def _get_identity(self, request): return request.environ.get('repoze.who.identity') def _get_identifier(self, request): plugins = request.environ.get('repoze.who.plugins') if plugins is None: return None identifier = plugins[self.identifier_name] return identifier def authenticated_userid(self, request): """ Return the authenticated userid or ``None``. If no callback is registered, this will be the same as ``unauthenticated_userid``. If a ``callback`` is registered, this will return the userid if and only if the callback returns a value that is not ``None``. """ identity = self._get_identity(request) if identity is None: self.debug and self._log( 'repoze.who identity is None, returning None', 'authenticated_userid', request) return None userid = identity['repoze.who.userid'] if userid is None: self.debug and self._log( 'repoze.who.userid is None, returning None' % userid, 'authenticated_userid', request) return None if self._clean_principal(userid) is None: self.debug and self._log( ('use of userid %r is disallowed by any built-in Pyramid ' 'security policy, returning None' % userid), 'authenticated_userid', request) return None if self.callback is None: return userid if self.callback(identity, request) is not None: # is not None! return userid def unauthenticated_userid(self, request): """ Return the ``repoze.who.userid`` key from the detected identity.""" identity = self._get_identity(request) if identity is None: return None return identity['repoze.who.userid'] def effective_principals(self, request): """ A list of effective principals derived from the identity. This will return a list of principals including, at least, :data:`pyramid.security.Everyone`. If there is no identity, or the ``callback`` returns ``None``, this will be the only principal. If the ``callback`` does not return ``None`` and an identity is found, then the principals will include :data:`pyramid.security.Authenticated`, the ``authenticated_userid`` and the list of principals returned by the ``callback``. """ effective_principals = [Everyone] identity = self._get_identity(request) if identity is None: self.debug and self._log( ('repoze.who identity was None; returning %r' % effective_principals), 'effective_principals', request ) return effective_principals if self.callback is None: groups = [] else: groups = self.callback(identity, request) if groups is None: # is None! self.debug and self._log( ('security policy groups callback returned None; returning %r' % effective_principals), 'effective_principals', request ) return effective_principals userid = identity['repoze.who.userid'] if userid is None: self.debug and self._log( ('repoze.who.userid was None; returning %r' % effective_principals), 'effective_principals', request ) return effective_principals if self._clean_principal(userid) is None: self.debug and self._log( ('unauthenticated_userid returned disallowed %r; returning %r ' 'as if it was None' % (userid, effective_principals)), 'effective_principals', request ) return effective_principals effective_principals.append(Authenticated) effective_principals.append(userid) effective_principals.extend(groups) return effective_principals 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 userid as ``userid``, and provide all keyword arguments as additional identity keys. Useful keys could be ``max_age`` or ``userdata``. """ identifier = self._get_identifier(request) if identifier is None: return [] environ = request.environ identity = kw identity['repoze.who.userid'] = userid return identifier.remember(environ, identity) def forget(self, request): """ Forget the current authenticated user. Return headers that, if included in a response, will delete the cookie responsible for tracking the current user. """ identifier = self._get_identifier(request) if identifier is None: return [] identity = self._get_identity(request) return identifier.forget(request.environ, identity) @implementer(IAuthenticationPolicy) class RemoteUserAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` :term:`authentication policy` which obtains data from the ``REMOTE_USER`` WSGI environment variable. Constructor Arguments ``environ_key`` Default: ``REMOTE_USER``. The key in the WSGI environ which provides the userid. ``callback`` Default: ``None``. A callback passed the userid and the request, expected to return None if the userid doesn't exist or a sequence of principal identifiers (possibly empty) representing groups if the user does exist. If ``callback`` is None, the userid will be assumed to exist with no group principals. ``debug`` Default: ``False``. If ``debug`` is ``True``, log messages to the Pyramid debug logger about the results of various authentication steps. The output from debugging is useful for reporting to maillist or IRC channels when asking for support. Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ def __init__(self, environ_key='REMOTE_USER', callback=None, debug=False): self.environ_key = environ_key self.callback = callback self.debug = debug def unauthenticated_userid(self, request): """ The ``REMOTE_USER`` value found within the ``environ``.""" return request.environ.get(self.environ_key) 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.""" return [] def forget(self, request): """ A no-op. The ``REMOTE_USER`` does not provide a protocol for forgetting the user. This will be application-specific and can be done somewhere else or in a subclass.""" return [] _marker = object() @implementer(IAuthenticationPolicy) class AuthTktAuthenticationPolicy(CallbackAuthenticationPolicy): """A :app:`Pyramid` :term:`authentication policy` which obtains data from a Pyramid "auth ticket" cookie. .. warning:: The default hash algorithm used in this policy is MD5 and has known hash collision vulnerabilities. The risk of an exploit is low. However, for improved authentication security, use ``hashalg='sha512'``. Constructor Arguments ``secret`` The secret (a string) used for auth_tkt cookie signing. This value should be unique across all values provided to Pyramid for various subsystem secrets (see :ref:`admonishment_against_secret_sharing`). Required. ``callback`` Default: ``None``. A callback passed the userid and the request, expected to return ``None`` if the userid doesn't exist or a sequence of principal identifiers (possibly empty) if the user does exist. If ``callback`` is ``None``, the userid will be assumed to exist with no principals. Optional. ``cookie_name`` Default: ``auth_tkt``. The cookie name used (string). Optional. ``secure`` Default: ``False``. Only send the cookie back over a secure conn. Optional. ``include_ip`` Default: ``False``. Make the requesting IP address part of the authentication data in the cookie. Optional. For IPv6 this option is not recommended. The ``mod_auth_tkt`` specification does not specify how to handle IPv6 addresses, so using this option in combination with IPv6 addresses may cause an incompatible cookie. It ties the authentication ticket to that individual's IPv6 address. ``timeout`` Default: ``None``. Maximum number of seconds which a newly issued ticket will be considered valid. After this amount of time, the ticket will expire (effectively logging the user out). If this value is ``None``, the ticket never expires. Optional. ``reissue_time`` Default: ``None``. If this parameter is set, it represents the number of seconds that must pass before an authentication token cookie is automatically reissued as the result of a request which requires authentication. The duration is measured as the number of seconds since the last auth_tkt cookie was issued and 'now'. If this value is ``0``, a new ticket cookie will be reissued on every request which requires authentication. A good rule of thumb: if you want auto-expired cookies based on inactivity: set the ``timeout`` value to 1200 (20 mins) and set the ``reissue_time`` value to perhaps a tenth of the ``timeout`` value (120 or 2 mins). It's nonsensical to set the ``timeout`` value lower than the ``reissue_time`` value, as the ticket will never be reissued if so. However, such a configuration is not explicitly prevented. Optional. ``max_age`` Default: ``None``. The max age of the auth_tkt cookie, in seconds. This differs from ``timeout`` inasmuch as ``timeout`` represents the lifetime of the ticket contained in the cookie, while this value represents the lifetime of the cookie itself. When this value is set, the cookie's ``Max-Age`` and ``Expires`` settings will be set, allowing the auth_tkt cookie to last between browser sessions. It is typically nonsensical to set this to a value that is lower than ``timeout`` or ``reissue_time``, although it is not explicitly prevented. Optional. ``path`` Default: ``/``. The path for which the auth_tkt cookie is valid. May be desirable if the application only serves part of a domain. Optional. ``http_only`` Default: ``False``. Hide cookie from JavaScript by setting the HttpOnly flag. Not honored by all browsers. Optional. ``wild_domain`` Default: ``True``. An auth_tkt cookie will be generated for the wildcard domain. If your site is hosted as ``example.com`` this will make the cookie available for sites underneath ``example.com`` such as ``www.example.com``. Optional. ``parent_domain`` Default: ``False``. An auth_tkt cookie will be generated for the parent domain of the current site. For example if your site is hosted under ``www.example.com`` a cookie will be generated for ``.example.com``. This can be useful if you have multiple sites sharing the same domain. This option supercedes the ``wild_domain`` option. Optional. This option is available as of :app:`Pyramid` 1.5. ``domain`` Default: ``None``. If provided the auth_tkt cookie will only be set for this domain. This option is not compatible with ``wild_domain`` and ``parent_domain``. Optional. This option is available as of :app:`Pyramid` 1.5. ``hashalg`` Default: ``md5`` (the literal string). Any hash algorithm supported by Python's ``hashlib.new()`` function can be used as the ``hashalg``. Cookies generated by different instances of AuthTktAuthenticationPolicy using different ``hashalg`` options are not compatible. Switching the ``hashalg`` will imply that all existing users with a valid cookie will be required to re-login. A warning is emitted at startup if an explicit ``hashalg`` is not passed. This is for backwards compatibility reasons. This option is available as of :app:`Pyramid` 1.4. Optional. .. note:: ``md5`` is the default for backwards compatibility reasons. However, if you don't specify ``md5`` as the hashalg explicitly, a warning is issued at application startup time. An explicit value of ``sha512`` is recommended for improved security, and ``sha512`` will become the default in a future Pyramid version. ``debug`` Default: ``False``. If ``debug`` is ``True``, log messages to the Pyramid debug logger about the results of various authentication steps. The output from debugging is useful for reporting to maillist or IRC channels when asking for support. Objects of this class implement the interface described by :class:`pyramid.interfaces.IAuthenticationPolicy`. """ def __init__(self, secret, callback=None, cookie_name='auth_tkt', secure=False, include_ip=False, timeout=None, reissue_time=None, max_age=None, path="/", http_only=False, wild_domain=True, debug=False, hashalg=_marker, parent_domain=False, domain=None, ): if hashalg is _marker: hashalg = 'md5' warnings.warn( 'The MD5 hash function used by default by the ' 'AuthTktAuthenticationPolicy is known to be ' 'susceptible to collision attacks. It is the current default ' 'for backwards compatibility reasons, but we recommend that ' 'you use the SHA512 algorithm instead for improved security. ' 'Pass ``hashalg=\'sha512\'`` to the ' 'AuthTktAuthenticationPolicy constructor to do so.\n\nNote ' 'that a change to the hash algorithms will invalidate existing ' 'auth tkt cookies set by your application. If backwards ' 'compatibility of existing auth tkt cookies is of greater ' 'concern than the risk posed by the potential for a hash ' 'collision, you\'ll want to continue using MD5 explicitly. ' 'To do so, pass ``hashalg=\'md5\'`` in your application to ' 'the AuthTktAuthenticationPolicy constructor. When you do so ' 'this warning will not be emitted again. The default ' 'algorithm used in this policy will change in the future, so ' 'setting an explicit hashalg will futureproof your ' 'application.', DeprecationWarning, stacklevel=2 ) self.cookie = AuthTktCookieHelper( secret, cookie_name=cookie_name, secure=secure, include_ip=include_ip, timeout=timeout, reissue_time=reissue_time, max_age=max_age, http_only=http_only, path=path, wild_domain=wild_domain, hashalg=hashalg, parent_domain=parent_domain, domain=domain, ) self.callback = callback self.debug = debug def unauthenticated_userid(self, request): """ The userid key within the auth_tkt cookie.""" result = self.cookie.identify(request) if result: return result['userid'] def remember(self, request, userid, **kw): """ Accepts the following kw args: ``max_age=, ``tokens=``. Return a list of headers which will set appropriate cookies on the response. """ return self.cookie.remember(request, userid, **kw) def forget(self, request): """ A list of headers which will delete appropriate cookies.""" return self.cookie.forget(request) def b64encode(v): return base64.b64encode(bytes_(v)).strip().replace(b'\n', b'') def b64decode(v): return base64.b64decode(bytes_(v)) # this class licensed under the MIT license (stolen from Paste) class AuthTicket(object): """ This class represents an authentication token. You must pass in the shared secret, the userid, and the IP address. Optionally you can include tokens (a list of strings, representing role names), 'user_data', which is arbitrary data available for your own use in later scripts. Lastly, you can override the cookie name and timestamp. Once you provide all the arguments, use .cookie_value() to generate the appropriate authentication ticket. Usage:: token = AuthTicket('sharedsecret', 'username', os.environ['REMOTE_ADDR'], tokens=['admin']) val = token.cookie_value() """ def __init__(self, secret, userid, ip, tokens=(), user_data='', time=None, cookie_name='auth_tkt', secure=False, hashalg='md5'): self.secret = secret self.userid = userid self.ip = ip self.tokens = ','.join(tokens) self.user_data = user_data if time is None: self.time = time_mod.time() else: self.time = time self.cookie_name = cookie_name self.secure = secure self.hashalg = hashalg def digest(self): return calculate_digest( self.ip, self.time, self.secret, self.userid, self.tokens, self.user_data, self.hashalg) def cookie_value(self): v = '%s%08x%s!' % (self.digest(), int(self.time), url_quote(self.userid)) if self.tokens: v += self.tokens + '!' v += self.user_data return v # this class licensed under the MIT license (stolen from Paste) class BadTicket(Exception): """ Exception raised when a ticket can't be parsed. If we get far enough to determine what the expected digest should have been, expected is set. This should not be shown by default, but can be useful for debugging. """ def __init__(self, msg, expected=None): self.expected = expected Exception.__init__(self, msg) # this function licensed under the MIT license (stolen from Paste) def parse_ticket(secret, ticket, ip, hashalg='md5'): """ Parse the ticket, returning (timestamp, userid, tokens, user_data). If the ticket cannot be parsed, a ``BadTicket`` exception will be raised with an explanation. """ ticket = native_(ticket).strip('"') digest_size = hashlib.new(hashalg).digest_size * 2 digest = ticket[:digest_size] try: timestamp = int(ticket[digest_size:digest_size + 8], 16) except ValueError as e: raise BadTicket('Timestamp is not a hex integer: %s' % e) try: userid, data = ticket[digest_size + 8:].split('!', 1) except ValueError: raise BadTicket('userid is not followed by !') userid = url_unquote(userid) if '!' in data: tokens, user_data = data.split('!', 1) else: # pragma: no cover (never generated) # @@: Is this the right order? tokens = '' user_data = data expected = calculate_digest(ip, timestamp, secret, userid, tokens, user_data, hashalg) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) if strings_differ(expected, digest): raise BadTicket('Digest signature is not correct', expected=(expected, digest)) tokens = tokens.split(',') return (timestamp, userid, tokens, user_data) # this function licensed under the MIT license (stolen from Paste) def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, hashalg='md5'): secret = bytes_(secret, 'utf-8') userid = bytes_(userid, 'utf-8') tokens = bytes_(tokens, 'utf-8') user_data = bytes_(user_data, 'utf-8') hash_obj = hashlib.new(hashalg) # Check to see if this is an IPv6 address if ':' in ip: ip_timestamp = ip + str(int(timestamp)) ip_timestamp = bytes_(ip_timestamp) else: # encode_ip_timestamp not required, left in for backwards compatibility ip_timestamp = encode_ip_timestamp(ip, timestamp) hash_obj.update(ip_timestamp + secret + userid + b'\0' + tokens + b'\0' + user_data) digest = hash_obj.hexdigest() hash_obj2 = hashlib.new(hashalg) hash_obj2.update(bytes_(digest) + secret) return hash_obj2.hexdigest() # this function licensed under the MIT license (stolen from Paste) def encode_ip_timestamp(ip, timestamp): ip_chars = ''.join(map(chr, map(int, ip.split('.')))) t = int(timestamp) ts = ((t & 0xff000000) >> 24, (t & 0xff0000) >> 16, (t & 0xff00) >> 8, t & 0xff) ts_chars = ''.join(map(chr, ts)) return bytes_(ip_chars + ts_chars) class AuthTktCookieHelper(object): """ A helper class for use in third-party authentication policy implementations. See :class:`pyramid.authentication.AuthTktAuthenticationPolicy` for the meanings of the constructor arguments. """ parse_ticket = staticmethod(parse_ticket) # for tests AuthTicket = AuthTicket # for tests BadTicket = BadTicket # for tests now = None # for tests userid_type_decoders = { 'int':int, 'unicode':lambda x: utf_8_decode(x)[0], # bw compat for old cookies 'b64unicode': lambda x: utf_8_decode(b64decode(x))[0], 'b64str': lambda x: b64decode(x), } userid_type_encoders = { int: ('int', str), long: ('int', str), text_type: ('b64unicode', lambda x: b64encode(utf_8_encode(x)[0])), binary_type: ('b64str', lambda x: b64encode(x)), } def __init__(self, secret, cookie_name='auth_tkt', secure=False, include_ip=False, timeout=None, reissue_time=None, max_age=None, http_only=False, path="/", wild_domain=True, hashalg='md5', parent_domain=False, domain=None): serializer = _SimpleSerializer() self.cookie_profile = CookieProfile( cookie_name = cookie_name, secure = secure, max_age = max_age, httponly = http_only, path = path, serializer=serializer ) self.secret = secret self.cookie_name = cookie_name self.secure = secure self.include_ip = include_ip self.timeout = timeout if timeout is None else int(timeout) self.reissue_time = reissue_time if reissue_time is None else int(reissue_time) self.max_age = max_age if max_age is None else int(max_age) self.wild_domain = wild_domain self.parent_domain = parent_domain self.domain = domain self.hashalg = hashalg def _get_cookies(self, request, value, max_age=None): cur_domain = request.domain domains = [] if self.domain: domains.append(self.domain) else: if self.parent_domain and cur_domain.count('.') > 1: domains.append('.' + cur_domain.split('.', 1)[1]) else: domains.append(None) domains.append(cur_domain) if self.wild_domain: domains.append('.' + cur_domain) profile = self.cookie_profile(request) kw = {} kw['domains'] = domains if max_age is not None: kw['max_age'] = max_age headers = profile.get_headers(value, **kw) return headers def identify(self, request): """ Return a dictionary with authentication information, or ``None`` if no valid auth_tkt is attached to ``request``""" environ = request.environ cookie = request.cookies.get(self.cookie_name) if cookie is None: return None if self.include_ip: remote_addr = environ['REMOTE_ADDR'] else: remote_addr = '0.0.0.0' try: timestamp, userid, tokens, user_data = self.parse_ticket( self.secret, cookie, remote_addr, self.hashalg) except self.BadTicket: return None now = self.now # service tests if now is None: now = time_mod.time() if self.timeout and ( (timestamp + self.timeout) < now ): # the auth_tkt data has expired return None userid_typename = 'userid_type:' user_data_info = user_data.split('|') for datum in filter(None, user_data_info): if datum.startswith(userid_typename): userid_type = datum[len(userid_typename):] decoder = self.userid_type_decoders.get(userid_type) if decoder: userid = decoder(userid) reissue = self.reissue_time is not None if reissue and not hasattr(request, '_authtkt_reissued'): if ( (now - timestamp) > self.reissue_time ): # 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) def reissue_authtkt(request, response): if not hasattr(request, '_authtkt_reissue_revoked'): for k, v in headers: response.headerlist.append((k, v)) request.add_response_callback(reissue_authtkt) request._authtkt_reissued = True environ['REMOTE_USER_TOKENS'] = tokens environ['REMOTE_USER_DATA'] = user_data environ['AUTH_TYPE'] = 'cookie' identity = {} identity['timestamp'] = timestamp identity['userid'] = userid identity['tokens'] = tokens identity['userdata'] = user_data return identity def forget(self, request): """ Return a set of expires Set-Cookie headers, which will destroy any existing auth_tkt cookie when attached to a response""" request._authtkt_reissue_revoked = True return self._get_cookies(request, None) def remember(self, request, userid, max_age=None, tokens=()): """ Return a set of Set-Cookie headers; when set into a response, these headers will represent a valid authentication ticket. ``max_age`` The max age of the auth_tkt cookie, in seconds. When this value is set, the cookie's ``Max-Age`` and ``Expires`` settings will be set, allowing the auth_tkt cookie to last between browser sessions. If this value is ``None``, the ``max_age`` value provided to the helper itself will be used as the ``max_age`` value. Default: ``None``. ``tokens`` A sequence of strings that will be placed into the auth_tkt tokens field. Each string in the sequence must be of the Python ``str`` type and must match the regex ``^[A-Za-z][A-Za-z0-9+_-]*$``. Tokens are available in the returned identity when an auth_tkt is found in the request and unpacked. Default: ``()``. """ max_age = self.max_age if max_age is None else int(max_age) environ = request.environ if self.include_ip: remote_addr = environ['REMOTE_ADDR'] else: remote_addr = '0.0.0.0' user_data = '' encoding_data = self.userid_type_encoders.get(type(userid)) if encoding_data: encoding, encoder = encoding_data userid = encoder(userid) user_data = 'userid_type:%s' % encoding new_tokens = [] for token in tokens: if isinstance(token, text_type): try: token = ascii_native_(token) except UnicodeEncodeError: raise ValueError("Invalid token %r" % (token,)) if not (isinstance(token, str) and VALID_TOKEN.match(token)): raise ValueError("Invalid token %r" % (token,)) new_tokens.append(token) tokens = tuple(new_tokens) if hasattr(request, '_authtkt_reissued'): request._authtkt_reissue_revoked = True ticket = self.AuthTicket( self.secret, userid, remote_addr, tokens=tokens, user_data=user_data, cookie_name=self.cookie_name, secure=self.secure, hashalg=self.hashalg ) cookie_value = ticket.cookie_value() return self._get_cookies(request, cookie_value, max_age) @implementer(IAuthenticationPolicy) class SessionAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` authentication policy which gets its data from the configured :term:`session`. For this authentication policy to work, you will have to follow the instructions in the :ref:`sessions_chapter` to configure a :term:`session factory`. Constructor Arguments ``prefix`` A prefix used when storing the authentication parameters in the session. Defaults to 'auth.'. Optional. ``callback`` Default: ``None``. A callback passed the userid and the request, expected to return ``None`` if the userid doesn't exist or a sequence of principal identifiers (possibly empty) if the user does exist. If ``callback`` is ``None``, the userid will be assumed to exist with no principals. Optional. ``debug`` Default: ``False``. If ``debug`` is ``True``, log messages to the Pyramid debug logger about the results of various authentication steps. The output from debugging is useful for reporting to maillist or IRC channels when asking for support. """ def __init__(self, prefix='auth.', callback=None, debug=False): self.callback = callback self.prefix = prefix or '' self.userid_key = prefix + 'userid' self.debug = debug 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 userid from the session.""" if self.userid_key in request.session: del request.session[self.userid_key] return [] def unauthenticated_userid(self, request): return request.session.get(self.userid_key) @implementer(IAuthenticationPolicy) class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy): """ A :app:`Pyramid` authentication policy which uses HTTP standard basic authentication protocol to authenticate users. To use this policy you will need to provide a callback which checks the supplied user credentials against your source of login data. Constructor Arguments ``check`` A callback function passed a username, password and request, in that order as positional arguments. Expected to return ``None`` if the userid doesn't exist or a sequence of principal identifiers (possibly empty) if the user does exist. ``realm`` Default: ``"Realm"``. The Basic Auth Realm string. Usually displayed to the user by the browser in the login dialog. ``debug`` Default: ``False``. If ``debug`` is ``True``, log messages to the Pyramid debug logger about the results of various authentication steps. The output from debugging is useful for reporting to maillist or IRC channels when asking for support. **Issuing a challenge** Regular browsers will not send username/password credentials unless they first receive a challenge from the server. The following recipe will register a view that will send a Basic Auth challenge to the user whenever there is an attempt to call a view which results in a Forbidden response:: from pyramid.httpexceptions import HTTPUnauthorized from pyramid.security import forget from pyramid.view import forbidden_view_config @forbidden_view_config() def basic_challenge(request): response = HTTPUnauthorized() response.headers.update(forget(request)) return response """ def __init__(self, check, realm='Realm', debug=False): self.check = check self.realm = realm self.debug = debug def unauthenticated_userid(self, request): """ The userid parsed from the ``Authorization`` request header.""" credentials = self._get_credentials(request) if credentials: return credentials[0] 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. """ return [] def forget(self, request): """ Returns challenge headers. This should be attached to a response to indicate that credentials are required.""" return [('WWW-Authenticate', 'Basic realm="%s"' % self.realm)] def callback(self, username, request): # Username arg is ignored. Unfortunately _get_credentials winds up # getting called twice when authenticated_userid is called. Avoiding # that, however, winds up duplicating logic from the superclass. credentials = self._get_credentials(request) if credentials: username, password = credentials return self.check(username, password, request) def _get_credentials(self, request): authorization = request.headers.get('Authorization') if not authorization: return None try: authmeth, auth = authorization.split(' ', 1) except ValueError: # not enough values to unpack return None if authmeth.lower() != 'basic': return None try: authbytes = b64decode(auth.strip()) except (TypeError, binascii.Error): # can't decode return None # try utf-8 first, then latin-1; see discussion in # https://github.com/Pylons/pyramid/issues/898 try: auth = authbytes.decode('utf-8') except UnicodeDecodeError: auth = authbytes.decode('latin-1') try: username, password = auth.split(':', 1) except ValueError: # not enough values to unpack return None return username, password class _SimpleSerializer(object): def loads(self, bstruct): return native_(bstruct) def dumps(self, appstruct): return bytes_(appstruct) pyramid-1.6/pyramid/authorization.py0000644000076500000240000001417512642137120020477 0ustar michaelstaff00000000000000from zope.interface import implementer from pyramid.interfaces import IAuthorizationPolicy from pyramid.location import lineage from pyramid.compat import is_nonstr_iter from pyramid.security import ( ACLAllowed, ACLDenied, Allow, Deny, Everyone, ) @implementer(IAuthorizationPolicy) class ACLAuthorizationPolicy(object): """ An :term:`authorization policy` which consults an :term:`ACL` object attached to a :term:`context` to determine authorization information about a :term:`principal` or multiple principals. If the context is part of a :term:`lineage`, the context's parents are consulted for ACL information too. The following is true about this security policy. - When checking whether the 'current' user is permitted (via the ``permits`` method), the security policy consults the ``context`` for an ACL first. If no ACL exists on the context, or one does exist but the ACL does not explicitly allow or deny access for any of the effective principals, consult the context's parent ACL, and so on, until the lineage is exhausted or we determine that the policy permits or denies. During this processing, if any :data:`pyramid.security.Deny` ACE is found matching any principal in ``principals``, stop processing by returning an :class:`pyramid.security.ACLDenied` instance (equals ``False``) immediately. If any :data:`pyramid.security.Allow` ACE is found matching any principal, stop processing by returning an :class:`pyramid.security.ACLAllowed` instance (equals ``True``) immediately. If we exhaust the context's :term:`lineage`, and no ACE has explicitly permitted or denied access, return an instance of :class:`pyramid.security.ACLDenied` (equals ``False``). - When computing principals allowed by a permission via the :func:`pyramid.security.principals_allowed_by_permission` method, we compute the set of principals that are explicitly granted the ``permission`` in the provided ``context``. We do this by walking 'up' the object graph *from the root* to the context. During this walking process, if we find an explicit :data:`pyramid.security.Allow` ACE for a principal that matches the ``permission``, the principal is included in the allow list. However, if later in the walking process that principal is mentioned in any :data:`pyramid.security.Deny` ACE for the permission, the principal is removed from the allow list. If a :data:`pyramid.security.Deny` to the principal :data:`pyramid.security.Everyone` is encountered during the walking process that matches the ``permission``, the allow list is cleared for all principals encountered in previous ACLs. The walking process ends after we've processed the any ACL directly attached to ``context``; a set of principals is returned. Objects of this class implement the :class:`pyramid.interfaces.IAuthorizationPolicy` interface. """ def permits(self, context, principals, permission): """ Return an instance of :class:`pyramid.security.ACLAllowed` instance if the policy permits access, return an instance of :class:`pyramid.security.ACLDenied` if not.""" acl = '' for location in lineage(context): try: acl = location.__acl__ except AttributeError: continue if acl and callable(acl): acl = acl() for ace in acl: ace_action, ace_principal, ace_permissions = ace if ace_principal in principals: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if permission in ace_permissions: if ace_action == Allow: return ACLAllowed(ace, acl, permission, principals, location) else: return ACLDenied(ace, acl, permission, principals, location) # default deny (if no ACL in lineage at all, or if none of the # principals were mentioned in any ACE we found) return ACLDenied( '', acl, permission, principals, context) def principals_allowed_by_permission(self, context, permission): """ Return the set of principals explicitly granted the permission named ``permission`` according to the ACL directly attached to the ``context`` as well as inherited ACLs based on the :term:`lineage`.""" allowed = set() for location in reversed(list(lineage(context))): # NB: we're walking *up* the object graph from the root try: acl = location.__acl__ except AttributeError: continue allowed_here = set() denied_here = set() if acl and callable(acl): acl = acl() for ace_action, ace_principal, ace_permissions in acl: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if (ace_action == Allow) and (permission in ace_permissions): if not ace_principal in denied_here: allowed_here.add(ace_principal) if (ace_action == Deny) and (permission in ace_permissions): denied_here.add(ace_principal) if ace_principal == Everyone: # clear the entire allowed set, as we've hit a # deny of Everyone ala (Deny, Everyone, ALL) allowed = set() break elif ace_principal in allowed: allowed.remove(ace_principal) allowed.update(allowed_here) return allowed pyramid-1.6/pyramid/compat.py0000644000076500000240000001623412524266531017070 0ustar michaelstaff00000000000000import inspect import platform import sys import types if platform.system() == 'Windows': # pragma: no cover WIN = True else: # pragma: no cover WIN = False try: # pragma: no cover import __pypy__ PYPY = True except: # pragma: no cover __pypy__ = None PYPY = False try: import cPickle as pickle except ImportError: # pragma: no cover import pickle # True if we are running on Python 3. PY3 = sys.version_info[0] == 3 if PY3: string_types = str, integer_types = int, class_types = type, text_type = str binary_type = bytes long = int else: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str long = long def text_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``binary_type``, return ``s.decode(encoding, errors)``, otherwise return ``s``""" if isinstance(s, binary_type): return s.decode(encoding, errors) return s def bytes_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``s``""" if isinstance(s, text_type): return s.encode(encoding, errors) return s if PY3: def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') return str(s, 'ascii', 'strict') else: def ascii_native_(s): if isinstance(s, text_type): s = s.encode('ascii') return str(s) ascii_native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return ``s.encode('ascii')``, otherwise return ``str(s, 'ascii', 'strict')`` Python 2: If ``s`` is an instance of ``text_type``, return ``s.encode('ascii')``, otherwise return ``str(s)`` """ if PY3: def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s``, otherwise return ``str(s, encoding, errors)``""" if isinstance(s, text_type): return s return str(s, encoding, errors) else: def native_(s, encoding='latin-1', errors='strict'): """ If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``str(s)``""" if isinstance(s, text_type): return s.encode(encoding, errors) return str(s) native_.__doc__ = """ Python 3: If ``s`` is an instance of ``text_type``, return ``s``, otherwise return ``str(s, encoding, errors)`` Python 2: If ``s`` is an instance of ``text_type``, return ``s.encode(encoding, errors)``, otherwise return ``str(s)`` """ if PY3: from urllib import parse urlparse = parse from urllib.parse import quote as url_quote from urllib.parse import quote_plus as url_quote_plus from urllib.parse import unquote as url_unquote from urllib.parse import urlencode as url_encode from urllib.request import urlopen as url_open url_unquote_text = url_unquote url_unquote_native = url_unquote else: import urlparse from urllib import quote as url_quote from urllib import quote_plus as url_quote_plus from urllib import unquote as url_unquote from urllib import urlencode as url_encode from urllib2 import urlopen as url_open def url_unquote_text(v, encoding='utf-8', errors='replace'): # pragma: no cover v = url_unquote(v) return v.decode(encoding, errors) def url_unquote_native(v, encoding='utf-8', errors='replace'): # pragma: no cover return native_(url_unquote_text(v, encoding, errors)) if PY3: # pragma: no cover import builtins exec_ = getattr(builtins, "exec") def reraise(tp, value, tb=None): if value is None: value = tp if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value del builtins else: # pragma: no cover def exec_(code, globs=None, locs=None): """Execute code in a namespace.""" if globs is None: frame = sys._getframe(1) globs = frame.f_globals if locs is None: locs = frame.f_locals del frame elif locs is None: locs = globs exec("""exec code in globs, locs""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) if PY3: # pragma: no cover def iteritems_(d): return d.items() def itervalues_(d): return d.values() def iterkeys_(d): return d.keys() else: # pragma: no cover def iteritems_(d): return d.iteritems() def itervalues_(d): return d.itervalues() def iterkeys_(d): return d.iterkeys() if PY3: def map_(*arg): return list(map(*arg)) else: map_ = map if PY3: def is_nonstr_iter(v): if isinstance(v, str): return False return hasattr(v, '__iter__') else: def is_nonstr_iter(v): return hasattr(v, '__iter__') if PY3: im_func = '__func__' im_self = '__self__' else: im_func = 'im_func' im_self = 'im_self' try: import configparser except ImportError: import ConfigParser as configparser try: from http.cookies import SimpleCookie except ImportError: from Cookie import SimpleCookie if PY3: from html import escape else: from cgi import escape if PY3: input_ = input else: input_ = raw_input if PY3: from inspect import getfullargspec as getargspec else: from inspect import getargspec if PY3: from io import StringIO as NativeIO else: from io import BytesIO as NativeIO # "json" is not an API; it's here to support older pyramid_debugtoolbar # versions which attempt to import it import json if PY3: # see PEP 3333 for why we encode WSGI PATH_INFO to latin-1 before # decoding it to utf-8 def decode_path_info(path): return path.encode('latin-1').decode('utf-8') else: def decode_path_info(path): return path.decode('utf-8') if PY3: # see PEP 3333 for why we decode the path to latin-1 from urllib.parse import unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): return unquote_to_bytes(bytestring).decode('latin-1') else: from urlparse import unquote as unquote_to_bytes def unquote_bytes_to_wsgi(bytestring): return unquote_to_bytes(bytestring) def is_bound_method(ob): return inspect.ismethod(ob) and getattr(ob, im_self, None) is not None # support annotations and keyword-only arguments in PY3 if PY3: # pragma: no cover from inspect import getfullargspec as getargspec else: from inspect import getargspec if PY3: # pragma: no cover from itertools import zip_longest else: from itertools import izip_longest as zip_longest def is_unbound_method(fn): """ This consistently verifies that the callable is bound to a class. """ is_bound = is_bound_method(fn) if not is_bound and inspect.isroutine(fn): spec = getargspec(fn) has_self = len(spec.args) > 0 and spec.args[0] == 'self' if PY3 and inspect.isfunction(fn) and has_self: # pragma: no cover return True elif inspect.ismethod(fn): return True return False pyramid-1.6/pyramid/config/0000755000076500000240000000000012642137501016465 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/config/__init__.py0000644000076500000240000015021012642137120020572 0ustar michaelstaff00000000000000import inspect import itertools import logging import operator import os import sys import threading import venusian from webob.exc import WSGIHTTPException as WebobWSGIHTTPException from pyramid.interfaces import ( IDebugLogger, IExceptionResponse, IPredicateList, PHASE0_CONFIG, PHASE1_CONFIG, PHASE2_CONFIG, PHASE3_CONFIG, ) from pyramid.asset import resolve_asset_spec from pyramid.authorization import ACLAuthorizationPolicy from pyramid.compat import ( text_, reraise, string_types, zip_longest, ) from pyramid.events import ApplicationCreated from pyramid.exceptions import ( ConfigurationConflictError, ConfigurationError, ConfigurationExecutionError, ) from pyramid.httpexceptions import default_exceptionresponse_view from pyramid.path import ( caller_package, package_of, ) from pyramid.registry import ( Introspectable, Introspector, Registry, undefer, ) from pyramid.router import Router from pyramid.settings import aslist from pyramid.threadlocal import manager from pyramid.util import ( ActionInfo, WeakOrderedSet, action_method, object_description, ) from pyramid.config.adapters import AdaptersConfiguratorMixin from pyramid.config.assets import AssetsConfiguratorMixin from pyramid.config.factories import FactoriesConfiguratorMixin from pyramid.config.i18n import I18NConfiguratorMixin from pyramid.config.rendering import RenderingConfiguratorMixin from pyramid.config.routes import RoutesConfiguratorMixin from pyramid.config.security import SecurityConfiguratorMixin from pyramid.config.settings import SettingsConfiguratorMixin from pyramid.config.testing import TestingConfiguratorMixin from pyramid.config.tweens import TweensConfiguratorMixin from pyramid.config.util import PredicateList, not_ from pyramid.config.views import ViewsConfiguratorMixin from pyramid.config.zca import ZCAConfiguratorMixin from pyramid.path import DottedNameResolver empty = text_('') _marker = object() ConfigurationError = ConfigurationError # pyflakes not_ = not_ # pyflakes, this is an API PHASE0_CONFIG = PHASE0_CONFIG # api PHASE1_CONFIG = PHASE1_CONFIG # api PHASE2_CONFIG = PHASE2_CONFIG # api PHASE3_CONFIG = PHASE3_CONFIG # api class Configurator( TestingConfiguratorMixin, TweensConfiguratorMixin, SecurityConfiguratorMixin, ViewsConfiguratorMixin, RoutesConfiguratorMixin, ZCAConfiguratorMixin, I18NConfiguratorMixin, RenderingConfiguratorMixin, AssetsConfiguratorMixin, SettingsConfiguratorMixin, FactoriesConfiguratorMixin, AdaptersConfiguratorMixin, ): """ A Configurator is used to configure a :app:`Pyramid` :term:`application registry`. If the ``registry`` argument is not ``None``, it must be an instance of the :class:`pyramid.registry.Registry` class representing the registry to configure. If ``registry`` is ``None``, the configurator will create a :class:`pyramid.registry.Registry` instance itself; it will also perform some default configuration that would not otherwise be done. After its construction, the configurator may be used to add further configuration to the registry. .. warning:: If ``registry`` is assigned the above-mentioned class instance, all other constructor arguments are ignored, with the exception of ``package``. If the ``package`` argument is passed, it must be a reference to a Python :term:`package` (e.g. ``sys.modules['thepackage']``) or a :term:`dotted Python name` to the same. This value is used as a basis to convert relative paths passed to various configuration methods, such as methods which accept a ``renderer`` argument, into absolute paths. If ``None`` 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 :attr:`pyramid.registry.Registry.settings` attribute (aka ``request.registry.settings``). If the ``root_factory`` argument is passed, it should be an object representing the default :term:`root factory` for your application or a :term:`dotted Python name` to the same. If it is ``None``, a default root factory will be used. If ``authentication_policy`` is passed, it should be an instance of an :term:`authentication policy` or a :term:`dotted Python name` to the same. If ``authorization_policy`` is passed, it should be an instance of an :term:`authorization policy` or a :term:`dotted Python name` to the same. .. note:: A ``ConfigurationError`` will be raised when an authorization policy is supplied without also supplying an authentication policy (authorization requires authentication). If ``renderers`` is ``None`` (the default), a default set of :term:`renderer` factories is used. Else, it should be a list of tuples representing a set of renderer factories which should be configured into this application, and each tuple representing a set of positional values that should be passed to :meth:`pyramid.config.Configurator.add_renderer`. If ``debug_logger`` is not passed, a default debug logger that logs to a logger will be used (the logger name will be the package name of the *caller* of this configurator). If it is passed, it should be an instance of the :class:`logging.Logger` (PEP 282) standard library class or a Python logger name. The debug logger is used by :app:`Pyramid` itself to log warnings and authorization debugging information. If ``locale_negotiator`` is passed, it should be a :term:`locale negotiator` implementation or a :term:`dotted Python name` to same. See :ref:`custom_locale_negotiator`. If ``request_factory`` is passed, it should be a :term:`request factory` implementation or a :term:`dotted Python name` to the same. 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 Configurator. An example of a permission string:``'view'``. Adding a default permission makes it unnecessary to protect each view configuration with an explicit permission, unless your application policy requires some exception for a particular view. By default, ``default_permission`` is ``None``, meaning that view 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`. If ``session_factory`` is passed, it should be an object which implements the :term:`session factory` interface. If a nondefault value is passed, the ``session_factory`` will be used to create a session object when ``request.session`` is accessed. Note that the same outcome can be achieved by calling :meth:`pyramid.config.Configurator.set_session_factory`. By default, this argument is ``None``, indicating that no session factory will be configured (and thus accessing ``request.session`` will throw an error) unless ``set_session_factory`` is called later during configuration. If ``autocommit`` is ``True``, every method called on the configurator will cause an immediate action, and no configuration conflict detection will be used. If ``autocommit`` is ``False``, most methods of the configurator will defer their action until :meth:`pyramid.config.Configurator.commit` is called. When :meth:`pyramid.config.Configurator.commit` is called, the actions implied by the called methods will be checked for configuration conflicts unless ``autocommit`` is ``True``. If a conflict is detected, a ``ConfigurationConflictError`` will be raised. Calling :meth:`pyramid.config.Configurator.make_wsgi_app` always implies a final commit. If ``default_view_mapper`` is passed, it will be used as the default :term:`view mapper` factory for view configurations that don't otherwise specify one (see :class:`pyramid.interfaces.IViewMapperFactory`). If ``default_view_mapper`` is not passed, a superdefault view mapper will be used. If ``exceptionresponse_view`` is passed, it must be a :term:`view callable` or ``None``. If it is a view callable, it will be used as an exception view callable when an :term:`exception response` is raised. If ``exceptionresponse_view`` is ``None``, no exception response view will be registered, and all raised exception responses will be bubbled up to Pyramid's caller. By default, the ``pyramid.httpexceptions.default_exceptionresponse_view`` function is used as the ``exceptionresponse_view``. If ``route_prefix`` is passed, all routes added with :meth:`pyramid.config.Configurator.add_route` will have the specified path prepended to their pattern. If ``introspection`` is passed, it must be a boolean value. If it's ``True``, introspection values during actions will be kept for use for tools like the debug toolbar. If it's ``False``, introspection values provided by registrations will be ignored. By default, it is ``True``. .. versionadded:: 1.1 The ``exceptionresponse_view`` argument. .. versionadded:: 1.2 The ``route_prefix`` argument. .. 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 _ainfo = None basepath = None includepath = () info = '' object_description = staticmethod(object_description) introspectable = Introspectable inspect = inspect def __init__(self, registry=None, package=None, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=None, debug_logger=None, locale_negotiator=None, request_factory=None, response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, autocommit=False, 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 self.introspection = introspection if registry is None: registry = Registry(self.package_name) self.registry = registry self.setup_registry( settings=settings, root_factory=root_factory, authentication_policy=authentication_policy, authorization_policy=authorization_policy, renderers=renderers, 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, exceptionresponse_view=exceptionresponse_view, ) def setup_registry(self, settings=None, root_factory=None, authentication_policy=None, authorization_policy=None, renderers=None, debug_logger=None, locale_negotiator=None, request_factory=None, response_factory=None, default_permission=None, session_factory=None, default_view_mapper=None, exceptionresponse_view=default_exceptionresponse_view, ): """ When you pass a non-``None`` ``registry`` argument to the :term:`Configurator` constructor, no initial setup is performed against the registry. This is because the registry you pass in may have already been initialized for use under :app:`Pyramid` via a different configurator. However, in some circumstances (such as when you want to use a global registry instead of a registry created as a result of the Configurator constructor), or when you want to reset the initial setup of a registry, you *do* want to explicitly initialize the registry associated with a Configurator for use under :app:`Pyramid`. Use ``setup_registry`` to do this initialization. ``setup_registry`` configures settings, a root factory, security policies, renderers, a debug logger, a locale negotiator, and various other settings using the configurator's current registry, as per the descriptions in the Configurator constructor.""" registry = self.registry self._fix_registry() self._set_settings(settings) if isinstance(debug_logger, string_types): debug_logger = logging.getLogger(debug_logger) if debug_logger is None: debug_logger = logging.getLogger(self.package_name) registry.registerUtility(debug_logger, IDebugLogger) self.add_default_response_adapters() self.add_default_renderers() self.add_default_view_predicates() self.add_default_route_predicates() if exceptionresponse_view is not None: exceptionresponse_view = self.maybe_dotted(exceptionresponse_view) self.add_view(exceptionresponse_view, context=IExceptionResponse) self.add_view(exceptionresponse_view,context=WebobWSGIHTTPException) # commit below because: # # - the default exceptionresponse_view requires the superdefault view # mapper, so we need to configure it before adding default_view_mapper # # - superdefault renderers should be overrideable without requiring # the user to commit before calling config.add_renderer self.commit() # self.commit() should not be called within this method after this # point because the following registrations should be treated as # analogues of methods called by the user after configurator # construction. Rationale: user-supplied implementations should be # preferred rather than add-on author implementations with the help of # automatic conflict resolution. if authentication_policy and not authorization_policy: authorization_policy = ACLAuthorizationPolicy() # default if authorization_policy: self.set_authorization_policy(authorization_policy) if authentication_policy: self.set_authentication_policy(authentication_policy) if default_view_mapper is not None: self.set_view_mapper(default_view_mapper) if renderers: for name, renderer in renderers: self.add_renderer(name, renderer) if root_factory is not None: self.set_root_factory(root_factory) if locale_negotiator: self.set_locale_negotiator(locale_negotiator) 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) if session_factory is not None: self.set_session_factory(session_factory) tweens = aslist(registry.settings.get('pyramid.tweens', [])) for factory in tweens: self._add_tween(factory, explicit=True) includes = aslist(registry.settings.get('pyramid.includes', [])) for inc in includes: self.include(inc) def _make_spec(self, path_or_spec): package, filename = resolve_asset_spec(path_or_spec, self.package_name) if package is None: return filename # absolute filename return '%s:%s' % (package, filename) def _split_spec(self, path_or_spec): return resolve_asset_spec(path_or_spec, self.package_name) def _fix_registry(self): """ Fix up a ZCA component registry that is not a pyramid.registry.Registry by adding analogues of ``has_listeners``, ``notify``, ``queryAdapterOrSelf``, and ``registerSelfAdapter`` through monkey-patching.""" _registry = self.registry if not hasattr(_registry, 'notify'): def notify(*events): [ _ for _ in _registry.subscribers(events, None) ] _registry.notify = notify if not hasattr(_registry, 'has_listeners'): _registry.has_listeners = True if not hasattr(_registry, 'queryAdapterOrSelf'): def queryAdapterOrSelf(object, interface, default=None): if not interface.providedBy(object): return _registry.queryAdapter(object, interface, default=default) return object _registry.queryAdapterOrSelf = queryAdapterOrSelf if not hasattr(_registry, 'registerSelfAdapter'): def registerSelfAdapter(required=None, provided=None, name=empty, info=empty, event=True): return _registry.registerAdapter(lambda x: x, required=required, provided=provided, name=name, info=info, event=event) _registry.registerSelfAdapter = registerSelfAdapter if not hasattr(_registry, '_lock'): _registry._lock = threading.Lock() if not hasattr(_registry, '_clear_view_lookup_cache'): def _clear_view_lookup_cache(): _registry._view_lookup_cache = {} _registry._clear_view_lookup_cache = _clear_view_lookup_cache # API def _get_introspector(self): introspector = getattr(self.registry, 'introspector', _marker) if introspector is _marker: introspector = Introspector() self._set_introspector(introspector) return introspector def _set_introspector(self, introspector): self.registry.introspector = introspector def _del_introspector(self): del self.registry.introspector introspector = property( _get_introspector, _set_introspector, _del_introspector ) def get_predlist(self, name): predlist = self.registry.queryUtility(IPredicateList, name=name) if predlist is None: predlist = PredicateList() self.registry.registerUtility(predlist, IPredicateList, name=name) return predlist 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, discriminator, '%s predicate named %s' % (type, name), '%s predicate' % type) intr['name'] = name intr['factory'] = factory intr['weighs_more_than'] = weighs_more_than intr['weighs_less_than'] = weighs_less_than def register(): predlist = self.get_predlist(type) predlist.add(name, factory, weighs_more_than=weighs_more_than, weighs_less_than=weighs_less_than) self.action(discriminator, register, introspectables=(intr,), order=PHASE1_CONFIG) # must be registered early @property def action_info(self): info = self.info # usually a ZCML action (ParserInfo) if self.info if not info: # Try to provide more accurate info for conflict reports if self._ainfo: info = self._ainfo[0] else: info = ActionInfo(None, 0, '', '') return info def action(self, discriminator, callable=None, args=(), kw=None, order=0, introspectables=(), **extra): """ Register an action which will be executed when :meth:`pyramid.config.Configurator.commit` is called (or executed immediately if ``autocommit`` is ``True``). .. warning:: This method is typically only used by :app:`Pyramid` framework extension authors, not by :app:`Pyramid` application developers. The ``discriminator`` uniquely identifies the action. It must be given, but it can be ``None``, to indicate that the action never conflicts. It must be a hashable value. The ``callable`` is a callable object which performs the task associated with the action when the action is executed. It is optional. ``args`` and ``kw`` are tuple and dict objects respectively, which are passed to ``callable`` when this action is executed. Both are optional. ``order`` is a grouping mechanism; an action with a lower order will be executed before an action with a higher order (has no effect when autocommit is ``True``). ``introspectables`` is a sequence of :term:`introspectable` objects (or the empty sequence if no introspectable objects are associated with this action). If this configurator's ``introspection`` attribute is ``False``, these introspectables will be ignored. ``extra`` provides a facility for inserting extra keys and values into an action dictionary. """ # catch nonhashable discriminators here; most unit tests use # autocommit=False, which won't catch unhashable discriminators assert hash(discriminator) if kw is None: kw = {} autocommit = self.autocommit action_info = self.action_info if not self.introspection: # if we're not introspecting, ignore any introspectables passed # to us introspectables = () if autocommit: # callables can depend on the side effects of resolving a # deferred discriminator undefer(discriminator) if callable is not None: callable(*args, **kw) for introspectable in introspectables: introspectable.register(self.introspector, action_info) else: action = extra action.update( dict( discriminator=discriminator, callable=callable, args=args, kw=kw, order=order, info=action_info, includepath=self.includepath, introspectables=introspectables, ) ) self.action_state.action(**action) def _get_action_state(self): registry = self.registry try: state = registry.action_state except AttributeError: state = ActionState() registry.action_state = state return state def _set_action_state(self, state): self.registry.action_state = state action_state = property(_get_action_state, _set_action_state) _ctx = action_state # bw compat def commit(self): """ Commit any pending configuration actions. If a configuration conflict is detected in the pending configuration actions, this method will raise a :exc:`ConfigurationConflictError`; within the traceback of this error will be information about the source of the conflict, usually including file names and line numbers of the cause of the configuration conflicts.""" self.action_state.execute_actions(introspector=self.introspector) self.action_state = ActionState() # old actions have been processed def include(self, callable, route_prefix=None): """Include a configuration callable, to support imperative application extensibility. .. warning:: In versions of :app:`Pyramid` prior to 1.2, this function accepted ``*callables``, but this has been changed to support only a single callable. A configuration callable should be a callable that accepts a single argument named ``config``, which will be an instance of a :term:`Configurator`. However, be warned that it will not be the same configurator instance on which you call this method. The code which runs as a result of calling the callable should invoke methods on the configurator passed to it which add configuration state. The return value of a callable will be ignored. Values allowed to be presented via the ``callable`` argument to this method: any callable Python object or any :term:`dotted Python name` which resolves to a callable Python object. It may also be a Python :term:`module`, in which case, the module will be searched for a callable named ``includeme``, which will be treated as the configuration callable. For example, if the ``includeme`` function below lives in a module named ``myapp.myconfig``: .. code-block:: python :linenos: # myapp.myconfig module def my_view(request): from pyramid.response import Response return Response('OK') def includeme(config): config.add_view(my_view) You might cause it to be included within your Pyramid application like so: .. code-block:: python :linenos: from pyramid.config import Configurator def main(global_config, **settings): config = Configurator() config.include('myapp.myconfig.includeme') Because the function is named ``includeme``, the function name can also be omitted from the dotted name reference: .. code-block:: python :linenos: from pyramid.config import Configurator def main(global_config, **settings): config = Configurator() config.include('myapp.myconfig') Included configuration statements will be overridden by local configuration statements if an included callable causes a configuration conflict by registering something with the same configuration parameters. If the ``route_prefix`` is supplied, it must be a string. Any calls to :meth:`pyramid.config.Configurator.add_route` within the included callable will have their pattern prefixed with the value of ``route_prefix``. This can be used to help mount a set of routes at a different location than the included callable's author intended, while still maintaining the same route names. For example: .. code-block:: python :linenos: from pyramid.config import Configurator def included(config): config.add_route('show_users', '/show') def main(global_config, **settings): config = Configurator() config.include(included, route_prefix='/users') In the above configuration, the ``show_users`` route will have an effective route pattern of ``/users/show``, instead of ``/show`` because the ``route_prefix`` argument will be prepended to the pattern. .. versionadded:: 1.2 The ``route_prefix`` parameter. """ # """ <-- emacs action_state = self.action_state if route_prefix is None: route_prefix = '' old_route_prefix = self.route_prefix if old_route_prefix is None: old_route_prefix = '' route_prefix = '%s/%s' % ( old_route_prefix.rstrip('/'), route_prefix.lstrip('/') ) route_prefix = route_prefix.strip('/') if not route_prefix: route_prefix = None c = self.maybe_dotted(callable) module = self.inspect.getmodule(c) if module is c: try: c = getattr(module, 'includeme') except AttributeError: raise ConfigurationError( "module %r has no attribute 'includeme'" % (module.__name__) ) spec = module.__name__ + ':' + c.__name__ sourcefile = self.inspect.getsourcefile(c) if sourcefile is None: raise ConfigurationError( 'No source file for module %r (.py file must exist, ' 'refusing to use orphan .pyc or .pyo file).' % module.__name__) if action_state.processSpec(spec): configurator = self.__class__( registry=self.registry, package=package_of(module), root_package=self.root_package, autocommit=self.autocommit, route_prefix=route_prefix, ) configurator.basepath = os.path.dirname(sourcefile) configurator.includepath = self.includepath + (spec,) c(configurator) def add_directive(self, name, directive, action_wrap=True): """ Add a directive method to the configurator. .. warning:: This method is typically only used by :app:`Pyramid` framework extension authors, not by :app:`Pyramid` application developers. Framework extenders can add directive methods to a configurator by instructing their users to call ``config.add_directive('somename', 'some.callable')``. This will make ``some.callable`` accessible as ``config.somename``. ``some.callable`` should be a function which accepts ``config`` as a first argument, and arbitrary positional and keyword arguments following. It should use config.action as necessary to perform actions. Directive methods can then be invoked like 'built-in' directives such as ``add_view``, ``add_route``, etc. The ``action_wrap`` argument should be ``True`` for directives which perform ``config.action`` with potentially conflicting discriminators. ``action_wrap`` will cause the directive to be wrapped in a decorator which provides more accurate conflict cause information. ``add_directive`` does not participate in conflict detection, and later calls to ``add_directive`` will override earlier calls. """ c = self.maybe_dotted(directive) if not hasattr(self.registry, '_directives'): self.registry._directives = {} self.registry._directives[name] = (c, action_wrap) def __getattr__(self, name): # allow directive extension names to work directives = getattr(self.registry, '_directives', {}) c = directives.get(name) if c is None: raise AttributeError(name) c, action_wrap = c if action_wrap: c = action_method(c) # Create a bound method (works on both Py2 and Py3) # http://stackoverflow.com/a/1015405/209039 m = c.__get__(self, self.__class__) return m def with_package(self, package): """ Return a new Configurator instance with the same registry as this configurator. ``package`` may be an actual Python package object or a :term:`dotted Python name` representing a package.""" configurator = self.__class__( registry=self.registry, package=package, root_package=self.root_package, autocommit=self.autocommit, route_prefix=self.route_prefix, introspection=self.introspection, ) configurator.basepath = self.basepath configurator.includepath = self.includepath configurator.info = self.info return configurator def maybe_dotted(self, dotted): """ Resolve the :term:`dotted Python name` ``dotted`` to a global Python object. If ``dotted`` is not a string, return it without attempting to do any name resolution. If ``dotted`` is a relative dotted name (e.g. ``.foo.bar``, consider it relative to the ``package`` argument supplied to this Configurator's constructor.""" return self.name_resolver.maybe_resolve(dotted) def absolute_asset_spec(self, relative_spec): """ Resolve the potentially relative :term:`asset specification` string passed as ``relative_spec`` into an absolute asset specification string and return the string. Use the ``package`` of this configurator as the package to which the asset specification will be considered relative when generating an absolute asset specification. If the provided ``relative_spec`` argument is already absolute, or if the ``relative_spec`` is not a string, it is simply returned.""" if not isinstance(relative_spec, string_types): return relative_spec return self._make_spec(relative_spec) absolute_resource_spec = absolute_asset_spec # b/w compat forever def begin(self, request=None): """ Indicate that application or test configuration has begun. This pushes a dictionary containing the :term:`application registry` implied by ``registry`` attribute of this configurator and the :term:`request` implied by the ``request`` argument onto the :term:`thread local` stack consulted by various :mod:`pyramid.threadlocal` API functions.""" self.manager.push({'registry':self.registry, 'request':request}) def end(self): """ Indicate that application or test configuration has ended. This pops the last value pushed onto the :term:`thread local` stack (usually by the ``begin`` method) and returns that value. """ return self.manager.pop() # this is *not* an action method (uses caller_package) def scan(self, package=None, categories=None, onerror=None, ignore=None, **kw): """Scan a Python package and any of its subpackages for objects marked with :term:`configuration decoration` such as :class:`pyramid.view.view_config`. Any decorated object found will influence the current configuration state. The ``package`` argument should be a Python :term:`package` or module object (or a :term:`dotted Python name` which refers to such a package or module). If ``package`` is ``None``, the package of the *caller* is used. The ``categories`` argument, if provided, should be the :term:`Venusian` 'scan categories' to use during scanning. Providing this argument is not often necessary; specifying scan categories is an extremely advanced usage. By default, ``categories`` is ``None`` which will execute *all* Venusian decorator callbacks including :app:`Pyramid`-related decorators such as :class:`pyramid.view.view_config`. See the :term:`Venusian` documentation for more information about limiting a scan by using an explicit set of categories. The ``onerror`` argument, if provided, should be a Venusian ``onerror`` callback function. The onerror function is passed to :meth:`venusian.Scanner.scan` to influence error behavior when an exception is raised during the scanning process. See the :term:`Venusian` documentation for more information about ``onerror`` callbacks. The ``ignore`` argument, if provided, should be a Venusian ``ignore`` value. Providing an ``ignore`` argument allows the scan to ignore particular modules, packages, or global objects during a scan. ``ignore`` can be a string or a callable, or a list containing strings or callables. The simplest usage of ``ignore`` is to provide a module or package by providing a full path to its dotted name. For example: ``config.scan(ignore='my.module.subpackage')`` would ignore the ``my.module.subpackage`` package during a scan, which would prevent the subpackage and any of its submodules from being imported and scanned. See the :term:`Venusian` documentation for more information about the ``ignore`` argument. To perform a ``scan``, Pyramid creates a Venusian ``Scanner`` object. The ``kw`` argument represents a set of keyword arguments to pass to the Venusian ``Scanner`` object's constructor. See the :term:`venusian` documentation (its ``Scanner`` class) for more information about the constructor. By default, the only keyword arguments passed to the Scanner constructor are ``{'config':self}`` where ``self`` is this configurator object. This services the requirement of all built-in Pyramid decorators, but extension systems may require additional arguments. Providing this argument is not often necessary; it's an advanced usage. .. versionadded:: 1.1 The ``**kw`` argument. .. versionadded:: 1.3 The ``ignore`` argument. """ package = self.maybe_dotted(package) if package is None: # pragma: no cover package = caller_package() ctorkw = {'config':self} ctorkw.update(kw) scanner = self.venusian.Scanner(**ctorkw) scanner.scan(package, categories=categories, onerror=onerror, ignore=ignore) def make_wsgi_app(self): """ Commits any pending configuration statements, sends a :class:`pyramid.events.ApplicationCreated` event to all listeners, adds this configuration's registry to :attr:`pyramid.config.global_registries`, and returns a :app:`Pyramid` WSGI application representing the committed configuration state.""" self.commit() app = Router(self.registry) # Allow tools like "pshell development.ini" to find the 'last' # registry configured. global_registries.add(self.registry) # Push the registry onto the stack in case any code that depends on # the registry threadlocal APIs used in listeners subscribed to the # IApplicationCreated event. self.manager.push({'registry':self.registry, 'request':None}) try: self.registry.notify(ApplicationCreated(app)) finally: self.manager.pop() return app # this class is licensed under the ZPL (stolen from Zope) class ActionState(object): def __init__(self): # NB "actions" is an API, dep'd upon by pyramid_zcml's load_zcml func self.actions = [] self._seen_files = set() def processSpec(self, spec): """Check whether a callable needs to be processed. The ``spec`` refers to a unique identifier for the callable. Return True if processing is needed and False otherwise. If the callable needs to be processed, it will be marked as processed, assuming that the caller will procces the callable if it needs to be processed. """ if spec in self._seen_files: return False self._seen_files.add(spec) return True def action(self, discriminator, callable=None, args=(), kw=None, order=0, includepath=(), info=None, introspectables=(), **extra): """Add an action with the given discriminator, callable and arguments """ if kw is None: kw = {} action = extra action.update( dict( discriminator=discriminator, callable=callable, args=args, kw=kw, includepath=includepath, info=info, order=order, introspectables=introspectables, ) ) self.actions.append(action) def execute_actions(self, clear=True, introspector=None): """Execute the configuration actions This calls the action callables after resolving conflicts For example: >>> output = [] >>> def f(*a, **k): ... output.append(('f', a, k)) >>> context = ActionState() >>> context.actions = [ ... (1, f, (1,)), ... (1, f, (11,), {}, ('x', )), ... (2, f, (2,)), ... ] >>> context.execute_actions() >>> output [('f', (1,), {}), ('f', (2,), {})] If the action raises an error, we convert it to a ConfigurationExecutionError. >>> output = [] >>> def bad(): ... bad.xxx >>> context.actions = [ ... (1, f, (1,)), ... (1, f, (11,), {}, ('x', )), ... (2, f, (2,)), ... (3, bad, (), {}, (), 'oops') ... ] >>> try: ... v = context.execute_actions() ... except ConfigurationExecutionError, v: ... pass >>> print(v) exceptions.AttributeError: 'function' object has no attribute 'xxx' in: oops Note that actions executed before the error still have an effect: >>> output [('f', (1,), {}), ('f', (2,), {})] The execution is re-entrant such that actions may be added by other actions with the one caveat that the order of any added actions must be equal to or larger than the current action. >>> output = [] >>> def f(*a, **k): ... output.append(('f', a, k)) ... context.actions.append((3, g, (8,), {})) >>> def g(*a, **k): ... output.append(('g', a, k)) >>> context.actions = [ ... (1, f, (1,)), ... ] >>> context.execute_actions() >>> output [('f', (1,), {}), ('g', (8,), {})] """ try: all_actions = [] executed_actions = [] pending_actions = iter([]) # resolve the new action list against what we have already # executed -- if a new action appears intertwined in the list # of already-executed actions then someone wrote a broken # re-entrant action because it scheduled the action *after* it # should have been executed (as defined by the action order) def resume(actions): for a, b in zip_longest(actions, executed_actions): if b is None and a is not None: # common case is that we are executing every action yield a elif b is not None and a != b: raise ConfigurationError( 'During execution a re-entrant action was added ' 'that modified the planned execution order in a ' 'way that is incompatible with what has already ' 'been executed.') else: # resolved action is in the same location as before, # so we are in good shape, but the action is already # executed so we skip it assert b is not None and a == b while True: # We clear the actions list prior to execution so if there # are some new actions then we add them to the mix and resolve # conflicts again. This orders the new actions as well as # ensures that the previously executed actions have no new # conflicts. if self.actions: # Only resolve the new actions against executed_actions # and pending_actions instead of everything to avoid # redundant checks. # Assume ``actions = resolveConflicts([A, B, C])`` which # after conflict checks, resulted in ``actions == [A]`` # then we know action A won out or a conflict would have # been raised. Thus, when action D is added later, we only # need to check the new action against A. # ``actions = resolveConflicts([A, D]) should drop the # number of redundant checks down from O(n^2) closer to # O(n lg n). all_actions.extend(self.actions) pending_actions = resume(resolveConflicts( executed_actions + list(pending_actions) + self.actions )) self.actions = [] action = next(pending_actions, None) if action is None: # we are done! break callable = action['callable'] args = action['args'] kw = action['kw'] info = action['info'] # we use "get" below in case an action was added via a ZCML # directive that did not know about introspectables introspectables = action.get('introspectables', ()) try: if callable is not None: callable(*args, **kw) except (KeyboardInterrupt, SystemExit): # pragma: no cover raise except: t, v, tb = sys.exc_info() try: reraise(ConfigurationExecutionError, ConfigurationExecutionError(t, v, info), tb) finally: del t, v, tb if introspector is not None: for introspectable in introspectables: introspectable.register(introspector, info) executed_actions.append(action) finally: if clear: del self.actions[:] else: self.actions = all_actions # this function is licensed under the ZPL (stolen from Zope) def resolveConflicts(actions): """Resolve conflicting actions Given an actions list, identify and try to resolve conflicting actions. Actions conflict if they have the same non-None discriminator. Conflicting actions can be resolved if the include path of one of the actions is a prefix of the includepaths of the other conflicting actions and is unequal to the include paths in the other conflicting actions. """ def orderandpos(v): n, v = v if not isinstance(v, dict): # old-style tuple action v = expand_action(*v) return (v['order'] or 0, n) sactions = sorted(enumerate(actions), key=orderandpos) def orderonly(v): n, v = v if not isinstance(v, dict): # old-style tuple action v = expand_action(*v) return v['order'] or 0 for order, actiongroup in itertools.groupby(sactions, orderonly): # "order" is an integer grouping. Actions in a lower order will be # executed before actions in a higher order. All of the actions in # one grouping will be executed (its callable, if any will be called) # before any of the actions in the next. unique = {} output = [] for i, action in actiongroup: # Within an order, actions are executed sequentially based on # original action ordering ("i"). if not isinstance(action, dict): # old-style tuple action action = expand_action(*action) # "ainfo" is a tuple of (order, i, action) where "order" is a # user-supplied grouping, "i" is an integer expressing the relative # position of this action in the action list being resolved, and # "action" is an action dictionary. The purpose of an ainfo is to # associate an "order" and an "i" with a particular action; "order" # and "i" exist for sorting purposes after conflict resolution. ainfo = (order, i, action) discriminator = undefer(action['discriminator']) action['discriminator'] = discriminator if discriminator is None: # The discriminator is None, so this action can never conflict. # We can add it directly to the result. output.append(ainfo) continue L = unique.setdefault(discriminator, []) L.append(ainfo) # Check for conflicts conflicts = {} for discriminator, ainfos in unique.items(): # We use (includepath, order, i) as a sort key because we need to # sort the actions by the paths so that the shortest path with a # given prefix comes first. The "first" action is the one with the # shortest include path. We break sorting ties using "order", then # "i". def bypath(ainfo): path, order, i = ainfo[2]['includepath'], ainfo[0], ainfo[1] return path, order, i ainfos.sort(key=bypath) ainfo, rest = ainfos[0], ainfos[1:] output.append(ainfo) _, _, action = ainfo basepath, baseinfo, discriminator = ( action['includepath'], action['info'], action['discriminator'], ) for _, _, action in rest: includepath = action['includepath'] # Test whether path is a prefix of opath if (includepath[:len(basepath)] != basepath # not a prefix or includepath == basepath): L = conflicts.setdefault(discriminator, [baseinfo]) L.append(action['info']) if conflicts: raise ConfigurationConflictError(conflicts) # sort conflict-resolved actions by (order, i) and yield them one by one for a in [x[2] for x in sorted(output, key=operator.itemgetter(0, 1))]: yield a def expand_action(discriminator, callable=None, args=(), kw=None, includepath=(), info=None, order=0, introspectables=()): if kw is None: kw = {} return dict( discriminator=discriminator, callable=callable, args=args, kw=kw, includepath=includepath, info=info, order=order, introspectables=introspectables, ) global_registries = WeakOrderedSet() pyramid-1.6/pyramid/config/adapters.py0000644000076500000240000003276112642137120020650 0ustar michaelstaff00000000000000from webob import Response as WebobResponse from functools import update_wrapper from zope.interface import Interface from pyramid.interfaces import ( IResponse, ITraverser, IResourceURL, ) from pyramid.config.util import ( action_method, takes_one_arg, ) class AdaptersConfiguratorMixin(object): @action_method def add_subscriber(self, subscriber, iface=None, **predicates): """Add an event :term:`subscriber` for the event stream implied by the supplied ``iface`` interface. The ``subscriber`` argument represents a callable object (or a :term:`dotted Python name` which identifies a callable); it will be called with a single object ``event`` whenever :app:`Pyramid` emits an :term:`event` associated with the ``iface``, which may be an :term:`interface` or a class or a :term:`dotted Python name` to a global object representing an interface or a class. Using the default ``iface`` value, ``None`` will cause the subscriber to be registered for all event types. See :ref:`events_chapter` for more information about events and subscribers. Any number of predicate keyword arguments may be passed in ``**predicates``. Each predicate named will narrow the set of circumstances in which the subscriber will be invoked. Each named predicate must have been registered via :meth:`pyramid.config.Configurator.add_subscriber_predicate` before it can be used. See :ref:`subscriber_predicates` for more information. .. versionadded:: 1.4 The ``**predicates`` argument. """ dotted = self.maybe_dotted subscriber, iface = dotted(subscriber), dotted(iface) if iface is None: iface = (Interface,) if not isinstance(iface, (tuple, list)): iface = (iface,) def register(): predlist = self.get_predlist('subscriber') order, preds, phash = predlist.make(self, **predicates) derived_predicates = [ self._derive_predicate(p) for p in preds ] derived_subscriber = self._derive_subscriber( subscriber, derived_predicates, ) intr.update( {'phash':phash, 'order':order, 'predicates':preds, 'derived_predicates':derived_predicates, 'derived_subscriber':derived_subscriber, } ) self.registry.registerHandler(derived_subscriber, iface) intr = self.introspectable( 'subscribers', id(subscriber), self.object_description(subscriber), 'subscriber' ) intr['subscriber'] = subscriber intr['interfaces'] = iface self.action(None, register, introspectables=(intr,)) return subscriber def _derive_predicate(self, predicate): derived_predicate = predicate if eventonly(predicate): def derived_predicate(*arg): return predicate(arg[0]) # seems pointless to try to fix __doc__, __module__, etc as # predicate will invariably be an instance return derived_predicate def _derive_subscriber(self, subscriber, predicates): derived_subscriber = subscriber if eventonly(subscriber): def derived_subscriber(*arg): return subscriber(arg[0]) if hasattr(subscriber, '__name__'): update_wrapper(derived_subscriber, subscriber) if not predicates: return derived_subscriber def subscriber_wrapper(*arg): # We need to accept *arg and pass it along because zope subscribers # are designed awkwardly. Notification via # registry.adapter.subscribers will always call an associated # subscriber with all of the objects involved in the subscription # lookup, despite the fact that the event sender always has the # option to attach those objects to the event object itself, and # almost always does. # # The "eventonly" jazz sprinkled in this function and related # functions allows users to define subscribers and predicates which # accept only an event argument without needing to accept the rest # of the adaptation arguments. Had I been smart enough early on to # use .subscriptions to find the subscriber functions in order to # call them manually with a single "event" argument instead of # relying on .subscribers to both find and call them implicitly # with all args, the eventonly hack would not have been required. # At this point, though, using .subscriptions and manual execution # is not possible without badly breaking backwards compatibility. if all((predicate(*arg) for predicate in predicates)): return derived_subscriber(*arg) if hasattr(subscriber, '__name__'): update_wrapper(subscriber_wrapper, subscriber) return subscriber_wrapper @action_method def add_subscriber_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): """ .. versionadded:: 1.4 Adds a subscriber predicate factory. The associated subscriber predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_subscriber` in the ``**predicates`` anonymous keyword argument dictionary. ``name`` should be the name of the predicate. It must be a valid Python identifier (it will be used as a ``**predicates`` keyword argument to :meth:`~pyramid.config.Configurator.add_subscriber`). ``factory`` should be a :term:`predicate factory` or :term:`dotted Python name` which refers to a predicate factory. See :ref:`subscriber_predicates` for more information. """ self._add_predicate( 'subscriber', name, factory, weighs_more_than=weighs_more_than, weighs_less_than=weighs_less_than ) @action_method def add_response_adapter(self, adapter, type_or_iface): """ When an object of type (or interface) ``type_or_iface`` is returned from a view callable, Pyramid will use the adapter ``adapter`` to convert it into an object which implements the :class:`pyramid.interfaces.IResponse` interface. If ``adapter`` is None, an object returned of type (or interface) ``type_or_iface`` will itself be used as a response object. ``adapter`` and ``type_or_interface`` may be Python objects or strings representing dotted names to importable Python global objects. See :ref:`using_iresponse` for more information.""" adapter = self.maybe_dotted(adapter) type_or_iface = self.maybe_dotted(type_or_iface) def register(): reg = self.registry if adapter is None: reg.registerSelfAdapter((type_or_iface,), IResponse) else: reg.registerAdapter(adapter, (type_or_iface,), IResponse) discriminator = (IResponse, type_or_iface) intr = self.introspectable( 'response adapters', discriminator, self.object_description(adapter), 'response adapter') intr['adapter'] = adapter intr['type'] = type_or_iface self.action(discriminator, register, introspectables=(intr,)) def add_default_response_adapters(self): # cope with WebOb response objects that aren't decorated with IResponse self.add_response_adapter(None, WebobResponse) @action_method def add_traverser(self, adapter, iface=None): """ The superdefault :term:`traversal` algorithm that :app:`Pyramid` uses is explained in :ref:`traversal_algorithm`. Though it is rarely necessary, this default algorithm can be swapped out selectively for a different traversal pattern via configuration. The section entitled :ref:`changing_the_traverser` details how to create a traverser class. For example, to override the superdefault traverser used by Pyramid, you might do something like this: .. code-block:: python from myapp.traversal import MyCustomTraverser config.add_traverser(MyCustomTraverser) This would cause the Pyramid superdefault traverser to never be used; instead all traversal would be done using your ``MyCustomTraverser`` class, no matter which object was returned by the :term:`root factory` of this application. Note that we passed no arguments to the ``iface`` keyword parameter. The default value of ``iface``, ``None`` represents that the registered traverser should be used when no other more specific traverser is available for the object returned by the root factory. However, more than one traversal algorithm can be active at the same time. The traverser used can depend on the result of the :term:`root factory`. For instance, if your root factory returns more than one type of object conditionally, you could claim that an alternate traverser adapter should be used against one particular class or interface returned by that root factory. When the root factory returned an object that implemented that class or interface, a custom traverser would be used. Otherwise, the default traverser would be used. The ``iface`` argument represents the class of the object that the root factory might return or an :term:`interface` that the object might implement. To use a particular traverser only when the root factory returns a particular class: .. code-block:: python config.add_traverser(MyCustomTraverser, MyRootClass) When more than one traverser is active, the "most specific" traverser will be used (the one that matches the class or interface of the value returned by the root factory most closely). Note that either ``adapter`` or ``iface`` can be a :term:`dotted Python name` or a Python object. See :ref:`changing_the_traverser` for more information. """ iface = self.maybe_dotted(iface) adapter= self.maybe_dotted(adapter) def register(iface=iface): if iface is None: iface = Interface self.registry.registerAdapter(adapter, (iface,), ITraverser) discriminator = ('traverser', iface) intr = self.introspectable( 'traversers', discriminator, 'traverser for %r' % iface, 'traverser', ) intr['adapter'] = adapter intr['iface'] = iface self.action(discriminator, register, introspectables=(intr,)) @action_method def add_resource_url_adapter(self, adapter, resource_iface=None): """ .. versionadded:: 1.3 When you add a traverser as described in :ref:`changing_the_traverser`, it's convenient to continue to use the :meth:`pyramid.request.Request.resource_url` API. However, since the way traversal is done may have been modified, the URLs that ``resource_url`` generates by default may be incorrect when resources are returned by a custom traverser. If you've added a traverser, you can change how :meth:`~pyramid.request.Request.resource_url` generates a URL for a specific type of resource by calling this method. The ``adapter`` argument represents a class that implements the :class:`~pyramid.interfaces.IResourceURL` interface. The class constructor should accept two arguments in its constructor (the resource and the request) and the resulting instance should provide the attributes detailed in that interface (``virtual_path`` and ``physical_path``, in particular). The ``resource_iface`` argument represents a class or interface that the resource should possess for this url adapter to be used when :meth:`pyramid.request.Request.resource_url` looks up a resource url adapter. If ``resource_iface`` is not passed, or it is passed as ``None``, the url adapter will be used for every type of resource. See :ref:`changing_resource_url` for more information. """ adapter = self.maybe_dotted(adapter) resource_iface = self.maybe_dotted(resource_iface) def register(resource_iface=resource_iface): if resource_iface is None: resource_iface = Interface self.registry.registerAdapter( adapter, (resource_iface, Interface), IResourceURL, ) discriminator = ('resource url adapter', resource_iface) intr = self.introspectable( 'resource url adapters', discriminator, 'resource url adapter for resource iface %r' % resource_iface, 'resource url adapter', ) intr['adapter'] = adapter intr['resource_iface'] = resource_iface self.action(discriminator, register, introspectables=(intr,)) def eventonly(callee): return takes_one_arg(callee, argname='event') pyramid-1.6/pyramid/config/assets.py0000644000076500000240000003321112642137120020336 0ustar michaelstaff00000000000000import os import pkg_resources import sys from zope.interface import implementer from pyramid.interfaces import IPackageOverrides from pyramid.exceptions import ConfigurationError from pyramid.threadlocal import get_current_registry from pyramid.util import action_method class OverrideProvider(pkg_resources.DefaultProvider): def __init__(self, module): pkg_resources.DefaultProvider.__init__(self, module) self.module_name = module.__name__ def _get_overrides(self): reg = get_current_registry() overrides = reg.queryUtility(IPackageOverrides, self.module_name) return overrides def get_resource_filename(self, manager, resource_name): """ Return a true filesystem path for resource_name, co-ordinating the extraction with manager, if the resource must be unpacked to the filesystem. """ overrides = self._get_overrides() if overrides is not None: filename = overrides.get_filename(resource_name) if filename is not None: return filename return pkg_resources.DefaultProvider.get_resource_filename( self, manager, resource_name) def get_resource_stream(self, manager, resource_name): """ Return a readable file-like object for resource_name.""" overrides = self._get_overrides() if overrides is not None: stream = overrides.get_stream(resource_name) if stream is not None: return stream return pkg_resources.DefaultProvider.get_resource_stream( self, manager, resource_name) def get_resource_string(self, manager, resource_name): """ Return a string containing the contents of resource_name.""" overrides = self._get_overrides() if overrides is not None: string = overrides.get_string(resource_name) if string is not None: return string return pkg_resources.DefaultProvider.get_resource_string( self, manager, resource_name) def has_resource(self, resource_name): overrides = self._get_overrides() if overrides is not None: result = overrides.has_resource(resource_name) if result is not None: return result return pkg_resources.DefaultProvider.has_resource( self, resource_name) def resource_isdir(self, resource_name): overrides = self._get_overrides() if overrides is not None: result = overrides.isdir(resource_name) if result is not None: return result return pkg_resources.DefaultProvider.resource_isdir( self, resource_name) def resource_listdir(self, resource_name): overrides = self._get_overrides() if overrides is not None: result = overrides.listdir(resource_name) if result is not None: 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 def __init__(self, package, pkg_resources=pkg_resources): loader = self._real_loader = getattr(package, '__loader__', None) if isinstance(loader, self.__class__): self._real_loader = None # We register ourselves as a __loader__ *only* to support the # setuptools _find_adapter adapter lookup; this class doesn't # actually support the PEP 302 loader "API". This is # excusable due to the following statement in the spec: # ... Loader objects are not # required to offer any useful functionality (any such functionality, # such as the zipimport get_data() method mentioned above, is # optional)... # A __loader__ attribute is basically metadata, and setuptools # uses it as such. 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, source): if not path or path.endswith('/'): override = DirectoryOverride(path, source) else: override = FileOverride(path, source) self.overrides.insert(0, override) return override def filtered_sources(self, resource_name): for override in self.overrides: o = override(resource_name) if o is not None: yield o def get_filename(self, resource_name): 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 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 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 source, path in self.filtered_sources(resource_name): if source.exists(path): return True def isdir(self, resource_name): 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 source, path in self.filtered_sources(resource_name): result = source.listdir(path) if result is not None: return result @property def real_loader(self): if self._real_loader is None: raise NotImplementedError() return self._real_loader def get_data(self, path): """ See IPEP302Loader. """ return self.real_loader.get_data(path) def is_package(self, fullname): """ See IPEP302Loader. """ return self.real_loader.is_package(fullname) def get_code(self, fullname): """ See IPEP302Loader. """ return self.real_loader.get_code(fullname) def get_source(self, fullname): """ See IPEP302Loader. """ return self.real_loader.get_source(fullname) class DirectoryOverride: def __init__(self, path, source): self.path = path self.pathlen = len(self.path) self.source = source def __call__(self, resource_name): if resource_name.startswith(self.path): new_path = resource_name[self.pathlen:] return self.source, new_path class FileOverride: def __init__(self, path, source): self.path = path self.source = source def __call__(self, resource_name): if resource_name == self.path: 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 if hasattr(package, '__name__'): self.pkg_name = package.__name__ else: self.pkg_name = 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.pkg_name, path): return pkg_resources.resource_filename(self.pkg_name, path) def get_stream(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): return pkg_resources.resource_stream(self.pkg_name, path) def get_string(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): return pkg_resources.resource_string(self.pkg_name, path) def exists(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): return True def isdir(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): return pkg_resources.resource_isdir(self.pkg_name, path) def listdir(self, resource_name): path = self.get_path(resource_name) if pkg_resources.resource_exists(self.pkg_name, path): return pkg_resources.resource_listdir(self.pkg_name, path) class FSAssetSource(object): """ An asset source relative to a path in the filesystem. """ def __init__(self, prefix): self.prefix = prefix def get_path(self, resource_name): if resource_name: path = os.path.join(self.prefix, resource_name) else: path = self.prefix return path def get_filename(self, resource_name): path = self.get_path(resource_name) 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_source, PackageOverrides=PackageOverrides): pkg_name = 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_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 an :term:`asset specification` to the asset being overridden. ``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') package = to_override path = '' if ':' in to_override: package, path = to_override.split(':', 1) # *_isdir = override is package or directory 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( 'A directory cannot be overridden with a file (put a ' 'slash at the end of override_with if necessary)') if (not overridden_isdir) and override_isdir: raise ConfigurationError( 'A file cannot be overridden with a directory (put a ' 'slash at the end of to_override if necessary)') override = _override or self._override # test jig def register(): __import__(package) from_package = sys.modules[package] override(from_package, path, override_source) intr = self.introspectable( 'asset overrides', (package, override_package, path, override_prefix), '%s -> %s' % (to_override, override_with), 'asset override', ) intr['to_override'] = to_override intr['override_with'] = override_with self.action(None, register, introspectables=(intr,)) override_resource = override_asset # bw compat pyramid-1.6/pyramid/config/factories.py0000644000076500000240000002162112524266531021025 0ustar michaelstaff00000000000000from zope.deprecation import deprecated from zope.interface import implementer from pyramid.interfaces import ( IDefaultRootFactory, IRequestFactory, IResponseFactory, IRequestExtensions, IRootFactory, ISessionFactory, ) from pyramid.traversal import DefaultRootFactory from pyramid.util import ( action_method, get_callable_name, InstancePropertyHelper, ) class FactoriesConfiguratorMixin(object): @action_method def set_root_factory(self, factory): """ Add a :term:`root factory` to the current configuration state. If the ``factory`` argument is ``None`` a default root factory will be registered. .. note:: Using the ``root_factory`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ factory = self.maybe_dotted(factory) if factory is None: factory = DefaultRootFactory def register(): self.registry.registerUtility(factory, IRootFactory) self.registry.registerUtility(factory, IDefaultRootFactory) # b/c intr = self.introspectable('root factories', None, self.object_description(factory), 'root factory') intr['factory'] = factory self.action(IRootFactory, register, introspectables=(intr,)) _set_root_factory = set_root_factory # bw compat @action_method def set_session_factory(self, factory): """ Configure the application with a :term:`session factory`. If this method is called, the ``factory`` argument must be a session factory callable or a :term:`dotted Python name` to that factory. .. note:: Using the ``session_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, ISessionFactory) intr = self.introspectable('session factory', None, self.object_description(factory), 'session factory') intr['factory'] = factory self.action(ISessionFactory, register, introspectables=(intr,)) @action_method def set_request_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` router to create all request objects. This factory object must have the same methods and attributes as the :class:`pyramid.request.Request` class (particularly ``__call__``, and ``blank``). See :meth:`pyramid.config.Configurator.add_request_method` for a less intrusive way to extend the request objects with custom methods and properties. .. note:: Using the ``request_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, IRequestFactory) intr = self.introspectable('request factory', None, self.object_description(factory), 'request factory') intr['factory'] = factory 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. The factory should conform to the :class:`pyramid.interfaces.IResponseFactory` interface. .. 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, property=False, reify=False): """ Add a property or method to the request object. When adding a method to the request, ``callable`` may be any function that receives the request object as the first parameter. If ``name`` is ``None`` then it will be computed from the name of the ``callable``. When adding a property to the request, ``callable`` can either be a callable that accepts the request as its single positional parameter, or it can be a property descriptor. If ``name`` is ``None``, the name of the property will be computed from the name of the ``callable``. If the ``callable`` is a property descriptor a ``ValueError`` will be raised if ``name`` is ``None`` or ``reify`` is ``True``. See :meth:`pyramid.request.Request.set_property` for more details on ``property`` vs ``reify``. When ``reify`` is ``True``, the value of ``property`` is assumed to also be ``True``. In all cases, ``callable`` may also be a :term:`dotted Python name` which refers to either a callable or a property descriptor. If ``callable`` is ``None`` then the method is only used to assist in conflict detection between different addons requesting the same attribute on the request object. This is the recommended method for extending the request object and should be used in favor of providing a custom request factory via :meth:`pyramid.config.Configurator.set_request_factory`. .. versionadded:: 1.4 """ if callable is not None: callable = self.maybe_dotted(callable) property = property or reify if property: name, callable = InstancePropertyHelper.make_property( callable, name=name, reify=reify) elif name is None: name = callable.__name__ else: name = get_callable_name(name) def register(): exts = self.registry.queryUtility(IRequestExtensions) if exts is None: exts = _RequestExtensions() self.registry.registerUtility(exts, IRequestExtensions) plist = exts.descriptors if property else exts.methods plist[name] = callable if callable is None: self.action(('request extensions', name), None) elif property: intr = self.introspectable('request extensions', name, self.object_description(callable), 'request property') intr['callable'] = callable intr['property'] = True intr['reify'] = reify self.action(('request extensions', name), register, introspectables=(intr,)) else: intr = self.introspectable('request extensions', name, self.object_description(callable), 'request method') intr['callable'] = callable intr['property'] = False intr['reify'] = False self.action(('request extensions', name), register, introspectables=(intr,)) @action_method def set_request_property(self, callable, name=None, reify=False): """ Add a property to the request object. .. deprecated:: 1.5 :meth:`pyramid.config.Configurator.add_request_method` should be used instead. (This method was docs-deprecated in 1.4 and issues a real deprecation warning in 1.5). .. versionadded:: 1.3 """ 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): self.descriptors = {} self.methods = {} pyramid-1.6/pyramid/config/i18n.py0000644000076500000240000001045012520062551017613 0ustar michaelstaff00000000000000import os import sys from pyramid.interfaces import ( ILocaleNegotiator, ITranslationDirectories, ) from pyramid.exceptions import ConfigurationError from pyramid.path import package_path from pyramid.util import action_method class I18NConfiguratorMixin(object): @action_method def set_locale_negotiator(self, negotiator): """ Set the :term:`locale negotiator` for this application. The :term:`locale negotiator` is a callable which accepts a :term:`request` object and which returns a :term:`locale name`. The ``negotiator`` argument should be the locale negotiator implementation or a :term:`dotted Python name` which refers to such an implementation. Later calls to this method override earlier calls; there can be only one locale negotiator active at a time within an application. See :ref:`activating_translation` for more information. .. note:: Using the ``locale_negotiator`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ def register(): self._set_locale_negotiator(negotiator) intr = self.introspectable('locale negotiator', None, self.object_description(negotiator), 'locale negotiator') intr['negotiator'] = negotiator self.action(ILocaleNegotiator, register, introspectables=(intr,)) def _set_locale_negotiator(self, negotiator): locale_negotiator = self.maybe_dotted(negotiator) self.registry.registerUtility(locale_negotiator, ILocaleNegotiator) @action_method def add_translation_dirs(self, *specs): """ Add one or more :term:`translation directory` paths to the current configuration state. The ``specs`` argument is a sequence that may contain absolute directory paths (e.g. ``/usr/share/locale``) or :term:`asset specification` names naming a directory path (e.g. ``some.package:locale``) or a combination of the two. Example: .. code-block:: python config.add_translation_dirs('/usr/share/locale', 'some.package:locale') Later calls to ``add_translation_dir`` insert directories into the beginning of the list of translation directories created by earlier calls. This means that the same translation found in a directory added later in the configuration process will be found before one added earlier in the configuration process. However, if multiple specs are provided in a single call to ``add_translation_dirs``, the directories will be inserted into the beginning of the directory list in the order they're provided in the ``*specs`` list argument (items earlier in the list trump ones later in the list). """ directories = [] introspectables = [] for spec in specs[::-1]: # reversed package_name, filename = self._split_spec(spec) if package_name is None: # absolute filename directory = filename else: __import__(package_name) package = sys.modules[package_name] directory = os.path.join(package_path(package), filename) if not os.path.isdir(os.path.realpath(directory)): raise ConfigurationError('"%s" is not a directory' % directory) intr = self.introspectable('translation directories', directory, spec, 'translation directory') intr['directory'] = directory intr['spec'] = spec introspectables.append(intr) directories.append(directory) def register(): for directory in directories: tdirs = self.registry.queryUtility(ITranslationDirectories) if tdirs is None: tdirs = [] self.registry.registerUtility(tdirs, ITranslationDirectories) tdirs.insert(0, directory) self.action(None, register, introspectables=introspectables) pyramid-1.6/pyramid/config/predicates.py0000644000076500000240000002015412520062551021161 0ustar michaelstaff00000000000000import re from pyramid.exceptions import ConfigurationError from pyramid.compat import is_nonstr_iter from pyramid.traversal import ( find_interface, traversal_path, resource_path_tuple ) from pyramid.urldispatch import _compile_route from pyramid.util import object_description from pyramid.session import check_csrf_token from .util import as_sorted_tuple _marker = object() class XHRPredicate(object): def __init__(self, val, config): self.val = bool(val) def text(self): return 'xhr = %s' % self.val phash = text def __call__(self, context, request): return bool(request.is_xhr) is self.val class RequestMethodPredicate(object): def __init__(self, val, config): request_method = as_sorted_tuple(val) if 'GET' in request_method and 'HEAD' not in request_method: # GET implies HEAD too request_method = as_sorted_tuple(request_method + ('HEAD',)) self.val = request_method def text(self): return 'request_method = %s' % (','.join(self.val)) phash = text def __call__(self, context, request): return request.method in self.val class PathInfoPredicate(object): def __init__(self, val, config): self.orig = val try: val = re.compile(val) except re.error as why: raise ConfigurationError(why.args[0]) self.val = val def text(self): return 'path_info = %s' % (self.orig,) phash = text def __call__(self, context, request): return self.val.match(request.upath_info) is not None class RequestParamPredicate(object): def __init__(self, val, config): val = as_sorted_tuple(val) reqs = [] for p in val: k = p v = None if '=' in p: k, v = p.split('=', 1) k, v = k.strip(), v.strip() reqs.append((k, v)) self.val = val self.reqs = reqs def text(self): return 'request_param %s' % ','.join( ['%s=%s' % (x,y) if y else x for x, y in self.reqs] ) phash = text def __call__(self, context, request): for k, v in self.reqs: actual = request.params.get(k) if actual is None: return False if v is not None and actual != v: return False return True class HeaderPredicate(object): def __init__(self, val, config): name = val v = None if ':' in name: name, val_str = name.split(':', 1) try: v = re.compile(val_str) except re.error as why: raise ConfigurationError(why.args[0]) if v is None: self._text = 'header %s' % (name,) else: self._text = 'header %s=%s' % (name, val_str) self.name = name self.val = v def text(self): return self._text phash = text def __call__(self, context, request): if self.val is None: return self.name in request.headers val = request.headers.get(self.name) if val is None: return False return self.val.match(val) is not None class AcceptPredicate(object): def __init__(self, val, config): self.val = val def text(self): return 'accept = %s' % (self.val,) phash = text def __call__(self, context, request): return self.val in request.accept class ContainmentPredicate(object): def __init__(self, val, config): self.val = config.maybe_dotted(val) def text(self): return 'containment = %s' % (self.val,) phash = text def __call__(self, context, request): ctx = getattr(request, 'context', context) return find_interface(ctx, self.val) is not None class RequestTypePredicate(object): def __init__(self, val, config): self.val = val def text(self): return 'request_type = %s' % (self.val,) phash = text def __call__(self, context, request): return self.val.providedBy(request) class MatchParamPredicate(object): def __init__(self, val, config): val = as_sorted_tuple(val) self.val = val reqs = [ p.split('=', 1) for p in val ] self.reqs = [ (x.strip(), y.strip()) for x, y in reqs ] def text(self): return 'match_param %s' % ','.join( ['%s=%s' % (x,y) for x, y in self.reqs] ) phash = text def __call__(self, context, request): if not request.matchdict: # might be None return False for k, v in self.reqs: if request.matchdict.get(k) != v: return False return True class CustomPredicate(object): def __init__(self, func, config): self.func = func def text(self): return getattr( self.func, '__text__', 'custom predicate: %s' % object_description(self.func) ) def phash(self): # using hash() here rather than id() is intentional: we # want to allow custom predicates that are part of # frameworks to be able to define custom __hash__ # functions for custom predicates, so that the hash output # of predicate instances which are "logically the same" # may compare equal. return 'custom:%r' % hash(self.func) def __call__(self, context, request): return self.func(context, request) class TraversePredicate(object): # Can only be used as a *route* "predicate"; it adds 'traverse' to the # matchdict if it's specified in the routing args. This causes the # ResourceTreeTraverser to use the resolved traverse pattern as the # traversal path. def __init__(self, val, config): _, self.tgenerate = _compile_route(val) self.val = val def text(self): return 'traverse matchdict pseudo-predicate' def phash(self): # This isn't actually a predicate, it's just a infodict modifier that # injects ``traverse`` into the matchdict. As a result, we don't # need to update the hash. return '' def __call__(self, context, request): if 'traverse' in context: return True m = context['match'] tvalue = self.tgenerate(m) # tvalue will be urlquoted string m['traverse'] = traversal_path(tvalue) # This isn't actually a predicate, it's just a infodict modifier that # injects ``traverse`` into the matchdict. As a result, we just # return True. return True class CheckCSRFTokenPredicate(object): check_csrf_token = staticmethod(check_csrf_token) # testing def __init__(self, val, config): self.val = val def text(self): return 'check_csrf = %s' % (self.val,) phash = text def __call__(self, context, request): val = self.val if val: if val is True: val = 'csrf_token' return self.check_csrf_token(request, val, raises=False) return True class PhysicalPathPredicate(object): def __init__(self, val, config): if is_nonstr_iter(val): self.val = tuple(val) else: val = tuple(filter(None, val.split('/'))) self.val = ('',) + val def text(self): return 'physical_path = %s' % (self.val,) phash = text def __call__(self, context, request): if getattr(context, '__name__', _marker) is not _marker: return resource_path_tuple(context) == self.val return False class EffectivePrincipalsPredicate(object): def __init__(self, val, config): if is_nonstr_iter(val): self.val = set(val) else: self.val = set((val,)) def text(self): return 'effective_principals = %s' % sorted(list(self.val)) phash = text def __call__(self, context, request): req_principals = request.effective_principals if is_nonstr_iter(req_principals): rpset = set(req_principals) if self.val.issubset(rpset): return True return False pyramid-1.6/pyramid/config/rendering.py0000644000076500000240000000360612520062551021016 0ustar michaelstaff00000000000000from pyramid.interfaces import ( IRendererFactory, PHASE1_CONFIG, ) from pyramid.util import action_method from pyramid import renderers DEFAULT_RENDERERS = ( ('json', renderers.json_renderer_factory), ('string', renderers.string_renderer_factory), ) class RenderingConfiguratorMixin(object): def add_default_renderers(self): for name, renderer in DEFAULT_RENDERERS: self.add_renderer(name, renderer) @action_method def add_renderer(self, name, factory): """ Add a :app:`Pyramid` :term:`renderer` factory to the current configuration state. The ``name`` argument is the renderer name. Use ``None`` to represent the default renderer (a renderer which will be used for all views unless they name another renderer specifically). The ``factory`` argument is Python reference to an implementation of a :term:`renderer` factory or a :term:`dotted Python name` to same. """ factory = self.maybe_dotted(factory) # if name is None or the empty string, we're trying to register # a default renderer, but registerUtility is too dumb to accept None # as a name if not name: name = '' def register(): self.registry.registerUtility(factory, IRendererFactory, name=name) intr = self.introspectable('renderer factories', name, self.object_description(factory), 'renderer factory') intr['factory'] = factory intr['name'] = name # we need to register renderers early (in phase 1) because they are # used during view configuration (which happens in phase 3) self.action((IRendererFactory, name), register, order=PHASE1_CONFIG, introspectables=(intr,)) pyramid-1.6/pyramid/config/routes.py0000644000076500000240000004745112621241570020372 0ustar michaelstaff00000000000000import warnings from pyramid.compat import urlparse from pyramid.interfaces import ( IRequest, IRouteRequest, IRoutesMapper, PHASE2_CONFIG, ) from pyramid.exceptions import ConfigurationError from pyramid.registry import predvalseq from pyramid.request import route_request_iface from pyramid.urldispatch import RoutesMapper from pyramid.config.util import ( action_method, as_sorted_tuple, ) import pyramid.config.predicates class RoutesConfiguratorMixin(object): @action_method def add_route(self, name, pattern=None, permission=None, factory=None, for_=None, header=None, xhr=None, accept=None, path_info=None, request_method=None, request_param=None, traverse=None, custom_predicates=(), use_global_views=False, path=None, pregenerator=None, static=False, **predicates): """ Add a :term:`route configuration` to the current configuration state, as well as possibly a :term:`view configuration` to be used to specify a :term:`view callable` that will be invoked when this route matches. The arguments to this method are divided into *predicate*, *non-predicate*, and *view-related* types. :term:`Route predicate` arguments narrow the circumstances in which a route will be match a request; non-predicate arguments are informational. Non-Predicate Arguments name The name of the route, e.g. ``myroute``. This attribute is required. It must be unique among all defined routes in a given application. factory A Python object (often a function or a class) or a :term:`dotted Python name` which refers to the same object that will generate a :app:`Pyramid` root resource object when this route matches. For example, ``mypackage.resources.MyFactory``. If this argument is not specified, a default root factory will be used. See :ref:`the_resource_tree` for more information about root factories. traverse If you would like to cause the :term:`context` to be something other than the :term:`root` object when this route matches, you can spell a traversal pattern as the ``traverse`` argument. This traversal pattern will be used as the traversal path: traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided to ``add_route`` is ``articles/{article}/edit``, and the ``traverse`` argument provided to ``add_route`` is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``'1'`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``'1'`` during the traversal phase. If the ``'1'`` object exists, it will become the :term:`context` of the request. :ref:`traversal_chapter` has more information about traversal. If the traversal path contains segment marker names which are not present in the ``pattern`` argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``pattern`` argument. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in its pattern (see :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument to add_route allows you to associate route patterns with an arbitrary traversal path without using a ``*traverse`` remainder marker; instead you can use other match information. Note that the ``traverse`` argument to ``add_route`` is ignored when attached to a route that has a ``*traverse`` remainder marker in its pattern. pregenerator This option should be a callable object that implements the :class:`pyramid.interfaces.IRoutePregenerator` interface. A :term:`pregenerator` is a callable called by the :meth:`pyramid.request.Request.route_url` function to augment or replace the arguments it is passed when generating a URL for the route. This is a feature not often used directly by applications, it is meant to be hooked by frameworks that use :app:`Pyramid` as a base. use_global_views When a request matches this route, and view lookup cannot find a view which has a ``route_name`` predicate argument that matches the route, try to fall back to using a view that otherwise matches the context, request, and view name (but which does not match the route_name predicate). static If ``static`` is ``True``, this route will never match an incoming request; it will only be useful for URL generation. By default, ``static`` is ``False``. See :ref:`static_route_narr`. .. versionadded:: 1.1 accept This value represents a match query for one or more mimetypes in the ``Accept`` HTTP request header. If this value is specified, it must be in one of the following forms: a mimetype match token in the form ``text/plain``, a wildcard mimetype match token in the form ``text/*`` or a match-all wildcard mimetype match token in the form ``*/*``. If any of the forms matches the ``Accept`` header of the request, or if the ``Accept`` header isn't set at all in the request, this will match the current route. If this does not match the ``Accept`` header of the request, route matching continues. Predicate Arguments pattern The pattern of the route e.g. ``ideas/{idea}``. This argument is required. See :ref:`route_pattern_syntax` for information about the syntax of route patterns. If the pattern doesn't match the current URL, route matching continues. .. note:: For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a ``path`` keyword argument passed to this function will be used to represent the pattern value if the ``pattern`` argument is ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` wins. xhr This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`request` must possess an ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this route to match. This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. If this predicate returns ``False``, route matching continues. request_method A string representing an HTTP method name, e.g. ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT`` or a tuple of elements containing HTTP method names. If this argument is not specified, this route will match if the request has *any* request method. If this predicate returns ``False``, route matching continues. .. versionchanged:: 1.2 The ability to pass a tuple of items as ``request_method``. Previous versions allowed only a string. path_info This value represents a regular expression pattern that will be tested against the ``PATH_INFO`` WSGI environment variable. If the regex matches, this predicate will return ``True``. If this predicate returns ``False``, route matching continues. request_param This value can be any string. A view declaration with this argument ensures that the associated route will only match when the 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 supplied as the argument has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, and the value must match the right hand side of the expression (``123``) for the route to "match" the current request. If this predicate returns ``False``, route matching continues. header This argument represents an HTTP header name or a header name/value pair. If the argument contains a ``:`` (colon), it will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If the value contains a colon, the value portion should be a regular expression. If the value does not contain a colon, the entire value will be considered to be the header name (e.g. ``If-Modified-Since``). If the value evaluates to a header name only without a value, the header specified by the name must be present in the request for this predicate to be true. If the value evaluates to a header name/value pair, the header specified by the name must be present in the request *and* the regular expression specified as the value must match the header value. Whether or not the value represents a header name or a header name/value pair, the case of the header name is not significant. If this predicate returns ``False``, route matching continues. effective_principals If specified, this value should be a :term:`principal` identifier or a sequence of principal identifiers. If the :attr:`pyramid.request.Request.effective_principals` property indicates that every principal named in the argument list is present in the current request, this predicate will return True; otherwise it will return False. For example: ``effective_principals=pyramid.security.Authenticated`` or ``effective_principals=('fred', 'group:admins')``. .. versionadded:: 1.4a4 custom_predicates .. deprecated:: 1.5 This value should be a sequence of references to custom predicate callables. Use custom predicates when no set of predefined predicates does what you need. Custom predicates can be combined with predefined predicates as necessary. Each custom predicate callable should accept two arguments: ``info`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of the info and/or the request. If all custom and non-custom predicate callables return ``True`` the associated route will be considered viable for a given request. If any predicate callable returns ``False``, route matching continues. Note that the value ``info`` passed to a custom route predicate is a dictionary containing matching information; see :ref:`custom_route_predicates` for more information about ``info``. predicates Pass a key/value pair here to use a third-party predicate registered via :meth:`pyramid.config.Configurator.add_route_predicate`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. .. versionadded:: 1.4 """ if custom_predicates: warnings.warn( ('The "custom_predicates" argument to Configurator.add_route ' 'is deprecated as of Pyramid 1.5. Use ' '"config.add_route_predicate" and use the registered ' 'route predicate as a predicate argument to add_route ' 'instead. See "Adding A Third Party View, Route, or ' 'Subscriber Predicate" in the "Hooks" chapter of the ' 'documentation for more information.'), DeprecationWarning, stacklevel=3 ) # these are route predicates; if they do not match, the next route # in the routelist will be tried if request_method is not None: request_method = as_sorted_tuple(request_method) factory = self.maybe_dotted(factory) if pattern is None: pattern = path if pattern is None: raise ConfigurationError('"pattern" argument may not be None') # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse.urlparse(pattern) external_url = pattern if parsed.hostname: pattern = parsed.path original_pregenerator = pregenerator def external_url_pregenerator(request, elements, kw): if '_app_url' in kw: raise ValueError( 'You cannot generate a path to an external route ' 'pattern via request.route_path nor pass an _app_url ' 'to request.route_url when generating a URL for an ' 'external route pattern (pattern was "%s") ' % (pattern,) ) if '_scheme' in kw: scheme = kw['_scheme'] elif parsed.scheme: scheme = parsed.scheme else: scheme = request.scheme kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) if original_pregenerator: elements, kw = original_pregenerator( request, elements, kw) return elements, kw pregenerator = external_url_pregenerator static = True elif self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') mapper = self.get_routes_mapper() introspectables = [] intr = self.introspectable('routes', name, '%s (pattern: %r)' % (name, pattern), 'route') intr['name'] = name intr['pattern'] = pattern intr['factory'] = factory intr['xhr'] = xhr intr['request_methods'] = request_method intr['path_info'] = path_info intr['request_param'] = request_param intr['header'] = header intr['accept'] = accept intr['traverse'] = traverse intr['custom_predicates'] = custom_predicates intr['pregenerator'] = pregenerator intr['static'] = static intr['use_global_views'] = use_global_views if static is True: intr['external_url'] = external_url introspectables.append(intr) if factory: factory_intr = self.introspectable('root factories', name, self.object_description(factory), 'root factory') factory_intr['factory'] = factory factory_intr['route_name'] = name factory_intr.relate('routes', name) introspectables.append(factory_intr) def register_route_request_iface(): request_iface = self.registry.queryUtility(IRouteRequest, name=name) if request_iface is None: if use_global_views: bases = (IRequest,) else: bases = () request_iface = route_request_iface(name, bases) self.registry.registerUtility( request_iface, IRouteRequest, name=name) def register_connect(): pvals = predicates.copy() pvals.update( dict( xhr=xhr, request_method=request_method, path_info=path_info, request_param=request_param, header=header, accept=accept, traverse=traverse, custom=predvalseq(custom_predicates), ) ) predlist = self.get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( name, pattern, factory, predicates=preds, pregenerator=pregenerator, static=static ) intr['object'] = route return route # We have to connect routes in the order they were provided; # we can't use a phase to do that, because when the actions are # sorted, actions in the same phase lose relative ordering self.action(('route-connect', name), register_connect) # But IRouteRequest interfaces must be registered before we begin to # process view registrations (in phase 3) self.action(('route', name), register_route_request_iface, order=PHASE2_CONFIG, introspectables=introspectables) @action_method def add_route_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): """ Adds a route predicate factory. The view predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_route`. ``name`` should be the name of the predicate. It must be a valid Python identifier (it will be used as a keyword argument to ``add_route``). ``factory`` should be a :term:`predicate factory` or :term:`dotted Python name` which refers to a predicate factory. See :ref:`view_and_route_predicates` for more information. .. versionadded:: 1.4 """ self._add_predicate( 'route', name, factory, weighs_more_than=weighs_more_than, weighs_less_than=weighs_less_than ) def add_default_route_predicates(self): p = pyramid.config.predicates for (name, factory) in ( ('xhr', p.XHRPredicate), ('request_method', p.RequestMethodPredicate), ('path_info', p.PathInfoPredicate), ('request_param', p.RequestParamPredicate), ('header', p.HeaderPredicate), ('accept', p.AcceptPredicate), ('effective_principals', p.EffectivePrincipalsPredicate), ('custom', p.CustomPredicate), ('traverse', p.TraversePredicate), ): self.add_route_predicate(name, factory) def get_routes_mapper(self): """ Return the :term:`routes mapper` object associated with this configurator's :term:`registry`.""" mapper = self.registry.queryUtility(IRoutesMapper) if mapper is None: mapper = RoutesMapper() self.registry.registerUtility(mapper, IRoutesMapper) return mapper pyramid-1.6/pyramid/config/security.py0000644000076500000240000001530112520062551020703 0ustar michaelstaff00000000000000from pyramid.interfaces import ( IAuthorizationPolicy, IAuthenticationPolicy, IDefaultPermission, PHASE1_CONFIG, PHASE2_CONFIG, ) from pyramid.exceptions import ConfigurationError from pyramid.util import action_method class SecurityConfiguratorMixin(object): @action_method def set_authentication_policy(self, policy): """ Override the :app:`Pyramid` :term:`authentication policy` in the current configuration. The ``policy`` argument must be an instance of an authentication policy or a :term:`dotted Python name` that points at an instance of an authentication policy. .. note:: Using the ``authentication_policy`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ def register(): self._set_authentication_policy(policy) if self.registry.queryUtility(IAuthorizationPolicy) is None: raise ConfigurationError( 'Cannot configure an authentication policy without ' 'also configuring an authorization policy ' '(use the set_authorization_policy method)') intr = self.introspectable('authentication policy', None, self.object_description(policy), 'authentication policy') intr['policy'] = policy # authentication policy used by view config (phase 3) self.action(IAuthenticationPolicy, register, order=PHASE2_CONFIG, introspectables=(intr,)) def _set_authentication_policy(self, policy): policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthenticationPolicy) @action_method def set_authorization_policy(self, policy): """ Override the :app:`Pyramid` :term:`authorization policy` in the current configuration. The ``policy`` argument must be an instance of an authorization policy or a :term:`dotted Python name` that points at an instance of an authorization policy. .. note:: Using the ``authorization_policy`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ def register(): self._set_authorization_policy(policy) def ensure(): if self.autocommit: return if self.registry.queryUtility(IAuthenticationPolicy) is None: raise ConfigurationError( 'Cannot configure an authorization policy without ' 'also configuring an authentication policy ' '(use the set_authorization_policy method)') intr = self.introspectable('authorization policy', None, self.object_description(policy), 'authorization policy') intr['policy'] = policy # authorization policy used by view config (phase 3) and # authentication policy (phase 2) self.action(IAuthorizationPolicy, register, order=PHASE1_CONFIG, introspectables=(intr,)) self.action(None, ensure) def _set_authorization_policy(self, policy): policy = self.maybe_dotted(policy) self.registry.registerUtility(policy, IAuthorizationPolicy) @action_method def set_default_permission(self, permission): """ Set the default permission to be used by all subsequent :term:`view configuration` registrations. ``permission`` should be a :term:`permission` string to be used as the default permission. An example of a permission string:``'view'``. Adding a default permission makes it unnecessary to protect each view configuration with an explicit permission, unless your application policy requires some exception for a particular view. If a default permission is *not* set, views represented by view configuration registrations which do not explicitly declare a permission will be executable by entirely anonymous users (any authorization policy is ignored). Later calls to this method override will conflict with earlier calls; there can be only one default permission active at a time within an application. .. warning:: If a default permission is in effect, view configurations meant to create a truly anonymously accessible view (even :term:`exception view` views) *must* use the value of the permission importable as :data:`pyramid.security.NO_PERMISSION_REQUIRED`. When this string is used as the ``permission`` for a view configuration, the default permission is ignored, and the view is registered, making it available to all callers regardless of their credentials. .. seealso:: See also :ref:`setting_a_default_permission`. .. note:: Using the ``default_permission`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ def register(): self.registry.registerUtility(permission, IDefaultPermission) intr = self.introspectable('default permission', None, permission, 'default permission') intr['value'] = permission perm_intr = self.introspectable('permissions', permission, permission, 'permission') perm_intr['value'] = permission # default permission used during view registration (phase 3) self.action(IDefaultPermission, register, order=PHASE1_CONFIG, introspectables=(intr, perm_intr,)) def add_permission(self, permission_name): """ A configurator directive which registers a free-standing permission without associating it with a view callable. This can be used so that the permission shows up in the introspectable data under the ``permissions`` category (permissions mentioned via ``add_view`` already end up in there). For example:: config = Configurator() config.add_permission('view') """ intr = self.introspectable( 'permissions', permission_name, permission_name, 'permission' ) intr['value'] = permission_name self.action(None, introspectables=(intr,)) pyramid-1.6/pyramid/config/settings.py0000644000076500000240000001752012524266531020711 0ustar michaelstaff00000000000000import os import warnings from zope.interface import implementer from pyramid.interfaces import ISettings from pyramid.settings import asbool class SettingsConfiguratorMixin(object): def _set_settings(self, mapping): if not mapping: mapping = {} settings = Settings(mapping) self.registry.settings = settings return settings def add_settings(self, settings=None, **kw): """Augment the :term:`deployment settings` with one or more key/value pairs. You may pass a dictionary:: config.add_settings({'external_uri':'http://example.com'}) Or a set of key/value pairs:: config.add_settings(external_uri='http://example.com') This function is useful when you need to test code that accesses the :attr:`pyramid.registry.Registry.settings` API (or the :meth:`pyramid.config.Configurator.get_settings` API) and which uses values from that API. """ if settings is None: settings = {} utility = self.registry.settings if utility is None: utility = self._set_settings(settings) utility.update(settings) utility.update(kw) def get_settings(self): """ Return a :term:`deployment settings` object for the current application. A deployment settings object is a dictionary-like object that contains key/value pairs based on the dictionary passed as the ``settings`` argument to the :class:`pyramid.config.Configurator` constructor. .. note:: the :attr:`pyramid.registry.Registry.settings` API performs the same duty. """ return self.registry.settings @implementer(ISettings) class Settings(dict): """ Deployment settings. Update application settings (usually from PasteDeploy keywords) with framework-specific key/value pairs (e.g. find ``PYRAMID_DEBUG_AUTHORIZATION`` in os.environ and jam into keyword args).""" # _environ_ is dep inj for testing def __init__(self, d=None, _environ_=os.environ, **kw): if d is None: d = {} dict.__init__(self, d, **kw) eget = _environ_.get config_debug_all = self.get('debug_all', '') config_debug_all = self.get('pyramid.debug_all', config_debug_all) eff_debug_all = asbool(eget('PYRAMID_DEBUG_ALL', config_debug_all)) config_reload_all = self.get('reload_all', '') config_reload_all = self.get('pyramid.reload_all', config_reload_all) eff_reload_all = asbool(eget('PYRAMID_RELOAD_ALL', config_reload_all)) config_debug_auth = self.get('debug_authorization', '') config_debug_auth = self.get('pyramid.debug_authorization', config_debug_auth) eff_debug_auth = asbool(eget('PYRAMID_DEBUG_AUTHORIZATION', config_debug_auth)) config_debug_notfound = self.get('debug_notfound', '') config_debug_notfound = self.get('pyramid.debug_notfound', config_debug_notfound) eff_debug_notfound = asbool(eget('PYRAMID_DEBUG_NOTFOUND', config_debug_notfound)) config_debug_routematch = self.get('debug_routematch', '') config_debug_routematch = self.get('pyramid.debug_routematch', config_debug_routematch) eff_debug_routematch = asbool(eget('PYRAMID_DEBUG_ROUTEMATCH', config_debug_routematch)) config_debug_templates = self.get('debug_templates', '') config_debug_templates = self.get('pyramid.debug_templates', config_debug_templates) eff_debug_templates = asbool(eget('PYRAMID_DEBUG_TEMPLATES', config_debug_templates)) config_reload_templates = self.get('reload_templates', '') config_reload_templates = self.get('pyramid.reload_templates', config_reload_templates) eff_reload_templates = asbool(eget('PYRAMID_RELOAD_TEMPLATES', config_reload_templates)) config_reload_assets = self.get('reload_assets', '') config_reload_assets = self.get('pyramid.reload_assets', config_reload_assets) reload_assets = asbool(eget('PYRAMID_RELOAD_ASSETS', config_reload_assets)) config_reload_resources = self.get('reload_resources', '') config_reload_resources = self.get('pyramid.reload_resources', config_reload_resources) reload_resources = asbool(eget('PYRAMID_RELOAD_RESOURCES', config_reload_resources)) # reload_resources is an older alias for reload_assets eff_reload_assets = reload_assets or reload_resources locale_name = self.get('default_locale_name', 'en') locale_name = self.get('pyramid.default_locale_name', locale_name) eff_locale_name = eget('PYRAMID_DEFAULT_LOCALE_NAME', locale_name) config_prevent_http_cache = self.get('prevent_http_cache', '') config_prevent_http_cache = self.get('pyramid.prevent_http_cache', 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, 'debug_notfound': eff_debug_all or eff_debug_notfound, 'debug_routematch': eff_debug_all or eff_debug_routematch, 'debug_templates': eff_debug_all or eff_debug_templates, 'reload_templates': eff_reload_all or eff_reload_templates, 'reload_resources':eff_reload_all or eff_reload_assets, '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, 'pyramid.debug_routematch': eff_debug_all or eff_debug_routematch, 'pyramid.debug_templates': eff_debug_all or eff_debug_templates, 'pyramid.reload_templates': eff_reload_all or eff_reload_templates, 'pyramid.reload_resources':eff_reload_all or eff_reload_assets, '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) def __getattr__(self, name): try: val = self[name] # only deprecate on success; a probing getattr/hasattr should not # print this warning warnings.warn( 'Obtaining settings via attributes of the settings dictionary ' 'is deprecated as of Pyramid 1.2; use settings["foo"] instead ' 'of settings.foo', DeprecationWarning, 2 ) return val except KeyError: raise AttributeError(name) pyramid-1.6/pyramid/config/testing.py0000644000076500000240000001620612520062551020516 0ustar michaelstaff00000000000000from zope.interface import Interface from pyramid.interfaces import ( ITraverser, IAuthorizationPolicy, IAuthenticationPolicy, IRendererFactory, ) from pyramid.renderers import RendererHelper from pyramid.traversal import ( decode_path_info, split_path_info, ) from pyramid.util import action_method class TestingConfiguratorMixin(object): # testing API def testing_securitypolicy(self, userid=None, groupids=(), permissive=True, remember_result=None, forget_result=None): """Unit/integration testing helper: Registers a pair of faux :app:`Pyramid` security policies: a :term:`authentication policy` and a :term:`authorization policy`. The behavior of the registered :term:`authorization policy` depends on the ``permissive`` argument. If ``permissive`` is true, a permissive :term:`authorization policy` is registered; this policy allows all access. If ``permissive`` is false, a nonpermissive :term:`authorization policy` is registered; this policy denies all access. ``remember_result``, if provided, should be the result returned by the ``remember`` method of the faux authentication policy. If it is not provided (or it is provided, and is ``None``), the default value ``[]`` (the empty list) will be returned by ``remember``. ``forget_result``, if provided, should be the result returned by the ``forget`` method of the faux authentication policy. If it is not provided (or it is provided, and is ``None``), the default value ``[]`` (the empty list) will be returned by ``forget``. The behavior of the registered :term:`authentication policy` depends on the values provided for the ``userid`` and ``groupids`` argument. The authentication policy will return the userid identifier implied by the ``userid`` argument and the group ids implied by the ``groupids`` argument when the :attr:`pyramid.request.Request.authenticated_userid` or :attr:`pyramid.request.Request.effective_principals` APIs are used. This function is most useful when testing code that uses the APIs named :meth:`pyramid.request.Request.has_permission`, :attr:`pyramid.request.Request.authenticated_userid`, :attr:`pyramid.request.Request.effective_principals`, and :func:`pyramid.security.principals_allowed_by_permission`. .. versionadded:: 1.4 The ``remember_result`` argument. .. versionadded:: 1.4 The ``forget_result`` argument. """ from pyramid.testing import DummySecurityPolicy policy = DummySecurityPolicy( userid, groupids, permissive, remember_result, forget_result ) self.registry.registerUtility(policy, IAuthorizationPolicy) self.registry.registerUtility(policy, IAuthenticationPolicy) return policy def testing_resources(self, resources): """Unit/integration testing helper: registers a dictionary of :term:`resource` objects that can be resolved via the :func:`pyramid.traversal.find_resource` API. The :func:`pyramid.traversal.find_resource` API is called with a path as one of its arguments. If the dictionary you register when calling this method contains that path as a string key (e.g. ``/foo/bar`` or ``foo/bar``), the corresponding value will be returned to ``find_resource`` (and thus to your code) when :func:`pyramid.traversal.find_resource` is called with an equivalent path string or tuple. """ class DummyTraverserFactory: def __init__(self, context): self.context = context def __call__(self, request): path = decode_path_info(request.environ['PATH_INFO']) ob = resources[path] traversed = split_path_info(path) return {'context':ob, 'view_name':'','subpath':(), 'traversed':traversed, 'virtual_root':ob, 'virtual_root_path':(), 'root':ob} self.registry.registerAdapter(DummyTraverserFactory, (Interface,), ITraverser) return resources testing_models = testing_resources # b/w compat @action_method def testing_add_subscriber(self, event_iface=None): """Unit/integration testing helper: Registers a :term:`subscriber` which listens for events of the type ``event_iface``. This method returns a list object which is appended to by the subscriber whenever an event is captured. When an event is dispatched that matches the value implied by the ``event_iface`` argument, that event will be appended to the list. You can then compare the values in the list to expected event notifications. This method is useful when testing code that wants to call :meth:`pyramid.registry.Registry.notify`, or :func:`zope.component.event.dispatch`. The default value of ``event_iface`` (``None``) implies a subscriber registered for *any* kind of event. """ event_iface = self.maybe_dotted(event_iface) L = [] def subscriber(*event): L.extend(event) self.add_subscriber(subscriber, event_iface) return L def testing_add_renderer(self, path, renderer=None): """Unit/integration testing helper: register a renderer at ``path`` (usually a relative filename ala ``templates/foo.pt`` or an asset specification) and return the renderer object. If the ``renderer`` argument is None, a 'dummy' renderer will be used. This function is useful when testing code that calls the :func:`pyramid.renderers.render` function or :func:`pyramid.renderers.render_to_response` function or any other ``render_*`` or ``get_*`` API of the :mod:`pyramid.renderers` module. Note that calling this method for with a ``path`` argument representing a renderer factory type (e.g. for ``foo.pt`` usually implies the ``chameleon_zpt`` renderer factory) clobbers any existing renderer factory registered for that type. .. note:: This method is also available under the alias ``testing_add_template`` (an older name for it). """ from pyramid.testing import DummyRendererFactory helper = RendererHelper(name=path, registry=self.registry) factory = self.registry.queryUtility(IRendererFactory, name=helper.type) if not isinstance(factory, DummyRendererFactory): factory = DummyRendererFactory(helper.type, factory) self.registry.registerUtility(factory, IRendererFactory, name=helper.type) from pyramid.testing import DummyTemplateRenderer if renderer is None: renderer = DummyTemplateRenderer() factory.add(path, renderer) return renderer testing_add_template = testing_add_renderer pyramid-1.6/pyramid/config/tweens.py0000644000076500000240000002052312520062551020343 0ustar michaelstaff00000000000000from zope.interface import implementer from pyramid.interfaces import ITweens from pyramid.compat import ( string_types, is_nonstr_iter, ) from pyramid.exceptions import ConfigurationError from pyramid.tweens import ( excview_tween_factory, MAIN, INGRESS, EXCVIEW, ) from pyramid.config.util import ( action_method, TopologicalSorter, ) class TweensConfiguratorMixin(object): def add_tween(self, tween_factory, under=None, over=None): """ .. versionadded:: 1.2 Add a 'tween factory'. A :term:`tween` (a contraction of 'between') is a bit of code that sits between the Pyramid router's main request handling function and the upstream WSGI component that uses :app:`Pyramid` as its 'app'. Tweens are a feature that may be used by Pyramid framework extensions, to provide, for example, Pyramid-specific view timing support, bookkeeping code that examines exceptions before they are returned to the upstream WSGI application, or a variety of other features. Tweens behave a bit like :term:`WSGI` 'middleware' but they have the benefit of running in a context in which they have access to the Pyramid :term:`application registry` as well as the Pyramid rendering machinery. .. note:: You can view the tween ordering configured into a given Pyramid application by using the ``ptweens`` command. See :ref:`displaying_tweens`. The ``tween_factory`` argument must be a :term:`dotted Python name` to a global object representing the tween factory. The ``under`` and ``over`` arguments allow the caller of ``add_tween`` to provide a hint about where in the tween chain this tween factory should be placed when an implicit tween chain is used. These hints are only used when an explicit tween chain is not used (when the ``pyramid.tweens`` configuration value is not set). Allowable values for ``under`` or ``over`` (or both) are: - ``None`` (the default). - A :term:`dotted Python name` to a tween factory: a string representing the dotted name of a tween factory added in a call to ``add_tween`` in the same configuration session. - One of the constants :attr:`pyramid.tweens.MAIN`, :attr:`pyramid.tweens.INGRESS`, or :attr:`pyramid.tweens.EXCVIEW`. - An iterable of any combination of the above. This allows the user to specify fallbacks if the desired tween is not included, as well as compatibility with multiple other tweens. ``under`` means 'closer to the main Pyramid application than', ``over`` means 'closer to the request ingress than'. For example, calling ``add_tween('myapp.tfactory', over=pyramid.tweens.MAIN)`` will attempt to place the tween factory represented by the dotted name ``myapp.tfactory`` directly 'above' (in ``ptweens`` order) the main Pyramid request handler. Likewise, calling ``add_tween('myapp.tfactory', over=pyramid.tweens.MAIN, under='mypkg.someothertween')`` will attempt to place this tween factory 'above' the main handler but 'below' (a fictional) 'mypkg.someothertween' tween factory. If all options for ``under`` (or ``over``) cannot be found in the current configuration, it is an error. If some options are specified purely for compatibilty with other tweens, just add a fallback of MAIN or INGRESS. For example, ``under=('mypkg.someothertween', 'mypkg.someothertween2', INGRESS)``. This constraint will require the tween to be located under both the 'mypkg.someothertween' tween, the 'mypkg.someothertween2' tween, and INGRESS. If any of these is not in the current configuration, this constraint will only organize itself based on the tweens that are present. Specifying neither ``over`` nor ``under`` is equivalent to specifying ``under=INGRESS``. Implicit tween ordering is obviously only best-effort. Pyramid will attempt to present an implicit order of tweens as best it can, but the only surefire way to get any particular ordering is to use an explicit tween order. A user may always override the implicit tween ordering by using an explicit ``pyramid.tweens`` configuration value setting. ``under``, and ``over`` arguments are ignored when an explicit tween chain is specified using the ``pyramid.tweens`` configuration value. For more information, see :ref:`registering_tweens`. """ return self._add_tween(tween_factory, under=under, over=over, explicit=False) @action_method def _add_tween(self, tween_factory, under=None, over=None, explicit=False): if not isinstance(tween_factory, string_types): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' 'dotted name to a globally importable object, not %r' % tween_factory) name = tween_factory if name in (MAIN, INGRESS): raise ConfigurationError('%s is a reserved tween name' % name) tween_factory = self.maybe_dotted(tween_factory) def is_string_or_iterable(v): if isinstance(v, string_types): return True if hasattr(v, '__iter__'): return True for t, p in [('over', over), ('under', under)]: if p is not None: if not is_string_or_iterable(p): raise ConfigurationError( '"%s" must be a string or iterable, not %s' % (t, p)) if over is INGRESS or is_nonstr_iter(over) and INGRESS in over: raise ConfigurationError('%s cannot be over INGRESS' % name) if under is MAIN or is_nonstr_iter(under) and MAIN in under: raise ConfigurationError('%s cannot be under MAIN' % name) registry = self.registry introspectables = [] tweens = registry.queryUtility(ITweens) if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) ex_intr = self.introspectable('tweens', ('tween', EXCVIEW, False), EXCVIEW, 'implicit tween') ex_intr['name'] = EXCVIEW ex_intr['factory'] = excview_tween_factory ex_intr['type'] = 'implicit' ex_intr['under'] = None ex_intr['over'] = MAIN introspectables.append(ex_intr) tweens.add_implicit(EXCVIEW, excview_tween_factory, over=MAIN) def register(): if explicit: tweens.add_explicit(name, tween_factory) else: tweens.add_implicit(name, tween_factory, under=under, over=over) discriminator = ('tween', name, explicit) tween_type = explicit and 'explicit' or 'implicit' intr = self.introspectable('tweens', discriminator, name, '%s tween' % tween_type) intr['name'] = name intr['factory'] = tween_factory intr['type'] = tween_type intr['under'] = under intr['over'] = over introspectables.append(intr) self.action(discriminator, register, introspectables=introspectables) @implementer(ITweens) class Tweens(object): def __init__(self): self.sorter = TopologicalSorter( default_before=None, default_after=INGRESS, first=INGRESS, last=MAIN) self.explicit = [] def add_explicit(self, name, factory): self.explicit.append((name, factory)) def add_implicit(self, name, factory, under=None, over=None): self.sorter.add(name, factory, after=under, before=over) def implicit(self): return self.sorter.sorted() def __call__(self, handler, registry): if self.explicit: use = self.explicit else: use = self.implicit() for name, factory in use[::-1]: handler = factory(handler, registry) return handler pyramid-1.6/pyramid/config/util.py0000644000076500000240000001634612642137120020023 0ustar michaelstaff00000000000000from hashlib import md5 import inspect from pyramid.compat import ( bytes_, getargspec, is_nonstr_iter, ) from pyramid.compat import im_func from pyramid.exceptions import ConfigurationError from pyramid.registry import predvalseq from pyramid.util import ( TopologicalSorter, action_method, ActionInfo, ) action_method = action_method # support bw compat imports ActionInfo = ActionInfo # support bw compat imports MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() def as_sorted_tuple(val): if not is_nonstr_iter(val): val = (val,) val = tuple(sorted(val)) return val class not_(object): """ You can invert the meaning of any predicate value by wrapping it in a call to :class:`pyramid.config.not_`. .. code-block:: python :linenos: from pyramid.config import not_ config.add_view( 'mypackage.views.my_view', route_name='ok', request_method=not_('POST') ) The above example will ensure that the view is called if the request method is *not* ``POST``, at least if no other view is more specific. This technique of wrapping a predicate value in ``not_`` can be used anywhere predicate values are accepted: - :meth:`pyramid.config.Configurator.add_view` - :meth:`pyramid.config.Configurator.add_route` - :meth:`pyramid.config.Configurator.add_subscriber` - :meth:`pyramid.view.view_config` - :meth:`pyramid.events.subscriber` .. versionadded:: 1.5 """ def __init__(self, value): self.value = value class Notted(object): def __init__(self, predicate): self.predicate = predicate def _notted_text(self, val): # if the underlying predicate doesnt return a value, it's not really # a predicate, it's just something pretending to be a predicate, # so dont update the hash if val: val = '!' + val return val def text(self): return self._notted_text(self.predicate.text()) def phash(self): return self._notted_text(self.predicate.phash()) def __call__(self, context, request): result = self.predicate(context, request) phash = self.phash() if phash: result = not result return result # under = after # over = before class PredicateList(object): def __init__(self): self.sorter = TopologicalSorter() self.last_added = None def add(self, name, factory, weighs_more_than=None, weighs_less_than=None): # Predicates should be added to a predicate list in (presumed) # computation expense order. ## if weighs_more_than is None and weighs_less_than is None: ## weighs_more_than = self.last_added or FIRST ## weighs_less_than = LAST self.last_added = name self.sorter.add( name, factory, after=weighs_more_than, before=weighs_less_than, ) def make(self, config, **kw): # Given a configurator and a list of keywords, a predicate list is # computed. Elsewhere in the code, we evaluate predicates using a # generator expression. All predicates associated with a view or # route must evaluate true for the view or route to "match" during a # request. The fastest predicate should be evaluated first, then the # next fastest, and so on, as if one returns false, the remainder of # the predicates won't need to be evaluated. # # While we compute predicates, we also compute a predicate hash (aka # phash) that can be used by a caller to identify identical predicate # lists. ordered = self.sorter.sorted() phash = md5() weights = [] preds = [] for n, (name, predicate_factory) in enumerate(ordered): vals = kw.pop(name, None) if vals is None: # XXX should this be a sentinel other than None? continue if not isinstance(vals, predvalseq): vals = (vals,) for val in vals: realval = val notted = False if isinstance(val, not_): realval = val.value notted = True pred = predicate_factory(realval, config) if notted: pred = Notted(pred) hashes = pred.phash() if not is_nonstr_iter(hashes): hashes = [hashes] for h in hashes: phash.update(bytes_(h)) weights.append(1 << n+1) preds.append(pred) if kw: raise ConfigurationError('Unknown predicate values: %r' % (kw,)) # A "order" is computed for the predicate list. An order is # a scoring. # # Each predicate is associated with a weight value. The weight of a # predicate symbolizes the relative potential "importance" of the # predicate to all other predicates. A larger weight indicates # greater importance. # # All weights for a given predicate list are bitwise ORed together # to create a "score"; this score is then subtracted from # MAX_ORDER and divided by an integer representing the number of # predicates+1 to determine the order. # # For views, the order represents the ordering in which a "multiview" # ( a collection of views that share the same context/request/name # triad but differ in other ways via predicates) will attempt to call # its set of views. Views with lower orders will be tried first. # The intent is to a) ensure that views with more predicates are # always evaluated before views with fewer predicates and b) to # ensure a stable call ordering of views that share the same number # of predicates. Views which do not have any predicates get an order # of MAX_ORDER, meaning that they will be tried very last. score = 0 for bit in weights: score = score | bit order = (MAX_ORDER - score) / (len(preds) + 1) return order, preds, phash.hexdigest() def takes_one_arg(callee, attr=None, argname=None): ismethod = False if attr is None: attr = '__call__' if inspect.isroutine(callee): fn = callee elif inspect.isclass(callee): try: fn = callee.__init__ except AttributeError: return False ismethod = hasattr(fn, '__call__') else: try: fn = getattr(callee, attr) except AttributeError: return False try: argspec = getargspec(fn) except TypeError: return False args = argspec[0] if hasattr(fn, im_func) or ismethod: # it's an instance method (or unbound method on py2) if not args: return False args = args[1:] if not args: return False if len(args) == 1: return True if argname: defaults = argspec[3] if defaults is None: defaults = () if args[0] == argname: if len(args) - len(defaults) == 1: return True return False pyramid-1.6/pyramid/config/views.py0000644000076500000240000025543512642137120020207 0ustar michaelstaff00000000000000import inspect import posixpath import operator import os import warnings from zope.interface import ( Interface, implementedBy, implementer, provider, ) from zope.interface.interfaces import IInterface from pyramid.interfaces import ( IAuthenticationPolicy, IAuthorizationPolicy, IDebugLogger, IDefaultPermission, IException, IExceptionViewClassifier, IMultiView, IPackageOverrides, IRendererFactory, IRequest, IResponse, IRouteRequest, ISecuredView, IStaticURLInfo, IView, IViewClassifier, IViewMapper, IViewMapperFactory, PHASE1_CONFIG, ) from pyramid import renderers from pyramid.asset import resolve_asset_spec from pyramid.compat import ( string_types, urlparse, url_quote, WIN, is_bound_method, is_unbound_method, is_nonstr_iter, ) from pyramid.exceptions import ( ConfigurationError, PredicateMismatch, ) from pyramid.httpexceptions import ( HTTPForbidden, HTTPNotFound, default_exceptionresponse_view, ) from pyramid.registry import ( predvalseq, Deferred, ) from pyramid.response import Response from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.static import static_view from pyramid.url import parse_url_overrides from pyramid.view import ( render_view_to_response, AppendSlashNotFoundViewFactory, ) from pyramid.util import ( object_description, viewdefaults, action_method, ) import pyramid.config.predicates from pyramid.config.util import ( DEFAULT_PHASH, MAX_ORDER, takes_one_arg, ) urljoin = urlparse.urljoin url_parse = urlparse.urlparse def view_description(view): try: return view.__text__ except AttributeError: # custom view mappers might not add __text__ return object_description(view) def wraps_view(wrapper): def inner(self, view): wrapper_view = wrapper(self, view) return preserve_view_attrs(view, wrapper_view) return inner def preserve_view_attrs(view, wrapper): if view is None: return wrapper if wrapper is view: return view original_view = getattr(view, '__original_view__', None) if original_view is None: original_view = view wrapper.__wraps__ = view wrapper.__original_view__ = original_view wrapper.__module__ = view.__module__ wrapper.__doc__ = view.__doc__ try: wrapper.__name__ = view.__name__ except AttributeError: wrapper.__name__ = repr(view) # attrs that may not exist on "view", but, if so, must be attached to # "wrapped view" for attr in ('__permitted__', '__call_permissive__', '__permission__', '__predicated__', '__predicates__', '__accept__', '__order__', '__text__'): try: setattr(wrapper, attr, getattr(view, attr)) except AttributeError: pass return wrapper class ViewDeriver(object): def __init__(self, **kw): self.kw = kw self.registry = kw['registry'] self.authn_policy = self.registry.queryUtility(IAuthenticationPolicy) self.authz_policy = self.registry.queryUtility(IAuthorizationPolicy) self.logger = self.registry.queryUtility(IDebugLogger) def __call__(self, view): return self.attr_wrapped_view( self.predicated_view( self.authdebug_view( self.secured_view( self.owrapped_view( self.http_cached_view( self.decorated_view( self.rendered_view( self.mapped_view( view))))))))) @wraps_view def mapped_view(self, view): mapper = self.kw.get('mapper') if mapper is None: mapper = getattr(view, '__view_mapper__', None) if mapper is None: mapper = self.registry.queryUtility(IViewMapperFactory) if mapper is None: mapper = DefaultViewMapper mapped_view = mapper(**self.kw)(view) return mapped_view @wraps_view def owrapped_view(self, view): wrapper_viewname = self.kw.get('wrapper_viewname') viewname = self.kw.get('viewname') if not wrapper_viewname: return view def _owrapped_view(context, request): response = view(context, request) request.wrapped_response = response request.wrapped_body = response.body request.wrapped_view = view wrapped_response = render_view_to_response(context, request, wrapper_viewname) if wrapped_response is None: raise ValueError( 'No wrapper view named %r found when executing view ' 'named %r' % (wrapper_viewname, viewname)) return wrapped_response return _owrapped_view @wraps_view def http_cached_view(self, view): if self.registry.settings.get('prevent_http_cache', False): return view seconds = self.kw.get('http_cache') if seconds is None: return view options = {} if isinstance(seconds, (tuple, list)): try: seconds, options = seconds except ValueError: raise ConfigurationError( 'If http_cache parameter is a tuple or list, it must be ' 'in the form (seconds, options); not %s' % (seconds,)) def wrapper(context, request): response = view(context, request) prevent_caching = getattr(response.cache_control, 'prevent_auto', False) if not prevent_caching: response.cache_expires(seconds, **options) return response return wrapper @wraps_view def secured_view(self, view): permission = self.kw.get('permission') if permission == NO_PERMISSION_REQUIRED: # allow views registered within configurations that have a # default permission to explicitly override the default # permission, replacing it with no permission at all permission = None wrapped_view = view if self.authn_policy and self.authz_policy and (permission is not None): def _permitted(context, request): principals = self.authn_policy.effective_principals(request) return self.authz_policy.permits(context, principals, permission) def _secured_view(context, request): result = _permitted(context, request) if result: return view(context, request) view_name = getattr(view, '__name__', view) msg = getattr( request, 'authdebug_message', 'Unauthorized: %s failed permission check' % view_name) raise HTTPForbidden(msg, result=result) _secured_view.__call_permissive__ = view _secured_view.__permitted__ = _permitted _secured_view.__permission__ = permission wrapped_view = _secured_view return wrapped_view @wraps_view def authdebug_view(self, view): wrapped_view = view settings = self.registry.settings permission = self.kw.get('permission') if settings and settings.get('debug_authorization', False): def _authdebug_view(context, request): view_name = getattr(request, 'view_name', None) if self.authn_policy and self.authz_policy: if permission is NO_PERMISSION_REQUIRED: msg = 'Allowed (NO_PERMISSION_REQUIRED)' elif permission is None: msg = 'Allowed (no permission registered)' else: principals = self.authn_policy.effective_principals( request) msg = str(self.authz_policy.permits(context, principals, permission)) else: msg = 'Allowed (no authorization policy in use)' view_name = getattr(request, 'view_name', None) url = getattr(request, 'url', None) msg = ('debug_authorization of url %s (view name %r against ' 'context %r): %s' % (url, view_name, context, msg)) self.logger and self.logger.debug(msg) if request is not None: request.authdebug_message = msg return view(context, request) wrapped_view = _authdebug_view return wrapped_view @wraps_view def predicated_view(self, view): preds = self.kw.get('predicates', ()) if not preds: return view def predicate_wrapper(context, request): for predicate in preds: if not predicate(context, request): view_name = getattr(view, '__name__', view) raise PredicateMismatch( 'predicate mismatch for view %s (%s)' % ( view_name, predicate.text())) return view(context, request) def checker(context, request): return all((predicate(context, request) for predicate in preds)) predicate_wrapper.__predicated__ = checker predicate_wrapper.__predicates__ = preds return predicate_wrapper @wraps_view def attr_wrapped_view(self, view): kw = self.kw accept, order, phash = (kw.get('accept', None), kw.get('order', MAX_ORDER), kw.get('phash', DEFAULT_PHASH)) # this is a little silly but we don't want to decorate the original # function with attributes that indicate accept, order, and phash, # so we use a wrapper if ( (accept is None) and (order == MAX_ORDER) and (phash == DEFAULT_PHASH) ): return view # defaults def attr_view(context, request): return view(context, request) attr_view.__accept__ = accept attr_view.__order__ = order attr_view.__phash__ = phash attr_view.__view_attr__ = self.kw.get('attr') attr_view.__permission__ = self.kw.get('permission') return attr_view @wraps_view def rendered_view(self, view): # one way or another this wrapper must produce a Response (unless # the renderer is a NullRendererHelper) renderer = self.kw.get('renderer') if renderer is None: # register a default renderer if you want super-dynamic # rendering. registering a default renderer will also allow # override_renderer to work if a renderer is left unspecified for # a view registration. return self._response_resolved_view(view) if renderer is renderers.null_renderer: return view return self._rendered_view(view, renderer) def _rendered_view(self, view, view_renderer): def rendered_view(context, request): result = view(context, request) if result.__class__ is Response: # potential common case response = result else: registry = self.registry # this must adapt, it can't do a simple interface check # (avoid trying to render webob responses) response = registry.queryAdapterOrSelf(result, IResponse) if response is None: attrs = getattr(request, '__dict__', {}) if 'override_renderer' in attrs: # renderer overridden by newrequest event or other renderer_name = attrs.pop('override_renderer') renderer = renderers.RendererHelper( name=renderer_name, package=self.kw.get('package'), registry = registry) else: renderer = view_renderer.clone() if '__view__' in attrs: view_inst = attrs.pop('__view__') else: view_inst = getattr(view, '__original_view__', view) response = renderer.render_view(request, result, view_inst, context) return response return rendered_view def _response_resolved_view(self, view): registry = self.registry def viewresult_to_response(context, request): result = view(context, request) if result.__class__ is Response: # common case response = result else: response = registry.queryAdapterOrSelf(result, IResponse) if response is None: if result is None: append = (' You may have forgotten to return a value ' 'from the view callable.') elif isinstance(result, dict): append = (' You may have forgotten to define a ' 'renderer in the view configuration.') else: append = '' msg = ('Could not convert return value of the view ' 'callable %s into a response object. ' 'The value returned was %r.' + append) raise ValueError(msg % (view_description(view), result)) return response return viewresult_to_response @wraps_view def decorated_view(self, view): decorator = self.kw.get('decorator') if decorator is None: return view return decorator(view) @implementer(IViewMapper) @provider(IViewMapperFactory) class DefaultViewMapper(object): def __init__(self, **kw): self.attr = kw.get('attr') def __call__(self, view): if is_unbound_method(view) and self.attr is None: raise ConfigurationError(( 'Unbound method calls are not supported, please set the class ' 'as your `view` and the method as your `attr`' )) if inspect.isclass(view): view = self.map_class(view) else: view = self.map_nonclass(view) return view def map_class(self, view): ronly = requestonly(view, self.attr) if ronly: mapped_view = self.map_class_requestonly(view) else: mapped_view = self.map_class_native(view) mapped_view.__text__ = 'method %s of %s' % ( self.attr or '__call__', object_description(view)) return mapped_view def map_nonclass(self, view): # We do more work here than appears necessary to avoid wrapping the # view unless it actually requires wrapping (to avoid function call # overhead). mapped_view = view ronly = requestonly(view, self.attr) if ronly: mapped_view = self.map_nonclass_requestonly(view) elif self.attr: mapped_view = self.map_nonclass_attr(view) if inspect.isroutine(mapped_view): # This branch will be true if the view is a function or a method. # We potentially mutate an unwrapped object here if it's a # function. We do this to avoid function call overhead of # injecting another wrapper. However, we must wrap if the # function is a bound method because we can't set attributes on a # bound method. if is_bound_method(view): _mapped_view = mapped_view def mapped_view(context, request): return _mapped_view(context, request) if self.attr is not None: mapped_view.__text__ = 'attr %s of %s' % ( self.attr, object_description(view)) else: mapped_view.__text__ = object_description(view) return mapped_view def map_class_requestonly(self, view): # its a class that has an __init__ which only accepts request attr = self.attr def _class_requestonly_view(context, request): inst = view(request) request.__view__ = inst if attr is None: response = inst() else: response = getattr(inst, attr)() return response return _class_requestonly_view def map_class_native(self, view): # its a class that has an __init__ which accepts both context and # request attr = self.attr def _class_view(context, request): inst = view(context, request) request.__view__ = inst if attr is None: response = inst() else: response = getattr(inst, attr)() return response return _class_view def map_nonclass_requestonly(self, view): # its a function that has a __call__ which accepts only a single # request argument attr = self.attr def _requestonly_view(context, request): if attr is None: response = view(request) else: response = getattr(view, attr)(request) return response return _requestonly_view def map_nonclass_attr(self, view): # its a function that has a __call__ which accepts both context and # request, but still has an attr def _attr_view(context, request): response = getattr(view, self.attr)(context, request) return response return _attr_view def requestonly(view, attr=None): return takes_one_arg(view, attr=attr, argname='request') @implementer(IMultiView) class MultiView(object): def __init__(self, name): self.name = name self.media_views = {} self.views = [] self.accepts = [] def __discriminator__(self, context, request): # used by introspection systems like so: # view = adapters.lookup(....) # view.__discriminator__(context, request) -> view's discriminator # so that superdynamic systems can feed the discriminator to # the introspection system to get info about it view = self.match(context, request) return view.__discriminator__(context, request) def add(self, view, order, accept=None, phash=None): if phash is not None: for i, (s, v, h) in enumerate(list(self.views)): if phash == h: self.views[i] = (order, view, phash) return if accept is None or '*' in accept: self.views.append((order, view, phash)) self.views.sort(key=operator.itemgetter(0)) else: subset = self.media_views.setdefault(accept, []) for i, (s, v, h) in enumerate(list(subset)): if phash == h: subset[i] = (order, view, phash) return else: subset.append((order, view, phash)) subset.sort(key=operator.itemgetter(0)) accepts = set(self.accepts) accepts.add(accept) self.accepts = list(accepts) # dedupe def get_views(self, request): if self.accepts and hasattr(request, 'accept'): accepts = self.accepts[:] views = [] while accepts: match = request.accept.best_match(accepts) if match is None: break subset = self.media_views[match] views.extend(subset) accepts.remove(match) views.extend(self.views) return views return self.views def match(self, context, request): for order, view, phash in self.get_views(request): if not hasattr(view, '__predicated__'): return view if view.__predicated__(context, request): return view raise PredicateMismatch(self.name) def __permitted__(self, context, request): view = self.match(context, request) if hasattr(view, '__permitted__'): return view.__permitted__(context, request) return True def __call_permissive__(self, context, request): view = self.match(context, request) view = getattr(view, '__call_permissive__', view) return view(context, request) def __call__(self, context, request): for order, view, phash in self.get_views(request): try: return view(context, request) except PredicateMismatch: continue raise PredicateMismatch(self.name) class ViewsConfiguratorMixin(object): @viewdefaults @action_method def add_view( self, view=None, name="", for_=None, permission=None, request_type=None, route_name=None, request_method=None, request_param=None, containment=None, attr=None, renderer=None, wrapper=None, xhr=None, accept=None, header=None, path_info=None, custom_predicates=(), context=None, decorator=None, mapper=None, http_cache=None, match_param=None, check_csrf=None, **predicates): """ Add a :term:`view configuration` to the current configuration state. Arguments to ``add_view`` are broken down below into *predicate* arguments and *non-predicate* arguments. Predicate arguments narrow the circumstances in which the view callable will be invoked when a request is presented to :app:`Pyramid`; non-predicate arguments are informational. Non-Predicate Arguments view A :term:`view callable` or a :term:`dotted Python name` which refers to a view callable. This argument is required unless a ``renderer`` argument also exists. If a ``renderer`` argument is passed, and a ``view`` argument is not provided, the view callable defaults to a callable that returns an empty dictionary (see :ref:`views_which_use_a_renderer`). permission A :term:`permission` that the user must possess in order to invoke the :term:`view callable`. See :ref:`view_security_section` for more information about view security and permissions. This is often a string like ``view`` or ``edit``. If ``permission`` is omitted, a *default* permission may be used for this view registration if one was named as the :class:`pyramid.config.Configurator` constructor's ``default_permission`` argument, or if :meth:`pyramid.config.Configurator.set_default_permission` was used prior to this view registration. Pass the value :data:`pyramid.security.NO_PERMISSION_REQUIRED` as the permission argument to explicitly indicate that the view should always be executable by entirely anonymous users, regardless of the default permission, bypassing any :term:`authorization policy` that may be in effect. attr This knob is most useful when the view definition is a class. The view machinery defaults to using the ``__call__`` method of the :term:`view callable` (or the function itself, if the view callable is a function) to obtain a response. The ``attr`` value allows you to vary the method attribute used to obtain the response. For example, if your view was a class, and the class has a method named ``index`` and you wanted to use this method instead of the class' ``__call__`` method to return the response, you'd say ``attr="index"`` in the view configuration for the view. renderer This is either a single string term (e.g. ``json``) or a string implying a path or :term:`asset specification` (e.g. ``templates/views.pt``) naming a :term:`renderer` implementation. If the ``renderer`` value does not contain a dot ``.``, the specified string will be used to look up a renderer implementation, and that renderer implementation will be used to construct a response from the view return value. If the ``renderer`` value contains a dot (``.``), the specified term will be treated as a path, and the filename extension of the last element in the path will be used to look up the renderer implementation, which will be passed the full path. The renderer implementation will be used to construct a :term:`response` from the view return value. Note that if the view itself returns a :term:`response` (see :ref:`the_response`), the specified renderer implementation is never called. When the renderer is a path, although a path is usually just a simple relative pathname (e.g. ``templates/foo.pt``, implying that a template named "foo.pt" is in the "templates" directory relative to the directory of the current :term:`package` of the Configurator), a path can be absolute, starting with a slash on UNIX or a drive letter prefix on Windows. The path can alternately be a :term:`asset specification` in the form ``some.dotted.package_name:relative/path``, making it possible to address template assets which live in a separate package. The ``renderer`` attribute is optional. If it is not defined, the "null" renderer is assumed (no rendering is performed and the value is passed back to the upstream :app:`Pyramid` machinery unmodified). http_cache .. versionadded:: 1.1 When you supply an ``http_cache`` value to a view configuration, the ``Expires`` and ``Cache-Control`` headers of a response generated by the associated view callable are modified. The value for ``http_cache`` may be one of the following: - A nonzero integer. If it's a nonzero integer, it's treated as a number of seconds. This number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=3600`` instructs the requesting browser to 'cache this response for an hour, please'. - A ``datetime.timedelta`` instance. If it's a ``datetime.timedelta`` instance, it will be converted into a number of seconds, and that number of seconds will be used to compute the ``Expires`` header and the ``Cache-Control: max-age`` parameter of responses to requests which call this view. For example: ``http_cache=datetime.timedelta(days=1)`` instructs the requesting browser to 'cache this response for a day, please'. - Zero (``0``). If the value is zero, the ``Cache-Control`` and ``Expires`` headers present in all responses from this view will be composed such that client browser cache (and any intermediate caches) are instructed to never cache the response. - A two-tuple. If it's a two tuple (e.g. ``http_cache=(1, {'public':True})``), the first value in the tuple may be a nonzero integer or a ``datetime.timedelta`` instance; in either case this value will be used as the number of seconds to cache the response. The second value in the tuple must be a dictionary. The values present in the dictionary will be used as input to the ``Cache-Control`` response header. For example: ``http_cache=(3600, {'public':True})`` means 'cache for an hour, and add ``public`` to the Cache-Control header of the response'. All keys and values supported by the ``webob.cachecontrol.CacheControl`` interface may be added to the dictionary. Supplying ``{'public':True}`` is equivalent to calling ``response.cache_control.public = True``. Providing a non-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value)`` within your view's body. Providing a two-tuple value as ``http_cache`` is equivalent to calling ``response.cache_expires(value[0], **value[1])`` within your view's body. If you wish to avoid influencing, the ``Expires`` header, and instead wish to only influence ``Cache-Control`` headers, pass a tuple as ``http_cache`` with the first element of ``None``, e.g.: ``(None, {'public':True})``. If you wish to prevent a view that uses ``http_cache`` in its configuration from having its caching response headers changed by this machinery, set ``response.cache_control.prevent_auto = True`` before returning the response from the view. This effectively disables any HTTP caching done by ``http_cache`` for that response. wrapper The :term:`view name` of a different :term:`view configuration` which will receive the response body of this view as the ``request.wrapped_body`` attribute of its own :term:`request`, and the :term:`response` returned by this view as the ``request.wrapped_response`` attribute of its own request. Using a wrapper makes it possible to "chain" views together to form a composite response. The response of the outermost wrapper view will be returned to the user. The wrapper view will be found as any view is found: see :ref:`view_lookup`. The "best" wrapper view will be found based on the lookup ordering: "under the hood" this wrapper view is looked up via ``pyramid.view.render_view_to_response(context, request, 'wrapper_viewname')``. The context and request of a wrapper view is the same context and request of the inner view. If this attribute is unspecified, no view wrapping is done. decorator A :term:`dotted Python name` to function (or the function itself, or an iterable of the aforementioned) which will be used to decorate the registered :term:`view callable`. The decorator function(s) will be called with the view callable as a single argument. The view callable it is passed will accept ``(context, request)``. The decorator(s) must return a replacement view callable which also accepts ``(context, request)``. If decorator is an iterable, the callables will be combined and used in the order provided as a decorator. For example:: @view_config(..., decorator=(decorator2, decorator1)) def myview(request): .... Is similar to doing:: @view_config(...) @decorator2 @decorator1 def myview(request): ... Except with the existing benefits of ``decorator=`` (having a common decorator syntax for all view calling conventions and not having to think about preserving function attributes such as ``__name__`` and ``__module__`` within decorator logic). All view callables in the decorator chain must return a response object implementing :class:`pyramid.interfaces.IResponse` or raise an exception: .. code-block:: python def log_timer(wrapped): def wrapper(context, request): start = time.time() response = wrapped(context, request) duration = time.time() - start response.headers['X-View-Time'] = '%.3f' % (duration,) log.info('view took %.3f seconds', duration) return response return wrapper .. versionchanged:: 1.4a4 Passing an iterable. mapper A Python object or :term:`dotted Python name` which refers to a :term:`view mapper`, or ``None``. By default it is ``None``, which indicates that the view should use the default view mapper. This plug-point is useful for Pyramid extension developers, but it's not very useful for 'civilians' who are just developing stock Pyramid applications. Pay no attention to the man behind the curtain. accept This value represents a match query for one or more mimetypes in the ``Accept`` HTTP request header. If this value is specified, it must be in one of the following forms: a mimetype match token in the form ``text/plain``, a wildcard mimetype match token in the form ``text/*`` or a match-all wildcard mimetype match token in the form ``*/*``. If any of the forms matches the ``Accept`` header of the request, or if the ``Accept`` header isn't set at all in the request, this will match the current view. If this does not match the ``Accept`` header of the request, view matching continues. Predicate Arguments name The :term:`view name`. Read :ref:`traversal_chapter` to understand the concept of a view name. context An object or a :term:`dotted Python name` referring to an interface or class object that the :term:`context` must be an instance of, *or* the :term:`interface` that the :term:`context` must provide in order for this view to be found and called. This predicate is true when the :term:`context` is an instance of the represented class or if the :term:`context` provides the represented interface; it is otherwise false. This argument may also be provided to ``add_view`` as ``for_`` (an older, still-supported spelling). route_name This value must match the ``name`` of a :term:`route configuration` declaration (see :ref:`urldispatch_chapter`) that must match before this view will be called. request_type This value should be an :term:`interface` that the :term:`request` must provide in order for this view to be found and called. This value exists only for backwards compatibility purposes. request_method 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. .. versionchanged:: 1.2 The ability to pass a tuple of items as ``request_method``. Previous versions allowed only a string. 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 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) or values (if the value is a tuple). If any value supplied has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, *and* the value must match the right hand side of the expression (``123``) for the view to "match" the current request. match_param .. versionadded:: 1.2 This value can be a string of the format "key=value" 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 :term:`request` has key/value pairs in its :term:`matchdict` that equal those supplied in the predicate. e.g. ``match_param="action=edit"`` would require the ``action`` parameter in the :term:`matchdict` match the right hand side of the expression (``edit``) for the view to "match" the current request. If the ``match_param`` is a tuple, every key/value pair must match for the predicate to pass. containment This value should be a Python class or :term:`interface` (or a :term:`dotted Python name`) that an object in the :term:`lineage` of the context must provide in order for this view to be found and called. The nodes in your object graph must be "location-aware" to use this feature. See :ref:`location_aware` for more information about location-awareness. xhr This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`request` must possess an ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header that has the value ``XMLHttpRequest`` for this view to be found and called. This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. header This value represents an HTTP header name or a header name/value pair. If the value contains a ``:`` (colon), it will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). The value portion should be a regular expression. If the value does not contain a colon, the entire value will be considered to be the header name (e.g. ``If-Modified-Since``). If the value evaluates to a header name only without a value, the header specified by the name must be present in the request for this predicate to be true. If the value evaluates to a header name/value pair, the header specified by the name must be present in the request *and* the regular expression specified as the value must match the header value. Whether or not the value represents a header name or a header name/value pair, the case of the header name is not significant. path_info This value represents a regular expression pattern that will be tested against the ``PATH_INFO`` WSGI environment variable. If the regex matches, this predicate will be ``True``. check_csrf If specified, this value should be one of ``None``, ``True``, ``False``, or a string representing the 'check name'. If the value is ``True`` or a string, CSRF checking will be performed. If the value is ``False`` or ``None``, CSRF checking will not be performed. If the value provided is a string, that string will be used as the 'check name'. If the value provided is ``True``, ``csrf_token`` will be used as the check name. If CSRF checking is performed, the checked value will be the value of ``request.params[check_name]``. This value will be compared against the value of ``request.session.get_csrf_token()``, and the check will pass if these two values are the same. If the check passes, the associated view will be permitted to execute. If the check fails, the associated view will not be permitted to execute. Note that using this feature requires a :term:`session factory` to have been configured. .. versionadded:: 1.4a2 physical_path If specified, this value should be a string or a tuple representing the :term:`physical path` of the context found via traversal for this predicate to match as true. For example: ``physical_path='/'`` or ``physical_path='/a/b/c'`` or ``physical_path=('', 'a', 'b', 'c')``. This is not a path prefix match or a regex, it's a whole-path match. It's useful when you want to always potentially show a view when some object is traversed to, but you can't be sure about what kind of object it will be, so you can't use the ``context`` predicate. The individual path elements inbetween slash characters or in tuple elements should be the Unicode representation of the name of the resource and should not be encoded in any way. .. versionadded:: 1.4a3 effective_principals If specified, this value should be a :term:`principal` identifier or a sequence of principal identifiers. If the :attr:`pyramid.request.Request.effective_principals` property indicates that every principal named in the argument list is present in the current request, this predicate will return True; otherwise it will return False. For example: ``effective_principals=pyramid.security.Authenticated`` or ``effective_principals=('fred', 'group:admins')``. .. versionadded:: 1.4a4 custom_predicates .. deprecated:: 1.5 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. Each custom predicate callable should accept two arguments: ``context`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of the context and/or the request. The ``predicates`` argument to this method and the ability to register third-party view predicates via :meth:`pyramid.config.Configurator.add_view_predicate` obsoletes this argument, but it is kept around for backwards compatibility. predicates Pass a key/value pair here to use a third-party predicate registered via :meth:`pyramid.config.Configurator.add_view_predicate`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. .. versionadded: 1.4a1 """ if custom_predicates: warnings.warn( ('The "custom_predicates" argument to Configurator.add_view ' 'is deprecated as of Pyramid 1.5. Use ' '"config.add_view_predicate" and use the registered ' 'view predicate as a predicate argument to add_view instead. ' 'See "Adding A Third Party View, Route, or Subscriber ' 'Predicate" in the "Hooks" chapter of the documentation ' 'for more information.'), DeprecationWarning, stacklevel=4 ) view = self.maybe_dotted(view) context = self.maybe_dotted(context) for_ = self.maybe_dotted(for_) containment = self.maybe_dotted(containment) mapper = self.maybe_dotted(mapper) def combine(*decorators): def decorated(view_callable): # reversed() is allows a more natural ordering in the api for decorator in reversed(decorators): view_callable = decorator(view_callable) return view_callable return decorated if is_nonstr_iter(decorator): decorator = combine(*map(self.maybe_dotted, decorator)) else: decorator = self.maybe_dotted(decorator) if not view: if renderer: def view(context, request): return {} else: raise ConfigurationError('"view" was not specified and ' 'no "renderer" specified') if request_type is not None: request_type = self.maybe_dotted(request_type) if not IInterface.providedBy(request_type): raise ConfigurationError( 'request_type must be an interface, not %s' % request_type) if context is None: context = for_ r_context = context if r_context is None: r_context = Interface if not IInterface.providedBy(r_context): r_context = implementedBy(r_context) if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry = self.registry) if accept is not None: accept = accept.lower() introspectables = [] pvals = predicates.copy() pvals.update( dict( xhr=xhr, request_method=request_method, path_info=path_info, request_param=request_param, header=header, accept=accept, containment=containment, request_type=request_type, match_param=match_param, check_csrf=check_csrf, custom=predvalseq(custom_predicates), ) ) def discrim_func(): # We need to defer the discriminator until we know what the phash # is. It can't be computed any sooner because thirdparty # predicates may not yet exist when add_view is called. order, preds, phash = predlist.make(self, **pvals) view_intr.update({'phash':phash, 'order':order, 'predicates':preds}) return ('view', context, name, route_name, phash) discriminator = Deferred(discrim_func) if inspect.isclass(view) and attr: view_desc = 'method %r of %s' % ( attr, self.object_description(view)) else: view_desc = self.object_description(view) tmpl_intr = None view_intr = self.introspectable('views', discriminator, view_desc, 'view') view_intr.update( dict(name=name, context=context, containment=containment, request_param=request_param, request_methods=request_method, route_name=route_name, attr=attr, xhr=xhr, accept=accept, header=header, path_info=path_info, match_param=match_param, check_csrf=check_csrf, callable=view, mapper=mapper, decorator=decorator, ) ) view_intr.update(**predicates) introspectables.append(view_intr) predlist = self.get_predlist('view') def register(permission=permission, renderer=renderer): request_iface = IRequest if route_name is not None: request_iface = self.registry.queryUtility(IRouteRequest, name=route_name) if request_iface is None: # route configuration should have already happened in # phase 2 raise ConfigurationError( 'No route named %s found for view registration' % route_name) if renderer is None: # use default renderer if one exists (reg'd in phase 1) if self.registry.queryUtility(IRendererFactory) is not None: renderer = renderers.RendererHelper( name=None, package=self.package, registry=self.registry ) if permission is None: # intent: will be None if no default permission is registered # (reg'd in phase 1) permission = self.registry.queryUtility(IDefaultPermission) # added by discrim_func above during conflict resolving preds = view_intr['predicates'] order = view_intr['order'] phash = view_intr['phash'] # __no_permission_required__ handled by _secure_view deriver = ViewDeriver( registry=self.registry, permission=permission, predicates=preds, attr=attr, renderer=renderer, wrapper_viewname=wrapper, viewname=name, accept=accept, order=order, phash=phash, package=self.package, mapper=mapper, decorator=decorator, http_cache=http_cache, ) derived_view = deriver(view) derived_view.__discriminator__ = lambda *arg: discriminator # __discriminator__ is used by superdynamic systems # that require it for introspection after manual view lookup; # see also MultiView.__discriminator__ view_intr['derived_callable'] = derived_view registered = self.registry.adapters.registered # A multiviews is a set of views which are registered for # exactly the same context type/request type/name triad. Each # consituent view in a multiview differs only by the # predicates which it possesses. # To find a previously registered view for a context # type/request type/name triad, we need to use the # ``registered`` method of the adapter registry rather than # ``lookup``. ``registered`` ignores interface inheritance # for the required and provided arguments, returning only a # view registered previously with the *exact* triad we pass # in. # We need to do this three times, because we use three # different interfaces as the ``provided`` interface while # doing registrations, and ``registered`` performs exact # matches on all the arguments it receives. old_view = None for view_type in (IView, ISecuredView, IMultiView): old_view = registered((IViewClassifier, request_iface, r_context), view_type, name) if old_view is not None: break isexc = isexception(context) def regclosure(): if hasattr(derived_view, '__call_permissive__'): view_iface = ISecuredView else: view_iface = IView self.registry.registerAdapter( derived_view, (IViewClassifier, request_iface, context), view_iface, name ) if isexc: self.registry.registerAdapter( derived_view, (IExceptionViewClassifier, request_iface, context), view_iface, name) is_multiview = IMultiView.providedBy(old_view) old_phash = getattr(old_view, '__phash__', DEFAULT_PHASH) if old_view is None: # - No component was yet registered for any of our I*View # interfaces exactly; this is the first view for this # triad. regclosure() elif (not is_multiview) and (old_phash == phash): # - A single view component was previously registered with # the same predicate hash as this view; this registration # is therefore an override. regclosure() else: # - A view or multiview was already registered for this # triad, and the new view is not an override. # XXX we could try to be more efficient here and register # a non-secured view for a multiview if none of the # multiview's consituent views have a permission # associated with them, but this code is getting pretty # rough already if is_multiview: multiview = old_view else: multiview = MultiView(name) old_accept = getattr(old_view, '__accept__', None) old_order = getattr(old_view, '__order__', MAX_ORDER) multiview.add(old_view, old_order, old_accept, old_phash) multiview.add(derived_view, order, accept, phash) for view_type in (IView, ISecuredView): # unregister any existing views self.registry.adapters.unregister( (IViewClassifier, request_iface, r_context), view_type, name=name) if isexc: self.registry.adapters.unregister( (IExceptionViewClassifier, request_iface, r_context), view_type, name=name) self.registry.registerAdapter( multiview, (IViewClassifier, request_iface, context), IMultiView, name=name) if isexc: self.registry.registerAdapter( multiview, (IExceptionViewClassifier, request_iface, context), IMultiView, name=name) self.registry._clear_view_lookup_cache() renderer_type = getattr(renderer, 'type', None) # gard against None intrspc = self.introspector if ( renderer_type is not None and tmpl_intr is not None and intrspc is not None and intrspc.get('renderer factories', renderer_type) is not None ): # allow failure of registered template factories to be deferred # until view execution, like other bad renderer factories; if # we tried to relate this to an existing renderer factory # without checking if it the factory actually existed, we'd end # up with a KeyError at startup time, which is inconsistent # with how other bad renderer registrations behave (they throw # a ValueError at view execution time) tmpl_intr.relate('renderer factories', renderer.type) if mapper: mapper_intr = self.introspectable( 'view mappers', discriminator, 'view mapper for %s' % view_desc, 'view mapper' ) mapper_intr['mapper'] = mapper mapper_intr.relate('views', discriminator) introspectables.append(mapper_intr) if route_name: view_intr.relate('routes', route_name) # see add_route if renderer is not None and renderer.name and '.' in renderer.name: # the renderer is a template tmpl_intr = self.introspectable( 'templates', discriminator, renderer.name, 'template' ) tmpl_intr.relate('views', discriminator) tmpl_intr['name'] = renderer.name tmpl_intr['type'] = renderer.type tmpl_intr['renderer'] = renderer introspectables.append(tmpl_intr) if permission is not None: # if a permission exists, register a permission introspectable perm_intr = self.introspectable( 'permissions', permission, permission, 'permission' ) perm_intr['value'] = permission perm_intr.relate('views', discriminator) introspectables.append(perm_intr) self.action(discriminator, register, introspectables=introspectables) @action_method def add_view_predicate(self, name, factory, weighs_more_than=None, weighs_less_than=None): """ .. versionadded:: 1.4 Adds a view predicate factory. The associated view predicate can later be named as a keyword argument to :meth:`pyramid.config.Configurator.add_view` in the ``predicates`` anonyous keyword argument dictionary. ``name`` should be the name of the predicate. It must be a valid Python identifier (it will be used as a keyword argument to ``add_view`` by others). ``factory`` should be a :term:`predicate factory` or :term:`dotted Python name` which refers to a predicate factory. See :ref:`view_and_route_predicates` for more information. """ self._add_predicate( 'view', name, factory, weighs_more_than=weighs_more_than, weighs_less_than=weighs_less_than ) def add_default_view_predicates(self): p = pyramid.config.predicates for (name, factory) in ( ('xhr', p.XHRPredicate), ('request_method', p.RequestMethodPredicate), ('path_info', p.PathInfoPredicate), ('request_param', p.RequestParamPredicate), ('header', p.HeaderPredicate), ('accept', p.AcceptPredicate), ('containment', p.ContainmentPredicate), ('request_type', p.RequestTypePredicate), ('match_param', p.MatchParamPredicate), ('check_csrf', p.CheckCSRFTokenPredicate), ('physical_path', p.PhysicalPathPredicate), ('effective_principals', p.EffectivePrincipalsPredicate), ('custom', p.CustomPredicate), ): self.add_view_predicate(name, factory) def derive_view(self, view, attr=None, renderer=None): """ Create a :term:`view callable` using the function, instance, or class (or :term:`dotted Python name` referring to the same) provided as ``view`` object. .. warning:: This method is typically only used by :app:`Pyramid` framework extension authors, not by :app:`Pyramid` application developers. This is API is useful to framework extenders who create pluggable systems which need to register 'proxy' view callables for functions, instances, or classes which meet the requirements of being a :app:`Pyramid` view callable. For example, a ``some_other_framework`` function in another framework may want to allow a user to supply a view callable, but he may want to wrap the view callable in his own before registering the wrapper as a :app:`Pyramid` view callable. Because a :app:`Pyramid` view callable can be any of a number of valid objects, the framework extender will not know how to call the user-supplied object. Running it through ``derive_view`` normalizes it to a callable which accepts two arguments: ``context`` and ``request``. For example: .. code-block:: python def some_other_framework(user_supplied_view): config = Configurator(reg) proxy_view = config.derive_view(user_supplied_view) def my_wrapper(context, request): do_something_that_mutates(request) return proxy_view(context, request) config.add_view(my_wrapper) The ``view`` object provided should be one of the following: - A function or another non-class callable object that accepts a :term:`request` as a single positional argument and which returns a :term:`response` object. - A function or other non-class callable object that accepts two positional arguments, ``context, request`` and which returns a :term:`response` object. - A class which accepts a single positional argument in its constructor named ``request``, and which has a ``__call__`` method that accepts no arguments that returns a :term:`response` object. - A class which accepts two positional arguments named ``context, request``, and which has a ``__call__`` method that accepts no arguments that returns a :term:`response` object. - A :term:`dotted Python name` which refers to any of the kinds of objects above. This API returns a callable which accepts the arguments ``context, request`` and which returns the result of calling the provided ``view`` object. The ``attr`` keyword argument is most useful when the view object is a class. It names the method that should be used as the callable. If ``attr`` is not provided, the attribute effectively defaults to ``__call__``. See :ref:`class_as_view` for more information. The ``renderer`` keyword argument should be a renderer name. If supplied, it will cause the returned callable to use a :term:`renderer` to convert the user-supplied view result to a :term:`response` object. If a ``renderer`` argument is not supplied, the user-supplied view must itself return a :term:`response` object. """ return self._derive_view(view, attr=attr, renderer=renderer) # b/w compat def _derive_view(self, view, permission=None, predicates=(), attr=None, renderer=None, wrapper_viewname=None, viewname=None, accept=None, order=MAX_ORDER, phash=DEFAULT_PHASH, decorator=None, mapper=None, http_cache=None): view = self.maybe_dotted(view) mapper = self.maybe_dotted(mapper) if isinstance(renderer, string_types): renderer = renderers.RendererHelper( name=renderer, package=self.package, registry = self.registry) if renderer is None: # use default renderer if one exists if self.registry.queryUtility(IRendererFactory) is not None: renderer = renderers.RendererHelper( name=None, package=self.package, registry=self.registry) deriver = ViewDeriver(registry=self.registry, permission=permission, predicates=predicates, attr=attr, renderer=renderer, wrapper_viewname=wrapper_viewname, viewname=viewname, accept=accept, order=order, phash=phash, package=self.package, mapper=mapper, decorator=decorator, http_cache=http_cache) return deriver(view) @viewdefaults @action_method def add_forbidden_view( self, view=None, attr=None, renderer=None, wrapper=None, route_name=None, request_type=None, request_method=None, request_param=None, containment=None, xhr=None, accept=None, header=None, path_info=None, custom_predicates=(), decorator=None, mapper=None, match_param=None, **predicates ): """ Add a forbidden view to the current configuration state. The view will be called when Pyramid or application code raises a :exc:`pyramid.httpexceptions.HTTPForbidden` exception and the set of circumstances implied by the predicates provided are matched. The simplest example is: .. code-block:: python def forbidden(request): return Response('Forbidden', status='403 Forbidden') 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 forbidden view will be invoked. Unlike :meth:`pyramid.config.Configurator.add_view`, this method will raise an exception if passed ``name``, ``permission``, ``context``, ``for_``, or ``http_cache`` keyword arguments. These argument values make no sense in the context of a forbidden view. .. versionadded:: 1.3 """ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'): if arg in predicates: raise ConfigurationError( '%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, wrapper=wrapper, request_type=request_type, request_method=request_method, request_param=request_param, containment=containment, xhr=xhr, accept=accept, header=header, path_info=path_info, custom_predicates=custom_predicates, decorator=decorator, mapper=mapper, match_param=match_param, route_name=route_name, permission=NO_PERMISSION_REQUIRED, attr=attr, renderer=renderer, ) settings.update(predicates) return self.add_view(**settings) set_forbidden_view = add_forbidden_view # deprecated sorta-bw-compat alias @viewdefaults @action_method def add_notfound_view( self, view=None, attr=None, renderer=None, wrapper=None, route_name=None, request_type=None, request_method=None, request_param=None, containment=None, xhr=None, accept=None, header=None, path_info=None, custom_predicates=(), decorator=None, mapper=None, match_param=None, append_slash=False, **predicates ): """ Add a default Not Found View to the current configuration state. The view will be called when Pyramid or application code raises an :exc:`pyramid.httpexceptions.HTTPNotFound` exception (e.g. when a view cannot be found for the request). The simplest example is: .. code-block:: python def notfound(request): return Response('Not Found', status='404 Not Found') 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 view will be invoked. Unlike :meth:`pyramid.config.Configurator.add_view`, this method will raise an exception if passed ``name``, ``permission``, ``context``, ``for_``, or ``http_cache`` keyword arguments. These argument values make no sense in the context of a Not Found View. If ``append_slash`` is ``True``, when this Not Found View is invoked, and the current path info does not end in a slash, the notfound logic will attempt to find a :term:`route` that matches the request's path info suffixed with a slash. If such a route exists, Pyramid will issue a redirect to the URL implied by the route; if it does not, Pyramid will return the result of the view callable provided as ``view``, as normal. If the argument provided as ``append_slash`` is not a boolean but instead implements :class:`~pyramid.interfaces.IResponse`, the append_slash logic will behave as if ``append_slash=True`` was passed, but the provided class will be used as the response class instead of the default :class:`~pyramid.httpexceptions.HTTPFound` response class when a redirect is performed. For example: .. code-block:: python from pyramid.httpexceptions import HTTPMovedPermanently config.add_notfound_view(append_slash=HTTPMovedPermanently) The above means that a redirect to a slash-appended route will be attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound` being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will be used` for the redirect response if a slash-appended route is found. .. versionchanged:: 1.6 .. versionadded:: 1.3 """ for arg in ('name', 'permission', 'context', 'for_', 'http_cache'): if arg in predicates: raise ConfigurationError( '%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, wrapper=wrapper, request_type=request_type, request_method=request_method, request_param=request_param, containment=containment, xhr=xhr, accept=accept, header=header, path_info=path_info, custom_predicates=custom_predicates, decorator=decorator, mapper=mapper, match_param=match_param, route_name=route_name, permission=NO_PERMISSION_REQUIRED, ) settings.update(predicates) if append_slash: view = self._derive_view(view, attr=attr, renderer=renderer) if IResponse.implementedBy(append_slash): view = AppendSlashNotFoundViewFactory( view, redirect_class=append_slash, ) else: view = AppendSlashNotFoundViewFactory(view) settings['view'] = view else: settings['attr'] = attr settings['renderer'] = renderer return self.add_view(**settings) set_notfound_view = add_notfound_view # deprecated sorta-bw-compat alias @action_method def set_view_mapper(self, mapper): """ Setting a :term:`view mapper` makes it possible to make use of :term:`view callable` objects which implement different call signatures than the ones supported by :app:`Pyramid` as described in its narrative documentation. The ``mapper`` argument should be an object implementing :class:`pyramid.interfaces.IViewMapperFactory` or a :term:`dotted Python name` to such an object. The provided ``mapper`` will become the default view mapper to be used by all subsequent :term:`view configuration` registrations. .. seealso:: See also :ref:`using_a_view_mapper`. .. note:: Using the ``default_view_mapper`` argument to the :class:`pyramid.config.Configurator` constructor can be used to achieve the same purpose. """ mapper = self.maybe_dotted(mapper) def register(): self.registry.registerUtility(mapper, IViewMapperFactory) # IViewMapperFactory is looked up as the result of view config # in phase 3 intr = self.introspectable('view mappers', IViewMapperFactory, self.object_description(mapper), 'default view mapper') intr['mapper'] = mapper self.action(IViewMapperFactory, register, order=PHASE1_CONFIG, introspectables=(intr,)) @action_method def add_static_view(self, name, path, **kw): """ Add a view used to render static assets such as images and CSS files. The ``name`` argument is a string representing an application-relative local URL prefix. It may alternately be a full URL. The ``path`` argument is the path on disk where the static files reside. This can be an absolute path, a package-relative path, or a :term:`asset specification`. The ``cache_max_age`` keyword argument is input to set the ``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. The ``permission`` keyword argument is used to specify the :term:`permission` required by a user to execute the static view. By default, it is the string :data:`pyramid.security.NO_PERMISSION_REQUIRED`, a special sentinel which indicates that, even if a :term:`default permission` exists for the current application, the static view should be renderered to completely anonymous users. This default value is permissive because, in most web apps, static assets seldom need protection from viewing. If ``permission`` is specified, the security checking will be performed against the default root factory ACL. Any other keyword arguments sent to ``add_static_view`` are passed on to :meth:`pyramid.config.Configurator.add_route` (e.g. ``factory``, perhaps to define a custom factory with a custom ACL for this static view). *Usage* The ``add_static_view`` function is typically used in conjunction with the :meth:`pyramid.request.Request.static_url` method. ``add_static_view`` adds a view which renders a static asset when some URL is visited; :meth:`pyramid.request.Request.static_url` generates a URL to that asset. The ``name`` argument to ``add_static_view`` is usually a simple URL prefix (e.g. ``'images'``). When this is the case, the :meth:`pyramid.request.Request.static_url` API will generate a URL which points to a Pyramid view, which will serve up a set of assets that live in the package itself. For example: .. code-block:: python add_static_view('images', 'mypackage:images/') Code that registers such a view can generate URLs to the view via :meth:`pyramid.request.Request.static_url`: .. code-block:: python request.static_url('mypackage:images/logo.png') When ``add_static_view`` is called with a ``name`` argument that represents a URL prefix, as it is above, subsequent calls to :meth:`pyramid.request.Request.static_url` with paths that start with the ``path`` argument passed to ``add_static_view`` will generate a URL something like ``http:///images/logo.png``, which will cause the ``logo.png`` file in the ``images`` subdirectory of the ``mypackage`` package to be served. ``add_static_view`` can alternately be used with a ``name`` argument which is a *URL*, causing static assets to be served from an external webserver. This happens when the ``name`` argument is a fully qualified URL (e.g. starts with ``http://`` or similar). In this mode, the ``name`` is used as the prefix of the full URL when generating a URL using :meth:`pyramid.request.Request.static_url`. Furthermore, if a protocol-relative URL (e.g. ``//example.com/images``) is used as the ``name`` argument, the generated URL will use the protocol of the request (http or https, respectively). For example, if ``add_static_view`` is called like so: .. code-block:: python add_static_view('http://example.com/images', 'mypackage:images/') Subsequently, the URLs generated by :meth:`pyramid.request.Request.static_url` for that static view will be prefixed with ``http://example.com/images`` (the external webserver listening on ``example.com`` must be itself configured to respond properly to such a request.): .. code-block:: python static_url('mypackage:images/logo.png', request) See :ref:`static_assets_section` for more information. """ spec = self._make_spec(path) info = self._get_static_info() info.add(self, name, spec, **kw) def add_cache_buster(self, path, cachebust, explicit=False): """ Add a cache buster to a set of files on disk. The ``path`` should be the path on disk where the static files reside. This can be an absolute path, a package-relative path, or a :term:`asset specification`. The ``cachebust`` 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 must be an object which implements :class:`~pyramid.interfaces.ICacheBuster`. If ``explicit`` is set to ``True`` then the ``path`` for the cache buster will be matched based on the ``rawspec`` instead of the ``pathspec`` as defined in the :class:`~pyramid.interfaces.ICacheBuster` interface. Default: ``False``. """ spec = self._make_spec(path) info = self._get_static_info() info.add_cache_buster(self, spec, cachebust, explicit=explicit) def _get_static_info(self): info = self.registry.queryUtility(IStaticURLInfo) if info is None: info = StaticURLInfo() self.registry.registerUtility(info, IStaticURLInfo) return info def isexception(o): if IInterface.providedBy(o): if IException.isEqualOrExtendedBy(o): return True return ( isinstance(o, Exception) or (inspect.isclass(o) and (issubclass(o, Exception))) ) @implementer(IStaticURLInfo) class StaticURLInfo(object): def __init__(self): self.registrations = [] self.cache_busters = [] def generate(self, path, request, **kw): for (url, spec, route_name) in self.registrations: if path.startswith(spec): subpath = path[len(spec):] if WIN: # pragma: no cover subpath = subpath.replace('\\', '/') # windows if self.cache_busters: subpath, kw = self._bust_asset_path( request, spec, subpath, kw) if url is None: kw['subpath'] = subpath return request.route_url(route_name, **kw) else: app_url, scheme, host, port, qs, anchor = \ parse_url_overrides(kw) parsed = url_parse(url) if not parsed.scheme: url = urlparse.urlunparse(parsed._replace( scheme=request.environ['wsgi.url_scheme'])) subpath = url_quote(subpath) result = urljoin(url, subpath) return result + qs + anchor raise ValueError('No static URL definition matching %s' % path) def add(self, config, name, spec, **extra): # This feature only allows for the serving of a directory and # the files contained within, not of a single asset; # appending a slash here if the spec doesn't have one is # required for proper prefix matching done in ``generate`` # (``subpath = path[len(spec):]``). if os.path.isabs(spec): # FBO windows sep = os.sep else: 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 # convenience: a name that is a url is required to end in a # slash, so that ``urljoin(name, subpath))`` will work above # when the name is a URL, and it doesn't hurt things for it to # have a name that ends in a slash if it's used as a route # name instead of a URL. if not name.endswith('/'): # make sure it ends with a slash name = name + '/' if url_parse(name).netloc: # it's a URL # url, spec, route_name url = name route_name = None else: # it's a view name url = None cache_max_age = extra.pop('cache_max_age', None) # create a view view = static_view(spec, cache_max_age=cache_max_age, use_subpath=True) # Mutate extra to allow factory, etc to be passed through here. # Treat permission specially because we'd like to default to # permissiveness (see docs of config.add_static_view). permission = extra.pop('permission', None) if permission is None: permission = NO_PERMISSION_REQUIRED context = extra.pop('context', None) if context is None: context = extra.pop('for_', None) renderer = extra.pop('renderer', None) # register a route using the computed view, permission, and # pattern, plus any extras passed to us via add_static_view pattern = "%s*subpath" % name # name already ends with slash if config.route_prefix: route_name = '__%s/%s' % (config.route_prefix, name) else: route_name = '__%s' % name config.add_route(route_name, pattern, **extra) config.add_view( route_name=route_name, view=view, permission=permission, context=context, renderer=renderer, ) def register(): registrations = self.registrations names = [ t[0] for t in registrations ] if name in names: idx = names.index(name) registrations.pop(idx) # url, spec, route_name registrations.append((url, spec, route_name)) intr = config.introspectable('static views', name, 'static view for %r' % name, 'static view') intr['name'] = name intr['spec'] = spec config.action(None, callable=register, introspectables=(intr,)) def add_cache_buster(self, config, spec, cachebust, explicit=False): # ensure the spec always has a trailing slash as we only support # adding cache busters to folders, not files if os.path.isabs(spec): # FBO windows sep = os.sep else: sep = '/' if not spec.endswith(sep) and not spec.endswith(':'): spec = spec + sep def register(): if config.registry.settings.get('pyramid.prevent_cachebust'): return cache_busters = self.cache_busters # find duplicate cache buster (old_idx) # and insertion location (new_idx) new_idx, old_idx = len(cache_busters), None for idx, (spec_, cb_, explicit_) in enumerate(cache_busters): # if we find an identical (spec, explicit) then use it if spec == spec_ and explicit == explicit_: old_idx = new_idx = idx break # past all explicit==False specs then add to the end elif not explicit and explicit_: new_idx = idx break # explicit matches and spec is shorter elif explicit == explicit_ and len(spec) < len(spec_): new_idx = idx break if old_idx is not None: cache_busters.pop(old_idx) cache_busters.insert(new_idx, (spec, cachebust, explicit)) intr = config.introspectable('cache busters', spec, 'cache buster for %r' % spec, 'cache buster') intr['cachebust'] = cachebust intr['path'] = spec intr['explicit'] = explicit config.action(None, callable=register, introspectables=(intr,)) def _bust_asset_path(self, request, spec, subpath, kw): registry = request.registry pkg_name, pkg_subpath = resolve_asset_spec(spec) rawspec = None if pkg_name is not None: pathspec = '{0}:{1}{2}'.format(pkg_name, pkg_subpath, subpath) overrides = registry.queryUtility(IPackageOverrides, name=pkg_name) if overrides is not None: resource_name = posixpath.join(pkg_subpath, subpath) sources = overrides.filtered_sources(resource_name) for source, filtered_path in sources: rawspec = source.get_path(filtered_path) if hasattr(source, 'pkg_name'): rawspec = '{0}:{1}'.format(source.pkg_name, rawspec) break else: pathspec = pkg_subpath + subpath if rawspec is None: rawspec = pathspec kw['pathspec'] = pathspec kw['rawspec'] = rawspec for spec_, cachebust, explicit in reversed(self.cache_busters): if ( (explicit and rawspec.startswith(spec_)) or (not explicit and pathspec.startswith(spec_)) ): subpath, kw = cachebust(request, subpath, kw) break return subpath, kw pyramid-1.6/pyramid/config/zca.py0000644000076500000240000000160112234375161017615 0ustar michaelstaff00000000000000from pyramid.threadlocal import get_current_registry class ZCAConfiguratorMixin(object): def hook_zca(self): """ Call :func:`zope.component.getSiteManager.sethook` with the argument :data:`pyramid.threadlocal.get_current_registry`, causing the :term:`Zope Component Architecture` 'global' APIs such as :func:`zope.component.getSiteManager`, :func:`zope.component.getAdapter` and others to use the :app:`Pyramid` :term:`application registry` rather than the Zope 'global' registry.""" from zope.component import getSiteManager getSiteManager.sethook(get_current_registry) def unhook_zca(self): """ Call :func:`zope.component.getSiteManager.reset` to undo the action of :meth:`pyramid.config.Configurator.hook_zca`.""" from zope.component import getSiteManager getSiteManager.reset() pyramid-1.6/pyramid/decorator.py0000644000076500000240000000206712524266531017566 0ustar michaelstaff00000000000000import functools class reify(object): """ Use as a class method decorator. It operates almost exactly like the Python ``@property`` decorator, but it puts the result of the method it decorates into the instance dict after the first call, effectively replacing the function it decorates with an instance variable. It is, in Python parlance, a non-data descriptor. An example: .. code-block:: python class Foo(object): @reify def jammy(self): print('jammy called') return 1 And usage of Foo: >>> f = Foo() >>> v = f.jammy 'jammy called' >>> print(v) 1 >>> f.jammy 1 >>> # jammy func not called the second time; it replaced itself with 1 """ def __init__(self, wrapped): self.wrapped = wrapped functools.update_wrapper(self, wrapped) def __get__(self, inst, objtype=None): if inst is None: return self val = self.wrapped(inst) setattr(inst, self.wrapped.__name__, val) return val pyramid-1.6/pyramid/encode.py0000644000076500000240000000463712520062551017036 0ustar michaelstaff00000000000000from pyramid.compat import ( text_type, binary_type, is_nonstr_iter, url_quote as _url_quote, url_quote_plus as _quote_plus, ) def url_quote(val, safe=''): # bw compat api cls = val.__class__ if cls is text_type: val = val.encode('utf-8') elif cls is not binary_type: val = str(val).encode('utf-8') return _url_quote(val, safe=safe) def urlencode(query, doseq=True): """ An alternate implementation of Python's stdlib `urllib.urlencode function `_ which accepts unicode keys and values within the ``query`` dict/sequence; all Unicode keys and values are first converted to UTF-8 before being used to compose the query string. The value of ``query`` must be a sequence of two-tuples representing key/value pairs *or* an object (often a dictionary) with an ``.items()`` method that returns a sequence of two-tuples representing key/value pairs. For minimal calling convention backwards compatibility, this version of urlencode accepts *but ignores* a second argument conventionally named ``doseq``. The Python stdlib version behaves differently when ``doseq`` is False and when a sequence is presented as one of the values. This version always behaves in the ``doseq=True`` mode, no matter what the value of the second argument. See the Python stdlib documentation for ``urllib.urlencode`` for more information. .. versionchanged:: 1.5 In a key/value pair, if the value is ``None`` then it will be dropped from the resulting output. """ try: # presumed to be a dictionary query = query.items() except AttributeError: pass result = '' prefix = '' for (k, v) in query: k = quote_plus(k) if is_nonstr_iter(v): for x in v: x = quote_plus(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' elif v is None: result += '%s%s=' % (prefix, k) else: v = quote_plus(v) result += '%s%s=%s' % (prefix, k, v) prefix = '&' return result # bw compat api (dnr) def quote_plus(val, safe=''): cls = val.__class__ if cls is text_type: val = val.encode('utf-8') elif cls is not binary_type: val = str(val).encode('utf-8') return _quote_plus(val, safe=safe) pyramid-1.6/pyramid/events.py0000644000076500000240000002112412520062551017073 0ustar michaelstaff00000000000000import venusian from zope.interface import ( implementer, Interface ) from pyramid.interfaces import ( IContextFound, INewRequest, INewResponse, IApplicationCreated, IBeforeRender, ) class subscriber(object): """ Decorator activated via a :term:`scan` which treats the function being decorated as an event subscriber for the set of interfaces passed as ``*ifaces`` and the set of predicate terms passed as ``**predicates`` to the decorator constructor. For example: .. code-block:: python from pyramid.events import NewRequest from pyramid.events import subscriber @subscriber(NewRequest) def mysubscriber(event): event.request.foo = 1 More than one event type can be passed as a constructor argument. The decorated subscriber will be called for each event type. .. code-block:: python from pyramid.events import NewRequest, NewResponse from pyramid.events import subscriber @subscriber(NewRequest, NewResponse) def mysubscriber(event): print(event) When the ``subscriber`` decorator is used without passing an arguments, the function it decorates is called for every event sent: .. code-block:: python from pyramid.events import subscriber @subscriber() def mysubscriber(event): print(event) This method will have no effect until a :term:`scan` is performed against the package or module which contains it, ala: .. code-block:: python from pyramid.config import Configurator config = Configurator() config.scan('somepackage_containing_subscribers') Any ``**predicate`` arguments will be passed along to :meth:`pyramid.config.Configurator.add_subscriber`. See :ref:`subscriber_predicates` for a description of how predicates can narrow the set of circumstances in which a subscriber will be called. """ venusian = venusian # for unit testing def __init__(self, *ifaces, **predicates): self.ifaces = ifaces self.predicates = predicates def register(self, scanner, name, wrapped): config = scanner.config for iface in self.ifaces or (Interface,): config.add_subscriber(wrapped, iface, **self.predicates) def __call__(self, wrapped): self.venusian.attach(wrapped, self.register, category='pyramid') return wrapped @implementer(INewRequest) class NewRequest(object): """ An instance of this class is emitted as an :term:`event` whenever :app:`Pyramid` begins to process a new request. The event instance has an attribute, ``request``, which is a :term:`request` object. This event class implements the :class:`pyramid.interfaces.INewRequest` interface.""" def __init__(self, request): self.request = request @implementer(INewResponse) class NewResponse(object): """ An instance of this class is emitted as an :term:`event` whenever any :app:`Pyramid` :term:`view` or :term:`exception view` returns a :term:`response`. The instance has two attributes:``request``, which is the request which caused the response, and ``response``, which is the response object returned by a view or renderer. If the ``response`` was generated by an :term:`exception view`, the request will have an attribute named ``exception``, which is the exception object which caused the exception view to be executed. If the response was generated by a 'normal' view, this attribute of the request will be ``None``. This event will not be generated if a response cannot be created due to an exception that is not caught by an exception view (no response is created under this circumstace). This class implements the :class:`pyramid.interfaces.INewResponse` interface. .. note:: Postprocessing a response is usually better handled in a WSGI :term:`middleware` component than in subscriber code that is called by a :class:`pyramid.interfaces.INewResponse` event. The :class:`pyramid.interfaces.INewResponse` event exists almost purely for symmetry with the :class:`pyramid.interfaces.INewRequest` event. """ def __init__(self, request, response): self.request = request self.response = response @implementer(IContextFound) class ContextFound(object): """ An instance of this class is emitted as an :term:`event` after the :app:`Pyramid` :term:`router` finds a :term:`context` object (after it performs traversal) but before any view code is executed. The instance has an attribute, ``request``, which is the request object generated by :app:`Pyramid`. Notably, the request object will have an attribute named ``context``, which is the context that will be provided to the view which will eventually be called, as well as other attributes attached by context-finding code. This class implements the :class:`pyramid.interfaces.IContextFound` interface. .. note:: As of :app:`Pyramid` 1.0, for backwards compatibility purposes, this event may also be imported as :class:`pyramid.events.AfterTraversal`. """ def __init__(self, request): self.request = request AfterTraversal = ContextFound # b/c as of 1.0 @implementer(IApplicationCreated) class ApplicationCreated(object): """ An instance of this class is emitted as an :term:`event` when the :meth:`pyramid.config.Configurator.make_wsgi_app` is called. The instance has an attribute, ``app``, which is an instance of the :term:`router` that will handle WSGI requests. This class implements the :class:`pyramid.interfaces.IApplicationCreated` interface. .. note:: For backwards compatibility purposes, this class can also be imported as :class:`pyramid.events.WSGIApplicationCreatedEvent`. This was the name of the event class before :app:`Pyramid` 1.0. """ def __init__(self, app): self.app = app self.object = app WSGIApplicationCreatedEvent = ApplicationCreated # b/c (as of 1.0) @implementer(IBeforeRender) class BeforeRender(dict): """ Subscribers to this event may introspect and modify the set of :term:`renderer globals` before they are passed to a :term:`renderer`. This event object iself has a dictionary-like interface that can be used for this purpose. For example:: from pyramid.events import subscriber from pyramid.events import BeforeRender @subscriber(BeforeRender) def add_global(event): event['mykey'] = 'foo' An object of this type is sent as an event just before a :term:`renderer` is invoked. If a subscriber adds a key via ``__setitem__`` that already exists in the renderer globals dictionary, it will overwrite the older value there. This can be problematic because event subscribers to the BeforeRender event do not possess any relative ordering. For maximum interoperability with other third-party subscribers, if you write an event subscriber meant to be used as a BeforeRender subscriber, your subscriber code will need to ensure no value already exists in the renderer globals dictionary before setting an overriding value (which can be done using ``.get`` or ``__contains__`` of the event object). The dictionary returned from the view is accessible through the :attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender` event. Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from your view callable, like so:: from pyramid.view import view_config @view_config(renderer='some_renderer') def myview(request): return {'mykey': 'somevalue', 'mykey2': 'somevalue2'} :attr:`rendering_val` can be used to access these values from the :class:`~pyramid.events.BeforeRender` object:: from pyramid.events import subscriber from pyramid.events import BeforeRender @subscriber(BeforeRender) def read_return(event): # {'mykey': 'somevalue'} is returned from the view print(event.rendering_val['mykey']) In other words, :attr:`rendering_val` is the (non-system) value returned by a view or passed to ``render*`` as ``value``. This feature is new in Pyramid 1.2. For a description of the values present in the renderer globals dictionary, see :ref:`renderer_system_values`. .. seealso:: See also :class:`pyramid.interfaces.IBeforeRender`. """ def __init__(self, system, rendering_val=None): dict.__init__(self, system) self.rendering_val = rendering_val pyramid-1.6/pyramid/exceptions.py0000644000076500000240000000761412642137120017760 0ustar michaelstaff00000000000000from pyramid.httpexceptions import ( HTTPBadRequest, HTTPNotFound, HTTPForbidden, ) NotFound = HTTPNotFound # bw compat Forbidden = HTTPForbidden # bw compat CR = '\n' class BadCSRFToken(HTTPBadRequest): """ This exception indicates the request has failed cross-site request forgery token validation. """ title = 'Bad CSRF Token' explanation = ( 'Access is denied. This server can not verify that your cross-site ' 'request forgery token belongs to your login session. Either you ' 'supplied the wrong cross-site request forgery token or your session ' 'no longer exists. This may be due to session timeout or because ' 'browser is not supplying the credentials required, as can happen ' 'when the browser has cookies turned off.') class PredicateMismatch(HTTPNotFound): """ This exception is raised by multiviews when no view matches all given predicates. This exception subclasses the :class:`HTTPNotFound` exception for a specific reason: if it reaches the main exception handler, it should be treated as :class:`HTTPNotFound`` by any exception view registrations. Thus, typically, this exception will not be seen publicly. However, this exception will be raised if the predicates of all views configured to handle another exception context cannot be successfully matched. For instance, if a view is configured to handle a context of ``HTTPForbidden`` and the configured with additional predicates, then :class:`PredicateMismatch` will be raised if: * An original view callable has raised :class:`HTTPForbidden` (thus invoking an exception view); and * The given request fails to match all predicates for said exception view associated with :class:`HTTPForbidden`. The same applies to any type of exception being handled by an exception view. """ class URLDecodeError(UnicodeDecodeError): """ This exception is raised when :app:`Pyramid` cannot successfully decode a URL or a URL path segment. This exception behaves just like the Python builtin :exc:`UnicodeDecodeError`. It is a subclass of the builtin :exc:`UnicodeDecodeError` exception only for identity purposes, mostly so an exception view can be registered when a URL cannot be decoded. """ class ConfigurationError(Exception): """ Raised when inappropriate input values are supplied to an API method of a :term:`Configurator`""" class ConfigurationConflictError(ConfigurationError): """ Raised when a configuration conflict is detected during action processing""" def __init__(self, conflicts): self._conflicts = conflicts def __str__(self): r = ["Conflicting configuration actions"] items = sorted(self._conflicts.items()) for discriminator, infos in items: r.append(" For: %s" % (discriminator, )) for info in infos: for line in str(info).rstrip().split(CR): r.append(" "+line) return CR.join(r) class ConfigurationExecutionError(ConfigurationError): """An error occurred during execution of a configuration action """ def __init__(self, etype, evalue, info): self.etype, self.evalue, self.info = etype, evalue, info def __str__(self): return "%s: %s\n in:\n %s" % (self.etype, self.evalue, self.info) class CyclicDependencyError(Exception): """ The exception raised when the Pyramid topological sorter detects a cyclic dependency.""" def __init__(self, cycles): self.cycles = cycles def __str__(self): L = [] cycles = self.cycles for cycle in cycles: dependent = cycle dependees = cycles[cycle] L.append('%r sorts before %r' % (dependent, dependees)) msg = 'Implicit ordering cycle:' + '; '.join(L) return msg pyramid-1.6/pyramid/httpexceptions.py0000644000076500000240000010602512642137120020654 0ustar michaelstaff00000000000000""" HTTP Exceptions --------------- This module contains Pyramid HTTP exception classes. Each class relates to a single HTTP status code. Each class is a subclass of the :class:`~HTTPException`. Each exception class is also a :term:`response` object. Each exception class has a status code according to :rfc:`2068`: codes with 100-300 are not really errors; 400s are client errors, and 500s are server errors. Exception HTTPException HTTPSuccessful * 200 - HTTPOk * 201 - HTTPCreated * 202 - HTTPAccepted * 203 - HTTPNonAuthoritativeInformation * 204 - HTTPNoContent * 205 - HTTPResetContent * 206 - HTTPPartialContent HTTPRedirection * 300 - HTTPMultipleChoices * 301 - HTTPMovedPermanently * 302 - HTTPFound * 303 - HTTPSeeOther * 304 - HTTPNotModified * 305 - HTTPUseProxy * 307 - HTTPTemporaryRedirect HTTPError HTTPClientError * 400 - HTTPBadRequest * 401 - HTTPUnauthorized * 402 - HTTPPaymentRequired * 403 - HTTPForbidden * 404 - HTTPNotFound * 405 - HTTPMethodNotAllowed * 406 - HTTPNotAcceptable * 407 - HTTPProxyAuthenticationRequired * 408 - HTTPRequestTimeout * 409 - HTTPConflict * 410 - HTTPGone * 411 - HTTPLengthRequired * 412 - HTTPPreconditionFailed * 413 - HTTPRequestEntityTooLarge * 414 - HTTPRequestURITooLong * 415 - HTTPUnsupportedMediaType * 416 - HTTPRequestRangeNotSatisfiable * 417 - HTTPExpectationFailed * 422 - HTTPUnprocessableEntity * 423 - HTTPLocked * 424 - HTTPFailedDependency * 428 - HTTPPreconditionRequired * 429 - HTTPTooManyRequests * 431 - HTTPRequestHeaderFieldsTooLarge HTTPServerError * 500 - HTTPInternalServerError * 501 - HTTPNotImplemented * 502 - HTTPBadGateway * 503 - HTTPServiceUnavailable * 504 - HTTPGatewayTimeout * 505 - HTTPVersionNotSupported * 507 - HTTPInsufficientStorage HTTP exceptions are also :term:`response` objects, thus they accept most of the same parameters that can be passed to a regular :class:`~pyramid.response.Response`. Each HTTP exception also has the following attributes: ``code`` the HTTP status code for the exception ``title`` remainder of the status line (stuff after the code) ``explanation`` a plain-text explanation of the error message that is not subject to environment or header substitutions; it is accessible in the template via ${explanation} ``detail`` a plain-text message customization that is not subject to environment or header substitutions; accessible in the template via ${detail} ``body_template`` a ``String.template``-format content fragment used for environment and header substitution; the default template includes both the explanation and further detail provided in the message. Each HTTP exception accepts the following parameters, any others will be forwarded to its :class:`~pyramid.response.Response` superclass: ``detail`` a plain-text override of the default ``detail`` ``headers`` a list of (k,v) header pairs ``comment`` a plain-text additional information which is usually stripped/hidden for end-users ``body_template`` a ``string.Template`` object containing a content fragment in HTML that frames the explanation and further detail ``body`` a string that will override the ``body_template`` and be used as the body of the response. Substitution of response headers into template values is always performed. Substitution of WSGI environment values is performed if a ``request`` is passed to the exception's constructor. The subclasses of :class:`~_HTTPMove` (:class:`~HTTPMultipleChoices`, :class:`~HTTPMovedPermanently`, :class:`~HTTPFound`, :class:`~HTTPSeeOther`, :class:`~HTTPUseProxy` and :class:`~HTTPTemporaryRedirect`) are redirections that require a ``Location`` field. Reflecting this, these subclasses have one additional keyword argument: ``location``, which indicates the location to which to redirect. """ from string import Template from zope.interface import implementer from webob import html_escape as _html_escape from pyramid.compat import ( class_types, text_type, binary_type, text_, ) from pyramid.interfaces import IExceptionResponse from pyramid.response import Response def _no_escape(value): if value is None: return '' if not isinstance(value, text_type): if hasattr(value, '__unicode__'): value = value.__unicode__() if isinstance(value, binary_type): value = text_(value, 'utf-8') else: value = text_type(value) return value @implementer(IExceptionResponse) class HTTPException(Response, Exception): ## You should set in subclasses: # code = 200 # title = 'OK' # explanation = 'why this happens' # body_template_obj = Template('response template') # # This class itself uses the error code "520" with the error message/title # of "Unknown Error". This is not an RFC standard, however it is # implemented in practice. Sub-classes should be overriding the default # values and 520 should not be seen in the wild from Pyramid applications. # Due to changes in WebOb, a code of "None" is not valid, and WebOb due to # more strict error checking rejects it now. # differences from webob.exc.WSGIHTTPException: # # - doesn't use "strip_tags" (${br} placeholder for
, no other html # in default body template) # # - __call__ never generates a new Response, it always mutates self # # - explicitly sets self.message = detail to prevent whining by Python # 2.6.5+ access of Exception.message # # - its base class of HTTPException is no longer a Python 2.4 compatibility # shim; it's purely a base class that inherits from Exception. This # implies that this class' ``exception`` property always returns # ``self`` (it exists only for bw compat at this point). # # - documentation improvements (Pyramid-specific docstrings where necessary) # code = 520 title = 'Unknown Error' explanation = '' body_template_obj = Template('''\ ${explanation}${br}${br} ${detail} ${html_comment} ''') plain_template_obj = Template('''\ ${status} ${body}''') html_template_obj = Template('''\ ${status}

${status}

${body} ''') ## Set this to True for responses that should have no request body empty_body = False def __init__(self, detail=None, headers=None, comment=None, body_template=None, **kw): status = '%s %s' % (self.code, self.title) Response.__init__(self, status=status, **kw) Exception.__init__(self, detail) self.detail = self.message = detail if headers: self.headers.extend(headers) self.comment = comment if body_template is not None: self.body_template = body_template self.body_template_obj = Template(body_template) if self.empty_body: del self.content_type del self.content_length def __str__(self): return self.detail or self.explanation def prepare(self, environ): if not self.body and not self.empty_body: html_comment = '' comment = self.comment or '' accept = environ.get('HTTP_ACCEPT', '') if accept and 'html' in accept or '*/*' in accept: self.content_type = 'text/html' escape = _html_escape page_template = self.html_template_obj br = '
' if comment: html_comment = '' % escape(comment) else: self.content_type = 'text/plain' escape = _no_escape page_template = self.plain_template_obj br = '\n' if comment: html_comment = escape(comment) args = { 'br':br, 'explanation': escape(self.explanation), 'detail': escape(self.detail or ''), 'comment': escape(comment), 'html_comment':html_comment, } body_tmpl = self.body_template_obj if HTTPException.body_template_obj is not body_tmpl: # Custom template; add headers to args for k, v in environ.items(): if (not k.startswith('wsgi.')) and ('.' in k): # omit custom environ variables, stringifying them may # trigger code that should not be executed here; see # https://github.com/Pylons/pyramid/issues/239 continue args[k] = escape(v) for k, v in self.headers.items(): args[k.lower()] = escape(v) body = body_tmpl.substitute(args) page = page_template.substitute(status=self.status, body=body) if isinstance(page, text_type): page = page.encode(self.charset) self.app_iter = [page] self.body = page @property def wsgi_response(self): # bw compat only return self exception = wsgi_response # bw compat only def __call__(self, environ, start_response): # differences from webob.exc.WSGIHTTPException # # - does not try to deal with HEAD requests # # - does not manufacture a new response object when generating # the default response # self.prepare(environ) return Response.__call__(self, environ, start_response) WSGIHTTPException = HTTPException # b/c post 1.5 class HTTPError(HTTPException): """ base class for exceptions with status codes in the 400s and 500s This is an exception which indicates that an error has occurred, and that any work in progress should not be committed. """ class HTTPRedirection(HTTPException): """ base class for exceptions with status codes in the 300s (redirections) This is an abstract base class for 3xx redirection. It indicates that further action needs to be taken by the user agent in order to fulfill the request. It does not necessarly signal an error condition. """ class HTTPSuccessful(HTTPException): """ Base class for exceptions with status codes in the 200s (successful responses) """ ############################################################ ## 2xx success ############################################################ class HTTPOk(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` Indicates that the request has succeeded. code: 200, title: OK """ code = 200 title = 'OK' class HTTPCreated(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that request has been fulfilled and resulted in a new resource being created. code: 201, title: Created """ code = 201 title = 'Created' class HTTPAccepted(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that the request has been accepted for processing, but the processing has not been completed. code: 202, title: Accepted """ code = 202 title = 'Accepted' explanation = 'The request is accepted for processing.' class HTTPNonAuthoritativeInformation(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that the returned metainformation in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy. code: 203, title: Non-Authoritative Information """ code = 203 title = 'Non-Authoritative Information' class HTTPNoContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that the server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. code: 204, title: No Content """ code = 204 title = 'No Content' empty_body = True class HTTPResetContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that the server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent. code: 205, title: Reset Content """ code = 205 title = 'Reset Content' empty_body = True class HTTPPartialContent(HTTPSuccessful): """ subclass of :class:`~HTTPSuccessful` This indicates that the server has fulfilled the partial GET request for the resource. code: 206, title: Partial Content """ code = 206 title = 'Partial Content' ## FIXME: add 207 Multi-Status (but it's complicated) ############################################################ ## 3xx redirection ############################################################ class _HTTPMove(HTTPRedirection): """ redirections which require a Location field Since a 'Location' header is a required attribute of 301, 302, 303, 305 and 307 (but not 304), this base class provides the mechanics to make this easy. You must provide a ``location`` keyword argument. """ # differences from webob.exc._HTTPMove: # # - ${location} isn't wrapped in an tag in body # # - location keyword arg defaults to '' # # - location isn't prepended with req.path_url when adding it as # a header # # - ``location`` is first keyword (and positional) argument # # - ``add_slash`` argument is no longer accepted: code that passes # add_slash argument to the constructor will receive an exception. explanation = 'The resource has been moved to' body_template_obj = Template('''\ ${explanation} ${location}; you should be redirected automatically. ${detail} ${html_comment}''') def __init__(self, location='', detail=None, headers=None, comment=None, body_template=None, **kw): if location is None: raise ValueError("HTTP redirects need a location to redirect to.") super(_HTTPMove, self).__init__( detail=detail, headers=headers, comment=comment, body_template=body_template, location=location, **kw) class HTTPMultipleChoices(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user can select a preferred representation and redirect its request to that location. code: 300, title: Multiple Choices """ code = 300 title = 'Multiple Choices' class HTTPMovedPermanently(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs. code: 301, title: Moved Permanently """ code = 301 title = 'Moved Permanently' class HTTPFound(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the requested resource resides temporarily under a different URI. code: 302, title: Found """ code = 302 title = 'Found' explanation = 'The resource was found at' # This one is safe after a POST (the redirected location will be # retrieved with GET): class HTTPSeeOther(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. code: 303, title: See Other """ code = 303 title = 'See Other' class HTTPNotModified(HTTPRedirection): """ subclass of :class:`~HTTPRedirection` This indicates that if the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. code: 304, title: Not Modified """ # FIXME: this should include a date or etag header code = 304 title = 'Not Modified' empty_body = True class HTTPUseProxy(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the requested resource MUST be accessed through the proxy given by the Location field. code: 305, title: Use Proxy """ # Not a move, but looks a little like one code = 305 title = 'Use Proxy' explanation = ( 'The resource must be accessed through a proxy located at') class HTTPTemporaryRedirect(_HTTPMove): """ subclass of :class:`~_HTTPMove` This indicates that the requested resource resides temporarily under a different URI. code: 307, title: Temporary Redirect """ code = 307 title = 'Temporary Redirect' ############################################################ ## 4xx client error ############################################################ class HTTPClientError(HTTPError): """ base class for the 400s, where the client is in error This is an error condition in which the client is presumed to be in-error. This is an expected problem, and thus is not considered a bug. A server-side traceback is not warranted. Unless specialized, this is a '400 Bad Request' """ class HTTPBadRequest(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the body or headers failed validity checks, preventing the server from being able to continue processing. code: 400, title: Bad Request """ code = 400 title = 'Bad Request' explanation = ('The server could not comply with the request since ' 'it is either malformed or otherwise incorrect.') class HTTPUnauthorized(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the request requires user authentication. code: 401, title: Unauthorized """ code = 401 title = 'Unauthorized' explanation = ( 'This server could not verify that you are authorized to ' 'access the document you requested. Either you supplied the ' 'wrong credentials (e.g., bad password), or your browser ' 'does not understand how to supply the credentials required.') class HTTPPaymentRequired(HTTPClientError): """ subclass of :class:`~HTTPClientError` code: 402, title: Payment Required """ code = 402 title = 'Payment Required' explanation = ('Access was denied for financial reasons.') class HTTPForbidden(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server understood the request, but is refusing to fulfill it. code: 403, title: Forbidden Raise this exception within :term:`view` code to immediately return the :term:`forbidden view` to the invoking user. Usually this is a basic ``403`` page, but the forbidden view can be customized as necessary. See :ref:`changing_the_forbidden_view`. A ``Forbidden`` exception will be the ``context`` of a :term:`Forbidden View`. This exception's constructor treats two arguments specially. The first argument, ``detail``, should be a string. The value of this string will be used as the ``message`` attribute of the exception object. The second special keyword argument, ``result`` is usually an instance of :class:`pyramid.security.Denied` or :class:`pyramid.security.ACLDenied` each of which indicates a reason for the forbidden error. However, ``result`` is also permitted to be just a plain boolean ``False`` object or ``None``. The ``result`` value will be used as the ``result`` attribute of the exception object. It defaults to ``None``. The :term:`Forbidden View` can use the attributes of a Forbidden exception as necessary to provide extended information in an error report shown to a user. """ # differences from webob.exc.HTTPForbidden: # # - accepts a ``result`` keyword argument # # - overrides constructor to set ``self.result`` # # differences from older ``pyramid.exceptions.Forbidden``: # # - ``result`` must be passed as a keyword argument. # code = 403 title = 'Forbidden' explanation = ('Access was denied to this resource.') def __init__(self, detail=None, headers=None, comment=None, body_template=None, result=None, **kw): HTTPClientError.__init__(self, detail=detail, headers=headers, comment=comment, body_template=body_template, **kw) self.result = result class HTTPNotFound(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server did not find anything matching the Request-URI. code: 404, title: Not Found Raise this exception within :term:`view` code to immediately return the :term:`Not Found View` to the invoking user. Usually this is a basic ``404`` page, but the Not Found View can be customized as necessary. See :ref:`changing_the_notfound_view`. This exception's constructor accepts a ``detail`` argument (the first argument), which should be a string. The value of this string will be available as the ``message`` attribute of this exception, for availability to the :term:`Not Found View`. """ code = 404 title = 'Not Found' explanation = ('The resource could not be found.') class HTTPMethodNotAllowed(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the method specified in the Request-Line is not allowed for the resource identified by the Request-URI. code: 405, title: Method Not Allowed """ # differences from webob.exc.HTTPMethodNotAllowed: # # - body_template_obj uses ${br} instead of
code = 405 title = 'Method Not Allowed' body_template_obj = Template('''\ The method ${REQUEST_METHOD} is not allowed for this resource. ${br}${br} ${detail}''') class HTTPNotAcceptable(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates the resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request. code: 406, title: Not Acceptable """ # differences from webob.exc.HTTPNotAcceptable: # # - "template" attribute left off (useless, bug in webob?) code = 406 title = 'Not Acceptable' class HTTPProxyAuthenticationRequired(HTTPClientError): """ subclass of :class:`~HTTPClientError` This is similar to 401, but indicates that the client must first authenticate itself with the proxy. code: 407, title: Proxy Authentication Required """ code = 407 title = 'Proxy Authentication Required' explanation = ('Authentication with a local proxy is needed.') class HTTPRequestTimeout(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the client did not produce a request within the time that the server was prepared to wait. code: 408, title: Request Timeout """ code = 408 title = 'Request Timeout' explanation = ('The server has waited too long for the request to ' 'be sent by the client.') class HTTPConflict(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the request could not be completed due to a conflict with the current state of the resource. code: 409, title: Conflict """ code = 409 title = 'Conflict' explanation = ('There was a conflict when trying to complete ' 'your request.') class HTTPGone(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the requested resource is no longer available at the server and no forwarding address is known. code: 410, title: Gone """ code = 410 title = 'Gone' explanation = ('This resource is no longer available. No forwarding ' 'address is given.') class HTTPLengthRequired(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server refuses to accept the request without a defined Content-Length. code: 411, title: Length Required """ code = 411 title = 'Length Required' explanation = ('Content-Length header required.') class HTTPPreconditionFailed(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. code: 412, title: Precondition Failed """ code = 412 title = 'Precondition Failed' explanation = ('Request precondition failed.') class HTTPRequestEntityTooLarge(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server is refusing to process a request because the request entity is larger than the server is willing or able to process. code: 413, title: Request Entity Too Large """ code = 413 title = 'Request Entity Too Large' explanation = ('The body of your request was too large for this server.') class HTTPRequestURITooLong(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. code: 414, title: Request-URI Too Long """ code = 414 title = 'Request-URI Too Long' explanation = ('The request URI was too long for this server.') class HTTPUnsupportedMediaType(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method. code: 415, title: Unsupported Media Type """ # differences from webob.exc.HTTPUnsupportedMediaType: # # - "template_obj" attribute left off (useless, bug in webob?) code = 415 title = 'Unsupported Media Type' class HTTPRequestRangeNotSatisfiable(HTTPClientError): """ subclass of :class:`~HTTPClientError` The server SHOULD return a response with this status code if a request included a Range request-header field, and none of the range-specifier values in this field overlap the current extent of the selected resource, and the request did not include an If-Range request-header field. code: 416, title: Request Range Not Satisfiable """ code = 416 title = 'Request Range Not Satisfiable' explanation = ('The Range requested is not available.') class HTTPExpectationFailed(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indidcates that the expectation given in an Expect request-header field could not be met by this server. code: 417, title: Expectation Failed """ code = 417 title = 'Expectation Failed' explanation = ('Expectation failed.') class HTTPUnprocessableEntity(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the server is unable to process the contained 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 """ ## Note: from WebDAV code = 422 title = 'Unprocessable Entity' explanation = 'Unable to process the contained instructions' class HTTPLocked(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the resource is locked. code: 423, title: Locked """ ## Note: from WebDAV code = 423 title = 'Locked' explanation = ('The resource is locked') class HTTPFailedDependency(HTTPClientError): """ subclass of :class:`~HTTPClientError` This indicates that the method could not be performed because the requested action depended on another action and that action failed. code: 424, title: Failed Dependency """ ## Note: from WebDAV code = 424 title = 'Failed Dependency' explanation = ( '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 ############################################################ # Response status codes beginning with the digit "5" indicate cases in # which the server is aware that it has erred or is incapable of # performing the request. Except when responding to a HEAD request, the # server SHOULD include an entity containing an explanation of the error # situation, and whether it is a temporary or permanent condition. User # agents SHOULD display any included entity to the user. These response # codes are applicable to any request method. class HTTPServerError(HTTPError): """ base class for the 500s, where the server is in-error This is an error condition in which the server is presumed to be in-error. Unless specialized, this is a '500 Internal Server Error'. """ class HTTPInternalServerError(HTTPServerError): code = 500 title = 'Internal Server Error' explanation = ( 'The server has either erred or is incapable of performing ' 'the requested operation.') class HTTPNotImplemented(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server does not support the functionality required to fulfill the request. code: 501, title: Not Implemented """ # differences from webob.exc.HTTPNotAcceptable: # # - "template" attr left off (useless, bug in webob?) code = 501 title = 'Not Implemented' class HTTPBadGateway(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request. code: 502, title: Bad Gateway """ code = 502 title = 'Bad Gateway' explanation = ('Bad gateway.') class HTTPServiceUnavailable(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server is currently unable to handle the request due to a temporary overloading or maintenance of the server. code: 503, title: Service Unavailable """ code = 503 title = 'Service Unavailable' explanation = ('The server is currently unavailable. ' 'Please try again at a later time.') class HTTPGatewayTimeout(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed to access in attempting to complete the request. code: 504, title: Gateway Timeout """ code = 504 title = 'Gateway Timeout' explanation = ('The gateway has timed out.') class HTTPVersionNotSupported(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server does not support, or refuses to support, the HTTP protocol version that was used in the request message. code: 505, title: HTTP Version Not Supported """ code = 505 title = 'HTTP Version Not Supported' explanation = ('The HTTP version is not supported.') class HTTPInsufficientStorage(HTTPServerError): """ subclass of :class:`~HTTPServerError` This indicates that the server does not have enough space to save the resource. code: 507, title: Insufficient Storage """ code = 507 title = 'Insufficient Storage' explanation = ('There was not enough space to save the resource') def exception_response(status_code, **kw): """Creates an HTTP exception based on a status code. Example:: raise exception_response(404) # raises an HTTPNotFound exception. The values passed as ``kw`` are provided to the exception's constructor. """ exc = status_map[status_code](**kw) return exc def default_exceptionresponse_view(context, request): if not isinstance(context, Exception): # backwards compat for an exception response view registered via # config.set_notfound_view or config.set_forbidden_view # instead of as a proper exception view context = request.exception or context return context # assumed to be an IResponse status_map={} code = None for name, value in list(globals().items()): if (isinstance(value, class_types) and issubclass(value, HTTPException) and not name.startswith('_')): code = getattr(value, 'code', None) if code: status_map[code] = value del name, value, code pyramid-1.6/pyramid/i18n.py0000644000076500000240000003527212642137120016357 0ustar michaelstaff00000000000000import gettext import os from translationstring import ( Translator, Pluralizer, TranslationString, # API TranslationStringFactory, # API ) TranslationString = TranslationString # PyFlakes TranslationStringFactory = TranslationStringFactory # PyFlakes from pyramid.compat import PY3 from pyramid.decorator import reify from pyramid.interfaces import ( ILocalizer, ITranslationDirectories, ILocaleNegotiator, ) from pyramid.threadlocal import get_current_registry class Localizer(object): """ An object providing translation and pluralizations related to the current request's locale name. A :class:`pyramid.i18n.Localizer` object is created using the :func:`pyramid.i18n.get_localizer` function. """ def __init__(self, locale_name, translations): self.locale_name = locale_name self.translations = translations self.pluralizer = None self.translator = None def translate(self, tstring, domain=None, mapping=None): """ Translate a :term:`translation string` to the current language and interpolate any *replacement markers* in the result. The ``translate`` method accepts three arguments: ``tstring`` (required), ``domain`` (optional) and ``mapping`` (optional). When called, it will translate the ``tstring`` translation string to a ``unicode`` object using the current locale. If the current locale could not be determined, the result of interpolation of the default value is returned. The optional ``domain`` argument can be used to specify or override the domain of the ``tstring`` (useful when ``tstring`` is a normal string rather than a translation string). The optional ``mapping`` argument can specify or override the ``tstring`` interpolation mapping, useful when the ``tstring`` argument is a simple string instead of a translation string. Example:: from pyramid.18n import TranslationString ts = TranslationString('Add ${item}', domain='mypackage', mapping={'item':'Item'}) translated = localizer.translate(ts) Example:: translated = localizer.translate('Add ${item}', domain='mypackage', mapping={'item':'Item'}) """ if self.translator is None: self.translator = Translator(self.translations) return self.translator(tstring, domain=domain, mapping=mapping) def pluralize(self, singular, plural, n, domain=None, mapping=None): """ Return a Unicode string translation by using two :term:`message identifier` objects as a singular/plural pair and an ``n`` value representing the number that appears in the message using gettext plural forms support. The ``singular`` and ``plural`` objects should be unicode strings. There is no reason to use translation string objects as arguments as all metadata is ignored. ``n`` represents the number of elements. ``domain`` is the translation domain to use to do the pluralization, and ``mapping`` is the interpolation mapping that should be used on the result. If the ``domain`` is not supplied, a default domain is used (usually ``messages``). Example:: num = 1 translated = localizer.pluralize('Add ${num} item', 'Add ${num} items', num, mapping={'num':num}) If using the gettext plural support, which is required for languages that have pluralisation rules other than n != 1, the ``singular`` argument must be the message_id defined in the translation file. The plural argument is not used in this case. Example:: num = 1 translated = localizer.pluralize('item_plural', '', num, mapping={'num':num}) """ if self.pluralizer is None: self.pluralizer = Pluralizer(self.translations) return self.pluralizer(singular, plural, n, domain=domain, mapping=mapping) def default_locale_negotiator(request): """ The default :term:`locale negotiator`. Returns a locale name or ``None``. - First, the negotiator looks for the ``_LOCALE_`` attribute of the request object (possibly set by a view or a listener for an :term:`event`). If the attribute exists and it is not ``None``, its value will be used. - Then it looks for the ``request.params['_LOCALE_']`` value. - Then it looks for the ``request.cookies['_LOCALE_']`` value. - Finally, the negotiator returns ``None`` if the locale could not be determined via any of the previous checks (when a locale negotiator returns ``None``, it signifies that the :term:`default locale name` should be used.) """ name = '_LOCALE_' locale_name = getattr(request, name, None) if locale_name is None: locale_name = request.params.get(name) if locale_name is None: locale_name = request.cookies.get(name) return locale_name def negotiate_locale_name(request): """ Negotiate and return the :term:`locale name` associated with the current request.""" try: registry = request.registry except AttributeError: registry = get_current_registry() negotiator = registry.queryUtility(ILocaleNegotiator, default=default_locale_negotiator) locale_name = negotiator(request) if locale_name is None: settings = registry.settings or {} locale_name = settings.get('default_locale_name', 'en') return locale_name def get_locale_name(request): """ .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.locale_name` directly instead. Return the :term:`locale name` associated with the current request. """ return request.locale_name def make_localizer(current_locale_name, translation_directories): """ Create a :class:`pyramid.i18n.Localizer` object corresponding to the provided locale name from the translations found in the list of translation directories.""" translations = Translations() translations._catalog = {} locales_to_try = [] if '_' in current_locale_name: locales_to_try = [current_locale_name.split('_')[0]] locales_to_try.append(current_locale_name) # intent: order locales left to right in least specific to most specific, # e.g. ['de', 'de_DE']. This services the intent of creating a # translations object that returns a "more specific" translation for a # region, but will fall back to a "less specific" translation for the # locale if necessary. Ordering from least specific to most specific # allows us to call translations.add in the below loop to get this # behavior. for tdir in translation_directories: locale_dirs = [] for lname in locales_to_try: ldir = os.path.realpath(os.path.join(tdir, lname)) if os.path.isdir(ldir): locale_dirs.append(ldir) for locale_dir in locale_dirs: messages_dir = os.path.join(locale_dir, 'LC_MESSAGES') if not os.path.isdir(os.path.realpath(messages_dir)): continue for mofile in os.listdir(messages_dir): mopath = os.path.realpath(os.path.join(messages_dir, mofile)) if mofile.endswith('.mo') and os.path.isfile(mopath): with open(mopath, 'rb') as mofp: domain = mofile[:-3] dtrans = Translations(mofp, domain) translations.add(dtrans) return Localizer(locale_name=current_locale_name, translations=translations) def get_localizer(request): """ .. deprecated:: 1.5 Use the :attr:`pyramid.request.Request.localizer` attribute directly instead. Retrieve a :class:`pyramid.i18n.Localizer` object corresponding to the current request's locale name. """ return request.localizer class Translations(gettext.GNUTranslations, object): """An extended translation catalog class (ripped off from Babel) """ DEFAULT_DOMAIN = 'messages' def __init__(self, fileobj=None, domain=DEFAULT_DOMAIN): """Initialize the translations catalog. :param fileobj: the file-like object the translation should be read from """ # germanic plural by default; self.plural will be overwritten by # GNUTranslations._parse (called as a side effect if fileobj is # passed to GNUTranslations.__init__) with a "real" self.plural for # this domain; see https://github.com/Pylons/pyramid/issues/235 self.plural = lambda n: int(n != 1) gettext.GNUTranslations.__init__(self, fp=fileobj) self.files = list(filter(None, [getattr(fileobj, 'name', None)])) self.domain = domain self._domains = {} @classmethod def load(cls, dirname=None, locales=None, domain=DEFAULT_DOMAIN): """Load translations from the given directory. :param dirname: the directory containing the ``MO`` files :param locales: the list of locales in order of preference (items in this list can be either `Locale` objects or locale strings) :param domain: the message domain :return: the loaded catalog, or a ``NullTranslations`` instance if no matching translations were found :rtype: `Translations` """ if locales is not None: if not isinstance(locales, (list, tuple)): locales = [locales] locales = [str(l) for l in locales] if not domain: domain = cls.DEFAULT_DOMAIN filename = gettext.find(domain, dirname, locales) if not filename: return gettext.NullTranslations() with open(filename, 'rb') as fp: return cls(fileobj=fp, domain=domain) def __repr__(self): return '<%s: "%s">' % (type(self).__name__, self._info.get('project-id-version')) def add(self, translations, merge=True): """Add the given translations to the catalog. If the domain of the translations is different than that of the current catalog, they are added as a catalog that is only accessible by the various ``d*gettext`` functions. :param translations: the `Translations` instance with the messages to add :param merge: whether translations for message domains that have already been added should be merged with the existing translations :return: the `Translations` instance (``self``) so that `merge` calls can be easily chained :rtype: `Translations` """ domain = getattr(translations, 'domain', self.DEFAULT_DOMAIN) if merge and domain == self.domain: return self.merge(translations) existing = self._domains.get(domain) if merge and existing is not None: existing.merge(translations) else: translations.add_fallback(self) self._domains[domain] = translations return self def merge(self, translations): """Merge the given translations into the catalog. Message translations in the specified catalog override any messages with the same identifier in the existing catalog. :param translations: the `Translations` instance with the messages to merge :return: the `Translations` instance (``self``) so that `merge` calls can be easily chained :rtype: `Translations` """ if isinstance(translations, gettext.GNUTranslations): self._catalog.update(translations._catalog) if isinstance(translations, Translations): self.files.extend(translations.files) return self def dgettext(self, domain, message): """Like ``gettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).gettext(message) def ldgettext(self, domain, message): """Like ``lgettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).lgettext(message) def dugettext(self, domain, message): """Like ``ugettext()``, but look the message up in the specified domain. """ if PY3: return self._domains.get(domain, self).gettext(message) else: return self._domains.get(domain, self).ugettext(message) def dngettext(self, domain, singular, plural, num): """Like ``ngettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).ngettext(singular, plural, num) def ldngettext(self, domain, singular, plural, num): """Like ``lngettext()``, but look the message up in the specified domain. """ return self._domains.get(domain, self).lngettext(singular, plural, num) def dungettext(self, domain, singular, plural, num): """Like ``ungettext()`` but look the message up in the specified domain. """ if PY3: return self._domains.get(domain, self).ngettext( singular, plural, num) else: return self._domains.get(domain, self).ungettext( singular, plural, num) class LocalizerRequestMixin(object): @reify def localizer(self): """ Convenience property to return a localizer """ registry = self.registry current_locale_name = self.locale_name localizer = registry.queryUtility(ILocalizer, name=current_locale_name) if localizer is None: # no localizer utility registered yet tdirs = registry.queryUtility(ITranslationDirectories, default=[]) localizer = make_localizer(current_locale_name, tdirs) registry.registerUtility(localizer, ILocalizer, name=current_locale_name) return localizer @reify def locale_name(self): locale_name = negotiate_locale_name(self) return locale_name pyramid-1.6/pyramid/interfaces.py0000644000076500000240000014126012634703652017730 0ustar michaelstaff00000000000000from zope.deprecation import deprecated from zope.interface import ( Attribute, Interface, ) from pyramid.compat import PY3 # public API interfaces class IContextFound(Interface): """ An event type that is emitted after :app:`Pyramid` finds a :term:`context` object but before it calls any view code. See the documentation attached to :class:`pyramid.events.ContextFound` for more information. .. note:: For backwards compatibility with versions of :app:`Pyramid` before 1.0, this event interface can also be imported as :class:`pyramid.interfaces.IAfterTraversal`. """ request = Attribute('The request object') IAfterTraversal = IContextFound class INewRequest(Interface): """ An event type that is emitted whenever :app:`Pyramid` begins to process a new request. See the documentation attached to :class:`pyramid.events.NewRequest` for more information.""" request = Attribute('The request object') class INewResponse(Interface): """ An event type that is emitted whenever any :app:`Pyramid` view returns a response. See the documentation attached to :class:`pyramid.events.NewResponse` for more information.""" request = Attribute('The request object') response = Attribute('The response object') class IApplicationCreated(Interface): """ Event issued when the :meth:`pyramid.config.Configurator.make_wsgi_app` method is called. See the documentation attached to :class:`pyramid.events.ApplicationCreated` for more information. .. note:: For backwards compatibility with :app:`Pyramid` versions before 1.0, this interface can also be imported as :class:`pyramid.interfaces.IWSGIApplicationCreatedEvent`. """ app = Attribute("Created application") IWSGIApplicationCreatedEvent = IApplicationCreated # b /c class IResponse(Interface): """ Represents a WSGI response using the WebOb response interface. Some attribute and method documentation of this interface references :rfc:`2616`. This interface is most famously implemented by :class:`pyramid.response.Response` and the HTTP exception classes in :mod:`pyramid.httpexceptions`.""" RequestClass = Attribute( """ Alias for :class:`pyramid.request.Request` """) def __call__(environ, start_response): """ :term:`WSGI` call interface, should call the start_response callback and should return an iterable""" accept_ranges = Attribute( """Gets and sets and deletes the Accept-Ranges header. For more information on Accept-Ranges see RFC 2616, section 14.5""") age = Attribute( """Gets and sets and deletes the Age header. Converts using int. For more information on Age see RFC 2616, section 14.6.""") allow = Attribute( """Gets and sets and deletes the Allow header. Converts using list. For more information on Allow see RFC 2616, Section 14.7.""") app_iter = Attribute( """Returns the app_iter of the response. If body was set, this will create an app_iter from that body (a single-item list)""") def app_iter_range(start, stop): """ Return a new app_iter built from the response app_iter that serves up only the given start:stop range. """ body = Attribute( """The body of the response, as a str. This will read in the entire app_iter if necessary.""") body_file = Attribute( """A file-like object that can be used to write to the body. If you passed in a list app_iter, that app_iter will be modified by writes.""") cache_control = Attribute( """Get/set/modify the Cache-Control header (RFC 2616 section 14.9)""") cache_expires = Attribute( """ Get/set the Cache-Control and Expires headers. This sets the response to expire in the number of seconds passed when set. """) charset = Attribute( """Get/set the charset (in the Content-Type)""") def conditional_response_app(environ, start_response): """ Like the normal __call__ interface, but checks conditional headers: - If-Modified-Since (304 Not Modified; only on GET, HEAD) - If-None-Match (304 Not Modified; only on GET, HEAD) - Range (406 Partial Content; only on GET, HEAD)""" content_disposition = Attribute( """Gets and sets and deletes the Content-Disposition header. For more information on Content-Disposition see RFC 2616 section 19.5.1.""") content_encoding = Attribute( """Gets and sets and deletes the Content-Encoding header. For more information about Content-Encoding see RFC 2616 section 14.11.""") content_language = Attribute( """Gets and sets and deletes the Content-Language header. Converts using list. For more information about Content-Language see RFC 2616 section 14.12.""") content_length = Attribute( """Gets and sets and deletes the Content-Length header. For more information on Content-Length see RFC 2616 section 14.17. Converts using int. """) content_location = Attribute( """Gets and sets and deletes the Content-Location header. For more information on Content-Location see RFC 2616 section 14.14.""") content_md5 = Attribute( """Gets and sets and deletes the Content-MD5 header. For more information on Content-MD5 see RFC 2616 section 14.14.""") content_range = Attribute( """Gets and sets and deletes the Content-Range header. For more information on Content-Range see section 14.16. Converts using ContentRange object.""") content_type = Attribute( """Get/set the Content-Type header (or None), without the charset or any parameters. If you include parameters (or ; at all) when setting the content_type, any existing parameters will be deleted; otherwise they will be preserved.""") content_type_params = Attribute( """A dictionary of all the parameters in the content type. This is not a view, set to change, modifications of the dict would not be applied otherwise.""") def copy(): """ Makes a copy of the response and returns the copy. """ date = Attribute( """Gets and sets and deletes the Date header. For more information on Date see RFC 2616 section 14.18. Converts using HTTP date.""") def delete_cookie(key, path='/', domain=None): """ Delete a cookie from the client. Note that path and domain must match how the cookie was originally set. This sets the cookie to the empty string, and max_age=0 so that it should expire immediately. """ def encode_content(encoding='gzip', lazy=False): """ Encode the content with the given encoding (only gzip and identity are supported).""" environ = Attribute( """Get/set the request environ associated with this response, if any.""") etag = Attribute( """ Gets and sets and deletes the ETag header. For more information on ETag see RFC 2616 section 14.19. Converts using Entity tag.""") expires = Attribute( """ Gets and sets and deletes the Expires header. For more information on Expires see RFC 2616 section 14.21. Converts using HTTP date.""") headerlist = Attribute( """ The list of response headers. """) headers = Attribute( """ The headers in a dictionary-like object """) last_modified = Attribute( """ Gets and sets and deletes the Last-Modified header. For more information on Last-Modified see RFC 2616 section 14.29. Converts using HTTP date.""") location = Attribute( """ Gets and sets and deletes the Location header. For more information on Location see RFC 2616 section 14.30.""") def md5_etag(body=None, set_content_md5=False): """ Generate an etag for the response object using an MD5 hash of the body (the body parameter, or self.body if not given). Sets self.etag. If set_content_md5 is True sets self.content_md5 as well """ def merge_cookies(resp): """ Merge the cookies that were set on this response with the given resp object (which can be any WSGI application). If the resp is a webob.Response object, then the other object will be modified in-place. """ pragma = Attribute( """ Gets and sets and deletes the Pragma header. For more information on Pragma see RFC 2616 section 14.32. """) request = Attribute( """ Return the request associated with this response if any. """) retry_after = Attribute( """ Gets and sets and deletes the Retry-After header. For more information on Retry-After see RFC 2616 section 14.37. Converts using HTTP date or delta seconds.""") server = Attribute( """ Gets and sets and deletes the Server header. For more information on Server see RFC216 section 14.38. """) def set_cookie(key, value='', max_age=None, path='/', domain=None, secure=False, httponly=False, comment=None, expires=None, overwrite=False): """ Set (add) a cookie for the response """ status = Attribute( """ The status string. """) status_int = Attribute( """ The status as an integer """) unicode_body = Attribute( """ Get/set the unicode value of the body (using the charset of the Content-Type)""") def unset_cookie(key, strict=True): """ Unset a cookie with the given name (remove it from the response).""" vary = Attribute( """Gets and sets and deletes the Vary header. For more information on Vary see section 14.44. Converts using list.""") www_authenticate = Attribute( """ Gets and sets and deletes the WWW-Authenticate header. For more information on WWW-Authenticate see RFC 2616 section 14.47. Converts using 'parse_auth' and 'serialize_auth'. """) class IException(Interface): # not an API """ An interface representing a generic exception """ class IExceptionResponse(IException, IResponse): """ An interface representing a WSGI response which is also an exception object. Register an exception view using this interface as a ``context`` to apply the registered view for all exception types raised by :app:`Pyramid` internally (any exception that inherits from :class:`pyramid.response.Response`, including :class:`pyramid.httpexceptions.HTTPNotFound` and :class:`pyramid.httpexceptions.HTTPForbidden`).""" def prepare(environ): """ Prepares the response for being called as a WSGI application """ class IDict(Interface): # Documentation-only interface def __contains__(k): """ Return ``True`` if key ``k`` exists in the dictionary.""" def __setitem__(k, value): """ Set a key/value pair into the dictionary""" def __delitem__(k): """ Delete an item from the dictionary which is passed to the renderer as the renderer globals dictionary.""" def __getitem__(k): """ Return the value for key ``k`` from the dictionary or raise a KeyError if the key doesn't exist""" def __iter__(): """ Return an iterator over the keys of this dictionary """ def get(k, default=None): """ Return the value for key ``k`` from the renderer dictionary, or the default if no such value exists.""" def items(): """ Return a list of [(k,v)] pairs from the dictionary """ def keys(): """ Return a list of keys from the dictionary """ def values(): """ Return a list of values from the dictionary """ if not PY3: def iterkeys(): """ Return an iterator of keys from the dictionary """ def iteritems(): """ Return an iterator of (k,v) pairs from the dictionary """ def itervalues(): """ Return an iterator of values from the dictionary """ has_key = __contains__ def pop(k, default=None): """ Pop the key k from the dictionary and return its value. If k doesn't exist, and default is provided, return the default. If k doesn't exist and default is not provided, raise a KeyError.""" def popitem(): """ Pop the item with key k from the dictionary and return it as a two-tuple (k, v). If k doesn't exist, raise a KeyError.""" def setdefault(k, default=None): """ Return the existing value for key ``k`` in the dictionary. If no value with ``k`` exists in the dictionary, set the ``default`` value into the dictionary under the k name passed. If a value already existed in the dictionary, return it. If a value did not exist in the dictionary, return the default""" def update(d): """ Update the renderer dictionary with another dictionary ``d``.""" def clear(): """ Clear all values from the dictionary """ class IBeforeRender(IDict): """ Subscribers to this event may introspect and modify the set of :term:`renderer globals` before they are passed to a :term:`renderer`. The event object itself provides a dictionary-like interface for adding and removing :term:`renderer globals`. The keys and values of the dictionary are those globals. For example:: from repoze.events import subscriber from pyramid.interfaces import IBeforeRender @subscriber(IBeforeRender) def add_global(event): event['mykey'] = 'foo' .. seealso:: See also :ref:`beforerender_event`. """ rendering_val = Attribute('The value returned by a view or passed to a ' '``render`` method for this rendering. ' 'This feature is new in Pyramid 1.2.') class IRendererInfo(Interface): """ An object implementing this interface is passed to every :term:`renderer factory` constructor as its only argument (conventionally named ``info``)""" name = Attribute('The value passed by the user as the renderer name') package = Attribute('The "current package" when the renderer ' 'configuration statement was found') type = Attribute('The renderer type name') registry = Attribute('The "current" application registry when the ' 'renderer was created') settings = Attribute('The deployment settings dictionary related ' 'to the current application') def clone(): """ Return a shallow copy that does not share any mutable state.""" class IRendererFactory(Interface): def __call__(info): """ Return an object that implements :class:`pyramid.interfaces.IRenderer`. ``info`` is an object that implements :class:`pyramid.interfaces.IRendererInfo`. """ class IRenderer(Interface): def __call__(value, system): """ Call the renderer with the result of the view (``value``) passed in and return a result (a string or unicode object useful as a response body). Values computed by the system are passed by the system in the ``system`` parameter, which is a dictionary. Keys in the dictionary include: ``view`` (the view callable that returned the value), ``renderer_name`` (the template name or simple name of the renderer), ``context`` (the context object passed to the view), and ``request`` (the request object passed to the view).""" class ITemplateRenderer(IRenderer): def implementation(): """ Return the object that the underlying templating system uses to render the template; it is typically a callable that accepts arbitrary keyword arguments and returns a string or unicode object """ deprecated( 'ITemplateRenderer', 'As of Pyramid 1.5 the, "pyramid.interfaces.ITemplateRenderer" interface ' 'is scheduled to be removed. It was used by the Mako and Chameleon ' 'renderers which have been split into their own packages.' ) class IViewMapper(Interface): def __call__(self, object): """ Provided with an arbitrary object (a function, class, or instance), returns a callable with the call signature ``(context, request)``. The callable returned should itself return a Response object. An IViewMapper is returned by :class:`pyramid.interfaces.IViewMapperFactory`.""" class IViewMapperFactory(Interface): def __call__(self, **kw): """ Return an object which implements :class:`pyramid.interfaces.IViewMapper`. ``kw`` will be a dictionary containing view-specific arguments, such as ``permission``, ``predicates``, ``attr``, ``renderer``, and other items. An IViewMapperFactory is used by :meth:`pyramid.config.Configurator.add_view` to provide a plugpoint to extension developers who want to modify potential view callable invocation signatures and response values. """ class IAuthenticationPolicy(Interface): """ An object representing a Pyramid authentication policy. """ def authenticated_userid(request): """ 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. 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 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, userid, **kw): """ Return a set of headers suitable for 'remembering' the :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. """ class IAuthorizationPolicy(Interface): """ An object representing a Pyramid authorization policy. """ def permits(context, principals, permission): """ Return ``True`` if any of the ``principals`` is allowed the ``permission`` in the current ``context``, else return ``False`` """ def principals_allowed_by_permission(context, permission): """ Return a set of principal identifiers allowed by the ``permission`` in ``context``. This behavior is optional; if you choose to not implement it you should define this method as something which raises a ``NotImplementedError``. This method will only be called when the ``pyramid.security.principals_allowed_by_permission`` API is used.""" class IMultiDict(IDict): # docs-only interface """ An ordered dictionary that can have multiple values for each key. A multidict adds the methods ``getall``, ``getone``, ``mixed``, ``extend``, ``add``, and ``dict_of_lists`` to the normal dictionary interface. A multidict data structure is used as ``request.POST``, ``request.GET``, and ``request.params`` within an :app:`Pyramid` application. """ def add(key, value): """ Add the key and value, not overwriting any previous value. """ def dict_of_lists(): """ Returns a dictionary where each key is associated with a list of values. """ def extend(other=None, **kwargs): """ Add a set of keys and values, not overwriting any previous values. The ``other`` structure may be a list of two-tuples or a dictionary. If ``**kwargs`` is passed, its value *will* overwrite existing values.""" def getall(key): """ Return a list of all values matching the key (may be an empty list) """ def getone(key): """ Get one value matching the key, raising a KeyError if multiple values were found. """ def mixed(): """ Returns a dictionary where the values are either single values, or a list of values when a key/value appears more than once in this dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. """ # internal interfaces class IRequest(Interface): """ Request type interface attached to all request objects """ class ITweens(Interface): """ Marker interface for utility registration representing the ordered set of a configuration's tween factories""" class IRequestHandler(Interface): """ """ def __call__(self, request): """ Must return a tuple of IReqest, IResponse or raise an exception. The ``request`` argument will be an instance of an object that provides IRequest.""" IRequest.combined = IRequest # for exception view lookups class IRequestExtensions(Interface): """ Marker interface for storing request extensions (properties and methods) which will be added to the request object.""" descriptors = Attribute( """A list of descriptors that will be added to each request.""") methods = Attribute( """A list of methods to be added to each request.""") class IRouteRequest(Interface): """ *internal only* interface used as in a utility lookup to find route-specific interfaces. Not an API.""" class IStaticURLInfo(Interface): """ A policy for generating URLs to static assets """ def add(config, name, spec, **extra): """ Add a new static info registration """ def generate(path, request, **kw): """ Generate a URL for the given path """ def add_cache_buster(config, spec, cache_buster): """ Add a new cache buster to a particular set of assets """ class IResponseFactory(Interface): """ A utility which generates a response """ def __call__(request): """ Return a response object implementing IResponse, e.g. :class:`pyramid.response.Response`). It should handle the case when ``request`` is ``None``.""" class IRequestFactory(Interface): """ A utility which generates a request """ def __call__(environ): """ Return an instance of ``pyramid.request.Request``""" def blank(path): """ Return an empty request object (see :meth:`pyramid.request.Request.blank`)""" class IViewClassifier(Interface): """ *Internal only* marker interface for views.""" class IExceptionViewClassifier(Interface): """ *Internal only* marker interface for exception views.""" class IView(Interface): def __call__(context, request): """ Must return an object that implements IResponse. """ class ISecuredView(IView): """ *Internal only* interface. Not an API. """ def __call_permissive__(context, request): """ Guaranteed-permissive version of __call__ """ def __permitted__(context, request): """ Return True if view execution will be permitted using the context and request, False otherwise""" class IMultiView(ISecuredView): """ *internal only*. A multiview is a secured view that is a collection of other views. Each of the views is associated with zero or more predicates. Not an API.""" def add(view, predicates, order, accept=None, phash=None): """ Add a view to the multiview. """ class IRootFactory(Interface): def __call__(request): """ Return a root object based on the request """ class IDefaultRootFactory(Interface): def __call__(request): """ Return the *default* root object for an application """ class ITraverser(Interface): def __call__(request): """ Return a dictionary with (at least) the keys ``root``, ``context``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. These values are typically the result of an object graph traversal. ``root`` is the physical root object, ``context`` will be a model object, ``view_name`` will be the view name used (a Unicode name), ``subpath`` will be a sequence of Unicode names that followed the view name but were not traversed, ``traversed`` will be a sequence of Unicode names that were traversed (including the virtual root path, if any) ``virtual_root`` will be a model object representing the virtual root (or the physical root if traversal was not performed), and ``virtual_root_path`` will be a sequence representing the virtual root path (a sequence of Unicode names) or ``None`` if traversal was not performed. Extra keys for special purpose functionality can be returned as necessary. All values returned in the dictionary will be made available as attributes of the ``request`` object by the :term:`router`. """ ITraverserFactory = ITraverser # b / c for 1.0 code class IViewPermission(Interface): def __call__(context, request): """ Return True if the permission allows, return False if it denies. """ class IRouter(Interface): """ WSGI application which routes requests to 'view' code based on a view registry.""" registry = Attribute( """Component architecture registry local to this application.""") class ISettings(Interface): """ Runtime settings utility for pyramid; represents the deployment settings for the application. Implements a mapping interface.""" # this interface, even if it becomes unused within Pyramid, is # imported by other packages (such as traversalwrapper) class ILocation(Interface): """Objects that have a structural location""" __parent__ = Attribute("The parent in the location hierarchy") __name__ = Attribute("The name within the parent") class IDebugLogger(Interface): """ Interface representing a PEP 282 logger """ ILogger = IDebugLogger # b/c class IRoutePregenerator(Interface): def __call__(request, elements, kw): """ A pregenerator is a function associated by a developer with a :term:`route`. The pregenerator for a route is called by :meth:`pyramid.request.Request.route_url` in order to adjust the set of arguments passed to it by the user for special purposes, such as Pylons 'subdomain' support. It will influence the URL returned by ``route_url``. A pregenerator should return a two-tuple of ``(elements, kw)`` after examining the originals passed to this function, which are the arguments ``(request, elements, kw)``. The simplest pregenerator is:: def pregenerator(request, elements, kw): return elements, kw You can employ a pregenerator by passing a ``pregenerator`` argument to the :meth:`pyramid.config.Configurator.add_route` function. """ class IRoute(Interface): """ Interface representing the type of object returned from ``IRoutesMapper.get_route``""" name = Attribute('The route name') pattern = Attribute('The route pattern') factory = Attribute( 'The :term:`root factory` used by the :app:`Pyramid` router ' 'when this route matches (or ``None``)') predicates = Attribute( 'A sequence of :term:`route predicate` objects used to ' 'determine if a request matches this route or not after ' 'basic pattern matching has been completed.') 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 ``pattern`` of this route, return a dictionary (the 'matchdict'), which will contain keys representing the dynamic segment markers in the pattern mapped to values extracted from the provided ``path``. If the ``path`` passed to this function cannot be matched by the ``pattern`` of this route, return ``None``. """ def generate(kw): """ Generate a URL based on filling in the dynamic segment markers in the pattern using the ``kw`` dictionary provided. """ class IRoutesMapper(Interface): """ Interface representing a Routes ``Mapper`` object """ def get_routes(): """ Return a sequence of Route objects registered in the mapper. Static routes will not be returned in this sequence.""" def has_routes(): """ Returns ``True`` if any route has been registered. """ def get_route(name): """ Returns an ``IRoute`` object if a route with the name ``name`` was registered, otherwise return ``None``.""" def connect(name, pattern, factory=None, predicates=(), pregenerator=None, static=True): """ Add a new route. """ def generate(name, kw): """ Generate a URL using the route named ``name`` with the keywords implied by kw""" def __call__(request): """ Return a dictionary containing matching information for the request; the ``route`` key of this dictionary will either be a Route object or ``None`` if no route matched; the ``match`` key will be the matchdict or ``None`` if no route matched. Static routes will not be considered for matching. """ class IResourceURL(Interface): virtual_path = Attribute( 'The virtual url path of the resource as a string.' ) physical_path = Attribute( 'The physical url path of the resource as a string.' ) virtual_path_tuple = Attribute( 'The virtual url path of the resource as a tuple. (New in 1.5)' ) physical_path_tuple = Attribute( 'The physical url path of the resource as a tuple. (New in 1.5)' ) class IContextURL(IResourceURL): """ .. deprecated:: 1.3 An adapter which deals with URLs related to a context. Use :class:`pyramid.interfaces.IResourceURL` instead. """ # this class subclasses IResourceURL because request.resource_url looks # for IResourceURL via queryAdapter. queryAdapter will find a deprecated # IContextURL registration if no registration for IResourceURL exists. # In reality, however, IContextURL objects were never required to have # the virtual_path or physical_path attributes spelled in IResourceURL. # The inheritance relationship is purely to benefit adapter lookup, # not to imply an inheritance relationship of interface attributes # and methods. # # Mechanics: # # class Fudge(object): # def __init__(self, one, two): # print(one, two) # class Another(object): # def __init__(self, one, two): # print(one, two) # ob = object() # r.registerAdapter(Fudge, (Interface, Interface), IContextURL) # print(r.queryMultiAdapter((ob, ob), IResourceURL)) # r.registerAdapter(Another, (Interface, Interface), IResourceURL) # print(r.queryMultiAdapter((ob, ob), IResourceURL)) # # prints # # # <__main__.Fudge object at 0x1cda890> # # <__main__.Another object at 0x1cda850> def virtual_root(): """ Return the virtual root related to a request and the current context""" def __call__(): """ Return a URL that points to the context. """ deprecated( 'IContextURL', 'As of Pyramid 1.3 the, "pyramid.interfaces.IContextURL" interface is ' 'scheduled to be removed. Use the ' '"pyramid.config.Configurator.add_resource_url_adapter" method to register ' 'a class that implements "pyramid.interfaces.IResourceURL" instead. ' 'See the "What\'s new In Pyramid 1.3" document for more details.' ) class IPEP302Loader(Interface): """ See http://www.python.org/dev/peps/pep-0302/#id30. """ def get_data(path): """ Retrieve data for and arbitrary "files" from storage backend. Raise IOError for not found. Data is returned as bytes. """ def is_package(fullname): """ Return True if the module specified by 'fullname' is a package. """ 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. Raise ImportError if the module can't be found by the importer at all. """ 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. """ class IPackageOverrides(IPEP302Loader): """ Utility for pkg_resources overrides """ # VH_ROOT_KEY is an interface; its imported from other packages (e.g. # traversalwrapper) VH_ROOT_KEY = 'HTTP_X_VHM_ROOT' class ILocalizer(Interface): """ Localizer for a specific language """ class ILocaleNegotiator(Interface): def __call__(request): """ Return a locale name """ class ITranslationDirectories(Interface): """ A list object representing all known translation directories for an application""" class IDefaultPermission(Interface): """ A string object representing the default permission to be used for all view configurations which do not explicitly declare their own.""" class ISessionFactory(Interface): """ An interface representing a factory which accepts a request object and returns an ISession object """ def __call__(request): """ Return an ISession object """ class ISession(IDict): """ An interface representing a session (a web session object, usually accessed via ``request.session``. Keys and values of a session must be pickleable. """ # attributes created = Attribute('Integer representing Epoch time when created.') new = Attribute('Boolean attribute. If ``True``, the session is new.') # special methods def invalidate(): """ Invalidate the session. The action caused by ``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. 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 call this method after he or she mutates a mutable object that is *a value of the session* (it should not be required after mutating the session itself). For example, if the user has stored a dictionary in the session under the key ``foo``, and he or she does ``session['foo'] = {}``, ``changed()`` needn't be called. However, if subsequently he or she does ``session['foo']['a'] = 1``, ``changed()`` must be called for the sessioning machinery to notice the mutation of the internal dictionary.""" def flash(msg, queue='', allow_duplicate=True): """ Push a flash message onto the end of the flash queue represented by ``queue``. An alternate flash message queue can used by passing an optional ``queue``, which must be a string. If ``allow_duplicate`` is false, if the ``msg`` already exists in the queue, it will not be re-added.""" def pop_flash(queue=''): """ Pop a queue from the flash storage. The queue is removed from flash storage after this message is called. The queue is returned; it is a list of flash messages added by :meth:`pyramid.interfaces.ISession.flash`""" def peek_flash(queue=''): """ Peek at a queue in the flash storage. The queue remains in flash storage after this message is called. The queue is returned; it is a list of flash messages added by :meth:`pyramid.interfaces.ISession.flash` """ def new_csrf_token(): """ Create and set into the session a new, random cross-site request forgery protection token. Return the token. It will be a string.""" def get_csrf_token(): """ Return a random cross-site request forgery protection token. It will be a string. If a token was previously added to the session via ``new_csrf_token``, that token will be returned. If no CSRF token was previously set into the session, ``new_csrf_token`` will be called, which will create and set a token, and this token will be returned. """ class IIntrospector(Interface): def get(category_name, discriminator, default=None): """ Get the IIntrospectable related to the category_name and the discriminator (or discriminator hash) ``discriminator``. If it does not exist in the introspector, return the value of ``default`` """ def get_category(category_name, default=None, sort_key=None): """ Get a sequence of dictionaries in the form ``[{'introspectable':IIntrospectable, 'related':[sequence of related IIntrospectables]}, ...]`` where each introspectable is part of the category associated with ``category_name`` . If the category named ``category_name`` does not exist in the introspector the value passed as ``default`` will be returned. If ``sort_key`` is ``None``, the sequence will be returned in the order the introspectables were added to the introspector. Otherwise, sort_key should be a function that accepts an IIntrospectable and returns a value from it (ala the ``key`` function of Python's ``sorted`` callable).""" def categories(): """ Return a sorted sequence of category names known by this introspector """ def categorized(sort_key=None): """ Get a sequence of tuples in the form ``[(category_name, [{'introspectable':IIntrospectable, 'related':[sequence of related IIntrospectables]}, ...])]`` representing all known introspectables. If ``sort_key`` is ``None``, each introspectables sequence will be returned in the order the introspectables were added to the introspector. Otherwise, sort_key should be a function that accepts an IIntrospectable and returns a value from it (ala the ``key`` function of Python's ``sorted`` callable).""" def remove(category_name, discriminator): """ Remove the IIntrospectable related to ``category_name`` and ``discriminator`` from the introspector, and fix up any relations that the introspectable participates in. This method will not raise an error if an introspectable related to the category name and discriminator does not exist.""" def related(intr): """ Return a sequence of IIntrospectables related to the IIntrospectable ``intr``. Return the empty sequence if no relations for exist.""" def add(intr): """ Add the IIntrospectable ``intr`` (use instead of :meth:`pyramid.interfaces.IIntrospector.add` when you have a custom IIntrospectable). Replaces any existing introspectable registered using the same category/discriminator. This method is not typically called directly, instead it's called indirectly by :meth:`pyramid.interfaces.IIntrospector.register`""" def relate(*pairs): """ Given any number of ``(category_name, discriminator)`` pairs passed as positional arguments, relate the associated introspectables to each other. The introspectable related to each pair must have already been added via ``.add`` or ``.add_intr``; a :exc:`KeyError` will result if this is not true. An error will not be raised if any pair has already been associated with another. This method is not typically called directly, instead it's called indirectly by :meth:`pyramid.interfaces.IIntrospector.register` """ def unrelate(*pairs): """ Given any number of ``(category_name, discriminator)`` pairs passed as positional arguments, unrelate the associated introspectables from each other. The introspectable related to each pair must have already been added via ``.add`` or ``.add_intr``; a :exc:`KeyError` will result if this is not true. An error will not be raised if any pair is not already related to another. This method is not typically called directly, instead it's called indirectly by :meth:`pyramid.interfaces.IIntrospector.register` """ class IIntrospectable(Interface): """ An introspectable object used for configuration introspection. In addition to the methods below, objects which implement this interface must also implement all the methods of Python's ``collections.MutableMapping`` (the "dictionary interface"), and must be hashable.""" title = Attribute('Text title describing this introspectable') type_name = Attribute('Text type name describing this introspectable') order = Attribute('integer order in which registered with introspector ' '(managed by introspector, usually)') category_name = Attribute('introspection category name') discriminator = Attribute('introspectable discriminator (within category) ' '(must be hashable)') discriminator_hash = Attribute('an integer hash of the discriminator') action_info = Attribute('An IActionInfo object representing the caller ' 'that invoked the creation of this introspectable ' '(usually a sentinel until updated during ' 'self.register)') def relate(category_name, discriminator): """ Indicate an intent to relate this IIntrospectable with another IIntrospectable (the one associated with the ``category_name`` and ``discriminator``) during action execution. """ def unrelate(category_name, discriminator): """ Indicate an intent to break the relationship between this IIntrospectable with another IIntrospectable (the one associated with the ``category_name`` and ``discriminator``) during action execution. """ def register(introspector, action_info): """ Register this IIntrospectable with an introspector. This method is invoked during action execution. Adds the introspectable and its relations to the introspector. ``introspector`` should be an object implementing IIntrospector. ``action_info`` should be a object implementing the interface :class:`pyramid.interfaces.IActionInfo` representing the call that registered this introspectable. Pseudocode for an implementation of this method: .. code-block:: python def register(self, introspector, action_info): self.action_info = action_info introspector.add(self) for methodname, category_name, discriminator in self._relations: method = getattr(introspector, methodname) method((i.category_name, i.discriminator), (category_name, discriminator)) """ def __hash__(): """ Introspectables must be hashable. The typical implementation of an introsepectable's __hash__ is:: return hash((self.category_name,) + (self.discriminator,)) """ class IActionInfo(Interface): """ Class which provides code introspection capability associated with an action. The ParserInfo class used by ZCML implements the same interface.""" file = Attribute( 'Filename of action-invoking code as a string') line = Attribute( 'Starting line number in file (as an integer) of action-invoking code.' 'This will be ``None`` if the value could not be determined.') def __str__(): """ Return a representation of the action information (including source code from file, if possible) """ class IAssetDescriptor(Interface): """ Describes an :term:`asset`. """ def absspec(): """ Returns the absolute asset specification for this asset (e.g. ``mypackage:templates/foo.pt``). """ def abspath(): """ Returns an absolute path in the filesystem to the asset. """ def stream(): """ Returns an input stream for reading asset contents. Raises an exception if the asset is a directory or does not exist. """ def isdir(): """ Returns True if the asset is a directory, otherwise returns False. """ def listdir(): """ Returns iterable of filenames of directory contents. Raises an exception if asset is not a directory. """ def exists(): """ Returns True if asset exists, otherwise returns False. """ class IJSONAdapter(Interface): """ Marker interface for objects that can convert an arbitrary object into a JSON-serializable primitive. """ class IPredicateList(Interface): """ Interface representing a predicate list """ class ICacheBuster(Interface): """ A cache buster modifies the URL generation machinery for :meth:`~pyramid.request.Request.static_url`. See :ref:`cache_busting`. .. versionadded:: 1.6 """ def __call__(request, subpath, kw): """ Modifies a subpath and/or keyword arguments from which a static asset URL will be computed during URL generation. The ``subpath`` argument is a path of ``/``-delimited segments 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.static_url` for URL generation. The return value should be a two-tuple of ``(subpath, kw)`` where ``subpath`` is the relative URL from where the file is served and ``kw`` is the same input argument. The return value should be modified to include the cache bust token in the generated URL. The ``kw`` dictionary contains extra arguments passed to :meth:`~pyramid.request.Request.static_url` as well as some extra items that may be usful including: - ``pathspec`` is the path specification for the resource to be cache busted. - ``rawspec`` is the original location of the file, ignoring any calls to :meth:`pyramid.config.Configurator.override_asset`. The ``pathspec`` and ``rawspec`` values are only different in cases where an asset has been mounted into a virtual location using :meth:`pyramid.config.Configurator.override_asset`. For example, with a call to ``request.static_url('myapp:static/foo.png'), the ``pathspec`` is ``myapp:static/foo.png`` whereas the ``rawspec`` may be ``themepkg:bar.png``, assuming a call to ``config.override_asset('myapp:static/foo.png', 'themepkg:bar.png')``. """ # 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. PHASE0_CONFIG = -30 PHASE1_CONFIG = -20 PHASE2_CONFIG = -10 PHASE3_CONFIG = 0 pyramid-1.6/pyramid/location.py0000644000076500000240000000516112234375161017410 0ustar michaelstaff00000000000000############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## def inside(resource1, resource2): """Is ``resource1`` 'inside' ``resource2``? Return ``True`` if so, else ``False``. ``resource1`` is 'inside' ``resource2`` if ``resource2`` is a :term:`lineage` ancestor of ``resource1``. It is a lineage ancestor if its parent (or one of its parent's parents, etc.) is an ancestor. """ while resource1 is not None: if resource1 is resource2: return True resource1 = resource1.__parent__ return False def lineage(resource): """ Return a generator representing the :term:`lineage` of the :term:`resource` object implied by the ``resource`` argument. The generator first returns ``resource`` unconditionally. Then, if ``resource`` supplies a ``__parent__`` attribute, return the resource represented by ``resource.__parent__``. If *that* resource has a ``__parent__`` attribute, return that resource's parent, and so on, until the resource being inspected either has no ``__parent__`` attribute or which has a ``__parent__`` attribute of ``None``. For example, if the resource tree is:: thing1 = Thing() thing2 = Thing() thing2.__parent__ = thing1 Calling ``lineage(thing2)`` will return a generator. When we turn it into a list, we will get:: list(lineage(thing2)) [ , ] """ while resource is not None: yield resource # The common case is that the AttributeError exception below # is exceptional as long as the developer is a "good citizen" # who has a root object with a __parent__ of None. Using an # exception here instead of a getattr with a default is an # important micro-optimization, because this function is # called in any non-trivial application over and over again to # generate URLs and paths. try: resource = resource.__parent__ except AttributeError: resource = None pyramid-1.6/pyramid/paster.py0000644000076500000240000001223512520062551017070 0ustar michaelstaff00000000000000import os from paste.deploy import ( loadapp, appconfig, ) from pyramid.compat import configparser from logging.config import fileConfig from pyramid.scripting import prepare def get_app(config_uri, name=None, options=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy config file specified by ``config_uri``. ``options``, if passed, should be a dictionary used as variable assignments like ``{'http_port': 8080}``. This is useful if e.g. ``%(http_port)s`` is used in the config file. If the ``name`` is None, this will attempt to parse the name from the ``config_uri`` string expecting the format ``inifile#name``. If no name is found, the name will default to "main".""" path, section = _getpathsec(config_uri, name) config_name = 'config:%s' % path here_dir = os.getcwd() app = loadapp( config_name, name=section, relative_to=here_dir, global_conf=options) return app def get_appsettings(config_uri, name=None, options=None, appconfig=appconfig): """ Return a dictionary representing the key/value pairs in an ``app`` section within the file represented by ``config_uri``. ``options``, if passed, should be a dictionary used as variable assignments like ``{'http_port': 8080}``. This is useful if e.g. ``%(http_port)s`` is used in the config file. If the ``name`` is None, this will attempt to parse the name from the ``config_uri`` string expecting the format ``inifile#name``. If no name is found, the name will default to "main".""" path, section = _getpathsec(config_uri, name) config_name = 'config:%s' % path here_dir = os.getcwd() return appconfig( config_name, name=section, relative_to=here_dir, global_conf=options) def setup_logging(config_uri, fileConfig=fileConfig, configparser=configparser): """ Set up logging via the logging module's fileConfig function with the filename specified via ``config_uri`` (a string in the form ``filename#sectionname``). ConfigParser defaults are specified for the special ``__file__`` and ``here`` variables, similar to PasteDeploy config loading. """ path, _ = _getpathsec(config_uri, None) parser = configparser.ConfigParser() parser.read([path]) if parser.has_section('loggers'): config_file = os.path.abspath(path) return fileConfig( config_file, dict(__file__=config_file, here=os.path.dirname(config_file)) ) def _getpathsec(config_uri, name): if '#' in config_uri: path, section = config_uri.split('#', 1) else: path, section = config_uri, 'main' if name: section = name return path, section def bootstrap(config_uri, request=None, options=None): """ Load a WSGI application from the PasteDeploy config file specified by ``config_uri``. The environment will be configured as if it is currently serving ``request``, leaving a natural environment in place to write scripts that can generate URLs and utilize renderers. This function returns a dictionary with ``app``, ``root``, ``closer``, ``request``, and ``registry`` keys. ``app`` is the WSGI app loaded (based on the ``config_uri``), ``root`` is the traversal root resource of the Pyramid application, and ``closer`` is a parameterless callback that may be called when your script is complete (it pops a threadlocal stack). .. note:: Most operations within :app:`Pyramid` expect to be invoked within the context of a WSGI request, thus it's important when loading your application to anchor it when executing scripts and other code that is not normally invoked during active WSGI requests. .. note:: For a complex config file containing multiple :app:`Pyramid` applications, this function will setup the environment under the context of the last-loaded :app:`Pyramid` application. You may load a specific application yourself by using the lower-level functions :meth:`pyramid.paster.get_app` and :meth:`pyramid.scripting.prepare` in conjunction with :attr:`pyramid.config.global_registries`. ``config_uri`` -- specifies the PasteDeploy config file to use for the interactive shell. The format is ``inifile#name``. If the name is left off, ``main`` will be assumed. ``request`` -- specified to anchor the script to a given set of WSGI parameters. For example, most people would want to specify the host, scheme and port such that their script will generate URLs in relation to those parameters. A request with default parameters is constructed for you if none is provided. You can mutate the request's ``environ`` later to setup a specific host/port/scheme/etc. ``options`` Is passed to get_app for use as variable assignments like {'http_port': 8080} and then use %(http_port)s in the config file. See :ref:`writing_a_script` for more information about how to use this function. """ app = get_app(config_uri, options=options) env = prepare(request) env['app'] = app return env pyramid-1.6/pyramid/path.py0000644000076500000240000003714612642137120016536 0ustar michaelstaff00000000000000import os import pkg_resources import sys import imp from zope.interface import implementer from pyramid.interfaces import IAssetDescriptor from pyramid.compat import string_types ignore_types = [ imp.C_EXTENSION, imp.C_BUILTIN ] init_names = [ '__init__%s' % x[0] for x in imp.get_suffixes() if x[0] and x[2] not in ignore_types ] def caller_path(path, level=2): if not os.path.isabs(path): module = caller_module(level+1) prefix = package_path(module) path = os.path.join(prefix, path) return path def caller_module(level=2, sys=sys): module_globals = sys._getframe(level).f_globals module_name = module_globals.get('__name__') or '__main__' module = sys.modules[module_name] return module def package_name(pkg_or_module): """ If this function is passed a module, return the dotted Python package name of the package in which the module lives. If this function is passed a package, return the dotted Python package name of the package itself.""" if pkg_or_module is None or pkg_or_module.__name__ == '__main__': return '__main__' pkg_name = pkg_or_module.__name__ pkg_filename = getattr(pkg_or_module, '__file__', None) if pkg_filename is None: # Namespace packages do not have __init__.py* files, # and so have no __file__ attribute return pkg_name splitted = os.path.split(pkg_filename) if splitted[-1] in init_names: # it's a package return pkg_name return pkg_name.rsplit('.', 1)[0] def package_of(pkg_or_module): """ Return the package of a module or return the package itself """ pkg_name = package_name(pkg_or_module) __import__(pkg_name) return sys.modules[pkg_name] def caller_package(level=2, caller_module=caller_module): # caller_module in arglist for tests module = caller_module(level+1) f = getattr(module, '__file__', '') if (('__init__.py' in f) or ('__init__$py' in f)): # empty at >>> # Module is a package return module # Go up one level to get package package_name = module.__name__.rsplit('.', 1)[0] return sys.modules[package_name] def package_path(package): # computing the abspath is actually kinda expensive so we memoize # the result prefix = getattr(package, '__abspath__', None) if prefix is None: prefix = pkg_resources.resource_filename(package.__name__, '') # pkg_resources doesn't care whether we feed it a package # name or a module name within the package, the result # will be the same: a directory name to the package itself try: package.__abspath__ = prefix except: # this is only an optimization, ignore any error pass return prefix class _CALLER_PACKAGE(object): def __repr__(self): # pragma: no cover (for docs) return 'pyramid.path.CALLER_PACKAGE' CALLER_PACKAGE = _CALLER_PACKAGE() class Resolver(object): def __init__(self, package=CALLER_PACKAGE): if package in (None, CALLER_PACKAGE): self.package = package else: if isinstance(package, string_types): try: __import__(package) except ImportError: raise ValueError( 'The dotted name %r cannot be imported' % (package,) ) package = sys.modules[package] self.package = package_of(package) def get_package_name(self): if self.package is CALLER_PACKAGE: package_name = caller_package().__name__ else: package_name = self.package.__name__ return package_name def get_package(self): if self.package is CALLER_PACKAGE: package = caller_package() else: package = self.package return package class AssetResolver(Resolver): """ A class used to resolve an :term:`asset specification` to an :term:`asset descriptor`. .. versionadded:: 1.3 The constructor accepts a single argument named ``package`` which may be any of: - A fully qualified (not relative) dotted name to a module or package - a Python module or package object - The value ``None`` - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. The default value is :attr:`pyramid.path.CALLER_PACKAGE`. The ``package`` is used when a relative asset specification is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method. An asset specification without a colon in it is treated as relative. If ``package`` is ``None``, the resolver will only be able to resolve fully qualified (not relative) asset specifications. Any attempt to resolve a relative asset specification will result in an :exc:`ValueError` exception. If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, the resolver will treat relative asset specifications as relative to the caller of the :meth:`~pyramid.path.AssetResolver.resolve` method. If ``package`` is a *module* or *module name* (as opposed to a package or package name), its containing package is computed and this package is used to derive the package name (all names are resolved relative to packages, never to modules). For example, if the ``package`` argument to this type was passed the string ``xml.dom.expatbuilder``, and ``template.pt`` is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute asset spec would be ``xml.minidom:template.pt``, because ``xml.dom.expatbuilder`` is a module object, not a package object. If ``package`` is a *package* or *package name* (as opposed to a module or module name), this package will be used to compute relative asset specifications. For example, if the ``package`` argument to this type was passed the string ``xml.dom``, and ``template.pt`` is supplied to the :meth:`~pyramid.path.AssetResolver.resolve` method, the resulting absolute asset spec would be ``xml.minidom:template.pt``. """ def resolve(self, spec): """ Resolve the asset spec named as ``spec`` to an object that has the attributes and methods described in :class:`pyramid.interfaces.IAssetDescriptor`. If ``spec`` is an absolute filename (e.g. ``/path/to/myproject/templates/foo.pt``) or an absolute asset spec (e.g. ``myproject:templates.foo.pt``), an asset descriptor is returned without taking into account the ``package`` passed to this class' constructor. If ``spec`` is a *relative* asset specification (an asset specification without a ``:`` in it, e.g. ``templates/foo.pt``), the ``package`` argument of the constructor is used as the package portion of the asset spec. For example: .. code-block:: python a = AssetResolver('myproject') resolver = a.resolve('templates/foo.pt') print(resolver.abspath()) # -> /path/to/myproject/templates/foo.pt If the AssetResolver is constructed without a ``package`` argument of ``None``, and a relative asset specification is passed to ``resolve``, an :exc:`ValueError` exception is raised. """ if os.path.isabs(spec): return FSAssetDescriptor(spec) path = spec if ':' in path: package_name, path = spec.split(':', 1) else: if self.package is CALLER_PACKAGE: package_name = caller_package().__name__ else: package_name = getattr(self.package, '__name__', None) if package_name is None: raise ValueError( 'relative spec %r irresolveable without package' % (spec,) ) return PkgResourcesAssetDescriptor(package_name, path) class DottedNameResolver(Resolver): """ A class used to resolve a :term:`dotted Python name` to a package or module object. .. versionadded:: 1.3 The constructor accepts a single argument named ``package`` which may be any of: - A fully qualified (not relative) dotted name to a module or package - a Python module or package object - The value ``None`` - The constant value :attr:`pyramid.path.CALLER_PACKAGE`. The default value is :attr:`pyramid.path.CALLER_PACKAGE`. The ``package`` is used when a relative dotted name is supplied to the :meth:`~pyramid.path.DottedNameResolver.resolve` method. A dotted name which has a ``.`` (dot) or ``:`` (colon) as its first character is treated as relative. If ``package`` is ``None``, the resolver will only be able to resolve fully qualified (not relative) names. Any attempt to resolve a relative name will result in an :exc:`ValueError` exception. If ``package`` is :attr:`pyramid.path.CALLER_PACKAGE`, the resolver will treat relative dotted names as relative to the caller of the :meth:`~pyramid.path.DottedNameResolver.resolve` method. If ``package`` is a *module* or *module name* (as opposed to a package or package name), its containing package is computed and this package used to derive the package name (all names are resolved relative to packages, never to modules). For example, if the ``package`` argument to this type was passed the string ``xml.dom.expatbuilder``, and ``.mindom`` is supplied to the :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting import would be for ``xml.minidom``, because ``xml.dom.expatbuilder`` is a module object, not a package object. If ``package`` is a *package* or *package name* (as opposed to a module or module name), this package will be used to relative compute dotted names. For example, if the ``package`` argument to this type was passed the string ``xml.dom``, and ``.minidom`` is supplied to the :meth:`~pyramid.path.DottedNameResolver.resolve` method, the resulting import would be for ``xml.minidom``. """ def resolve(self, dotted): """ This method resolves a dotted name reference to a global Python object (an object which can be imported) to the object itself. Two dotted name styles are supported: - ``pkg_resources``-style dotted names where non-module attributes of a package are separated from the rest of the path using a ``:`` e.g. ``package.module:attr``. - ``zope.dottedname``-style dotted names where non-module attributes of a package are separated from the rest of the path using a ``.`` e.g. ``package.module.attr``. These styles can be used interchangeably. If the supplied name contains a ``:`` (colon), the ``pkg_resources`` resolution mechanism will be chosen, otherwise the ``zope.dottedname`` resolution mechanism will be chosen. If the ``dotted`` argument passed to this method is not a string, a :exc:`ValueError` will be raised. When a dotted name cannot be resolved, a :exc:`ValueError` error is raised. Example: .. code-block:: python r = DottedNameResolver() v = r.resolve('xml') # v is the xml module """ if not isinstance(dotted, string_types): raise ValueError('%r is not a string' % (dotted,)) package = self.package if package is CALLER_PACKAGE: package = caller_package() return self._resolve(dotted, package) def maybe_resolve(self, dotted): """ This method behaves just like :meth:`~pyramid.path.DottedNameResolver.resolve`, except if the ``dotted`` value passed is not a string, it is simply returned. For example: .. code-block:: python import xml r = DottedNameResolver() v = r.maybe_resolve(xml) # v is the xml module; no exception raised """ if isinstance(dotted, string_types): package = self.package if package is CALLER_PACKAGE: package = caller_package() return self._resolve(dotted, package) return dotted def _resolve(self, dotted, package): if ':' in dotted: return self._pkg_resources_style(dotted, package) else: return self._zope_dottedname_style(dotted, package) def _pkg_resources_style(self, value, package): """ package.module:attr style """ if value.startswith(('.', ':')): if not package: raise ValueError( 'relative name %r irresolveable without package' % (value,) ) if value in ['.', ':']: value = package.__name__ else: value = package.__name__ + value # Calling EntryPoint.load with an argument is deprecated. # See https://pythonhosted.org/setuptools/history.html#id8 ep = pkg_resources.EntryPoint.parse('x=%s' % value) if hasattr(ep, 'resolve'): # setuptools>=10.2 return ep.resolve() # pragma: NO COVER else: return ep.load(False) # pragma: NO COVER def _zope_dottedname_style(self, value, package): """ package.module.attr style """ module = getattr(package, '__name__', None) # package may be None if not module: module = None if value == '.': if module is None: raise ValueError( 'relative name %r irresolveable without package' % (value,) ) name = module.split('.') else: name = value.split('.') if not name[0]: if module is None: raise ValueError( 'relative name %r irresolveable without ' 'package' % (value,) ) module = module.split('.') name.pop(0) while not name[0]: module.pop() name.pop(0) name = module + name used = name.pop(0) found = __import__(used) for n in name: used += '.' + n try: found = getattr(found, n) except AttributeError: __import__(used) found = getattr(found, n) # pragma: no cover return found @implementer(IAssetDescriptor) class PkgResourcesAssetDescriptor(object): pkg_resources = pkg_resources def __init__(self, pkg_name, path): self.pkg_name = pkg_name self.path = path def absspec(self): return '%s:%s' % (self.pkg_name, self.path) def abspath(self): return os.path.abspath( self.pkg_resources.resource_filename(self.pkg_name, self.path)) def stream(self): return self.pkg_resources.resource_stream(self.pkg_name, self.path) def isdir(self): return self.pkg_resources.resource_isdir(self.pkg_name, self.path) def listdir(self): return self.pkg_resources.resource_listdir(self.pkg_name, self.path) def exists(self): return self.pkg_resources.resource_exists(self.pkg_name, self.path) @implementer(IAssetDescriptor) class FSAssetDescriptor(object): def __init__(self, path): self.path = os.path.abspath(path) def absspec(self): raise NotImplementedError def abspath(self): return self.path def stream(self): return open(self.path, 'rb') def isdir(self): return os.path.isdir(self.path) def listdir(self): return os.listdir(self.path) def exists(self): return os.path.exists(self.path) pyramid-1.6/pyramid/registry.py0000644000076500000240000002253212642137120017443 0ustar michaelstaff00000000000000import operator import threading 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, IIntrospector, IIntrospectable, ) empty = text_('') class Registry(Components, dict): """ A registry object is an :term:`application registry`. It is used by the framework itself to perform mappings of URLs to view callables, as well as servicing other various framework duties. A registry has its own internal API, but this API is rarely used by Pyramid application developers (it's usually only used by developers of the Pyramid framework). But it has a number of attributes that may be useful to application developers within application code, such as ``settings``, which is a dictionary containing application deployment settings. For information about the purpose and usage of the application registry, see :ref:`zca_chapter`. The application registry is usually accessed as ``request.registry`` in application code. """ # for optimization purposes, if no listeners are listening, don't try # to notify them has_listeners = False _settings = None def __init__(self, *arg, **kw): # add a registry-instance-specific lock, which is used when the lookup # cache is mutated self._lock = threading.Lock() # add a view lookup cache self._clear_view_lookup_cache() Components.__init__(self, *arg, **kw) def _clear_view_lookup_cache(self): self._view_lookup_cache = {} def __nonzero__(self): # 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 return result def registerSelfAdapter(self, required=None, provided=None, name=empty, info=empty, event=True): # registerAdapter analogue which always returns the object itself # when required is matched return self.registerAdapter(lambda x: x, required=required, provided=provided, name=name, info=info, event=event) def queryAdapterOrSelf(self, object, interface, default=None): # queryAdapter analogue which returns the object if it implements # the interface, otherwise it will return an adaptation to the # interface if not interface.providedBy(object): return self.queryAdapter(object, interface, default=default) return object def registerHandler(self, *arg, **kw): result = Components.registerHandler(self, *arg, **kw) self.has_listeners = True return result def notify(self, *events): if self.has_listeners: # iterating over subscribers assures they get executed [ _ for _ in self.subscribers(events, None) ] # backwards compatibility for code that wants to look up a settings # object via ``registry.getUtility(ISettings)`` def _get_settings(self): return self._settings def _set_settings(self, settings): self.registerUtility(settings, ISettings) self._settings = settings settings = property(_get_settings, _set_settings) @implementer(IIntrospector) class Introspector(object): def __init__(self): self._refs = {} self._categories = {} self._counter = 0 def add(self, intr): category = self._categories.setdefault(intr.category_name, {}) category[intr.discriminator] = intr category[intr.discriminator_hash] = intr intr.order = self._counter self._counter += 1 def get(self, category_name, discriminator, default=None): category = self._categories.setdefault(category_name, {}) intr = category.get(discriminator, default) return intr def get_category(self, category_name, default=None, sort_key=None): if sort_key is None: sort_key = operator.attrgetter('order') category = self._categories.get(category_name) if category is None: return default values = category.values() values = sorted(set(values), key=sort_key) return [ {'introspectable':intr, 'related':self.related(intr)} for intr in values ] def categorized(self, sort_key=None): L = [] for category_name in self.categories(): L.append((category_name, self.get_category(category_name, sort_key=sort_key))) return L def categories(self): return sorted(self._categories.keys()) def remove(self, category_name, discriminator): intr = self.get(category_name, discriminator) if intr is None: return L = self._refs.pop(intr, []) for d in L: L2 = self._refs[d] L2.remove(intr) category = self._categories[intr.category_name] del category[intr.discriminator] del category[intr.discriminator_hash] def _get_intrs_by_pairs(self, pairs): introspectables = [] for pair in pairs: category_name, discriminator = pair intr = self._categories.get(category_name, {}).get(discriminator) if intr is None: raise KeyError((category_name, discriminator)) introspectables.append(intr) return introspectables def relate(self, *pairs): introspectables = self._get_intrs_by_pairs(pairs) relatable = ((x,y) for x in introspectables for y in introspectables) for x, y in relatable: L = self._refs.setdefault(x, []) if x is not y and y not in L: L.append(y) def unrelate(self, *pairs): introspectables = self._get_intrs_by_pairs(pairs) relatable = ((x,y) for x in introspectables for y in introspectables) for x, y in relatable: L = self._refs.get(x, []) if y in L: L.remove(y) def related(self, intr): category_name, discriminator = intr.category_name, intr.discriminator intr = self._categories.get(category_name, {}).get(discriminator) if intr is None: raise KeyError((category_name, discriminator)) return self._refs.get(intr, []) @implementer(IIntrospectable) class Introspectable(dict): order = 0 # mutated by introspector.add action_info = None # mutated by self.register def __init__(self, category_name, discriminator, title, type_name): self.category_name = category_name self.discriminator = discriminator self.title = title self.type_name = type_name self._relations = [] def relate(self, category_name, discriminator): self._relations.append((True, category_name, discriminator)) def unrelate(self, category_name, discriminator): self._relations.append((False, category_name, discriminator)) def _assert_resolved(self): assert undefer(self.discriminator) is self.discriminator @property def discriminator_hash(self): self._assert_resolved() return hash(self.discriminator) def __hash__(self): self._assert_resolved() return hash((self.category_name,) + (self.discriminator,)) def __repr__(self): self._assert_resolved() return '<%s category %r, discriminator %r>' % (self.__class__.__name__, self.category_name, self.discriminator) def __nonzero__(self): return True __bool__ = __nonzero__ # py3 def register(self, introspector, action_info): self.discriminator = undefer(self.discriminator) self.action_info = action_info introspector.add(self) for relate, category_name, discriminator in self._relations: discriminator = undefer(discriminator) if relate: method = introspector.relate else: method = introspector.unrelate method( (self.category_name, self.discriminator), (category_name, discriminator) ) class Deferred(object): """ Can be used by a third-party configuration extender to wrap a :term:`discriminator` during configuration if an immediately hashable discriminator cannot be computed because it relies on unresolved values. The function should accept no arguments and should return a hashable discriminator.""" def __init__(self, func): self.func = func def resolve(self): return self.func() def undefer(v): """ Function which accepts an object and returns it unless it is a :class:`pyramid.registry.Deferred` instance. If it is an instance of that class, its ``resolve`` method is called, and the result of the method is returned.""" if isinstance(v, Deferred): v = v.resolve() return v class predvalseq(tuple): """ A subtype of tuple used to represent a sequence of predicate values """ pass global_registry = Registry('global') pyramid-1.6/pyramid/renderers.py0000644000076500000240000004615212642137120017570 0ustar michaelstaff00000000000000import contextlib import json import os import re from zope.interface import ( implementer, providedBy, ) from zope.interface.registry import Components from pyramid.interfaces import ( IJSONAdapter, IRendererFactory, IRendererInfo, ) from pyramid.compat import ( string_types, text_type, ) from pyramid.decorator import reify from pyramid.events import BeforeRender from pyramid.httpexceptions import HTTPBadRequest from pyramid.path import caller_package from pyramid.response import _get_response_factory from pyramid.threadlocal import get_current_registry # API def render(renderer_name, value, request=None, package=None): """ Using the renderer ``renderer_name`` (a template or a static renderer), render the value (or set of values) present in ``value``. Return the result of the renderer's ``__call__`` method (usually a string or Unicode). If the ``renderer_name`` refers to a file on disk, such as when the renderer is a template, it's usually best to supply the name as an :term:`asset specification` (e.g. ``packagename:path/to/template.pt``). You may supply a relative asset spec as ``renderer_name``. If the ``package`` argument is supplied, a relative renderer path will be converted to an absolute asset specification by combining the package ``package`` with the relative asset specification ``renderer_name``. If ``package`` is ``None`` (the default), the package name of the *caller* of this function will be used as the package. The ``value`` provided will be supplied as the input to the renderer. Usually, for template renderings, this should be a dictionary. For other renderers, this will need to be whatever sort of value the renderer expects. The 'system' values supplied to the renderer will include a basic set of top-level system names, such as ``request``, ``context``, ``renderer_name``, and ``view``. See :ref:`renderer_system_values` for the full list. If :term:`renderer globals` have been specified, these will also be used to augment the value. Supply a ``request`` parameter in order to provide the renderer with the most correct 'system' values (``request`` and ``context`` in particular). """ try: registry = request.registry except AttributeError: registry = None if package is None: package = caller_package() helper = RendererHelper(name=renderer_name, package=package, registry=registry) with temporary_response(request): result = helper.render(value, None, request=request) return result def render_to_response(renderer_name, value, request=None, package=None, response=None): """ Using the renderer ``renderer_name`` (a template or a static renderer), render the value (or set of values) using the result of the renderer's ``__call__`` method (usually a string or Unicode) as the response body. If the renderer name refers to a file on disk (such as when the renderer is a template), it's usually best to supply the name as a :term:`asset specification`. You may supply a relative asset spec as ``renderer_name``. If the ``package`` argument is supplied, a relative renderer name will be converted to an absolute asset specification by combining the package ``package`` with the relative asset specification ``renderer_name``. If you do not supply a ``package`` (or ``package`` is ``None``) the package name of the *caller* of this function will be used as the package. The ``value`` provided will be supplied as the input to the renderer. Usually, for template renderings, this should be a dictionary. For other renderers, this will need to be whatever sort of value the renderer expects. The 'system' values supplied to the renderer will include a basic set of top-level system names, such as ``request``, ``context``, ``renderer_name``, and ``view``. See :ref:`renderer_system_values` for the full list. If :term:`renderer globals` have been specified, these will also be used to argument the value. Supply a ``request`` parameter in order to provide the renderer with the most correct 'system' values (``request`` and ``context`` in particular). Keep in mind that any changes made to ``request.response`` prior to calling this function will not be reflected in the resulting response object. A new response object will be created for each call unless one is passed as the ``response`` argument. .. versionchanged:: 1.6 In previous versions, any changes made to ``request.response`` outside of this function call would affect the returned response. This is no longer the case. If you wish to send in a pre-initialized response then you may pass one in the ``response`` argument. """ try: registry = request.registry except AttributeError: registry = None if package is None: package = caller_package() helper = RendererHelper(name=renderer_name, package=package, registry=registry) with temporary_response(request): if response is not None: request.response = response result = helper.render_to_response(value, None, request=request) return result _marker = object() @contextlib.contextmanager def temporary_response(request): """ Temporarily delete request.response and restore it afterward. """ attrs = request.__dict__ if request is not None else {} saved_response = attrs.pop('response', _marker) try: yield finally: if saved_response is not _marker: attrs['response'] = saved_response elif 'response' in attrs: del attrs['response'] def get_renderer(renderer_name, package=None): """ Return the renderer object for the renderer ``renderer_name``. You may supply a relative asset spec as ``renderer_name``. If the ``package`` argument is supplied, a relative renderer name will be converted to an absolute asset specification by combining the package ``package`` with the relative asset specification ``renderer_name``. If ``package`` is ``None`` (the default), the package name of the *caller* of this function will be used as the package. """ if package is None: package = caller_package() helper = RendererHelper(name=renderer_name, package=package) return helper.renderer # concrete renderer factory implementations (also API) def string_renderer_factory(info): def _render(value, system): if not isinstance(value, string_types): value = str(value) request = system.get('request') if request is not None: response = request.response ct = response.content_type if ct == response.default_content_type: response.content_type = 'text/plain' return value return _render _marker = object() class JSON(object): """ Renderer that returns a JSON-encoded string. Configure a custom JSON renderer using the :meth:`~pyramid.config.Configurator.add_renderer` API at application startup time: .. code-block:: python from pyramid.config import Configurator config = Configurator() config.add_renderer('myjson', JSON(indent=4)) Once this renderer is registered as above, you can use ``myjson`` as the ``renderer=`` parameter to ``@view_config`` or :meth:`~pyramid.config.Configurator.add_view``: .. code-block:: python from pyramid.view import view_config @view_config(renderer='myjson') def myview(request): return {'greeting':'Hello world'} Custom objects can be serialized using the renderer by either implementing the ``__json__`` magic method, or by registering adapters with the renderer. See :ref:`json_serializing_custom_objects` for more information. .. note:: The default serializer uses ``json.JSONEncoder``. A different serializer can be specified via the ``serializer`` argument. Custom serializers should accept the object, a callback ``default``, and any extra ``kw`` keyword arguments passed during renderer construction. This feature isn't widely used but it can be used to replace the stock JSON serializer with, say, simplejson. If all you want to do, however, is serialize custom objects, you should use the method explained in :ref:`json_serializing_custom_objects` instead of replacing the serializer. .. versionadded:: 1.4 Prior to this version, there was no public API for supplying options to the underlying serializer without defining a custom renderer. """ def __init__(self, serializer=json.dumps, adapters=(), **kw): """ Any keyword arguments will be passed to the ``serializer`` function.""" self.serializer = serializer self.kw = kw self.components = Components() for type, adapter in adapters: self.add_adapter(type, adapter) def add_adapter(self, type_or_iface, adapter): """ When an object of the type (or interface) ``type_or_iface`` fails to automatically encode using the serializer, the renderer will use the adapter ``adapter`` to convert it into a JSON-serializable object. The adapter must accept two arguments: the object and the currently active request. .. code-block:: python class Foo(object): x = 5 def foo_adapter(obj, request): return obj.x renderer = JSON(indent=4) renderer.add_adapter(Foo, foo_adapter) 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) def __call__(self, info): """ Returns a plain JSON-encoded string with content-type ``application/json``. The content-type may be overridden by setting ``request.response.content_type``.""" def _render(value, system): request = system.get('request') if request is not None: response = request.response ct = response.content_type if ct == response.default_content_type: 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): def default(obj): if hasattr(obj, '__json__'): return obj.__json__(request) obj_iface = providedBy(obj) adapters = self.components.adapters result = adapters.lookup((obj_iface,), IJSONAdapter, default=_marker) if result is _marker: raise TypeError('%r is not JSON serializable' % (obj,)) return result(obj, request) return default json_renderer_factory = JSON() # bw compat JSONP_VALID_CALLBACK = re.compile(r"^[$a-z_][$0-9a-z_\.\[\]]+[^.]$", re.I) class JSONP(JSON): """ `JSONP `_ renderer factory helper which implements a hybrid json/jsonp renderer. JSONP is useful for making cross-domain AJAX requests. Configure a JSONP renderer using the :meth:`pyramid.config.Configurator.add_renderer` API at application startup time: .. code-block:: python from pyramid.config import Configurator config = Configurator() config.add_renderer('jsonp', JSONP(param_name='callback')) The class' constructor also accepts arbitrary keyword arguments. All keyword arguments except ``param_name`` are passed to the ``json.dumps`` function as its keyword arguments. .. code-block:: python from pyramid.config import Configurator 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. The arguments passed to this class' constructor mean the same thing as the arguments passed to :class:`pyramid.renderers.JSON` (including ``serializer`` and ``adapters``). Once this renderer is registered via :meth:`~pyramid.config.Configurator.add_renderer` as above, you can use ``jsonp`` as the ``renderer=`` parameter to ``@view_config`` or :meth:`pyramid.config.Configurator.add_view``: .. code-block:: python from pyramid.view import view_config @view_config(renderer='jsonp') def myview(request): return {'greeting':'Hello world'} When a view is called that uses the JSONP renderer: - If there is a parameter in the request's HTTP query string that matches the ``param_name`` of the registered JSONP renderer (by default, ``callback``), the renderer will return a JSONP response. - If there is no callback parameter in the request's query string, the renderer will return a 'plain' JSON response. .. versionadded:: 1.1 .. seealso:: See also :ref:`jsonp_renderer`. """ def __init__(self, param_name='callback', **kw): self.param_name = param_name JSON.__init__(self, **kw) def __call__(self, info): """ Returns JSONP-encoded string with content-type ``application/javascript`` if query parameter matching ``self.param_name`` is present in request.GET; otherwise returns plain-JSON encoded string with content-type ``application/json``""" def _render(value, system): request = system.get('request') default = self._make_default(request) val = self.serializer(value, default=default, **self.kw) ct = 'application/json' body = val if request is not None: callback = request.GET.get(self.param_name) if callback is not None: if not JSONP_VALID_CALLBACK.match(callback): raise HTTPBadRequest( 'Invalid JSONP callback function name.') ct = 'application/javascript' body = '/**/{0}({1});'.format(callback, val) response = request.response if response.content_type == response.default_content_type: response.content_type = ct return body return _render @implementer(IRendererInfo) class RendererHelper(object): def __init__(self, name=None, package=None, registry=None): if name and '.' in name: rtype = os.path.splitext(name)[1] else: # important.. must be a string; cannot be None; see issue 249 rtype = name or '' if registry is None: registry = get_current_registry() self.name = name self.package = package self.type = rtype self.registry = registry @reify def settings(self): settings = self.registry.settings if settings is None: settings = {} return settings @reify def renderer(self): factory = self.registry.queryUtility(IRendererFactory, name=self.type) if factory is None: raise ValueError( 'No such renderer factory %s' % str(self.type)) return factory(self) def get_renderer(self): return self.renderer def render_view(self, request, response, view, context): system = {'view':view, 'renderer_name':self.name, # b/c 'renderer_info':self, 'context':context, 'request':request, 'req':request, } return self.render_to_response(response, system, request=request) def render(self, value, system_values, request=None): renderer = self.renderer if system_values is None: system_values = { 'view':None, 'renderer_name':self.name, # b/c 'renderer_info':self, 'context':getattr(request, 'context', None), 'request':request, 'req':request, } system_values = BeforeRender(system_values, value) registry = self.registry registry.notify(system_values) result = renderer(value, system_values) return result def render_to_response(self, value, system_values, request=None): result = self.render(value, system_values, request=request) return self._make_response(result, request) def _make_response(self, result, request): # broken out of render_to_response as a separate method for testing # purposes response = getattr(request, 'response', None) if response is None: # request is None or request is not a pyramid.response.Response registry = self.registry response_factory = _get_response_factory(registry) response = response_factory(request) if result is not None: if isinstance(result, text_type): response.text = result elif isinstance(result, bytes): response.body = result elif hasattr(result, '__iter__'): response.app_iter = result else: response.body = result return response def clone(self, name=None, package=None, registry=None): if name is None: name = self.name if package is None: package = self.package if registry is None: registry = self.registry return self.__class__(name=name, package=package, registry=registry) class NullRendererHelper(RendererHelper): """ Special renderer helper that has render_* methods which simply return the value they are fed rather than converting them to response objects; useful for testing purposes and special case view configuration registrations that want to use the view configuration machinery but do not want actual rendering to happen .""" def __init__(self, name=None, package=None, registry=None): # we override the initializer to avoid calling get_current_registry # (it will return a reference to the global registry when this # thing is called at module scope; we don't want that). self.name = None self.package = None self.type = '' self.registry = None @property def settings(self): 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() pyramid-1.6/pyramid/request.py0000644000076500000240000002764712642137120017277 0ustar michaelstaff00000000000000from collections import deque import json from zope.interface import implementer from zope.interface.interface import InterfaceClass from webob import BaseRequest from pyramid.interfaces import ( IRequest, IRequestExtensions, IResponse, ISessionFactory, ) from pyramid.compat import ( text_, bytes_, native_, iteritems_, ) from pyramid.decorator import reify from pyramid.i18n import LocalizerRequestMixin from pyramid.response import Response, _get_response_factory from pyramid.security import ( AuthenticationAPIMixin, AuthorizationAPIMixin, ) from pyramid.url import URLMethodsMixin from pyramid.util import ( InstancePropertyHelper, InstancePropertyMixin, ) class TemplateContext(object): pass class CallbackMethodsMixin(object): @reify def finished_callbacks(self): return deque() @reify def response_callbacks(self): return deque() def add_response_callback(self, callback): """ Add a callback to the set of callbacks to be called by the :term:`router` at a point after a :term:`response` object is successfully created. :app:`Pyramid` does not have a global response object: this functionality allows an application to register an action to be performed against the response once one is created. A 'callback' is a callable which accepts two positional parameters: ``request`` and ``response``. For example: .. code-block:: python :linenos: def cache_callback(request, response): 'Set the cache_control max_age for the response' response.cache_control.max_age = 360 request.add_response_callback(cache_callback) Response callbacks are called in the order they're added (first-to-most-recently-added). No response callback is 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 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` router application. .. seealso:: See also :ref:`using_response_callbacks`. """ self.response_callbacks.append(callback) def _process_response_callbacks(self, response): callbacks = self.response_callbacks while callbacks: callback = callbacks.popleft() callback(self, response) def add_finished_callback(self, callback): """ Add a callback to the set of callbacks to be called unconditionally by the :term:`router` at the very end of request processing. ``callback`` is a callable which accepts a single positional parameter: ``request``. For example: .. code-block:: python :linenos: import transaction def commit_callback(request): '''commit or abort the transaction associated with request''' if request.exception is not None: transaction.abort() else: transaction.commit() request.add_finished_callback(commit_callback) Finished callbacks are called in the order they're added ( first- to most-recently- added). Finished callbacks (unlike response callbacks) are *always* called, even if an exception happens in application code that prevents a response from being generated. The set of finished callbacks associated with a request are called *very late* in the processing of that request; they are essentially the last thing called by the :term:`router`. They are called after response processing has already occurred in a top-level ``finally:`` block within the router request processing code. As a result, mutations performed to the ``request`` provided to a finished callback will have no meaningful effect, because response processing will have already occurred, and the request's scope will expire almost immediately after all finished callbacks have been processed. Errors raised by finished callbacks are not handled specially. They will be propagated to the caller of the :app:`Pyramid` router application. .. seealso:: See also :ref:`using_finished_callbacks`. """ self.finished_callbacks.append(callback) def _process_finished_callbacks(self): callbacks = self.finished_callbacks while callbacks: callback = callbacks.popleft() callback(self) @implementer(IRequest) class Request( BaseRequest, URLMethodsMixin, CallbackMethodsMixin, InstancePropertyMixin, LocalizerRequestMixin, AuthenticationAPIMixin, AuthorizationAPIMixin, ): """ A subclass of the :term:`WebOb` Request class. An instance of this class is created by the :term:`router` and is provided to a view callable (and to other subsystems) as the ``request`` argument. The documentation below (save for the ``add_response_callback`` and ``add_finished_callback`` methods, which are defined in this subclass itself, and the attributes ``context``, ``registry``, ``root``, ``subpath``, ``traversed``, ``view_name``, ``virtual_root`` , and ``virtual_root_path``, each of which is added to the request by the :term:`router` at request ingress time) are autogenerated from the WebOb source code used when this documentation was generated. Due to technical constraints, we can't yet display the WebOb version number from which this documentation is autogenerated, but it will be the 'prevailing WebOb version' at the time of the release of this :app:`Pyramid` version. See http://webob.org/ for further information. """ exception = None exc_info = None matchdict = None matched_route = None request_iface = IRequest ResponseClass = Response @reify def tmpl_context(self): # docs-deprecated template context for Pylons-like apps; do not # remove. return TemplateContext() @reify def session(self): """ Obtain the :term:`session` object associated with this request. If a :term:`session factory` has not been registered during application configuration, a :class:`pyramid.exceptions.ConfigurationError` will be raised""" factory = self.registry.queryUtility(ISessionFactory) if factory is None: raise AttributeError( 'No session factory registered ' '(see the Sessions chapter of the Pyramid documentation)') return factory(self) @reify def response(self): """This attribute is actually a "reified" property which returns an instance of the :class:`pyramid.response.Response`. class. The response object returned does not exist until this attribute is accessed. Subsequent accesses will return the same Response object. The ``request.response`` API is used by renderers. A render obtains the response object it will return from a view that uses that renderer by accessing ``request.response``. Therefore, it's possible to use the ``request.response`` API to set up a response object with "the right" attributes (e.g. by calling ``request.response.set_cookie()``) within a view that uses a renderer. Mutations to this response object will be preserved in the response sent to the client.""" 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 response object, ``False`` otherwise.""" if ob.__class__ is Response: return True registry = self.registry adapted = registry.queryAdapterOrSelf(ob, IResponse) if adapted is None: return False return adapted is ob @property def json_body(self): return json.loads(text_(self.body, self.charset)) def route_request_iface(name, bases=()): # zope.interface treats the __name__ as the __doc__ and changes __name__ # to None for interfaces that contain spaces if you do not pass a # nonempty __doc__ (insane); see # zope.interface.interface.Element.__init__ and # https://github.com/Pylons/pyramid/issues/232; as a result, always pass # __doc__ to the InterfaceClass constructor. iface = InterfaceClass('%s_IRequest' % name, bases=bases, __doc__="route_request_iface-generated interface") # for exception view lookups iface.combined = InterfaceClass( '%s_combined_IRequest' % name, bases=(iface, IRequest), __doc__ = 'route_request_iface-generated combined interface') return iface def add_global_response_headers(request, headerlist): def add_headers(request, response): for k, v in headerlist: response.headerlist.append((k, v)) request.add_response_callback(add_headers) def call_app_with_subpath_as_path_info(request, app): # Copy the request. Use the source request's subpath (if it exists) as # the new request's PATH_INFO. Set the request copy's SCRIPT_NAME to the # prefix before the subpath. Call the application with the new request # and return a response. # # Postconditions: # - SCRIPT_NAME and PATH_INFO are empty or start with / # - At least one of SCRIPT_NAME or PATH_INFO are set. # - SCRIPT_NAME is not '/' (it should be '', and PATH_INFO should # be '/'). environ = request.environ script_name = environ.get('SCRIPT_NAME', '') path_info = environ.get('PATH_INFO', '/') subpath = list(getattr(request, 'subpath', ())) new_script_name = '' # compute new_path_info new_path_info = '/' + '/'.join([native_(x.encode('utf-8'), 'latin-1') for x in subpath]) if new_path_info != '/': # don't want a sole double-slash if path_info != '/': # if orig path_info is '/', we're already done if path_info.endswith('/'): # readd trailing slash stripped by subpath (traversal) # conversion new_path_info += '/' # compute new_script_name workback = (script_name + path_info).split('/') tmp = [] while workback: if tmp == subpath: break el = workback.pop() if el: tmp.insert(0, text_(bytes_(el, 'latin-1'), 'utf-8')) # strip all trailing slashes from workback to avoid appending undue slashes # to end of script_name while workback and (workback[-1] == ''): workback = workback[:-1] new_script_name = '/'.join(workback) new_request = request.copy() new_request.environ['SCRIPT_NAME'] = new_script_name new_request.environ['PATH_INFO'] = new_path_info return new_request.get_response(app) def apply_request_extensions(request, extensions=None): """Apply request extensions (methods and properties) to an instance of :class:`pyramid.interfaces.IRequest`. This method is dependent on the ``request`` containing a properly initialized registry. After invoking this method, the ``request`` should have the methods and properties that were defined using :meth:`pyramid.config.Configurator.add_request_method`. """ if extensions is None: extensions = request.registry.queryUtility(IRequestExtensions) if extensions is not None: for name, fn in iteritems_(extensions.methods): method = fn.__get__(request, request.__class__) setattr(request, name, method) InstancePropertyHelper.apply_properties( request, extensions.descriptors) pyramid-1.6/pyramid/resource.py0000644000076500000240000000036512234375161017430 0ustar michaelstaff00000000000000""" Backwards compatibility shim module (forever). """ from pyramid.asset import * # b/w compat resolve_resource_spec = resolve_asset_spec resource_spec_from_abspath = asset_spec_from_abspath abspath_from_resource_spec = abspath_from_asset_spec pyramid-1.6/pyramid/response.py0000644000076500000240000001321312524266531017435 0ustar michaelstaff00000000000000import mimetypes from os.path import ( getmtime, getsize, ) import venusian from webob import Response as _Response from zope.interface import implementer from pyramid.interfaces import IResponse, IResponseFactory def init_mimetypes(mimetypes): # this is a function so it can be unittested if hasattr(mimetypes, 'init'): mimetypes.init() return True return False # See http://bugs.python.org/issue5853 which is a recursion bug # that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix # has been applied on the Python 2 trunk). init_mimetypes(mimetypes) _BLOCK_SIZE = 4096 * 64 # 256K @implementer(IResponse) class Response(_Response): pass class FileResponse(Response): """ A Response object that can be used to serve a static file from disk simply. ``path`` is a file path on disk. ``request`` must be a Pyramid :term:`request` object. Note that a request *must* be passed if the response is meant to attempt to use the ``wsgi.file_wrapper`` feature of the web server that you're using to serve your Pyramid application. ``cache_max_age`` is the number of seconds that should be used to HTTP cache this response. ``content_type`` is the content_type of the response. ``content_encoding`` is the content_encoding of the response. It's generally safe to leave this set to ``None`` if you're serving a binary file. This argument will be ignored if you also leave ``content-type`` as ``None``. """ def __init__(self, path, request=None, cache_max_age=None, content_type=None, content_encoding=None): 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' # 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 if request is not None: environ = request.environ if 'wsgi.file_wrapper' in environ: app_iter = environ['wsgi.file_wrapper'](f, _BLOCK_SIZE) if app_iter is None: app_iter = FileIter(f, _BLOCK_SIZE) self.app_iter = app_iter # assignment of content_length must come after assignment of app_iter self.content_length = content_length if cache_max_age is not None: self.cache_expires = cache_max_age class FileIter(object): """ A fixed-block-size iterator for use as a WSGI app_iter. ``file`` is a Python file pointer (or at least an object with a ``read`` method that takes a size hint). ``block_size`` is an optional block size for iteration. """ def __init__(self, file, block_size=_BLOCK_SIZE): self.file = file self.block_size = block_size def __iter__(self): return self def next(self): val = self.file.read(self.block_size) if not val: raise StopIteration return val __next__ = next # py3 def close(self): self.file.close() class response_adapter(object): """ Decorator activated via a :term:`scan` which treats the function being decorated as a :term:`response adapter` for the set of types or interfaces passed as ``*types_or_ifaces`` to the decorator constructor. For example, if you scan the following response adapter: .. code-block:: python from pyramid.response import Response from pyramid.response import response_adapter @response_adapter(int) def myadapter(i): return Response(status=i) You can then return an integer from your view callables, and it will be converted into a response with the integer as the status code. More than one type or interface can be passed as a constructor argument. The decorated response adapter will be called for each type or interface. .. code-block:: python import json from pyramid.response import Response from pyramid.response import response_adapter @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: .. code-block:: python from pyramid.config import Configurator config = Configurator() config.scan('somepackage_containing_adapters') """ venusian = venusian # for unit testing def __init__(self, *types_or_ifaces): self.types_or_ifaces = types_or_ifaces def register(self, scanner, name, wrapped): config = scanner.config for type_or_iface in self.types_or_ifaces: config.add_response_adapter(wrapped, type_or_iface) 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 pyramid-1.6/pyramid/router.py0000644000076500000240000001670112524266531017124 0ustar michaelstaff00000000000000from zope.interface import ( implementer, providedBy, ) from pyramid.interfaces import ( IDebugLogger, IRequest, IRequestExtensions, IRootFactory, IRouteRequest, IRouter, IRequestFactory, IRoutesMapper, ITraverser, ITweens, ) from pyramid.events import ( ContextFound, NewRequest, NewResponse, ) from pyramid.httpexceptions import HTTPNotFound from pyramid.request import Request from pyramid.view import _call_view from pyramid.request import apply_request_extensions from pyramid.threadlocal import manager from pyramid.traversal import ( DefaultRootFactory, ResourceTreeTraverser, ) from pyramid.tweens import excview_tween_factory @implementer(IRouter) class Router(object): debug_notfound = False debug_routematch = False threadlocal_manager = manager def __init__(self, registry): q = registry.queryUtility self.logger = q(IDebugLogger) self.root_factory = q(IRootFactory, default=DefaultRootFactory) self.routes_mapper = q(IRoutesMapper) self.request_factory = q(IRequestFactory, default=Request) self.request_extensions = q(IRequestExtensions) tweens = q(ITweens) if tweens is None: tweens = excview_tween_factory self.orig_handle_request = self.handle_request self.handle_request = tweens(self.handle_request, registry) self.root_policy = self.root_factory # b/w compat self.registry = registry settings = registry.settings if settings is not None: self.debug_notfound = settings['debug_notfound'] self.debug_routematch = settings['debug_routematch'] def handle_request(self, request): attrs = request.__dict__ registry = attrs['registry'] request.request_iface = IRequest context = None routes_mapper = self.routes_mapper debug_routematch = self.debug_routematch adapters = registry.adapters has_listeners = registry.has_listeners notify = registry.notify logger = self.logger has_listeners and notify(NewRequest(request)) # find the root object root_factory = self.root_factory if routes_mapper is not None: info = routes_mapper(request) match, route = info['match'], info['route'] if route is None: if debug_routematch: msg = ('no route matched for url %s' % request.url) logger and logger.debug(msg) else: attrs['matchdict'] = match attrs['matched_route'] = route if debug_routematch: msg = ( 'route matched for url %s; ' 'route_name: %r, ' 'path_info: %r, ' 'pattern: %r, ' 'matchdict: %r, ' 'predicates: %r' % ( request.url, route.name, request.path_info, route.pattern, match, ', '.join([p.text() for p in route.predicates])) ) logger and logger.debug(msg) request.request_iface = registry.queryUtility( IRouteRequest, name=route.name, default=IRequest) root_factory = route.factory or self.root_factory root = root_factory(request) attrs['root'] = root # find a context traverser = adapters.queryAdapter(root, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(root) tdict = traverser(request) context, view_name, subpath, traversed, vroot, vroot_path = ( tdict['context'], tdict['view_name'], tdict['subpath'], tdict['traversed'], tdict['virtual_root'], tdict['virtual_root_path'] ) attrs.update(tdict) has_listeners and notify(ContextFound(request)) # find a view callable context_iface = providedBy(context) response = _call_view( registry, request, context, context_iface, view_name ) if response is None: if self.debug_notfound: msg = ( 'debug_notfound of url %s; path_info: %r, ' 'context: %r, view_name: %r, subpath: %r, ' 'traversed: %r, root: %r, vroot: %r, ' 'vroot_path: %r' % ( request.url, request.path_info, context, view_name, subpath, traversed, root, vroot, vroot_path) ) logger and logger.debug(msg) else: msg = request.path_info raise HTTPNotFound(msg) return response def invoke_subrequest(self, request, use_tweens=False): """Obtain a response object from the Pyramid application based on information in the ``request`` object provided. The ``request`` object must be an object that implements the Pyramid request interface (such as a :class:`pyramid.request.Request` instance). If ``use_tweens`` is ``True``, the request will be sent to the :term:`tween` in the tween stack closest to the request ingress. If ``use_tweens`` is ``False``, the request will be sent to the main router handler, and no tweens will be invoked. See the API for pyramid.request for complete documentation. """ registry = self.registry has_listeners = self.registry.has_listeners notify = self.registry.notify threadlocals = {'registry':registry, 'request':request} manager = self.threadlocal_manager manager.push(threadlocals) request.registry = registry request.invoke_subrequest = self.invoke_subrequest if use_tweens: handle_request = self.handle_request else: handle_request = self.orig_handle_request try: try: extensions = self.request_extensions if extensions is not None: apply_request_extensions(request, extensions=extensions) response = handle_request(request) if request.response_callbacks: request._process_response_callbacks(response) has_listeners and notify(NewResponse(request, response)) return response finally: if request.finished_callbacks: request._process_finished_callbacks() finally: manager.pop() def __call__(self, environ, start_response): """ Accept ``environ`` and ``start_response``; create a :term:`request` and route the request to a :app:`Pyramid` view based on introspection of :term:`view configuration` within the application registry; call ``start_response`` and return an iterable. """ request = self.request_factory(environ) response = self.invoke_subrequest(request, use_tweens=True) return response(request.environ, start_response) pyramid-1.6/pyramid/scaffolds/0000755000076500000240000000000012642137501017164 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/__init__.py0000644000076500000240000000447012575217552021314 0ustar michaelstaff00000000000000import binascii import os from textwrap import dedent from pyramid.compat import native_ from pyramid.scaffolds.template import Template # API class PyramidTemplate(Template): """ A class that can be used as a base class for Pyramid scaffolding templates. """ def pre(self, command, output_dir, vars): """ Overrides :meth:`pyramid.scaffolds.template.Template.pre`, adding several variables to the default variables list (including ``random_string``, and ``package_logger``). It also prevents common misnamings (such as naming a package "site" or naming a package logger "root". """ vars['random_string'] = native_(binascii.hexlify(os.urandom(20))) package_logger = vars['package'] if package_logger == 'root': # Rename the app logger in the rare case a project is named 'root' package_logger = 'app' vars['package_logger'] = package_logger return Template.pre(self, command, output_dir, vars) def post(self, command, output_dir, vars): # pragma: no cover """ Overrides :meth:`pyramid.scaffolds.template.Template.post`, to print "Welcome to Pyramid. Sorry for the convenience." after a successful scaffolding rendering.""" separator = "=" * 79 msg = dedent( """ %(separator)s Tutorials: http://docs.pylonsproject.org/projects/pyramid_tutorials Documentation: http://docs.pylonsproject.org/projects/pyramid Twitter (tips & updates): http://twitter.com/pylons Mailing List: http://groups.google.com/group/pylons-discuss Welcome to Pyramid. Sorry for the convenience. %(separator)s """ % {'separator': separator}) self.out(msg) return Template.post(self, command, output_dir, vars) def out(self, msg): # pragma: no cover (replaceable testing hook) print(msg) class StarterProjectTemplate(PyramidTemplate): _template_dir = 'starter' summary = 'Pyramid starter project' class ZODBProjectTemplate(PyramidTemplate): _template_dir = 'zodb' summary = 'Pyramid ZODB project using traversal' class AlchemyProjectTemplate(PyramidTemplate): _template_dir = 'alchemy' summary = 'Pyramid SQLAlchemy project using url dispatch' pyramid-1.6/pyramid/scaffolds/alchemy/0000755000076500000240000000000012642137501020606 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/alchemy/+package+/0000755000076500000240000000000012642137501022327 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/alchemy/+package+/__init__.py0000644000076500000240000000113512621276257024451 0ustar michaelstaff00000000000000from pyramid.config import Configurator from sqlalchemy import engine_from_config from .models import ( DBSession, Base, ) def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/pyramid/scaffolds/alchemy/+package+/models.py0000644000076500000240000000110312621276257024170 0ustar michaelstaff00000000000000from sqlalchemy import ( Column, Index, Integer, Text, ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import ( scoped_session, sessionmaker, ) from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class MyModel(Base): __tablename__ = 'models' id = Column(Integer, primary_key=True) name = Column(Text) value = Column(Integer) Index('my_index', MyModel.name, unique=True, mysql_length=255) pyramid-1.6/pyramid/scaffolds/alchemy/+package+/scripts/0000755000076500000240000000000012642137501024016 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/alchemy/+package+/scripts/__init__.py0000644000076500000240000000001212234375161026123 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/scaffolds/alchemy/+package+/scripts/initializedb.py0000644000076500000240000000161212621276257027050 0ustar michaelstaff00000000000000import os import sys import transaction from sqlalchemy import engine_from_config from pyramid.paster import ( get_appsettings, setup_logging, ) from pyramid.scripts.common import parse_vars from ..models import ( DBSession, MyModel, Base, ) def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s [var=value]\n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) < 2: usage(argv) config_uri = argv[1] options = parse_vars(argv[2:]) setup_logging(config_uri) settings = get_appsettings(config_uri, options=options) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=1) DBSession.add(model) pyramid-1.6/pyramid/scaffolds/alchemy/+package+/static/0000755000076500000240000000000012642137501023616 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/alchemy/+package+/static/pyramid-16x16.png0000644000076500000240000000244712520062551026560 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/pyramid/scaffolds/alchemy/+package+/static/pyramid.png0000644000076500000240000003114512520062551025772 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/pyramid/scaffolds/alchemy/+package+/static/theme.css0000644000076500000240000000556612575217552025460 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a, a { color: #f2b7bd; text-decoration: underline; } .starter-template .links ul li a:hover, a:hover { color: #ffffff; text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/pyramid/scaffolds/alchemy/+package+/static/theme.min.css0000644000076500000240000000451012575217552026226 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a,a{color:#f2b7bd;text-decoration:underline}.starter-template .links ul li a:hover,a:hover{color:#fff;text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}} pyramid-1.6/pyramid/scaffolds/alchemy/+package+/templates/0000755000076500000240000000000012642137501024325 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl0000644000076500000240000000603112524266531030114 0ustar michaelstaff00000000000000 Alchemy Scaffold for The Pyramid Web Framework

Pyramid Alchemy scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework {{pyramid_version}}.

pyramid-1.6/pyramid/scaffolds/alchemy/+package+/tests.py_tmpl0000644000076500000240000000273612621276257025120 0ustar michaelstaff00000000000000import unittest import transaction from pyramid import testing from .models import DBSession class TestMyViewSuccessCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_passing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], '{{project}}') class TestMyViewFailureCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) def tearDown(self): DBSession.remove() testing.tearDown() def test_failing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info.status_int, 500)pyramid-1.6/pyramid/scaffolds/alchemy/+package+/views.py_tmpl0000644000076500000240000000210612621276257025102 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.view import view_config from sqlalchemy.exc import DBAPIError from .models import ( DBSession, MyModel, ) @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): try: one = DBSession.query(MyModel).filter(MyModel.name == 'one').first() except DBAPIError: return Response(conn_err_msg, content_type='text/plain', status_int=500) return {'one': one, 'project': '{{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 environment's "bin" directory for this script and try to run it. 2. Your database server may not be running. Check that the database server referred to by the "sqlalchemy.url" setting in your "development.ini" file is running. After you fix the problem, please restart the Pyramid application to try it again. """ pyramid-1.6/pyramid/scaffolds/alchemy/CHANGES.txt_tmpl0000644000076500000240000000003412234375161023453 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/pyramid/scaffolds/alchemy/development.ini_tmpl0000644000076500000240000000270012642137120024661 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}}, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_{{package_logger}}] level = DEBUG handlers = qualname = {{package}} [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/alchemy/MANIFEST.in_tmpl0000644000076500000240000000020612234375161023401 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/pyramid/scaffolds/alchemy/production.ini_tmpl0000644000076500000240000000237612524266531024546 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/{{project}}.sqlite [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}}, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_{{package_logger}}] level = WARN handlers = qualname = {{package}} [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/alchemy/README.txt_tmpl0000644000076500000240000000035712520062551023342 0ustar michaelstaff00000000000000{{project}} README ================== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_{{project}}_db development.ini - $VENV/bin/pserve development.ini pyramid-1.6/pyramid/scaffolds/alchemy/setup.py_tmpl0000644000076500000240000000230612520062551023352 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'SQLAlchemy', 'transaction', 'zope.sqlalchemy', 'waitress', ] setup(name='{{project}}', version='0.0', description='{{project}}', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='{{package}}', install_requires=requires, entry_points="""\ [paste.app_factory] main = {{package}}:main [console_scripts] initialize_{{project}}_db = {{package}}.scripts.initializedb:main """, ) pyramid-1.6/pyramid/scaffolds/copydir.py0000644000076500000240000002447412642137120021217 0ustar michaelstaff00000000000000# (c) 2005 Ian Bicking and contributors; written for Paste # (http://pythonpaste.org) Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php import os import sys import pkg_resources from pyramid.compat import ( input_, native_, url_quote as compat_url_quote, escape, ) fsenc = sys.getfilesystemencoding() class SkipTemplate(Exception): """ Raised to indicate that the template should not be copied over. Raise this exception during the substitution of your template """ def copy_dir(source, dest, vars, verbosity, simulate, indent=0, sub_vars=True, interactive=False, overwrite=True, template_renderer=None, out_=sys.stdout): """ Copies the ``source`` directory to the ``dest`` directory. ``vars``: A dictionary of variables to use in any substitutions. ``verbosity``: Higher numbers will show more about what is happening. ``simulate``: If true, then don't actually *do* anything. ``indent``: Indent any messages by this amount. ``sub_vars``: If true, variables in ``_tmpl`` files and ``+var+`` in filenames will be substituted. ``overwrite``: If false, then don't every overwrite anything. ``interactive``: If you are overwriting a file and interactive is true, then ask before overwriting. ``template_renderer``: This is a function for rendering templates (if you don't want to use string.Template). It should have the signature ``template_renderer(content_as_string, vars_as_dict, filename=filename)``. """ def out(msg): out_.write(msg) out_.write('\n') out_.flush() # This allows you to use a leading +dot+ in filenames which would # otherwise be skipped because leading dots make the file hidden: vars.setdefault('dot', '.') vars.setdefault('plus', '+') use_pkg_resources = isinstance(source, tuple) if use_pkg_resources: names = sorted(pkg_resources.resource_listdir(source[0], source[1])) else: names = sorted(os.listdir(source)) pad = ' '*(indent*2) if not os.path.exists(dest): if verbosity >= 1: out('%sCreating %s/' % (pad, dest)) if not simulate: makedirs(dest, verbosity=verbosity, pad=pad) elif verbosity >= 2: out('%sDirectory %s exists' % (pad, dest)) for name in names: if use_pkg_resources: full = '/'.join([source[1], name]) else: full = os.path.join(source, name) reason = should_skip_file(name) if reason: if verbosity >= 2: reason = pad + reason % {'filename': full} out(reason) continue # pragma: no cover if sub_vars: dest_full = os.path.join(dest, substitute_filename(name, vars)) sub_file = False if dest_full.endswith('_tmpl'): dest_full = dest_full[:-5] sub_file = sub_vars if use_pkg_resources and pkg_resources.resource_isdir(source[0], full): if verbosity: out('%sRecursing into %s' % (pad, os.path.basename(full))) copy_dir((source[0], full), dest_full, vars, verbosity, simulate, indent=indent+1, sub_vars=sub_vars, interactive=interactive, overwrite=overwrite, template_renderer=template_renderer, out_=out_) continue elif not use_pkg_resources and os.path.isdir(full): if verbosity: out('%sRecursing into %s' % (pad, os.path.basename(full))) copy_dir(full, dest_full, vars, verbosity, simulate, indent=indent+1, sub_vars=sub_vars, interactive=interactive, overwrite=overwrite, template_renderer=template_renderer, out_=out_) continue elif use_pkg_resources: content = pkg_resources.resource_string(source[0], full) else: f = open(full, 'rb') content = f.read() f.close() if sub_file: try: content = substitute_content( content, vars, filename=full, template_renderer=template_renderer ) except SkipTemplate: continue # pragma: no cover if content is None: continue # pragma: no cover already_exists = os.path.exists(dest_full) if already_exists: f = open(dest_full, 'rb') old_content = f.read() f.close() if old_content == content: if verbosity: out('%s%s already exists (same content)' % (pad, dest_full)) continue # pragma: no cover if interactive: if not query_interactive( native_(full, fsenc), native_(dest_full, fsenc), native_(content, fsenc), native_(old_content, fsenc), simulate=simulate, out_=out_): continue elif not overwrite: continue # pragma: no cover if verbosity and use_pkg_resources: out('%sCopying %s to %s' % (pad, full, dest_full)) elif verbosity: out( '%sCopying %s to %s' % (pad, os.path.basename(full), dest_full)) if not simulate: f = open(dest_full, 'wb') f.write(content) f.close() def should_skip_file(name): """ Checks if a file should be skipped based on its name. If it should be skipped, returns the reason, otherwise returns None. """ if name.startswith('.'): return 'Skipping hidden file %(filename)s' if name.endswith(('~', '.bak')): return 'Skipping backup file %(filename)s' if name.endswith(('.pyc', '.pyo')): return 'Skipping %s file ' % os.path.splitext(name)[1] + '%(filename)s' if name.endswith('$py.class'): return 'Skipping $py.class file %(filename)s' if name in ('CVS', '_darcs'): return 'Skipping version control directory %(filename)s' return None # Overridden on user's request: all_answer = None def query_interactive(src_fn, dest_fn, src_content, dest_content, simulate, out_=sys.stdout): def out(msg): out_.write(msg) out_.write('\n') out_.flush() global all_answer from difflib import unified_diff, context_diff u_diff = list(unified_diff( dest_content.splitlines(), src_content.splitlines(), dest_fn, src_fn)) c_diff = list(context_diff( dest_content.splitlines(), src_content.splitlines(), dest_fn, src_fn)) added = len([l for l in u_diff if l.startswith('+') and not l.startswith('+++')]) removed = len([l for l in u_diff if l.startswith('-') and not l.startswith('---')]) if added > removed: msg = '; %i lines added' % (added-removed) elif removed > added: msg = '; %i lines removed' % (removed-added) else: msg = '' out('Replace %i bytes with %i bytes (%i/%i lines changed%s)' % ( len(dest_content), len(src_content), removed, len(dest_content.splitlines()), msg)) prompt = 'Overwrite %s [y/n/d/B/?] ' % dest_fn while 1: if all_answer is None: response = input_(prompt).strip().lower() else: response = all_answer if not response or response[0] == 'b': import shutil new_dest_fn = dest_fn + '.bak' n = 0 while os.path.exists(new_dest_fn): n += 1 new_dest_fn = dest_fn + '.bak' + str(n) out('Backing up %s to %s' % (dest_fn, new_dest_fn)) if not simulate: shutil.copyfile(dest_fn, new_dest_fn) return True elif response.startswith('all '): rest = response[4:].strip() if not rest or rest[0] not in ('y', 'n', 'b'): out(query_usage) continue response = all_answer = rest[0] if response[0] == 'y': return True elif response[0] == 'n': return False elif response == 'dc': out('\n'.join(c_diff)) elif response[0] == 'd': out('\n'.join(u_diff)) else: out(query_usage) query_usage = """\ Responses: Y(es): Overwrite the file with the new content. N(o): Do not overwrite the file. D(iff): Show a unified diff of the proposed changes (dc=context diff) B(ackup): Save the current file contents to a .bak file (and overwrite) Type "all Y/N/B" to use Y/N/B for answer to all future questions """ def makedirs(dir, verbosity, pad): parent = os.path.dirname(os.path.abspath(dir)) if not os.path.exists(parent): makedirs(parent, verbosity, pad) # pragma: no cover os.mkdir(dir) def substitute_filename(fn, vars): for var, value in vars.items(): fn = fn.replace('+%s+' % var, str(value)) return fn def substitute_content(content, vars, filename='', template_renderer=None): v = standard_vars.copy() v.update(vars) return template_renderer(content, v, filename=filename) def html_quote(s): if s is None: return '' return escape(str(s), 1) def url_quote(s): if s is None: return '' return compat_url_quote(str(s)) def test(conf, true_cond, false_cond=None): if conf: return true_cond else: return false_cond def skip_template(condition=True, *args): """ Raise SkipTemplate, which causes copydir to skip the template being processed. If you pass in a condition, only raise if that condition is true (allows you to use this with string.Template) If you pass any additional arguments, they will be used to instantiate SkipTemplate (generally use like ``skip_template(license=='GPL', 'Skipping file; not using GPL')``) """ if condition: raise SkipTemplate(*args) standard_vars = { 'nothing': None, 'html_quote': html_quote, 'url_quote': url_quote, 'empty': '""', 'test': test, 'repr': repr, 'str': str, 'bool': bool, 'SkipTemplate': SkipTemplate, 'skip_template': skip_template, } pyramid-1.6/pyramid/scaffolds/starter/0000755000076500000240000000000012642137501020650 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/starter/+package+/0000755000076500000240000000000012642137501022371 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/starter/+package+/__init__.py0000644000076500000240000000057712520062551024510 0ustar michaelstaff00000000000000from pyramid.config import Configurator def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(settings=settings) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_route('home', '/') config.scan() return config.make_wsgi_app() pyramid-1.6/pyramid/scaffolds/starter/+package+/static/0000755000076500000240000000000012642137501023660 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/starter/+package+/static/pyramid-16x16.png0000644000076500000240000000244712520062551026622 0ustar michaelstaff00000000000000‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<#iTXtXML:com.adobe.xmp n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/pyramid/scaffolds/starter/+package+/static/pyramid.png0000644000076500000240000003114512520062551026034 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/pyramid/scaffolds/starter/+package+/static/theme.css0000644000076500000240000000547212520062551025501 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a { color: #ffffff; } .starter-template .links ul li a:hover { text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/pyramid/scaffolds/starter/+package+/static/theme.min.css0000644000076500000240000000442512520062551026260 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/pyramid/scaffolds/starter/+package+/templates/0000755000076500000240000000000012642137501024367 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl0000644000076500000240000000603112524266531030156 0ustar michaelstaff00000000000000 Starter Scaffold for The Pyramid Web Framework

Pyramid Starter scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework {{pyramid_version}}.

pyramid-1.6/pyramid/scaffolds/starter/+package+/tests.py_tmpl0000644000076500000240000000060412517346416025151 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], '{{project}}') pyramid-1.6/pyramid/scaffolds/starter/+package+/views.py_tmpl0000644000076500000240000000024712517346416025147 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(route_name='home', renderer='templates/mytemplate.pt') def my_view(request): return {'project': '{{project}}'} pyramid-1.6/pyramid/scaffolds/starter/CHANGES.txt_tmpl0000644000076500000240000000003412234375161023515 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/pyramid/scaffolds/starter/development.ini_tmpl0000644000076500000240000000221212642137120024721 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_{{package_logger}}] level = DEBUG handlers = qualname = {{package}} [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/starter/MANIFEST.in_tmpl0000644000076500000240000000020612234375161023443 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/pyramid/scaffolds/starter/production.ini_tmpl0000644000076500000240000000173212524266531024603 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_{{package_logger}}] level = WARN handlers = qualname = {{package}} [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/starter/README.txt_tmpl0000644000076500000240000000002312234375161023400 0ustar michaelstaff00000000000000{{project}} README pyramid-1.6/pyramid/scaffolds/starter/setup.py_tmpl0000644000076500000240000000205512520062551023415 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'waitress', ] setup(name='{{project}}', version='0.0', description='{{project}}', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="{{package}}", entry_points="""\ [paste.app_factory] main = {{package}}:main """, ) pyramid-1.6/pyramid/scaffolds/template.py0000644000076500000240000001350212520062551021347 0ustar michaelstaff00000000000000# (c) 2005 Ian Bicking and contributors; written for Paste # (http://pythonpaste.org) Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php import re import sys import os from pyramid.compat import ( native_, bytes_, ) from pyramid.scaffolds import copydir fsenc = sys.getfilesystemencoding() class Template(object): """ Inherit from this base class and override methods to use the Pyramid scaffolding system.""" copydir = copydir # for testing _template_dir = None def __init__(self, name): self.name = name def render_template(self, content, vars, filename=None): """ Return a bytestring representing a templated file based on the input (content) and the variable names defined (vars). ``filename`` is used for exception reporting.""" # this method must not be named "template_renderer" fbo of extension # scaffolds that need to work under pyramid 1.2 and 1.3, and which # need to do "template_renderer = # staticmethod(paste_script_template_renderer)" content = native_(content, fsenc) try: return bytes_( substitute_escaped_double_braces( substitute_double_braces(content, TypeMapper(vars))), fsenc) except Exception as e: _add_except(e, ' in file %s' % filename) raise def module_dir(self): mod = sys.modules[self.__class__.__module__] return os.path.dirname(mod.__file__) def template_dir(self): """ Return the template directory of the scaffold. By default, it returns the value of ``os.path.join(self.module_dir(), self._template_dir)`` (``self.module_dir()`` returns the module in which your subclass has been defined). If ``self._template_dir`` is a tuple this method just returns the value instead of trying to construct a path. If _template_dir is a tuple, it should be a 2-element tuple: ``(package_name, package_relative_path)``.""" assert self._template_dir is not None, ( "Template %r didn't set _template_dir" % self) if isinstance(self._template_dir, tuple): return self._template_dir else: return os.path.join(self.module_dir(), self._template_dir) def run(self, command, output_dir, vars): self.pre(command, output_dir, vars) self.write_files(command, output_dir, vars) self.post(command, output_dir, vars) def pre(self, command, output_dir, vars): # pragma: no cover """ Called before template is applied. """ pass def post(self, command, output_dir, vars): # pragma: no cover """ Called after template is applied. """ pass def write_files(self, command, output_dir, vars): template_dir = self.template_dir() if not self.exists(output_dir): self.out("Creating directory %s" % output_dir) if not command.options.simulate: # Don't let copydir create this top-level directory, # since copydir will svn add it sometimes: self.makedirs(output_dir) self.copydir.copy_dir( template_dir, output_dir, vars, verbosity=command.verbosity, simulate=command.options.simulate, interactive=command.options.interactive, overwrite=command.options.overwrite, indent=1, template_renderer=self.render_template, ) def makedirs(self, dir): # pragma: no cover return os.makedirs(dir) def exists(self, path): # pragma: no cover return os.path.exists(path) def out(self, msg): # pragma: no cover print(msg) # hair for exit with usage when paster create is used under 1.3 instead # of pcreate for extension scaffolds which need to support multiple # versions of pyramid; the check_vars method is called by pastescript # only as the result of "paster create"; pyramid doesn't use it. the # required_templates tuple is required to allow it to get as far as # calling check_vars. required_templates = () def check_vars(self, vars, other): raise RuntimeError( 'Under Pyramid 1.3, you should use the "pcreate" command rather ' 'than "paster create"') class TypeMapper(dict): def __getitem__(self, item): options = item.split('|') for op in options[:-1]: try: value = eval_with_catch(op, dict(self.items())) break except (NameError, KeyError): pass else: value = eval(options[-1], dict(self.items())) if value is None: return '' else: return str(value) def eval_with_catch(expr, vars): try: return eval(expr, vars) except Exception as e: _add_except(e, 'in expression %r' % expr) raise double_brace_pattern = re.compile(r'{{(?P.*?)}}') def substitute_double_braces(content, values): def double_bracerepl(match): value = match.group('braced').strip() return values[value] return double_brace_pattern.sub(double_bracerepl, content) escaped_double_brace_pattern = re.compile(r'\\{\\{(?P[^\\]*?)\\}\\}') def substitute_escaped_double_braces(content): def escaped_double_bracerepl(match): value = match.group('escape_braced').strip() return "{{%(value)s}}" % locals() return escaped_double_brace_pattern.sub(escaped_double_bracerepl, content) def _add_except(exc, info): # pragma: no cover if not hasattr(exc, 'args') or exc.args is None: return args = list(exc.args) if args: args[0] += ' ' + info else: args = [info] exc.args = tuple(args) return pyramid-1.6/pyramid/scaffolds/tests.py0000644000076500000240000000563012642137120020701 0ustar michaelstaff00000000000000import sys import os import shutil import subprocess import tempfile import time try: import http.client as httplib except ImportError: import httplib class TemplateTest(object): def make_venv(self, directory): # pragma: no cover import virtualenv from virtualenv import Logger logger = Logger([(Logger.level_for_integer(2), sys.stdout)]) virtualenv.logger = logger virtualenv.create_environment(directory, site_packages=False, clear=False, unzip_setuptools=True) def install(self, tmpl_name): # pragma: no cover try: self.old_cwd = os.getcwd() self.directory = tempfile.mkdtemp() self.make_venv(self.directory) here = os.path.abspath(os.path.dirname(__file__)) os.chdir(os.path.dirname(os.path.dirname(here))) subprocess.check_call( [os.path.join(self.directory, 'bin', 'python'), 'setup.py', 'develop']) os.chdir(self.directory) subprocess.check_call(['bin/pcreate', '-s', tmpl_name, 'Dingle']) os.chdir('Dingle') py = os.path.join(self.directory, 'bin', 'python') subprocess.check_call([py, 'setup.py', 'install']) if tmpl_name == 'alchemy': populate = os.path.join(self.directory, 'bin', 'initialize_Dingle_db') subprocess.check_call([populate, 'development.ini']) subprocess.check_call([py, 'setup.py', 'test']) pserve = os.path.join(self.directory, 'bin', 'pserve') for ininame, hastoolbar in (('development.ini', True), ('production.ini', False)): proc = subprocess.Popen([pserve, ininame]) try: time.sleep(5) proc.poll() if proc.returncode is not None: raise RuntimeError('%s didnt start' % ininame) conn = httplib.HTTPConnection('localhost:6543') conn.request('GET', '/') resp = conn.getresponse() assert resp.status == 200, ininame data = resp.read() toolbarchunk = b'
n#¸šIDATxÚŒ“M(DQÇgô2i$Â4S¾vˆ”b!e#Êdž…²·±°`©llD4±PRfAì$© % ‹)’’±ðQÂ$Í¿Sgôf¼Wsë7çνçãß¹ç9‰„ÃŽ·6qX²óqÊÕÚ÷Õb^LGyí‘ðGº_–E` tâüÊß-=^³ –•¢€@/æFwä,×.ØJÁÜûZY.’Œ€+˜8|C1LÁ¬CÌHrõ&cŒ´à\B+TÁ­ö¡ Ρ¢f†iÿ§êXÝT#±›}³ô*µØ8F NþõgIÐ¥IÜÉpþ‰Y†'Èj˜† IšÞD‘¾gMÉ¥'õà‡+íÉ:™2ŸÈe8^ Odö@A><@6ë|¤ô Ò]‚Ëp¸Æeò´i»P.Á0ÜÏcûaAÈ¸ÊØÆ TŸ¯ Ú¨9X„8Ðû;3Tþ0lSý™ì'ì}ªJªöIw£ú˜3h”ù÷1á°Š„!ØÔ' “ jò‘™ۯ1Óõ+ÀS:ÀŸžw”IEND®B`‚pyramid-1.6/pyramid/scaffolds/zodb/+package+/static/pyramid.png0000644000076500000240000003114512520062551025306 0ustar michaelstaff00000000000000‰PNG  IHDRÈÈ­X®ž AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœ$iTXtXML:com.adobe.xmp 1 5 72 1 72 200 1 200 2014-01-03T20:01:68 Pixelmator 3.0 ÆÑ›7#šIDATxí ¸ŕǣDpÅW6Q#&j¢1n!q‰fƸD£$11‘ȸ/c&‰h\ˆQ'î(ÊŒÄ݈¨!‰AI\ƒˆŠ‚ ²ˆ\E™ßÿå]¼ï¾{oWuWu÷í[çûþ¯ßíª:çÔ©ªîªSKîs‚‚‚‚‚‚‚‚‚‚‚Ò³À*é‰ ’L-°bÅŠõ‰Ût‚@¤òZf·WYe•E\y´@h kÚF±.ñÀv`+°¨Fpsx<¼Í5P°@±,@ÃØœ¦€O€-)Ítð+°i±¬rÓ´ 2¯ ƒ—+RC9 thZÆŒ7¾¨ÀëƒëÀràƒFÃTc—@Áe*n_0ÑG«¨àù<¿wj,ëm›ÚTØ^àÉŠŠìóç4˜oßÔF™o PQ7Oùl 5x?Ëý-ÃJA˦´Tò›jTà4nkLòù¦4~Ètþ-@åüa­ ŽŒO ;.ÿ–ÊŸ†a¢Ðs™P1åM4ù—%ibqŠod©D£É^µÑn@}C笇ÌÖ­™[ ¼AÌme“·G= ¶¶Nì'ÁLØîÀ[d‰öÅãÞ ~ËtØkMU^¨ŠœeAÐ@ü–Ò°Ï“ÕcÈ›-O:ù-„܃¡°Vr*¡Vç~¡Vx†÷5Ú Cù %:4Å¥•µ›ùc›³ºYyÔ+v†|& ÄŸu7†uŸÔk£×zþ²],Ρø+Ïΰ^ÍûDœóØpeÈWâÐ@|Yö³m²þ$ÄçºX†¶ ÄÐP‹VÚã^°l¹ÏNh îmÚg7‚’yÐ14¥ð>¬óú¤~Ç_¶‹Å94å¹ÖBÞH§ŸäQ¯¼Ù©EŸÐ@üË,X¿î}lÎZÍ;7vê&Kˆ§gAà»°Ö¹Uy£IèöVޔʫ>¡ø-™»aÿ©_VÜ5&ú£UŠ&ˆß ð0ì_ò+Šû+Äk•¢É#‡â±Еyö#<аe}KØ bg²°aÊÎ^Ö±[·Ü>LÂm­»M ·Ç^47ݲ-6·ðñ\¾TH¹T/Yωü64Ï…ØÇ³o‘σkAV4ÁáØŸxÅR¥a*è†à6=Ž@-½,`n*M'°Øt4O?&rz™›=‡ ~ñ5)›fNEéMqï €]€¶Äv¢¥@듞ãÁDúëÔ:'ôè ÓÀîΙ·e¨¼C>¦´½~ ”Y€ ¹3бŸ³)- âàeO9|»«@œæ,’®'FøüAY=ÿVX€ ² Þqi /ëT°Oüž¸^®Hã9„yâ*0*ˆÆã+ú+Œ¶ña2øêc:úÒ”Æ qi OùÐ1ð,¨$=ÁÀ5½Ãþ¾LïõÀ@];5–yàPIrc!Ð`ÿFp‡0x*˜B Ò©(úlòí@ƒp4¦û3ðõº#|tFNOÐ ¨ò—€Ž •3A«q_A}í6P°@´¨TÁmÀ7Ý€TÜÂѹ1‚ -@¥ý.ðõQÌòF·ŒGª¢ do*ìš Í 8GºfŸó o e±¢¾Ááe΢FlÇýÿ¨nÈ ß@x’Ëç H;/šÃó j Õ²’v¥ª¦CÒ{ò\휔IŒôZ㤯6*°ŠÐ@zP>›gPF› ³WrƒÈ-P”’¢ÉVŠ’íÂ÷ÇWš£˜ÿ¡dyó¦Å¬!W% ¡¨«“j%BVF̳Ü"4¹¸öËÐ|ù]„^”¡™çd(;ˆNÁEh S±S'†èÄÄ™)”Q‘¡ŠÐ@ÞÀ~Yt³t¾Ôë–]‚ŠÐ@Ô8&¥`«J:˜Ú˾õJAáwvhøžˆ1ß íC¢ï@vÚ2³«)ArãZ€5Qi¯æ}™úœr ‚[ áß *žä:$zHk°> ™Úݨà(ÌDOô”Õhpˆç2ÿÃh Ë<˱bOþµhS‹'u¸„ÊU¿™@Ž„)軜k f¶•dsðà‹&Ã8‹…‘5‹}úÞð*¨Fÿäæ8pPM&! y,@EØL®IçWy;ÕĶ„ÐeUðcð&0!mVC ÊÙ»hñ©zªê,+W4FÚÿ‘ B—UÀÙàc`K²K8Ì:%™¡T‚uÁpð6ˆK:•Q<ÖÏ0+íD£Ï äØRþ¢1[ f·a ÃØSRøø:_+v± Ó`HJ?ˆ­D%,Œ«^™Q“”O ®5PÝôz+”ž¢ñ¿=ê@¸GÁ=`žŸÜM’—ÓÑí"”‡Á¾Á]]ߌMÑ@*M@%ëÂ=Z¸zk˜fãçSYr}R!zwBO}Ö`ûV½“\ä¦þ:yVCIDèÕ:éek w³~Ï/ƒ‘¡Ó  øµQ‡rël^W¤·QlB‰mÁ¯N•”; >å÷û@ŸøØ4å9¶›-¡*ÐgbUÒ} ¸<=òê8e€r€\L]ÌDmq*üž«—SòãäÃ$Íj&‘Bœø B¨1h°¿Øl4y÷W}Íêq‹®º….Ïâ²æ…Þ=Ñá&°°!ÍìÈi2˜<³IâÐT„mÀ V·Hoƒ?/šdŸxû—okMä–â [ßsœ’Ò„“)K†mÆ+`{ 3|MH]•ý¢ìDUÐ¥& ã ‰’Y ‡ßÚ`‚!_“hsˆ”›• ¥|†k  à7¦£T™4˜Ýªžz„ëO–$¼j@½G=yåaÄ=+¡¼jÉÇp³äj/þ/ª(p Ä5€C#ITwOøIqWI£·ÁÚ&å@<}µK_µrMj¤G˜èUœBìÉÊx5äê´Åƒk„EÝÖRý‘î |ZDœ¨`M€^Ã@ÙtOË1Ä—sÁ5ÉItDó;¹¤Ð@ÜK_XöŠÉvMÒ ¬—–J=‡ð³@’ý¿'ýmõä”ÂZ+oäø¨?ÆU>M.æ’ÒÍK¡i™ÈN` J¥'¢\–sfšµAh×,H³ÉI(²q‘7yƶDÈÀö!÷Ò Ç'†Jö&žÏ=0*KÍäòŒ†j T 5ˆùÓ»‚Ž ’´\d*qoázaveÏ¿×HÈßhÛ0ùú yœ¬¡ ª[&•>#Áé¤5íZ)æ^JÕo¤­÷ú`Ü<©r1žÒ&'õŸS#äj£`•¸?·Q–ô}Àyà P9GRZî¡7ξ6|KqI§ü$YZOòH^’—·kîß ˜V¯÷ëAœÖ“é:xèú+žœZ”蛦#@Oh#Q…2z{]±Zo4þ PÕ~ Lã=‰«1ÊUÀ¡—VA¨KWmEs¹Ü½ù¡·Ècå7]üß:†Ð$âÙðÓ€[†­€¼sš× ¹Ö‚- î'\Žr×@0¤&Î^Ä’ û¢ç-oDÜñõ˪ÀäEûT|‘–Õ›±3W|ÏÝÁí šû˜ÛuIùRy}9¶’LvÿjM†T—Þ"ô(¨¿JúÍ€N¬Eª˜ç‚Lž¾ÈÕbHyÏvêŠT%ÂÖãAéiì|&¾ªR7‘{˜¥ A¸Êþ,×±vEÎü$“G›;zßvM ¶MRxéiýSðPz hߊžÆ/åšÐQÛ¡À”ôO­r!KöÕÊ×t9 ‹¿ç„LvGõ™ÕÐëWo5]Õ•Ò¾‹À>`u_5Þ›ƒÞ@nà† tÕûÀ”4nÛ1­Ì!ë ²ôA×ÁÔú힨ۑ–á*åQymÖ Ë£¡ßÍqLÓñ×@Ànšá— ݆NÄžWØ$ˆÝŽ&ÝH ²ôE©äÅ—ò¯g P Õ··¥_zVKò¾`¶­b1âkq¥Õ Ôú—¾øY`¡Q¬¶‘|­hh‘B…•;öWÀvµB[-Í~©×q2»Z¡˜¶M, ÜQhs³1~<…š6þcâûž…W79ÖüTL“$±û74 +Ó(4ð¿Œ$ò³Ëã’ʤ …šõ¢Î#ð®z*Â4ÿHÅ=g?±ÆÀƒu1f¤ur?ŒŽb[€‚ÔŒ®<,‚J’WèXcfGD×íÀ¬ÊLTù-–ž¶ÞþkƒgªÈö}ëUtõ–±fcŒ1OŒ(±÷ —‡¨!]ÓëäI[w½7zd¨±fAšÔ=¨! +ïJbÈn@뀢èA"4L—]5‡s!˜4§´ÌÚ!˜Ê2ähN++:Õ¤îA—‰êÇéI°Eýˆ ¿3£"æ!œ¹×ÐCË0†r]¨.,æ¾æ“Ò¢ÓTEŽÑêìÂ6 ~uŒ¢™‚"-?ÿ€  « ­Mdñ‰"Mp5œg {,Co!P ®Ð0´7ZnÃo}‘ªTiµééo„Ë5–Š¡=Ó&¤¯8)­|èõè׋‚2³ Ei&GM&Þ÷L”%Þà!Eטð q>³=9ʨǦIÁÿÈZŠ>ÞÒ˜Ÿÿ— r`Mœ@ÙZ¤U½½ nfçÙÃfò¦eAZyœó å‘!ݨë‡ÔH†šä‹xû-q_J¤ÕăFš,4É®ÖGÉ«Aüm@Ûîš Ú/Õ¨' >[­ÀN›ä±PO·Â„‘Qm¦JBï’xƒ¯´Üþ×ü¯·Ï·AiŒcÂ"÷qȺªWMV#íÖ< íԜҦ©lŠý!{‘QMú$%½\‘û†PMAlÐ ]ÕÓS,2|1q_±ˆ7êL£Qj¿Å1ˆã"iæÚåöLͼ7+mKÆãzâ´ÒÀj—^¥‘©°orï°¤2ÌáïOáu2²^¶áÙÈ Ä&Ÿ!n} ô#8ÉÃFéw N>‰–Òœ‰Œ?Ø*ÙÈ Dgο* £¥"ÍJk%Ìxi½["6Tà›`0¸l$ª'§Âû®ÖÔÈ d.¹ÕŠTWô”+F ÈG¶LBï'I\ž–Š<šßrL*¿óÿWI7žWÆLߨÉ\þ¸ M~5ó } ò¯ Ó¸ô×5 E6:änN ¥t¢âEÀhI»kÝsÃl dŒ¤4±]•¹1HLE”w Ä!m¸Ú,¦èÈdâ ÔPšœ¬EÚúüwpp¶.ÎIß12—#`Œÿ†ýy Dȃ2×°‹Wz5²MŠwE*g{€Âϱ݅¾µG?-ééäÞï4¦ú«ñÊL r|]¡b%a8­ç¹ Ä!-6üÑJfMþ¶8È&¦4–ˆÍìoŒC!mF˜–jk<-oøhø·¨ËRÂÇ}™¶iô(µ‰Ì¥jW PXŸCÀ$P4 ¿ì™D^ÖiÑ¿7Ø [»Ô~»€›ÁtP~˜´>Ið0È¥¼<ó*ÜÓ“ÂÓú UþoõY…wÀl0hËíôU÷¦Ã³iºõ}å,‚ÇR®©ºhŒ q×!@‡¨ u¤è£@“aÒÍ !«'Œ4×R”Á|0ú?P£[€^tz»X? H£®Gƒ?ƒe@] AýôG€ÎËÚ0-;!KúèíW‹þA@¯´ô ršØT´>@ #Š&!ÑZ$S3#çÌ(e¿4ò°©9B¼¬,@랦¤¾¹ñÈqòÿµ€É¶´G#ñÚ¨8:†4M`*WGp7°%íJ´C0¶(¼·3@© 8À˜qˆX×«Õ 5 ¤@äîÓàX¯v-ÖdÁï0 ÓN#Ñn(» …ÕÍ:Œ2I‹ÍÖ'ž–·tìÏÏa¯Z‡¸­ \ˆ"Åù4*R7³@¬Báv†}_ ]x{ÍnÊË£¢0¸ Ië{äùxÜž¤,æšgúw”‹;ùu(ùý}=/á²ùÉàû`K ™Hv™LøpÒßÝr§í=h€¨A¸¼XÓÛ& ¿R±…§dGyp–’?} ÐQ2}RQØRzÉÛ¥Aw\Òz uj‰%¬3¸2‚¹¼ej@íˆûƒAù¼D5VWµKq&Z‹¥q×v@{Ä7ˆH‚Ë-€ÁdÀCÀóÀ½“³AÍÊT.?­ÿÑGãͰÇ%ñ´^-} ;×±øìSɇ{Òïú:<$L]7#"®ÃeàI0 ÈÙ Õ³zÇ€\•‘QÆÒŒ„6ú¸ÒÀ67§‰ ‹æLf&Ȩ>°Sµp_+åa2¥?±]W{zXzc šÿ89 ˆ× \j}EÐJzÿÔí Ti ó%¤Ë±ÒÊuþÑYºGTÊÎâ7z¬ôŽKSIØ¥šîÜdÉT¿æa„­ôª6Êz(¾ ›è-“¤çqÕ3,€1dH½jÓ r°žív]ZèpR‚ _CÚª•ˆûgZò•»¶]7+I~á·1°måjW%Ñ)ïi«z±°H¿l›R¤‡ö Ïw¥$³–˜{ 8lQ+BûÚ—0T-«mãWC³MSCµ–žø]v©):@ݸÉäñ/ÑQýÆ@2¡½ôz{ª¬Jkåñ¿ê‘<~òü½‡¾µ\ç[‚5cûw©žøÌWË,·‹Ž§ÆÈüí•Ý€ßO,y¾Mü/–±Hô/¼¾ävOJ÷Á äšN¤SœÄÈî Ž7—€Þ´Õ<{º? Ü †€ãÈk—F€,é1„¯ÙN±o _‹/Õ Ïívt3w4AZ“— Uã-SOÄvƒôšêÀGù¹¸ Ý™úC ™ÛƒëÀ ‡Þ"Ñí@ŠxG‘pµY†(Þéø:åZ¹ü˜^'·zB ªÌ+%Þïêð*ÒÓO“°N^]€®èD'Š0Aá®@6ÖÕ-ƒÉH°øÏ¢@>v½>ó@3PBKW2'ôè~þ<ÔŠÑ\åjÕL¸1]ð¨Gzký˜©ADøml'vëé8Ò@lâ((°x²ž" Âf“öpc%‰¼#Ðë3/t¦±ò 㪑èSËoU’vC œ Îeø©gà²üյޕEÿƒ€&.}Ò‡0ÿ%¨Ùå*÷b}%µs,/t8Š_‰¢P§TŸbà“ÉÛÕ\µXQ®äùÒú)yˆä}qMµ‚¬¾±Q›U›}ù¥ gY’ºô§—+Pj ]Ëoæè-#è#}VÞ"SP~D Ì#ýoàµ+ßb¥ÒFáýЛ̖5äÈ®T‘Çææ˜ÌÞ%Ýñ4Žb¦¯—Loõ"¤¦Ý±ß-ÉË{‘.ŒAJ¥•ðJåÖ¢½ÿ£,YiÿÒßc™Î4úá¦SЧµ^-ÅJ D®®<Ò›(¥B äÈTr½ ~ÒŠW¹Ös×Êõ©ýñûîV®Î‰Š¸9L¿êœq2†ÝI¾‡XÈs ’—#ô6J}”GÅÒÔ‰J$‹¼+zªi2KƒíùTÚz•›(Õ‰tzèhëî\»UŠõì­7†Æ,c‰ûWŸÔæ›úƒ·æ5õ1¶Ô@d<Òë(•ׯëÕ^T^õ…¿´Ýu Æ¡†¢Yñ%@«SŸàú`ùc®VDšwHpÐÚ×â Àæt ÏZÔ›€R=¬'‹ûZ¿¶jI±Éh ƒ”~g¡P5™/PPMÕ@(»¾NrsW#5ž>@½9ÜEºK±Õ?ø?‘ö ©:«k¿ujíéºVi "Ã.²Kï=¶º½KÉ‘*Œ å.p-¨Õ8*5–kT^—q¤?­µÒUÆÉëïŽ(¶yN•ÓC¨K˃§‡öž?Ë ¹ÛòB ?ãKò«¹íØÓ¬äNV÷E^³9@Ý»—°·&¬ÛúôãŽÆÛµ 1þ¥9£‹¶-h®Bc•¼“Þ yõR¶xPË»T· lžÈß(äi.K˜Š£7æÎà8°è:Z4Ÿ4š5¾ ܃>Zfᜡøh·q”ët.?4ÿmùÍðB PH:ñBºå>B‰¯'ÌR›äðë 4»çEw¾Ý†©ƒð\­U/.ÎH{ÏÚÏAVÛ±@ÇN /›ô*¯¶°Yi ò9žŽo’ƒír‘Í ùÞ'¸MF€×_Á±`˜|¿BºÛà¥F¦îŒ+Ò›l+f­|´~MâæÕ±:5Ù}Dˆº²y¤…(õÞÊÒªá•\ŸÎX[Md]ìªM%9 ~#ºSIIýR5²;à›xp Ùÿx ¾®I~ü–É.׌]ñ£Œ5æ{Ã?Ç|æÁïý6 …51wŠ ³a7 =œ4R*ài¾¸®€šŸ¸þÚd¬JÜîÜÛ¯Ê}·ä!Ò›3ï¤ Éå9TòUêáò6 DJrs—óA^yq†ƒÄDåÝ&¿¾¼${Âû|äÈ—ÄCÝ!_¤½è|1wÄW^B=­óD¢Ì£R¨]iÕRîÂK[ÿOëò8‚´Z4ñÄ •Bc„ËÀ:ž•ÿü¿—@†æ:’4°(Ñ›¡wT¤ŒÃ5™”±•âÕí{D7«6½Z;\Òx“ü 9G!wWt"LvpÁ(‚‡ìw Rs*q¨oœDiÔøâêf!&~TÊ\·Ççà%åÝèõ–8Wm ‚Æ!?“hàìƒÔ¯"ï5¨¬›Ãg ^†<´Nê0øiGÓ<—æXòNcPpbN”T½UÒ¥fQµnpÿ Zúdºïˆôjý©€ —pê×÷r¤£ =¥Ñ0ã8Ô×õIZÀ(;çš(ÿ¢ ê™˜YÓ­(0µ¤DÝRŠDÆñ¿¼-ƒÁó IFf“~(ؾ7½b]Ò!.™òÚ‰xqºK3 ùÇö) [º q¤˜Nݬ )Ê«&j&7ϧNÆVð¤Ô9¯zbê `}¶Ë„tŠàŸÁÀÛÓÞ:™þ ©;jE(y øÄ£²Ï»|9‘•~iGF×Aµ'=šh%kÙ{xež­Gë*õÑFÁp3v;yL´RW=µ49L¯éÒ.ãê“úÁܷ窖þqœzbj浘&¼ÿ6Oò¶O(Þ.9ºêkfš8Õº4Íã¤I—!ÿ¶4¦. ã ô$È‚ˆ“a½Ô“²Z¥…™ Gè}*й¹iÑ êRÍPFcj szÏõxÆ&›qeË‹§·ˆkÒz¶ç\3MƒOr­DÖ¡ÄsbúªÌF€ÁÈTϨ­´Ë`ÞoP0/¡£<8.I]Ú¡ðn˜îUeæÑ]spGƒW*Ãý^Ÿ3s,ТɪT´"ïCÜ'yUYÜŒïùø×F§-dÕ‹ª9«(ôõ"5By¸ =ÿ Ü ª>ácäCåô øü/‰‘¾q“ÐÔgβúBÖð$–CïnàV„“øÈ$zä5-ùÚÜâîçч‰Ç_ëóòj¾éEƵÝôU•Ô:(­ïTüЉ%¶¤ ]»%Õ!ÏéÉŸ6—õ²‘ò;Ôj0K ›žr„ì :ÛæO³À…"Œ A—¦Iï!¬¿«n yØ~§‚ +¨Gˆß ®Bþ;õ"- ;õ&OýÁ¦ ØÌZ¡ ëË` v‰Ýí.bÙ£ŒiŽ¯Æ ï;„ÓA1`Køîö[€@ž/&ƒÇÀ³È]Ä5P°@´¨Tú*êÓ -Ò¼ËÁÑš…h4Ÿ²©Ø‡§©vE^™Š° ÑSüþåQÁÉ,À]ƒ¹4NËÐlõÉ´ ©ƒ2°·/0]LITk’Ûð” ²D ¸±x ˆã25i-×Éz±§›œ.ÁŽ,@%¦šÔxÃ8ˉw!ˆ³AÊQ®›`‡ 2ŒIi †8T-° ȇ¨Øk-3˜4~°¡DÖ‰Š}ó‘› EZ(ÜDa”á¨äÚP¥õJßÚ*ÛT£%ÜÔdœŽ#ûøÙj‘½b[ éHyqÒX´D¡Ð µ–*¬ZÈ6 ÌúÌ™–‘ ðnÿnÑËÖX—>$IEND®B`‚pyramid-1.6/pyramid/scaffolds/zodb/+package+/static/theme.css0000644000076500000240000000547212520062551024753 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700); body { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; color: #ffffff; background: #bc2131; } h1, h2, h3, h4, h5, h6 { font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; } p { font-weight: 300; } .font-normal { font-weight: 400; } .font-semi-bold { font-weight: 600; } .font-bold { font-weight: 700; } .starter-template { margin-top: 250px; } .starter-template .content { margin-left: 10px; } .starter-template .content h1 { margin-top: 10px; font-size: 60px; } .starter-template .content h1 .smaller { font-size: 40px; color: #f2b7bd; } .starter-template .content .lead { font-size: 25px; color: #f2b7bd; } .starter-template .content .lead .font-normal { color: #ffffff; } .starter-template .links { float: right; right: 0; margin-top: 125px; } .starter-template .links ul { display: block; padding: 0; margin: 0; } .starter-template .links ul li { list-style: none; display: inline; margin: 0 10px; } .starter-template .links ul li:first-child { margin-left: 0; } .starter-template .links ul li:last-child { margin-right: 0; } .starter-template .links ul li.current-version { color: #f2b7bd; font-weight: 400; } .starter-template .links ul li a { color: #ffffff; } .starter-template .links ul li a:hover { text-decoration: underline; } .starter-template .links ul li .icon-muted { color: #eb8b95; margin-right: 5px; } .starter-template .links ul li:hover .icon-muted { color: #ffffff; } .starter-template .copyright { margin-top: 10px; font-size: 0.9em; color: #f2b7bd; text-transform: lowercase; float: right; right: 0; } @media (max-width: 1199px) { .starter-template .content h1 { font-size: 45px; } .starter-template .content h1 .smaller { font-size: 30px; } .starter-template .content .lead { font-size: 20px; } } @media (max-width: 991px) { .starter-template { margin-top: 0; } .starter-template .logo { margin: 40px auto; } .starter-template .content { margin-left: 0; text-align: center; } .starter-template .content h1 { margin-bottom: 20px; } .starter-template .links { float: none; text-align: center; margin-top: 60px; } .starter-template .copyright { float: none; text-align: center; } } @media (max-width: 767px) { .starter-template .content h1 .smaller { font-size: 25px; display: block; } .starter-template .content .lead { font-size: 16px; } .starter-template .links { margin-top: 40px; } .starter-template .links ul li { display: block; margin: 0; } .starter-template .links ul li .icon-muted { display: none; } .starter-template .copyright { margin-top: 20px; } } pyramid-1.6/pyramid/scaffolds/zodb/+package+/static/theme.min.css0000644000076500000240000000442512520062551025532 0ustar michaelstaff00000000000000@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;color:#fff;background:#bc2131}h1,h2,h3,h4,h5,h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300}p{font-weight:300}.font-normal{font-weight:400}.font-semi-bold{font-weight:600}.font-bold{font-weight:700}.starter-template{margin-top:250px}.starter-template .content{margin-left:10px}.starter-template .content h1{margin-top:10px;font-size:60px}.starter-template .content h1 .smaller{font-size:40px;color:#f2b7bd}.starter-template .content .lead{font-size:25px;color:#f2b7bd}.starter-template .content .lead .font-normal{color:#fff}.starter-template .links{float:right;right:0;margin-top:125px}.starter-template .links ul{display:block;padding:0;margin:0}.starter-template .links ul li{list-style:none;display:inline;margin:0 10px}.starter-template .links ul li:first-child{margin-left:0}.starter-template .links ul li:last-child{margin-right:0}.starter-template .links ul li.current-version{color:#f2b7bd;font-weight:400}.starter-template .links ul li a{color:#fff}.starter-template .links ul li a:hover{text-decoration:underline}.starter-template .links ul li .icon-muted{color:#eb8b95;margin-right:5px}.starter-template .links ul li:hover .icon-muted{color:#fff}.starter-template .copyright{margin-top:10px;font-size:.9em;color:#f2b7bd;text-transform:lowercase;float:right;right:0}@media (max-width:1199px){.starter-template .content h1{font-size:45px}.starter-template .content h1 .smaller{font-size:30px}.starter-template .content .lead{font-size:20px}}@media (max-width:991px){.starter-template{margin-top:0}.starter-template .logo{margin:40px auto}.starter-template .content{margin-left:0;text-align:center}.starter-template .content h1{margin-bottom:20px}.starter-template .links{float:none;text-align:center;margin-top:60px}.starter-template .copyright{float:none;text-align:center}}@media (max-width:767px){.starter-template .content h1 .smaller{font-size:25px;display:block}.starter-template .content .lead{font-size:16px}.starter-template .links{margin-top:40px}.starter-template .links ul li{display:block;margin:0}.starter-template .links ul li .icon-muted{display:none}.starter-template .copyright{margin-top:20px}}pyramid-1.6/pyramid/scaffolds/zodb/+package+/templates/0000755000076500000240000000000012642137501023641 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl0000644000076500000240000000602312524266531027431 0ustar michaelstaff00000000000000 ZODB Scaffold for The Pyramid Web Framework

Pyramid ZODB scaffold

Welcome to ${project}, an application generated by
the Pyramid Web Framework {{pyramid_version}}.

pyramid-1.6/pyramid/scaffolds/zodb/+package+/tests.py_tmpl0000644000076500000240000000060412517346416024423 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], '{{project}}') pyramid-1.6/pyramid/scaffolds/zodb/+package+/views.py_tmpl0000644000076500000240000000030112517346416024410 0ustar michaelstaff00000000000000from pyramid.view import view_config from .models import MyModel @view_config(context=MyModel, renderer='templates/mytemplate.pt') def my_view(request): return {'project': '{{project}}'} pyramid-1.6/pyramid/scaffolds/zodb/CHANGES.txt_tmpl0000644000076500000240000000003412234375161022767 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/pyramid/scaffolds/zodb/development.ini_tmpl0000644000076500000240000000240212642137120024174 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_zodbconn pyramid_tm tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_{{package_logger}}] level = DEBUG handlers = qualname = {{package}} [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/zodb/MANIFEST.in_tmpl0000644000076500000240000000020612234375161022715 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/pyramid/scaffolds/zodb/production.ini_tmpl0000644000076500000240000000214512524266531024054 0ustar michaelstaff00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/environment.html ### [app:main] use = egg:{{project}} pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm pyramid_zodbconn tm.attempts = 3 zodbconn.uri = file://%(here)s/Data.fs?connection_cache_size=20000 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/{{pyramid_docs_branch}}/narr/logging.html ### [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_{{package_logger}}] level = WARN handlers = qualname = {{package}} [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s pyramid-1.6/pyramid/scaffolds/zodb/README.txt_tmpl0000644000076500000240000000002312234375161022652 0ustar michaelstaff00000000000000{{project}} README pyramid-1.6/pyramid/scaffolds/zodb/setup.py_tmpl0000644000076500000240000000216712520062551022673 0ustar michaelstaff00000000000000import 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 = [ 'pyramid', 'pyramid_chameleon', 'pyramid_debugtoolbar', 'pyramid_tm', 'pyramid_zodbconn', 'transaction', 'ZODB3', 'waitress', ] setup(name='{{project}}', version='0.0', description='{{project}}', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="{{package}}", entry_points="""\ [paste.app_factory] main = {{package}}:main """, ) pyramid-1.6/pyramid/scripting.py0000644000076500000240000001243212524266531017603 0ustar michaelstaff00000000000000from pyramid.config import global_registries from pyramid.exceptions import ConfigurationError from pyramid.interfaces import ( IRequestFactory, IRootFactory, ) from pyramid.request import Request from pyramid.request import apply_request_extensions from pyramid.threadlocal import manager as threadlocal_manager from pyramid.traversal import DefaultRootFactory def get_root(app, request=None): """ Return a tuple composed of ``(root, closer)`` when provided a :term:`router` instance as the ``app`` argument. The ``root`` returned is the application root object. The ``closer`` returned is a callable (accepting no arguments) that should be called when your scripting application is finished using the root. ``request`` is passed to the :app:`Pyramid` application root factory to compute the root. If ``request`` is None, a default will be constructed using the registry's :term:`Request Factory` via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. """ registry = app.registry if request is None: request = _make_request('/', registry) threadlocals = {'registry':registry, 'request':request} app.threadlocal_manager.push(threadlocals) def closer(request=request): # keep request alive via this function default app.threadlocal_manager.pop() root = app.root_factory(request) return root, closer def prepare(request=None, registry=None): """ This function pushes data onto the Pyramid threadlocal stack (request and registry), making those objects 'current'. It returns a dictionary useful for bootstrapping a Pyramid application in a scripting environment. ``request`` is passed to the :app:`Pyramid` application root factory to compute the root. If ``request`` is None, a default will be constructed using the registry's :term:`Request Factory` via the :meth:`pyramid.interfaces.IRequestFactory.blank` method. If ``registry`` is not supplied, the last registry loaded from :attr:`pyramid.config.global_registries` will be used. If you have loaded more than one :app:`Pyramid` application in the current process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate one based on your own criteria. The function returns a dictionary composed of ``root``, ``closer``, ``registry``, ``request`` and ``root_factory``. The ``root`` returned is the application's root resource object. The ``closer`` returned is a callable (accepting no arguments) that should be called when your scripting application is finished using the root. ``registry`` is the registry object passed or the last registry loaded into :attr:`pyramid.config.global_registries` if no registry is passed. ``request`` is the request object passed or the constructed request if no request is passed. ``root_factory`` is the root factory used to construct the root. """ if registry is None: registry = getattr(request, 'registry', global_registries.last) if registry is None: raise ConfigurationError('No valid Pyramid applications could be ' 'found, make sure one has been created ' 'before trying to activate it.') if request is None: request = _make_request('/', registry) # NB: even though _make_request might have already set registry on # request, we reset it in case someone has passed in their own # request. request.registry = registry threadlocals = {'registry':registry, 'request':request} threadlocal_manager.push(threadlocals) apply_request_extensions(request) def closer(): threadlocal_manager.pop() root_factory = registry.queryUtility(IRootFactory, default=DefaultRootFactory) root = root_factory(request) if getattr(request, 'context', None) is None: request.context = root return {'root':root, 'closer':closer, 'registry':registry, 'request':request, 'root_factory':root_factory} def _make_request(path, registry=None): """ Return a :meth:`pyramid.request.Request` object anchored at a given path. The object returned will be generated from the supplied registry's :term:`Request Factory` using the :meth:`pyramid.interfaces.IRequestFactory.blank` method. This request object can be passed to :meth:`pyramid.scripting.get_root` or :meth:`pyramid.scripting.prepare` to initialize an application in preparation for executing a script with a proper environment setup. URLs can then be generated with the object, as well as rendering templates. If ``registry`` is not supplied, the last registry loaded from :attr:`pyramid.config.global_registries` will be used. If you have loaded more than one :app:`Pyramid` application in the current process, you may not want to use the last registry loaded, thus you can search the ``global_registries`` and supply the appropriate one based on your own criteria. """ if registry is None: registry = global_registries.last request_factory = registry.queryUtility(IRequestFactory, default=Request) request = request_factory.blank(path) request.registry = registry return request pyramid-1.6/pyramid/scripts/0000755000076500000240000000000012642137501016707 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/scripts/__init__.py0000644000076500000240000000001212517346416021021 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/scripts/common.py0000644000076500000240000000221112517346416020555 0ustar michaelstaff00000000000000import os from pyramid.compat import configparser from logging.config import fileConfig def parse_vars(args): """ Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': 'b', 'c': 'd'}`` """ result = {} for arg in args: if '=' not in arg: raise ValueError( 'Variable assignment %r invalid (no "=")' % arg) name, value = arg.split('=', 1) result[name] = value return result def logging_file_config(config_file, fileConfig=fileConfig, configparser=configparser): """ Setup logging via the logging module's fileConfig function with the specified ``config_file``, if applicable. ConfigParser defaults are specified for the special ``__file__`` and ``here`` variables, similar to PasteDeploy config loading. """ parser = configparser.ConfigParser() parser.read([config_file]) if parser.has_section('loggers'): config_file = os.path.abspath(config_file) return fileConfig( config_file, dict(__file__=config_file, here=os.path.dirname(config_file)) ) pyramid-1.6/pyramid/scripts/pcreate.py0000644000076500000240000002002212642137120020675 0ustar michaelstaff00000000000000# (c) 2005 Ian Bicking and contributors; written for Paste # (http://pythonpaste.org) Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php import optparse import os import os.path import pkg_resources import re import sys from pyramid.compat import input_ _bad_chars_re = re.compile('[^a-zA-Z0-9_]') def main(argv=sys.argv, quiet=False): command = PCreateCommand(argv, quiet) try: return command.run() except KeyboardInterrupt: # pragma: no cover return 1 class PCreateCommand(object): verbosity = 1 # required description = "Render Pyramid scaffolding to an output directory" usage = "usage: %prog [options] -s output_directory" parser = optparse.OptionParser(usage, description=description) parser.add_option('-s', '--scaffold', dest='scaffold_name', action='append', help=("Add a scaffold to the create process " "(multiple -s args accepted)")) parser.add_option('-t', '--template', dest='scaffold_name', action='append', help=('A backwards compatibility alias for ' '-s/--scaffold. Add a scaffold to the ' 'create process (multiple -t args accepted)')) parser.add_option('-l', '--list', dest='list', action='store_true', help="List all available scaffold names") parser.add_option('--list-templates', dest='list', action='store_true', help=("A backwards compatibility alias for -l/--list. " "List all available scaffold names.")) parser.add_option('--simulate', dest='simulate', action='store_true', help='Simulate but do no work') parser.add_option('--overwrite', dest='overwrite', action='store_true', help='Always overwrite') parser.add_option('--interactive', dest='interactive', action='store_true', help='When a file would be overwritten, interrogate') parser.add_option('--ignore-conflicting-name', dest='force_bad_name', action='store_true', default=False, help='Do create a project even if the chosen name ' 'is the name of an already existing / importable ' 'package.') 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:]) self.scaffolds = self.all_scaffolds() def run(self): if self.options.list: return self.show_scaffolds() if not self.options.scaffold_name and not self.args: if not self.quiet: # pragma: no cover self.parser.print_help() self.out('') self.show_scaffolds() return 2 if not self.validate_input(): return 2 return self.render_scaffolds() @property def output_path(self): return os.path.abspath(os.path.normpath(self.args[0])) @property def project_vars(self): output_dir = self.output_path project_name = os.path.basename(os.path.split(output_dir)[1]) 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' return { 'project': project_name, 'package': pkg_name, 'egg': egg_name, 'pyramid_version': pyramid_version, 'pyramid_docs_branch': pyramid_docs_branch, } def render_scaffolds(self): props = self.project_vars output_dir = self.output_path for scaffold_name in self.options.scaffold_name: for scaffold in self.scaffolds: if scaffold.name == scaffold_name: scaffold.run(self, output_dir, props) return 0 def show_scaffolds(self): scaffolds = sorted(self.scaffolds, key=lambda x: x.name) if scaffolds: max_name = max([len(t.name) for t in scaffolds]) self.out('Available scaffolds:') for scaffold in scaffolds: self.out(' %s:%s %s' % ( scaffold.name, ' '*(max_name-len(scaffold.name)), scaffold.summary)) else: self.out('No scaffolds available') return 0 def all_scaffolds(self): scaffolds = [] eps = list(pkg_resources.iter_entry_points('pyramid.scaffold')) for entry in eps: try: scaffold_class = entry.load() scaffold = scaffold_class(entry.name) scaffolds.append(scaffold) except Exception as e: # pragma: no cover self.out('Warning: could not load entry point %s (%s: %s)' % ( entry.name, e.__class__.__name__, e)) return scaffolds def out(self, msg): # pragma: no cover if not self.quiet: print(msg) def validate_input(self): if not self.options.scaffold_name: self.out('You must provide at least one scaffold name: -s ') self.out('') self.show_scaffolds() return False if not self.args: self.out('You must provide a project name') return False available = [x.name for x in self.scaffolds] diff = set(self.options.scaffold_name).difference(available) if diff: self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff))) return False pkg_name = self.project_vars['package'] if pkg_name == 'site' and not self.options.force_bad_name: self.out('The package name "site" has a special meaning in ' 'Python. Are you sure you want to use it as your ' 'project\'s name?') return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name)) # check if pkg_name can be imported (i.e. already exists in current # $PYTHON_PATH, if so - let the user confirm pkg_exists = True try: __import__(pkg_name, globals(), locals(), [], 0) # use absolute imports except ImportError as error: pkg_exists = False if not pkg_exists: return True if self.options.force_bad_name: return True self.out('A package named "{0}" already exists, are you sure you want ' 'to use it as your project\'s name?'.format(pkg_name)) return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name)) def confirm_bad_name(self, prompt): # pragma: no cover answer = input_('{0} [y|N]: '.format(prompt)) return answer.strip().lower() == 'y' if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/pdistreport.py0000644000076500000240000000251512520062551021640 0ustar michaelstaff00000000000000import sys import platform import pkg_resources import optparse from operator import itemgetter def out(*args): # pragma: no cover for arg in args: sys.stdout.write(arg) sys.stdout.write(' ') sys.stdout.write('\n') def main(argv=sys.argv, pkg_resources=pkg_resources, platform=platform.platform, out=out): # all args except argv are for unit testing purposes only description = "Show Python distribution versions and locations in use" usage = "usage: %prog" parser = optparse.OptionParser(usage, description=description) parser.parse_args(argv[1:]) packages = [] for distribution in pkg_resources.working_set: name = distribution.project_name packages.append( {'version': distribution.version, 'lowername': name.lower(), 'name': name, 'location':distribution.location} ) packages = sorted(packages, key=itemgetter('lowername')) pyramid_version = pkg_resources.get_distribution('pyramid').version plat = platform() out('Pyramid version:', pyramid_version) out('Platform:', plat) out('Packages:') for package in packages: out(' ', package['name'], package['version']) out(' ', package['location']) if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/prequest.py0000644000076500000240000001372712642137120021140 0ustar michaelstaff00000000000000import base64 import optparse import sys import textwrap from pyramid.compat import url_unquote from pyramid.request import Request from pyramid.paster import get_app, setup_logging from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): command = PRequestCommand(argv, quiet) return command.run() class PRequestCommand(object): description = """\ Submit a HTTP request to a web application. This command makes an artifical request to a web application that uses a PasteDeploy (.ini) configuration file for the server and application. Use "prequest config.ini /path" to request "/path". Use "prequest --method=POST config.ini /path < data" to do a POST with the given request body. Use "prequest --method=PUT config.ini /path < data" to do a PUT with the given request body. Use "prequest --method=PATCH config.ini /path < data" to do a PATCH with the given request body. Use "prequest --method=OPTIONS config.ini /path" to do an OPTIONS request. Use "prequest --method=PROPFIND config.ini /path" to do a PROPFIND request. If the path is relative (doesn't begin with "/") it is interpreted as relative to "/". The path passed to this script should be URL-quoted. The path can be succeeded with a query string (e.g. `/path?a=1&=b2'). The variable "environ['paste.command_request']" will be set to "True" in the request's WSGI environment, so your application can distinguish these calls from normal requests. """ usage = "usage: %prog config_uri path_info [args/options]" parser = optparse.OptionParser( usage=usage, description=textwrap.dedent(description) ) parser.add_option( '-n', '--app-name', dest='app_name', metavar= 'NAME', help="Load the named application from the config file (default 'main')", type="string", ) parser.add_option( '--header', dest='headers', metavar='NAME:VALUE', type='string', action='append', help="Header to add to request (you can use this option multiple times)" ) parser.add_option( '-d', '--display-headers', dest='display_headers', action='store_true', help='Display status and headers before the response body' ) parser.add_option( '-m', '--method', dest='method', choices=['GET', 'HEAD', 'POST', 'PUT', 'PATCH','DELETE', 'PROPFIND', 'OPTIONS'], type='choice', help='Request method type (GET, POST, PUT, PATCH, DELETE, ' 'PROPFIND, OPTIONS)', ) parser.add_option( '-l', '--login', dest='login', type='string', help='HTTP basic auth username:password pair', ) get_app = staticmethod(get_app) stdin = sys.stdin def __init__(self, argv, quiet=False): self.quiet = quiet self.options, self.args = self.parser.parse_args(argv[1:]) def out(self, msg): # pragma: no cover 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 try: path, qs = path.split('?', 1) except ValueError: qs = '' path = url_unquote(path) headers = {} if self.options.login: enc = base64.b64encode(self.options.login.encode('ascii')) headers['Authorization'] = 'Basic ' + enc.decode('ascii') if self.options.headers: for item in self.options.headers: if ':' not in item: self.out( "Bad --header=%s option, value must be in the form " "'name:value'" % item) return 2 name, value = item.split(':', 1) headers[name] = value.strip() app = self.get_app(app_spec, self.options.app_name, options=parse_vars(self.args[2:])) request_method = (self.options.method or 'GET').upper() environ = { 'REQUEST_METHOD': request_method, 'SCRIPT_NAME': '', # may be empty if app is at the root 'PATH_INFO': path, 'SERVER_NAME': 'localhost', # always mandatory 'SERVER_PORT': '80', # always mandatory 'SERVER_PROTOCOL': 'HTTP/1.0', 'CONTENT_TYPE': 'text/plain', 'REMOTE_ADDR':'127.0.0.1', 'wsgi.run_once': True, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.errors': sys.stderr, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0), 'QUERY_STRING': qs, 'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1', 'paste.command_request': True, } if request_method in ('POST', 'PUT', 'PATCH'): environ['wsgi.input'] = self.stdin environ['CONTENT_LENGTH'] = '-1' for name, value in headers.items(): if name.lower() == 'content-type': name = 'CONTENT_TYPE' else: name = 'HTTP_'+name.upper().replace('-', '_') environ[name] = value request = Request.blank(path, environ=environ) response = request.get_response(app) if self.options.display_headers: self.out(response.status) for name, value in response.headerlist: self.out('%s: %s' % (name, value)) if response.charset: self.out(response.ubody) else: self.out(response.body) return 0 if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/proutes.py0000644000076500000240000002732212621001276020764 0ustar michaelstaff00000000000000import fnmatch import optparse import sys import textwrap import re from zope.interface import Interface from pyramid.paster import bootstrap from pyramid.compat import (string_types, configparser) from pyramid.interfaces import IRouteRequest from pyramid.config import not_ from pyramid.scripts.common import parse_vars from pyramid.static import static_view from pyramid.view import _find_views PAD = 3 ANY_KEY = '*' UNKNOWN_KEY = '' def main(argv=sys.argv, quiet=False): command = PRoutesCommand(argv, quiet) return command.run() def _get_pattern(route): pattern = route.pattern if not pattern.startswith('/'): pattern = '/%s' % pattern return pattern def _get_print_format(fmt, max_name, max_pattern, max_view, max_method): print_fmt = '' max_map = { 'name': max_name, 'pattern': max_pattern, 'view': max_view, 'method': max_method, } sizes = [] for index, col in enumerate(fmt): size = max_map[col] + PAD print_fmt += '{{%s: <{%s}}} ' % (col, index) sizes.append(size) return print_fmt.format(*sizes) def _get_request_methods(route_request_methods, view_request_methods): excludes = set() if route_request_methods: route_request_methods = set(route_request_methods) if view_request_methods: view_request_methods = set(view_request_methods) for method in view_request_methods.copy(): if method.startswith('!'): view_request_methods.remove(method) excludes.add(method[1:]) has_route_methods = route_request_methods is not None has_view_methods = len(view_request_methods) > 0 has_methods = has_route_methods or has_view_methods if has_route_methods is False and has_view_methods is False: request_methods = [ANY_KEY] elif has_route_methods is False and has_view_methods is True: request_methods = view_request_methods elif has_route_methods is True and has_view_methods is False: request_methods = route_request_methods else: request_methods = route_request_methods.intersection( view_request_methods ) request_methods = set(request_methods).difference(excludes) if has_methods and not request_methods: request_methods = '' elif request_methods: if excludes and request_methods == set([ANY_KEY]): for exclude in excludes: request_methods.add('!%s' % exclude) request_methods = ','.join(sorted(request_methods)) return request_methods def _get_view_module(view_callable): if view_callable is None: return UNKNOWN_KEY if hasattr(view_callable, '__name__'): if hasattr(view_callable, '__original_view__'): original_view = view_callable.__original_view__ else: original_view = None if isinstance(original_view, static_view): if original_view.package_name is not None: return '%s:%s' % ( original_view.package_name, original_view.docroot ) else: return original_view.docroot else: view_name = view_callable.__name__ else: # Currently only MultiView hits this, # we could just not run _get_view_module # for them and remove this logic view_name = str(view_callable) view_module = '%s.%s' % ( view_callable.__module__, view_name, ) # If pyramid wraps something in wsgiapp or wsgiapp2 decorators # that is currently returned as pyramid.router.decorator, lets # hack a nice name in: if view_module == 'pyramid.router.decorator': view_module = '' return view_module def get_route_data(route, registry): pattern = _get_pattern(route) request_iface = registry.queryUtility( IRouteRequest, name=route.name ) route_request_methods = None view_request_methods_order = [] view_request_methods = {} view_callable = None route_intr = registry.introspector.get( 'routes', route.name ) if request_iface is None: return [ (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY) ] view_callables = _find_views(registry, request_iface, Interface, '') if view_callables: view_callable = view_callables[0] else: view_callable = None view_module = _get_view_module(view_callable) # Introspectables can be turned off, so there could be a chance # that we have no `route_intr` but we do have a route + callable if route_intr is None: view_request_methods[view_module] = [] view_request_methods_order.append(view_module) else: if route_intr.get('static', False) is True: return [ (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY) ] route_request_methods = route_intr['request_methods'] view_intr = registry.introspector.related(route_intr) if view_intr: for view in view_intr: request_method = view.get('request_methods') if request_method is not None: view_callable = view['callable'] view_module = _get_view_module(view_callable) if view_module not in view_request_methods: view_request_methods[view_module] = [] view_request_methods_order.append(view_module) if isinstance(request_method, string_types): request_method = (request_method,) elif isinstance(request_method, not_): request_method = ('!%s' % request_method.value,) view_request_methods[view_module].extend(request_method) else: if view_module not in view_request_methods: view_request_methods[view_module] = [] view_request_methods_order.append(view_module) else: view_request_methods[view_module] = [] view_request_methods_order.append(view_module) final_routes = [] for view_module in view_request_methods_order: methods = view_request_methods[view_module] request_methods = _get_request_methods( route_request_methods, methods ) final_routes.append(( route.name, pattern, view_module, request_methods, )) return final_routes class PRoutesCommand(object): description = """\ Print all URL dispatch routes used by a Pyramid application in the order in which they are evaluated. Each route includes the name of the route, the pattern of the route, and the view callable which will be invoked when the route is matched. This command accepts one positional argument named 'config_uri'. It specifies the PasteDeploy config file to use for the interactive shell. The format is 'inifile#name'. If the name is left off, 'main' will be assumed. Example: 'proutes myapp.ini'. """ bootstrap = (bootstrap,) stdout = sys.stdout usage = '%prog config_uri' ConfigParser = configparser.ConfigParser # testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) ) parser.add_option('-g', '--glob', action='store', type='string', dest='glob', default='', help='Display routes matching glob pattern') parser.add_option('-f', '--format', action='store', type='string', dest='format', default='', help=('Choose which columns to display, this ' 'will override the format key in the ' '[proutes] ini section')) def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) self.quiet = quiet self.available_formats = [ 'name', 'pattern', 'view', 'method' ] self.column_format = self.available_formats def validate_formats(self, formats): invalid_formats = [] for fmt in formats: if fmt not in self.available_formats: invalid_formats.append(fmt) msg = ( 'You provided invalid formats %s, ' 'Available formats are %s' ) if invalid_formats: msg = msg % (invalid_formats, self.available_formats) self.out(msg) return False return True def proutes_file_config(self, filename): config = self.ConfigParser() config.read(filename) try: items = config.items('proutes') for k, v in items: if 'format' == k: cols = re.split(r'[,|\s|\n]*', v) self.column_format = [x.strip() for x in cols] except configparser.NoSectionError: return def out(self, msg): # pragma: no cover if not self.quiet: print(msg) def _get_mapper(self, registry): from pyramid.config import Configurator config = Configurator(registry=registry) return config.get_routes_mapper() def run(self, quiet=False): if not self.args: self.out('requires a config file argument') return 2 config_uri = self.args[0] env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] mapper = self._get_mapper(registry) self.proutes_file_config(config_uri) if self.options.format: columns = self.options.format.split(',') self.column_format = [x.strip() for x in columns] is_valid = self.validate_formats(self.column_format) if is_valid is False: return 2 if mapper is None: return 0 max_name = len('Name') max_pattern = len('Pattern') max_view = len('View') max_method = len('Method') routes = mapper.get_routes(include_static=True) if len(routes) == 0: return 0 mapped_routes = [{ 'name': 'Name', 'pattern': 'Pattern', 'view': 'View', 'method': 'Method' },{ 'name': '----', 'pattern': '-------', 'view': '----', 'method': '------' }] for route in routes: route_data = get_route_data(route, registry) for name, pattern, view, method in route_data: if self.options.glob: match = (fnmatch.fnmatch(name, self.options.glob) or fnmatch.fnmatch(pattern, self.options.glob)) if not match: continue if len(name) > max_name: max_name = len(name) if len(pattern) > max_pattern: max_pattern = len(pattern) if len(view) > max_view: max_view = len(view) if len(method) > max_method: max_method = len(method) mapped_routes.append({ 'name': name, 'pattern': pattern, 'view': view, 'method': method }) fmt = _get_print_format( self.column_format, max_name, max_pattern, max_view, max_method ) for route in mapped_routes: self.out(fmt.format(**route)) return 0 if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/pserve.py0000644000076500000240000011124112642137120020562 0ustar michaelstaff00000000000000# (c) 2005 Ian Bicking and contributors; written for Paste # (http://pythonpaste.org) Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php # # For discussion of daemonizing: # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 # # Code taken also from QP: http://www.mems-exchange.org/software/qp/ From # lib/site.py import atexit import ctypes import errno import logging import optparse import os import py_compile import re import subprocess import sys import tempfile import textwrap import threading import time import traceback import webbrowser from paste.deploy import loadserver from paste.deploy import loadapp from paste.deploy.loadwsgi import loadcontext, SERVER from pyramid.compat import PY3 from pyramid.compat import WIN from pyramid.paster import setup_logging from pyramid.scripts.common import parse_vars MAXFD = 1024 try: import termios except ImportError: # pragma: no cover termios = None if WIN and not hasattr(os, 'kill'): # pragma: no cover # py 2.6 on windows def kill(pid, sig=None): """kill function for Win32""" # signal is ignored, semibogus raise message kernel32 = ctypes.windll.kernel32 handle = kernel32.OpenProcess(1, 0, pid) if (0 == kernel32.TerminateProcess(handle, 0)): raise OSError('No such process %s' % pid) else: kill = os.kill def main(argv=sys.argv, quiet=False): command = PServeCommand(argv, quiet=quiet) return command.run() class DaemonizeException(Exception): pass class PServeCommand(object): usage = '%prog config_uri [start|stop|restart|status] [var=value]' description = """\ This command serves a web application that uses a PasteDeploy configuration file for the server and application. If start/stop/restart is given, then --daemon is implied, and it will start (normal operation), stop (--stop-daemon), or do both. Note: Daemonization features are deprecated. You can also include variable assignments like 'http_port=8080' and then use %(http_port)s in your config files. """ default_verbosity = 1 parser = optparse.OptionParser( usage, description=textwrap.dedent(description) ) parser.add_option( '-n', '--app-name', dest='app_name', metavar='NAME', help="Load the named application (default main)") parser.add_option( '-s', '--server', dest='server', metavar='SERVER_TYPE', help="Use the named server.") parser.add_option( '--server-name', dest='server_name', metavar='SECTION_NAME', help=("Use the named server as defined in the configuration file " "(default: main)")) if hasattr(os, 'fork'): parser.add_option( '--daemon', dest="daemon", action="store_true", help="Run in daemon (background) mode [DEPRECATED]") parser.add_option( '--pid-file', dest='pid_file', metavar='FILENAME', help=("Save PID to file (default to pyramid.pid if running in " "daemon mode) [DEPRECATED]")) parser.add_option( '--log-file', dest='log_file', metavar='LOG_FILE', help="Save output to the given log file (redirects stdout)") parser.add_option( '--reload', dest='reload', action='store_true', help="Use auto-restart file monitor") parser.add_option( '--reload-interval', dest='reload_interval', default=1, help=("Seconds between checking files (low number can cause " "significant CPU usage)")) parser.add_option( '--monitor-restart', dest='monitor_restart', action='store_true', help="Auto-restart server if it dies [DEPRECATED]") parser.add_option( '-b', '--browser', dest='browser', action='store_true', help="Open a web browser to server url") parser.add_option( '--status', action='store_true', dest='show_status', help=("Show the status of the (presumably daemonized) server " "[DEPRECATED]")) parser.add_option( '-v', '--verbose', default=default_verbosity, dest='verbose', action='count', help="Set verbose level (default "+str(default_verbosity)+")") parser.add_option( '-q', '--quiet', action='store_const', const=0, dest='verbose', help="Suppress verbose output") if hasattr(os, 'setuid'): # I don't think these are available on Windows parser.add_option( '--user', dest='set_user', metavar="USERNAME", help="Set the user (usually only possible when run as root)") parser.add_option( '--group', dest='set_group', metavar="GROUP", help="Set the group (usually only possible when run as root)") parser.add_option( '--stop-daemon', dest='stop_daemon', action='store_true', help=('Stop a daemonized server (given a PID file, or default ' 'pyramid.pid file) [DEPRECATED]')) _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' possible_subcommands = ('start', 'stop', 'restart', 'status') def __init__(self, argv, quiet=False): self.options, self.args = self.parser.parse_args(argv[1:]) if quiet: self.options.verbose = 0 def out(self, msg): # pragma: no cover if self.options.verbose > 0: print(msg) def get_options(self): if (len(self.args) > 1 and self.args[1] in self.possible_subcommands): restvars = self.args[2:] else: restvars = self.args[1:] return parse_vars(restvars) def run(self): # pragma: no cover if self.options.stop_daemon: self._warn_daemon_deprecated() return self.stop_daemon() if not hasattr(self.options, 'set_user'): # Windows case: self.options.set_user = self.options.set_group = None # @@: Is this the right stage to set the user at? if self.options.set_user or self.options.set_group: self.change_user_group( self.options.set_user, self.options.set_group) if not self.args: self.out('You must give a config file') return 2 app_spec = self.args[0] if (len(self.args) > 1 and self.args[1] in self.possible_subcommands): cmd = self.args[1] else: cmd = None if self.options.reload: if ( getattr(self.options, 'daemon', False) or cmd in ('start', 'stop', 'restart') ): self.out( 'Error: Cannot use reloading while running as a dameon.') return 2 if os.environ.get(self._reloader_environ_key): if self.options.verbose > 1: self.out('Running reloading file monitor') install_reloader(int(self.options.reload_interval), [app_spec]) # if self.requires_config_file: # watch_file(self.args[0]) else: return self.restart_with_reloader() if cmd not in (None, 'start', 'stop', 'restart', 'status'): self.out( 'Error: must give start|stop|restart (not %s)' % cmd) return 2 if cmd == 'status' or self.options.show_status: self._warn_daemon_deprecated() return self.show_status() if cmd == 'restart' or cmd == 'stop': self._warn_daemon_deprecated() result = self.stop_daemon() if result: if cmd == 'restart': self.out("Could not stop daemon; aborting") else: self.out("Could not stop daemon") return result if cmd == 'stop': return result self.options.daemon = True if cmd == 'start': self.options.daemon = True app_name = self.options.app_name vars = self.get_options() if not self._scheme_re.search(app_spec): app_spec = 'config:' + app_spec server_name = self.options.server_name if self.options.server: server_spec = 'egg:pyramid' assert server_name is None server_name = self.options.server else: server_spec = app_spec base = os.getcwd() # warn before setting a default if self.options.pid_file: self._warn_daemon_deprecated() if getattr(self.options, 'daemon', False): if not self.options.pid_file: self.options.pid_file = 'pyramid.pid' if not self.options.log_file: self.options.log_file = 'pyramid.log' # Ensure the log file is writeable if self.options.log_file: try: writeable_log_file = open(self.options.log_file, 'a') except IOError as ioe: msg = 'Error: Unable to write to log file: %s' % ioe raise ValueError(msg) writeable_log_file.close() # Ensure the pid file is writeable if self.options.pid_file: try: writeable_pid_file = open(self.options.pid_file, 'a') except IOError as ioe: msg = 'Error: Unable to write to pid file: %s' % ioe raise ValueError(msg) writeable_pid_file.close() # warn before forking if ( self.options.monitor_restart and not os.environ.get(self._monitor_environ_key) ): self.out('''\ --monitor-restart has been deprecated in Pyramid 1.6. It will be removed in a future release per Pyramid's deprecation policy. Please consider using a real process manager for your processes like Systemd, Circus, or Supervisor. ''') if ( getattr(self.options, 'daemon', False) and not os.environ.get(self._monitor_environ_key) ): self._warn_daemon_deprecated() try: self.daemonize() except DaemonizeException as ex: if self.options.verbose > 0: self.out(str(ex)) return 2 if ( not os.environ.get(self._monitor_environ_key) and self.options.pid_file ): self.record_pid(self.options.pid_file) if ( self.options.monitor_restart and not os.environ.get(self._monitor_environ_key) ): return self.restart_with_monitor() if self.options.log_file: stdout_log = LazyWriter(self.options.log_file, 'a') sys.stdout = stdout_log sys.stderr = stdout_log logging.basicConfig(stream=stdout_log) log_fn = app_spec if log_fn.startswith('config:'): log_fn = app_spec[len('config:'):] elif log_fn.startswith('egg:'): log_fn = None if log_fn: log_fn = os.path.join(base, log_fn) setup_logging(log_fn) server = self.loadserver(server_spec, name=server_name, relative_to=base, global_conf=vars) app = self.loadapp(app_spec, name=app_name, relative_to=base, global_conf=vars) if self.options.verbose > 0: if hasattr(os, 'getpid'): msg = 'Starting server in PID %i.' % os.getpid() else: msg = 'Starting server.' self.out(msg) def serve(): try: server(app) except (SystemExit, KeyboardInterrupt) as e: if self.options.verbose > 1: raise if str(e): msg = ' ' + str(e) else: msg = '' self.out('Exiting%s (-v to see traceback)' % msg) if self.options.browser: def open_browser(): context = loadcontext(SERVER, app_spec, name=app_name, relative_to=base, global_conf=vars) url = 'http://127.0.0.1:{port}/'.format(**context.config()) time.sleep(1) webbrowser.open(url) t = threading.Thread(target=open_browser) t.setDaemon(True) t.start() serve() def loadapp(self, app_spec, name, relative_to, **kw): # pragma: no cover return loadapp(app_spec, name=name, relative_to=relative_to, **kw) def loadserver(self, server_spec, name, relative_to, **kw):# pragma:no cover return loadserver( server_spec, name=name, relative_to=relative_to, **kw) def quote_first_command_arg(self, arg): # pragma: no cover """ There's a bug in Windows when running an executable that's located inside a path with a space in it. This method handles that case, or on non-Windows systems or an executable with no spaces, it just leaves well enough alone. """ if (sys.platform != 'win32' or ' ' not in arg): # Problem does not apply: return arg try: import win32api except ImportError: raise ValueError( "The executable %r contains a space, and in order to " "handle this issue you must have the win32api module " "installed" % arg) arg = win32api.GetShortPathName(arg) return arg def find_script_path(self, name): # pragma: no cover """ Return the path to the script being invoked by the python interpreter. There's an issue on Windows when running the executable from a console_script causing the script name (sys.argv[0]) to not end with .exe or .py and thus cannot be run via popen. """ if sys.platform == 'win32': if not name.endswith('.exe') and not name.endswith('.py'): name += '.exe' return name def daemonize(self): # pragma: no cover pid = live_pidfile(self.options.pid_file) if pid: raise DaemonizeException( "Daemon is already running (PID: %s from PID file %s)" % (pid, self.options.pid_file)) if self.options.verbose > 0: self.out('Entering daemon mode') pid = os.fork() if pid: # The forked process also has a handle on resources, so we # *don't* want proper termination of the process, we just # want to exit quick (which os._exit() does) os._exit(0) # Make this the session leader os.setsid() # Fork again for good measure! pid = os.fork() if pid: os._exit(0) # @@: Should we set the umask and cwd now? import resource # Resource usage information. maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] if (maxfd == resource.RLIM_INFINITY): maxfd = MAXFD # Iterate through and close all file descriptors. for fd in range(0, maxfd): try: os.close(fd) except OSError: # ERROR, fd wasn't open to begin with (ignored) pass if (hasattr(os, "devnull")): REDIRECT_TO = os.devnull else: REDIRECT_TO = "/dev/null" os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) # Duplicate standard input to standard output and standard error. os.dup2(0, 1) # standard output (1) os.dup2(0, 2) # standard error (2) def _remove_pid_file(self, written_pid, filename, verbosity): current_pid = os.getpid() if written_pid != current_pid: # A forked process must be exiting, not the process that # wrote the PID file return if not os.path.exists(filename): return with open(filename) as f: content = f.read().strip() try: pid_in_file = int(content) except ValueError: pass else: if pid_in_file != current_pid: msg = "PID file %s contains %s, not expected PID %s" self.out(msg % (filename, pid_in_file, current_pid)) return if verbosity > 0: self.out("Removing PID file %s" % filename) try: os.unlink(filename) return except OSError as e: # Record, but don't give traceback self.out("Cannot remove PID file: (%s)" % e) # well, at least lets not leave the invalid PID around... try: with open(filename, 'w') as f: f.write('') except OSError as e: self.out('Stale PID left in file: %s (%s)' % (filename, e)) else: self.out('Stale PID removed') def record_pid(self, pid_file): pid = os.getpid() if self.options.verbose > 1: self.out('Writing PID %s to %s' % (pid, pid_file)) with open(pid_file, 'w') as f: f.write(str(pid)) atexit.register(self._remove_pid_file, pid, pid_file, self.options.verbose) def stop_daemon(self): # pragma: no cover pid_file = self.options.pid_file or 'pyramid.pid' if not os.path.exists(pid_file): self.out('No PID file exists in %s' % pid_file) return 1 pid = read_pidfile(pid_file) if not pid: self.out("Not a valid PID file in %s" % pid_file) return 1 pid = live_pidfile(pid_file) if not pid: self.out("PID in %s is not valid (deleting)" % pid_file) try: os.unlink(pid_file) except (OSError, IOError) as e: self.out("Could not delete: %s" % e) return 2 return 1 for j in range(10): if not live_pidfile(pid_file): break import signal kill(pid, signal.SIGTERM) time.sleep(1) else: self.out("failed to kill web process %s" % pid) return 3 if os.path.exists(pid_file): os.unlink(pid_file) return 0 def show_status(self): # pragma: no cover pid_file = self.options.pid_file or 'pyramid.pid' if not os.path.exists(pid_file): self.out('No PID file %s' % pid_file) return 1 pid = read_pidfile(pid_file) if not pid: self.out('No PID in file %s' % pid_file) return 1 pid = live_pidfile(pid_file) if not pid: self.out('PID %s in %s is not running' % (pid, pid_file)) return 1 self.out('Server running in PID %s' % pid) return 0 def restart_with_reloader(self): # pragma: no cover self.restart_with_monitor(reloader=True) def restart_with_monitor(self, reloader=False): # pragma: no cover if self.options.verbose > 0: if reloader: self.out('Starting subprocess with file monitor') else: self.out('Starting subprocess with monitor parent') while 1: args = [ self.quote_first_command_arg(sys.executable), self.find_script_path(sys.argv[0]), ] + sys.argv[1:] new_environ = os.environ.copy() if reloader: new_environ[self._reloader_environ_key] = 'true' else: new_environ[self._monitor_environ_key] = 'true' proc = None try: try: _turn_sigterm_into_systemexit() proc = subprocess.Popen(args, env=new_environ) exit_code = proc.wait() proc = None except KeyboardInterrupt: self.out('^C caught in monitor process') if self.options.verbose > 1: raise return 1 finally: if proc is not None: import signal try: kill(proc.pid, signal.SIGTERM) except (OSError, IOError): pass if reloader: # Reloader always exits with code 3; but if we are # a monitor, any exit code will restart if exit_code != 3: return exit_code if self.options.verbose > 0: self.out('%s %s %s' % ('-' * 20, 'Restarting', '-' * 20)) def change_user_group(self, user, group): # pragma: no cover import pwd import grp self.out('''\ The --user and --group options have been deprecated in Pyramid 1.6. They will be removed in a future release per Pyramid's deprecation policy. Please consider using a real process manager for your processes like Systemd, Circus, or Supervisor, all of which support process security. ''') uid = gid = None if group: try: gid = int(group) group = grp.getgrgid(gid).gr_name except ValueError: import grp try: entry = grp.getgrnam(group) except KeyError: raise ValueError( "Bad group: %r; no such group exists" % group) gid = entry.gr_gid try: uid = int(user) user = pwd.getpwuid(uid).pw_name except ValueError: try: entry = pwd.getpwnam(user) except KeyError: raise ValueError( "Bad username: %r; no such user exists" % user) if not gid: gid = entry.pw_gid uid = entry.pw_uid if self.options.verbose > 0: self.out('Changing user to %s:%s (%s:%s)' % ( user, group or '(unknown)', uid, gid)) if gid: os.setgid(gid) if uid: os.setuid(uid) def _warn_daemon_deprecated(self): self.out('''\ The daemon options have been deprecated in Pyramid 1.6. They will be removed in a future release per Pyramid's deprecation policy. Please consider using a real process manager for your processes like Systemd, Circus, or Supervisor. The following commands are deprecated: [start,stop,restart,status] --daemon, --stop-server, --status, --pid-file ''') class LazyWriter(object): """ File-like object that opens a file lazily when it is first written to. """ def __init__(self, filename, mode='w'): self.filename = filename self.fileobj = None self.lock = threading.Lock() self.mode = mode def open(self): if self.fileobj is None: with self.lock: self.fileobj = open(self.filename, self.mode) return self.fileobj def close(self): fileobj = self.fileobj if fileobj is not None: fileobj.close() def __del__(self): self.close() def write(self, text): fileobj = self.open() fileobj.write(text) fileobj.flush() def writelines(self, text): fileobj = self.open() fileobj.writelines(text) fileobj.flush() def flush(self): self.open().flush() def live_pidfile(pidfile): # pragma: no cover """(pidfile:str) -> int | None Returns an int found in the named file, if there is one, and if there is a running process with that process id. Return None if no such process exists. """ pid = read_pidfile(pidfile) if pid: try: kill(int(pid), 0) return pid except OSError as e: if e.errno == errno.EPERM: return pid return None def read_pidfile(filename): if os.path.exists(filename): try: with open(filename) as f: content = f.read() return int(content.strip()) except (ValueError, IOError): return None else: return None def ensure_port_cleanup( bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover """ This makes sure any open ports are closed. Does this by connecting to them until they give connection refused. Servers should call like:: ensure_port_cleanup([80, 443]) """ atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries, sleeptime=sleeptime) def _cleanup_ports( bound_addresses, maxtries=30, sleeptime=2): # pragma: no cover # Wait for the server to bind to the port. import socket import errno for bound_address in bound_addresses: for attempt in range(maxtries): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect(bound_address) except socket.error as e: if e.args[0] != errno.ECONNREFUSED: raise break else: time.sleep(sleeptime) else: raise SystemExit('Timeout waiting for port.') sock.close() def _turn_sigterm_into_systemexit(): # pragma: no cover """ Attempts to turn a SIGTERM exception into a SystemExit exception. """ try: import signal except ImportError: return def handle_term(signo, frame): raise SystemExit signal.signal(signal.SIGTERM, handle_term) def ensure_echo_on(): # pragma: no cover if termios: fd = sys.stdin if fd.isatty(): attr_list = termios.tcgetattr(fd) if not attr_list[3] & termios.ECHO: attr_list[3] |= termios.ECHO termios.tcsetattr(fd, termios.TCSANOW, attr_list) def install_reloader(poll_interval=1, extra_files=None): # pragma: no cover """ Install the reloading monitor. On some platforms server threads may not terminate when the main thread does, causing ports to remain open/locked. """ ensure_echo_on() mon = Monitor(poll_interval=poll_interval) if extra_files is None: extra_files = [] mon.extra_files.extend(extra_files) t = threading.Thread(target=mon.periodic_reload) t.setDaemon(True) t.start() class classinstancemethod(object): """ Acts like a class method when called from a class, like an instance method when called by an instance. The method should take two arguments, 'self' and 'cls'; one of these will be None depending on how the method was called. """ def __init__(self, func): self.func = func self.__doc__ = func.__doc__ def __get__(self, obj, type=None): return _methodwrapper(self.func, obj=obj, type=type) class _methodwrapper(object): def __init__(self, func, obj, type): self.func = func self.obj = obj self.type = type def __call__(self, *args, **kw): assert not 'self' in kw and not 'cls' in kw, ( "You cannot use 'self' or 'cls' arguments to a " "classinstancemethod") return self.func(*((self.obj, self.type) + args), **kw) class Monitor(object): # pragma: no cover """ A file monitor and server restarter. Use this like: ..code-block:: Python install_reloader() Then make sure your server is installed with a shell script like:: err=3 while test "$err" -eq 3 ; do python server.py err="$?" done or is run from this .bat file (if you use Windows):: @echo off :repeat python server.py if %errorlevel% == 3 goto repeat or run a monitoring process in Python (``pserve --reload`` does this). Use the ``watch_file(filename)`` function to cause a reload/restart for other non-Python files (e.g., configuration files). If you have a dynamic set of files that grows over time you can use something like:: def watch_config_files(): return CONFIG_FILE_CACHE.keys() add_file_callback(watch_config_files) Then every time the reloader polls files it will call ``watch_config_files`` and check all the filenames it returns. """ instances = [] global_extra_files = [] global_file_callbacks = [] def __init__(self, poll_interval): self.module_mtimes = {} self.keep_running = True self.poll_interval = poll_interval self.extra_files = list(self.global_extra_files) self.instances.append(self) self.syntax_error_files = set() self.pending_reload = False self.file_callbacks = list(self.global_file_callbacks) temp_pyc_fp = tempfile.NamedTemporaryFile(delete=False) self.temp_pyc = temp_pyc_fp.name temp_pyc_fp.close() def _exit(self): try: os.unlink(self.temp_pyc) except IOError: # not worried if the tempfile can't be removed pass # use os._exit() here and not sys.exit() since within a # thread sys.exit() just closes the given thread and # won't kill the process; note os._exit does not call # any atexit callbacks, nor does it do finally blocks, # flush open files, etc. In otherwords, it is rude. os._exit(3) def periodic_reload(self): while True: if not self.check_reload(): self._exit() break time.sleep(self.poll_interval) def check_reload(self): filenames = list(self.extra_files) for file_callback in self.file_callbacks: try: filenames.extend(file_callback()) except: print( "Error calling reloader callback %r:" % file_callback) traceback.print_exc() for module in list(sys.modules.values()): try: filename = module.__file__ except (AttributeError, ImportError): continue if filename is not None: filenames.append(filename) new_changes = False for filename in filenames: try: stat = os.stat(filename) if stat: mtime = stat.st_mtime else: mtime = 0 except (OSError, IOError): continue if filename.endswith('.pyc') and os.path.exists(filename[:-1]): mtime = max(os.stat(filename[:-1]).st_mtime, mtime) pyc = True else: pyc = False old_mtime = self.module_mtimes.get(filename) self.module_mtimes[filename] = mtime if old_mtime is not None and old_mtime < mtime: new_changes = True if pyc: filename = filename[:-1] is_valid = True if filename.endswith('.py'): is_valid = self.check_syntax(filename) if is_valid: print("%s changed ..." % filename) if new_changes: self.pending_reload = True if self.syntax_error_files: for filename in sorted(self.syntax_error_files): print("%s has a SyntaxError; NOT reloading." % filename) if self.pending_reload and not self.syntax_error_files: self.pending_reload = False return False return True def check_syntax(self, filename): # check if a file has syntax errors. # If so, track it until it's fixed. try: py_compile.compile(filename, cfile=self.temp_pyc, doraise=True) except py_compile.PyCompileError as ex: print(ex.msg) self.syntax_error_files.add(filename) return False else: if filename in self.syntax_error_files: self.syntax_error_files.remove(filename) return True def watch_file(self, cls, filename): """Watch the named file for changes""" filename = os.path.abspath(filename) if self is None: for instance in cls.instances: instance.watch_file(filename) cls.global_extra_files.append(filename) else: self.extra_files.append(filename) watch_file = classinstancemethod(watch_file) def add_file_callback(self, cls, callback): """Add a callback -- a function that takes no parameters -- that will return a list of filenames to watch for changes.""" if self is None: for instance in cls.instances: instance.add_file_callback(callback) cls.global_file_callbacks.append(callback) else: self.file_callbacks.append(callback) add_file_callback = classinstancemethod(add_file_callback) watch_file = Monitor.watch_file add_file_callback = Monitor.add_file_callback # For paste.deploy server instantiation (egg:pyramid#wsgiref) def wsgiref_server_runner(wsgi_app, global_conf, **kw): # pragma: no cover from wsgiref.simple_server import make_server host = kw.get('host', '0.0.0.0') port = int(kw.get('port', 8080)) server = make_server(host, port, wsgi_app) print('Starting HTTP server on http://%s:%s' % (host, port)) server.serve_forever() # For paste.deploy server instantiation (egg:pyramid#cherrypy) def cherrypy_server_runner( app, global_conf=None, host='127.0.0.1', port=None, ssl_pem=None, protocol_version=None, numthreads=None, server_name=None, max=None, request_queue_size=None, timeout=None ): # pragma: no cover """ Entry point for CherryPy's WSGI server Serves the specified WSGI app via CherryPyWSGIServer. ``app`` The WSGI 'application callable'; multiple WSGI applications may be passed as (script_name, callable) pairs. ``host`` This is the ipaddress to bind to (or a hostname if your nameserver is properly configured). This defaults to 127.0.0.1, which is not a public interface. ``port`` The port to run on, defaults to 8080 for HTTP, or 4443 for HTTPS. This can be a string or an integer value. ``ssl_pem`` This an optional SSL certificate file (via OpenSSL) You can generate a self-signed test PEM certificate file as follows: $ openssl genrsa 1024 > host.key $ chmod 400 host.key $ openssl req -new -x509 -nodes -sha1 -days 365 \\ -key host.key > host.cert $ cat host.cert host.key > host.pem $ chmod 400 host.pem ``protocol_version`` The protocol used by the server, by default ``HTTP/1.1``. ``numthreads`` The number of worker threads to create. ``server_name`` The string to set for WSGI's SERVER_NAME environ entry. ``max`` The maximum number of queued requests. (defaults to -1 = no limit). ``request_queue_size`` The 'backlog' argument to socket.listen(); specifies the maximum number of queued connections. ``timeout`` The timeout in seconds for accepted connections. """ is_ssl = False if ssl_pem: port = port or 4443 is_ssl = True if not port: if ':' in host: host, port = host.split(':', 1) else: port = 8080 bind_addr = (host, int(port)) kwargs = {} for var_name in ('numthreads', 'max', 'request_queue_size', 'timeout'): var = locals()[var_name] if var is not None: kwargs[var_name] = int(var) from cherrypy import wsgiserver server = wsgiserver.CherryPyWSGIServer(bind_addr, app, server_name=server_name, **kwargs) if ssl_pem is not None: if not PY3: server.ssl_certificate = server.ssl_private_key = ssl_pem else: # creates wsgiserver.ssl_builtin as side-effect wsgiserver.get_ssl_adapter_class() server.ssl_adapter = wsgiserver.ssl_builtin.BuiltinSSLAdapter( ssl_pem, ssl_pem) if protocol_version: server.protocol = protocol_version try: protocol = is_ssl and 'https' or 'http' if host == '0.0.0.0': print('serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % (port, protocol, port)) else: print('serving on %s://%s:%s' % (protocol, host, port)) server.start() except (KeyboardInterrupt, SystemExit): server.stop() return server if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/pshell.py0000644000076500000240000002077612621241570020563 0ustar michaelstaff00000000000000from code import interact import optparse import os import sys import textwrap import pkg_resources from pyramid.compat import configparser from pyramid.compat import exec_ from pyramid.util import DottedNameResolver from pyramid.paster import bootstrap from pyramid.paster import setup_logging from pyramid.settings import aslist from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): command = PShellCommand(argv, quiet) return command.run() def python_shell_runner(env, help, interact=interact): cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) banner += '\n\n' + help + '\n' interact(banner, local=env) class PShellCommand(object): usage = '%prog config_uri' description = """\ Open an interactive shell with a Pyramid app loaded. This command accepts one positional argument named "config_uri" which specifies the PasteDeploy config file to use for the interactive shell. The format is "inifile#name". If the name is left off, the Pyramid default application will be assumed. Example: "pshell myapp.ini#main" If you do not point the loader directly at the section of the ini file containing your Pyramid application, the command will attempt to find the app for you. If you are loading a pipeline that contains more than one Pyramid application within it, the loader will use the last one. """ bootstrap = (bootstrap,) # for testing pkg_resources = pkg_resources # for testing parser = optparse.OptionParser( usage, description=textwrap.dedent(description) ) parser.add_option('-p', '--python-shell', action='store', type='string', dest='python_shell', default='', help=('Select the shell to use. A list of possible ' 'shells is available using the --list-shells ' 'option.')) parser.add_option('-l', '--list-shells', dest='list', action='store_true', help='List all available shells.') parser.add_option('--setup', dest='setup', help=("A callable that will be passed the environment " "before it is made available to the shell. This " "option will override the 'setup' key in the " "[pshell] ini section.")) ConfigParser = configparser.ConfigParser # testing default_runner = python_shell_runner # testing loaded_objects = {} object_help = {} preferred_shells = [] setup = None pystartup = os.environ.get('PYTHONSTARTUP') def __init__(self, argv, quiet=False): self.quiet = quiet self.options, self.args = self.parser.parse_args(argv[1:]) def pshell_file_config(self, filename): config = self.ConfigParser() config.optionxform = str config.read(filename) try: items = config.items('pshell') except configparser.NoSectionError: return resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} self.setup = None for k, v in items: if k == 'setup': self.setup = v elif k == 'default_shell': self.preferred_shells = [x.lower() for x in aslist(v)] else: self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v def out(self, msg): # pragma: no cover if not self.quiet: print(msg) def run(self, shell=None): if self.options.list: return self.show_shells() if not self.args: self.out('Requires a config file argument') return 2 config_uri = self.args[0] config_file = config_uri.split('#', 1)[0] setup_logging(config_file) self.pshell_file_config(config_file) # bootstrap the environ env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) # remove the closer from the env self.closer = env.pop('closer') # setup help text for default environment env_help = dict(env) env_help['app'] = 'The WSGI application.' env_help['root'] = 'Root of the default resource tree.' env_help['registry'] = 'Active Pyramid registry.' env_help['request'] = 'Active request object.' env_help['root_factory'] = ( 'Default root factory used to create `root`.') # override use_script with command-line options if self.options.setup: self.setup = self.options.setup if self.setup: # store the env before muddling it with the script orig_env = env.copy() # call the setup callable resolver = DottedNameResolver(None) setup = resolver.maybe_resolve(self.setup) setup(env) # remove any objects from default help that were overidden for k, v in env.items(): if k not in orig_env or env[k] != orig_env[k]: if getattr(v, '__doc__', False): env_help[k] = v.__doc__.replace("\n", " ") else: env_help[k] = v # load the pshell section of the ini file env.update(self.loaded_objects) # eliminate duplicates from env, allowing custom vars to override for k in self.loaded_objects: if k in env_help: del env_help[k] # generate help text help = '' if env_help: help += 'Environment:' for var in sorted(env_help.keys()): help += '\n %-12s %s' % (var, env_help[var]) if self.object_help: help += '\n\nCustom Variables:' for var in sorted(self.object_help.keys()): help += '\n %-12s %s' % (var, self.object_help[var]) if shell is None: try: shell = self.make_shell() except ValueError as e: self.out(str(e)) self.closer() return 1 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: self.closer() def show_shells(self): shells = self.find_all_shells() sorted_names = sorted(shells.keys(), key=lambda x: x.lower()) self.out('Available shells:') for name in sorted_names: self.out(' %s' % (name,)) return 0 def find_all_shells(self): pkg_resources = self.pkg_resources shells = {} for ep in pkg_resources.iter_entry_points('pyramid.pshell_runner'): name = ep.name shell_factory = ep.load() shells[name] = shell_factory return shells def make_shell(self): shells = self.find_all_shells() shell = None user_shell = self.options.python_shell.lower() if not user_shell: preferred_shells = self.preferred_shells if not preferred_shells: # by default prioritize all shells above python preferred_shells = [k for k in shells.keys() if k != 'python'] max_weight = len(preferred_shells) def order(x): # invert weight to reverse sort the list # (closer to the front is higher priority) try: return preferred_shells.index(x[0].lower()) - max_weight except ValueError: return 1 sorted_shells = sorted(shells.items(), key=order) if len(sorted_shells) > 0: shell = sorted_shells[0][1] else: runner = shells.get(user_shell) if runner is not None: shell = runner if shell is None: raise ValueError( 'could not find a shell named "%s"' % user_shell ) if shell is None: # should never happen, but just incase entry points are borked shell = self.default_runner return shell if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/ptweens.py0000644000076500000240000000626512642137120020754 0ustar michaelstaff00000000000000import optparse import sys import textwrap from pyramid.interfaces import ITweens from pyramid.tweens import MAIN from pyramid.tweens import INGRESS from pyramid.paster import bootstrap from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): command = PTweensCommand(argv, quiet) return command.run() class PTweensCommand(object): usage = '%prog config_uri' description = """\ Print all implicit and explicit tween objects used by a Pyramid application. The handler output includes whether the system is using an explicit tweens ordering (will be true when the "pyramid.tweens" deployment setting is used) or an implicit tweens ordering (will be true when the "pyramid.tweens" deployment setting is *not* used). This command accepts one positional argument named "config_uri" which specifies the PasteDeploy config file to use for the interactive shell. The format is "inifile#name". If the name is left off, "main" will be assumed. Example: "ptweens myapp.ini#main". """ parser = optparse.OptionParser( usage, description=textwrap.dedent(description), ) stdout = sys.stdout bootstrap = (bootstrap,) # testing def __init__(self, argv, quiet=False): self.quiet = quiet self.options, self.args = self.parser.parse_args(argv[1:]) def _get_tweens(self, registry): from pyramid.config import Configurator config = Configurator(registry = registry) return config.registry.queryUtility(ITweens) def out(self, msg): # pragma: no cover if not self.quiet: print(msg) def show_chain(self, chain): fmt = '%-10s %-65s' self.out(fmt % ('Position', 'Name')) self.out(fmt % ('-'*len('Position'), '-'*len('Name'))) self.out(fmt % ('-', INGRESS)) for pos, (name, _) in enumerate(chain): self.out(fmt % (pos, name)) self.out(fmt % ('-', MAIN)) def run(self): if not self.args: self.out('Requires a config file argument') return 2 config_uri = self.args[0] env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) registry = env['registry'] tweens = self._get_tweens(registry) if tweens is not None: explicit = tweens.explicit if explicit: self.out('"pyramid.tweens" config value set ' '(explicitly ordered tweens used)') self.out('') self.out('Explicit Tween Chain (used)') self.out('') self.show_chain(tweens.explicit) self.out('') self.out('Implicit Tween Chain (not used)') self.out('') self.show_chain(tweens.implicit()) else: self.out('"pyramid.tweens" config value NOT set ' '(implicitly ordered tweens used)') self.out('') self.out('Implicit Tween Chain') self.out('') self.show_chain(tweens.implicit()) return 0 if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/scripts/pviews.py0000644000076500000240000002453112642137120020600 0ustar michaelstaff00000000000000import optparse import sys import textwrap from pyramid.interfaces import IMultiView from pyramid.paster import bootstrap from pyramid.request import Request from pyramid.scripts.common import parse_vars from pyramid.view import _find_views def main(argv=sys.argv, quiet=False): command = PViewsCommand(argv, quiet) return command.run() class PViewsCommand(object): usage = '%prog config_uri url' description = """\ Print, for a given URL, the views that might match. Underneath each potentially matching route, list the predicates required. Underneath each route+predicate set, print each view that might match and its predicates. This command accepts two positional arguments: 'config_uri' specifies the PasteDeploy config file to use for the interactive shell. The format is 'inifile#name'. If the name is left off, 'main' will be assumed. 'url' specifies the path info portion of a URL that will be used to find matching views. Example: 'proutes myapp.ini#main /url' """ stdout = sys.stdout parser = optparse.OptionParser( usage, description=textwrap.dedent(description) ) bootstrap = (bootstrap,) # testing def __init__(self, argv, quiet=False): self.quiet = quiet self.options, self.args = self.parser.parse_args(argv[1:]) def out(self, msg): # pragma: no cover if not self.quiet: print(msg) def _find_multi_routes(self, mapper, request): infos = [] path = request.environ['PATH_INFO'] # find all routes that match path, regardless of predicates for route in mapper.get_routes(): match = route.match(path) if match is not None: info = {'match':match, 'route':route} infos.append(info) return infos def _find_view(self, request): """ Accept ``url`` and ``registry``; create a :term:`request` and find a :app:`Pyramid` view based on introspection of :term:`view configuration` within the application registry; return the view. """ from zope.interface import providedBy from zope.interface import implementer from pyramid.interfaces import IRequest from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ITraverser from pyramid.traversal import DefaultRootFactory from pyramid.traversal import ResourceTreeTraverser registry = request.registry q = registry.queryUtility root_factory = q(IRootFactory, default=DefaultRootFactory) routes_mapper = q(IRoutesMapper) adapters = registry.adapters @implementer(IMultiView) class RoutesMultiView(object): def __init__(self, infos, context_iface, root_factory, request): self.views = [] for info in infos: match, route = info['match'], info['route'] if route is not None: request_iface = registry.queryUtility( IRouteRequest, name=route.name, default=IRequest) views = _find_views( request.registry, request_iface, context_iface, '' ) if not views: continue view = views[0] view.__request_attrs__ = {} view.__request_attrs__['matchdict'] = match view.__request_attrs__['matched_route'] = route root_factory = route.factory or root_factory root = root_factory(request) traverser = adapters.queryAdapter(root, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(root) tdict = traverser(request) view.__request_attrs__.update(tdict) if not hasattr(view, '__view_attr__'): view.__view_attr__ = '' self.views.append((None, view, None)) context = None routes_multiview = None attrs = request.__dict__ request_iface = IRequest # find the root object if routes_mapper is not None: infos = self._find_multi_routes(routes_mapper, request) if len(infos) == 1: info = infos[0] match, route = info['match'], info['route'] if route is not None: attrs['matchdict'] = match attrs['matched_route'] = route request.environ['bfg.routes.matchdict'] = match request_iface = registry.queryUtility( IRouteRequest, name=route.name, default=IRequest) root_factory = route.factory or root_factory if len(infos) > 1: routes_multiview = infos root = root_factory(request) attrs['root'] = root # find a context traverser = adapters.queryAdapter(root, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(root) tdict = traverser(request) context, view_name, subpath, traversed, vroot, vroot_path =( tdict['context'], tdict['view_name'], tdict['subpath'], tdict['traversed'], tdict['virtual_root'], tdict['virtual_root_path']) attrs.update(tdict) # find a view callable context_iface = providedBy(context) if routes_multiview is None: views = _find_views( request.registry, request_iface, context_iface, view_name, ) if views: view = views[0] else: view = None else: view = RoutesMultiView(infos, context_iface, root_factory, request) # routes are not registered with a view name if view is None: views = _find_views( request.registry, request_iface, context_iface, '', ) if views: view = views[0] else: view = None # we don't want a multiview here if IMultiView.providedBy(view): view = None if view is not None: view.__request_attrs__ = attrs return view def output_route_attrs(self, attrs, indent): route = attrs['matched_route'] self.out("%sroute name: %s" % (indent, route.name)) self.out("%sroute pattern: %s" % (indent, route.pattern)) self.out("%sroute path: %s" % (indent, route.path)) self.out("%ssubpath: %s" % (indent, '/'.join(attrs['subpath']))) predicates = ', '.join([p.text() for p in route.predicates]) if predicates != '': self.out("%sroute predicates (%s)" % (indent, predicates)) def output_view_info(self, view_wrapper, level=1): indent = " " * level name = getattr(view_wrapper, '__name__', '') module = getattr(view_wrapper, '__module__', '') attr = getattr(view_wrapper, '__view_attr__', None) request_attrs = getattr(view_wrapper, '__request_attrs__', {}) if attr is not None: view_callable = "%s.%s.%s" % (module, name, attr) else: attr = view_wrapper.__class__.__name__ if attr == 'function': attr = name view_callable = "%s.%s" % (module, attr) self.out('') if 'matched_route' in request_attrs: self.out("%sRoute:" % indent) self.out("%s------" % indent) self.output_route_attrs(request_attrs, indent) permission = getattr(view_wrapper, '__permission__', None) if not IMultiView.providedBy(view_wrapper): # single view for this route, so repeat call without route data del request_attrs['matched_route'] self.output_view_info(view_wrapper, level+1) else: self.out("%sView:" % indent) self.out("%s-----" % indent) self.out("%s%s" % (indent, view_callable)) permission = getattr(view_wrapper, '__permission__', None) if permission is not None: self.out("%srequired permission = %s" % (indent, permission)) predicates = getattr(view_wrapper, '__predicates__', None) if predicates is not None: predicate_text = ', '.join([p.text() for p in predicates]) self.out("%sview predicates (%s)" % (indent, predicate_text)) def run(self): if len(self.args) < 2: self.out('Command requires a config file arg and a url arg') return 2 config_uri = self.args[0] url = self.args[1] if not url.startswith('/'): url = '/%s' % url request = Request.blank(url) env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]), request=request) view = self._find_view(request) self.out('') self.out("URL = %s" % url) self.out('') if view is not None: self.out(" context: %s" % view.__request_attrs__['context']) self.out(" view name: %s" % view.__request_attrs__['view_name']) if IMultiView.providedBy(view): for dummy, view_wrapper, dummy in view.views: self.output_view_info(view_wrapper) if IMultiView.providedBy(view_wrapper): for dummy, mv_view_wrapper, dummy in view_wrapper.views: self.output_view_info(mv_view_wrapper, level=2) else: if view is not None: self.output_view_info(view) else: self.out(" Not found.") self.out('') env['closer']() return 0 if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) pyramid-1.6/pyramid/security.py0000644000076500000240000003622112524266531017452 0ustar michaelstaff00000000000000from zope.deprecation import deprecated from zope.interface import providedBy from pyramid.interfaces import ( IAuthenticationPolicy, IAuthorizationPolicy, ISecuredView, IView, IViewClassifier, ) from pyramid.compat import map_ from pyramid.threadlocal import get_current_registry Everyone = 'system.Everyone' Authenticated = 'system.Authenticated' Allow = 'Allow' Deny = 'Deny' _marker = object() class AllPermissionsList(object): """ Stand in 'permission list' to represent all permissions """ def __iter__(self): return () def __contains__(self, other): return True def __eq__(self, other): return isinstance(other, self.__class__) ALL_PERMISSIONS = AllPermissionsList() DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS) NO_PERMISSION_REQUIRED = '__no_permission_required__' def _get_registry(request): try: reg = request.registry except AttributeError: reg = get_current_registry() # b/c return reg def _get_authentication_policy(request): registry = _get_registry(request) return registry.queryUtility(IAuthenticationPolicy) def has_permission(permission, context, request): """ A function that calls :meth:`pyramid.request.Request.has_permission` and returns its result. .. deprecated:: 1.5 Use :meth:`pyramid.request.Request.has_permission` instead. .. versionchanged:: 1.5a3 If context is None, then attempt to use the context attribute of self; if not set, then the AttributeError is propagated. """ return request.has_permission(permission, context) deprecated( 'has_permission', 'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now ' 'deprecated. It will be removed in Pyramid 1.8. Use the ' '"has_permission" method of the Pyramid request instead.' ) def authenticated_userid(request): """ A function that returns the value of the property :attr:`pyramid.request.Request.authenticated_userid`. .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.authenticated_userid` instead. """ return request.authenticated_userid deprecated( 'authenticated_userid', 'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now ' 'deprecated. It will be removed in Pyramid 1.8. Use the ' '"authenticated_userid" attribute of the Pyramid request instead.' ) def unauthenticated_userid(request): """ A function that returns the value of the property :attr:`pyramid.request.Request.unauthenticated_userid`. .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. """ return request.unauthenticated_userid deprecated( 'unauthenticated_userid', 'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is ' 'now deprecated. It will be removed in Pyramid 1.8. Use the ' '"unauthenticated_userid" attribute of the Pyramid request instead.' ) def effective_principals(request): """ A function that returns the value of the property :attr:`pyramid.request.Request.effective_principals`. .. deprecated:: 1.5 Use :attr:`pyramid.request.Request.effective_principals` instead. """ return request.effective_principals deprecated( 'effective_principals', 'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is ' 'now deprecated. It will be removed in Pyramid 1.8. Use the ' '"effective_principals" attribute of the Pyramid request instead.' ) 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 ``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 computed previously by the view code): .. code-block:: python from pyramid.security import remember headers = remember(request, 'chrism', password='123', max_age='86400') response = request.response response.headerlist.extend(headers) return response If no :term:`authentication policy` is in use, this function will 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, userid, **kw) def forget(request): """ Return a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``) suitable for 'forgetting' the set of credentials possessed by the currently authenticated user. A common usage might look like so within the body of a view function (``response`` is assumed to be an :term:`WebOb` -style :term:`response` object computed previously by the view code): .. code-block:: python from pyramid.security import forget headers = forget(request) response.headerlist.extend(headers) return response If no :term:`authentication policy` is in use, this function will always return an empty sequence. """ policy = _get_authentication_policy(request) if policy is None: return [] return policy.forget(request) def principals_allowed_by_permission(context, permission): """ Provided a ``context`` (a resource object), and a ``permission`` (a string or unicode object), if a :term:`authorization policy` is in effect, return a sequence of :term:`principal` ids that possess the permission in the ``context``. If no authorization policy is in effect, this will return a sequence with the single value :mod:`pyramid.security.Everyone` (the special principal identifier representing all principals). .. note:: even if an :term:`authorization policy` is in effect, some (exotic) authorization policies may not implement the required machinery for this function; those will cause a :exc:`NotImplementedError` exception to be raised when this function is invoked. """ reg = get_current_registry() policy = reg.queryUtility(IAuthorizationPolicy) if policy is None: return [Everyone] return policy.principals_allowed_by_permission(context, permission) def view_execution_permitted(context, request, name=''): """ If the view specified by ``context`` and ``name`` is protected by a :term:`permission`, check the permission associated with the view using the effective authentication/authorization policies and the ``request``. Return a boolean result. If no :term:`authorization policy` is in effect, or if the view is not protected by a permission, return ``True``. If no view can view found, an exception will be raised. .. versionchanged:: 1.4a4 An exception is raised if no view is found. """ reg = _get_registry(request) provides = [IViewClassifier] + map_(providedBy, (request, context)) # XXX not sure what to do here about using _find_views or analogue; # for now let's just keep it as-is view = reg.adapters.lookup(provides, ISecuredView, name=name) if view is None: view = reg.adapters.lookup(provides, IView, name=name) if view is None: raise TypeError('No registered view satisfies the constraints. ' 'It would not make sense to claim that this view ' '"is" or "is not" permitted.') return Allowed( 'Allowed: view name %r in context %r (no permission defined)' % (name, context)) return view.__permitted__(context, request) class PermitsResult(int): def __new__(cls, s, *args): inst = int.__new__(cls, cls.boolval) inst.s = s inst.args = args return inst @property def msg(self): return self.s % self.args def __str__(self): return self.msg def __repr__(self): return '<%s instance at %s with msg %r>' % (self.__class__.__name__, id(self), self.msg) class Denied(PermitsResult): """ An instance of ``Denied`` is returned when a security-related API or other :app:`Pyramid` code denies an action unrelated to an ACL check. It evaluates equal to all boolean false types. It has an attribute named ``msg`` describing the circumstances for the deny.""" boolval = 0 class Allowed(PermitsResult): """ An instance of ``Allowed`` is returned when a security-related API or other :app:`Pyramid` code allows an action unrelated to an ACL check. It evaluates equal to all boolean true types. It has an attribute named ``msg`` describing the circumstances for the allow.""" boolval = 1 class ACLPermitsResult(int): def __new__(cls, ace, acl, permission, principals, context): inst = int.__new__(cls, cls.boolval) inst.permission = permission inst.ace = ace inst.acl = acl inst.principals = principals inst.context = context return inst @property def msg(self): s = ('%s permission %r via ACE %r in ACL %r on context %r for ' 'principals %r') return s % (self.__class__.__name__, self.permission, self.ace, self.acl, self.context, self.principals) def __str__(self): return self.msg def __repr__(self): return '<%s instance at %s with msg %r>' % (self.__class__.__name__, id(self), self.msg) class ACLDenied(ACLPermitsResult): """ An instance of ``ACLDenied`` represents that a security check made explicitly against ACL was denied. It evaluates equal to all boolean false types. It also has the following attributes: ``acl``, ``ace``, ``permission``, ``principals``, and ``context``. These attributes indicate the security values involved in the request. Its __str__ method prints a summary of these attributes for debugging purposes. The same summary is available as the ``msg`` attribute.""" boolval = 0 class ACLAllowed(ACLPermitsResult): """ An instance of ``ACLAllowed`` represents that a security check made explicitly against ACL was allowed. It evaluates equal to all boolean true types. It also has the following attributes: ``acl``, ``ace``, ``permission``, ``principals``, and ``context``. These attributes indicate the security values involved in the request. Its __str__ method prints a summary of these attributes for debugging purposes. The same summary is available as the ``msg`` attribute.""" boolval = 1 class AuthenticationAPIMixin(object): def _get_authentication_policy(self): reg = _get_registry(self) return reg.queryUtility(IAuthenticationPolicy) @property def authenticated_userid(self): """ Return 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. .. versionadded:: 1.5 """ policy = self._get_authentication_policy() if policy is None: return None return policy.authenticated_userid(self) @property def unauthenticated_userid(self): """ Return an object 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. .. versionadded:: 1.5 """ policy = self._get_authentication_policy() if policy is None: return None return policy.unauthenticated_userid(self) @property def effective_principals(self): """ Return the list of 'effective' :term:`principal` identifiers for the ``request``. If no :term:`authentication policy` is in effect, this will return a one-element list containing the :data:`pyramid.security.Everyone` principal. .. versionadded:: 1.5 """ policy = self._get_authentication_policy() if policy is None: return [Everyone] return policy.effective_principals(self) class AuthorizationAPIMixin(object): def has_permission(self, permission, context=None): """ Given a permission and an optional context, returns an instance of :data:`pyramid.security.Allowed` if the permission is granted to this request with the provided context, or the context already associated with the request. Otherwise, returns an instance of :data:`pyramid.security.Denied`. This method delegates to the current authentication and authorization policies. Returns :data:`pyramid.security.Allowed` unconditionally if no authentication policy has been registered for this request. If ``context`` is not supplied or is supplied as ``None``, the context used is the ``request.context`` attribute. :param permission: Does this request have the given permission? :type permission: unicode, str :param context: A resource object or ``None`` :type context: object :returns: `pyramid.security.PermitsResult` .. versionadded:: 1.5 """ if context is None: context = self.context reg = _get_registry(self) authn_policy = reg.queryUtility(IAuthenticationPolicy) if authn_policy is None: return Allowed('No authentication policy in use.') authz_policy = reg.queryUtility(IAuthorizationPolicy) if authz_policy is None: raise ValueError('Authentication policy registered without ' 'authorization policy') # should never happen principals = authn_policy.effective_principals(self) return authz_policy.permits(context, principals, permission) pyramid-1.6/pyramid/session.py0000644000076500000240000005475712625453553017310 0ustar michaelstaff00000000000000import base64 import binascii import hashlib import hmac import os import time from zope.deprecation import deprecated from zope.interface import implementer from webob.cookies import SignedSerializer from pyramid.compat import ( pickle, PY3, text_, bytes_, native_, ) from pyramid.exceptions import BadCSRFToken from pyramid.interfaces import ISession from pyramid.util import strings_differ def manage_accessed(wrapped): """ Decorator which causes a cookie to be renewed when an accessor method is called.""" def accessed(session, *arg, **kw): session.accessed = now = int(time.time()) if session._reissue_time is not None: if now - session.renewed > session._reissue_time: session.changed() return wrapped(session, *arg, **kw) accessed.__doc__ = wrapped.__doc__ return accessed def manage_changed(wrapped): """ Decorator which causes a cookie to be set when a setter method is called.""" def changed(session, *arg, **kw): session.accessed = int(time.time()) session.changed() return wrapped(session, *arg, **kw) changed.__doc__ = wrapped.__doc__ return changed def signed_serialize(data, secret): """ Serialize any pickleable structure (``data``) and sign it using the ``secret`` (must be a string). Return the serialization, which includes the signature as its first 40 bytes. The ``signed_deserialize`` method will deserialize such a value. This function is useful for creating signed cookies. For example: .. code-block:: python cookieval = signed_serialize({'a':1}, 'secret') response.set_cookie('signed_cookie', cookieval) """ pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) try: # bw-compat with pyramid <= 1.5b1 where latin1 is the default secret = bytes_(secret) except UnicodeEncodeError: secret = bytes_(secret, 'utf-8') sig = hmac.new(secret, pickled, hashlib.sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) def signed_deserialize(serialized, secret, hmac=hmac): """ Deserialize the value returned from ``signed_serialize``. If the value cannot be deserialized for any reason, a :exc:`ValueError` exception will be raised. This function is useful for deserializing a signed cookie value created by ``signed_serialize``. For example: .. code-block:: python cookieval = request.cookies['signed_cookie'] data = signed_deserialize(cookieval, 'secret') """ # hmac parameterized only for unit tests try: input_sig, pickled = (bytes_(serialized[:40]), base64.b64decode(bytes_(serialized[40:]))) except (binascii.Error, TypeError) as e: # Badly formed data can make base64 die raise ValueError('Badly formed base64 data: %s' % e) try: # bw-compat with pyramid <= 1.5b1 where latin1 is the default secret = bytes_(secret) except UnicodeEncodeError: secret = bytes_(secret, 'utf-8') sig = bytes_(hmac.new(secret, pickled, hashlib.sha1).hexdigest()) # Avoid timing attacks (see # http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf) if strings_differ(sig, input_sig): raise ValueError('Invalid signature') return pickle.loads(pickled) def check_csrf_token(request, token='csrf_token', header='X-CSRF-Token', raises=True): """ Check the CSRF token in the request's session against the value in ``request.params.get(token)`` or ``request.headers.get(header)``. If a ``token`` keyword is not supplied to this function, the string ``csrf_token`` will be used to look up the token in ``request.params``. If a ``header`` keyword is not supplied to this function, the string ``X-CSRF-Token`` will be used to look up the token in ``request.headers``. If the value supplied by param or by header doesn't match the value supplied by ``request.session.get_csrf_token()``, and ``raises`` is ``True``, this function will raise an :exc:`pyramid.exceptions.BadCSRFToken` exception. If the check does succeed and ``raises`` is ``False``, this function will return ``False``. If the CSRF check is successful, this function will return ``True`` unconditionally. Note that using this function requires that a :term:`session factory` is configured. .. versionadded:: 1.4a2 """ supplied_token = request.params.get(token, request.headers.get(header, "")) if strings_differ(request.session.get_csrf_token(), supplied_token): if raises: raise BadCSRFToken('check_csrf_token(): Invalid token') return False return True class PickleSerializer(object): """ A serializer that uses the pickle protocol to dump Python data to bytes. This is the default serializer used by Pyramid. ``protocol`` may be specified to control the version of pickle used. Defaults to :attr:`pickle.HIGHEST_PROTOCOL`. """ def __init__(self, protocol=pickle.HIGHEST_PROTOCOL): self.protocol = protocol def loads(self, bstruct): """Accept bytes and return a Python object.""" return pickle.loads(bstruct) def dumps(self, appstruct): """Accept a Python object and return bytes.""" return pickle.dumps(appstruct, self.protocol) def BaseCookieSessionFactory( serializer, cookie_name='session', max_age=None, path='/', domain=None, secure=False, httponly=False, timeout=1200, reissue_time=0, set_on_exception=True, ): """ .. versionadded:: 1.5 Configure a :term:`session factory` which will provide cookie-based sessions. The return value of this function is a :term:`session factory`, which may be provided as the ``session_factory`` argument of a :class:`pyramid.config.Configurator` constructor, or used as the ``session_factory`` argument of the :meth:`pyramid.config.Configurator.set_session_factory` method. The session factory returned by this function will create sessions which are limited to storing fewer than 4000 bytes of data (as the payload must fit into a single cookie). .. warning: This class provides no protection from tampering and is only intended to be used by framework authors to create their own cookie-based session factories. Parameters: ``serializer`` An object with two methods: ``loads`` and ``dumps``. The ``loads`` method should accept bytes and return a Python object. The ``dumps`` method should accept a Python object and return bytes. A ``ValueError`` should be raised for malformed inputs. ``cookie_name`` The name of the cookie used for sessioning. Default: ``'session'``. ``max_age`` The maximum age of the cookie used for sessioning (in seconds). Default: ``None`` (browser scope). ``path`` The path used for the session cookie. Default: ``'/'``. ``domain`` The domain used for the session cookie. Default: ``None`` (no domain). ``secure`` The 'secure' flag of the session cookie. Default: ``False``. ``httponly`` Hide the cookie from Javascript by setting the 'HttpOnly' flag of the session cookie. Default: ``False``. ``timeout`` A number of seconds of inactivity before a session times out. If ``None`` then the cookie never expires. This lifetime only applies to the *value* within the cookie. Meaning that if the cookie expires due to a lower ``max_age``, then this setting has no effect. Default: ``1200``. ``reissue_time`` The number of seconds that must pass before the cookie is automatically reissued as the result of a request which accesses the session. The duration is measured as the number of seconds since the last session cookie was issued and 'now'. If this value is ``0``, a new cookie will be reissued on every request accessing the session. If ``None`` then the cookie's lifetime will never be extended. A good rule of thumb: if you want auto-expired cookies based on inactivity: set the ``timeout`` value to 1200 (20 mins) and set the ``reissue_time`` value to perhaps a tenth of the ``timeout`` value (120 or 2 mins). It's nonsensical to set the ``timeout`` value lower than the ``reissue_time`` value, as the ticket will never be reissued. However, such a configuration is not explicitly prevented. Default: ``0``. ``set_on_exception`` If ``True``, set a session cookie even if an exception occurs while rendering a view. Default: ``True``. .. versionadded: 1.5a3 """ @implementer(ISession) class CookieSession(dict): """ Dictionary-like session object """ # configuration parameters _cookie_name = cookie_name _cookie_max_age = max_age if max_age is None else int(max_age) _cookie_path = path _cookie_domain = domain _cookie_secure = secure _cookie_httponly = httponly _cookie_on_exception = set_on_exception _timeout = timeout if timeout is None else int(timeout) _reissue_time = reissue_time if reissue_time is None else int(reissue_time) # dirty flag _dirty = False def __init__(self, request): self.request = request now = time.time() created = renewed = now new = True value = None state = {} cookieval = request.cookies.get(self._cookie_name) if cookieval is not None: try: value = serializer.loads(bytes_(cookieval)) except ValueError: # the cookie failed to deserialize, dropped value = None if value is not None: try: # since the value is not necessarily signed, we have # to unpack it a little carefully rval, cval, sval = value renewed = float(rval) created = float(cval) state = sval new = False except (TypeError, ValueError): # value failed to unpack properly or renewed was not # a numeric type so we'll fail deserialization here state = {} if self._timeout is not None: if now - renewed > self._timeout: # expire the session because it was not renewed # before the timeout threshold state = {} self.created = created self.accessed = renewed self.renewed = renewed self.new = new dict.__init__(self, state) # ISession methods def changed(self): if not self._dirty: self._dirty = True def set_cookie_callback(request, response): self._set_cookie(response) self.request = None # explicitly break cycle for gc self.request.add_response_callback(set_cookie_callback) def invalidate(self): self.clear() # XXX probably needs to unset cookie # non-modifying dictionary methods get = manage_accessed(dict.get) __getitem__ = manage_accessed(dict.__getitem__) items = manage_accessed(dict.items) values = manage_accessed(dict.values) keys = manage_accessed(dict.keys) __contains__ = manage_accessed(dict.__contains__) __len__ = manage_accessed(dict.__len__) __iter__ = manage_accessed(dict.__iter__) if not PY3: iteritems = manage_accessed(dict.iteritems) itervalues = manage_accessed(dict.itervalues) iterkeys = manage_accessed(dict.iterkeys) has_key = manage_accessed(dict.has_key) # modifying dictionary methods clear = manage_changed(dict.clear) update = manage_changed(dict.update) setdefault = manage_changed(dict.setdefault) pop = manage_changed(dict.pop) popitem = manage_changed(dict.popitem) __setitem__ = manage_changed(dict.__setitem__) __delitem__ = manage_changed(dict.__delitem__) # flash API methods @manage_changed def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) @manage_changed def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage @manage_accessed def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods @manage_changed def new_csrf_token(self): token = text_(binascii.hexlify(os.urandom(20))) self['_csrft_'] = token return token @manage_accessed def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token # non-API methods def _set_cookie(self, response): if not self._cookie_on_exception: exception = getattr(self.request, 'exception', None) if exception is not None: # dont set a cookie during exceptions return False cookieval = native_(serializer.dumps( (self.accessed, self.created, dict(self)) )) if len(cookieval) > 4064: raise ValueError( 'Cookie value is too long to store (%s bytes)' % len(cookieval) ) response.set_cookie( self._cookie_name, value=cookieval, max_age=self._cookie_max_age, path=self._cookie_path, domain=self._cookie_domain, secure=self._cookie_secure, httponly=self._cookie_httponly, ) return True return CookieSession def UnencryptedCookieSessionFactoryConfig( secret, timeout=1200, cookie_name='session', cookie_max_age=None, cookie_path='/', cookie_domain=None, cookie_secure=False, cookie_httponly=False, cookie_on_exception=True, signed_serialize=signed_serialize, signed_deserialize=signed_deserialize, ): """ .. deprecated:: 1.5 Use :func:`pyramid.session.SignedCookieSessionFactory` instead. Caveat: Cookies generated using ``SignedCookieSessionFactory`` are not compatible with cookies generated using ``UnencryptedCookieSessionFactory``, so existing user session data will be destroyed if you switch to it. Configure a :term:`session factory` which will provide unencrypted (but signed) cookie-based sessions. The return value of this function is a :term:`session factory`, which may be provided as the ``session_factory`` argument of a :class:`pyramid.config.Configurator` constructor, or used as the ``session_factory`` argument of the :meth:`pyramid.config.Configurator.set_session_factory` method. The session factory returned by this function will create sessions which are limited to storing fewer than 4000 bytes of data (as the payload must fit into a single cookie). Parameters: ``secret`` A string which is used to sign the cookie. ``timeout`` A number of seconds of inactivity before a session times out. ``cookie_name`` The name of the cookie used for sessioning. ``cookie_max_age`` The maximum age of the cookie used for sessioning (in seconds). Default: ``None`` (browser scope). ``cookie_path`` The path used for the session cookie. ``cookie_domain`` The domain used for the session cookie. Default: ``None`` (no domain). ``cookie_secure`` The 'secure' flag of the session cookie. ``cookie_httponly`` The 'httpOnly' flag of the session cookie. ``cookie_on_exception`` If ``True``, set a session cookie even if an exception occurs while rendering a view. ``signed_serialize`` A callable which takes more or less arbitrary Python data structure and a secret and returns a signed serialization in bytes. Default: ``signed_serialize`` (using pickle). ``signed_deserialize`` A callable which takes a signed and serialized data structure in bytes and a secret and returns the original data structure if the signature is valid. Default: ``signed_deserialize`` (using pickle). """ class SerializerWrapper(object): def __init__(self, secret): self.secret = secret def loads(self, bstruct): return signed_deserialize(bstruct, secret) def dumps(self, appstruct): return signed_serialize(appstruct, secret) serializer = SerializerWrapper(secret) return BaseCookieSessionFactory( serializer, cookie_name=cookie_name, max_age=cookie_max_age, path=cookie_path, domain=cookie_domain, secure=cookie_secure, httponly=cookie_httponly, timeout=timeout, reissue_time=0, # to keep session.accessed == session.renewed set_on_exception=cookie_on_exception, ) deprecated( 'UnencryptedCookieSessionFactoryConfig', 'The UnencryptedCookieSessionFactoryConfig callable is deprecated as of ' 'Pyramid 1.5. Use ``pyramid.session.SignedCookieSessionFactory`` instead.' ' Caveat: Cookies generated using SignedCookieSessionFactory are not ' 'compatible with cookies generated using UnencryptedCookieSessionFactory, ' 'so existing user session data will be destroyed if you switch to it.' ) def SignedCookieSessionFactory( secret, cookie_name='session', max_age=None, path='/', domain=None, secure=False, httponly=False, set_on_exception=True, timeout=1200, reissue_time=0, hashalg='sha512', salt='pyramid.session.', serializer=None, ): """ .. versionadded:: 1.5 Configure a :term:`session factory` which will provide signed cookie-based sessions. The return value of this function is a :term:`session factory`, which may be provided as the ``session_factory`` argument of a :class:`pyramid.config.Configurator` constructor, or used as the ``session_factory`` argument of the :meth:`pyramid.config.Configurator.set_session_factory` method. The session factory returned by this function will create sessions which are limited to storing fewer than 4000 bytes of data (as the payload must fit into a single cookie). Parameters: ``secret`` A string which is used to sign the cookie. The secret should be at least as long as the block size of the selected hash algorithm. For ``sha512`` this would mean a 128 bit (64 character) secret. It should be unique within the set of secret values provided to Pyramid for its various subsystems (see :ref:`admonishment_against_secret_sharing`). ``hashalg`` The HMAC digest algorithm to use for signing. The algorithm must be supported by the :mod:`hashlib` library. Default: ``'sha512'``. ``salt`` A namespace to avoid collisions between different uses of a shared secret. Reusing a secret for different parts of an application is strongly discouraged (see :ref:`admonishment_against_secret_sharing`). Default: ``'pyramid.session.'``. ``cookie_name`` The name of the cookie used for sessioning. Default: ``'session'``. ``max_age`` The maximum age of the cookie used for sessioning (in seconds). Default: ``None`` (browser scope). ``path`` The path used for the session cookie. Default: ``'/'``. ``domain`` The domain used for the session cookie. Default: ``None`` (no domain). ``secure`` The 'secure' flag of the session cookie. Default: ``False``. ``httponly`` Hide the cookie from Javascript by setting the 'HttpOnly' flag of the session cookie. Default: ``False``. ``timeout`` A number of seconds of inactivity before a session times out. If ``None`` then the cookie never expires. This lifetime only applies to the *value* within the cookie. Meaning that if the cookie expires due to a lower ``max_age``, then this setting has no effect. Default: ``1200``. ``reissue_time`` The number of seconds that must pass before the cookie is automatically reissued as the result of accessing the session. The duration is measured as the number of seconds since the last session cookie was issued and 'now'. If this value is ``0``, a new cookie will be reissued on every request accessing the session. If ``None`` then the cookie's lifetime will never be extended. A good rule of thumb: if you want auto-expired cookies based on inactivity: set the ``timeout`` value to 1200 (20 mins) and set the ``reissue_time`` value to perhaps a tenth of the ``timeout`` value (120 or 2 mins). It's nonsensical to set the ``timeout`` value lower than the ``reissue_time`` value, as the ticket will never be reissued. However, such a configuration is not explicitly prevented. Default: ``0``. ``set_on_exception`` If ``True``, set a session cookie even if an exception occurs while rendering a view. Default: ``True``. ``serializer`` An object with two methods: ``loads`` and ``dumps``. The ``loads`` method should accept bytes and return a Python object. The ``dumps`` method should accept a Python object and return bytes. A ``ValueError`` should be raised for malformed inputs. If a serializer is not passed, the :class:`pyramid.session.PickleSerializer` serializer will be used. .. versionadded: 1.5a3 """ if serializer is None: serializer = PickleSerializer() signed_serializer = SignedSerializer( secret, salt, hashalg, serializer=serializer, ) return BaseCookieSessionFactory( signed_serializer, cookie_name=cookie_name, max_age=max_age, path=path, domain=domain, secure=secure, httponly=httponly, timeout=timeout, reissue_time=reissue_time, set_on_exception=set_on_exception, ) pyramid-1.6/pyramid/settings.py0000644000076500000240000000217212517346416017444 0ustar michaelstaff00000000000000from pyramid.compat import string_types truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) def asbool(s): """ Return the boolean value ``True`` if the case-lowered value of string input ``s`` is any of ``t``, ``true``, ``y``, ``on``, or ``1``, otherwise return the boolean value ``False``. If ``s`` is the value ``None``, return ``False``. If ``s`` is already one of the boolean values ``True`` or ``False``, return it.""" if s is None: return False if isinstance(s, bool): return s s = str(s).strip() return s.lower() in truthy def aslist_cronly(value): if isinstance(value, string_types): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) def aslist(value, flatten=True): """ Return a list of strings, separating the input based on newlines and, if flatten=True (the default), also split on spaces within each line.""" values = aslist_cronly(value) if not flatten: return values result = [] for value in values: subvalues = value.split() result.extend(subvalues) return result pyramid-1.6/pyramid/static.py0000644000076500000240000002430112642137120017056 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- import json import os from os.path import ( getmtime, normcase, normpath, join, isdir, exists, ) from pkg_resources import ( resource_exists, resource_filename, resource_isdir, ) from repoze.lru import lru_cache from pyramid.asset import ( abspath_from_asset_spec, resolve_asset_spec, ) from pyramid.compat import text_ from pyramid.httpexceptions import ( HTTPNotFound, HTTPMovedPermanently, ) from pyramid.path import caller_package from pyramid.response import FileResponse from pyramid.traversal import traversal_path_info slash = text_('/') class static_view(object): """ An instance of this class is a callable which can act as a :app:`Pyramid` :term:`view callable`; this view will serve static files from a directory on disk based on the ``root_dir`` you provide to its constructor. The directory may contain subdirectories (recursively); the static view implementation will descend into these directories as necessary based on the components of the URL in order to resolve a path into a response. You may pass an absolute or relative filesystem path or a :term:`asset specification` representing the directory containing static files as the ``root_dir`` argument to this class' constructor. If the ``root_dir`` path is relative, and the ``package_name`` argument is ``None``, ``root_dir`` will be considered relative to the directory in which the Python file which *calls* ``static`` resides. If the ``package_name`` name argument is provided, and a relative ``root_dir`` is provided, the ``root_dir`` will be considered relative to the Python :term:`package` specified by ``package_name`` (a dotted path to a Python package). ``cache_max_age`` influences the ``Expires`` and ``Max-Age`` response headers returned by the view (default is 3600 seconds or one hour). ``use_subpath`` influences whether ``request.subpath`` will be used as ``PATH_INFO`` when calling the underlying WSGI application which actually serves the static files. If it is ``True``, the static application will consider ``request.subpath`` as ``PATH_INFO`` input. If it is ``False``, the static application will consider request.environ[``PATH_INFO``] as ``PATH_INFO`` input. By default, this is ``False``. .. note:: If the ``root_dir`` is relative to a :term:`package`, or is a :term:`asset specification` the :app:`Pyramid` :class:`pyramid.config.Configurator` method can be used to override assets within the named ``root_dir`` package-relative directory. However, if the ``root_dir`` is absolute, configuration will not be able to override the assets it contains. """ def __init__(self, root_dir, cache_max_age=3600, package_name=None, 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``). self.cache_max_age = cache_max_age if package_name is None: package_name = caller_package().__name__ package_name, docroot = resolve_asset_spec(root_dir, package_name) self.use_subpath = use_subpath self.package_name = package_name 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: raise HTTPNotFound('Out of bounds: %s' % request.url) if self.package_name: # package resource resource_path ='%s/%s' % (self.docroot.rstrip('/'), path) if resource_isdir(self.package_name, resource_path): if not request.path_url.endswith('/'): self.add_slash_redirect(request) resource_path = '%s/%s' % (resource_path.rstrip('/'),self.index) if not resource_exists(self.package_name, resource_path): raise HTTPNotFound(request.url) filepath = resource_filename(self.package_name, resource_path) else: # filesystem file # os.path.normpath converts / to \ on windows filepath = normcase(normpath(join(self.norm_docroot, path))) if isdir(filepath): if not request.path_url.endswith('/'): self.add_slash_redirect(request) filepath = join(filepath, self.index) if not exists(filepath): raise HTTPNotFound(request.url) return FileResponse(filepath, request, self.cache_max_age) def add_slash_redirect(self, request): url = request.path_url + '/' qs = request.query_string if qs: url = url + '?' + qs raise HTTPMovedPermanently(url) _seps = set(['/', os.sep]) def _contains_slash(item): for sep in _seps: if sep in item: return True _has_insecure_pathelement = set(['..', '.', '']).intersection @lru_cache(1000) def _secure_path(path_tuple): if _has_insecure_pathelement(path_tuple): # belt-and-suspenders security; this should never be true # unless someone screws up the traversal_path code # (request.subpath is computed via traversal_path too) return None if any([_contains_slash(item) for item in path_tuple]): return None encoded = slash.join(path_tuple) # will be unicode return encoded class QueryStringCacheBuster(object): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which adds a token for cache busting in the query string of an asset URL. The optional ``param`` argument determines the name of the parameter added to the query string and defaults to ``'x'``. To use this class, subclass it and provide a ``tokenize`` method which accepts ``request, pathspec, kw`` and returns a token. .. versionadded:: 1.6 """ def __init__(self, param='x'): self.param = param def __call__(self, request, subpath, kw): token = self.tokenize(request, 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(QueryStringCacheBuster): """ 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'): super(QueryStringConstantCacheBuster, self).__init__(param=param) self._token = token def tokenize(self, request, subpath, kw): return self._token class ManifestCacheBuster(object): """ An implementation of :class:`~pyramid.interfaces.ICacheBuster` which uses a supplied manifest file to map an asset path to a cache-busted version of the path. The ``manifest_spec`` can be an absolute path or a :term:`asset specification` pointing to a package-relative file. The manifest file is expected to conform to the following simple JSON format: .. code-block:: json { "css/main.css": "css/main-678b7c80.css", "images/background.png": "images/background-a8169106.png", } By default, it is a JSON-serialized dictionary where the keys are the source asset paths used in calls to :meth:`~pyramid.request.Request.static_url`. For example:: .. code-block:: python >>> request.static_url('myapp:static/css/main.css') "http://www.example.com/static/css/main-678b7c80.css" The file format and location can be changed by subclassing and overriding :meth:`.parse_manifest`. If a path is not found in the manifest it will pass through unchanged. If ``reload`` is ``True`` then the manifest file will be reloaded when changed. It is not recommended to leave this enabled in production. If the manifest file cannot be found on disk it will be treated as an empty mapping unless ``reload`` is ``False``. .. versionadded:: 1.6 """ exists = staticmethod(exists) # testing getmtime = staticmethod(getmtime) # testing def __init__(self, manifest_spec, reload=False): package_name = caller_package().__name__ self.manifest_path = abspath_from_asset_spec( manifest_spec, package_name) self.reload = reload self._mtime = None if not reload: self._manifest = self.get_manifest() def get_manifest(self): with open(self.manifest_path, 'rb') as fp: return self.parse_manifest(fp.read()) def parse_manifest(self, content): """ Parse the ``content`` read from the ``manifest_path`` into a dictionary mapping. Subclasses may override this method to use something other than ``json.loads`` to load any type of file format and return a conforming dictionary. """ return json.loads(content.decode('utf-8')) @property def manifest(self): """ The current manifest dictionary.""" if self.reload: if not self.exists(self.manifest_path): return {} mtime = self.getmtime(self.manifest_path) if self._mtime is None or mtime > self._mtime: self._manifest = self.get_manifest() self._mtime = mtime return self._manifest def __call__(self, request, subpath, kw): subpath = self.manifest.get(subpath, subpath) return (subpath, kw) pyramid-1.6/pyramid/testing.py0000644000076500000240000005267212642137120017260 0ustar michaelstaff00000000000000import copy import os from contextlib import contextmanager from zope.interface import ( implementer, alsoProvides, ) from pyramid.interfaces import ( IRequest, ISession, ) from pyramid.compat import ( PY3, PYPY, class_types, ) from pyramid.config import Configurator from pyramid.decorator import reify from pyramid.path import caller_package from pyramid.response import _get_response_factory from pyramid.registry import Registry from pyramid.security import ( Authenticated, Everyone, AuthenticationAPIMixin, AuthorizationAPIMixin, ) from pyramid.threadlocal import ( get_current_registry, manager, ) from pyramid.i18n import LocalizerRequestMixin from pyramid.request import CallbackMethodsMixin from pyramid.url import URLMethodsMixin from pyramid.util import InstancePropertyMixin _marker = object() class DummyRootFactory(object): __parent__ = None __name__ = None def __init__(self, request): if 'bfg.routes.matchdict' in request: self.__dict__.update(request['bfg.routes.matchdict']) class DummySecurityPolicy(object): """ A standin for both an IAuthentication and IAuthorization policy """ def __init__(self, userid=None, groupids=(), permissive=True, remember_result=None, forget_result=None): self.userid = userid self.groupids = groupids self.permissive = permissive if remember_result is None: remember_result = [] if forget_result is None: forget_result = [] self.remember_result = remember_result self.forget_result = forget_result def authenticated_userid(self, request): return self.userid def unauthenticated_userid(self, request): return self.userid def effective_principals(self, request): effective_principals = [Everyone] if self.userid: effective_principals.append(Authenticated) effective_principals.append(self.userid) effective_principals.extend(self.groupids) return effective_principals def remember(self, request, userid, **kw): self.remembered = userid return self.remember_result def forget(self, request): self.forgotten = True return self.forget_result def permits(self, context, principals, permission): return self.permissive def principals_allowed_by_permission(self, context, permission): return self.effective_principals(None) class DummyTemplateRenderer(object): """ An instance of this class is returned from :meth:`pyramid.config.Configurator.testing_add_renderer`. It has a helper function (``assert_``) that makes it possible to make an assertion which compares data passed to the renderer by the view function against expected key/value pairs. """ def __init__(self, string_response=''): self._received = {} self._string_response = string_response self._implementation = MockTemplate(string_response) # For in-the-wild test code that doesn't create its own renderer, # but mutates our internals instead. When all you read is the # source code, *everything* is an API! def _get_string_response(self): return self._string_response def _set_string_response(self, response): self._string_response = response self._implementation.response = response string_response = property(_get_string_response, _set_string_response) def implementation(self): return self._implementation def __call__(self, kw, system=None): if system: self._received.update(system) self._received.update(kw) return self.string_response def __getattr__(self, k): """ Backwards compatibility """ val = self._received.get(k, _marker) if val is _marker: val = self._implementation._received.get(k, _marker) if val is _marker: raise AttributeError(k) return val def assert_(self, **kw): """ Accept an arbitrary set of assertion key/value pairs. For each assertion key/value pair assert that the renderer (eg. :func:`pyramid.renderers.render_to_response`) received the key with a value that equals the asserted value. If the renderer did not receive the key at all, or the value received by the renderer doesn't match the assertion value, raise an :exc:`AssertionError`.""" for k, v in kw.items(): myval = self._received.get(k, _marker) if myval is _marker: myval = self._implementation._received.get(k, _marker) if myval is _marker: raise AssertionError( 'A value for key "%s" was not passed to the renderer' % k) if myval != v: raise AssertionError( '\nasserted value for %s: %r\nactual value: %r' % ( k, v, myval)) return True class DummyResource: """ A dummy :app:`Pyramid` :term:`resource` object.""" def __init__(self, __name__=None, __parent__=None, __provides__=None, **kw): """ The resource's ``__name__`` attribute will be set to the value of the ``__name__`` argument, and the resource's ``__parent__`` attribute will be set to the value of the ``__parent__`` argument. If ``__provides__`` is specified, it should be an interface object or tuple of interface objects that will be attached to the resulting resource via :func:`zope.interface.alsoProvides`. Any extra keywords passed in the ``kw`` argumnent will be set as direct attributes of the resource object. .. note:: For backwards compatibility purposes, this class can also be imported as :class:`pyramid.testing.DummyModel`. """ self.__name__ = __name__ self.__parent__ = __parent__ if __provides__ is not None: alsoProvides(self, __provides__) self.kw = kw self.__dict__.update(**kw) self.subs = {} def __setitem__(self, name, val): """ When the ``__setitem__`` method is called, the object passed in as ``val`` will be decorated with a ``__parent__`` attribute pointing at the dummy resource and a ``__name__`` attribute that is the value of ``name``. The value will then be returned when dummy resource's ``__getitem__`` is called with the name ``name```.""" val.__name__ = name val.__parent__ = self self.subs[name] = val def __getitem__(self, name): """ Return a named subobject (see ``__setitem__``)""" ob = self.subs[name] return ob def __delitem__(self, name): del self.subs[name] def get(self, name, default=None): return self.subs.get(name, default) def values(self): """ Return the values set by __setitem__ """ return self.subs.values() def items(self): """ Return the items set by __setitem__ """ return self.subs.items() def keys(self): """ Return the keys set by __setitem__ """ return self.subs.keys() __iter__ = keys def __nonzero__(self): return True __bool__ = __nonzero__ def __len__(self): return len(self.subs) def __contains__(self, name): return name in self.subs def clone(self, __name__=_marker, __parent__=_marker, **kw): """ Create a clone of the resource object. If ``__name__`` or ``__parent__`` arguments are passed, use these values to override the existing ``__name__`` or ``__parent__`` of the resource. If any extra keyword args are passed in via the ``kw`` argument, use these keywords to add to or override existing resource keywords (attributes).""" oldkw = self.kw.copy() oldkw.update(kw) inst = self.__class__(self.__name__, self.__parent__, **oldkw) inst.subs = copy.deepcopy(self.subs) if __name__ is not _marker: inst.__name__ = __name__ if __parent__ is not _marker: inst.__parent__ = __parent__ return inst DummyModel = DummyResource # b/w compat (forever) @implementer(ISession) class DummySession(dict): created = None new = True def changed(self): pass def invalidate(self): self.clear() def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage def new_csrf_token(self): token = '0123456789012345678901234567890123456789' self['_csrft_'] = token return token def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token @implementer(IRequest) class DummyRequest( URLMethodsMixin, CallbackMethodsMixin, InstancePropertyMixin, LocalizerRequestMixin, AuthenticationAPIMixin, AuthorizationAPIMixin, ): """ A DummyRequest object (incompletely) imitates a :term:`request` object. The ``params``, ``environ``, ``headers``, ``path``, and ``cookies`` arguments correspond to their :term:`WebOb` equivalents. The ``post`` argument, if passed, populates the request's ``POST`` attribute, but *not* ``params``, in order to allow testing that the app accepts data for a given view only from POST requests. This argument also sets ``self.method`` to "POST". Extra keyword arguments are assigned as attributes of the request itself. Note that DummyRequest does not have complete fidelity with a "real" request. For example, by default, the DummyRequest ``GET`` and ``POST`` attributes are of type ``dict``, unlike a normal Request's GET and POST, which are of type ``MultiDict``. If your code uses the features of MultiDict, you should either use a real :class:`pyramid.request.Request` or adapt your DummyRequest by replacing the attributes with ``MultiDict`` instances. Other similar incompatibilities exist. If you need all the features of a Request, use the :class:`pyramid.request.Request` class itself rather than this class while writing tests. """ method = 'GET' application_url = 'http://example.com' host = 'example.com:80' domain = 'example.com' content_length = 0 query_string = '' charset = 'UTF-8' script_name = '' _registry = None request_iface = IRequest def __init__(self, params=None, environ=None, headers=None, path='/', cookies=None, post=None, **kw): if environ is None: environ = {} if params is None: params = {} if headers is None: headers = {} if cookies is None: cookies = {} self.environ = environ self.headers = headers self.params = params self.cookies = cookies self.matchdict = {} self.GET = params if post is not None: self.method = 'POST' self.POST = post else: self.POST = params self.host_url = self.application_url self.path_url = self.application_url self.url = self.application_url self.path = path self.path_info = path self.script_name = '' self.path_qs = '' self.body = '' self.view_name = '' self.subpath = () self.traversed = () self.virtual_root_path = () self.context = None self.root = None self.virtual_root = None self.marshalled = params # repoze.monty self.session = DummySession() self.__dict__.update(kw) def _get_registry(self): if self._registry is None: return get_current_registry() return self._registry def _set_registry(self, registry): self._registry = registry def _del_registry(self): self._registry = None registry = property(_get_registry, _set_registry, _del_registry) @reify def response(self): f = _get_response_factory(self.registry) return f(self) have_zca = True def setUp(registry=None, request=None, hook_zca=True, autocommit=True, settings=None, package=None): """ Set :app:`Pyramid` registry and request thread locals for the duration of a single unit test. Use this function in the ``setUp`` method of a unittest test case which directly or indirectly uses: - any method of the :class:`pyramid.config.Configurator` object returned by this function. - the :func:`pyramid.threadlocal.get_current_registry` or :func:`pyramid.threadlocal.get_current_request` functions. If you use the ``get_current_*`` functions (or call :app:`Pyramid` code that uses these functions) without calling ``setUp``, :func:`pyramid.threadlocal.get_current_registry` will return a *global* :term:`application registry`, which may cause unit tests to not be isolated with respect to registrations they perform. If the ``registry`` argument is ``None``, a new empty :term:`application registry` will be created (an instance of the :class:`pyramid.registry.Registry` class). If the ``registry`` argument is not ``None``, the value passed in should be an instance of the :class:`pyramid.registry.Registry` class or a suitable testing analogue. After ``setUp`` is finished, the registry returned by the :func:`pyramid.threadlocal.get_current_registry` function will be the passed (or constructed) registry until :func:`pyramid.testing.tearDown` is called (or :func:`pyramid.testing.setUp` is called again) . If the ``hook_zca`` argument is ``True``, ``setUp`` will attempt to perform the operation ``zope.component.getSiteManager.sethook( pyramid.threadlocal.get_current_registry)``, which will cause the :term:`Zope Component Architecture` global API (e.g. :func:`zope.component.getSiteManager`, :func:`zope.component.getAdapter`, and so on) to use the registry constructed by ``setUp`` as the value it returns from :func:`zope.component.getSiteManager`. If the :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 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 for a unit or integration test. The ``registry`` attribute attached to the Configurator instance represents the 'current' :term:`application registry`; the same registry will be returned by :func:`pyramid.threadlocal.get_current_registry` during the execution of the test. """ manager.clear() if registry is None: registry = Registry('testing') 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: config._set_settings(settings) if hasattr(registry, 'registerUtility'): # Sometimes nose calls us with a non-registry object because # it thinks this function is module test setup. Likewise, # someone may be passing us an esoteric "dummy" registry, and # the below won't succeed if it doesn't have a registerUtility # method. config.add_default_renderers() config.add_default_view_predicates() config.add_default_route_predicates() config.commit() global have_zca try: have_zca and hook_zca and config.hook_zca() except ImportError: # pragma: no cover # (dont choke on not being able to import z.component) have_zca = False config.begin(request=request) return config def tearDown(unhook_zca=True): """Undo the effects of :func:`pyramid.testing.setUp`. Use this function in the ``tearDown`` method of a unit test that uses :func:`pyramid.testing.setUp` in its ``setUp`` method. If the ``unhook_zca`` argument is ``True`` (the default), call :func:`zope.component.getSiteManager.reset`. This undoes the action of :func:`pyramid.testing.setUp` when called with the argument ``hook_zca=True``. If :mod:`zope.component` cannot be imported, ``unhook_zca`` is set to ``False``. """ global have_zca if unhook_zca and have_zca: try: from zope.component import getSiteManager getSiteManager.reset() except ImportError: # pragma: no cover have_zca = False info = manager.pop() manager.clear() if info is not None: registry = info['registry'] if hasattr(registry, '__init__') and hasattr(registry, '__name__'): try: registry.__init__(registry.__name__) except TypeError: # calling __init__ is largely for the benefit of # people who want to use the global ZCA registry; # however maybe somebody's using a registry we don't # understand, let's not blow up pass 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): """ Registered by :meth:`pyramid.config.Configurator.testing_add_renderer` as a dummy renderer factory. The indecision about what to use as a key (a spec vs. a relative name) is caused by test suites in the wild believing they can register either. The ``factory`` argument passed to this constructor is usually the *real* template renderer factory, found when ``testing_add_renderer`` is called.""" def __init__(self, name, factory): self.name = name self.factory = factory # the "real" renderer factory reg'd previously self.renderers = {} def add(self, spec, renderer): self.renderers[spec] = renderer if ':' in spec: package, relative = spec.split(':', 1) self.renderers[relative] = renderer def __call__(self, info): spec = info.name renderer = self.renderers.get(spec) if renderer is None: if ':' in spec: package, relative = spec.split(':', 1) renderer = self.renderers.get(relative) if renderer is None: if self.factory: renderer = self.factory(info) else: raise KeyError('No testing renderer registered for %r' % spec) return renderer class MockTemplate(object): def __init__(self, response): self._received = {} self.response = response def __getattr__(self, attrname): return self def __getitem__(self, attrname): return self def __call__(self, *arg, **kw): self._received.update(kw) return self.response def skip_on(*platforms): # pragma: no cover skip = False for platform in platforms: if skip_on.os_name.startswith(platform): skip = True if platform == 'pypy' and PYPY: skip = True if platform == 'py3' and PY3: skip = True def decorator(func): if isinstance(func, class_types): if skip: return None else: return func else: def wrapper(*args, **kw): if skip: return return func(*args, **kw) wrapper.__name__ = func.__name__ wrapper.__doc__ = func.__doc__ return wrapper return decorator skip_on.os_name = os.name # for testing @contextmanager def testConfig(registry=None, request=None, hook_zca=True, autocommit=True, settings=None): """Returns a context manager for test set up. This context manager calls :func:`pyramid.testing.setUp` when entering and :func:`pyramid.testing.tearDown` when exiting. All arguments are passed directly to :func:`pyramid.testing.setUp`. If the ZCA is hooked, it will always be un-hooked in tearDown. This context manager allows you to write test code like this: .. code-block:: python :linenos: with testConfig() as config: config.add_route('bar', '/bar/{id}') req = DummyRequest() resp = myview(req), """ config = setUp(registry=registry, request=request, hook_zca=hook_zca, autocommit=autocommit, settings=settings) try: yield config finally: tearDown(unhook_zca=hook_zca) pyramid-1.6/pyramid/tests/0000755000076500000240000000000012642137501016362 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/__init__.py0000644000076500000240000000010512234375161020472 0ustar michaelstaff00000000000000 def dummy_extend(*args): """used to test Configurator.extend""" pyramid-1.6/pyramid/tests/fixtures/0000755000076500000240000000000012642137501020233 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/fixtures/dummy.ini0000644000076500000240000000011612520062551022062 0ustar michaelstaff00000000000000[app:myapp] use = call:pyramid.tests.test_paster:make_dummyapp foo = %(bar)s pyramid-1.6/pyramid/tests/fixtures/manifest.json0000644000076500000240000000015312621251355022734 0ustar michaelstaff00000000000000{ "css/main.css": "css/main-test.css", "images/background.png": "images/background-a8169106.png" } pyramid-1.6/pyramid/tests/fixtures/manifest2.json0000644000076500000240000000015712621251355023022 0ustar michaelstaff00000000000000{ "css/main.css": "css/main-678b7c80.css", "images/background.png": "images/background-a8169106.png" } pyramid-1.6/pyramid/tests/fixtures/minimal.jpg0000644000076500000240000000116712520062551022365 0ustar michaelstaff00000000000000ÿØÿàJFIF``ÿÛC    $.' ",#(7),01444'9=82<.342ÿÛC  2!!22222222222222222222222222222222222222222222222222ÿÀ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?÷ú(¢€?ÿÙpyramid-1.6/pyramid/tests/fixtures/minimal.pdf0000755000076500000240000000203612520062551022355 0ustar michaelstaff00000000000000%PDF-1.5 %µí®û 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xœ]1 1 …÷üŠ7 Ʀi¯íê"ç*RáDp8üûVm+Há}ð^’™¦Ô}Âêd0=È|ô~ÃÏ"·¥¯t8`p&‡f|]Ë÷ˆN8Æ‚ ßHƒe ¶À$¸¡X€·PI¬Þéa7¬WòÓ#›@ˆºâô=¡ëº"wbád‡š+Ê*õG»¢‘zd ø{"ãBëFéׄ;I endstream endobj 4 0 obj 169 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> >> endobj 5 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 595.275574 841.889771 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /CS /DeviceRGB >> /Resources 2 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 5 0 R ] /Count 1 >> endobj 6 0 obj << /Creator (cairo 1.11.2 (http://cairographics.org)) /Producer (cairo 1.11.2 (http://cairographics.org)) >> endobj 7 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 8 0000000000 65535 f 0000000569 00000 n 0000000283 00000 n 0000000015 00000 n 0000000261 00000 n 0000000355 00000 n 0000000634 00000 n 0000000761 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R >> startxref 813 %%EOF pyramid-1.6/pyramid/tests/fixtures/minimal.txt0000644000076500000240000000000712234375161022422 0ustar michaelstaff00000000000000Hello. pyramid-1.6/pyramid/tests/fixtures/minimal.xml0000644000076500000240000000001012520062551022367 0ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/fixtures/nonminimal.txt0000644000076500000240000000002012234375161023130 0ustar michaelstaff00000000000000Hello, ${name}! pyramid-1.6/pyramid/tests/fixtures/static/0000755000076500000240000000000012642137501021522 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/fixtures/static/.hiddenfile0000644000076500000240000000003112234375161023613 0ustar michaelstaff00000000000000I'm hidden pyramid-1.6/pyramid/tests/fixtures/static/arcs.svg.tgz0000644000076500000240000000426412234375161024007 0ustar michaelstaff00000000000000 pyramid-1.6/pyramid/tests/fixtures/static/index.html0000644000076500000240000000002312234375161023515 0ustar michaelstaff00000000000000staticpyramid-1.6/pyramid/tests/fixtures/static/subdir/0000755000076500000240000000000012642137501023012 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/fixtures/static/subdir/index.html0000644000076500000240000000002412234375161025006 0ustar michaelstaff00000000000000subdir pyramid-1.6/pyramid/tests/pkgs/0000755000076500000240000000000012642137501017326 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/__init__.py0000644000076500000240000000001212234375161021433 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/pkgs/ccbugapp/0000755000076500000240000000000012642137501021112 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/ccbugapp/__init__.py0000644000076500000240000000071312234375161023227 0ustar michaelstaff00000000000000from webob import Response def rdf_view(request): """ """ return Response('rdf') def juri_view(request): """ """ return Response('juri') def includeme(config): config.add_route('rdf', 'licenses/:license_code/:license_version/rdf') config.add_route('juri', 'licenses/:license_code/:license_version/:jurisdiction') config.add_view(rdf_view, route_name='rdf') config.add_view(juri_view, route_name='juri') pyramid-1.6/pyramid/tests/pkgs/conflictapp/0000755000076500000240000000000012642137501021630 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/conflictapp/__init__.py0000644000076500000240000000156512517346416023760 0ustar michaelstaff00000000000000from pyramid.response import Response from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy def aview(request): return Response('a view') def routeview(request): return Response('route view') def protectedview(request): return Response('protected view') def includeme(config): # purposely sorta-randomly ordered (route comes after view naming it, # authz comes after views) config.add_view(aview) config.add_view(protectedview, name='protected', permission='view') config.add_view(routeview, route_name='aroute') config.add_route('aroute', '/route') config.set_authentication_policy(AuthTktAuthenticationPolicy( 'seekri1t', hashalg='sha512')) config.set_authorization_policy(ACLAuthorizationPolicy()) config.include('pyramid.tests.pkgs.conflictapp.included') pyramid-1.6/pyramid/tests/pkgs/conflictapp/included.py0000644000076500000240000000017512234375161023777 0ustar michaelstaff00000000000000from webob import Response def bview(request): return Response('b view') def includeme(config): config.add_view(bview) pyramid-1.6/pyramid/tests/pkgs/defpermbugapp/0000755000076500000240000000000012642137501022147 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/defpermbugapp/__init__.py0000644000076500000240000000170312517346416024271 0ustar michaelstaff00000000000000from webob import Response from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.view import view_config @view_config(name='x') def x_view(request): # pragma: no cover return Response('this is private!') @view_config(name='y', permission='private2') def y_view(request): # pragma: no cover return Response('this is private too!') @view_config(name='z', permission=NO_PERMISSION_REQUIRED) def z_view(request): return Response('this is public') def includeme(config): from pyramid.authorization import ACLAuthorizationPolicy from pyramid.authentication import AuthTktAuthenticationPolicy authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config.scan('pyramid.tests.pkgs.defpermbugapp') config._set_authentication_policy(authn_policy) config._set_authorization_policy(authz_policy) config.set_default_permission('private') pyramid-1.6/pyramid/tests/pkgs/eventonly/0000755000076500000240000000000012642137501021351 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/eventonly/__init__.py0000644000076500000240000000266512517346416023503 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.events import subscriber class Yup(object): def __init__(self, val, config): self.val = val def text(self): return 'path_startswith = %s' % (self.val,) phash = text def __call__(self, event): return getattr(event.response, 'yup', False) class Foo(object): def __init__(self, response): self.response = response class Bar(object): pass @subscriber(Foo) def foo(event): event.response.text += 'foo ' @subscriber(Foo, yup=True) def fooyup(event): event.response.text += 'fooyup ' @subscriber([Foo, Bar]) def foobar(event): event.response.text += 'foobar ' @subscriber([Foo, Bar]) def foobar2(event, context): event.response.text += 'foobar2 ' @subscriber([Foo, Bar], yup=True) def foobaryup(event): event.response.text += 'foobaryup ' @subscriber([Foo, Bar], yup=True) def foobaryup2(event, context): event.response.text += 'foobaryup2 ' @view_config(name='sendfoo') def sendfoo(request): response = request.response response.yup = True request.registry.notify(Foo(response)) return response @view_config(name='sendfoobar') def sendfoobar(request): response = request.response response.yup = True request.registry.notify(Foo(response), Bar()) return response def includeme(config): config.add_subscriber_predicate('yup', Yup) config.scan('pyramid.tests.pkgs.eventonly') pyramid-1.6/pyramid/tests/pkgs/exceptionviewapp/0000755000076500000240000000000012642137501022720 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/exceptionviewapp/__init__.py0000644000076500000240000000316612517346416025047 0ustar michaelstaff00000000000000from pyramid.httpexceptions import HTTPException def includeme(config): config.add_route('route_raise_exception', 'route_raise_exception') config.add_route('route_raise_httpexception', 'route_raise_httpexception') config.add_route('route_raise_exception2', 'route_raise_exception2', factory='.models.route_factory') config.add_route('route_raise_exception3', 'route_raise_exception3', factory='.models.route_factory2') config.add_route('route_raise_exception4', 'route_raise_exception4') config.add_view('.views.maybe') config.add_view('.views.no', context='.models.NotAnException') config.add_view('.views.yes', context=".models.AnException") config.add_view('.views.raise_exception', name='raise_exception') config.add_view('.views.raise_exception', route_name='route_raise_exception') config.add_view('.views.raise_exception', route_name='route_raise_exception2') config.add_view('.views.raise_exception', route_name='route_raise_exception3') config.add_view('.views.whoa', context='.models.AnException', route_name='route_raise_exception3') config.add_view('.views.raise_exception', route_name='route_raise_exception4') config.add_view('.views.whoa', context='.models.AnException', route_name='route_raise_exception4') config.add_view('.views.raise_httpexception', route_name='route_raise_httpexception') config.add_view('.views.catch_httpexception', context=HTTPException) pyramid-1.6/pyramid/tests/pkgs/exceptionviewapp/models.py0000644000076500000240000000040712234375161024561 0ustar michaelstaff00000000000000 class NotAnException(object): pass class AnException(Exception): pass class RouteContext(object): pass class RouteContext2(object): pass def route_factory(*arg): return RouteContext() def route_factory2(*arg): return RouteContext2() pyramid-1.6/pyramid/tests/pkgs/exceptionviewapp/views.py0000644000076500000240000000074112517346416024441 0ustar michaelstaff00000000000000from webob import Response from .models import AnException from pyramid.httpexceptions import HTTPBadRequest def no(request): return Response('no') def yes(request): return Response('yes') def maybe(request): return Response('maybe') def whoa(request): return Response('whoa') def raise_exception(request): raise AnException() def raise_httpexception(request): raise HTTPBadRequest def catch_httpexception(request): return Response('caught') pyramid-1.6/pyramid/tests/pkgs/fixtureapp/0000755000076500000240000000000012642137501021515 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/fixtureapp/__init__.py0000644000076500000240000000104412234375161023630 0ustar michaelstaff00000000000000def includeme(config): config.add_view('.views.fixture_view') config.add_view('.views.exception_view', context=RuntimeError) config.add_view('.views.protected_view', name='protected.html') config.add_view('.views.erroneous_view', name='error.html') config.add_view('.views.fixture_view', name='dummyskin.html', request_type='.views.IDummy') from .models import fixture, IFixture config.registry.registerUtility(fixture, IFixture) config.add_view('.views.fixture_view', name='another.html') pyramid-1.6/pyramid/tests/pkgs/fixtureapp/models.py0000644000076500000240000000014712234375161023357 0ustar michaelstaff00000000000000from zope.interface import Interface class IFixture(Interface): pass def fixture(): """ """ pyramid-1.6/pyramid/tests/pkgs/fixtureapp/subpackage/0000755000076500000240000000000012642137501023622 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/fixtureapp/subpackage/__init__.py0000644000076500000240000000001112234375161025726 0ustar michaelstaff00000000000000#package pyramid-1.6/pyramid/tests/pkgs/fixtureapp/views.py0000644000076500000240000000072112234375161023227 0ustar michaelstaff00000000000000from zope.interface import Interface from webob import Response from pyramid.httpexceptions import HTTPForbidden def fixture_view(context, request): """ """ return Response('fixture') def erroneous_view(context, request): """ """ raise RuntimeError() def exception_view(context, request): """ """ return Response('supressed') def protected_view(context, request): """ """ raise HTTPForbidden() class IDummy(Interface): pass pyramid-1.6/pyramid/tests/pkgs/forbiddenapp/0000755000076500000240000000000012642137501021763 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/forbiddenapp/__init__.py0000644000076500000240000000161312517346416024105 0ustar michaelstaff00000000000000from webob import Response from pyramid.httpexceptions import HTTPForbidden from pyramid.compat import bytes_ def x_view(request): # pragma: no cover return Response('this is private!') def forbidden_view(context, request): msg = context.message result = context.result message = msg + '\n' + str(result) resp = HTTPForbidden() resp.body = bytes_(message) return resp def includeme(config): from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy authn_policy = AuthTktAuthenticationPolicy('seekr1t', hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config._set_authentication_policy(authn_policy) config._set_authorization_policy(authz_policy) config.add_view(x_view, name='x', permission='private') config.add_view(forbidden_view, context=HTTPForbidden) pyramid-1.6/pyramid/tests/pkgs/forbiddenview/0000755000076500000240000000000012642137501022155 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/forbiddenview/__init__.py0000644000076500000240000000202612517346416024276 0ustar michaelstaff00000000000000from pyramid.view import forbidden_view_config, view_config from pyramid.response import Response from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy @forbidden_view_config(route_name='foo') def foo_forbidden(request): # pragma: no cover return Response('foo_forbidden') @forbidden_view_config() def forbidden(request): return Response('generic_forbidden') @view_config(route_name='foo') def foo(request): # pragma: no cover return Response('OK foo') @view_config(route_name='bar') def bar(request): # pragma: no cover return Response('OK bar') def includeme(config): authn_policy = AuthTktAuthenticationPolicy('seekri1', hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.set_default_permission('a') config.add_route('foo', '/foo') config.add_route('bar', '/bar') config.scan('pyramid.tests.pkgs.forbiddenview') pyramid-1.6/pyramid/tests/pkgs/hybridapp/0000755000076500000240000000000012642137501021310 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/hybridapp/__init__.py0000644000076500000240000000410712234375161023426 0ustar michaelstaff00000000000000def includeme(config): # config.add_route('route', 'abc') config.add_view('.views.route_view', route_name='route') # config.add_view('.views.global_view', context='pyramid.traversal.DefaultRootFactory') config.add_view('.views.global2_view', context='pyramid.traversal.DefaultRootFactory', name='global2') config.add_route('route2', 'def') # config.add_view('.views.route2_view', route_name='route2') # config.add_route('route3', 'ghi', use_global_views=True) # config.add_route('route4', 'jkl') # config.add_route('route5', 'mno/*traverse') # config.add_route('route6', 'pqr/*traverse', use_global_views=True) config.add_route('route7', 'error') config.add_view('.views.erroneous_view', route_name='route7') config.add_route('route8', 'error2') config.add_view('.views.erroneous_view', route_name='route8') # config.add_view('.views.exception_view', context=RuntimeError) # config.add_view('.views.exception2_view', context=RuntimeError, route_name='route8') config.add_route('route9', 'error_sub') config.add_view('.views.erroneous_sub_view', route_name='route9') # config.add_view('.views.exception2_view', context='.views.SuperException', route_name='route9') # config.add_view('.views.exception_view', context='.views.SubException') pyramid-1.6/pyramid/tests/pkgs/hybridapp/views.py0000644000076500000240000000126012234375161023021 0ustar michaelstaff00000000000000from webob import Response def route_view(request): """ """ return Response('route') def global_view(request): """ """ return Response('global') def global2_view(request): """ """ return Response('global2') def route2_view(request): """ """ return Response('route2') def exception_view(request): """ """ return Response('supressed') def exception2_view(request): """ """ return Response('supressed2') def erroneous_view(request): """ """ raise RuntimeError() def erroneous_sub_view(request): """ """ raise SubException() class SuperException(Exception): """ """ class SubException(SuperException): """ """ pyramid-1.6/pyramid/tests/pkgs/includeapp1/0000755000076500000240000000000012642137501021533 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/includeapp1/__init__.py0000644000076500000240000000001612234375161023644 0ustar michaelstaff00000000000000# include app pyramid-1.6/pyramid/tests/pkgs/includeapp1/root.py0000644000076500000240000000034612234375161023076 0ustar michaelstaff00000000000000from pyramid.response import Response def aview(request): return Response('root') def configure(config): config.add_view(aview) config.include('pyramid.tests.pkgs.includeapp1.two.configure') config.commit() pyramid-1.6/pyramid/tests/pkgs/includeapp1/three.py0000644000076500000240000000047212234375161023222 0ustar michaelstaff00000000000000from pyramid.response import Response def aview(request): return Response('three') def configure(config): config.add_view(aview, name='three') config.include('pyramid.tests.pkgs.includeapp1.two.configure') # should not cycle config.add_view(aview) # will be overridden by root when resolved pyramid-1.6/pyramid/tests/pkgs/includeapp1/two.py0000644000076500000240000000044012234375161022717 0ustar michaelstaff00000000000000from pyramid.response import Response def aview(request): return Response('two') def configure(config): config.add_view(aview, name='two') config.include('pyramid.tests.pkgs.includeapp1.three.configure') config.add_view(aview) # will be overridden by root when resolved pyramid-1.6/pyramid/tests/pkgs/localeapp/0000755000076500000240000000000012642137501021266 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/__init__.py0000644000076500000240000000001112234375161023372 0ustar michaelstaff00000000000000# a file pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/0000755000076500000240000000000012642137501022525 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/be/0000755000076500000240000000000012642137501023113 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/be/LC_MESSAGES0000644000076500000240000000001012234375161024615 0ustar michaelstaff00000000000000busted. pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de/0000755000076500000240000000000012642137501023115 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de/LC_MESSAGES/0000755000076500000240000000000012642137501024702 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027404 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027411 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de_DE/0000755000076500000240000000000012642137501023465 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de_DE/LC_MESSAGES/0000755000076500000240000000000012642137501025252 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de_DE/LC_MESSAGES/deformsite.mo0000644000076500000240000000102312234375161027747 0ustar michaelstaff00000000000000Þ•4L` aovö Show approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:17-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 Zeigen Genehmigungdifferentpyramid-1.6/pyramid/tests/pkgs/localeapp/locale/de_DE/LC_MESSAGES/deformsite.po0000644000076500000240000000141512234375161027757 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "different" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/en/0000755000076500000240000000000012642137501023127 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/en/LC_MESSAGES/0000755000076500000240000000000012642137501024714 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/en/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027416 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale/en/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027423 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale/GARBAGE0000644000076500000240000000001612234375161023500 0ustar michaelstaff00000000000000Garbage file. pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/0000755000076500000240000000000012642137501022607 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/be/0000755000076500000240000000000012642137501023175 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/be/LC_MESSAGES0000644000076500000240000000001012234375161024677 0ustar michaelstaff00000000000000busted. pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/de/0000755000076500000240000000000012642137501023177 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/de/LC_MESSAGES/0000755000076500000240000000000012642137501024764 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/de/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027466 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/de/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027473 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/en/0000755000076500000240000000000012642137501023211 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/en/LC_MESSAGES/0000755000076500000240000000000012642137501024776 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/en/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027500 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/en/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027505 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale2/GARBAGE0000644000076500000240000000001612234375161023562 0ustar michaelstaff00000000000000Garbage file. pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/0000755000076500000240000000000012642137501022610 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/be/0000755000076500000240000000000012642137501023176 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/be/LC_MESSAGES0000644000076500000240000000001012234375161024700 0ustar michaelstaff00000000000000busted. pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/de/0000755000076500000240000000000012642137501023200 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/de/LC_MESSAGES/0000755000076500000240000000000012642137501024765 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/de/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027467 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/de/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027474 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/en/0000755000076500000240000000000012642137501023212 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/en/LC_MESSAGES/0000755000076500000240000000000012642137501024777 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/en/LC_MESSAGES/deformsite.mo0000644000076500000240000000103712234375161027501 0ustar michaelstaff00000000000000Þ•<\] esz úApproveShow approvalSubmitProject-Id-Version: deformsite 0.0 Report-Msgid-Bugs-To: EMAIL@ADDRESS POT-Creation-Date: 2010-04-22 14:17+0400 PO-Revision-Date: 2010-04-22 14:19-0400 Last-Translator: FULL NAME Language-Team: de Plural-Forms: nplurals=2; plural=(n != 1) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Generated-By: Babel 0.9.5 GenehmigenZeigen GenehmigungBeugenpyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/en/LC_MESSAGES/deformsite.po0000644000076500000240000000151612234375161027506 0ustar michaelstaff00000000000000# German translations for deformsite. # Copyright (C) 2010 ORGANIZATION # This file is distributed under the same license as the deformsite project. # FIRST AUTHOR , 2010. # msgid "" msgstr "" "Project-Id-Version: deformsite 0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "POT-Creation-Date: 2010-04-22 14:17+0400\n" "PO-Revision-Date: 2010-04-22 14:17-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: de \n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.5\n" #: deformsite/__init__.py:458 msgid "Approve" msgstr "Genehmigen" #: deformsite/__init__.py:459 msgid "Show approval" msgstr "Zeigen Genehmigung" #: deformsite/__init__.py:466 msgid "Submit" msgstr "Beugen" pyramid-1.6/pyramid/tests/pkgs/localeapp/locale3/GARBAGE0000644000076500000240000000001612234375161023563 0ustar michaelstaff00000000000000Garbage file. pyramid-1.6/pyramid/tests/pkgs/notfoundview/0000755000076500000240000000000012642137501022055 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/notfoundview/__init__.py0000644000076500000240000000150612234375161024173 0ustar michaelstaff00000000000000from pyramid.view import notfound_view_config, view_config from pyramid.response import Response @notfound_view_config(route_name='foo', append_slash=True) def foo_notfound(request): # pragma: no cover return Response('foo_notfound') @notfound_view_config(route_name='baz') def baz_notfound(request): return Response('baz_notfound') @notfound_view_config(append_slash=True) def notfound(request): return Response('generic_notfound') @view_config(route_name='bar') def bar(request): return Response('OK bar') @view_config(route_name='foo2') def foo2(request): return Response('OK foo2') def includeme(config): config.add_route('foo', '/foo') config.add_route('foo2', '/foo/') config.add_route('bar', '/bar/') config.add_route('baz', '/baz') config.scan('pyramid.tests.pkgs.notfoundview') pyramid-1.6/pyramid/tests/pkgs/permbugapp/0000755000076500000240000000000012642137501021470 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/permbugapp/__init__.py0000644000076500000240000000154612517346416023617 0ustar michaelstaff00000000000000from pyramid.compat import escape from pyramid.security import view_execution_permitted from pyramid.response import Response def x_view(request): # pragma: no cover return Response('this is private!') def test(context, request): # should return false msg = 'Allow ./x? %s' % repr(view_execution_permitted( context, request, 'x')) return Response(escape(msg)) def includeme(config): from pyramid.authentication import AuthTktAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy authn_policy = AuthTktAuthenticationPolicy('seekt1t', hashalg='sha512') authz_policy = ACLAuthorizationPolicy() config.set_authentication_policy(authn_policy) config.set_authorization_policy(authz_policy) config.add_view(test, name='test') config.add_view(x_view, name='x', permission='private') pyramid-1.6/pyramid/tests/pkgs/rendererscanapp/0000755000076500000240000000000012642137501022502 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/rendererscanapp/__init__.py0000644000076500000240000000025412520062551024611 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(name='one', renderer='json') def one(request): return {'name':'One!'} def includeme(config): config.scan() pyramid-1.6/pyramid/tests/pkgs/rendererscanapp/two/0000755000076500000240000000000012642137501023313 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/rendererscanapp/two/__init__.py0000644000076500000240000000020312520062551025414 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(name='two', renderer='json') def two(request): return {'nameagain':'Two!'} pyramid-1.6/pyramid/tests/pkgs/restbugapp/0000755000076500000240000000000012642137501021502 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/restbugapp/__init__.py0000644000076500000240000000121312234375161023613 0ustar michaelstaff00000000000000def includeme(config): config.add_route('gameactions_pet_get_pets', '/pet', request_method='GET') config.add_route('gameactions_pet_care_for_pet', '/pet', request_method='POST') config.add_view('.views.PetRESTView', route_name='gameactions_pet_get_pets', attr='GET', permission='view', renderer='json') config.add_view('.views.PetRESTView', route_name='gameactions_pet_care_for_pet', attr='POST', permission='view', renderer='json') pyramid-1.6/pyramid/tests/pkgs/restbugapp/views.py0000644000076500000240000000066112234375161023217 0ustar michaelstaff00000000000000from pyramid.response import Response class BaseRESTView(object): def __init__(self, context, request): self.context = context self.request = request class PetRESTView(BaseRESTView): """ REST Controller to control action of an avatar """ def __init__(self, context, request): super(PetRESTView, self).__init__(context, request) def GET(self): return Response('gotten') pyramid-1.6/pyramid/tests/pkgs/static_abspath/0000755000076500000240000000000012642137501022317 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/static_abspath/__init__.py0000644000076500000240000000030612234375161024432 0ustar michaelstaff00000000000000import os def includeme(config): here = here = os.path.dirname(__file__) fixtures = os.path.normpath(os.path.join(here, '..', '..', 'fixtures')) config.add_static_view('/', fixtures) pyramid-1.6/pyramid/tests/pkgs/static_assetspec/0000755000076500000240000000000012642137501022667 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/static_assetspec/__init__.py0000644000076500000240000000012212234375161024776 0ustar michaelstaff00000000000000def includeme(config): config.add_static_view('/', 'pyramid.tests:fixtures') pyramid-1.6/pyramid/tests/pkgs/static_routeprefix/0000755000076500000240000000000012642137501023251 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/static_routeprefix/__init__.py0000644000076500000240000000035712234375161025372 0ustar michaelstaff00000000000000def includeme(config): config.add_static_view('/static', 'pyramid.tests:fixtures') config.include(includeme2, route_prefix='/prefix') def includeme2(config): config.add_static_view('/static', 'pyramid.tests:fixtures/static') pyramid-1.6/pyramid/tests/pkgs/staticpermapp/0000755000076500000240000000000012642137501022202 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/staticpermapp/__init__.py0000644000076500000240000000175712234375161024330 0ustar michaelstaff00000000000000class RootFactory(object): __acl__ = [('Allow', 'fred', 'view')] def __init__(self, request): pass class LocalRootFactory(object): __acl__ = [('Allow', 'bob', 'view')] def __init__(self, request): pass def includeme(config): from pyramid.authentication import RemoteUserAuthenticationPolicy from pyramid.authorization import ACLAuthorizationPolicy authn_policy = RemoteUserAuthenticationPolicy() authz_policy = ACLAuthorizationPolicy() config._set_authentication_policy(authn_policy) config._set_authorization_policy(authz_policy) config.add_static_view('allowed', 'pyramid.tests:fixtures/static/') config.add_static_view('protected', 'pyramid.tests:fixtures/static/', permission='view') config.add_static_view('factory_protected', 'pyramid.tests:fixtures/static/', permission='view', factory=LocalRootFactory) pyramid-1.6/pyramid/tests/pkgs/subrequestapp/0000755000076500000240000000000012642137501022231 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/subrequestapp/__init__.py0000644000076500000240000000305212517346416024352 0ustar michaelstaff00000000000000from pyramid.config import Configurator from pyramid.request import Request def view_one(request): subreq = Request.blank('/view_two') response = request.invoke_subrequest(subreq, use_tweens=False) return response def view_two(request): return 'This came from view_two' def view_three(request): subreq = Request.blank('/view_four') try: return request.invoke_subrequest(subreq, use_tweens=True) except: # pragma: no cover request.response.body = b'Value error raised' return request.response def view_four(request): raise ValueError('foo') def view_five(request): subreq = Request.blank('/view_four') try: return request.invoke_subrequest(subreq, use_tweens=False) except ValueError: request.response.body = b'Value error raised' return request.response def excview(request): request.response.status_int = 500 request.response.body = b'Bad stuff happened' return request.response def main(): config = Configurator() config.add_route('one', '/view_one') config.add_route('two', '/view_two') config.add_route('three', '/view_three') config.add_route('four', '/view_four') config.add_route('five', '/view_five') config.add_view(excview, context=Exception) config.add_view(view_one, route_name='one') config.add_view(view_two, route_name='two', renderer='string') config.add_view(view_three, route_name='three') config.add_view(view_four, route_name='four') config.add_view(view_five, route_name='five') return config pyramid-1.6/pyramid/tests/pkgs/viewdecoratorapp/0000755000076500000240000000000012642137501022704 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/viewdecoratorapp/__init__.py0000644000076500000240000000012312234375161025014 0ustar michaelstaff00000000000000def includeme(config): config.scan('pyramid.tests.pkgs.viewdecoratorapp') pyramid-1.6/pyramid/tests/pkgs/viewdecoratorapp/views/0000755000076500000240000000000012642137501024041 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/viewdecoratorapp/views/__init__.py0000644000076500000240000000001212234375161026146 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/pkgs/viewdecoratorapp/views/views.py0000644000076500000240000000036412520062551025550 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(renderer='json', name='first') def first(request): return {'result':'OK1'} @view_config( renderer='json', name='second') def second(request): return {'result':'OK2'} pyramid-1.6/pyramid/tests/pkgs/wsgiapp2app/0000755000076500000240000000000012642137501021563 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/pkgs/wsgiapp2app/__init__.py0000644000076500000240000000074112234375161023701 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.wsgi import wsgiapp2 @view_config(name='hello', renderer='string') @wsgiapp2 def hello(environ, start_response): assert environ['PATH_INFO'] == '/' assert environ['SCRIPT_NAME'] == '/hello' response_headers = [('Content-Type', 'text/plain')] start_response('200 OK', response_headers) return [b'Hello!'] def main(): from pyramid.config import Configurator c = Configurator() c.scan() return c pyramid-1.6/pyramid/tests/test_asset.py0000644000076500000240000000613312234375161021120 0ustar michaelstaff00000000000000import unittest import os here = os.path.abspath(os.path.dirname(__file__)) class Test_resolve_asset_spec(unittest.TestCase): def _callFUT(self, spec, package_name='__main__'): from pyramid.resource import resolve_asset_spec return resolve_asset_spec(spec, package_name) def test_abspath(self): package_name, filename = self._callFUT(here, 'apackage') self.assertEqual(filename, here) self.assertEqual(package_name, None) def test_rel_spec(self): pkg = 'pyramid.tests' path = 'test_asset.py' package_name, filename = self._callFUT(path, pkg) self.assertEqual(package_name, 'pyramid.tests') self.assertEqual(filename, 'test_asset.py') def test_abs_spec(self): pkg = 'pyramid.tests' path = 'pyramid.nottests:test_asset.py' package_name, filename = self._callFUT(path, pkg) self.assertEqual(package_name, 'pyramid.nottests') self.assertEqual(filename, 'test_asset.py') def test_package_name_is_None(self): pkg = None path = 'test_asset.py' package_name, filename = self._callFUT(path, pkg) self.assertEqual(package_name, None) self.assertEqual(filename, 'test_asset.py') def test_package_name_is_package_object(self): import pyramid.tests pkg = pyramid.tests path = 'test_asset.py' package_name, filename = self._callFUT(path, pkg) self.assertEqual(package_name, 'pyramid.tests') self.assertEqual(filename, 'test_asset.py') class Test_abspath_from_asset_spec(unittest.TestCase): def _callFUT(self, spec, pname='__main__'): from pyramid.resource import abspath_from_asset_spec return abspath_from_asset_spec(spec, pname) def test_pname_is_None_before_resolve_asset_spec(self): result = self._callFUT('abc', None) self.assertEqual(result, 'abc') def test_pname_is_None_after_resolve_asset_spec(self): result = self._callFUT('/abc', '__main__') self.assertEqual(result, '/abc') def test_pkgrelative(self): result = self._callFUT('abc', 'pyramid.tests') self.assertEqual(result, os.path.join(here, 'abc')) class Test_asset_spec_from_abspath(unittest.TestCase): def _callFUT(self, abspath, package): from pyramid.asset import asset_spec_from_abspath return asset_spec_from_abspath(abspath, package) def test_package_name_is_main(self): pkg = DummyPackage('__main__') result = self._callFUT('abspath', pkg) self.assertEqual(result, 'abspath') def test_abspath_startswith_package_path(self): abspath = os.path.join(here, 'fixtureapp') pkg = DummyPackage('pyramid.tests') pkg.__file__ = 'file' result = self._callFUT(abspath, pkg) self.assertEqual(result, 'pyramid:fixtureapp') def test_abspath_doesnt_startwith_package_path(self): pkg = DummyPackage('pyramid.tests') result = self._callFUT(here, pkg) self.assertEqual(result, here) class DummyPackage: def __init__(self, name): self.__name__ = name pyramid-1.6/pyramid/tests/test_authentication.py0000644000076500000240000020054712642137120023017 0ustar michaelstaff00000000000000import unittest import warnings from pyramid import testing from pyramid.compat import ( text_, bytes_, ) class TestCallbackAuthenticationPolicyDebugging(unittest.TestCase): def setUp(self): from pyramid.interfaces import IDebugLogger self.config = testing.setUp() self.config.registry.registerUtility(self, IDebugLogger) self.messages = [] def tearDown(self): del self.config def debug(self, msg): self.messages.append(msg) def _makeOne(self, userid=None, callback=None): from pyramid.authentication import CallbackAuthenticationPolicy class MyAuthenticationPolicy(CallbackAuthenticationPolicy): def unauthenticated_userid(self, request): return userid policy = MyAuthenticationPolicy() policy.debug = True policy.callback = callback return policy def test_authenticated_userid_no_unauthenticated_userid(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], 'pyramid.tests.test_authentication.MyAuthenticationPolicy.' 'authenticated_userid: call to unauthenticated_userid returned ' 'None; returning None') def test_authenticated_userid_no_callback(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='fred') self.assertEqual(policy.authenticated_userid(request), 'fred') self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "authenticated_userid: there was no groupfinder callback; " "returning 'fred'") def test_authenticated_userid_with_callback_fail(self): request = DummyRequest(registry=self.config.registry) def callback(userid, request): return None policy = self._makeOne(userid='fred', callback=callback) self.assertEqual(policy.authenticated_userid(request), None) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], 'pyramid.tests.test_authentication.MyAuthenticationPolicy.' 'authenticated_userid: groupfinder callback returned None; ' 'returning None') def test_authenticated_userid_with_callback_success(self): request = DummyRequest(registry=self.config.registry) def callback(userid, request): return [] policy = self._makeOne(userid='fred', callback=callback) self.assertEqual(policy.authenticated_userid(request), 'fred') self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "authenticated_userid: groupfinder callback returned []; " "returning 'fred'") def test_authenticated_userid_fails_cleaning_as_Authenticated(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='system.Authenticated') self.assertEqual(policy.authenticated_userid(request), None) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "authenticated_userid: use of userid 'system.Authenticated' is " "disallowed by any built-in Pyramid security policy, returning " "None") def test_authenticated_userid_fails_cleaning_as_Everyone(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='system.Everyone') self.assertEqual(policy.authenticated_userid(request), None) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "authenticated_userid: use of userid 'system.Everyone' is " "disallowed by any built-in Pyramid security policy, returning " "None") def test_effective_principals_no_unauthenticated_userid(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), ['system.Everyone']) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: unauthenticated_userid returned None; " "returning ['system.Everyone']") def test_effective_principals_no_callback(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='fred') self.assertEqual( policy.effective_principals(request), ['system.Everyone', 'system.Authenticated', 'fred']) self.assertEqual(len(self.messages), 2) self.assertEqual( self.messages[0], 'pyramid.tests.test_authentication.MyAuthenticationPolicy.' 'effective_principals: groupfinder callback is None, so groups ' 'is []') self.assertEqual( self.messages[1], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: returning effective principals: " "['system.Everyone', 'system.Authenticated', 'fred']") def test_effective_principals_with_callback_fail(self): request = DummyRequest(registry=self.config.registry) def callback(userid, request): return None policy = self._makeOne(userid='fred', callback=callback) self.assertEqual( policy.effective_principals(request), ['system.Everyone']) self.assertEqual(len(self.messages), 2) self.assertEqual( self.messages[0], 'pyramid.tests.test_authentication.MyAuthenticationPolicy.' 'effective_principals: groupfinder callback returned None as ' 'groups') self.assertEqual( self.messages[1], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: returning effective principals: " "['system.Everyone']") def test_effective_principals_with_callback_success(self): request = DummyRequest(registry=self.config.registry) def callback(userid, request): return [] policy = self._makeOne(userid='fred', callback=callback) self.assertEqual( policy.effective_principals(request), ['system.Everyone', 'system.Authenticated', 'fred']) self.assertEqual(len(self.messages), 2) self.assertEqual( self.messages[0], 'pyramid.tests.test_authentication.MyAuthenticationPolicy.' 'effective_principals: groupfinder callback returned [] as groups') self.assertEqual( self.messages[1], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: returning effective principals: " "['system.Everyone', 'system.Authenticated', 'fred']") def test_effective_principals_with_unclean_principal_Authenticated(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='system.Authenticated') self.assertEqual( policy.effective_principals(request), ['system.Everyone']) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: unauthenticated_userid returned disallowed " "'system.Authenticated'; returning ['system.Everyone'] as if it " "was None") def test_effective_principals_with_unclean_principal_Everyone(self): request = DummyRequest(registry=self.config.registry) policy = self._makeOne(userid='system.Everyone') self.assertEqual( policy.effective_principals(request), ['system.Everyone']) self.assertEqual(len(self.messages), 1) self.assertEqual( self.messages[0], "pyramid.tests.test_authentication.MyAuthenticationPolicy." "effective_principals: unauthenticated_userid returned disallowed " "'system.Everyone'; returning ['system.Everyone'] as if it " "was None") class TestRepozeWho1AuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import RepozeWho1AuthenticationPolicy return RepozeWho1AuthenticationPolicy def _makeOne(self, identifier_name='auth_tkt', callback=None): return self._getTargetClass()(identifier_name, callback) def test_class_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthenticationPolicy verifyClass(IAuthenticationPolicy, self._getTargetClass()) def test_instance_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne()) def test_unauthenticated_userid_returns_None(self): request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), 'fred') def test_authenticated_userid_None(self): request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), 'fred') def test_authenticated_userid_repoze_who_userid_is_None(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':None}}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid_with_callback_returns_None(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) def callback(identity, request): return None policy = self._makeOne(callback=callback) self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid_with_callback_returns_something(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) def callback(identity, request): return ['agroup'] policy = self._makeOne(callback=callback) self.assertEqual(policy.authenticated_userid(request), 'fred') def test_authenticated_userid_unclean_principal_Authenticated(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'system.Authenticated'}} ) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid_unclean_principal_Everyone(self): request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'system.Everyone'}} ) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_effective_principals_None(self): from pyramid.security import Everyone request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_userid_only(self): from pyramid.security import Everyone from pyramid.security import Authenticated request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred'}}) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone, Authenticated, 'fred']) def test_effective_principals_userid_and_groups(self): from pyramid.security import Everyone from pyramid.security import Authenticated request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred', 'groups':['quux', 'biz']}}) def callback(identity, request): return identity['groups'] policy = self._makeOne(callback=callback) self.assertEqual(policy.effective_principals(request), [Everyone, Authenticated, 'fred', 'quux', 'biz']) def test_effective_principals_userid_callback_returns_None(self): from pyramid.security import Everyone request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'fred', 'groups':['quux', 'biz']}}) def callback(identity, request): return None policy = self._makeOne(callback=callback) self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_repoze_who_userid_is_None(self): from pyramid.security import Everyone request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':None}} ) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_repoze_who_userid_is_unclean_Everyone(self): from pyramid.security import Everyone request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'system.Everyone'}} ) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_repoze_who_userid_is_unclean_Authenticated( self): from pyramid.security import Everyone request = DummyRequest( {'repoze.who.identity':{'repoze.who.userid':'system.Authenticated'}} ) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_remember_no_plugins(self): request = DummyRequest({}) policy = self._makeOne() result = policy.remember(request, 'fred') self.assertEqual(result, []) def test_remember(self): authtkt = DummyWhoPlugin() request = DummyRequest( {'repoze.who.plugins':{'auth_tkt':authtkt}}) policy = self._makeOne() result = policy.remember(request, 'fred') self.assertEqual(result[0], request.environ) self.assertEqual(result[1], {'repoze.who.userid':'fred'}) def test_remember_kwargs(self): authtkt = DummyWhoPlugin() request = DummyRequest( {'repoze.who.plugins':{'auth_tkt':authtkt}}) policy = self._makeOne() result = policy.remember(request, 'fred', max_age=23) self.assertEqual(result[1], {'repoze.who.userid':'fred', 'max_age': 23}) def test_forget_no_plugins(self): request = DummyRequest({}) policy = self._makeOne() result = policy.forget(request) self.assertEqual(result, []) def test_forget(self): authtkt = DummyWhoPlugin() request = DummyRequest( {'repoze.who.plugins':{'auth_tkt':authtkt}, 'repoze.who.identity':{'repoze.who.userid':'fred'}, }) policy = self._makeOne() result = policy.forget(request) self.assertEqual(result[0], request.environ) self.assertEqual(result[1], request.environ['repoze.who.identity']) class TestRemoteUserAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import RemoteUserAuthenticationPolicy return RemoteUserAuthenticationPolicy def _makeOne(self, environ_key='REMOTE_USER', callback=None): return self._getTargetClass()(environ_key, callback) def test_class_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthenticationPolicy verifyClass(IAuthenticationPolicy, self._getTargetClass()) def test_instance_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne()) def test_unauthenticated_userid_returns_None(self): request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid(self): request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), 'fred') def test_authenticated_userid_None(self): request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid(self): request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), 'fred') def test_effective_principals_None(self): from pyramid.security import Everyone request = DummyRequest({}) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals(self): from pyramid.security import Everyone from pyramid.security import Authenticated request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone, Authenticated, 'fred']) def test_remember(self): request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne() result = policy.remember(request, 'fred') self.assertEqual(result, []) def test_forget(self): request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne() result = policy.forget(request) self.assertEqual(result, []) class TestAuthTktAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import AuthTktAuthenticationPolicy return AuthTktAuthenticationPolicy def _makeOne(self, callback, cookieidentity, **kw): inst = self._getTargetClass()('secret', callback, **kw) inst.cookie = DummyCookieHelper(cookieidentity) return inst def setUp(self): self.warnings = warnings.catch_warnings() self.warnings.__enter__() warnings.simplefilter('ignore', DeprecationWarning) def tearDown(self): self.warnings.__exit__(None, None, None) def test_allargs(self): # pass all known args inst = self._getTargetClass()( 'secret', callback=None, cookie_name=None, secure=False, include_ip=False, timeout=None, reissue_time=None, hashalg='sha512', ) self.assertEqual(inst.callback, None) def test_hashalg_override(self): # important to ensure hashalg is passed to cookie helper inst = self._getTargetClass()('secret', hashalg='sha512') self.assertEqual(inst.cookie.hashalg, 'sha512') def test_unauthenticated_userid_returns_None(self): request = DummyRequest({}) policy = self._makeOne(None, None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid(self): request = DummyRequest({'REMOTE_USER':'fred'}) policy = self._makeOne(None, {'userid':'fred'}) self.assertEqual(policy.unauthenticated_userid(request), 'fred') def test_authenticated_userid_no_cookie_identity(self): request = DummyRequest({}) policy = self._makeOne(None, None) self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid_callback_returns_None(self): request = DummyRequest({}) def callback(userid, request): return None policy = self._makeOne(callback, {'userid':'fred'}) self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid(self): request = DummyRequest({}) def callback(userid, request): return True policy = self._makeOne(callback, {'userid':'fred'}) self.assertEqual(policy.authenticated_userid(request), 'fred') def test_effective_principals_no_cookie_identity(self): from pyramid.security import Everyone request = DummyRequest({}) policy = self._makeOne(None, None) self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_callback_returns_None(self): from pyramid.security import Everyone request = DummyRequest({}) def callback(userid, request): return None policy = self._makeOne(callback, {'userid':'fred'}) self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals(self): from pyramid.security import Everyone from pyramid.security import Authenticated request = DummyRequest({}) def callback(userid, request): return ['group.foo'] policy = self._makeOne(callback, {'userid':'fred'}) self.assertEqual(policy.effective_principals(request), [Everyone, Authenticated, 'fred', 'group.foo']) def test_remember(self): request = DummyRequest({}) policy = self._makeOne(None, None) result = policy.remember(request, 'fred') self.assertEqual(result, []) def test_remember_with_extra_kargs(self): request = DummyRequest({}) policy = self._makeOne(None, None) result = policy.remember(request, 'fred', a=1, b=2) self.assertEqual(policy.cookie.kw, {'a':1, 'b':2}) self.assertEqual(result, []) def test_forget(self): request = DummyRequest({}) policy = self._makeOne(None, None) result = policy.forget(request) self.assertEqual(result, []) def test_class_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthenticationPolicy verifyClass(IAuthenticationPolicy, self._getTargetClass()) def test_instance_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne(None, None)) class TestAuthTktCookieHelper(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import AuthTktCookieHelper return AuthTktCookieHelper def _makeOne(self, *arg, **kw): helper = self._getTargetClass()(*arg, **kw) # laziness after moving auth_tkt classes and funcs into # authentication module auth_tkt = DummyAuthTktModule() helper.auth_tkt = auth_tkt helper.AuthTicket = auth_tkt.AuthTicket helper.parse_ticket = auth_tkt.parse_ticket helper.BadTicket = auth_tkt.BadTicket return helper def _makeRequest(self, cookie=None, ipv6=False): environ = {'wsgi.version': (1,0)} if ipv6 is False: environ['REMOTE_ADDR'] = '1.1.1.1' else: environ['REMOTE_ADDR'] = '::1' environ['SERVER_NAME'] = 'localhost' return DummyRequest(environ, cookie=cookie) def _cookieValue(self, cookie): items = cookie.value.split('/') D = {} for item in items: k, v = item.split('=', 1) D[k] = v return D def _parseHeaders(self, headers): return [ self._parseHeader(header) for header in headers ] def _parseHeader(self, header): cookie = self._parseCookie(header[1]) return cookie def _parseCookie(self, cookie): from pyramid.compat import SimpleCookie cookies = SimpleCookie() cookies.load(cookie) return cookies.get('auth_tkt') def test_init_cookie_str_reissue_invalid(self): self.assertRaises(ValueError, self._makeOne, 'secret', reissue_time='invalid value') def test_init_cookie_str_timeout_invalid(self): self.assertRaises(ValueError, self._makeOne, 'secret', timeout='invalid value') def test_init_cookie_str_max_age_invalid(self): self.assertRaises(ValueError, self._makeOne, 'secret', max_age='invalid value') def test_identify_nocookie(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.identify(request) self.assertEqual(result, None) def test_identify_cookie_value_is_None(self): helper = self._makeOne('secret') request = self._makeRequest(None) result = helper.identify(request) self.assertEqual(result, None) def test_identify_good_cookie_include_ip(self): helper = self._makeOne('secret', include_ip=True) request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], 'userid') self.assertEqual(result['userdata'], '') self.assertEqual(result['timestamp'], 0) self.assertEqual(helper.auth_tkt.value, 'ticket') self.assertEqual(helper.auth_tkt.remote_addr, '1.1.1.1') self.assertEqual(helper.auth_tkt.secret, 'secret') environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_include_ipv6(self): helper = self._makeOne('secret', include_ip=True) request = self._makeRequest('ticket', ipv6=True) result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], 'userid') self.assertEqual(result['userdata'], '') self.assertEqual(result['timestamp'], 0) self.assertEqual(helper.auth_tkt.value, 'ticket') self.assertEqual(helper.auth_tkt.remote_addr, '::1') self.assertEqual(helper.auth_tkt.secret, 'secret') environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_dont_include_ip(self): helper = self._makeOne('secret', include_ip=False) request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], 'userid') self.assertEqual(result['userdata'], '') self.assertEqual(result['timestamp'], 0) self.assertEqual(helper.auth_tkt.value, 'ticket') self.assertEqual(helper.auth_tkt.remote_addr, '0.0.0.0') self.assertEqual(helper.auth_tkt.secret, 'secret') environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_int_useridtype(self): helper = self._makeOne('secret', include_ip=False) helper.auth_tkt.userid = '1' helper.auth_tkt.user_data = 'userid_type:int' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], 1) self.assertEqual(result['userdata'], 'userid_type:int') self.assertEqual(result['timestamp'], 0) environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'userid_type:int') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_nonuseridtype_user_data(self): helper = self._makeOne('secret', include_ip=False) helper.auth_tkt.userid = '1' helper.auth_tkt.user_data = 'bogus:int' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], '1') self.assertEqual(result['userdata'], 'bogus:int') self.assertEqual(result['timestamp'], 0) environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'bogus:int') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_unknown_useridtype(self): helper = self._makeOne('secret', include_ip=False) helper.auth_tkt.userid = 'abc' helper.auth_tkt.user_data = 'userid_type:unknown' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], 'abc') self.assertEqual(result['userdata'], 'userid_type:unknown') self.assertEqual(result['timestamp'], 0) environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'userid_type:unknown') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_b64str_useridtype(self): from base64 import b64encode helper = self._makeOne('secret', include_ip=False) helper.auth_tkt.userid = b64encode(b'encoded').strip() helper.auth_tkt.user_data = 'userid_type:b64str' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], b'encoded') self.assertEqual(result['userdata'], 'userid_type:b64str') self.assertEqual(result['timestamp'], 0) environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'userid_type:b64str') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_good_cookie_b64unicode_useridtype(self): from base64 import b64encode helper = self._makeOne('secret', include_ip=False) helper.auth_tkt.userid = b64encode(b'\xc3\xa9ncoded').strip() helper.auth_tkt.user_data = 'userid_type:b64unicode' request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(len(result), 4) self.assertEqual(result['tokens'], ()) self.assertEqual(result['userid'], text_(b'\xc3\xa9ncoded', 'utf-8')) self.assertEqual(result['userdata'], 'userid_type:b64unicode') self.assertEqual(result['timestamp'], 0) environ = request.environ self.assertEqual(environ['REMOTE_USER_TOKENS'], ()) self.assertEqual(environ['REMOTE_USER_DATA'],'userid_type:b64unicode') self.assertEqual(environ['AUTH_TYPE'],'cookie') def test_identify_bad_cookie(self): helper = self._makeOne('secret', include_ip=True) helper.auth_tkt.parse_raise = True request = self._makeRequest('ticket') result = helper.identify(request) self.assertEqual(result, None) def test_identify_cookie_timeout(self): helper = self._makeOne('secret', timeout=1) self.assertEqual(helper.timeout, 1) def test_identify_cookie_str_timeout(self): helper = self._makeOne('secret', timeout='1') self.assertEqual(helper.timeout, 1) def test_identify_cookie_timeout_aged(self): import time helper = self._makeOne('secret', timeout=10) now = time.time() helper.auth_tkt.timestamp = now - 1 helper.now = now + 10 helper.auth_tkt.tokens = (text_('a'), ) request = self._makeRequest('bogus') result = helper.identify(request) self.assertFalse(result) def test_identify_cookie_reissue(self): import time helper = self._makeOne('secret', timeout=10, reissue_time=0) now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 helper.auth_tkt.tokens = (text_('a'), ) request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](request, response) self.assertEqual(len(response.headerlist), 3) self.assertEqual(response.headerlist[0][0], 'Set-Cookie') def test_identify_cookie_str_reissue(self): import time helper = self._makeOne('secret', timeout=10, reissue_time='0') now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 helper.auth_tkt.tokens = (text_('a'), ) request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](request, response) self.assertEqual(len(response.headerlist), 3) self.assertEqual(response.headerlist[0][0], 'Set-Cookie') def test_identify_cookie_reissue_already_reissued_this_request(self): import time helper = self._makeOne('secret', timeout=10, reissue_time=0) now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 request = self._makeRequest('bogus') request._authtkt_reissued = True result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 0) def test_identify_cookie_reissue_notyet(self): import time helper = self._makeOne('secret', timeout=10, reissue_time=10) now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 0) def test_identify_cookie_reissue_revoked_by_forget(self): import time helper = self._makeOne('secret', timeout=10, reissue_time=0) now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) result = helper.forget(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](request, response) self.assertEqual(len(response.headerlist), 0) def test_identify_cookie_reissue_revoked_by_remember(self): import time helper = self._makeOne('secret', timeout=10, reissue_time=0) now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) result = helper.remember(request, 'bob') self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](request, response) self.assertEqual(len(response.headerlist), 0) def test_identify_cookie_reissue_with_tokens_default(self): # see https://github.com/Pylons/pyramid/issues#issue/108 import time helper = self._makeOne('secret', timeout=10, reissue_time=0) auth_tkt = DummyAuthTktModule(tokens=['']) helper.auth_tkt = auth_tkt helper.AuthTicket = auth_tkt.AuthTicket helper.parse_ticket = auth_tkt.parse_ticket helper.BadTicket = auth_tkt.BadTicket now = time.time() helper.auth_tkt.timestamp = now helper.now = now + 1 request = self._makeRequest('bogus') result = helper.identify(request) self.assertTrue(result) self.assertEqual(len(request.callbacks), 1) response = DummyResponse() request.callbacks[0](None, response) self.assertEqual(len(response.headerlist), 3) self.assertEqual(response.headerlist[0][0], 'Set-Cookie') self.assertTrue("/tokens=/" in response.headerlist[0][1]) def test_remember(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, 'userid') self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_include_ip(self): helper = self._makeOne('secret', include_ip=True) request = self._makeRequest() result = helper.remember(request, 'other') self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_path(self): helper = self._makeOne('secret', include_ip=True, path="/cgi-bin/app.cgi/") request = self._makeRequest() result = helper.remember(request, 'other') self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/cgi-bin/app.cgi/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith( '; Domain=localhost; Path=/cgi-bin/app.cgi/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue(result[2][1].endswith( '; Domain=.localhost; Path=/cgi-bin/app.cgi/')) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_http_only(self): helper = self._makeOne('secret', include_ip=True, http_only=True) request = self._makeRequest() result = helper.remember(request, 'other') self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; HttpOnly')) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue('; HttpOnly' in result[1][1]) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue('; HttpOnly' in result[2][1]) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_secure(self): helper = self._makeOne('secret', include_ip=True, secure=True) request = self._makeRequest() result = helper.remember(request, 'other') self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue('; secure' in result[0][1]) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue('; secure' in result[1][1]) self.assertTrue(result[1][1].startswith('auth_tkt=')) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue('; secure' in result[2][1]) self.assertTrue(result[2][1].startswith('auth_tkt=')) def test_remember_wild_domain_disabled(self): helper = self._makeOne('secret', wild_domain=False) request = self._makeRequest() result = helper.remember(request, 'other') self.assertEqual(len(result), 2) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/')) self.assertTrue(result[1][1].startswith('auth_tkt=')) def test_remember_parent_domain(self): helper = self._makeOne('secret', parent_domain=True) request = self._makeRequest() request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) def test_remember_parent_domain_supercedes_wild_domain(self): helper = self._makeOne('secret', parent_domain=True, wild_domain=True) request = self._makeRequest() request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/')) def test_remember_explicit_domain(self): helper = self._makeOne('secret', domain='pyramid.bazinga') request = self._makeRequest() request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue(result[0][1].endswith( '; Domain=pyramid.bazinga; Path=/')) self.assertTrue(result[0][1].startswith('auth_tkt=')) def test_remember_domain_supercedes_parent_and_wild_domain(self): helper = self._makeOne('secret', domain='pyramid.bazinga', parent_domain=True, wild_domain=True) request = self._makeRequest() request.domain = 'www.example.com' result = helper.remember(request, 'other') self.assertEqual(len(result), 1) self.assertTrue(result[0][1].endswith( '; Domain=pyramid.bazinga; Path=/')) def test_remember_binary_userid(self): import base64 helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, b'userid') values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], text_(base64.b64encode(b'userid').strip())) self.assertEqual(val['user_data'], 'userid_type:b64str') def test_remember_int_userid(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, 1) values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], '1') self.assertEqual(val['user_data'], 'userid_type:int') def test_remember_long_userid(self): from pyramid.compat import long helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, long(1)) values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], '1') self.assertEqual(val['user_data'], 'userid_type:int') def test_remember_unicode_userid(self): import base64 helper = self._makeOne('secret') request = self._makeRequest() userid = text_(b'\xc2\xa9', 'utf-8') result = helper.remember(request, userid) values = self._parseHeaders(result) self.assertEqual(len(result), 3) val = self._cookieValue(values[0]) self.assertEqual(val['userid'], text_(base64.b64encode(userid.encode('utf-8')))) self.assertEqual(val['user_data'], 'userid_type:b64unicode') def test_remember_insane_userid(self): helper = self._makeOne('secret') request = self._makeRequest() userid = object() result = helper.remember(request, userid) values = self._parseHeaders(result) self.assertEqual(len(result), 3) value = values[0] self.assertTrue('userid' in value.value) def test_remember_max_age(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, 'userid', max_age=500) values = self._parseHeaders(result) self.assertEqual(len(result), 3) self.assertEqual(values[0]['max-age'], '500') self.assertTrue(values[0]['expires']) def test_remember_str_max_age(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, 'userid', max_age='500') values = self._parseHeaders(result) self.assertEqual(len(result), 3) self.assertEqual(values[0]['max-age'], '500') self.assertTrue(values[0]['expires']) def test_remember_str_max_age_invalid(self): helper = self._makeOne('secret') request = self._makeRequest() self.assertRaises(ValueError, helper.remember, request, 'userid', max_age='invalid value') def test_remember_tokens(self): helper = self._makeOne('secret') request = self._makeRequest() result = helper.remember(request, 'other', tokens=('foo', 'bar')) self.assertEqual(len(result), 3) self.assertEqual(result[0][0], 'Set-Cookie') self.assertTrue("/tokens=foo|bar/" in result[0][1]) self.assertEqual(result[1][0], 'Set-Cookie') self.assertTrue("/tokens=foo|bar/" in result[1][1]) self.assertEqual(result[2][0], 'Set-Cookie') self.assertTrue("/tokens=foo|bar/" in result[2][1]) def test_remember_unicode_but_ascii_token(self): helper = self._makeOne('secret') request = self._makeRequest() la = text_(b'foo', 'utf-8') result = helper.remember(request, 'other', tokens=(la,)) # tokens must be str type on both Python 2 and 3 self.assertTrue("/tokens=foo/" in result[0][1]) def test_remember_nonascii_token(self): helper = self._makeOne('secret') request = self._makeRequest() la = text_(b'La Pe\xc3\xb1a', 'utf-8') self.assertRaises(ValueError, helper.remember, request, 'other', tokens=(la,)) def test_remember_invalid_token_format(self): helper = self._makeOne('secret') request = self._makeRequest() self.assertRaises(ValueError, helper.remember, request, 'other', tokens=('foo bar',)) self.assertRaises(ValueError, helper.remember, request, 'other', tokens=('1bar',)) def test_forget(self): helper = self._makeOne('secret') request = self._makeRequest() headers = helper.forget(request) self.assertEqual(len(headers), 3) name, value = headers[0] self.assertEqual(name, 'Set-Cookie') self.assertEqual( value, 'auth_tkt=; Max-Age=0; Path=/; ' 'expires=Wed, 31-Dec-97 23:59:59 GMT' ) name, value = headers[1] self.assertEqual(name, 'Set-Cookie') self.assertEqual( value, 'auth_tkt=; Domain=localhost; Max-Age=0; Path=/; ' 'expires=Wed, 31-Dec-97 23:59:59 GMT' ) name, value = headers[2] self.assertEqual(name, 'Set-Cookie') self.assertEqual( value, 'auth_tkt=; Domain=.localhost; Max-Age=0; Path=/; ' 'expires=Wed, 31-Dec-97 23:59:59 GMT' ) class TestAuthTicket(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.authentication import AuthTicket return AuthTicket(*arg, **kw) def test_ctor_with_tokens(self): ticket = self._makeOne('secret', 'userid', 'ip', tokens=('a', 'b')) self.assertEqual(ticket.tokens, 'a,b') def test_ctor_with_time(self): ticket = self._makeOne('secret', 'userid', 'ip', time='time') self.assertEqual(ticket.time, 'time') def test_digest(self): ticket = self._makeOne('secret', 'userid', '0.0.0.0', time=10) result = ticket.digest() self.assertEqual(result, '126fd6224912187ee9ffa80e0b81420c') def test_digest_sha512(self): ticket = self._makeOne('secret', 'userid', '0.0.0.0', time=10, hashalg='sha512') result = ticket.digest() self.assertEqual(result, '74770b2e0d5b1a54c2a466ec567a40f7d7823576aa49'\ '3c65fc3445e9b44097f4a80410319ef8cb256a2e60b9'\ 'c2002e48a9e33a3e8ee4379352c04ef96d2cb278') def test_cookie_value(self): ticket = self._makeOne('secret', 'userid', '0.0.0.0', time=10, tokens=('a', 'b')) result = ticket.cookie_value() self.assertEqual(result, '66f9cc3e423dc57c91df696cf3d1f0d80000000auserid!a,b!') def test_ipv4(self): ticket = self._makeOne('secret', 'userid', '198.51.100.1', time=10, hashalg='sha256') result = ticket.cookie_value() self.assertEqual(result, 'b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b'\ '798400ecdade8d76c530000000auserid!') def test_ipv6(self): ticket = self._makeOne('secret', 'userid', '2001:db8::1', time=10, hashalg='sha256') result = ticket.cookie_value() self.assertEqual(result, 'd025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c8'\ '5becf8760cd7a2fa4910000000auserid!') class TestBadTicket(unittest.TestCase): def _makeOne(self, msg, expected=None): from pyramid.authentication import BadTicket return BadTicket(msg, expected) def test_it(self): exc = self._makeOne('msg', expected=True) self.assertEqual(exc.expected, True) self.assertTrue(isinstance(exc, Exception)) class Test_parse_ticket(unittest.TestCase): def _callFUT(self, secret, ticket, ip, hashalg='md5'): from pyramid.authentication import parse_ticket return parse_ticket(secret, ticket, ip, hashalg) def _assertRaisesBadTicket(self, secret, ticket, ip, hashalg='md5'): from pyramid.authentication import BadTicket self.assertRaises(BadTicket,self._callFUT, secret, ticket, ip, hashalg) def test_bad_timestamp(self): ticket = 'x' * 64 self._assertRaisesBadTicket('secret', ticket, 'ip') def test_bad_userid_or_data(self): ticket = 'x' * 32 + '11111111' + 'x' * 10 self._assertRaisesBadTicket('secret', ticket, 'ip') def test_digest_sig_incorrect(self): ticket = 'x' * 32 + '11111111' + 'a!b!c' self._assertRaisesBadTicket('secret', ticket, '0.0.0.0') def test_correct_with_user_data(self): 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 = 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 = text_('b3e7156db4f8abde4439c4a6499a0668f9e7ffd7fa27b798400ecd' 'ade8d76c530000000auserid!') result = self._callFUT('secret', ticket, '198.51.100.1', 'sha256') self.assertEqual(result, (10, 'userid', [''], '')) def test_ipv6(self): ticket = text_('d025b601a0f12ca6d008aa35ff3a22b7d8f3d1c1456c85becf8760' 'cd7a2fa4910000000auserid!') result = self._callFUT('secret', ticket, '2001:db8::1', 'sha256') self.assertEqual(result, (10, 'userid', [''], '')) pass class TestSessionAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import SessionAuthenticationPolicy return SessionAuthenticationPolicy def _makeOne(self, callback=None, prefix=''): return self._getTargetClass()(prefix=prefix, callback=callback) def test_class_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthenticationPolicy verifyClass(IAuthenticationPolicy, self._getTargetClass()) def test_instance_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAuthenticationPolicy verifyObject(IAuthenticationPolicy, self._makeOne()) def test_unauthenticated_userid_returns_None(self): request = DummyRequest() policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid(self): request = DummyRequest(session={'userid':'fred'}) policy = self._makeOne() self.assertEqual(policy.unauthenticated_userid(request), 'fred') def test_authenticated_userid_no_cookie_identity(self): request = DummyRequest() policy = self._makeOne() self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid_callback_returns_None(self): request = DummyRequest(session={'userid':'fred'}) def callback(userid, request): return None policy = self._makeOne(callback) self.assertEqual(policy.authenticated_userid(request), None) def test_authenticated_userid(self): request = DummyRequest(session={'userid':'fred'}) def callback(userid, request): return True policy = self._makeOne(callback) self.assertEqual(policy.authenticated_userid(request), 'fred') def test_effective_principals_no_identity(self): from pyramid.security import Everyone request = DummyRequest() policy = self._makeOne() self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals_callback_returns_None(self): from pyramid.security import Everyone request = DummyRequest(session={'userid':'fred'}) def callback(userid, request): return None policy = self._makeOne(callback) self.assertEqual(policy.effective_principals(request), [Everyone]) def test_effective_principals(self): from pyramid.security import Everyone from pyramid.security import Authenticated request = DummyRequest(session={'userid':'fred'}) def callback(userid, request): return ['group.foo'] policy = self._makeOne(callback) self.assertEqual(policy.effective_principals(request), [Everyone, Authenticated, 'fred', 'group.foo']) def test_remember(self): request = DummyRequest() policy = self._makeOne() result = policy.remember(request, 'fred') self.assertEqual(request.session.get('userid'), 'fred') self.assertEqual(result, []) def test_forget(self): request = DummyRequest(session={'userid':'fred'}) policy = self._makeOne() result = policy.forget(request) self.assertEqual(request.session.get('userid'), None) self.assertEqual(result, []) def test_forget_no_identity(self): request = DummyRequest() policy = self._makeOne() result = policy.forget(request) self.assertEqual(request.session.get('userid'), None) self.assertEqual(result, []) class TestBasicAuthAuthenticationPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.authentication import BasicAuthAuthenticationPolicy as cls return cls def _makeOne(self, check): return self._getTargetClass()(check, realm='SomeRealm') def test_class_implements_IAuthenticationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthenticationPolicy verifyClass(IAuthenticationPolicy, self._getTargetClass()) def test_unauthenticated_userid(self): import base64 request = testing.DummyRequest() request.headers['Authorization'] = 'Basic %s' % base64.b64encode( bytes_('chrisr:password')).decode('ascii') policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), 'chrisr') def test_unauthenticated_userid_no_credentials(self): request = testing.DummyRequest() policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_bad_header(self): request = testing.DummyRequest() request.headers['Authorization'] = '...' policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid_not_basic(self): request = testing.DummyRequest() request.headers['Authorization'] = 'Complicated things' policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_unauthenticated_userid_corrupt_base64(self): request = testing.DummyRequest() request.headers['Authorization'] = 'Basic chrisr:password' policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_authenticated_userid(self): import base64 request = testing.DummyRequest() request.headers['Authorization'] = 'Basic %s' % base64.b64encode( bytes_('chrisr:password')).decode('ascii') def check(username, password, request): return [] policy = self._makeOne(check) self.assertEqual(policy.authenticated_userid(request), 'chrisr') def test_authenticated_userid_utf8(self): import base64 request = testing.DummyRequest() inputs = (b'm\xc3\xb6rk\xc3\xb6:' b'm\xc3\xb6rk\xc3\xb6password').decode('utf-8') request.headers['Authorization'] = 'Basic %s' % ( base64.b64encode(inputs.encode('utf-8')).decode('latin-1')) def check(username, password, request): return [] policy = self._makeOne(check) self.assertEqual(policy.authenticated_userid(request), b'm\xc3\xb6rk\xc3\xb6'.decode('utf-8')) def test_authenticated_userid_latin1(self): import base64 request = testing.DummyRequest() inputs = (b'm\xc3\xb6rk\xc3\xb6:' b'm\xc3\xb6rk\xc3\xb6password').decode('utf-8') request.headers['Authorization'] = 'Basic %s' % ( base64.b64encode(inputs.encode('latin-1')).decode('latin-1')) def check(username, password, request): return [] policy = self._makeOne(check) self.assertEqual(policy.authenticated_userid(request), b'm\xc3\xb6rk\xc3\xb6'.decode('utf-8')) def test_unauthenticated_userid_invalid_payload(self): import base64 request = testing.DummyRequest() request.headers['Authorization'] = 'Basic %s' % base64.b64encode( bytes_('chrisrpassword')).decode('ascii') policy = self._makeOne(None) self.assertEqual(policy.unauthenticated_userid(request), None) def test_remember(self): policy = self._makeOne(None) self.assertEqual(policy.remember(None, None), []) def test_forget(self): policy = self._makeOne(None) self.assertEqual(policy.forget(None), [ ('WWW-Authenticate', 'Basic realm="SomeRealm"')]) class TestSimpleSerializer(unittest.TestCase): def _makeOne(self): from pyramid.authentication import _SimpleSerializer return _SimpleSerializer() def test_loads(self): inst = self._makeOne() self.assertEqual(inst.loads(b'abc'), text_('abc')) def test_dumps(self): inst = self._makeOne() self.assertEqual(inst.dumps('abc'), bytes_('abc')) class DummyContext: pass class DummyCookies(object): def __init__(self, cookie): self.cookie = cookie def get(self, name): return self.cookie class DummyRequest: domain = 'localhost' def __init__(self, environ=None, session=None, registry=None, cookie=None): self.environ = environ or {} self.session = session or {} self.registry = registry self.callbacks = [] self.cookies = DummyCookies(cookie) def add_response_callback(self, callback): self.callbacks.append(callback) class DummyWhoPlugin: def remember(self, environ, identity): return environ, identity def forget(self, environ, identity): return environ, identity class DummyCookieHelper: def __init__(self, result): self.result = result def identify(self, *arg, **kw): return self.result def remember(self, *arg, **kw): self.kw = kw return [] def forget(self, *arg): return [] class DummyAuthTktModule(object): def __init__(self, timestamp=0, userid='userid', tokens=(), user_data='', parse_raise=False, hashalg="md5"): self.timestamp = timestamp self.userid = userid self.tokens = tokens self.user_data = user_data self.parse_raise = parse_raise self.hashalg = hashalg def parse_ticket(secret, value, remote_addr, hashalg): self.secret = secret self.value = value self.remote_addr = remote_addr if self.parse_raise: raise self.BadTicket() return self.timestamp, self.userid, self.tokens, self.user_data self.parse_ticket = parse_ticket class AuthTicket(object): def __init__(self, secret, userid, remote_addr, **kw): self.secret = secret self.userid = userid self.remote_addr = remote_addr self.kw = kw def cookie_value(self): result = { 'secret':self.secret, 'userid':self.userid, 'remote_addr':self.remote_addr } result.update(self.kw) tokens = result.pop('tokens', None) if tokens is not None: tokens = '|'.join(tokens) result['tokens'] = tokens items = sorted(result.items()) new_items = [] for k, v in items: if isinstance(v, bytes): v = text_(v) new_items.append((k,v)) result = '/'.join(['%s=%s' % (k, v) for k,v in new_items ]) return result self.AuthTicket = AuthTicket class BadTicket(Exception): pass class DummyResponse: def __init__(self): self.headerlist = [] pyramid-1.6/pyramid/tests/test_authorization.py0000644000076500000240000002415612520062551022700 0ustar michaelstaff00000000000000import unittest from pyramid.testing import cleanUp class TestACLAuthorizationPolicy(unittest.TestCase): def setUp(self): cleanUp() def tearDown(self): cleanUp() def _getTargetClass(self): from pyramid.authorization import ACLAuthorizationPolicy return ACLAuthorizationPolicy def _makeOne(self): return self._getTargetClass()() def test_class_implements_IAuthorizationPolicy(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAuthorizationPolicy verifyClass(IAuthorizationPolicy, self._getTargetClass()) def test_instance_implements_IAuthorizationPolicy(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAuthorizationPolicy verifyObject(IAuthorizationPolicy, self._makeOne()) def test_permits_no_acl(self): context = DummyContext() policy = self._makeOne() self.assertEqual(policy.permits(context, [], 'view'), False) def test_permits(self): from pyramid.security import Deny from pyramid.security import Allow from pyramid.security import Everyone from pyramid.security import Authenticated from pyramid.security import ALL_PERMISSIONS from pyramid.security import DENY_ALL root = DummyContext() community = DummyContext(__name__='community', __parent__=root) blog = DummyContext(__name__='blog', __parent__=community) root.__acl__ = [ (Allow, Authenticated, VIEW), ] community.__acl__ = [ (Allow, 'fred', ALL_PERMISSIONS), (Allow, 'wilma', VIEW), DENY_ALL, ] blog.__acl__ = [ (Allow, 'barney', MEMBER_PERMS), (Allow, 'wilma', VIEW), ] policy = self._makeOne() result = policy.permits(blog, [Everyone, Authenticated, 'wilma'], 'view') self.assertEqual(result, True) self.assertEqual(result.context, blog) self.assertEqual(result.ace, (Allow, 'wilma', VIEW)) self.assertEqual(result.acl, blog.__acl__) result = policy.permits(blog, [Everyone, Authenticated, 'wilma'], 'delete') self.assertEqual(result, False) self.assertEqual(result.context, community) self.assertEqual(result.ace, (Deny, Everyone, ALL_PERMISSIONS)) self.assertEqual(result.acl, community.__acl__) result = policy.permits(blog, [Everyone, Authenticated, 'fred'], 'view') self.assertEqual(result, True) self.assertEqual(result.context, community) self.assertEqual(result.ace, (Allow, 'fred', ALL_PERMISSIONS)) result = policy.permits(blog, [Everyone, Authenticated, 'fred'], 'doesntevenexistyet') self.assertEqual(result, True) self.assertEqual(result.context, community) self.assertEqual(result.ace, (Allow, 'fred', ALL_PERMISSIONS)) self.assertEqual(result.acl, community.__acl__) result = policy.permits(blog, [Everyone, Authenticated, 'barney'], 'view') self.assertEqual(result, True) self.assertEqual(result.context, blog) self.assertEqual(result.ace, (Allow, 'barney', MEMBER_PERMS)) result = policy.permits(blog, [Everyone, Authenticated, 'barney'], 'administer') self.assertEqual(result, False) self.assertEqual(result.context, community) self.assertEqual(result.ace, (Deny, Everyone, ALL_PERMISSIONS)) self.assertEqual(result.acl, community.__acl__) result = policy.permits(root, [Everyone, Authenticated, 'someguy'], 'view') self.assertEqual(result, True) self.assertEqual(result.context, root) self.assertEqual(result.ace, (Allow, Authenticated, VIEW)) result = policy.permits(blog, [Everyone, Authenticated, 'someguy'], 'view') self.assertEqual(result, False) self.assertEqual(result.context, community) self.assertEqual(result.ace, (Deny, Everyone, ALL_PERMISSIONS)) self.assertEqual(result.acl, community.__acl__) result = policy.permits(root, [Everyone], 'view') self.assertEqual(result, False) self.assertEqual(result.context, root) self.assertEqual(result.ace, '') self.assertEqual(result.acl, root.__acl__) context = DummyContext() result = policy.permits(context, [Everyone], 'view') self.assertEqual(result, False) self.assertEqual(result.ace, '') self.assertEqual( result.acl, '') def test_permits_string_permissions_in_acl(self): from pyramid.security import Allow root = DummyContext() root.__acl__ = [ (Allow, 'wilma', 'view_stuff'), ] policy = self._makeOne() result = policy.permits(root, ['wilma'], 'view') # would be True if matching against 'view_stuff' instead of against # ['view_stuff'] self.assertEqual(result, False) def test_principals_allowed_by_permission_direct(self): from pyramid.security import Allow from pyramid.security import DENY_ALL context = DummyContext() acl = [ (Allow, 'chrism', ('read', 'write')), DENY_ALL, (Allow, 'other', 'read') ] context.__acl__ = acl policy = self._makeOne() result = sorted( policy.principals_allowed_by_permission(context, 'read')) self.assertEqual(result, ['chrism']) def test_principals_allowed_by_permission_callable_acl(self): from pyramid.security import Allow from pyramid.security import DENY_ALL context = DummyContext() acl = lambda: [ (Allow, 'chrism', ('read', 'write')), DENY_ALL, (Allow, 'other', 'read') ] context.__acl__ = acl policy = self._makeOne() result = sorted( policy.principals_allowed_by_permission(context, 'read')) self.assertEqual(result, ['chrism']) def test_principals_allowed_by_permission_string_permission(self): from pyramid.security import Allow context = DummyContext() acl = [ (Allow, 'chrism', 'read_it')] context.__acl__ = acl policy = self._makeOne() result = policy.principals_allowed_by_permission(context, 'read') # would be ['chrism'] if 'read' were compared against 'read_it' instead # of against ['read_it'] self.assertEqual(list(result), []) def test_principals_allowed_by_permission(self): from pyramid.security import Allow from pyramid.security import Deny from pyramid.security import DENY_ALL from pyramid.security import ALL_PERMISSIONS root = DummyContext(__name__='', __parent__=None) community = DummyContext(__name__='community', __parent__=root) blog = DummyContext(__name__='blog', __parent__=community) root.__acl__ = [ (Allow, 'chrism', ('read', 'write')), (Allow, 'other', ('read',)), (Allow, 'jim', ALL_PERMISSIONS)] community.__acl__ = [ (Deny, 'flooz', 'read'), (Allow, 'flooz', 'read'), (Allow, 'mork', 'read'), (Deny, 'jim', 'read'), (Allow, 'someguy', 'manage')] blog.__acl__ = [ (Allow, 'fred', 'read'), DENY_ALL] policy = self._makeOne() result = sorted(policy.principals_allowed_by_permission(blog, 'read')) self.assertEqual(result, ['fred']) result = sorted(policy.principals_allowed_by_permission(community, 'read')) self.assertEqual(result, ['chrism', 'mork', 'other']) result = sorted(policy.principals_allowed_by_permission(community, 'read')) result = sorted(policy.principals_allowed_by_permission(root, 'read')) self.assertEqual(result, ['chrism', 'jim', 'other']) def test_principals_allowed_by_permission_no_acls(self): context = DummyContext() policy = self._makeOne() result = sorted(policy.principals_allowed_by_permission(context,'read')) self.assertEqual(result, []) def test_principals_allowed_by_permission_deny_not_permission_in_acl(self): from pyramid.security import Deny from pyramid.security import Everyone context = DummyContext() acl = [ (Deny, Everyone, 'write') ] context.__acl__ = acl policy = self._makeOne() result = sorted( policy.principals_allowed_by_permission(context, 'read')) self.assertEqual(result, []) def test_principals_allowed_by_permission_deny_permission_in_acl(self): from pyramid.security import Deny from pyramid.security import Everyone context = DummyContext() acl = [ (Deny, Everyone, 'read') ] context.__acl__ = acl policy = self._makeOne() result = sorted( policy.principals_allowed_by_permission(context, 'read')) self.assertEqual(result, []) def test_callable_acl(self): from pyramid.security import Allow context = DummyContext() fn = lambda self: [(Allow, 'bob', 'read')] context.__acl__ = fn.__get__(context, context.__class__) policy = self._makeOne() result = policy.permits(context, ['bob'], 'read') self.assertTrue(result) class DummyContext: def __init__(self, *arg, **kw): self.__dict__.update(kw) VIEW = 'view' EDIT = 'edit' CREATE = 'create' DELETE = 'delete' MODERATE = 'moderate' ADMINISTER = 'administer' COMMENT = 'comment' GUEST_PERMS = (VIEW, COMMENT) MEMBER_PERMS = GUEST_PERMS + (EDIT, CREATE, DELETE) MODERATOR_PERMS = MEMBER_PERMS + (MODERATE,) ADMINISTRATOR_PERMS = MODERATOR_PERMS + (ADMINISTER,) pyramid-1.6/pyramid/tests/test_compat.py0000644000076500000240000000132012524266531021257 0ustar michaelstaff00000000000000import unittest from pyramid.compat import is_unbound_method class TestUnboundMethods(unittest.TestCase): def test_old_style_bound(self): self.assertFalse(is_unbound_method(OldStyle().run)) def test_new_style_bound(self): self.assertFalse(is_unbound_method(NewStyle().run)) def test_old_style_unbound(self): self.assertTrue(is_unbound_method(OldStyle.run)) def test_new_style_unbound(self): self.assertTrue(is_unbound_method(NewStyle.run)) def test_normal_func_unbound(self): def func(): return 'OK' self.assertFalse(is_unbound_method(func)) class OldStyle: def run(self): return 'OK' class NewStyle(object): def run(self): return 'OK' pyramid-1.6/pyramid/tests/test_config/0000755000076500000240000000000012642137501020666 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/__init__.py0000644000076500000240000000217112517346416023010 0ustar michaelstaff00000000000000# package from zope.interface import implementer from zope.interface import Interface class IFactory(Interface): pass def dummy_tween_factory(handler, registry): pass def dummy_tween_factory2(handler, registry): pass def dummy_include(config): config.registry.included = True config.action('discrim', None, config.package) def dummy_include2(config): config.registry.also_included = True config.action('discrim', None, config.package) includeme = dummy_include class DummyContext: pass @implementer(IFactory) class DummyFactory(object): def __call__(self): """ """ def dummyfactory(request): """ """ class IDummy(Interface): pass def dummy_view(request): return 'OK' def dummy_extend(config, discrim): config.action(discrim, None, config.package) def dummy_extend2(config, discrim): config.action(discrim, None, config.registry) from functools import partial dummy_partial = partial(dummy_extend, discrim='partial') class DummyCallable(object): def __call__(self, config, discrim): config.action(discrim, None, config.package) dummy_callable = DummyCallable() pyramid-1.6/pyramid/tests/test_config/files/0000755000076500000240000000000012642137501021770 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/files/assets/0000755000076500000240000000000012642137501023272 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/files/assets/dummy.txt0000644000076500000240000000000712234375161025166 0ustar michaelstaff00000000000000Hello. pyramid-1.6/pyramid/tests/test_config/files/minimal.txt0000644000076500000240000000003212520062551024147 0ustar michaelstaff00000000000000
pyramid-1.6/pyramid/tests/test_config/path/0000755000076500000240000000000012642137501021622 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/path/scanerror/0000755000076500000240000000000012642137501023620 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/path/scanerror/__init__.py0000644000076500000240000000002712234375161025733 0ustar michaelstaff00000000000000# scan error package pyramid-1.6/pyramid/tests/test_config/path/scanerror/will_raise_error.py0000644000076500000240000000002212234375161027532 0ustar michaelstaff00000000000000import wont.exist pyramid-1.6/pyramid/tests/test_config/pkgs/0000755000076500000240000000000012642137501021632 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/__init__.py0000644000076500000240000000001312234375161023740 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/test_config/pkgs/asset/0000755000076500000240000000000012642137501022751 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/asset/__init__.py0000644000076500000240000000002012234375161025055 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/test_config/pkgs/asset/subpackage/0000755000076500000240000000000012642137501025056 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/asset/subpackage/__init__.py0000644000076500000240000000001112234375161027162 0ustar michaelstaff00000000000000#package pyramid-1.6/pyramid/tests/test_config/pkgs/asset/subpackage/templates/0000755000076500000240000000000012642137501027054 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt0000644000076500000240000000000012524266531030160 0ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scanextrakw/0000755000076500000240000000000012642137501024164 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scanextrakw/__init__.py0000644000076500000240000000033312234375161026277 0ustar michaelstaff00000000000000import venusian def foo(wrapped): def bar(scanner, name, wrapped): scanner.config.a = scanner.a venusian.attach(wrapped, bar) return wrapped @foo def hello(): pass hello() # appease coverage pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/0000755000076500000240000000000012642137501023560 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/__init__.py0000644000076500000240000000503312234375161025675 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(renderer=null_renderer) def grokked(context, request): return 'grokked' @view_config(request_method='POST', renderer=null_renderer) def grokked_post(context, request): return 'grokked_post' @view_config(name='stacked2', renderer=null_renderer) @view_config(name='stacked1', renderer=null_renderer) def stacked(context, request): return 'stacked' class stacked_class(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'stacked_class' stacked_class = view_config(name='stacked_class1', renderer=null_renderer)(stacked_class) stacked_class = view_config(name='stacked_class2', renderer=null_renderer)(stacked_class) class oldstyle_grokked_class: def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'oldstyle_grokked_class' oldstyle_grokked_class = view_config(name='oldstyle_grokked_class', renderer=null_renderer)( oldstyle_grokked_class) class grokked_class(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'grokked_class' grokked_class = view_config(name='grokked_class', renderer=null_renderer)(grokked_class) class Foo(object): def __call__(self, context, request): return 'grokked_instance' grokked_instance = Foo() grokked_instance = view_config(name='grokked_instance', renderer=null_renderer)(grokked_instance) class Base(object): @view_config(name='basemethod', renderer=null_renderer) def basemethod(self): """ """ class MethodViews(Base): def __init__(self, context, request): self.context = context self.request = request @view_config(name='method1', renderer=null_renderer) def method1(self): return 'method1' @view_config(name='method2', renderer=null_renderer) def method2(self): return 'method2' @view_config(name='stacked_method2', renderer=null_renderer) @view_config(name='stacked_method1', renderer=null_renderer) def stacked(self): return 'stacked_method' # ungrokkable A = 1 B = {} def stuff(): """ """ class Whatever(object): pass class Whatever2: pass pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/another.py0000644000076500000240000000374512234375161025606 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(name='another', renderer=null_renderer) def grokked(context, request): return 'another_grokked' @view_config(request_method='POST', name='another', renderer=null_renderer) def grokked_post(context, request): return 'another_grokked_post' @view_config(name='another_stacked2', renderer=null_renderer) @view_config(name='another_stacked1', renderer=null_renderer) def stacked(context, request): return 'another_stacked' class stacked_class(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'another_stacked_class' stacked_class = view_config(name='another_stacked_class1', renderer=null_renderer)(stacked_class) stacked_class = view_config(name='another_stacked_class2', renderer=null_renderer)(stacked_class) class oldstyle_grokked_class: def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'another_oldstyle_grokked_class' oldstyle_grokked_class = view_config(name='another_oldstyle_grokked_class', renderer=null_renderer)( oldstyle_grokked_class) class grokked_class(object): def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'another_grokked_class' grokked_class = view_config(name='another_grokked_class', renderer=null_renderer)(grokked_class) class Foo(object): def __call__(self, context, request): return 'another_grokked_instance' grokked_instance = Foo() grokked_instance = view_config(name='another_grokked_instance', renderer=null_renderer)( grokked_instance) # ungrokkable A = 1 B = {} def stuff(): """ """ pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/pod/0000755000076500000240000000000012642137501024342 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/pod/notinit.py0000644000076500000240000000031612234375161026403 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(name='pod_notinit', renderer=null_renderer) def subpackage_notinit(context, request): return 'pod_notinit' pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/subpackage/0000755000076500000240000000000012642137501025665 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/subpackage/__init__.py0000644000076500000240000000032312234375161027777 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(name='subpackage_init', renderer=null_renderer) def subpackage_init(context, request): return 'subpackage_init' pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/subpackage/notinit.py0000644000076500000240000000033412234375161027726 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(name='subpackage_notinit', renderer=null_renderer) def subpackage_notinit(context, request): return 'subpackage_notinit' pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/subpackage/subsubpackage/0000755000076500000240000000000012642137501030504 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/scannable/subpackage/subsubpackage/__init__.py0000644000076500000240000000033112234375161032615 0ustar michaelstaff00000000000000from pyramid.view import view_config from pyramid.renderers import null_renderer @view_config(name='subsubpackage_init', renderer=null_renderer) def subpackage_init(context, request): return 'subsubpackage_init' pyramid-1.6/pyramid/tests/test_config/pkgs/selfscan/0000755000076500000240000000000012642137501023430 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_config/pkgs/selfscan/__init__.py0000644000076500000240000000032412234375161025543 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(renderer='string') def abc(request): return 'root' def main(): from pyramid.config import Configurator c = Configurator() c.scan() return c pyramid-1.6/pyramid/tests/test_config/pkgs/selfscan/another.py0000644000076500000240000000016612234375161025450 0ustar michaelstaff00000000000000from pyramid.view import view_config @view_config(name='two', renderer='string') def two(request): return 'two' pyramid-1.6/pyramid/tests/test_config/test_adapters.py0000644000076500000240000003344612524266531024121 0ustar michaelstaff00000000000000import unittest from pyramid.compat import PY3 from pyramid.tests.test_config import IDummy class AdaptersConfiguratorMixinTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_add_subscriber_defaults(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) config.add_subscriber(subscriber) event = Event() config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 2) def test_add_subscriber_iface_specified(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) config.add_subscriber(subscriber, IEvent) event = Event() config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 1) def test_add_subscriber_dottednames(self): import pyramid.tests.test_config from pyramid.interfaces import INewRequest config = self._makeOne(autocommit=True) config.add_subscriber('pyramid.tests.test_config', 'pyramid.interfaces.INewRequest') handlers = list(config.registry.registeredHandlers()) self.assertEqual(len(handlers), 1) handler = handlers[0] self.assertEqual(handler.handler, pyramid.tests.test_config) self.assertEqual(handler.required, (INewRequest,)) def test_add_object_event_subscriber(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: object = 'foo' event = Event() L = [] def subscriber(object, event): L.append(event) config = self._makeOne(autocommit=True) config.add_subscriber(subscriber, (Interface, IEvent)) config.registry.subscribers((event.object, event), None) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.subscribers((event.object, IDummy), None) self.assertEqual(len(L), 1) def test_add_subscriber_with_specific_type_and_predicates_True(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) predlist = config.get_predlist('subscriber') jam_predicate = predicate_maker('jam') jim_predicate = predicate_maker('jim') predlist.add('jam', jam_predicate) predlist.add('jim', jim_predicate) config.add_subscriber(subscriber, IEvent, jam=True, jim=True) event = Event() event.jam = True event.jim = True config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 1) def test_add_subscriber_with_default_type_predicates_True(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) predlist = config.get_predlist('subscriber') jam_predicate = predicate_maker('jam') jim_predicate = predicate_maker('jim') predlist.add('jam', jam_predicate) predlist.add('jim', jim_predicate) config.add_subscriber(subscriber, jam=True, jim=True) event = Event() event.jam = True event.jim = True config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 1) def test_add_subscriber_with_specific_type_and_predicates_False(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) predlist = config.get_predlist('subscriber') jam_predicate = predicate_maker('jam') jim_predicate = predicate_maker('jim') predlist.add('jam', jam_predicate) predlist.add('jim', jim_predicate) config.add_subscriber(subscriber, IEvent, jam=True, jim=True) event = Event() event.jam = True event.jim = False config.registry.notify(event) self.assertEqual(len(L), 0) def test_add_subscriber_with_default_type_predicates_False(self): from zope.interface import implementer from zope.interface import Interface class IEvent(Interface): pass @implementer(IEvent) class Event: pass L = [] def subscriber(event): L.append(event) config = self._makeOne(autocommit=True) predlist = config.get_predlist('subscriber') jam_predicate = predicate_maker('jam') jim_predicate = predicate_maker('jim') predlist.add('jam', jam_predicate) predlist.add('jim', jim_predicate) config.add_subscriber(subscriber, jam=True, jim=True) event = Event() event.jam = False event.jim = True config.registry.notify(event) self.assertEqual(len(L), 0) def test_add_subscriber_predicate(self): config = self._makeOne() L = [] def add_predicate(type, name, factory, weighs_less_than=None, weighs_more_than=None): self.assertEqual(type, 'subscriber') self.assertEqual(name, 'name') self.assertEqual(factory, 'factory') self.assertEqual(weighs_more_than, 1) self.assertEqual(weighs_less_than, 2) L.append(1) config._add_predicate = add_predicate config.add_subscriber_predicate('name', 'factory', 1, 2) self.assertTrue(L) def test_add_response_adapter(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) class Adapter(object): def __init__(self, other): self.other = other config.add_response_adapter(Adapter, str) result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.other, 'foo') def test_add_response_adapter_self(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) class Adapter(object): pass config.add_response_adapter(None, Adapter) adapter = Adapter() result = config.registry.queryAdapter(adapter, IResponse) self.assertTrue(result is adapter) def test_add_response_adapter_dottednames(self): from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) if PY3: str_name = 'builtins.str' else: str_name = '__builtin__.str' config.add_response_adapter('pyramid.response.Response', str_name) result = config.registry.queryAdapter('foo', IResponse) self.assertTrue(result.body, b'foo') def test_add_traverser_dotted_names(self): from pyramid.interfaces import ITraverser config = self._makeOne(autocommit=True) config.add_traverser( 'pyramid.tests.test_config.test_adapters.DummyTraverser', 'pyramid.tests.test_config.test_adapters.DummyIface') iface = DummyIface() traverser = config.registry.getAdapter(iface, ITraverser) self.assertEqual(traverser.__class__, DummyTraverser) self.assertEqual(traverser.root, iface) def test_add_traverser_default_iface_means_Interface(self): from pyramid.interfaces import ITraverser config = self._makeOne(autocommit=True) config.add_traverser(DummyTraverser) traverser = config.registry.getAdapter(None, ITraverser) self.assertEqual(traverser.__class__, DummyTraverser) def test_add_traverser_nondefault_iface(self): from pyramid.interfaces import ITraverser config = self._makeOne(autocommit=True) config.add_traverser(DummyTraverser, DummyIface) iface = DummyIface() traverser = config.registry.getAdapter(iface, ITraverser) self.assertEqual(traverser.__class__, DummyTraverser) self.assertEqual(traverser.root, iface) def test_add_traverser_introspectables(self): config = self._makeOne() config.add_traverser(DummyTraverser, DummyIface) actions = config.action_state.actions self.assertEqual(len(actions), 1) intrs = actions[0]['introspectables'] self.assertEqual(len(intrs), 1) intr = intrs[0] self.assertEqual(intr.type_name, 'traverser') self.assertEqual(intr.discriminator, ('traverser', DummyIface)) self.assertEqual(intr.category_name, 'traversers') self.assertEqual(intr.title, 'traverser for %r' % DummyIface) self.assertEqual(intr['adapter'], DummyTraverser) self.assertEqual(intr['iface'], DummyIface) def test_add_resource_url_adapter_dotted_names(self): from pyramid.interfaces import IResourceURL config = self._makeOne(autocommit=True) config.add_resource_url_adapter( 'pyramid.tests.test_config.test_adapters.DummyResourceURL', 'pyramid.tests.test_config.test_adapters.DummyIface', ) iface = DummyIface() adapter = config.registry.getMultiAdapter((iface, iface), IResourceURL) self.assertEqual(adapter.__class__, DummyResourceURL) self.assertEqual(adapter.resource, iface) self.assertEqual(adapter.request, iface) def test_add_resource_url_default_resource_iface_means_Interface(self): from pyramid.interfaces import IResourceURL config = self._makeOne(autocommit=True) config.add_resource_url_adapter(DummyResourceURL) iface = DummyIface() adapter = config.registry.getMultiAdapter((iface, iface), IResourceURL) self.assertEqual(adapter.__class__, DummyResourceURL) self.assertEqual(adapter.resource, iface) self.assertEqual(adapter.request, iface) def test_add_resource_url_nodefault_resource_iface(self): from zope.interface import Interface from pyramid.interfaces import IResourceURL config = self._makeOne(autocommit=True) config.add_resource_url_adapter(DummyResourceURL, DummyIface) iface = DummyIface() adapter = config.registry.getMultiAdapter((iface, iface), IResourceURL) self.assertEqual(adapter.__class__, DummyResourceURL) self.assertEqual(adapter.resource, iface) self.assertEqual(adapter.request, iface) bad_result = config.registry.queryMultiAdapter( (Interface, Interface), IResourceURL, ) self.assertEqual(bad_result, None) def test_add_resource_url_adapter_introspectables(self): config = self._makeOne() config.add_resource_url_adapter(DummyResourceURL, DummyIface) actions = config.action_state.actions self.assertEqual(len(actions), 1) intrs = actions[0]['introspectables'] self.assertEqual(len(intrs), 1) intr = intrs[0] self.assertEqual(intr.type_name, 'resource url adapter') self.assertEqual(intr.discriminator, ('resource url adapter', DummyIface)) self.assertEqual(intr.category_name, 'resource url adapters') self.assertEqual( intr.title, "resource url adapter for resource iface " "" ) self.assertEqual(intr['adapter'], DummyResourceURL) self.assertEqual(intr['resource_iface'], DummyIface) class Test_eventonly(unittest.TestCase): def _callFUT(self, callee): from pyramid.config.adapters import eventonly return eventonly(callee) def test_defaults(self): def acallable(event, a=1, b=2): pass self.assertTrue(self._callFUT(acallable)) class DummyTraverser(object): def __init__(self, root): self.root = root class DummyIface(object): pass class DummyResourceURL(object): def __init__(self, resource, request): self.resource = resource self.request = request def predicate_maker(name): class Predicate(object): def __init__(self, val, config): self.val = val def phash(self): return 'phash' text = phash def __call__(self, event): return getattr(event, name, None) == self.val return Predicate pyramid-1.6/pyramid/tests/test_config/test_assets.py0000644000076500000240000011277112524266531023617 0ustar michaelstaff00000000000000import 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 config = Configurator(*arg, **kw) return config def test_override_asset_samename(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() 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/', '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', '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', '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( 'pyramid.tests.test_config.pkgs.asset:templates/foo.pt', 'pyramid.tests.test_config.pkgs.asset.subpackage:templates/bar.pt', _override=override) from pyramid.tests.test_config.pkgs import asset from pyramid.tests.test_config.pkgs.asset import subpackage self.assertEqual(override.package, asset) self.assertEqual(override.path, 'templates/foo.pt') source = override.source self.assertTrue(isinstance(source, PackageAssetSource)) self.assertEqual(source.package, subpackage) self.assertEqual(source.prefix, 'templates/bar.pt') resource_name = '' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) def test_override_asset_package_with_package(self): from pyramid.config.assets import PackageAssetSource config = self._makeOne(autocommit=True) override = DummyUnderOverride() config.override_asset( 'pyramid.tests.test_config.pkgs.asset', 'pyramid.tests.test_config.pkgs.asset.subpackage', _override=override) from pyramid.tests.test_config.pkgs import asset from pyramid.tests.test_config.pkgs.asset import subpackage self.assertEqual(override.package, asset) self.assertEqual(override.path, '') source = override.source self.assertTrue(isinstance(source, PackageAssetSource)) self.assertEqual(source.package, subpackage) self.assertEqual(source.prefix, '') resource_name = 'templates/bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) def test_override_asset_directory_with_directory(self): from pyramid.config.assets import PackageAssetSource config = self._makeOne(autocommit=True) override = DummyUnderOverride() config.override_asset( 'pyramid.tests.test_config.pkgs.asset:templates/', 'pyramid.tests.test_config.pkgs.asset.subpackage:templates/', _override=override) from pyramid.tests.test_config.pkgs import asset from pyramid.tests.test_config.pkgs.asset import subpackage self.assertEqual(override.package, asset) self.assertEqual(override.path, 'templates/') source = override.source self.assertTrue(isinstance(source, PackageAssetSource)) self.assertEqual(source.package, subpackage) self.assertEqual(source.prefix, 'templates/') resource_name = 'bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) def test_override_asset_directory_with_package(self): from pyramid.config.assets import PackageAssetSource config = self._makeOne(autocommit=True) override = DummyUnderOverride() config.override_asset( 'pyramid.tests.test_config.pkgs.asset:templates/', 'pyramid.tests.test_config.pkgs.asset.subpackage', _override=override) from pyramid.tests.test_config.pkgs import asset from pyramid.tests.test_config.pkgs.asset import subpackage self.assertEqual(override.package, asset) self.assertEqual(override.path, 'templates/') source = override.source self.assertTrue(isinstance(source, PackageAssetSource)) self.assertEqual(source.package, subpackage) self.assertEqual(source.prefix, '') resource_name = 'templates/bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) def test_override_asset_package_with_directory(self): from pyramid.config.assets import PackageAssetSource config = self._makeOne(autocommit=True) override = DummyUnderOverride() config.override_asset( 'pyramid.tests.test_config.pkgs.asset', 'pyramid.tests.test_config.pkgs.asset.subpackage:templates/', _override=override) from pyramid.tests.test_config.pkgs import asset from pyramid.tests.test_config.pkgs.asset import subpackage self.assertEqual(override.package, asset) self.assertEqual(override.path, '') source = override.source self.assertTrue(isinstance(source, PackageAssetSource)) self.assertEqual(source.package, subpackage) self.assertEqual(source.prefix, 'templates/') resource_name = 'bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) 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) resource_name = '' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) 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) resource_name = 'bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) 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) resource_name = 'bar.pt' expected = os.path.join(here, 'pkgs', 'asset', 'subpackage', 'templates', 'bar.pt') self.assertEqual(override.source.get_filename(resource_name), expected) def test__override_not_yet_registered(self): from pyramid.interfaces import IPackageOverrides package = DummyPackage('package') source = DummyAssetSource() config = self._makeOne() config._override(package, 'path', source, PackageOverrides=DummyPackageOverrides) overrides = config.registry.queryUtility(IPackageOverrides, name='package') 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') source = DummyAssetSource() overrides = DummyPackageOverrides(package) config = self._makeOne() config.registry.registerUtility(overrides, IPackageOverrides, name='package') config._override(package, 'path', source, PackageOverrides=DummyPackageOverrides) self.assertEqual(overrides.inserted, [('path', source)]) self.assertEqual(overrides.package, package) class TestOverrideProvider(unittest.TestCase): def setUp(self): cleanUp() def tearDown(self): cleanUp() def _getTargetClass(self): from pyramid.config.assets import OverrideProvider return OverrideProvider def _makeOne(self, module): klass = self._getTargetClass() return klass(module) def _registerOverrides(self, overrides, name='pyramid.tests.test_config'): from pyramid.interfaces import IPackageOverrides from pyramid.threadlocal import get_current_registry reg = get_current_registry() reg.registerUtility(overrides, IPackageOverrides, name=name) def test_get_resource_filename_no_overrides(self): resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) 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): resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) 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): resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.get_resource_string(None, resource_name) _assertBody(result, os.path.join(here, resource_name)) def test_has_resource_no_overrides(self): resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.has_resource(resource_name) self.assertEqual(result, True) def test_resource_isdir_no_overrides(self): file_resource_name = 'test_assets.py' directory_resource_name = 'files' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_isdir(file_resource_name) self.assertEqual(result, False) result = provider.resource_isdir(directory_resource_name) self.assertEqual(result, True) def test_resource_listdir_no_overrides(self): resource_name = 'files' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_listdir(resource_name) self.assertTrue(result) def test_get_resource_filename_override_returns_None(self): overrides = DummyOverrides(None) self._registerOverrides(overrides) resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) expected = os.path.join(here, resource_name) result = provider.get_resource_filename(None, resource_name) self.assertEqual(result, expected) def test_get_resource_stream_override_returns_None(self): overrides = DummyOverrides(None) self._registerOverrides(overrides) resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) 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) resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.get_resource_string(None, resource_name) _assertBody(result, os.path.join(here, resource_name)) def test_has_resource_override_returns_None(self): overrides = DummyOverrides(None) self._registerOverrides(overrides) resource_name = 'test_assets.py' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.has_resource(resource_name) self.assertEqual(result, True) def test_resource_isdir_override_returns_None(self): overrides = DummyOverrides(None) self._registerOverrides(overrides) resource_name = 'files' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_isdir(resource_name) self.assertEqual(result, True) def test_resource_listdir_override_returns_None(self): overrides = DummyOverrides(None) self._registerOverrides(overrides) resource_name = 'files' import pyramid.tests.test_config provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_listdir(resource_name) self.assertTrue(result) def test_get_resource_filename_override_returns_value(self): overrides = DummyOverrides('value') import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) result = provider.get_resource_filename(None, 'test_assets.py') self.assertEqual(result, 'value') def test_get_resource_stream_override_returns_value(self): from io import BytesIO overrides = DummyOverrides(BytesIO(b'value')) import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) with provider.get_resource_stream(None, 'test_assets.py') as stream: self.assertEqual(stream.getvalue(), b'value') def test_get_resource_string_override_returns_value(self): overrides = DummyOverrides('value') import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) result = provider.get_resource_string(None, 'test_assets.py') self.assertEqual(result, 'value') def test_has_resource_override_returns_True(self): overrides = DummyOverrides(True) import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) result = provider.has_resource('test_assets.py') self.assertEqual(result, True) def test_resource_isdir_override_returns_False(self): overrides = DummyOverrides(False) import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_isdir('files') self.assertEqual(result, False) def test_resource_listdir_override_returns_values(self): overrides = DummyOverrides(['a']) import pyramid.tests.test_config self._registerOverrides(overrides) provider = self._makeOne(pyramid.tests.test_config) result = provider.resource_listdir('files') self.assertEqual(result, ['a']) class TestPackageOverrides(unittest.TestCase): def _getTargetClass(self): from pyramid.config.assets import PackageOverrides return PackageOverrides def _makeOne(self, package=None, pkg_resources=None): if package is None: package = DummyPackage('package') klass = self._getTargetClass() if pkg_resources is None: pkg_resources = DummyPkgResources() return klass(package, pkg_resources=pkg_resources) def test_class_conforms_to_IPackageOverrides(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IPackageOverrides verifyClass(IPackageOverrides, self._getTargetClass()) def test_instance_conforms_to_IPackageOverrides(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IPackageOverrides verifyObject(IPackageOverrides, self._makeOne()) def test_class_conforms_to_IPEP302Loader(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IPEP302Loader verifyClass(IPEP302Loader, self._getTargetClass()) def test_instance_conforms_to_IPEP302Loader(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IPEP302Loader verifyObject(IPEP302Loader, self._makeOne()) def test_ctor_package_already_has_loader_of_different_type(self): package = DummyPackage('package') loader = package.__loader__ = DummyLoader() po = self._makeOne(package) self.assertTrue(package.__loader__ is po) self.assertTrue(po.real_loader is loader) def test_ctor_package_already_has_loader_of_same_type(self): package = DummyPackage('package') package.__loader__ = self._makeOne(package) po = self._makeOne(package) self.assertEqual(package.__loader__, po) def test_ctor_sets_loader(self): package = DummyPackage('package') po = self._makeOne(package) self.assertEqual(package.__loader__, po) def test_ctor_registers_loader_type(self): from pyramid.config.assets import OverrideProvider dummy_pkg_resources = DummyPkgResources() package = DummyPackage('package') po = self._makeOne(package, dummy_pkg_resources) self.assertEqual(dummy_pkg_resources.registered, [(po.__class__, OverrideProvider)]) def test_ctor_sets_local_state(self): package = DummyPackage('package') po = self._makeOne(package) self.assertEqual(po.overrides, []) self.assertEqual(po.overridden_package_name, 'package') def test_insert_directory(self): from pyramid.config.assets import DirectoryOverride package = DummyPackage('package') po = self._makeOne(package) po.overrides = [None] po.insert('foo/', DummyAssetSource()) self.assertEqual(len(po.overrides), 2) override = po.overrides[0] self.assertEqual(override.__class__, DirectoryOverride) def test_insert_file(self): from pyramid.config.assets import FileOverride package = DummyPackage('package') po = self._makeOne(package) po.overrides = [None] po.insert('foo.pt', DummyAssetSource()) self.assertEqual(len(po.overrides), 2) override = po.overrides[0] self.assertEqual(override.__class__, FileOverride) def test_insert_emptystring(self): # XXX is this a valid case for a directory? from pyramid.config.assets import DirectoryOverride package = DummyPackage('package') po = self._makeOne(package) 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_filtered_sources(self): overrides = [ DummyOverride(None), DummyOverride('foo')] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(list(po.filtered_sources('whatever')), ['foo']) def test_get_filename(self): source = DummyAssetSource(filename='foo.pt') overrides = [ DummyOverride(None), DummyOverride((source, ''))] package = DummyPackage('package') po = self._makeOne(package) 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): source = DummyAssetSource(filename=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.get_filename('whatever'), None) self.assertEqual(source.resource_name, 'wont_exist') def test_get_stream(self): source = DummyAssetSource(stream='a stream?') overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) 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): source = DummyAssetSource(stream=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.get_stream('whatever'), None) self.assertEqual(source.resource_name, 'wont_exist') def test_get_string(self): source = DummyAssetSource(string='a string') overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) 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): source = DummyAssetSource(string=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.get_string('whatever'), None) self.assertEqual(source.resource_name, 'wont_exist') def test_has_resource(self): source = DummyAssetSource(exists=True) overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) 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): source = DummyAssetSource(exists=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.has_resource('whatever'), None) self.assertEqual(source.resource_name, 'wont_exist') def test_isdir_false(self): source = DummyAssetSource(isdir=False) overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.isdir('whatever'), False) self.assertEqual(source.resource_name, 'foo.pt') def test_isdir_true(self): source = DummyAssetSource(isdir=True) overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.isdir('whatever'), True) self.assertEqual(source.resource_name, 'foo.pt') def test_isdir_doesnt_exist(self): source = DummyAssetSource(isdir=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.isdir('whatever'), None) self.assertEqual(source.resource_name, 'wont_exist') def test_listdir(self): source = DummyAssetSource(listdir=True) overrides = [DummyOverride(None), DummyOverride((source, 'foo.pt'))] package = DummyPackage('package') po = self._makeOne(package) po.overrides = overrides self.assertEqual(po.listdir('whatever'), True) self.assertEqual(source.resource_name, 'foo.pt') def test_listdir_doesnt_exist(self): source = DummyAssetSource(listdir=None) overrides = [DummyOverride(None), DummyOverride((source, 'wont_exist'))] package = DummyPackage('package') po = self._makeOne(package) 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): package = DummyPackage('package') po = self._makeOne(package) self.assertRaises(NotImplementedError, po.get_data, 'whatever') def test_get_data_pkg_has___loader__(self): package = DummyPackage('package') loader = package.__loader__ = DummyLoader() po = self._makeOne(package) self.assertEqual(po.get_data('whatever'), b'DEADBEEF') self.assertEqual(loader._got_data, 'whatever') def test_is_package_pkg_has_no___loader__(self): package = DummyPackage('package') po = self._makeOne(package) self.assertRaises(NotImplementedError, po.is_package, 'whatever') def test_is_package_pkg_has___loader__(self): package = DummyPackage('package') loader = package.__loader__ = DummyLoader() po = self._makeOne(package) self.assertTrue(po.is_package('whatever')) self.assertEqual(loader._is_package, 'whatever') def test_get_code_pkg_has_no___loader__(self): package = DummyPackage('package') po = self._makeOne(package) self.assertRaises(NotImplementedError, po.get_code, 'whatever') def test_get_code_pkg_has___loader__(self): package = DummyPackage('package') loader = package.__loader__ = DummyLoader() po = self._makeOne(package) self.assertEqual(po.get_code('whatever'), b'DEADBEEF') self.assertEqual(loader._got_code, 'whatever') def test_get_source_pkg_has_no___loader__(self): package = DummyPackage('package') po = self._makeOne(package) self.assertRaises(NotImplementedError, po.get_source, 'whatever') def test_get_source_pkg_has___loader__(self): package = DummyPackage('package') 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, source): klass = self._getTargetClass() return klass(path, source) def test_it_match(self): source = DummyAssetSource() o = self._makeOne('foo/', source) result = o('foo/something.pt') self.assertEqual(result, (source, 'something.pt')) def test_it_no_match(self): source = DummyAssetSource() o = self._makeOne('foo/', source) result = o('baz/notfound.pt') self.assertEqual(result, None) class TestFileOverride(unittest.TestCase): def _getTargetClass(self): from pyramid.config.assets import FileOverride return FileOverride def _makeOne(self, path, source): klass = self._getTargetClass() return klass(path, source) def test_it_match(self): source = DummyAssetSource() o = self._makeOne('foo.pt', source) result = o('foo.pt') self.assertEqual(result, (source, '')) def test_it_no_match(self): source = DummyAssetSource() o = self._makeOne('foo.pt', source) result = o('notfound.pt') self.assertEqual(result, None) class DummyOverride: def __init__(self, result): self.result = result def __call__(self, resource_name): return self.result class DummyOverrides: def __init__(self, result): self.result = result def get_filename(self, resource_name): return self.result listdir = isdir = has_resource = get_stream = get_string = get_filename class DummyPackageOverrides: def __init__(self, package): self.package = package self.inserted = [] def insert(self, path, source): self.inserted.append((path, source)) class DummyPkgResources: def __init__(self): self.registered = [] def register_loader_type(self, typ, inst): self.registered.append((typ, inst)) 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 def get_data(self, path): self._got_data = path return b'DEADBEEF' def is_package(self, fullname): self._is_package = fullname return True def get_code(self, fullname): self._got_code = fullname return b'DEADBEEF' def get_source(self, fullname): self._got_source = fullname return 'def foo():\n pass' class DummyUnderOverride: def __call__(self, package, path, source, _info=''): self.package = package self.path = path self.source = source def read_(src): with open(src, 'rb') as f: contents = f.read() return contents def _assertBody(body, filename): # strip both \n and \r for windows body = body.replace(b'\r', b'') body = body.replace(b'\n', b'') data = read_(filename) data = data.replace(b'\r', b'') data = data.replace(b'\n', b'') assert(body == data) pyramid-1.6/pyramid/tests/test_config/test_factories.py0000644000076500000240000002057512524266531024274 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_config import dummyfactory class TestFactoriesMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_set_request_factory(self): from pyramid.interfaces import IRequestFactory config = self._makeOne(autocommit=True) factory = object() config.set_request_factory(factory) self.assertEqual(config.registry.getUtility(IRequestFactory), factory) def test_set_request_factory_dottedname(self): from pyramid.interfaces import IRequestFactory config = self._makeOne(autocommit=True) config.set_request_factory( 'pyramid.tests.test_config.dummyfactory') 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() config.set_root_factory(dummyfactory) self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.getUtility(IRootFactory), dummyfactory) def test_set_root_factory_as_None(self): from pyramid.interfaces import IRootFactory from pyramid.traversal import DefaultRootFactory config = self._makeOne() config.set_root_factory(None) self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.getUtility(IRootFactory), DefaultRootFactory) def test_set_root_factory_dottedname(self): from pyramid.interfaces import IRootFactory config = self._makeOne() config.set_root_factory('pyramid.tests.test_config.dummyfactory') self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.getUtility(IRootFactory), dummyfactory) def test_set_session_factory(self): from pyramid.interfaces import ISessionFactory config = self._makeOne() config.set_session_factory(dummyfactory) self.assertEqual(config.registry.queryUtility(ISessionFactory), None) config.commit() self.assertEqual(config.registry.getUtility(ISessionFactory), dummyfactory) def test_set_session_factory_dottedname(self): from pyramid.interfaces import ISessionFactory config = self._makeOne() config.set_session_factory('pyramid.tests.test_config.dummyfactory') self.assertEqual(config.registry.queryUtility(ISessionFactory), None) config.commit() self.assertEqual(config.registry.getUtility(ISessionFactory), dummyfactory) def test_add_request_method_with_callable(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) callable = lambda x: None config.add_request_method(callable, name='foo') exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.methods) def test_add_request_method_with_unnamed_callable(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) def foo(self): pass config.add_request_method(foo) exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.methods) def test_set_multiple_request_methods_conflict(self): from pyramid.exceptions import ConfigurationConflictError config = self._makeOne() def foo(self): pass def bar(self): pass config.add_request_method(foo, name='bar') config.add_request_method(bar, name='bar') self.assertRaises(ConfigurationConflictError, config.commit) def test_add_request_method_with_None_callable(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) config.add_request_method(name='foo') exts = config.registry.queryUtility(IRequestExtensions) self.assertTrue(exts is None) def test_add_request_method_with_None_callable_conflict(self): from pyramid.exceptions import ConfigurationConflictError config = self._makeOne() def bar(self): pass config.add_request_method(name='foo') config.add_request_method(bar, name='foo') self.assertRaises(ConfigurationConflictError, config.commit) def test_add_request_method_with_None_callable_and_no_name(self): config = self._makeOne(autocommit=True) self.assertRaises(AttributeError, config.add_request_method) def test_add_request_method_with_text_type_name(self): from pyramid.interfaces import IRequestExtensions from pyramid.compat import text_, PY3 from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) def boomshaka(r): pass def get_bad_name(): if PY3: # pragma: nocover name = b'La Pe\xc3\xb1a' else: # pragma: nocover name = text_(b'La Pe\xc3\xb1a', 'utf-8') config.add_request_method(boomshaka, name=name) self.assertRaises(ConfigurationError, get_bad_name) class TestDeprecatedFactoriesMixinMethods(unittest.TestCase): def setUp(self): from zope.deprecation import __show__ __show__.off() def tearDown(self): from zope.deprecation import __show__ __show__.on() def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_set_request_property_with_callable(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) callable = lambda x: None config.set_request_property(callable, name='foo') exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.descriptors) def test_set_request_property_with_unnamed_callable(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) def foo(self): pass config.set_request_property(foo, reify=True) exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.descriptors) def test_set_request_property_with_property(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne(autocommit=True) callable = property(lambda x: None) config.set_request_property(callable, name='foo') exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.descriptors) def test_set_multiple_request_properties(self): from pyramid.interfaces import IRequestExtensions config = self._makeOne() def foo(self): pass bar = property(lambda x: None) config.set_request_property(foo, reify=True) config.set_request_property(bar, name='bar') config.commit() exts = config.registry.getUtility(IRequestExtensions) self.assertTrue('foo' in exts.descriptors) self.assertTrue('bar' in exts.descriptors) def test_set_multiple_request_properties_conflict(self): from pyramid.exceptions import ConfigurationConflictError config = self._makeOne() def foo(self): pass bar = property(lambda x: None) config.set_request_property(foo, name='bar', reify=True) config.set_request_property(bar, name='bar') self.assertRaises(ConfigurationConflictError, config.commit) pyramid-1.6/pyramid/tests/test_config/test_i18n.py0000644000076500000240000000767112520062551023066 0ustar michaelstaff00000000000000import os import unittest from pyramid.tests.test_config import dummyfactory here = os.path.dirname(__file__) locale = os.path.abspath( os.path.join(here, '..', 'pkgs', 'localeapp', 'locale')) locale2 = os.path.abspath( os.path.join(here, '..', 'pkgs', 'localeapp', 'locale2')) locale3 = os.path.abspath( os.path.join(here, '..', 'pkgs', 'localeapp', 'locale3')) class TestI18NConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_set_locale_negotiator(self): from pyramid.interfaces import ILocaleNegotiator config = self._makeOne(autocommit=True) def negotiator(request): pass config.set_locale_negotiator(negotiator) self.assertEqual(config.registry.getUtility(ILocaleNegotiator), negotiator) def test_set_locale_negotiator_dottedname(self): from pyramid.interfaces import ILocaleNegotiator config = self._makeOne(autocommit=True) config.set_locale_negotiator( 'pyramid.tests.test_config.dummyfactory') self.assertEqual(config.registry.getUtility(ILocaleNegotiator), dummyfactory) def test_add_translation_dirs_missing_dir(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() self.assertRaises(ConfigurationError, config.add_translation_dirs, '/wont/exist/on/my/system') def test_add_translation_dirs_no_specs(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne() config.add_translation_dirs() self.assertEqual(config.registry.queryUtility(ITranslationDirectories), None) def test_add_translation_dirs_asset_spec(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne(autocommit=True) config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale') self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale]) def test_add_translation_dirs_asset_spec_existing_translation_dirs(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne(autocommit=True) directories = ['abc'] config.registry.registerUtility(directories, ITranslationDirectories) config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale') result = config.registry.getUtility(ITranslationDirectories) self.assertEqual(result, [locale, 'abc']) def test_add_translation_dirs_multiple_specs(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne(autocommit=True) config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale', 'pyramid.tests.pkgs.localeapp:locale2') self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale, locale2]) def test_add_translation_dirs_multiple_specs_multiple_calls(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne(autocommit=True) config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale', 'pyramid.tests.pkgs.localeapp:locale2') config.add_translation_dirs('pyramid.tests.pkgs.localeapp:locale3') self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale3, locale, locale2]) def test_add_translation_dirs_abspath(self): from pyramid.interfaces import ITranslationDirectories config = self._makeOne(autocommit=True) config.add_translation_dirs(locale) self.assertEqual(config.registry.getUtility(ITranslationDirectories), [locale]) pyramid-1.6/pyramid/tests/test_config/test_init.py0000644000076500000240000021466612606614106023262 0ustar michaelstaff00000000000000import unittest import os from pyramid.compat import im_func from pyramid.testing import skip_on from pyramid.tests.test_config import dummy_tween_factory from pyramid.tests.test_config import dummy_include from pyramid.tests.test_config import dummy_extend from pyramid.tests.test_config import dummy_extend2 from pyramid.tests.test_config import IDummy from pyramid.tests.test_config import DummyContext from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError from pyramid.interfaces import IRequest class ConfiguratorTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def _getViewCallable(self, config, ctx_iface=None, request_iface=None, name='', exception_view=False): from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier if exception_view: # pragma: no cover classifier = IExceptionViewClassifier else: classifier = IViewClassifier if ctx_iface is None: ctx_iface = Interface if request_iface is None: request_iface = IRequest return config.registry.adapters.lookup( (classifier, request_iface, ctx_iface), IView, name=name, default=None) def _registerEventListener(self, config, event_iface=None): if event_iface is None: # pragma: no cover from zope.interface import Interface event_iface = Interface L = [] def subscriber(*event): L.extend(event) config.registry.registerHandler(subscriber, (event_iface,)) return L def _makeRequest(self, config): request = DummyRequest() request.registry = config.registry return request def test_ctor_no_registry(self): import sys from pyramid.interfaces import ISettings from pyramid.config import Configurator from pyramid.interfaces import IRendererFactory config = Configurator() this_pkg = sys.modules['pyramid.tests.test_config'] self.assertTrue(config.registry.getUtility(ISettings)) self.assertEqual(config.package, this_pkg) config.commit() self.assertTrue(config.registry.getUtility(IRendererFactory, 'json')) self.assertTrue(config.registry.getUtility(IRendererFactory, 'string')) def test_begin(self): from pyramid.config import Configurator config = Configurator() manager = DummyThreadLocalManager() config.manager = manager config.begin() self.assertEqual(manager.pushed, {'registry':config.registry, 'request':None}) self.assertEqual(manager.popped, False) def test_begin_with_request(self): from pyramid.config import Configurator config = Configurator() request = object() manager = DummyThreadLocalManager() config.manager = manager config.begin(request=request) self.assertEqual(manager.pushed, {'registry':config.registry, 'request':request}) self.assertEqual(manager.popped, False) def test_end(self): from pyramid.config import Configurator config = Configurator() manager = DummyThreadLocalManager() config.manager = manager config.end() self.assertEqual(manager.pushed, None) self.assertEqual(manager.popped, True) def test_ctor_with_package_registry(self): import sys from pyramid.config import Configurator pkg = sys.modules['pyramid'] config = Configurator(package=pkg) self.assertEqual(config.package, pkg) def test_ctor_noreg_custom_settings(self): from pyramid.interfaces import ISettings settings = {'reload_templates':True, 'mysetting':True} config = self._makeOne(settings=settings) settings = config.registry.getUtility(ISettings) self.assertEqual(settings['reload_templates'], True) self.assertEqual(settings['debug_authorization'], False) self.assertEqual(settings['mysetting'], True) def test_ctor_noreg_debug_logger_None_default(self): from pyramid.interfaces import IDebugLogger config = self._makeOne() logger = config.registry.getUtility(IDebugLogger) self.assertEqual(logger.name, 'pyramid.tests.test_config') def test_ctor_noreg_debug_logger_non_None(self): from pyramid.interfaces import IDebugLogger logger = object() config = self._makeOne(debug_logger=logger) result = config.registry.getUtility(IDebugLogger) self.assertEqual(logger, result) def test_ctor_authentication_policy(self): from pyramid.interfaces import IAuthenticationPolicy policy = object() config = self._makeOne(authentication_policy=policy) config.commit() result = config.registry.getUtility(IAuthenticationPolicy) self.assertEqual(policy, result) def test_ctor_authorization_policy_only(self): policy = object() config = self._makeOne(authorization_policy=policy) self.assertRaises(ConfigurationExecutionError, config.commit) def test_ctor_no_root_factory(self): from pyramid.interfaces import IRootFactory config = self._makeOne() self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.queryUtility(IRootFactory), None) def test_ctor_with_root_factory(self): from pyramid.interfaces import IRootFactory factory = object() config = self._makeOne(root_factory=factory) self.assertEqual(config.registry.queryUtility(IRootFactory), None) config.commit() self.assertEqual(config.registry.queryUtility(IRootFactory), factory) def test_ctor_alternate_renderers(self): from pyramid.interfaces import IRendererFactory renderer = object() config = self._makeOne(renderers=[('yeah', renderer)]) config.commit() self.assertEqual(config.registry.getUtility(IRendererFactory, 'yeah'), renderer) def test_ctor_default_renderers(self): from pyramid.interfaces import IRendererFactory from pyramid.renderers import json_renderer_factory config = self._makeOne() self.assertEqual(config.registry.getUtility(IRendererFactory, 'json'), json_renderer_factory) def test_ctor_default_permission(self): from pyramid.interfaces import IDefaultPermission config = self._makeOne(default_permission='view') config.commit() self.assertEqual(config.registry.getUtility(IDefaultPermission), 'view') def test_ctor_session_factory(self): from pyramid.interfaces import ISessionFactory factory = object() config = self._makeOne(session_factory=factory) self.assertEqual(config.registry.queryUtility(ISessionFactory), None) config.commit() self.assertEqual(config.registry.getUtility(ISessionFactory), factory) def test_ctor_default_view_mapper(self): from pyramid.interfaces import IViewMapperFactory mapper = object() config = self._makeOne(default_view_mapper=mapper) config.commit() self.assertEqual(config.registry.getUtility(IViewMapperFactory), mapper) def test_ctor_httpexception_view_default(self): from pyramid.interfaces import IExceptionResponse from pyramid.httpexceptions import default_exceptionresponse_view from pyramid.interfaces import IRequest config = self._makeOne() view = self._getViewCallable(config, ctx_iface=IExceptionResponse, request_iface=IRequest) self.assertTrue(view.__wraps__ is default_exceptionresponse_view) def test_ctor_exceptionresponse_view_None(self): from pyramid.interfaces import IExceptionResponse from pyramid.interfaces import IRequest config = self._makeOne(exceptionresponse_view=None) view = self._getViewCallable(config, ctx_iface=IExceptionResponse, request_iface=IRequest) self.assertTrue(view is None) def test_ctor_exceptionresponse_view_custom(self): from pyramid.interfaces import IExceptionResponse from pyramid.interfaces import IRequest def exceptionresponse_view(context, request): pass config = self._makeOne(exceptionresponse_view=exceptionresponse_view) view = self._getViewCallable(config, ctx_iface=IExceptionResponse, request_iface=IRequest) self.assertTrue(view.__wraps__ is exceptionresponse_view) def test_ctor_with_introspection(self): config = self._makeOne(introspection=False) self.assertEqual(config.introspection, False) def test_ctor_default_webob_response_adapter_registered(self): from webob import Response as WebobResponse response = WebobResponse() from pyramid.interfaces import IResponse config = self._makeOne(autocommit=True) result = config.registry.queryAdapter(response, IResponse) self.assertEqual(result, response) def test_with_package_module(self): from pyramid.tests.test_config import test_init import pyramid.tests config = self._makeOne() newconfig = config.with_package(test_init) self.assertEqual(newconfig.package, pyramid.tests.test_config) def test_with_package_package(self): import pyramid.tests.test_config config = self._makeOne() newconfig = config.with_package(pyramid.tests.test_config) self.assertEqual(newconfig.package, pyramid.tests.test_config) def test_with_package(self): import pyramid.tests config = self._makeOne() config.basepath = 'basepath' config.info = 'info' config.includepath = ('spec',) config.autocommit = True config.route_prefix = 'prefix' newconfig = config.with_package(pyramid.tests) self.assertEqual(newconfig.package, pyramid.tests) self.assertEqual(newconfig.registry, config.registry) self.assertEqual(newconfig.autocommit, True) self.assertEqual(newconfig.route_prefix, 'prefix') self.assertEqual(newconfig.info, 'info') self.assertEqual(newconfig.basepath, 'basepath') self.assertEqual(newconfig.includepath, ('spec',)) def test_maybe_dotted_string_success(self): import pyramid.tests.test_config config = self._makeOne() result = config.maybe_dotted('pyramid.tests.test_config') self.assertEqual(result, pyramid.tests.test_config) def test_maybe_dotted_string_fail(self): config = self._makeOne() self.assertRaises(ImportError, config.maybe_dotted, 'cant.be.found') def test_maybe_dotted_notstring_success(self): import pyramid.tests.test_config config = self._makeOne() result = config.maybe_dotted(pyramid.tests.test_config) self.assertEqual(result, pyramid.tests.test_config) def test_absolute_asset_spec_already_absolute(self): import pyramid.tests.test_config config = self._makeOne(package=pyramid.tests.test_config) result = config.absolute_asset_spec('already:absolute') self.assertEqual(result, 'already:absolute') def test_absolute_asset_spec_notastring(self): import pyramid.tests.test_config config = self._makeOne(package=pyramid.tests.test_config) result = config.absolute_asset_spec(None) self.assertEqual(result, None) def test_absolute_asset_spec_relative(self): import pyramid.tests.test_config config = self._makeOne(package=pyramid.tests.test_config) result = config.absolute_asset_spec('files') self.assertEqual(result, 'pyramid.tests.test_config:files') def test__fix_registry_has_listeners(self): reg = DummyRegistry() config = self._makeOne(reg) config._fix_registry() self.assertEqual(reg.has_listeners, True) def test__fix_registry_notify(self): reg = DummyRegistry() config = self._makeOne(reg) config._fix_registry() self.assertEqual(reg.notify(1), None) self.assertEqual(reg.events, (1,)) def test__fix_registry_queryAdapterOrSelf(self): from zope.interface import Interface from zope.interface import implementer class IFoo(Interface): pass @implementer(IFoo) class Foo(object): pass class Bar(object): pass adaptation = () foo = Foo() bar = Bar() reg = DummyRegistry(adaptation) config = self._makeOne(reg) config._fix_registry() self.assertTrue(reg.queryAdapterOrSelf(foo, IFoo) is foo) self.assertTrue(reg.queryAdapterOrSelf(bar, IFoo) is adaptation) def test__fix_registry_registerSelfAdapter(self): reg = DummyRegistry() config = self._makeOne(reg) config._fix_registry() reg.registerSelfAdapter('required', 'provided', name='abc') self.assertEqual(len(reg.adapters), 1) args, kw = reg.adapters[0] self.assertEqual(args[0]('abc'), 'abc') self.assertEqual(kw, {'info': '', 'provided': 'provided', 'required': 'required', 'name': 'abc', 'event': True}) def test__fix_registry_adds__lock(self): reg = DummyRegistry() config = self._makeOne(reg) config._fix_registry() self.assertTrue(hasattr(reg, '_lock')) def test__fix_registry_adds_clear_view_lookup_cache(self): reg = DummyRegistry() config = self._makeOne(reg) self.assertFalse(hasattr(reg, '_clear_view_lookup_cache')) config._fix_registry() self.assertFalse(hasattr(reg, '_view_lookup_cache')) reg._clear_view_lookup_cache() self.assertEqual(reg._view_lookup_cache, {}) def test_setup_registry_calls_fix_registry(self): reg = DummyRegistry() config = self._makeOne(reg) config.add_view = lambda *arg, **kw: False config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(reg.has_listeners, True) def test_setup_registry_registers_default_exceptionresponse_views(self): from webob.exc import WSGIHTTPException from pyramid.interfaces import IExceptionResponse from pyramid.view import default_exceptionresponse_view reg = DummyRegistry() config = self._makeOne(reg) views = [] config.add_view = lambda *arg, **kw: views.append((arg, kw)) config.add_default_view_predicates = lambda *arg: None config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertEqual(views[0], ((default_exceptionresponse_view,), {'context':IExceptionResponse})) self.assertEqual(views[1], ((default_exceptionresponse_view,), {'context':WSGIHTTPException})) def test_setup_registry_registers_default_view_predicates(self): reg = DummyRegistry() config = self._makeOne(reg) vp_called = [] config.add_view = lambda *arg, **kw: None config.add_default_view_predicates = lambda *arg: vp_called.append(True) config._add_tween = lambda *arg, **kw: False config.setup_registry() self.assertTrue(vp_called) def test_setup_registry_registers_default_webob_iresponse_adapter(self): from webob import Response from pyramid.interfaces import IResponse config = self._makeOne() config.setup_registry() response = Response() self.assertTrue( config.registry.queryAdapter(response, IResponse) is response) def test_setup_registry_explicit_notfound_trumps_iexceptionresponse(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.httpexceptions import HTTPNotFound from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg, autocommit=True) config.setup_registry() # registers IExceptionResponse default view def myview(context, request): return 'OK' config.add_view(myview, context=HTTPNotFound, renderer=null_renderer) request = self._makeRequest(config) view = self._getViewCallable(config, ctx_iface=implementedBy(HTTPNotFound), request_iface=IRequest) result = view(None, request) self.assertEqual(result, 'OK') def test_setup_registry_custom_settings(self): from pyramid.registry import Registry from pyramid.interfaces import ISettings settings = {'reload_templates':True, 'mysetting':True} reg = Registry() config = self._makeOne(reg) config.setup_registry(settings=settings) settings = reg.getUtility(ISettings) self.assertEqual(settings['reload_templates'], True) self.assertEqual(settings['debug_authorization'], False) self.assertEqual(settings['mysetting'], True) def test_setup_registry_debug_logger_None_default(self): from pyramid.registry import Registry from pyramid.interfaces import IDebugLogger reg = Registry() config = self._makeOne(reg) config.setup_registry() logger = reg.getUtility(IDebugLogger) self.assertEqual(logger.name, 'pyramid.tests.test_config') def test_setup_registry_debug_logger_non_None(self): from pyramid.registry import Registry from pyramid.interfaces import IDebugLogger logger = object() reg = Registry() config = self._makeOne(reg) config.setup_registry(debug_logger=logger) result = reg.getUtility(IDebugLogger) self.assertEqual(logger, result) def test_setup_registry_debug_logger_name(self): from pyramid.registry import Registry from pyramid.interfaces import IDebugLogger reg = Registry() config = self._makeOne(reg) config.setup_registry(debug_logger='foo') result = reg.getUtility(IDebugLogger) self.assertEqual(result.name, 'foo') def test_setup_registry_authentication_policy(self): from pyramid.registry import Registry from pyramid.interfaces import IAuthenticationPolicy policy = object() reg = Registry() config = self._makeOne(reg) config.setup_registry(authentication_policy=policy) config.commit() result = reg.getUtility(IAuthenticationPolicy) self.assertEqual(policy, result) def test_setup_registry_authentication_policy_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import IAuthenticationPolicy reg = Registry() config = self._makeOne(reg) config.setup_registry(authentication_policy='pyramid.tests.test_config') config.commit() result = reg.getUtility(IAuthenticationPolicy) import pyramid.tests.test_config self.assertEqual(result, pyramid.tests.test_config) def test_setup_registry_authorization_policy_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import IAuthorizationPolicy reg = Registry() config = self._makeOne(reg) dummy = object() config.setup_registry(authentication_policy=dummy, authorization_policy='pyramid.tests.test_config') config.commit() result = reg.getUtility(IAuthorizationPolicy) import pyramid.tests.test_config self.assertEqual(result, pyramid.tests.test_config) def test_setup_registry_authorization_policy_only(self): from pyramid.registry import Registry policy = object() reg = Registry() config = self._makeOne(reg) config.setup_registry(authorization_policy=policy) config = self.assertRaises(ConfigurationExecutionError, config.commit) def test_setup_registry_no_default_root_factory(self): from pyramid.registry import Registry from pyramid.interfaces import IRootFactory reg = Registry() config = self._makeOne(reg) config.setup_registry() config.commit() self.assertEqual(reg.queryUtility(IRootFactory), None) def test_setup_registry_dottedname_root_factory(self): from pyramid.registry import Registry from pyramid.interfaces import IRootFactory reg = Registry() config = self._makeOne(reg) import pyramid.tests.test_config config.setup_registry(root_factory='pyramid.tests.test_config') self.assertEqual(reg.queryUtility(IRootFactory), None) config.commit() self.assertEqual(reg.getUtility(IRootFactory), pyramid.tests.test_config) def test_setup_registry_locale_negotiator_dottedname(self): from pyramid.registry import Registry from pyramid.interfaces import ILocaleNegotiator reg = Registry() config = self._makeOne(reg) import pyramid.tests.test_config config.setup_registry(locale_negotiator='pyramid.tests.test_config') self.assertEqual(reg.queryUtility(ILocaleNegotiator), None) config.commit() utility = reg.getUtility(ILocaleNegotiator) self.assertEqual(utility, pyramid.tests.test_config) def test_setup_registry_locale_negotiator(self): from pyramid.registry import Registry from pyramid.interfaces import ILocaleNegotiator reg = Registry() config = self._makeOne(reg) negotiator = object() config.setup_registry(locale_negotiator=negotiator) self.assertEqual(reg.queryUtility(ILocaleNegotiator), None) config.commit() utility = reg.getUtility(ILocaleNegotiator) self.assertEqual(utility, negotiator) def test_setup_registry_request_factory(self): from pyramid.registry import Registry from pyramid.interfaces import IRequestFactory reg = Registry() config = self._makeOne(reg) factory = object() config.setup_registry(request_factory=factory) self.assertEqual(reg.queryUtility(IRequestFactory), None) config.commit() 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 reg = Registry() config = self._makeOne(reg) import pyramid.tests.test_config config.setup_registry(request_factory='pyramid.tests.test_config') self.assertEqual(reg.queryUtility(IRequestFactory), None) config.commit() utility = reg.getUtility(IRequestFactory) self.assertEqual(utility, pyramid.tests.test_config) def test_setup_registry_alternate_renderers(self): from pyramid.registry import Registry from pyramid.interfaces import IRendererFactory renderer = object() reg = Registry() config = self._makeOne(reg) config.setup_registry(renderers=[('yeah', renderer)]) config.commit() self.assertEqual(reg.getUtility(IRendererFactory, 'yeah'), renderer) def test_setup_registry_default_permission(self): from pyramid.registry import Registry from pyramid.interfaces import IDefaultPermission reg = Registry() config = self._makeOne(reg) config.setup_registry(default_permission='view') config.commit() self.assertEqual(reg.getUtility(IDefaultPermission), 'view') def test_setup_registry_includes(self): from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg) settings = { 'pyramid.includes': """pyramid.tests.test_config.dummy_include pyramid.tests.test_config.dummy_include2""", } config.setup_registry(settings=settings) self.assertTrue(reg.included) self.assertTrue(reg.also_included) def test_setup_registry_includes_spaces(self): from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg) settings = { 'pyramid.includes': """pyramid.tests.test_config.dummy_include pyramid.tests.test_config.dummy_include2""", } config.setup_registry(settings=settings) self.assertTrue(reg.included) self.assertTrue(reg.also_included) def test_setup_registry_tweens(self): from pyramid.interfaces import ITweens from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg) settings = { 'pyramid.tweens': 'pyramid.tests.test_config.dummy_tween_factory' } config.setup_registry(settings=settings) config.commit() tweens = config.registry.getUtility(ITweens) self.assertEqual( tweens.explicit, [('pyramid.tests.test_config.dummy_tween_factory', dummy_tween_factory)]) def test_introspector_decorator(self): inst = self._makeOne() default = inst.introspector self.assertTrue(hasattr(default, 'add')) self.assertEqual(inst.introspector, inst.registry.introspector) introspector = object() inst.introspector = introspector new = inst.introspector self.assertTrue(new is introspector) self.assertEqual(inst.introspector, inst.registry.introspector) del inst.introspector default = inst.introspector self.assertFalse(default is new) self.assertTrue(hasattr(default, 'add')) def test_make_wsgi_app(self): import pyramid.config from pyramid.router import Router from pyramid.interfaces import IApplicationCreated manager = DummyThreadLocalManager() config = self._makeOne() subscriber = self._registerEventListener(config, IApplicationCreated) config.manager = manager app = config.make_wsgi_app() self.assertEqual(app.__class__, Router) self.assertEqual(manager.pushed['registry'], config.registry) self.assertEqual(manager.pushed['request'], None) self.assertTrue(manager.popped) self.assertEqual(pyramid.config.global_registries.last, app.registry) self.assertEqual(len(subscriber), 1) self.assertTrue(IApplicationCreated.providedBy(subscriber[0])) pyramid.config.global_registries.empty() def test_include_with_dotted_name(self): from pyramid.tests import test_config config = self._makeOne() config.include('pyramid.tests.test_config.dummy_include') after = config.action_state actions = after.actions self.assertEqual(len(actions), 1) action = after.actions[0] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_include_with_python_callable(self): from pyramid.tests import test_config config = self._makeOne() config.include(dummy_include) after = config.action_state actions = after.actions self.assertEqual(len(actions), 1) action = actions[0] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_include_with_module_defaults_to_includeme(self): from pyramid.tests import test_config config = self._makeOne() config.include('pyramid.tests.test_config') after = config.action_state actions = after.actions self.assertEqual(len(actions), 1) action = actions[0] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_include_with_module_defaults_to_includeme_missing(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() self.assertRaises(ConfigurationError, config.include, 'pyramid.tests') def test_include_with_route_prefix(self): root_config = self._makeOne(autocommit=True) def dummy_subapp(config): self.assertEqual(config.route_prefix, 'root') root_config.include(dummy_subapp, route_prefix='root') def test_include_with_nested_route_prefix(self): root_config = self._makeOne(autocommit=True, route_prefix='root') def dummy_subapp2(config): self.assertEqual(config.route_prefix, 'root/nested') def dummy_subapp3(config): self.assertEqual(config.route_prefix, 'root/nested/nested2') config.include(dummy_subapp4) def dummy_subapp4(config): self.assertEqual(config.route_prefix, 'root/nested/nested2') def dummy_subapp(config): self.assertEqual(config.route_prefix, 'root/nested') config.include(dummy_subapp2) config.include(dummy_subapp3, route_prefix='nested2') root_config.include(dummy_subapp, route_prefix='nested') def test_include_with_missing_source_file(self): from pyramid.exceptions import ConfigurationError import inspect config = self._makeOne() class DummyInspect(object): def getmodule(self, c): return inspect.getmodule(c) def getsourcefile(self, c): return None config.inspect = DummyInspect() try: config.include('pyramid.tests.test_config.dummy_include') except ConfigurationError as e: self.assertEqual( e.args[0], "No source file for module 'pyramid.tests.test_config' (.py " "file must exist, refusing to use orphan .pyc or .pyo file).") 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) def test_action_branching_kw_is_not_None(self): config = self._makeOne(autocommit=True) self.assertEqual(config.action('discrim', kw={'a':1}), None) def test_action_autocommit_with_introspectables(self): from pyramid.util import ActionInfo config = self._makeOne(autocommit=True) intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) self.assertEqual(len(intr.registered), 1) self.assertEqual(intr.registered[0][0], config.introspector) self.assertEqual(intr.registered[0][1].__class__, ActionInfo) def test_action_autocommit_with_introspectables_introspection_off(self): config = self._makeOne(autocommit=True) config.introspection = False intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) self.assertEqual(len(intr.registered), 0) def test_action_branching_nonautocommit_with_config_info(self): config = self._makeOne(autocommit=False) config.info = 'abc' state = DummyActionState() state.autocommit = False config.action_state = state config.action('discrim', kw={'a':1}) self.assertEqual( state.actions, [((), {'args': (), 'callable': None, 'discriminator': 'discrim', 'includepath': (), 'info': 'abc', 'introspectables': (), 'kw': {'a': 1}, 'order': 0})]) def test_action_branching_nonautocommit_without_config_info(self): config = self._makeOne(autocommit=False) config.info = '' config._ainfo = ['z'] state = DummyActionState() config.action_state = state state.autocommit = False config.action('discrim', kw={'a':1}) self.assertEqual( state.actions, [((), {'args': (), 'callable': None, 'discriminator': 'discrim', 'includepath': (), 'info': 'z', 'introspectables': (), 'kw': {'a': 1}, 'order': 0})]) def test_action_branching_nonautocommit_with_introspectables(self): config = self._makeOne(autocommit=False) config.info = '' config._ainfo = [] state = DummyActionState() config.action_state = state state.autocommit = False intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) self.assertEqual( state.actions[0][1]['introspectables'], (intr,)) def test_action_nonautocommit_with_introspectables_introspection_off(self): config = self._makeOne(autocommit=False) config.info = '' config._ainfo = [] config.introspection = False state = DummyActionState() config.action_state = state state.autocommit = False intr = DummyIntrospectable() config.action('discrim', introspectables=(intr,)) self.assertEqual( state.actions[0][1]['introspectables'], ()) def test_scan_integration(self): from zope.interface import alsoProvides from pyramid.interfaces import IRequest from pyramid.view import render_view_to_response import pyramid.tests.test_config.pkgs.scannable as package config = self._makeOne(autocommit=True) config.scan(package) ctx = DummyContext() req = DummyRequest() alsoProvides(req, IRequest) req.registry = config.registry req.method = 'GET' result = render_view_to_response(ctx, req, '') self.assertEqual(result, 'grokked') req.method = 'POST' result = render_view_to_response(ctx, req, '') self.assertEqual(result, 'grokked_post') result= render_view_to_response(ctx, req, 'grokked_class') self.assertEqual(result, 'grokked_class') result= render_view_to_response(ctx, req, 'grokked_instance') self.assertEqual(result, 'grokked_instance') result= render_view_to_response(ctx, req, 'oldstyle_grokked_class') self.assertEqual(result, 'oldstyle_grokked_class') req.method = 'GET' result = render_view_to_response(ctx, req, 'another') self.assertEqual(result, 'another_grokked') req.method = 'POST' result = render_view_to_response(ctx, req, 'another') self.assertEqual(result, 'another_grokked_post') result= render_view_to_response(ctx, req, 'another_grokked_class') self.assertEqual(result, 'another_grokked_class') result= render_view_to_response(ctx, req, 'another_grokked_instance') self.assertEqual(result, 'another_grokked_instance') result= render_view_to_response(ctx, req, 'another_oldstyle_grokked_class') self.assertEqual(result, 'another_oldstyle_grokked_class') result = render_view_to_response(ctx, req, 'stacked1') self.assertEqual(result, 'stacked') result = render_view_to_response(ctx, req, 'stacked2') self.assertEqual(result, 'stacked') result = render_view_to_response(ctx, req, 'another_stacked1') self.assertEqual(result, 'another_stacked') result = render_view_to_response(ctx, req, 'another_stacked2') self.assertEqual(result, 'another_stacked') result = render_view_to_response(ctx, req, 'stacked_class1') self.assertEqual(result, 'stacked_class') result = render_view_to_response(ctx, req, 'stacked_class2') self.assertEqual(result, 'stacked_class') result = render_view_to_response(ctx, req, 'another_stacked_class1') self.assertEqual(result, 'another_stacked_class') result = render_view_to_response(ctx, req, 'another_stacked_class2') self.assertEqual(result, 'another_stacked_class') # NB: on Jython, a class without an __init__ apparently accepts # any number of arguments without raising a TypeError, so the next # assertion may fail there. We don't support Jython at the moment, # this is just a note to a future self. self.assertRaises(TypeError, render_view_to_response, ctx, req, 'basemethod') result = render_view_to_response(ctx, req, 'method1') self.assertEqual(result, 'method1') result = render_view_to_response(ctx, req, 'method2') self.assertEqual(result, 'method2') result = render_view_to_response(ctx, req, 'stacked_method1') self.assertEqual(result, 'stacked_method') result = render_view_to_response(ctx, req, 'stacked_method2') self.assertEqual(result, 'stacked_method') result = render_view_to_response(ctx, req, 'subpackage_init') self.assertEqual(result, 'subpackage_init') result = render_view_to_response(ctx, req, 'subpackage_notinit') self.assertEqual(result, 'subpackage_notinit') result = render_view_to_response(ctx, req, 'subsubpackage_init') self.assertEqual(result, 'subsubpackage_init') result = render_view_to_response(ctx, req, 'pod_notinit') self.assertEqual(result, None) def test_scan_integration_with_ignore(self): from zope.interface import alsoProvides from pyramid.interfaces import IRequest from pyramid.view import render_view_to_response import pyramid.tests.test_config.pkgs.scannable as package config = self._makeOne(autocommit=True) config.scan(package, ignore='pyramid.tests.test_config.pkgs.scannable.another') ctx = DummyContext() req = DummyRequest() alsoProvides(req, IRequest) req.registry = config.registry req.method = 'GET' result = render_view_to_response(ctx, req, '') self.assertEqual(result, 'grokked') # ignored v = render_view_to_response(ctx, req, 'another_stacked_class2') self.assertEqual(v, None) def test_scan_integration_dottedname_package(self): from zope.interface import alsoProvides from pyramid.interfaces import IRequest from pyramid.view import render_view_to_response config = self._makeOne(autocommit=True) config.scan('pyramid.tests.test_config.pkgs.scannable') ctx = DummyContext() req = DummyRequest() alsoProvides(req, IRequest) req.registry = config.registry req.method = 'GET' result = render_view_to_response(ctx, req, '') self.assertEqual(result, 'grokked') def test_scan_integration_with_extra_kw(self): config = self._makeOne(autocommit=True) config.scan('pyramid.tests.test_config.pkgs.scanextrakw', a=1) self.assertEqual(config.a, 1) def test_scan_integration_with_onerror(self): # fancy sys.path manipulation here to appease "setup.py test" which # fails miserably when it can't import something in the package import sys try: here = os.path.dirname(__file__) path = os.path.join(here, 'path') sys.path.append(path) config = self._makeOne(autocommit=True) class FooException(Exception): pass def onerror(name): raise FooException self.assertRaises(FooException, config.scan, 'scanerror', onerror=onerror) finally: sys.path.remove(path) def test_scan_integration_conflict(self): from pyramid.tests.test_config.pkgs import selfscan from pyramid.config import Configurator c = Configurator() c.scan(selfscan) c.scan(selfscan) try: c.commit() except ConfigurationConflictError as why: def scanconflicts(e): conflicts = e._conflicts.values() for conflict in conflicts: for confinst in conflict: yield confinst.src which = list(scanconflicts(why)) self.assertEqual(len(which), 4) self.assertTrue("@view_config(renderer='string')" in which) self.assertTrue("@view_config(name='two', renderer='string')" in which) @skip_on('py3') def test_hook_zca(self): from zope.component import getSiteManager def foo(): '123' try: config = self._makeOne() config.hook_zca() config.begin() sm = getSiteManager() self.assertEqual(sm, config.registry) finally: getSiteManager.reset() @skip_on('py3') def test_unhook_zca(self): from zope.component import getSiteManager def foo(): '123' try: getSiteManager.sethook(foo) config = self._makeOne() config.unhook_zca() sm = getSiteManager() self.assertNotEqual(sm, '123') finally: getSiteManager.reset() def test_commit_conflict_simple(self): config = self._makeOne() def view1(request): pass def view2(request): pass config.add_view(view1) config.add_view(view2) self.assertRaises(ConfigurationConflictError, config.commit) def test_commit_conflict_resolved_with_include(self): config = self._makeOne() def view1(request): pass def view2(request): pass def includeme(config): config.add_view(view2) config.add_view(view1) config.include(includeme) config.commit() registeredview = self._getViewCallable(config) self.assertEqual(registeredview.__name__, 'view1') def test_commit_conflict_with_two_includes(self): config = self._makeOne() def view1(request): pass def view2(request): pass def includeme1(config): config.add_view(view1) def includeme2(config): config.add_view(view2) config.include(includeme1) config.include(includeme2) try: config.commit() except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'includeme1') self.assertEqual(c2, 'includeme2') else: #pragma: no cover raise AssertionError def test_commit_conflict_resolved_with_two_includes_and_local(self): config = self._makeOne() def view1(request): pass def view2(request): pass def view3(request): pass def includeme1(config): config.add_view(view1) def includeme2(config): config.add_view(view2) config.include(includeme1) config.include(includeme2) config.add_view(view3) config.commit() registeredview = self._getViewCallable(config) self.assertEqual(registeredview.__name__, 'view3') def test_autocommit_no_conflicts(self): from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) def view1(request): pass def view2(request): pass def view3(request): pass config.add_view(view1, renderer=null_renderer) config.add_view(view2, renderer=null_renderer) config.add_view(view3, renderer=null_renderer) config.commit() registeredview = self._getViewCallable(config) self.assertEqual(registeredview.__name__, 'view3') def test_conflict_set_notfound_view(self): config = self._makeOne() def view1(request): pass def view2(request): pass config.set_notfound_view(view1) config.set_notfound_view(view2) try: config.commit() except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'test_conflict_set_notfound_view') self.assertEqual(c2, 'test_conflict_set_notfound_view') else: # pragma: no cover raise AssertionError def test_conflict_set_forbidden_view(self): config = self._makeOne() def view1(request): pass def view2(request): pass config.set_forbidden_view(view1) config.set_forbidden_view(view2) try: config.commit() except ConfigurationConflictError as why: c1, c2 = _conflictFunctions(why) self.assertEqual(c1, 'test_conflict_set_forbidden_view') self.assertEqual(c2, 'test_conflict_set_forbidden_view') else: # pragma: no cover raise AssertionError def test___getattr__missing_when_directives_exist(self): config = self._makeOne() directives = {} config.registry._directives = directives self.assertRaises(AttributeError, config.__getattr__, 'wontexist') def test___getattr__missing_when_directives_dont_exist(self): config = self._makeOne() self.assertRaises(AttributeError, config.__getattr__, 'wontexist') def test___getattr__matches(self): config = self._makeOne() def foo(config): pass directives = {'foo':(foo, True)} config.registry._directives = directives foo_meth = config.foo self.assertTrue(getattr(foo_meth, im_func).__docobj__ is foo) def test___getattr__matches_no_action_wrap(self): config = self._makeOne() def foo(config): pass directives = {'foo':(foo, False)} config.registry._directives = directives foo_meth = config.foo self.assertTrue(getattr(foo_meth, im_func) is foo) class TestConfigurator_add_directive(unittest.TestCase): def setUp(self): from pyramid.config import Configurator self.config = Configurator() def test_extend_with_dotted_name(self): from pyramid.tests import test_config config = self.config config.add_directive( 'dummy_extend', 'pyramid.tests.test_config.dummy_extend') self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state action = after.actions[-1] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_add_directive_with_partial(self): from pyramid.tests import test_config config = self.config config.add_directive( 'dummy_partial', 'pyramid.tests.test_config.dummy_partial') self.assertTrue(hasattr(config, 'dummy_partial')) config.dummy_partial() after = config.action_state action = after.actions[-1] self.assertEqual(action['discriminator'], 'partial') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_add_directive_with_custom_callable(self): from pyramid.tests import test_config config = self.config config.add_directive( 'dummy_callable', 'pyramid.tests.test_config.dummy_callable') self.assertTrue(hasattr(config, 'dummy_callable')) config.dummy_callable('discrim') after = config.action_state action = after.actions[-1] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_extend_with_python_callable(self): from pyramid.tests import test_config config = self.config config.add_directive( 'dummy_extend', dummy_extend) self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state action = after.actions[-1] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], test_config) def test_extend_same_name_doesnt_conflict(self): config = self.config config.add_directive( 'dummy_extend', dummy_extend) config.add_directive( 'dummy_extend', dummy_extend2) self.assertTrue(hasattr(config, 'dummy_extend')) config.dummy_extend('discrim') after = config.action_state action = after.actions[-1] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], config.registry) def test_extend_action_method_successful(self): config = self.config config.add_directive( 'dummy_extend', dummy_extend) config.dummy_extend('discrim') config.dummy_extend('discrim') self.assertRaises(ConfigurationConflictError, config.commit) def test_directive_persists_across_configurator_creations(self): config = self.config config.add_directive('dummy_extend', dummy_extend) config2 = config.with_package('pyramid.tests') config2.dummy_extend('discrim') after = config2.action_state actions = after.actions self.assertEqual(len(actions), 1) action = actions[0] self.assertEqual(action['discriminator'], 'discrim') self.assertEqual(action['callable'], None) self.assertEqual(action['args'], config2.package) class TestConfigurator__add_predicate(unittest.TestCase): def _makeOne(self): from pyramid.config import Configurator return Configurator() def test_factory_as_object(self): config = self._makeOne() def _fakeAction(discriminator, callable=None, args=(), kw=None, order=0, introspectables=(), **extra): self.assertEqual(len(introspectables), 1) self.assertEqual(introspectables[0]['name'], 'testing') self.assertEqual(introspectables[0]['factory'], DummyPredicate) config.action = _fakeAction config._add_predicate('route', 'testing', DummyPredicate) def test_factory_as_dotted_name(self): config = self._makeOne() def _fakeAction(discriminator, callable=None, args=(), kw=None, order=0, introspectables=(), **extra): self.assertEqual(len(introspectables), 1) self.assertEqual(introspectables[0]['name'], 'testing') self.assertEqual(introspectables[0]['factory'], DummyPredicate) config.action = _fakeAction config._add_predicate( 'route', 'testing', 'pyramid.tests.test_config.test_init.DummyPredicate' ) class TestActionState(unittest.TestCase): def _makeOne(self): from pyramid.config import ActionState return ActionState() def test_it(self): c = self._makeOne() self.assertEqual(c.actions, []) def test_action_simple(self): from pyramid.tests.test_config import dummyfactory as f c = self._makeOne() c.actions = [] c.action(1, f, (1,), {'x':1}) self.assertEqual( c.actions, [{'args': (1,), 'callable': f, 'discriminator': 1, 'includepath': (), 'info': None, 'introspectables': (), 'kw': {'x': 1}, 'order': 0}]) c.action(None) self.assertEqual( c.actions, [{'args': (1,), 'callable': f, 'discriminator': 1, 'includepath': (), 'info': None, 'introspectables': (), 'kw': {'x': 1}, 'order': 0}, {'args': (), 'callable': None, 'discriminator': None, 'includepath': (), 'info': None, 'introspectables': (), 'kw': {}, 'order': 0},]) def test_action_with_includepath(self): c = self._makeOne() c.actions = [] c.action(None, includepath=('abc',)) self.assertEqual( c.actions, [{'args': (), 'callable': None, 'discriminator': None, 'includepath': ('abc',), 'info': None, 'introspectables': (), 'kw': {}, 'order': 0}]) def test_action_with_info(self): c = self._makeOne() c.action(None, info='abc') self.assertEqual( c.actions, [{'args': (), 'callable': None, 'discriminator': None, 'includepath': (), 'info': 'abc', 'introspectables': (), 'kw': {}, 'order': 0}]) def test_action_with_includepath_and_info(self): c = self._makeOne() c.action(None, includepath=('spec',), info='bleh') self.assertEqual( c.actions, [{'args': (), 'callable': None, 'discriminator': None, 'includepath': ('spec',), 'info': 'bleh', 'introspectables': (), 'kw': {}, 'order': 0}]) def test_action_with_order(self): c = self._makeOne() c.actions = [] c.action(None, order=99999) self.assertEqual( c.actions, [{'args': (), 'callable': None, 'discriminator': None, 'includepath': (), 'info': None, 'introspectables': (), 'kw': {}, 'order': 99999}]) def test_action_with_introspectables(self): c = self._makeOne() c.actions = [] intr = DummyIntrospectable() c.action(None, introspectables=(intr,)) self.assertEqual( c.actions, [{'args': (), 'callable': None, 'discriminator': None, 'includepath': (), 'info': None, 'introspectables': (intr,), 'kw': {}, 'order': 0}]) def test_processSpec(self): c = self._makeOne() self.assertTrue(c.processSpec('spec')) self.assertFalse(c.processSpec('spec')) def test_execute_actions_tuples(self): output = [] def f(*a, **k): output.append((a, k)) c = self._makeOne() c.actions = [ (1, f, (1,)), (1, f, (11,), {}, ('x', )), (2, f, (2,)), (None, None), ] c.execute_actions() self.assertEqual(output, [((1,), {}), ((2,), {})]) def test_execute_actions_dicts(self): output = [] def f(*a, **k): output.append((a, k)) c = self._makeOne() c.actions = [ {'discriminator':1, 'callable':f, 'args':(1,), 'kw':{}, 'order':0, 'includepath':(), 'info':None, 'introspectables':()}, {'discriminator':1, 'callable':f, 'args':(11,), 'kw':{}, 'includepath':('x',), 'order': 0, 'info':None, 'introspectables':()}, {'discriminator':2, 'callable':f, 'args':(2,), 'kw':{}, 'order':0, 'includepath':(), 'info':None, 'introspectables':()}, {'discriminator':None, 'callable':None, 'args':(), 'kw':{}, 'order':0, 'includepath':(), 'info':None, 'introspectables':()}, ] c.execute_actions() self.assertEqual(output, [((1,), {}), ((2,), {})]) def test_execute_actions_with_introspectables(self): output = [] def f(*a, **k): output.append((a, k)) c = self._makeOne() intr = DummyIntrospectable() c.actions = [ {'discriminator':1, 'callable':f, 'args':(1,), 'kw':{}, 'order':0, 'includepath':(), 'info':None, 'introspectables':(intr,)}, ] introspector = object() c.execute_actions(introspector=introspector) self.assertEqual(output, [((1,), {})]) self.assertEqual(intr.registered, [(introspector, None)]) def test_execute_actions_with_introspectable_no_callable(self): c = self._makeOne() intr = DummyIntrospectable() c.actions = [ {'discriminator':1, 'callable':None, 'args':(1,), 'kw':{}, 'order':0, 'includepath':(), 'info':None, 'introspectables':(intr,)}, ] introspector = object() c.execute_actions(introspector=introspector) self.assertEqual(intr.registered, [(introspector, None)]) def test_execute_actions_error(self): output = [] def f(*a, **k): output.append(('f', a, k)) def bad(): raise NotImplementedError c = self._makeOne() c.actions = [ (1, f, (1,)), (1, f, (11,), {}, ('x', )), (2, f, (2,)), (3, bad, (), {}, (), 'oops') ] self.assertRaises(ConfigurationExecutionError, c.execute_actions) self.assertEqual(output, [('f', (1,), {}), ('f', (2,), {})]) def test_reentrant_action(self): output = [] c = self._makeOne() def f(*a, **k): output.append(('f', a, k)) c.actions.append((3, g, (8,), {})) def g(*a, **k): output.append(('g', a, k)) c.actions = [ (1, f, (1,)), ] c.execute_actions() self.assertEqual(output, [('f', (1,), {}), ('g', (8,), {})]) def test_reentrant_action_error(self): from pyramid.exceptions import ConfigurationError c = self._makeOne() def f(*a, **k): c.actions.append((3, g, (8,), {}, (), None, -1)) def g(*a, **k): pass c.actions = [ (1, f, (1,)), ] self.assertRaises(ConfigurationError, c.execute_actions) def test_reentrant_action_without_clear(self): c = self._makeOne() def f(*a, **k): c.actions.append((3, g, (8,))) def g(*a, **k): pass c.actions = [ (1, f, (1,)), ] c.execute_actions(clear=False) self.assertEqual(c.actions, [ (1, f, (1,)), (3, g, (8,)), ]) class Test_reentrant_action_functional(unittest.TestCase): def _makeConfigurator(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_functional(self): def add_auto_route(config, name, view): def register(): config.add_view(route_name=name, view=view) config.add_route(name, '/' + name) config.action( ('auto route', name), register, order=-30 ) config = self._makeConfigurator() config.add_directive('add_auto_route', add_auto_route) def my_view(request): return request.response config.add_auto_route('foo', my_view) config.commit() from pyramid.interfaces import IRoutesMapper mapper = config.registry.getUtility(IRoutesMapper) routes = mapper.get_routes() route = routes[0] self.assertEqual(len(routes), 1) self.assertEqual(route.name, 'foo') self.assertEqual(route.path, '/foo') class Test_resolveConflicts(unittest.TestCase): def _callFUT(self, actions): from pyramid.config import resolveConflicts return resolveConflicts(actions) def test_it_success_tuples(self): from pyramid.tests.test_config import dummyfactory as f result = self._callFUT([ (None, f), (1, f, (1,), {}, (), 'first'), (1, f, (2,), {}, ('x',), 'second'), (1, f, (3,), {}, ('y',), 'third'), (4, f, (4,), {}, ('y',), 'should be last', 99999), (3, f, (3,), {}, ('y',)), (None, f, (5,), {}, ('y',)), ]) result = list(result) self.assertEqual( result, [{'info': None, 'args': (), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': None, 'includepath': (), 'order': 0}, {'info': 'first', 'args': (1,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 1, 'includepath': (), 'order': 0}, {'info': None, 'args': (3,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 3, 'includepath': ('y',), 'order': 0}, {'info': None, 'args': (5,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': None, 'includepath': ('y',), 'order': 0}, {'info': 'should be last', 'args': (4,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 4, 'includepath': ('y',), 'order': 99999} ] ) def test_it_success_dicts(self): from pyramid.tests.test_config import dummyfactory as f from pyramid.config import expand_action result = self._callFUT([ expand_action(None, f), expand_action(1, f, (1,), {}, (), 'first'), expand_action(1, f, (2,), {}, ('x',), 'second'), expand_action(1, f, (3,), {}, ('y',), 'third'), expand_action(4, f, (4,), {}, ('y',), 'should be last', 99999), expand_action(3, f, (3,), {}, ('y',)), expand_action(None, f, (5,), {}, ('y',)), ]) result = list(result) self.assertEqual( result, [{'info': None, 'args': (), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': None, 'includepath': (), 'order': 0}, {'info': 'first', 'args': (1,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 1, 'includepath': (), 'order': 0}, {'info': None, 'args': (3,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 3, 'includepath': ('y',), 'order': 0}, {'info': None, 'args': (5,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': None, 'includepath': ('y',), 'order': 0}, {'info': 'should be last', 'args': (4,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 4, 'includepath': ('y',), 'order': 99999} ] ) def test_it_conflict(self): from pyramid.tests.test_config import dummyfactory as f result = self._callFUT([ (None, f), (1, f, (2,), {}, ('x',), 'eek'), # will conflict (1, f, (3,), {}, ('y',), 'ack'), # will conflict (4, f, (4,), {}, ('y',)), (3, f, (3,), {}, ('y',)), (None, f, (5,), {}, ('y',)), ]) self.assertRaises(ConfigurationConflictError, list, result) def test_it_with_actions_grouped_by_order(self): from pyramid.tests.test_config import dummyfactory as f from pyramid.config import expand_action result = self._callFUT([ expand_action(None, f), # X expand_action(1, f, (1,), {}, (), 'third', 10), # X expand_action(1, f, (2,), {}, ('x',), 'fourth', 10), expand_action(1, f, (3,), {}, ('y',), 'fifth', 10), expand_action(2, f, (1,), {}, (), 'sixth', 10), # X expand_action(3, f, (1,), {}, (), 'seventh', 10), # X expand_action(5, f, (4,), {}, ('y',), 'eighth', 99999), # X expand_action(4, f, (3,), {}, (), 'first', 5), # X expand_action(4, f, (5,), {}, ('y',), 'second', 5), ]) result = list(result) self.assertEqual(len(result), 6) # resolved actions should be grouped by (order, i) self.assertEqual( result, [{'info': None, 'args': (), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': None, 'includepath': (), 'order': 0}, {'info': 'first', 'args': (3,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 4, 'includepath': (), 'order': 5}, {'info': 'third', 'args': (1,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 1, 'includepath': (), 'order': 10}, {'info': 'sixth', 'args': (1,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 2, 'includepath': (), 'order': 10}, {'info': 'seventh', 'args': (1,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 3, 'includepath': (), 'order': 10}, {'info': 'eighth', 'args': (4,), 'callable': f, 'introspectables': (), 'kw': {}, 'discriminator': 5, 'includepath': ('y',), 'order': 99999} ] ) class TestGlobalRegistriesIntegration(unittest.TestCase): def setUp(self): from pyramid.config import global_registries global_registries.empty() tearDown = setUp def _makeConfigurator(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_global_registries_empty(self): from pyramid.config import global_registries self.assertEqual(global_registries.last, None) def test_global_registries(self): from pyramid.config import global_registries config1 = self._makeConfigurator() config1.make_wsgi_app() self.assertEqual(global_registries.last, config1.registry) config2 = self._makeConfigurator() config2.make_wsgi_app() self.assertEqual(global_registries.last, config2.registry) self.assertEqual(list(global_registries), [config1.registry, config2.registry]) global_registries.remove(config2.registry) self.assertEqual(global_registries.last, config1.registry) class DummyRequest: subpath = () matchdict = None request_iface = IRequest def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ self.params = {} self.cookies = {} class DummyThreadLocalManager(object): pushed = None popped = False def push(self, d): self.pushed = d def pop(self): self.popped = True from zope.interface import implementer @implementer(IDummy) class DummyEvent: pass class DummyRegistry(object): def __init__(self, adaptation=None, util=None): self.utilities = [] self.adapters = [] self.adaptation = adaptation self.util = util def subscribers(self, events, name): self.events = events return events def registerUtility(self, *arg, **kw): self.utilities.append((arg, kw)) def registerAdapter(self, *arg, **kw): self.adapters.append((arg, kw)) def queryAdapter(self, *arg, **kw): return self.adaptation def queryUtility(self, *arg, **kw): return self.util from zope.interface import Interface class IOther(Interface): pass def _conflictFunctions(e): conflicts = e._conflicts.values() for conflict in conflicts: for confinst in conflict: yield confinst.function class DummyActionState(object): autocommit = False info = '' def __init__(self): self.actions = [] def action(self, *arg, **kw): self.actions.append((arg, kw)) class DummyIntrospectable(object): def __init__(self): self.registered = [] def register(self, introspector, action_info): self.registered.append((introspector, action_info)) class DummyPredicate(object): pass pyramid-1.6/pyramid/tests/test_config/test_predicates.py0000644000076500000240000004120112517346416024430 0ustar michaelstaff00000000000000import unittest from pyramid import testing from pyramid.compat import text_ class TestXHRPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import XHRPredicate return XHRPredicate(val, None) def test___call___true(self): inst = self._makeOne(True) request = Dummy() request.is_xhr = True result = inst(None, request) self.assertTrue(result) def test___call___false(self): inst = self._makeOne(True) request = Dummy() request.is_xhr = False result = inst(None, request) self.assertFalse(result) def test_text(self): inst = self._makeOne(True) self.assertEqual(inst.text(), 'xhr = True') def test_phash(self): inst = self._makeOne(True) self.assertEqual(inst.phash(), 'xhr = True') class TestRequestMethodPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import RequestMethodPredicate return RequestMethodPredicate(val, None) def test_ctor_get_but_no_head(self): inst = self._makeOne('GET') self.assertEqual(inst.val, ('GET', 'HEAD')) def test___call___true_single(self): inst = self._makeOne('GET') request = Dummy() request.method = 'GET' result = inst(None, request) self.assertTrue(result) def test___call___true_multi(self): inst = self._makeOne(('GET','HEAD')) request = Dummy() request.method = 'GET' result = inst(None, request) self.assertTrue(result) def test___call___false(self): inst = self._makeOne(('GET','HEAD')) request = Dummy() request.method = 'POST' result = inst(None, request) self.assertFalse(result) def test_text(self): inst = self._makeOne(('HEAD','GET')) self.assertEqual(inst.text(), 'request_method = GET,HEAD') def test_phash(self): inst = self._makeOne(('HEAD','GET')) self.assertEqual(inst.phash(), 'request_method = GET,HEAD') class TestPathInfoPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import PathInfoPredicate return PathInfoPredicate(val, None) def test_ctor_compilefail(self): from pyramid.exceptions import ConfigurationError self.assertRaises(ConfigurationError, self._makeOne, '\\') def test___call___true(self): inst = self._makeOne(r'/\d{2}') request = Dummy() request.upath_info = text_('/12') result = inst(None, request) self.assertTrue(result) def test___call___false(self): inst = self._makeOne(r'/\d{2}') request = Dummy() request.upath_info = text_('/n12') result = inst(None, request) self.assertFalse(result) def test_text(self): inst = self._makeOne('/') self.assertEqual(inst.text(), 'path_info = /') def test_phash(self): inst = self._makeOne('/') self.assertEqual(inst.phash(), 'path_info = /') class TestRequestParamPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import RequestParamPredicate return RequestParamPredicate(val, None) def test___call___true_exists(self): inst = self._makeOne('abc') request = Dummy() request.params = {'abc':1} result = inst(None, request) self.assertTrue(result) def test___call___true_withval(self): inst = self._makeOne('abc=1') request = Dummy() request.params = {'abc':'1'} result = inst(None, request) self.assertTrue(result) def test___call___true_multi(self): inst = self._makeOne(('abc', 'def =2 ')) request = Dummy() request.params = {'abc':'1', 'def': '2'} result = inst(None, request) self.assertTrue(result) def test___call___false_multi(self): inst = self._makeOne(('abc=3', 'def =2 ')) request = Dummy() request.params = {'abc':'3', 'def': '1'} result = inst(None, request) self.assertFalse(result) def test___call___false(self): inst = self._makeOne('abc') request = Dummy() request.params = {} result = inst(None, request) self.assertFalse(result) def test_text_exists(self): inst = self._makeOne('abc') self.assertEqual(inst.text(), 'request_param abc') def test_text_withval(self): inst = self._makeOne('abc= 1') self.assertEqual(inst.text(), 'request_param abc=1') def test_text_multi(self): inst = self._makeOne(('abc= 1', 'def')) self.assertEqual(inst.text(), 'request_param abc=1,def') def test_phash_exists(self): inst = self._makeOne('abc') self.assertEqual(inst.phash(), 'request_param abc') def test_phash_withval(self): inst = self._makeOne('abc= 1') self.assertEqual(inst.phash(), "request_param abc=1") class TestMatchParamPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import MatchParamPredicate return MatchParamPredicate(val, None) def test___call___true_single(self): inst = self._makeOne('abc=1') request = Dummy() request.matchdict = {'abc':'1'} result = inst(None, request) self.assertTrue(result) def test___call___true_multi(self): inst = self._makeOne(('abc=1', 'def=2')) request = Dummy() request.matchdict = {'abc':'1', 'def':'2'} result = inst(None, request) self.assertTrue(result) def test___call___false(self): inst = self._makeOne('abc=1') request = Dummy() request.matchdict = {} result = inst(None, request) self.assertFalse(result) def test___call___matchdict_is_None(self): inst = self._makeOne('abc=1') request = Dummy() request.matchdict = None result = inst(None, request) self.assertFalse(result) def test_text(self): inst = self._makeOne(('def= 1', 'abc =2')) self.assertEqual(inst.text(), 'match_param abc=2,def=1') def test_phash(self): inst = self._makeOne(('def= 1', 'abc =2')) self.assertEqual(inst.phash(), 'match_param abc=2,def=1') class TestCustomPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import CustomPredicate return CustomPredicate(val, None) def test___call___true(self): def func(context, request): self.assertEqual(context, None) self.assertEqual(request, None) return True inst = self._makeOne(func) result = inst(None, None) self.assertTrue(result) def test___call___false(self): def func(context, request): self.assertEqual(context, None) self.assertEqual(request, None) return False inst = self._makeOne(func) result = inst(None, None) self.assertFalse(result) def test_text_func_has___text__(self): pred = predicate() pred.__text__ = 'text' inst = self._makeOne(pred) self.assertEqual(inst.text(), 'text') def test_text_func_repr(self): pred = predicate() inst = self._makeOne(pred) self.assertEqual(inst.text(), 'custom predicate: object predicate') def test_phash(self): pred = predicate() inst = self._makeOne(pred) self.assertEqual(inst.phash(), 'custom:1') class TestTraversePredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import TraversePredicate return TraversePredicate(val, None) def test___call__traverse_has_remainder_already(self): inst = self._makeOne('/1/:a/:b') info = {'traverse':'abc'} request = Dummy() result = inst(info, request) self.assertEqual(result, True) self.assertEqual(info, {'traverse':'abc'}) def test___call__traverse_matches(self): inst = self._makeOne('/1/:a/:b') info = {'match':{'a':'a', 'b':'b'}} request = Dummy() result = inst(info, request) self.assertEqual(result, True) self.assertEqual(info, {'match': {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}}) def test___call__traverse_matches_with_highorder_chars(self): inst = self._makeOne(text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')) info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}} request = Dummy() result = inst(info, request) self.assertEqual(result, True) self.assertEqual( info['match']['traverse'], (text_(b'La Pe\xc3\xb1a', 'utf-8'), text_(b'Qu\xc3\xa9bec', 'utf-8')) ) def test_text(self): inst = self._makeOne('/abc') self.assertEqual(inst.text(), 'traverse matchdict pseudo-predicate') def test_phash(self): inst = self._makeOne('/abc') self.assertEqual(inst.phash(), '') class Test_CheckCSRFTokenPredicate(unittest.TestCase): def _makeOne(self, val, config): from pyramid.config.predicates import CheckCSRFTokenPredicate return CheckCSRFTokenPredicate(val, config) def test_text(self): inst = self._makeOne(True, None) self.assertEqual(inst.text(), 'check_csrf = True') def test_phash(self): inst = self._makeOne(True, None) self.assertEqual(inst.phash(), 'check_csrf = True') def test_it_call_val_True(self): inst = self._makeOne(True, None) request = Dummy() def check_csrf_token(req, val, raises=True): self.assertEqual(req, request) self.assertEqual(val, 'csrf_token') self.assertEqual(raises, False) return True inst.check_csrf_token = check_csrf_token result = inst(None, request) self.assertEqual(result, True) def test_it_call_val_str(self): inst = self._makeOne('abc', None) request = Dummy() def check_csrf_token(req, val, raises=True): self.assertEqual(req, request) self.assertEqual(val, 'abc') self.assertEqual(raises, False) return True inst.check_csrf_token = check_csrf_token result = inst(None, request) self.assertEqual(result, True) def test_it_call_val_False(self): inst = self._makeOne(False, None) request = Dummy() result = inst(None, request) self.assertEqual(result, True) class TestHeaderPredicate(unittest.TestCase): def _makeOne(self, val): from pyramid.config.predicates import HeaderPredicate return HeaderPredicate(val, None) def test___call___true_exists(self): inst = self._makeOne('abc') request = Dummy() request.headers = {'abc':1} result = inst(None, request) self.assertTrue(result) def test___call___true_withval(self): inst = self._makeOne('abc:1') request = Dummy() request.headers = {'abc':'1'} result = inst(None, request) self.assertTrue(result) def test___call___true_withregex(self): inst = self._makeOne(r'abc:\d+') request = Dummy() request.headers = {'abc':'1'} result = inst(None, request) self.assertTrue(result) def test___call___false_withregex(self): inst = self._makeOne(r'abc:\d+') request = Dummy() request.headers = {'abc':'a'} result = inst(None, request) self.assertFalse(result) def test___call___false(self): inst = self._makeOne('abc') request = Dummy() request.headers = {} result = inst(None, request) self.assertFalse(result) def test_text_exists(self): inst = self._makeOne('abc') self.assertEqual(inst.text(), 'header abc') def test_text_withval(self): inst = self._makeOne('abc:1') self.assertEqual(inst.text(), 'header abc=1') def test_text_withregex(self): inst = self._makeOne(r'abc:\d+') self.assertEqual(inst.text(), r'header abc=\d+') def test_phash_exists(self): inst = self._makeOne('abc') self.assertEqual(inst.phash(), 'header abc') def test_phash_withval(self): inst = self._makeOne('abc:1') self.assertEqual(inst.phash(), "header abc=1") def test_phash_withregex(self): inst = self._makeOne(r'abc:\d+') self.assertEqual(inst.phash(), r'header abc=\d+') class Test_PhysicalPathPredicate(unittest.TestCase): def _makeOne(self, val, config): from pyramid.config.predicates import PhysicalPathPredicate return PhysicalPathPredicate(val, config) def test_text(self): inst = self._makeOne('/', None) self.assertEqual(inst.text(), "physical_path = ('',)") def test_phash(self): inst = self._makeOne('/', None) self.assertEqual(inst.phash(), "physical_path = ('',)") def test_it_call_val_tuple_True(self): inst = self._makeOne(('', 'abc'), None) root = Dummy() root.__name__ = '' root.__parent__ = None context = Dummy() context.__name__ = 'abc' context.__parent__ = root self.assertTrue(inst(context, None)) def test_it_call_val_list_True(self): inst = self._makeOne(['', 'abc'], None) root = Dummy() root.__name__ = '' root.__parent__ = None context = Dummy() context.__name__ = 'abc' context.__parent__ = root self.assertTrue(inst(context, None)) def test_it_call_val_str_True(self): inst = self._makeOne('/abc', None) root = Dummy() root.__name__ = '' root.__parent__ = None context = Dummy() context.__name__ = 'abc' context.__parent__ = root self.assertTrue(inst(context, None)) def test_it_call_False(self): inst = self._makeOne('/', None) root = Dummy() root.__name__ = '' root.__parent__ = None context = Dummy() context.__name__ = 'abc' context.__parent__ = root self.assertFalse(inst(context, None)) def test_it_call_context_has_no_name(self): inst = self._makeOne('/', None) context = Dummy() self.assertFalse(inst(context, None)) class Test_EffectivePrincipalsPredicate(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self, val, config): from pyramid.config.predicates import EffectivePrincipalsPredicate return EffectivePrincipalsPredicate(val, config) def test_text(self): inst = self._makeOne(('verna', 'fred'), None) self.assertEqual(inst.text(), "effective_principals = ['fred', 'verna']") def test_text_noniter(self): inst = self._makeOne('verna', None) self.assertEqual(inst.text(), "effective_principals = ['verna']") def test_phash(self): inst = self._makeOne(('verna', 'fred'), None) self.assertEqual(inst.phash(), "effective_principals = ['fred', 'verna']") def test_it_call_no_authentication_policy(self): request = testing.DummyRequest() inst = self._makeOne(('verna', 'fred'), None) context = Dummy() self.assertFalse(inst(context, request)) def test_it_call_authentication_policy_provides_superset(self): request = testing.DummyRequest() self.config.testing_securitypolicy('fred', groupids=('verna', 'bambi')) inst = self._makeOne(('verna', 'fred'), None) context = Dummy() self.assertTrue(inst(context, request)) def test_it_call_authentication_policy_provides_superset_implicit(self): from pyramid.security import Authenticated request = testing.DummyRequest() self.config.testing_securitypolicy('fred', groupids=('verna', 'bambi')) inst = self._makeOne(Authenticated, None) context = Dummy() self.assertTrue(inst(context, request)) def test_it_call_authentication_policy_doesnt_provide_superset(self): request = testing.DummyRequest() self.config.testing_securitypolicy('fred') inst = self._makeOne(('verna', 'fred'), None) context = Dummy() self.assertFalse(inst(context, request)) class predicate(object): def __repr__(self): return 'predicate' def __hash__(self): return 1 class Dummy(object): def __init__(self, **kw): self.__dict__.update(**kw) pyramid-1.6/pyramid/tests/test_config/test_rendering.py0000644000076500000240000000252512520062551024255 0ustar michaelstaff00000000000000import unittest class TestRenderingConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_add_default_renderers(self): from pyramid.config.rendering import DEFAULT_RENDERERS from pyramid.interfaces import IRendererFactory config = self._makeOne(autocommit=True) config.add_default_renderers() for name, impl in DEFAULT_RENDERERS: self.assertTrue( config.registry.queryUtility(IRendererFactory, name) is not None ) def test_add_renderer(self): from pyramid.interfaces import IRendererFactory config = self._makeOne(autocommit=True) renderer = object() config.add_renderer('name', renderer) self.assertEqual(config.registry.getUtility(IRendererFactory, 'name'), renderer) def test_add_renderer_dottedname_factory(self): from pyramid.interfaces import IRendererFactory config = self._makeOne(autocommit=True) import pyramid.tests.test_config config.add_renderer('name', 'pyramid.tests.test_config') self.assertEqual(config.registry.getUtility(IRendererFactory, 'name'), pyramid.tests.test_config) pyramid-1.6/pyramid/tests/test_config/test_routes.py0000644000076500000240000002415212520062551023621 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_config import dummyfactory from pyramid.tests.test_config import DummyContext from pyramid.compat import text_ class RoutesConfiguratorMixinTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def _assertRoute(self, config, name, path, num_predicates=0): from pyramid.interfaces import IRoutesMapper mapper = config.registry.getUtility(IRoutesMapper) routes = mapper.get_routes() route = routes[0] self.assertEqual(len(routes), 1) self.assertEqual(route.name, name) self.assertEqual(route.path, path) self.assertEqual(len(routes[0].predicates), num_predicates) return route def _makeRequest(self, config): request = DummyRequest() request.registry = config.registry return request def test_get_routes_mapper_not_yet_registered(self): config = self._makeOne() mapper = config.get_routes_mapper() self.assertEqual(mapper.routelist, []) def test_get_routes_mapper_already_registered(self): from pyramid.interfaces import IRoutesMapper config = self._makeOne() mapper = object() config.registry.registerUtility(mapper, IRoutesMapper) result = config.get_routes_mapper() self.assertEqual(result, mapper) def test_add_route_defaults(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path') self._assertRoute(config, 'name', 'path') def test_add_route_with_route_prefix(self): config = self._makeOne(autocommit=True) config.route_prefix = 'root' config.add_route('name', 'path') self._assertRoute(config, 'name', 'root/path') def test_add_route_discriminator(self): config = self._makeOne() config.add_route('name', 'path') self.assertEqual(config.action_state.actions[-1]['discriminator'], ('route', 'name')) def test_add_route_with_factory(self): config = self._makeOne(autocommit=True) factory = object() config.add_route('name', 'path', factory=factory) route = self._assertRoute(config, 'name', 'path') self.assertEqual(route.factory, factory) def test_add_route_with_static(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path/{foo}', static=True) mapper = config.get_routes_mapper() self.assertEqual(len(mapper.get_routes()), 0) self.assertEqual(mapper.generate('name', {"foo":"a"}), '/path/a') def test_add_route_with_factory_dottedname(self): config = self._makeOne(autocommit=True) config.add_route( 'name', 'path', factory='pyramid.tests.test_config.dummyfactory') route = self._assertRoute(config, 'name', 'path') self.assertEqual(route.factory, dummyfactory) def test_add_route_with_xhr(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', xhr=True) route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.is_xhr = True self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.is_xhr = False self.assertEqual(predicate(None, request), False) def test_add_route_with_request_method(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', request_method='GET') route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.method = 'GET' self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.method = 'POST' self.assertEqual(predicate(None, request), False) def test_add_route_with_path_info(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', path_info='/foo') route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.upath_info = '/foo' self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.upath_info = '/' self.assertEqual(predicate(None, request), False) def test_add_route_with_path_info_highorder(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', path_info=text_(b'/La Pe\xc3\xb1a', 'utf-8')) route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8') self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.upath_info = text_('/') self.assertEqual(predicate(None, request), False) def test_add_route_with_path_info_regex(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', path_info=text_(br'/La Pe\w*', 'utf-8')) route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.upath_info = text_(b'/La Pe\xc3\xb1a', 'utf-8') self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.upath_info = text_('/') self.assertEqual(predicate(None, request), False) def test_add_route_with_request_param(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', request_param='abc') route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.params = {'abc':'123'} self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.params = {} self.assertEqual(predicate(None, request), False) def test_add_route_with_custom_predicates(self): import warnings config = self._makeOne(autocommit=True) def pred1(context, request): pass def pred2(context, request): pass with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') config.add_route('name', 'path', custom_predicates=(pred1, pred2)) self.assertEqual(len(w), 1) route = self._assertRoute(config, 'name', 'path', 2) self.assertEqual(len(route.predicates), 2) def test_add_route_with_header(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', header='Host') route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.headers = {'Host':'example.com'} self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.headers = {} self.assertEqual(predicate(None, request), False) def test_add_route_with_accept(self): config = self._makeOne(autocommit=True) config.add_route('name', 'path', accept='text/xml') route = self._assertRoute(config, 'name', 'path', 1) predicate = route.predicates[0] request = self._makeRequest(config) request.accept = ['text/xml'] self.assertEqual(predicate(None, request), True) request = self._makeRequest(config) request.accept = ['text/html'] self.assertEqual(predicate(None, request), False) def test_add_route_no_pattern_with_path(self): config = self._makeOne(autocommit=True) config.add_route('name', path='path') self._assertRoute(config, 'name', 'path') def test_add_route_no_path_no_pattern(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() self.assertRaises(ConfigurationError, config.add_route, 'name') def test_add_route_with_pregenerator(self): config = self._makeOne(autocommit=True) config.add_route('name', 'pattern', pregenerator='123') route = self._assertRoute(config, 'name', 'pattern') self.assertEqual(route.pregenerator, '123') def test_add_route_no_view_with_view_attr(self): config = self._makeOne(autocommit=True) from pyramid.exceptions import ConfigurationError try: config.add_route('name', '/pattern', view_attr='abc') except ConfigurationError: pass else: # pragma: no cover raise AssertionError def test_add_route_no_view_with_view_context(self): config = self._makeOne(autocommit=True) from pyramid.exceptions import ConfigurationError try: config.add_route('name', '/pattern', view_context=DummyContext) except ConfigurationError: pass else: # pragma: no cover raise AssertionError def test_add_route_no_view_with_view_permission(self): config = self._makeOne(autocommit=True) from pyramid.exceptions import ConfigurationError try: config.add_route('name', '/pattern', view_permission='edit') except ConfigurationError: pass else: # pragma: no cover raise AssertionError def test_add_route_no_view_with_view_renderer(self): config = self._makeOne(autocommit=True) from pyramid.exceptions import ConfigurationError try: config.add_route('name', '/pattern', view_renderer='json') except ConfigurationError: pass else: # pragma: no cover raise AssertionError class DummyRequest: subpath = () matchdict = None def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ self.params = {} self.cookies = {} pyramid-1.6/pyramid/tests/test_config/test_security.py0000644000076500000240000001023312517346416024155 0ustar michaelstaff00000000000000import unittest from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationError class ConfiguratorSecurityMethodsTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_set_authentication_policy_no_authz_policy(self): config = self._makeOne() policy = object() config.set_authentication_policy(policy) self.assertRaises(ConfigurationExecutionError, config.commit) def test_set_authentication_policy_no_authz_policy_autocommit(self): config = self._makeOne(autocommit=True) policy = object() self.assertRaises(ConfigurationError, config.set_authentication_policy, policy) def test_set_authentication_policy_with_authz_policy(self): from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy config = self._makeOne() authn_policy = object() authz_policy = object() config.registry.registerUtility(authz_policy, IAuthorizationPolicy) config.set_authentication_policy(authn_policy) config.commit() self.assertEqual( config.registry.getUtility(IAuthenticationPolicy), authn_policy) def test_set_authentication_policy_with_authz_policy_autocommit(self): from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy config = self._makeOne(autocommit=True) authn_policy = object() authz_policy = object() config.registry.registerUtility(authz_policy, IAuthorizationPolicy) config.set_authentication_policy(authn_policy) config.commit() self.assertEqual( config.registry.getUtility(IAuthenticationPolicy), authn_policy) def test_set_authorization_policy_no_authn_policy(self): config = self._makeOne() policy = object() config.set_authorization_policy(policy) self.assertRaises(ConfigurationExecutionError, config.commit) def test_set_authorization_policy_no_authn_policy_autocommit(self): from pyramid.interfaces import IAuthorizationPolicy config = self._makeOne(autocommit=True) policy = object() config.set_authorization_policy(policy) self.assertEqual( config.registry.getUtility(IAuthorizationPolicy), policy) def test_set_authorization_policy_with_authn_policy(self): from pyramid.interfaces import IAuthorizationPolicy from pyramid.interfaces import IAuthenticationPolicy config = self._makeOne() authn_policy = object() authz_policy = object() config.registry.registerUtility(authn_policy, IAuthenticationPolicy) config.set_authorization_policy(authz_policy) config.commit() self.assertEqual( config.registry.getUtility(IAuthorizationPolicy), authz_policy) def test_set_authorization_policy_with_authn_policy_autocommit(self): from pyramid.interfaces import IAuthorizationPolicy from pyramid.interfaces import IAuthenticationPolicy config = self._makeOne(autocommit=True) authn_policy = object() authz_policy = object() config.registry.registerUtility(authn_policy, IAuthenticationPolicy) config.set_authorization_policy(authz_policy) self.assertEqual( config.registry.getUtility(IAuthorizationPolicy), authz_policy) def test_set_default_permission(self): from pyramid.interfaces import IDefaultPermission config = self._makeOne(autocommit=True) config.set_default_permission('view') self.assertEqual(config.registry.getUtility(IDefaultPermission), 'view') def test_add_permission(self): config = self._makeOne(autocommit=True) config.add_permission('perm') cat = config.registry.introspector.get_category('permissions') self.assertEqual(len(cat), 1) D = cat[0] intr = D['introspectable'] self.assertEqual(intr['value'], 'perm') pyramid-1.6/pyramid/tests/test_config/test_settings.py0000644000076500000240000007464012524266531024157 0ustar michaelstaff00000000000000import unittest class TestSettingsConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test__set_settings_as_None(self): config = self._makeOne() settings = config._set_settings(None) self.assertTrue(settings) def test__set_settings_as_dictwithvalues(self): config = self._makeOne() settings = config._set_settings({'a':'1'}) self.assertEqual(settings['a'], '1') def test_get_settings_nosettings(self): from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg) self.assertEqual(config.get_settings(), None) def test_get_settings_withsettings(self): settings = {'a':1} config = self._makeOne() config.registry.settings = settings self.assertEqual(config.get_settings(), settings) def test_add_settings_settings_already_registered(self): from pyramid.registry import Registry reg = Registry() config = self._makeOne(reg) config._set_settings({'a':1}) config.add_settings({'b':2}) settings = reg.settings self.assertEqual(settings['a'], 1) self.assertEqual(settings['b'], 2) def test_add_settings_settings_not_yet_registered(self): from pyramid.registry import Registry from pyramid.interfaces import ISettings reg = Registry() config = self._makeOne(reg) config.add_settings({'a':1}) settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) def test_add_settings_settings_None(self): from pyramid.registry import Registry from pyramid.interfaces import ISettings reg = Registry() config = self._makeOne(reg) config.add_settings(None, a=1) settings = reg.getUtility(ISettings) self.assertEqual(settings['a'], 1) class TestSettings(unittest.TestCase): def _getTargetClass(self): from pyramid.config.settings import Settings return Settings def _makeOne(self, d=None, environ=None): if environ is None: environ = {} klass = self._getTargetClass() return klass(d, _environ_=environ) def test_getattr_success(self): import warnings with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') settings = self._makeOne({'reload_templates':False}) self.assertEqual(settings.reload_templates, False) self.assertEqual(len(w), 1) def test_getattr_fail(self): import warnings with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') settings = self._makeOne({}) self.assertRaises(AttributeError, settings.__getattr__, 'wontexist') self.assertEqual(len(w), 0) def test_getattr_raises_attribute_error(self): settings = self._makeOne() self.assertRaises(AttributeError, settings.__getattr__, 'mykey') def test_noargs(self): settings = self._makeOne() self.assertEqual(settings['debug_authorization'], False) self.assertEqual(settings['debug_notfound'], False) self.assertEqual(settings['debug_routematch'], False) self.assertEqual(settings['reload_templates'], False) self.assertEqual(settings['reload_resources'], False) self.assertEqual(settings['pyramid.debug_authorization'], False) self.assertEqual(settings['pyramid.debug_notfound'], False) self.assertEqual(settings['pyramid.debug_routematch'], False) self.assertEqual(settings['pyramid.reload_templates'], False) self.assertEqual(settings['pyramid.reload_resources'], False) def test_prevent_http_cache(self): settings = self._makeOne({}) self.assertEqual(settings['prevent_http_cache'], False) self.assertEqual(settings['pyramid.prevent_http_cache'], False) result = self._makeOne({'prevent_http_cache':'false'}) self.assertEqual(result['prevent_http_cache'], False) self.assertEqual(result['pyramid.prevent_http_cache'], False) result = self._makeOne({'prevent_http_cache':'t'}) self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'1'}) self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'pyramid.prevent_http_cache':'t'}) self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'false', 'pyramid.prevent_http_cache':'1'}) self.assertEqual(result['prevent_http_cache'], True) self.assertEqual(result['pyramid.prevent_http_cache'], True) result = self._makeOne({'prevent_http_cache':'false', 'pyramid.prevent_http_cache':'f'}, {'PYRAMID_PREVENT_HTTP_CACHE':'1'}) 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) self.assertEqual(settings['pyramid.reload_templates'], False) result = self._makeOne({'reload_templates':'false'}) self.assertEqual(result['reload_templates'], False) self.assertEqual(result['pyramid.reload_templates'], False) result = self._makeOne({'reload_templates':'t'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'pyramid.reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'false', 'pyramid.reload_templates':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) result = self._makeOne({'reload_templates':'false'}, {'PYRAMID_RELOAD_TEMPLATES':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['pyramid.reload_templates'], True) def test_reload_resources(self): # alias for reload_assets result = self._makeOne({}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['pyramid.reload_resources'], False) self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_resources':'false'}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['pyramid.reload_resources'], False) self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_resources':'t'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'pyramid.reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'false', 'pyramid.reload_resources':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_resources':'false', 'pyramid.reload_resources':'false'}, {'PYRAMID_RELOAD_RESOURCES':'1'}) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) def test_reload_assets(self): # alias for reload_resources result = self._makeOne({}) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['pyramid.reload_assets'], False) self.assertEqual(result['pyramid.reload_resources'], False) result = self._makeOne({'reload_assets':'false'}) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['pyramid.reload_assets'], False) self.assertEqual(result['pyramid.reload_resources'], False) result = self._makeOne({'reload_assets':'t'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'pyramid.reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'false', 'pyramid.reload_assets':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) result = self._makeOne({'reload_assets':'false', 'pyramid.reload_assets':'false'}, {'PYRAMID_RELOAD_ASSETS':'1'}) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) self.assertEqual(result['pyramid.reload_resources'], True) def test_reload_all(self): result = self._makeOne({}) self.assertEqual(result['reload_templates'], False) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['pyramid.reload_templates'], False) self.assertEqual(result['pyramid.reload_resources'], False) self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_all':'false'}) self.assertEqual(result['reload_templates'], False) self.assertEqual(result['reload_resources'], False) self.assertEqual(result['reload_assets'], False) self.assertEqual(result['pyramid.reload_templates'], False) self.assertEqual(result['pyramid.reload_resources'], False) self.assertEqual(result['pyramid.reload_assets'], False) result = self._makeOne({'reload_all':'t'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'pyramid.reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({}, {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'false', 'pyramid.reload_all':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) result = self._makeOne({'reload_all':'false', 'pyramid.reload_all':'false'}, {'PYRAMID_RELOAD_ALL':'1'}) self.assertEqual(result['reload_templates'], True) self.assertEqual(result['reload_resources'], True) self.assertEqual(result['reload_assets'], True) self.assertEqual(result['pyramid.reload_templates'], True) self.assertEqual(result['pyramid.reload_resources'], True) self.assertEqual(result['pyramid.reload_assets'], True) def test_debug_authorization(self): result = self._makeOne({}) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['pyramid.debug_authorization'], False) result = self._makeOne({'debug_authorization':'false'}) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['pyramid.debug_authorization'], False) result = self._makeOne({'debug_authorization':'t'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'pyramid.debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'false', 'pyramid.debug_authorization':'1'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) result = self._makeOne({'debug_authorization':'false', 'pyramid.debug_authorization':'false'}, {'PYRAMID_DEBUG_AUTHORIZATION':'1'}) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['pyramid.debug_authorization'], True) def test_debug_notfound(self): result = self._makeOne({}) self.assertEqual(result['debug_notfound'], False) self.assertEqual(result['pyramid.debug_notfound'], False) result = self._makeOne({'debug_notfound':'false'}) self.assertEqual(result['debug_notfound'], False) self.assertEqual(result['pyramid.debug_notfound'], False) result = self._makeOne({'debug_notfound':'t'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'pyramid.debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'false', 'pyramid.debug_notfound':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) result = self._makeOne({'debug_notfound':'false', 'pyramid.debug_notfound':'false'}, {'PYRAMID_DEBUG_NOTFOUND':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['pyramid.debug_notfound'], True) def test_debug_routematch(self): result = self._makeOne({}) self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['pyramid.debug_routematch'], False) result = self._makeOne({'debug_routematch':'false'}) self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['pyramid.debug_routematch'], False) result = self._makeOne({'debug_routematch':'t'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'pyramid.debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'false', 'pyramid.debug_routematch':'1'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) result = self._makeOne({'debug_routematch':'false', 'pyramid.debug_routematch':'false'}, {'PYRAMID_DEBUG_ROUTEMATCH':'1'}) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['pyramid.debug_routematch'], True) def test_debug_templates(self): result = self._makeOne({}) self.assertEqual(result['debug_templates'], False) self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_templates':'false'}) self.assertEqual(result['debug_templates'], False) self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_templates':'t'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'pyramid.debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'false', 'pyramid.debug_templates':'1'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_templates':'false', 'pyramid.debug_templates':'false'}, {'PYRAMID_DEBUG_TEMPLATES':'1'}) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_templates'], True) def test_debug_all(self): result = self._makeOne({}) self.assertEqual(result['debug_notfound'], False) self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['debug_templates'], False) self.assertEqual(result['pyramid.debug_notfound'], False) self.assertEqual(result['pyramid.debug_routematch'], False) self.assertEqual(result['pyramid.debug_authorization'], False) self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_all':'false'}) self.assertEqual(result['debug_notfound'], False) self.assertEqual(result['debug_routematch'], False) self.assertEqual(result['debug_authorization'], False) self.assertEqual(result['debug_templates'], False) self.assertEqual(result['pyramid.debug_notfound'], False) self.assertEqual(result['pyramid.debug_routematch'], False) self.assertEqual(result['pyramid.debug_authorization'], False) self.assertEqual(result['pyramid.debug_templates'], False) result = self._makeOne({'debug_all':'t'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'pyramid.debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({}, {'PYRAMID_DEBUG_ALL':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'false', 'pyramid.debug_all':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) result = self._makeOne({'debug_all':'false', 'pyramid.debug_all':'false'}, {'PYRAMID_DEBUG_ALL':'1'}) self.assertEqual(result['debug_notfound'], True) self.assertEqual(result['debug_routematch'], True) self.assertEqual(result['debug_authorization'], True) self.assertEqual(result['debug_templates'], True) self.assertEqual(result['pyramid.debug_notfound'], True) self.assertEqual(result['pyramid.debug_routematch'], True) self.assertEqual(result['pyramid.debug_authorization'], True) self.assertEqual(result['pyramid.debug_templates'], True) def test_default_locale_name(self): result = self._makeOne({}) self.assertEqual(result['default_locale_name'], 'en') self.assertEqual(result['pyramid.default_locale_name'], 'en') result = self._makeOne({'default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'pyramid.default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({}, {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'default_locale_name':'def', 'pyramid.default_locale_name':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') self.assertEqual(result['pyramid.default_locale_name'], 'abc') result = self._makeOne({'default_locale_name':'def', 'pyramid.default_locale_name':'ghi'}, {'PYRAMID_DEFAULT_LOCALE_NAME':'abc'}) self.assertEqual(result['default_locale_name'], 'abc') self.assertEqual(result['pyramid.default_locale_name'], 'abc') def test_originals_kept(self): result = self._makeOne({'a':'i am so a'}) self.assertEqual(result['a'], 'i am so a') pyramid-1.6/pyramid/tests/test_config/test_testing.py0000644000076500000240000002062212520062551023753 0ustar michaelstaff00000000000000import unittest from pyramid.compat import text_ from pyramid.security import AuthenticationAPIMixin, AuthorizationAPIMixin from pyramid.tests.test_config import IDummy class TestingConfiguratorMixinTests(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_testing_securitypolicy(self): from pyramid.testing import DummySecurityPolicy config = self._makeOne(autocommit=True) config.testing_securitypolicy('user', ('group1', 'group2'), permissive=False) from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy ut = config.registry.getUtility(IAuthenticationPolicy) self.assertTrue(isinstance(ut, DummySecurityPolicy)) ut = config.registry.getUtility(IAuthorizationPolicy) self.assertEqual(ut.userid, 'user') self.assertEqual(ut.groupids, ('group1', 'group2')) self.assertEqual(ut.permissive, False) def test_testing_securitypolicy_remember_result(self): from pyramid.security import remember config = self._makeOne(autocommit=True) pol = config.testing_securitypolicy( 'user', ('group1', 'group2'), permissive=False, remember_result=True) request = DummyRequest() request.registry = config.registry val = remember(request, 'fred') self.assertEqual(pol.remembered, 'fred') self.assertEqual(val, True) def test_testing_securitypolicy_forget_result(self): from pyramid.security import forget config = self._makeOne(autocommit=True) pol = config.testing_securitypolicy( 'user', ('group1', 'group2'), permissive=False, forget_result=True) request = DummyRequest() request.registry = config.registry val = forget(request) self.assertEqual(pol.forgotten, True) self.assertEqual(val, True) def test_testing_resources(self): from pyramid.traversal import find_resource from pyramid.interfaces import ITraverser ob1 = object() ob2 = object() resources = {'/ob1':ob1, '/ob2':ob2} config = self._makeOne(autocommit=True) config.testing_resources(resources) adapter = config.registry.getAdapter(None, ITraverser) result = adapter(DummyRequest({'PATH_INFO':'/ob1'})) self.assertEqual(result['context'], ob1) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob1'),)) self.assertEqual(result['virtual_root'], ob1) self.assertEqual(result['virtual_root_path'], ()) result = adapter(DummyRequest({'PATH_INFO':'/ob2'})) self.assertEqual(result['context'], ob2) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('ob2'),)) self.assertEqual(result['virtual_root'], ob2) self.assertEqual(result['virtual_root_path'], ()) self.assertRaises(KeyError, adapter, DummyRequest({'PATH_INFO':'/ob3'})) try: config.begin() self.assertEqual(find_resource(None, '/ob1'), ob1) finally: config.end() def test_testing_add_subscriber_single(self): config = self._makeOne(autocommit=True) L = config.testing_add_subscriber(IDummy) event = DummyEvent() config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 1) def test_testing_add_subscriber_dottedname(self): config = self._makeOne(autocommit=True) L = config.testing_add_subscriber( 'pyramid.tests.test_config.test_init.IDummy') event = DummyEvent() config.registry.notify(event) self.assertEqual(len(L), 1) self.assertEqual(L[0], event) config.registry.notify(object()) self.assertEqual(len(L), 1) def test_testing_add_subscriber_multiple(self): from zope.interface import Interface config = self._makeOne(autocommit=True) L = config.testing_add_subscriber((Interface, IDummy)) event = DummyEvent() event.object = 'foo' # the below is the equivalent of z.c.event.objectEventNotify(event) config.registry.subscribers((event.object, event), None) self.assertEqual(len(L), 2) self.assertEqual(L[0], 'foo') self.assertEqual(L[1], event) def test_testing_add_subscriber_defaults(self): config = self._makeOne(autocommit=True) L = config.testing_add_subscriber() event = object() config.registry.notify(event) self.assertEqual(L[-1], event) event2 = object() config.registry.notify(event2) self.assertEqual(L[-1], event2) def test_testing_add_renderer(self): config = self._makeOne(autocommit=True) renderer = config.testing_add_renderer('templates/foo.pt') from pyramid.testing import DummyTemplateRenderer self.assertTrue(isinstance(renderer, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() request.registry = config.registry render_to_response( 'templates/foo.pt', {'foo':1, 'bar':2}, request=request) renderer.assert_(foo=1) renderer.assert_(bar=2) renderer.assert_(request=request) def test_testing_add_renderer_twice(self): config = self._makeOne(autocommit=True) renderer1 = config.testing_add_renderer('templates/foo.pt') renderer2 = config.testing_add_renderer('templates/bar.pt') from pyramid.testing import DummyTemplateRenderer self.assertTrue(isinstance(renderer1, DummyTemplateRenderer)) self.assertTrue(isinstance(renderer2, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() request.registry = config.registry render_to_response( 'templates/foo.pt', {'foo':1, 'bar':2}, request=request) renderer1.assert_(foo=1) renderer1.assert_(bar=2) renderer1.assert_(request=request) render_to_response( 'templates/bar.pt', {'foo':1, 'bar':2}, request=request) renderer2.assert_(foo=1) renderer2.assert_(bar=2) renderer2.assert_(request=request) def test_testing_add_renderer_explicitrenderer(self): config = self._makeOne(autocommit=True) class E(Exception): pass def renderer(kw, system): self.assertEqual(kw, {'foo':1, 'bar':2}) raise E renderer = config.testing_add_renderer('templates/foo.pt', renderer) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() request.registry = config.registry try: render_to_response( 'templates/foo.pt', {'foo':1, 'bar':2}, request=request) except E: pass else: # pragma: no cover raise AssertionError def test_testing_add_template(self): config = self._makeOne(autocommit=True) renderer = config.testing_add_template('templates/foo.pt') from pyramid.testing import DummyTemplateRenderer self.assertTrue(isinstance(renderer, DummyTemplateRenderer)) from pyramid.renderers import render_to_response # must provide request to pass in registry (this is a functest) request = DummyRequest() request.registry = config.registry render_to_response('templates/foo.pt', dict(foo=1, bar=2), request=request) renderer.assert_(foo=1) renderer.assert_(bar=2) renderer.assert_(request=request) from zope.interface import implementer @implementer(IDummy) class DummyEvent: pass class DummyRequest(AuthenticationAPIMixin, AuthorizationAPIMixin): def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ pyramid-1.6/pyramid/tests/test_config/test_tweens.py0000644000076500000240000004163012517346416023620 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_config import dummy_tween_factory from pyramid.tests.test_config import dummy_tween_factory2 from pyramid.exceptions import ConfigurationConflictError class TestTweensConfiguratorMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_add_tweens_names_distinct(self): from pyramid.interfaces import ITweens from pyramid.tweens import excview_tween_factory def factory1(handler, registry): return handler def factory2(handler, registry): return handler config = self._makeOne() config.add_tween( 'pyramid.tests.test_config.dummy_tween_factory') config.add_tween( 'pyramid.tests.test_config.dummy_tween_factory2') config.commit() tweens = config.registry.queryUtility(ITweens) implicit = tweens.implicit() self.assertEqual( implicit, [ ('pyramid.tests.test_config.dummy_tween_factory2', dummy_tween_factory2), ('pyramid.tests.test_config.dummy_tween_factory', dummy_tween_factory), ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ] ) def test_add_tweens_names_with_underover(self): from pyramid.interfaces import ITweens from pyramid.tweens import excview_tween_factory from pyramid.tweens import MAIN config = self._makeOne() config.add_tween( 'pyramid.tests.test_config.dummy_tween_factory', over=MAIN) config.add_tween( 'pyramid.tests.test_config.dummy_tween_factory2', over=MAIN, under='pyramid.tests.test_config.dummy_tween_factory') config.commit() tweens = config.registry.queryUtility(ITweens) implicit = tweens.implicit() self.assertEqual( implicit, [ ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ('pyramid.tests.test_config.dummy_tween_factory', dummy_tween_factory), ('pyramid.tests.test_config.dummy_tween_factory2', dummy_tween_factory2), ]) def test_add_tweens_names_with_under_nonstringoriter(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', under=False) def test_add_tweens_names_with_over_nonstringoriter(self): from pyramid.exceptions import ConfigurationError config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', over=False) def test_add_tween_dottedname(self): from pyramid.interfaces import ITweens from pyramid.tweens import excview_tween_factory config = self._makeOne() config.add_tween('pyramid.tests.test_config.dummy_tween_factory') config.commit() tweens = config.registry.queryUtility(ITweens) self.assertEqual( tweens.implicit(), [ ('pyramid.tests.test_config.dummy_tween_factory', dummy_tween_factory), ('pyramid.tweens.excview_tween_factory', excview_tween_factory), ]) def test_add_tween_instance(self): from pyramid.exceptions import ConfigurationError class ATween(object): pass atween = ATween() config = self._makeOne() self.assertRaises(ConfigurationError, config.add_tween, atween) def test_add_tween_unsuitable(self): from pyramid.exceptions import ConfigurationError import pyramid.tests.test_config config = self._makeOne() self.assertRaises(ConfigurationError, config.add_tween, pyramid.tests.test_config) def test_add_tween_name_ingress(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import INGRESS config = self._makeOne() self.assertRaises(ConfigurationError, config.add_tween, INGRESS) def test_add_tween_name_main(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN config = self._makeOne() self.assertRaises(ConfigurationError, config.add_tween, MAIN) def test_add_tweens_conflict(self): config = self._makeOne() config.add_tween('pyramid.tests.test_config.dummy_tween_factory') config.add_tween('pyramid.tests.test_config.dummy_tween_factory') self.assertRaises(ConfigurationConflictError, config.commit) def test_add_tween_over_ingress(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import INGRESS config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', over=INGRESS) def test_add_tween_over_ingress_iterable(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import INGRESS config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', over=('a', INGRESS)) def test_add_tween_under_main(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', under=MAIN) def test_add_tween_under_main_iterable(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN config = self._makeOne() self.assertRaises( ConfigurationError, config.add_tween, 'pyramid.tests.test_config.dummy_tween_factory', under=('a', MAIN)) class TestTweens(unittest.TestCase): def _makeOne(self): from pyramid.config.tweens import Tweens return Tweens() def test_add_explicit(self): tweens = self._makeOne() tweens.add_explicit('name', 'factory') self.assertEqual(tweens.explicit, [('name', 'factory')]) tweens.add_explicit('name2', 'factory2') self.assertEqual(tweens.explicit, [('name', 'factory'), ('name2', 'factory2')]) def test_add_implicit(self): tweens = self._makeOne() tweens.add_implicit('name', 'factory') tweens.add_implicit('name2', 'factory2') self.assertEqual(tweens.sorter.sorted(), [('name2', 'factory2'), ('name', 'factory')]) def test___call___explicit(self): tweens = self._makeOne() def factory1(handler, registry): return handler def factory2(handler, registry): return '123' tweens.explicit = [('name', factory1), ('name', factory2)] self.assertEqual(tweens(None, None), '123') def test___call___implicit(self): tweens = self._makeOne() def factory1(handler, registry): return handler def factory2(handler, registry): return '123' tweens.add_implicit('name2', factory2) tweens.add_implicit('name1', factory1) self.assertEqual(tweens(None, None), '123') def test_implicit_ordering_1(self): tweens = self._makeOne() tweens.add_implicit('name1', 'factory1') tweens.add_implicit('name2', 'factory2') self.assertEqual(tweens.implicit(), [ ('name2', 'factory2'), ('name1', 'factory1'), ]) def test_implicit_ordering_2(self): from pyramid.tweens import MAIN tweens = self._makeOne() tweens.add_implicit('name1', 'factory1') tweens.add_implicit('name2', 'factory2', over=MAIN) self.assertEqual(tweens.implicit(), [ ('name1', 'factory1'), ('name2', 'factory2'), ]) def test_implicit_ordering_3(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('auth', 'auth_factory', under='browserid') add('dbt', 'dbt_factory') add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') add('txnmgr', 'txnmgr_factory', under='exceptionview') add('exceptionview', 'excview_factory', over=MAIN) self.assertEqual(tweens.implicit(), [ ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('dbt', 'dbt_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ]) def test_implicit_ordering_4(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('auth', 'auth_factory', under='browserid') add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') add('txnmgr', 'txnmgr_factory', under='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [ ('dbt', 'dbt_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ]) def test_implicit_ordering_5(self): from pyramid.tweens import MAIN, INGRESS tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('auth', 'auth_factory', under=INGRESS) add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory', under=INGRESS) add('txnmgr', 'txnmgr_factory', under='exceptionview', over=MAIN) add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [ ('dbt', 'dbt_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ]) def test_implicit_ordering_missing_over_partial(self): from pyramid.exceptions import ConfigurationError tweens = self._makeOne() add = tweens.add_implicit add('dbt', 'dbt_factory') add('auth', 'auth_factory', under='browserid') add('retry', 'retry_factory', over='txnmgr', under='exceptionview') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, tweens.implicit) def test_implicit_ordering_missing_under_partial(self): from pyramid.exceptions import ConfigurationError tweens = self._makeOne() add = tweens.add_implicit add('dbt', 'dbt_factory') add('auth', 'auth_factory', under='txnmgr') add('retry', 'retry_factory', over='dbt', under='exceptionview') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, tweens.implicit) def test_implicit_ordering_missing_over_and_under_partials(self): from pyramid.exceptions import ConfigurationError tweens = self._makeOne() add = tweens.add_implicit add('dbt', 'dbt_factory') add('auth', 'auth_factory', under='browserid') add('retry', 'retry_factory', over='foo', under='txnmgr') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, tweens.implicit) def test_implicit_ordering_missing_over_partial_with_fallback(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('auth', 'auth_factory', under='browserid') add('retry', 'retry_factory', over=('txnmgr',MAIN), under='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [ ('dbt', 'dbt_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_implicit_ordering_missing_under_partial_with_fallback(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('auth', 'auth_factory', under=('txnmgr','browserid')) add('retry', 'retry_factory', under='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(tweens.implicit(), [ ('dbt', 'dbt_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_implicit_ordering_with_partial_fallbacks(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=('wontbethere', MAIN)) add('retry', 'retry_factory', under='exceptionview') add('browserid', 'browserid_factory', over=('wont2', 'exceptionview')) self.assertEqual(tweens.implicit(), [ ('browserid', 'browserid_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_implicit_ordering_with_multiple_matching_fallbacks(self): from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('retry', 'retry_factory', under='exceptionview') add('browserid', 'browserid_factory', over=('retry', 'exceptionview')) self.assertEqual(tweens.implicit(), [ ('browserid', 'browserid_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_implicit_ordering_with_missing_fallbacks(self): from pyramid.exceptions import ConfigurationError from pyramid.tweens import MAIN tweens = self._makeOne() add = tweens.add_implicit add('exceptionview', 'excview_factory', over=MAIN) add('retry', 'retry_factory', under='exceptionview') add('browserid', 'browserid_factory', over=('txnmgr', 'auth')) self.assertRaises(ConfigurationError, tweens.implicit) def test_implicit_ordering_conflict_direct(self): from pyramid.exceptions import CyclicDependencyError tweens = self._makeOne() add = tweens.add_implicit add('browserid', 'browserid_factory') add('auth', 'auth_factory', over='browserid', under='browserid') self.assertRaises(CyclicDependencyError, tweens.implicit) def test_implicit_ordering_conflict_indirect(self): from pyramid.exceptions import CyclicDependencyError tweens = self._makeOne() add = tweens.add_implicit add('browserid', 'browserid_factory') add('auth', 'auth_factory', over='browserid') add('dbt', 'dbt_factory', under='browserid', over='auth') self.assertRaises(CyclicDependencyError, tweens.implicit) pyramid-1.6/pyramid/tests/test_config/test_util.py0000644000076500000240000005330412524266531023266 0ustar michaelstaff00000000000000import unittest from pyramid.compat import text_ class TestPredicateList(unittest.TestCase): def _makeOne(self): from pyramid.config.util import PredicateList from pyramid.config import predicates inst = PredicateList() for name, factory in ( ('xhr', predicates.XHRPredicate), ('request_method', predicates.RequestMethodPredicate), ('path_info', predicates.PathInfoPredicate), ('request_param', predicates.RequestParamPredicate), ('header', predicates.HeaderPredicate), ('accept', predicates.AcceptPredicate), ('containment', predicates.ContainmentPredicate), ('request_type', predicates.RequestTypePredicate), ('match_param', predicates.MatchParamPredicate), ('custom', predicates.CustomPredicate), ('traverse', predicates.TraversePredicate), ): inst.add(name, factory) return inst def _callFUT(self, **kw): inst = self._makeOne() config = DummyConfigurator() return inst.make(config, **kw) def test_ordering_xhr_and_request_method_trump_only_containment(self): order1, _, _ = self._callFUT(xhr=True, request_method='GET') order2, _, _ = self._callFUT(containment=True) self.assertTrue(order1 < order2) def test_ordering_number_of_predicates(self): from pyramid.config.util import predvalseq order1, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', accept='accept', containment='containment', request_type='request_type', custom=predvalseq([DummyCustomPredicate()]), ) order2, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', accept='accept', containment='containment', request_type='request_type', custom=predvalseq([DummyCustomPredicate()]), ) order3, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', accept='accept', containment='containment', request_type='request_type', ) order4, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', accept='accept', containment='containment', ) order5, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', accept='accept', ) order6, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', header='header', ) order7, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', match_param='foo=bar', ) order8, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', ) order9, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', ) order10, _, _ = self._callFUT( xhr='xhr', request_method='request_method', ) order11, _, _ = self._callFUT( xhr='xhr', ) order12, _, _ = self._callFUT( ) self.assertEqual(order1, order2) self.assertTrue(order3 > order2) self.assertTrue(order4 > order3) self.assertTrue(order5 > order4) self.assertTrue(order6 > order5) self.assertTrue(order7 > order6) self.assertTrue(order8 > order7) self.assertTrue(order9 > order8) self.assertTrue(order10 > order9) self.assertTrue(order11 > order10) self.assertTrue(order12 > order10) def test_ordering_importance_of_predicates(self): from pyramid.config.util import predvalseq order1, _, _ = self._callFUT( xhr='xhr', ) order2, _, _ = self._callFUT( request_method='request_method', ) order3, _, _ = self._callFUT( path_info='path_info', ) order4, _, _ = self._callFUT( request_param='param', ) order5, _, _ = self._callFUT( header='header', ) order6, _, _ = self._callFUT( accept='accept', ) order7, _, _ = self._callFUT( containment='containment', ) order8, _, _ = self._callFUT( request_type='request_type', ) order9, _, _ = self._callFUT( match_param='foo=bar', ) order10, _, _ = self._callFUT( custom=predvalseq([DummyCustomPredicate()]), ) self.assertTrue(order1 > order2) self.assertTrue(order2 > order3) self.assertTrue(order3 > order4) self.assertTrue(order4 > order5) self.assertTrue(order5 > order6) self.assertTrue(order6 > order7) self.assertTrue(order7 > order8) self.assertTrue(order8 > order9) self.assertTrue(order9 > order10) def test_ordering_importance_and_number(self): from pyramid.config.util import predvalseq order1, _, _ = self._callFUT( xhr='xhr', request_method='request_method', ) order2, _, _ = self._callFUT( custom=predvalseq([DummyCustomPredicate()]), ) self.assertTrue(order1 < order2) order1, _, _ = self._callFUT( xhr='xhr', request_method='request_method', ) order2, _, _ = self._callFUT( request_method='request_method', custom=predvalseq([DummyCustomPredicate()]), ) self.assertTrue(order1 > order2) order1, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', ) order2, _, _ = self._callFUT( request_method='request_method', custom=predvalseq([DummyCustomPredicate()]), ) self.assertTrue(order1 < order2) order1, _, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', ) order2, _, _ = self._callFUT( xhr='xhr', request_method='request_method', custom=predvalseq([DummyCustomPredicate()]), ) self.assertTrue(order1 > order2) def test_different_custom_predicates_with_same_hash(self): from pyramid.config.util import predvalseq class PredicateWithHash(object): def __hash__(self): return 1 a = PredicateWithHash() b = PredicateWithHash() _, _, a_phash = self._callFUT(custom=predvalseq([a])) _, _, b_phash = self._callFUT(custom=predvalseq([b])) self.assertEqual(a_phash, b_phash) def test_traverse_has_remainder_already(self): order, predicates, phash = self._callFUT(traverse='/1/:a/:b') self.assertEqual(len(predicates), 1) pred = predicates[0] info = {'traverse':'abc'} request = DummyRequest() result = pred(info, request) self.assertEqual(result, True) self.assertEqual(info, {'traverse':'abc'}) def test_traverse_matches(self): order, predicates, phash = self._callFUT(traverse='/1/:a/:b') self.assertEqual(len(predicates), 1) pred = predicates[0] info = {'match':{'a':'a', 'b':'b'}} request = DummyRequest() result = pred(info, request) self.assertEqual(result, True) self.assertEqual(info, {'match': {'a':'a', 'b':'b', 'traverse':('1', 'a', 'b')}}) def test_traverse_matches_with_highorder_chars(self): order, predicates, phash = self._callFUT( traverse=text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8')) self.assertEqual(len(predicates), 1) pred = predicates[0] info = {'match':{'x':text_(b'Qu\xc3\xa9bec', 'utf-8')}} request = DummyRequest() result = pred(info, request) self.assertEqual(result, True) self.assertEqual( info['match']['traverse'], (text_(b'La Pe\xc3\xb1a', 'utf-8'), text_(b'Qu\xc3\xa9bec', 'utf-8')) ) def test_custom_predicates_can_affect_traversal(self): from pyramid.config.util import predvalseq def custom(info, request): m = info['match'] m['dummy'] = 'foo' return True _, predicates, _ = self._callFUT( custom=predvalseq([custom]), traverse='/1/:dummy/:a') self.assertEqual(len(predicates), 2) info = {'match':{'a':'a'}} request = DummyRequest() self.assertTrue(all([p(info, request) for p in predicates])) self.assertEqual(info, {'match': {'a':'a', 'dummy':'foo', 'traverse':('1', 'foo', 'a')}}) def test_predicate_text_is_correct(self): from pyramid.config.util import predvalseq _, predicates, _ = self._callFUT( xhr='xhr', request_method='request_method', path_info='path_info', request_param='param', header='header', accept='accept', containment='containment', request_type='request_type', custom=predvalseq( [ DummyCustomPredicate(), DummyCustomPredicate.classmethod_predicate, DummyCustomPredicate.classmethod_predicate_no_text, ] ), match_param='foo=bar') self.assertEqual(predicates[0].text(), 'xhr = True') self.assertEqual(predicates[1].text(), "request_method = request_method") self.assertEqual(predicates[2].text(), 'path_info = path_info') self.assertEqual(predicates[3].text(), 'request_param param') self.assertEqual(predicates[4].text(), 'header header') self.assertEqual(predicates[5].text(), 'accept = accept') self.assertEqual(predicates[6].text(), 'containment = containment') self.assertEqual(predicates[7].text(), 'request_type = request_type') self.assertEqual(predicates[8].text(), "match_param foo=bar") self.assertEqual(predicates[9].text(), 'custom predicate') self.assertEqual(predicates[10].text(), 'classmethod predicate') self.assertTrue(predicates[11].text().startswith('custom predicate')) def test_match_param_from_string(self): _, predicates, _ = self._callFUT(match_param='foo=bar') request = DummyRequest() request.matchdict = {'foo':'bar', 'baz':'bum'} self.assertTrue(predicates[0](Dummy(), request)) def test_match_param_from_string_fails(self): _, predicates, _ = self._callFUT(match_param='foo=bar') request = DummyRequest() request.matchdict = {'foo':'bum', 'baz':'bum'} self.assertFalse(predicates[0](Dummy(), request)) def test_match_param_from_dict(self): _, predicates, _ = self._callFUT(match_param=('foo=bar','baz=bum')) request = DummyRequest() request.matchdict = {'foo':'bar', 'baz':'bum'} self.assertTrue(predicates[0](Dummy(), request)) def test_match_param_from_dict_fails(self): _, predicates, _ = self._callFUT(match_param=('foo=bar','baz=bum')) request = DummyRequest() request.matchdict = {'foo':'bar', 'baz':'foo'} self.assertFalse(predicates[0](Dummy(), request)) def test_request_method_sequence(self): _, predicates, _ = self._callFUT(request_method=('GET', 'HEAD')) request = DummyRequest() request.method = 'HEAD' self.assertTrue(predicates[0](Dummy(), request)) request.method = 'GET' self.assertTrue(predicates[0](Dummy(), request)) request.method = 'POST' self.assertFalse(predicates[0](Dummy(), request)) def test_request_method_ordering_hashes_same(self): hash1, _, __= self._callFUT(request_method=('GET', 'HEAD')) hash2, _, __= self._callFUT(request_method=('HEAD', 'GET')) self.assertEqual(hash1, hash2) hash1, _, __= self._callFUT(request_method=('GET',)) hash2, _, __= self._callFUT(request_method='GET') self.assertEqual(hash1, hash2) def test_unknown_predicate(self): from pyramid.exceptions import ConfigurationError self.assertRaises(ConfigurationError, self._callFUT, unknown=1) def test_notted(self): from pyramid.config import not_ from pyramid.testing import DummyRequest request = DummyRequest() _, predicates, _ = self._callFUT( xhr='xhr', request_method=not_('POST'), header=not_('header'), ) self.assertEqual(predicates[0].text(), 'xhr = True') self.assertEqual(predicates[1].text(), "!request_method = POST") self.assertEqual(predicates[2].text(), '!header header') self.assertEqual(predicates[1](None, request), True) self.assertEqual(predicates[2](None, request), True) class Test_takes_one_arg(unittest.TestCase): def _callFUT(self, view, attr=None, argname=None): from pyramid.config.util import takes_one_arg return takes_one_arg(view, attr=attr, argname=argname) def test_requestonly_newstyle_class_no_init(self): class foo(object): """ """ self.assertFalse(self._callFUT(foo)) def test_requestonly_newstyle_class_init_toomanyargs(self): class foo(object): def __init__(self, context, request): """ """ self.assertFalse(self._callFUT(foo)) def test_requestonly_newstyle_class_init_onearg_named_request(self): class foo(object): def __init__(self, request): """ """ self.assertTrue(self._callFUT(foo)) def test_newstyle_class_init_onearg_named_somethingelse(self): class foo(object): def __init__(self, req): """ """ self.assertTrue(self._callFUT(foo)) def test_newstyle_class_init_defaultargs_firstname_not_request(self): class foo(object): def __init__(self, context, request=None): """ """ self.assertFalse(self._callFUT(foo)) def test_newstyle_class_init_defaultargs_firstname_request(self): class foo(object): def __init__(self, request, foo=1, bar=2): """ """ self.assertTrue(self._callFUT(foo, argname='request')) def test_newstyle_class_init_firstname_request_with_secondname(self): class foo(object): def __init__(self, request, two): """ """ self.assertFalse(self._callFUT(foo)) def test_newstyle_class_init_noargs(self): class foo(object): def __init__(): """ """ self.assertFalse(self._callFUT(foo)) def test_oldstyle_class_no_init(self): class foo: """ """ self.assertFalse(self._callFUT(foo)) def test_oldstyle_class_init_toomanyargs(self): class foo: def __init__(self, context, request): """ """ self.assertFalse(self._callFUT(foo)) def test_oldstyle_class_init_onearg_named_request(self): class foo: def __init__(self, request): """ """ self.assertTrue(self._callFUT(foo)) def test_oldstyle_class_init_onearg_named_somethingelse(self): class foo: def __init__(self, req): """ """ self.assertTrue(self._callFUT(foo)) def test_oldstyle_class_init_defaultargs_firstname_not_request(self): class foo: def __init__(self, context, request=None): """ """ self.assertFalse(self._callFUT(foo)) def test_oldstyle_class_init_defaultargs_firstname_request(self): class foo: def __init__(self, request, foo=1, bar=2): """ """ self.assertTrue(self._callFUT(foo, argname='request'), True) def test_oldstyle_class_init_noargs(self): class foo: def __init__(): """ """ self.assertFalse(self._callFUT(foo)) def test_function_toomanyargs(self): def foo(context, request): """ """ self.assertFalse(self._callFUT(foo)) def test_function_with_attr_false(self): def bar(context, request): """ """ def foo(context, request): """ """ foo.bar = bar self.assertFalse(self._callFUT(foo, 'bar')) def test_function_with_attr_true(self): def bar(context, request): """ """ def foo(request): """ """ foo.bar = bar self.assertTrue(self._callFUT(foo, 'bar')) def test_function_onearg_named_request(self): def foo(request): """ """ self.assertTrue(self._callFUT(foo)) def test_function_onearg_named_somethingelse(self): def foo(req): """ """ self.assertTrue(self._callFUT(foo)) def test_function_defaultargs_firstname_not_request(self): def foo(context, request=None): """ """ self.assertFalse(self._callFUT(foo)) def test_function_defaultargs_firstname_request(self): def foo(request, foo=1, bar=2): """ """ self.assertTrue(self._callFUT(foo, argname='request')) def test_function_noargs(self): def foo(): """ """ self.assertFalse(self._callFUT(foo)) def test_instance_toomanyargs(self): class Foo: def __call__(self, context, request): """ """ foo = Foo() self.assertFalse(self._callFUT(foo)) def test_instance_defaultargs_onearg_named_request(self): class Foo: def __call__(self, request): """ """ foo = Foo() self.assertTrue(self._callFUT(foo)) def test_instance_defaultargs_onearg_named_somethingelse(self): class Foo: def __call__(self, req): """ """ foo = Foo() self.assertTrue(self._callFUT(foo)) def test_instance_defaultargs_firstname_not_request(self): class Foo: def __call__(self, context, request=None): """ """ foo = Foo() self.assertFalse(self._callFUT(foo)) def test_instance_defaultargs_firstname_request(self): class Foo: def __call__(self, request, foo=1, bar=2): """ """ foo = Foo() self.assertTrue(self._callFUT(foo, argname='request'), True) def test_instance_nocall(self): class Foo: pass foo = Foo() self.assertFalse(self._callFUT(foo)) def test_method_onearg_named_request(self): class Foo: def method(self, request): """ """ foo = Foo() self.assertTrue(self._callFUT(foo.method)) def test_function_annotations(self): def foo(bar): """ """ # avoid SyntaxErrors in python2, this if effectively nop getattr(foo, '__annotations__', {}).update({'bar': 'baz'}) self.assertTrue(self._callFUT(foo)) class TestNotted(unittest.TestCase): def _makeOne(self, predicate): from pyramid.config.util import Notted return Notted(predicate) def test_it_with_phash_val(self): pred = DummyPredicate('val') inst = self._makeOne(pred) self.assertEqual(inst.text(), '!val') self.assertEqual(inst.phash(), '!val') self.assertEqual(inst(None, None), False) def test_it_without_phash_val(self): pred = DummyPredicate('') inst = self._makeOne(pred) self.assertEqual(inst.text(), '') self.assertEqual(inst.phash(), '') self.assertEqual(inst(None, None), True) class DummyPredicate(object): def __init__(self, result): self.result = result def text(self): return self.result phash = text def __call__(self, context, request): return True class DummyCustomPredicate(object): def __init__(self): self.__text__ = 'custom predicate' def classmethod_predicate(*args): pass classmethod_predicate.__text__ = 'classmethod predicate' classmethod_predicate = classmethod(classmethod_predicate) @classmethod def classmethod_predicate_no_text(*args): pass # pragma: no cover class Dummy: pass class DummyRequest: subpath = () matchdict = None def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ self.params = {} self.cookies = {} class DummyConfigurator(object): def maybe_dotted(self, thing): return thing pyramid-1.6/pyramid/tests/test_config/test_views.py0000644000076500000240000053763712634703652023470 0ustar michaelstaff00000000000000import os import unittest from pyramid import testing from pyramid.tests.test_config import IDummy from pyramid.tests.test_config import dummy_view from pyramid.compat import ( im_func, text_, ) from pyramid.exceptions import ConfigurationError from pyramid.exceptions import ConfigurationExecutionError from pyramid.exceptions import ConfigurationConflictError class TestViewsConfigurationMixin(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def _getViewCallable(self, config, ctx_iface=None, request_iface=None, name='', exception_view=False): from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier if exception_view: classifier = IExceptionViewClassifier else: classifier = IViewClassifier if ctx_iface is None: ctx_iface = Interface if request_iface is None: request_iface = IRequest return config.registry.adapters.lookup( (classifier, request_iface, ctx_iface), IView, name=name, default=None) def _registerRenderer(self, config, name='.txt'): from pyramid.interfaces import IRendererFactory class Renderer: def __init__(self, info): self.__class__.info = info def __call__(self, *arg): return b'Hello!' config.registry.registerUtility(Renderer, IRendererFactory, name=name) return Renderer def _makeRequest(self, config): request = DummyRequest() request.registry = config.registry return request def _assertNotFound(self, wrapper, *arg): from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, wrapper, *arg) def _getRouteRequestIface(self, config, name): from pyramid.interfaces import IRouteRequest iface = config.registry.getUtility(IRouteRequest, name) return iface def _assertRoute(self, config, name, path, num_predicates=0): from pyramid.interfaces import IRoutesMapper mapper = config.registry.getUtility(IRoutesMapper) routes = mapper.get_routes() route = routes[0] self.assertEqual(len(routes), 1) self.assertEqual(route.name, name) self.assertEqual(route.path, path) self.assertEqual(len(routes[0].predicates), num_predicates) return route def test_add_view_view_callable_None_no_renderer(self): config = self._makeOne(autocommit=True) self.assertRaises(ConfigurationError, config.add_view) def test_add_view_with_request_type_and_route_name(self): config = self._makeOne(autocommit=True) view = lambda *arg: 'OK' self.assertRaises(ConfigurationError, config.add_view, view, '', None, None, True, True) def test_add_view_with_request_type(self): from pyramid.renderers import null_renderer from zope.interface import directlyProvides from pyramid.interfaces import IRequest view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_type='pyramid.interfaces.IRequest', renderer=null_renderer) wrapper = self._getViewCallable(config) request = DummyRequest() self._assertNotFound(wrapper, None, request) directlyProvides(request, IRequest) result = wrapper(None, request) self.assertEqual(result, 'OK') def test_add_view_view_callable_None_with_renderer(self): config = self._makeOne(autocommit=True) self._registerRenderer(config, name='dummy') config.add_view(renderer='dummy') view = self._getViewCallable(config) self.assertTrue(b'Hello!' in view(None, None).body) def test_add_view_with_tmpl_renderer_factory_introspector_missing(self): config = self._makeOne(autocommit=True) config.introspection = False config.introspector = None 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() config.introspector = introspector config.add_view(renderer='dummy.pt') self.assertFalse(('renderer factories', '.pt') in introspector.related[-1]) view = self._getViewCallable(config) self.assertRaises(ValueError, view, None, None) def test_add_view_with_tmpl_renderer_factory_with_renderer_factory(self): config = self._makeOne(autocommit=True) introspector = DummyIntrospector(True) config.introspector = introspector def dummy_factory(helper): return lambda val, system_vals: 'Hello!' config.add_renderer('.pt', dummy_factory) config.add_view(renderer='dummy.pt') self.assertTrue( ('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 """ """ config = self._makeOne(autocommit=True) config.add_view(view=view) wrapper = self._getViewCallable(config) self.assertEqual(wrapper.__module__, view.__module__) self.assertEqual(wrapper.__name__, view.__name__) self.assertEqual(wrapper.__doc__, view.__doc__) self.assertEqual(wrapper.__discriminator__(None, None).resolve()[0], 'view') def test_add_view_view_callable_dottedname(self): from pyramid.renderers import null_renderer config = self._makeOne(autocommit=True) config.add_view(view='pyramid.tests.test_config.dummy_view', renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_function_callable(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_function_callable_requestonly(self): from pyramid.renderers import null_renderer def view(request): return 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_name(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, name='abc', renderer=null_renderer) wrapper = self._getViewCallable(config, name='abc') result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_name_unicode(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) name = text_(b'La Pe\xc3\xb1a', 'utf-8') config.add_view(view=view, name=name, renderer=null_renderer) wrapper = self._getViewCallable(config, name=name) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_decorator(self): from pyramid.renderers import null_renderer def view(request): """ ABC """ return 'OK' def view_wrapper(fn): def inner(context, request): return fn(context, request) return inner config = self._makeOne(autocommit=True) config.add_view(view=view, decorator=view_wrapper, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(wrapper is view) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_with_decorator_tuple(self): from pyramid.renderers import null_renderer def view(request): """ ABC """ return 'OK' def view_wrapper1(fn): def inner(context, request): return 'wrapped1' + fn(context, request) return inner def view_wrapper2(fn): def inner(context, request): return 'wrapped2' + fn(context, request) return inner config = self._makeOne(autocommit=True) config.add_view(view=view, decorator=(view_wrapper2, view_wrapper1), renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(wrapper is view) self.assertEqual(wrapper.__doc__, view.__doc__) result = wrapper(None, None) self.assertEqual(result, 'wrapped2wrapped1OK') def test_add_view_with_http_cache(self): import datetime from pyramid.response import Response response = Response('OK') def view(request): """ ABC """ return response config = self._makeOne(autocommit=True) config.add_view(view=view, http_cache=(86400, {'public':True})) wrapper = self._getViewCallable(config) self.assertFalse(wrapper is view) self.assertEqual(wrapper.__doc__, view.__doc__) request = testing.DummyRequest() when = datetime.datetime.utcnow() + datetime.timedelta(days=1) result = wrapper(None, request) self.assertEqual(result, response) headers = dict(response.headerlist) self.assertEqual(headers['Cache-Control'], 'max-age=86400, public') expires = parse_httpdate(headers['Expires']) assert_similar_datetime(expires, when) def test_add_view_as_instance(self): from pyramid.renderers import null_renderer class AView: def __call__(self, context, request): """ """ return 'OK' view = AView() config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_instancemethod(self): from pyramid.renderers import null_renderer class View: def index(self, context, request): return 'OK' view = View() config=self._makeOne(autocommit=True) config.add_view(view=view.index, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_instancemethod_requestonly(self): from pyramid.renderers import null_renderer class View: def index(self, request): return 'OK' view = View() config=self._makeOne(autocommit=True) config.add_view(view=view.index, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_instance_requestonly(self): from pyramid.renderers import null_renderer class AView: def __call__(self, request): """ """ return 'OK' view = AView() config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) result = wrapper(None, None) self.assertEqual(result, 'OK') def test_add_view_as_oldstyle_class(self): from pyramid.renderers import null_renderer class view: def __init__(self, context, request): self.context = context self.request = request def __call__(self): return 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) self.assertEqual(result, 'OK') self.assertEqual(request.__view__.__class__, view) def test_add_view_as_oldstyle_class_requestonly(self): from pyramid.renderers import null_renderer class view: def __init__(self, request): self.request = request def __call__(self): return 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) self.assertEqual(result, 'OK') self.assertEqual(request.__view__.__class__, view) def test_add_view_context_as_class(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy view = lambda *arg: 'OK' class Foo: pass config = self._makeOne(autocommit=True) config.add_view(context=Foo, view=view, renderer=null_renderer) foo = implementedBy(Foo) wrapper = self._getViewCallable(config, foo) self.assertEqual(wrapper, view) def test_add_view_context_as_iface(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(context=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_context_as_dottedname(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(context='pyramid.tests.test_config.IDummy', view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_for__as_dottedname(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(for_='pyramid.tests.test_config.IDummy', view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_for_as_class(self): # ``for_`` is older spelling for ``context`` from pyramid.renderers import null_renderer from zope.interface import implementedBy view = lambda *arg: 'OK' class Foo: pass config = self._makeOne(autocommit=True) config.add_view(for_=Foo, view=view, renderer=null_renderer) foo = implementedBy(Foo) wrapper = self._getViewCallable(config, foo) self.assertEqual(wrapper, view) def test_add_view_for_as_iface(self): # ``for_`` is older spelling for ``context`` from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(for_=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_context_trumps_for(self): # ``for_`` is older spelling for ``context`` from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) class Foo: pass config.add_view(context=IDummy, for_=Foo, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, IDummy) self.assertEqual(wrapper, view) def test_add_view_register_secured_view(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView from pyramid.interfaces import IViewClassifier view = lambda *arg: 'OK' view.__call_permissive__ = view config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) wrapper = config.registry.adapters.lookup( (IViewClassifier, IRequest, Interface), ISecuredView, name='', default=None) self.assertEqual(wrapper, view) def test_add_view_exception_register_secured_view(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IExceptionViewClassifier view = lambda *arg: 'OK' view.__call_permissive__ = view config = self._makeOne(autocommit=True) config.add_view(view=view, context=RuntimeError, renderer=null_renderer) wrapper = config.registry.adapters.lookup( (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='', default=None) self.assertEqual(wrapper, view) def test_add_view_same_phash_overrides_existing_single_view(self): from pyramid.renderers import null_renderer from hashlib import md5 from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView phash = md5() phash.update(b'xhr = True') view = lambda *arg: 'NOT OK' view.__phash__ = phash.hexdigest() config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, xhr=True, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_same_phash_overrides_existing_single_view(self): from pyramid.renderers import null_renderer from hashlib import md5 from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IMultiView phash = md5() phash.update(b'xhr = True') view = lambda *arg: 'NOT OK' view.__phash__ = phash.hexdigest() config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, xhr=True, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_default_phash_overrides_no_phash(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_default_phash_overrides_no_phash(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_default_phash_overrides_default_phash(self): from pyramid.renderers import null_renderer from pyramid.config.util import DEFAULT_PHASH from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'NOT OK' view.__phash__ = DEFAULT_PHASH config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_default_phash_overrides_default_phash(self): from pyramid.renderers import null_renderer from pyramid.config.util import DEFAULT_PHASH from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'NOT OK' view.__phash__ = DEFAULT_PHASH config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') def newview(context, request): return 'OK' config.add_view(view=newview, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertFalse(IMultiView.providedBy(wrapper)) request = DummyRequest() request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_multiview_replaces_existing_view(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'OK' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_view(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'OK' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.add_view(view=view, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_multiview_replaces_existing_securedview(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier view = lambda *arg: 'OK' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), ISecuredView, name='') config.add_view(view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_exc_multiview_replaces_existing_securedview(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import ISecuredView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier view = lambda *arg: 'OK' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, implementedBy(RuntimeError)), ISecuredView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), ISecuredView, name='') config.add_view(view=view, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_accept_multiview_replaces_existing_view(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier def view(context, request): return 'OK' def view2(context, request): return 'OK2' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view2, accept='text/html', renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK') request = DummyRequest() request.accept = DummyAccept('text/html', 'text/html') self.assertEqual(wrapper(None, request), 'OK2') def test_add_view_mixed_case_replaces_existing_view(self): from pyramid.renderers import null_renderer def view(context, request): return 'OK' def view2(context, request): return 'OK2' def view3(context, request): return 'OK3' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) config.add_view(view=view2, accept='text/html', renderer=null_renderer) config.add_view(view=view3, accept='text/HTML', renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.media_views.items()),1) self.assertFalse('text/HTML' in wrapper.media_views) self.assertEqual(wrapper(None, None), 'OK') request = DummyRequest() request.accept = DummyAccept('text/html', 'text/html') self.assertEqual(wrapper(None, request), 'OK3') def test_add_views_with_accept_multiview_replaces_existing(self): from pyramid.renderers import null_renderer def view(context, request): return 'OK' def view2(context, request): return 'OK2' def view3(context, request): return 'OK3' config = self._makeOne(autocommit=True) config.add_view(view=view, renderer=null_renderer) config.add_view(view=view2, accept='text/html', renderer=null_renderer) config.add_view(view=view3, accept='text/html', renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertEqual(len(wrapper.media_views['text/html']), 1) self.assertEqual(wrapper(None, None), 'OK') request = DummyRequest() request.accept = DummyAccept('text/html', 'text/html') self.assertEqual(wrapper(None, request), 'OK3') def test_add_view_exc_with_accept_multiview_replaces_existing_view(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier def view(context, request): return 'OK' def view2(context, request): return 'OK2' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.add_view(view=view2, accept='text/html', context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK') request = DummyRequest() request.accept = DummyAccept('text/html', 'text/html') self.assertEqual(wrapper(None, request), 'OK2') def test_add_view_multiview_replaces_existing_view_with___accept__(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier def view(context, request): return 'OK' def view2(context, request): return 'OK2' view.__accept__ = 'text/html' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view2, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK2') request = DummyRequest() request.accept = DummyAccept('text/html') self.assertEqual(wrapper(None, request), 'OK') def test_add_view_exc_mulview_replaces_existing_view_with___accept__(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier def view(context, request): return 'OK' def view2(context, request): return 'OK2' view.__accept__ = 'text/html' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IView, name='') config.add_view(view=view2, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual(len(wrapper.views), 1) self.assertEqual(len(wrapper.media_views), 1) self.assertEqual(wrapper(None, None), 'OK2') request = DummyRequest() request.accept = DummyAccept('text/html') self.assertEqual(wrapper(None, request), 'OK') def test_add_view_multiview_replaces_multiview(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier view = DummyMultiView() config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IMultiView, name='') view2 = lambda *arg: 'OK2' config.add_view(view=view2, renderer=null_renderer) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual([x[:2] for x in wrapper.views], [(view2, None)]) self.assertEqual(wrapper(None, None), 'OK1') def test_add_view_exc_multiview_replaces_multiview(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier view = DummyMultiView() config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, implementedBy(RuntimeError)), IMultiView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, implementedBy(RuntimeError)), IMultiView, name='') view2 = lambda *arg: 'OK2' config.add_view(view=view2, context=RuntimeError, renderer=null_renderer) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), exception_view=True) self.assertTrue(IMultiView.providedBy(wrapper)) self.assertEqual([x[:2] for x in wrapper.views], [(view2, None)]) self.assertEqual(wrapper(None, None), 'OK1') def test_add_view_multiview_context_superclass_then_subclass(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier class ISuper(Interface): pass class ISub(ISuper): pass view = lambda *arg: 'OK' view2 = lambda *arg: 'OK2' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, ISuper), IView, name='') config.add_view(view=view2, for_=ISub, renderer=null_renderer) wrapper = self._getViewCallable(config, ISuper, IRequest) self.assertFalse(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK') wrapper = self._getViewCallable(config, ISub, IRequest) self.assertFalse(IMultiView.providedBy(wrapper)) self.assertEqual(wrapper(None, None), 'OK2') def test_add_view_multiview_exception_superclass_then_subclass(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier class Super(Exception): pass class Sub(Super): pass view = lambda *arg: 'OK' view2 = lambda *arg: 'OK2' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Super), IView, name='') config.registry.registerAdapter( view, (IExceptionViewClassifier, IRequest, Super), IView, name='') config.add_view(view=view2, for_=Sub, renderer=null_renderer) wrapper = self._getViewCallable( config, implementedBy(Super), IRequest) wrapper_exc_view = self._getViewCallable( config, implementedBy(Super), IRequest, exception_view=True) self.assertEqual(wrapper_exc_view, wrapper) self.assertFalse(IMultiView.providedBy(wrapper_exc_view)) self.assertEqual(wrapper_exc_view(None, None), 'OK') wrapper = self._getViewCallable( config, implementedBy(Sub), IRequest) wrapper_exc_view = self._getViewCallable( config, implementedBy(Sub), IRequest, exception_view=True) self.assertEqual(wrapper_exc_view, wrapper) self.assertFalse(IMultiView.providedBy(wrapper_exc_view)) self.assertEqual(wrapper_exc_view(None, None), 'OK2') def test_add_view_multiview_call_ordering(self): from pyramid.renderers import null_renderer as nr from zope.interface import directlyProvides def view1(context, request): return 'view1' def view2(context, request): return 'view2' def view3(context, request): return 'view3' def view4(context, request): return 'view4' def view5(context, request): return 'view5' def view6(context, request): return 'view6' def view7(context, request): return 'view7' def view8(context, request): return 'view8' config = self._makeOne(autocommit=True) config.add_view(view=view1, renderer=nr) config.add_view(view=view2, request_method='POST', renderer=nr) config.add_view(view=view3,request_param='param', renderer=nr) config.add_view(view=view4, containment=IDummy, renderer=nr) config.add_view(view=view5, request_method='POST', request_param='param', renderer=nr) config.add_view(view=view6, request_method='POST', containment=IDummy, renderer=nr) config.add_view(view=view7, request_param='param', containment=IDummy, renderer=nr) config.add_view(view=view8, request_method='POST',request_param='param', containment=IDummy, renderer=nr) wrapper = self._getViewCallable(config) ctx = DummyContext() request = self._makeRequest(config) request.method = 'GET' request.params = {} self.assertEqual(wrapper(ctx, request), 'view1') ctx = DummyContext() request = self._makeRequest(config) request.params = {} request.method = 'POST' self.assertEqual(wrapper(ctx, request), 'view2') ctx = DummyContext() request = self._makeRequest(config) request.params = {'param':'1'} request.method = 'GET' self.assertEqual(wrapper(ctx, request), 'view3') ctx = DummyContext() directlyProvides(ctx, IDummy) request = self._makeRequest(config) request.method = 'GET' request.params = {} self.assertEqual(wrapper(ctx, request), 'view4') ctx = DummyContext() request = self._makeRequest(config) request.method = 'POST' request.params = {'param':'1'} self.assertEqual(wrapper(ctx, request), 'view5') ctx = DummyContext() directlyProvides(ctx, IDummy) request = self._makeRequest(config) request.params = {} request.method = 'POST' self.assertEqual(wrapper(ctx, request), 'view6') ctx = DummyContext() directlyProvides(ctx, IDummy) request = self._makeRequest(config) request.method = 'GET' request.params = {'param':'1'} self.assertEqual(wrapper(ctx, request), 'view7') ctx = DummyContext() directlyProvides(ctx, IDummy) request = self._makeRequest(config) request.method = 'POST' request.params = {'param':'1'} self.assertEqual(wrapper(ctx, request), 'view8') def test_view_with_most_specific_predicate(self): from pyramid.renderers import null_renderer as nr from pyramid.router import Router class OtherBase(object): pass class Int1(object): pass class Int2(object): pass class Resource(OtherBase, Int1, Int2): def __init__(self, request): pass def unknown(context, request): return 'unknown' def view(context, request): return 'hello' config = self._makeOne(autocommit=True) config.add_route('root', '/', factory=Resource) config.add_view(unknown, route_name='root', renderer=nr) config.add_view( view, renderer=nr, route_name='root', context=Int1, request_method='GET' ) config.add_view( view=view, renderer=nr, route_name='root', context=Int2, request_method='POST' ) request = self._makeRequest(config) request.method = 'POST' request.params = {} router = Router(config.registry) response = router.handle_request(request) self.assertEqual(response, 'hello') def test_view_with_most_specific_predicate_with_mismatch(self): from pyramid.renderers import null_renderer as nr from pyramid.router import Router class OtherBase(object): pass class Int1(object): pass class Int2(object): pass class Resource(OtherBase, Int1, Int2): def __init__(self, request): pass def unknown(context, request): return 'unknown' def view(context, request): return 'hello' config = self._makeOne(autocommit=True) config.add_route('root', '/', factory=Resource) config.add_view( unknown, route_name='root', renderer=nr, request_method=('POST',), xhr=True, ) config.add_view( view, renderer=nr, route_name='root', context=Int1, request_method='GET' ) config.add_view( view=view, renderer=nr, route_name='root', context=Int2, request_method='POST' ) request = self._makeRequest(config) request.method = 'POST' request.params = {} router = Router(config.registry) response = router.handle_request(request) self.assertEqual(response, 'hello') def test_add_view_multiview___discriminator__(self): from pyramid.renderers import null_renderer from zope.interface import Interface class IFoo(Interface): pass class IBar(Interface): pass @implementer(IFoo) class Foo(object): pass @implementer(IBar) class Bar(object): pass foo = Foo() bar = Bar() from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView view = lambda *arg: 'OK' view.__phash__ = 'abc' config = self._makeOne(autocommit=True) config.registry.registerAdapter( view, (IViewClassifier, IRequest, Interface), IView, name='') config.add_view(view=view, renderer=null_renderer, containment=IFoo) config.add_view(view=view, renderer=null_renderer, containment=IBar) wrapper = self._getViewCallable(config) self.assertTrue(IMultiView.providedBy(wrapper)) request = self._makeRequest(config) self.assertNotEqual( wrapper.__discriminator__(foo, request), wrapper.__discriminator__(bar, request), ) def test_add_view_with_template_renderer(self): from pyramid.tests import test_config from pyramid.interfaces import ISettings class view(object): def __init__(self, context, request): self.request = request self.context = context def __call__(self): return {'a':'1'} config = self._makeOne(autocommit=True) renderer = self._registerRenderer(config) fixture = 'pyramid.tests.test_config:files/minimal.txt' config.introspection = False config.add_view(view=view, renderer=fixture) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) self.assertEqual(result.body, b'Hello!') settings = config.registry.queryUtility(ISettings) result = renderer.info self.assertEqual(result.registry, config.registry) self.assertEqual(result.type, '.txt') self.assertEqual(result.package, test_config) self.assertEqual(result.name, fixture) self.assertEqual(result.settings, settings) def test_add_view_with_default_renderer(self): class view(object): def __init__(self, context, request): self.request = request self.context = context def __call__(self): return {'a':'1'} config = self._makeOne(autocommit=True) class moo(object): def __init__(self, *arg, **kw): pass def __call__(self, *arg, **kw): return b'moo' config.add_renderer(None, moo) config.add_view(view=view) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) self.assertEqual(result.body, b'moo') def test_add_view_with_template_renderer_no_callable(self): from pyramid.tests import test_config from pyramid.interfaces import ISettings config = self._makeOne(autocommit=True) renderer = self._registerRenderer(config) fixture = 'pyramid.tests.test_config:files/minimal.txt' config.introspection = False config.add_view(view=None, renderer=fixture) wrapper = self._getViewCallable(config) request = self._makeRequest(config) result = wrapper(None, request) self.assertEqual(result.body, b'Hello!') settings = config.registry.queryUtility(ISettings) result = renderer.info self.assertEqual(result.registry, config.registry) self.assertEqual(result.type, '.txt') self.assertEqual(result.package, test_config) self.assertEqual(result.name, fixture) self.assertEqual(result.settings, settings) def test_add_view_with_request_type_as_iface(self): from pyramid.renderers import null_renderer from zope.interface import directlyProvides def view(context, request): return 'OK' config = self._makeOne(autocommit=True) config.add_view(request_type=IDummy, view=view, renderer=null_renderer) wrapper = self._getViewCallable(config, None) request = self._makeRequest(config) directlyProvides(request, IDummy) result = wrapper(None, request) self.assertEqual(result, 'OK') def test_add_view_with_request_type_as_noniface(self): view = lambda *arg: 'OK' config = self._makeOne() self.assertRaises(ConfigurationError, config.add_view, view, '', None, None, object) def test_add_view_with_route_name(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_route('foo', '/a/b') config.add_view(view=view, route_name='foo', renderer=null_renderer) request_iface = self._getRouteRequestIface(config, 'foo') self.assertNotEqual(request_iface, None) wrapper = self._getViewCallable(config, request_iface=request_iface) self.assertNotEqual(wrapper, None) self.assertEqual(wrapper(None, None), 'OK') def test_add_view_with_nonexistant_route_name(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view=view, route_name='foo', renderer=null_renderer) self.assertRaises(ConfigurationExecutionError, config.commit) def test_add_view_with_route_name_exception(self): from pyramid.renderers import null_renderer from zope.interface import implementedBy view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_route('foo', '/a/b') config.add_view(view=view, route_name='foo', context=RuntimeError, renderer=null_renderer) request_iface = self._getRouteRequestIface(config, 'foo') wrapper_exc_view = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), request_iface=request_iface, exception_view=True) self.assertNotEqual(wrapper_exc_view, None) wrapper = self._getViewCallable( config, ctx_iface=implementedBy(RuntimeError), request_iface=request_iface) self.assertEqual(wrapper_exc_view, wrapper) self.assertEqual(wrapper_exc_view(None, None), 'OK') def test_add_view_with_request_method_true(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method='POST', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'POST' self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_request_method_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method='POST') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' self._assertNotFound(wrapper, None, request) def test_add_view_with_request_method_sequence_true(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method=('POST', 'GET'), renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'POST' self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_request_method_sequence_conflict(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view=view, request_method=('POST', 'GET'), renderer=null_renderer) config.add_view(view=view, request_method=('GET', 'POST'), renderer=null_renderer) self.assertRaises(ConfigurationConflictError, config.commit) def test_add_view_with_request_method_sequence_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method=('POST', 'HEAD')) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' self._assertNotFound(wrapper, None, request) def test_add_view_with_request_method_get_implies_head(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method='GET', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'HEAD' self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_request_param_noval_true(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_param='abc', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {'abc':''} self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_request_param_noval_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_param='abc') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {} self._assertNotFound(wrapper, None, request) def test_add_view_with_request_param_val_true(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_param='abc=123', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {'abc':'123'} self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_request_param_val_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_param='abc=123') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.params = {'abc':''} self._assertNotFound(wrapper, None, request) def test_add_view_with_xhr_true(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, xhr=True, renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_xhr_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, xhr=True) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.is_xhr = False self._assertNotFound(wrapper, None, request) def test_add_view_with_header_badregex(self): view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view, header='Host:a\\') self.assertRaises(ConfigurationError, config.commit) def test_add_view_with_header_noval_match(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, header='Host', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'Host':'whatever'} self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_header_noval_nomatch(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, header='Host') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'NotHost':'whatever'} self._assertNotFound(wrapper, None, request) def test_add_view_with_header_val_match(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, header=r'Host:\d', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'Host':'1'} self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_header_val_nomatch(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, header=r'Host:\d') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'Host':'abc'} self._assertNotFound(wrapper, None, request) def test_add_view_with_header_val_missing(self): from pyramid.httpexceptions import HTTPNotFound view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, header=r'Host:\d') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.headers = {'NoHost':'1'} self.assertRaises(HTTPNotFound, wrapper, None, request) def test_add_view_with_accept_match(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, accept='text/xml', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.accept = ['text/xml'] self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_accept_nomatch(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, accept='text/xml') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.accept = ['text/html'] self._assertNotFound(wrapper, None, request) def test_add_view_with_containment_true(self): from pyramid.renderers import null_renderer from zope.interface import directlyProvides view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, containment=IDummy, renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) self.assertEqual(wrapper(context, None), 'OK') def test_add_view_with_containment_false(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, containment=IDummy) wrapper = self._getViewCallable(config) context = DummyContext() self._assertNotFound(wrapper, context, None) def test_add_view_with_containment_dottedname(self): from pyramid.renderers import null_renderer from zope.interface import directlyProvides view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view( view=view, containment='pyramid.tests.test_config.IDummy', renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) self.assertEqual(wrapper(context, None), 'OK') def test_add_view_with_path_info_badregex(self): view = lambda *arg: 'OK' config = self._makeOne() config.add_view(view, path_info='\\') self.assertRaises(ConfigurationError, config.commit) def test_add_view_with_path_info_match(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, path_info='/foo', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.upath_info = text_(b'/foo') self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_path_info_nomatch(self): view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) config.add_view(view=view, path_info='/foo') wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.upath_info = text_('/') self._assertNotFound(wrapper, None, request) def test_add_view_with_custom_predicates_match(self): import warnings from pyramid.renderers import null_renderer view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) def pred1(context, request): return True def pred2(context, request): return True predicates = (pred1, pred2) with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') config.add_view(view=view, custom_predicates=predicates, renderer=null_renderer) self.assertEqual(len(w), 1) wrapper = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(wrapper(None, request), 'OK') def test_add_view_with_custom_predicates_nomatch(self): import warnings view = lambda *arg: 'OK' config = self._makeOne(autocommit=True) def pred1(context, request): return True def pred2(context, request): return False predicates = (pred1, pred2) with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') config.add_view(view=view, custom_predicates=predicates) self.assertEqual(len(w), 1) wrapper = self._getViewCallable(config) request = self._makeRequest(config) self._assertNotFound(wrapper, None, request) def test_add_view_custom_predicate_bests_standard_predicate(self): import warnings from pyramid.renderers import null_renderer view = lambda *arg: 'OK' view2 = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) def pred1(context, request): return True with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') config.add_view(view=view, custom_predicates=(pred1,), renderer=null_renderer) config.add_view(view=view2, request_method='GET', renderer=null_renderer) self.assertEqual(len(w), 1) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' self.assertEqual(wrapper(None, request), 'OK') def test_add_view_custom_more_preds_first_bests_fewer_preds_last(self): from pyramid.renderers import null_renderer view = lambda *arg: 'OK' view2 = lambda *arg: 'NOT OK' config = self._makeOne(autocommit=True) config.add_view(view=view, request_method='GET', xhr=True, renderer=null_renderer) config.add_view(view=view2, request_method='GET', renderer=null_renderer) wrapper = self._getViewCallable(config) request = self._makeRequest(config) request.method = 'GET' request.is_xhr = True self.assertEqual(wrapper(None, request), 'OK') def test_add_view_same_predicates(self): view2 = lambda *arg: 'second' view1 = lambda *arg: 'first' config = self._makeOne() config.add_view(view=view1) config.add_view(view=view2) self.assertRaises(ConfigurationConflictError, config.commit) def test_add_view_with_permission(self): from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' outerself = self class DummyPolicy(object): def effective_principals(self, r): outerself.assertEqual(r, request) return ['abc'] def permits(self, context, principals, permission): outerself.assertEqual(context, None) outerself.assertEqual(principals, ['abc']) outerself.assertEqual(permission, 'view') return True policy = DummyPolicy() config = self._makeOne(authorization_policy=policy, authentication_policy=policy, autocommit=True) config.add_view(view=view1, permission='view', renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') def test_add_view_with_default_permission_no_explicit_permission(self): from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' outerself = self class DummyPolicy(object): def effective_principals(self, r): outerself.assertEqual(r, request) return ['abc'] def permits(self, context, principals, permission): outerself.assertEqual(context, None) outerself.assertEqual(principals, ['abc']) outerself.assertEqual(permission, 'view') return True policy = DummyPolicy() config = self._makeOne(authorization_policy=policy, authentication_policy=policy, default_permission='view', autocommit=True) config.add_view(view=view1, renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') def test_add_view_with_no_default_permission_no_explicit_permission(self): from pyramid.renderers import null_renderer view1 = lambda *arg: 'OK' class DummyPolicy(object): pass # wont be called policy = DummyPolicy() config = self._makeOne(authorization_policy=policy, authentication_policy=policy, autocommit=True) config.add_view(view=view1, renderer=null_renderer) view = self._getViewCallable(config) request = self._makeRequest(config) self.assertEqual(view(None, request), 'OK') def test_add_view_with_mapper(self): from pyramid.renderers import null_renderer class Mapper(object): def __init__(self, **kw): self.__class__.kw = kw def __call__(self, view): return view config = self._makeOne(autocommit=True) def view(context, request): return 'OK' config.add_view(view=view, mapper=Mapper, renderer=null_renderer) view = self._getViewCallable(config) self.assertEqual(view(None, None), 'OK') self.assertEqual(Mapper.kw['mapper'], Mapper) def test_add_view_with_view_defaults(self): from pyramid.renderers import null_renderer from pyramid.exceptions import PredicateMismatch from zope.interface import directlyProvides class view(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IDummy' } def __init__(self, request): pass def __call__(self): return 'OK' config = self._makeOne(autocommit=True) config.add_view( view=view, renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) request = self._makeRequest(config) self.assertEqual(wrapper(context, request), 'OK') context = DummyContext() request = self._makeRequest(config) self.assertRaises(PredicateMismatch, wrapper, context, request) def test_add_view_with_view_defaults_viewname_is_dottedname_kwarg(self): from pyramid.renderers import null_renderer from pyramid.exceptions import PredicateMismatch from zope.interface import directlyProvides config = self._makeOne(autocommit=True) config.add_view( view='pyramid.tests.test_config.test_views.DummyViewDefaultsClass', renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) request = self._makeRequest(config) self.assertEqual(wrapper(context, request), 'OK') context = DummyContext() request = self._makeRequest(config) self.assertRaises(PredicateMismatch, wrapper, context, request) def test_add_view_with_view_defaults_viewname_is_dottedname_nonkwarg(self): from pyramid.renderers import null_renderer from pyramid.exceptions import PredicateMismatch from zope.interface import directlyProvides config = self._makeOne(autocommit=True) config.add_view( 'pyramid.tests.test_config.test_views.DummyViewDefaultsClass', renderer=null_renderer) wrapper = self._getViewCallable(config) context = DummyContext() directlyProvides(context, IDummy) request = self._makeRequest(config) self.assertEqual(wrapper(context, request), 'OK') context = DummyContext() request = self._makeRequest(config) self.assertRaises(PredicateMismatch, wrapper, context, request) def test_add_view_with_view_config_and_view_defaults_doesnt_conflict(self): from pyramid.renderers import null_renderer class view(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IDummy' } class view2(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IFactory' } config = self._makeOne(autocommit=False) config.add_view( view=view, renderer=null_renderer) config.add_view( view=view2, renderer=null_renderer) config.commit() # does not raise def test_add_view_with_view_config_and_view_defaults_conflicts(self): from pyramid.renderers import null_renderer class view(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IDummy' } class view2(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IDummy' } config = self._makeOne(autocommit=False) config.add_view( view=view, renderer=null_renderer) config.add_view( view=view2, renderer=null_renderer) self.assertRaises(ConfigurationConflictError, config.commit) def test_add_view_class_method_no_attr(self): from pyramid.renderers import null_renderer from zope.interface import directlyProvides from pyramid.exceptions import ConfigurationError config = self._makeOne(autocommit=True) class DummyViewClass(object): def run(self): pass def configure_view(): config.add_view(view=DummyViewClass.run, renderer=null_renderer) self.assertRaises(ConfigurationError, configure_view) def test_derive_view_function(self): from pyramid.renderers import null_renderer def view(request): return 'OK' config = self._makeOne() result = config.derive_view(view, renderer=null_renderer) self.assertFalse(result is view) self.assertEqual(result(None, None), 'OK') def test_derive_view_dottedname(self): from pyramid.renderers import null_renderer config = self._makeOne() result = config.derive_view( 'pyramid.tests.test_config.dummy_view', renderer=null_renderer) self.assertFalse(result is dummy_view) self.assertEqual(result(None, None), 'OK') def test_derive_view_with_default_renderer_no_explicit_renderer(self): config = self._makeOne() class moo(object): def __init__(self, view): pass def __call__(self, *arg, **kw): return 'moo' config.add_renderer(None, moo) config.commit() def view(request): return 'OK' result = config.derive_view(view) self.assertFalse(result is view) self.assertEqual(result(None, None).body, b'moo') def test_derive_view_with_default_renderer_with_explicit_renderer(self): class moo(object): pass class foo(object): def __init__(self, view): pass def __call__(self, *arg, **kw): return b'foo' def view(request): return 'OK' config = self._makeOne() config.add_renderer(None, moo) config.add_renderer('foo', foo) config.commit() result = config.derive_view(view, renderer='foo') self.assertFalse(result is view) request = self._makeRequest(config) self.assertEqual(result(None, request).body, b'foo') def test_add_static_view_here_no_utility_registered(self): from pyramid.renderers import null_renderer from zope.interface import Interface from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier config = self._makeOne(autocommit=True) config.add_static_view('static', 'files', renderer=null_renderer) request_type = self._getRouteRequestIface(config, '__static/') self._assertRoute(config, '__static/', 'static/*subpath') wrapped = config.registry.adapters.lookup( (IViewClassifier, request_type, Interface), IView, name='') from pyramid.request import Request request = Request.blank('/static/minimal.txt') request.subpath = ('minimal.txt', ) result = wrapped(None, request) self.assertEqual(result.status, '200 OK') self.assertTrue(result.body.startswith(b' into a response object. The value returned was None. You ' 'may have forgotten to return a value from the view callable.')) else: # pragma: no cover raise AssertionError def test_function_returns_true_Response_no_renderer(self): from pyramid.response import Response r = Response('Hello') def view(request): return r deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) response = result(None, None) self.assertEqual(response, r) def test_function_returns_true_Response_with_renderer(self): from pyramid.response import Response r = Response('Hello') def view(request): return r renderer = object() deriver = self._makeOne(renderer=renderer) result = deriver(view) self.assertFalse(result is view) response = result(None, None) self.assertEqual(response, r) def test_requestonly_default_method_returns_non_adaptable(self): request = DummyRequest() class AView(object): def __init__(self, request): pass def __call__(self): return None deriver = self._makeOne() result = deriver(AView) self.assertFalse(result is AView) try: result(None, request) except ValueError as e: self.assertEqual( e.args[0], 'Could not convert return value of the view callable ' 'method __call__ of ' 'class pyramid.tests.test_config.test_views.AView into a ' 'response object. The value returned was None. You may have ' 'forgotten to return a value from the view callable.' ) else: # pragma: no cover raise AssertionError def test_requestonly_nondefault_method_returns_non_adaptable(self): request = DummyRequest() class AView(object): def __init__(self, request): pass def theviewmethod(self): return None deriver = self._makeOne(attr='theviewmethod') result = deriver(AView) self.assertFalse(result is AView) try: result(None, request) except ValueError as e: self.assertEqual( e.args[0], 'Could not convert return value of the view callable ' 'method theviewmethod of ' 'class pyramid.tests.test_config.test_views.AView into a ' 'response object. The value returned was None. You may have ' 'forgotten to return a value from the view callable.' ) else: # pragma: no cover raise AssertionError def test_requestonly_function(self): response = DummyResponse() def view(request): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(result(None, None), response) def test_requestonly_function_with_renderer(self): response = DummyResponse() class moo(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, 'OK') self.assertEqual(view_inst, view) self.assertEqual(ctx, context) return response def clone(self): return self def view(request): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) self.assertFalse(result.__wraps__ is view) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_requestonly_function_with_renderer_request_override(self): def moo(info): def inner(value, system): self.assertEqual(value, 'OK') self.assertEqual(system['request'], request) self.assertEqual(system['context'], context) return b'moo' return inner def view(request): return 'OK' self.config.add_renderer('moo', moo) deriver = self._makeOne(renderer='string') result = deriver(view) self.assertFalse(result is view) request = self._makeRequest() request.override_renderer = 'moo' context = testing.DummyResource() self.assertEqual(result(context, request).body, b'moo') def test_requestonly_function_with_renderer_request_has_view(self): response = DummyResponse() class moo(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, 'OK') self.assertEqual(view_inst, 'view') self.assertEqual(ctx, context) return response def clone(self): return self def view(request): return 'OK' deriver = self._makeOne(renderer=moo()) result = deriver(view) self.assertFalse(result.__wraps__ is view) request = self._makeRequest() request.__view__ = 'view' context = testing.DummyResource() r = result(context, request) self.assertEqual(r, response) self.assertFalse(hasattr(request, '__view__')) def test_class_without_attr(self): response = DummyResponse() class View(object): def __init__(self, request): pass def __call__(self): return response deriver = self._makeOne() result = deriver(View) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, View) def test_class_with_attr(self): response = DummyResponse() class View(object): def __init__(self, request): pass def another(self): return response deriver = self._makeOne(attr='another') result = deriver(View) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, View) def test_as_function_context_and_request(self): def view(context, request): return 'OK' deriver = self._makeOne() result = deriver(view) self.assertTrue(result.__wraps__ is view) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(view(None, None), 'OK') def test_as_function_requestonly(self): response = DummyResponse() def view(request): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), response) def test_as_newstyle_class_context_and_request(self): response = DummyResponse() class view(object): def __init__(self, context, request): pass def __call__(self): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_newstyle_class_requestonly(self): response = DummyResponse() class view(object): def __init__(self, context, request): pass def __call__(self): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_oldstyle_class_context_and_request(self): response = DummyResponse() class view: def __init__(self, context, request): pass def __call__(self): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_oldstyle_class_requestonly(self): response = DummyResponse() class view: def __init__(self, context, request): pass def __call__(self): return response deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() self.assertEqual(result(None, request), response) self.assertEqual(request.__view__.__class__, view) def test_as_instance_context_and_request(self): response = DummyResponse() class View: def __call__(self, context, request): return response view = View() deriver = self._makeOne() result = deriver(view) self.assertTrue(result.__wraps__ is view) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), response) def test_as_instance_requestonly(self): response = DummyResponse() class View: def __call__(self, request): return response view = View() deriver = self._makeOne() result = deriver(view) self.assertFalse(result is view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertTrue('test_views' in result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) self.assertEqual(result(None, None), response) def test_with_debug_authorization_no_authpol(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): Allowed " "(no authorization policy in use)") def test_with_debug_authorization_authn_policy_no_authz_policy(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict(debug_authorization=True) from pyramid.interfaces import IAuthenticationPolicy policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthenticationPolicy) logger = self._registerLogger() deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): Allowed " "(no authorization policy in use)") def test_with_debug_authorization_authz_policy_no_authn_policy(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict(debug_authorization=True) from pyramid.interfaces import IAuthorizationPolicy policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthorizationPolicy) logger = self._registerLogger() deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): Allowed " "(no authorization policy in use)") def test_with_debug_authorization_no_permission(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) self._registerSecurityPolicy(True) logger = self._registerLogger() deriver = self._makeOne() result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): Allowed (" "no permission registered)") def test_debug_auth_permission_authpol_permitted(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() self._registerSecurityPolicy(True) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertEqual(result.__call_permissive__.__wraps__, view) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): True") def test_debug_auth_permission_authpol_permitted_no_request(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() self._registerSecurityPolicy(True) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertEqual(result.__call_permissive__.__wraps__, view) self.assertEqual(result(None, None), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url None (view name " "None against context None): True") def test_debug_auth_permission_authpol_denied(self): from pyramid.httpexceptions import HTTPForbidden response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() self._registerSecurityPolicy(False) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertEqual(result.__call_permissive__.__wraps__, view) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertRaises(HTTPForbidden, result, None, request) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): False") def test_debug_auth_permission_authpol_denied2(self): view = lambda *arg: 'OK' self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) self._registerLogger() self._registerSecurityPolicy(False) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' permitted = result.__permitted__(None, None) self.assertEqual(permitted, False) def test_debug_auth_permission_authpol_overridden(self): from pyramid.security import NO_PERMISSION_REQUIRED response = DummyResponse() view = lambda *arg: response self.config.registry.settings = dict( debug_authorization=True, reload_templates=True) logger = self._registerLogger() self._registerSecurityPolicy(False) deriver = self._makeOne(permission=NO_PERMISSION_REQUIRED) result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) self.assertEqual(len(logger.messages), 1) self.assertEqual(logger.messages[0], "debug_authorization of url url (view name " "'view_name' against context None): " "Allowed (NO_PERMISSION_REQUIRED)") def test_secured_view_authn_policy_no_authz_policy(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = {} from pyramid.interfaces import IAuthenticationPolicy policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthenticationPolicy) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) def test_secured_view_authz_policy_no_authn_policy(self): response = DummyResponse() view = lambda *arg: response self.config.registry.settings = {} from pyramid.interfaces import IAuthorizationPolicy policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthorizationPolicy) deriver = self._makeOne(permission='view') result = deriver(view) self.assertEqual(view.__module__, result.__module__) self.assertEqual(view.__doc__, result.__doc__) self.assertEqual(view.__name__, result.__name__) self.assertFalse(hasattr(result, '__call_permissive__')) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' self.assertEqual(result(None, request), response) def test_secured_view_raises_forbidden_no_name(self): from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy from pyramid.httpexceptions import HTTPForbidden response = DummyResponse() view = lambda *arg: response self.config.registry.settings = {} policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthenticationPolicy) self.config.registry.registerUtility(policy, IAuthorizationPolicy) deriver = self._makeOne(permission='view') result = deriver(view) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' try: result(None, request) except HTTPForbidden as e: self.assertEqual(e.message, 'Unauthorized: failed permission check') else: # pragma: no cover raise AssertionError def test_secured_view_raises_forbidden_with_name(self): from pyramid.interfaces import IAuthenticationPolicy from pyramid.interfaces import IAuthorizationPolicy from pyramid.httpexceptions import HTTPForbidden def myview(request): pass self.config.registry.settings = {} policy = DummySecurityPolicy(False) self.config.registry.registerUtility(policy, IAuthenticationPolicy) self.config.registry.registerUtility(policy, IAuthorizationPolicy) deriver = self._makeOne(permission='view') result = deriver(myview) request = self._makeRequest() request.view_name = 'view_name' request.url = 'url' try: result(None, request) except HTTPForbidden as e: self.assertEqual(e.message, 'Unauthorized: myview failed permission check') else: # pragma: no cover raise AssertionError def test_predicate_mismatch_view_has_no_name(self): from pyramid.exceptions import PredicateMismatch response = DummyResponse() view = lambda *arg: response def predicate1(context, request): return False predicate1.text = lambda *arg: 'text' deriver = self._makeOne(predicates=[predicate1]) result = deriver(view) request = self._makeRequest() request.method = 'POST' try: result(None, None) except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view (text)') else: # pragma: no cover raise AssertionError def test_predicate_mismatch_view_has_name(self): from pyramid.exceptions import PredicateMismatch def myview(request): pass def predicate1(context, request): return False predicate1.text = lambda *arg: 'text' deriver = self._makeOne(predicates=[predicate1]) result = deriver(myview) request = self._makeRequest() request.method = 'POST' try: result(None, None) except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view myview (text)') else: # pragma: no cover raise AssertionError def test_predicate_mismatch_exception_has_text_in_detail(self): from pyramid.exceptions import PredicateMismatch def myview(request): pass def predicate1(context, request): return True predicate1.text = lambda *arg: 'pred1' def predicate2(context, request): return False predicate2.text = lambda *arg: 'pred2' deriver = self._makeOne(predicates=[predicate1, predicate2]) result = deriver(myview) request = self._makeRequest() request.method = 'POST' try: result(None, None) except PredicateMismatch as e: self.assertEqual(e.detail, 'predicate mismatch for view myview (pred2)') else: # pragma: no cover raise AssertionError def test_with_predicates_all(self): response = DummyResponse() view = lambda *arg: response predicates = [] def predicate1(context, request): predicates.append(True) return True def predicate2(context, request): predicates.append(True) return True deriver = self._makeOne(predicates=[predicate1, predicate2]) result = deriver(view) request = self._makeRequest() request.method = 'POST' next = result(None, None) self.assertEqual(next, response) self.assertEqual(predicates, [True, True]) def test_with_predicates_checker(self): view = lambda *arg: 'OK' predicates = [] def predicate1(context, request): predicates.append(True) return True def predicate2(context, request): predicates.append(True) return True deriver = self._makeOne(predicates=[predicate1, predicate2]) result = deriver(view) request = self._makeRequest() request.method = 'POST' next = result.__predicated__(None, None) self.assertEqual(next, True) self.assertEqual(predicates, [True, True]) def test_with_predicates_notall(self): from pyramid.httpexceptions import HTTPNotFound view = lambda *arg: 'OK' predicates = [] def predicate1(context, request): predicates.append(True) return True predicate1.text = lambda *arg: 'text' def predicate2(context, request): predicates.append(True) return False predicate2.text = lambda *arg: 'text' deriver = self._makeOne(predicates=[predicate1, predicate2]) result = deriver(view) request = self._makeRequest() request.method = 'POST' self.assertRaises(HTTPNotFound, result, None, None) self.assertEqual(predicates, [True, True]) def test_with_wrapper_viewname(self): from pyramid.response import Response from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier inner_response = Response('OK') def inner_view(context, request): return inner_response def outer_view(context, request): self.assertEqual(request.wrapped_response, inner_response) self.assertEqual(request.wrapped_body, inner_response.body) self.assertEqual(request.wrapped_view.__original_view__, inner_view) return Response(b'outer ' + request.wrapped_body) self.config.registry.registerAdapter( outer_view, (IViewClassifier, None, None), IView, 'owrap') deriver = self._makeOne(viewname='inner', wrapper_viewname='owrap') result = deriver(inner_view) self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() response = result(None, request) self.assertEqual(response.body, b'outer OK') def test_with_wrapper_viewname_notfound(self): from pyramid.response import Response inner_response = Response('OK') def inner_view(context, request): return inner_response deriver = self._makeOne(viewname='inner', wrapper_viewname='owrap') wrapped = deriver(inner_view) request = self._makeRequest() self.assertRaises(ValueError, wrapped, None, request) def test_as_newstyle_class_context_and_request_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) return response def clone(self): return self class View(object): def __init__(self, context, request): pass def index(self): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_as_newstyle_class_requestonly_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) return response def clone(self): return self class View(object): def __init__(self, request): pass def index(self): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_as_oldstyle_cls_context_request_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) return response def clone(self): return self class View: def __init__(self, context, request): pass def index(self): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_as_oldstyle_cls_requestonly_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst.__class__, View) self.assertEqual(ctx, context) return response def clone(self): return self class View: def __init__(self, request): pass def index(self): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') result = deriver(View) self.assertFalse(result is View) self.assertEqual(result.__module__, View.__module__) self.assertEqual(result.__doc__, View.__doc__) self.assertEqual(result.__name__, View.__name__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_as_instance_context_and_request_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst, view) self.assertEqual(ctx, context) return response def clone(self): return self class View: def index(self, context, request): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') view = View() result = deriver(view) self.assertFalse(result is view) self.assertEqual(result.__module__, view.__module__) self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_as_instance_requestonly_attr_and_renderer(self): response = DummyResponse() class renderer(object): def render_view(inself, req, resp, view_inst, ctx): self.assertEqual(req, request) self.assertEqual(resp, {'a':'1'}) self.assertEqual(view_inst, view) self.assertEqual(ctx, context) return response def clone(self): return self class View: def index(self, request): return {'a':'1'} deriver = self._makeOne(renderer=renderer(), attr='index') view = View() result = deriver(view) self.assertFalse(result is view) self.assertEqual(result.__module__, view.__module__) self.assertEqual(result.__doc__, view.__doc__) request = self._makeRequest() context = testing.DummyResource() self.assertEqual(result(context, request), response) def test_with_view_mapper_config_specified(self): response = DummyResponse() class mapper(object): def __init__(self, **kw): self.kw = kw def __call__(self, view): def wrapped(context, request): return response return wrapped def view(context, request): return 'NOTOK' deriver = self._makeOne(mapper=mapper) result = deriver(view) self.assertFalse(result.__wraps__ is view) self.assertEqual(result(None, None), response) def test_with_view_mapper_view_specified(self): from pyramid.response import Response response = Response() def mapper(**kw): def inner(view): def superinner(context, request): self.assertEqual(request, None) return response return superinner return inner def view(context, request): return 'NOTOK' view.__view_mapper__ = mapper deriver = self._makeOne() result = deriver(view) self.assertFalse(result.__wraps__ is view) self.assertEqual(result(None, None), response) def test_with_view_mapper_default_mapper_specified(self): from pyramid.response import Response response = Response() def mapper(**kw): def inner(view): def superinner(context, request): self.assertEqual(request, None) return response return superinner return inner self.config.set_view_mapper(mapper) def view(context, request): return 'NOTOK' deriver = self._makeOne() result = deriver(view) self.assertFalse(result.__wraps__ is view) self.assertEqual(result(None, None), response) def test_attr_wrapped_view_branching_default_phash(self): from pyramid.config.util import DEFAULT_PHASH def view(context, request): pass deriver = self._makeOne(phash=DEFAULT_PHASH) result = deriver(view) self.assertEqual(result.__wraps__, view) def test_attr_wrapped_view_branching_nondefault_phash(self): def view(context, request): pass deriver = self._makeOne(phash='nondefault') result = deriver(view) self.assertNotEqual(result, view) def test_http_cached_view_integer(self): import datetime from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response deriver = self._makeOne(http_cache=3600) result = deriver(inner_view) self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() when = datetime.datetime.utcnow() + datetime.timedelta(hours=1) result = result(None, request) self.assertEqual(result, response) headers = dict(result.headerlist) expires = parse_httpdate(headers['Expires']) assert_similar_datetime(expires, when) self.assertEqual(headers['Cache-Control'], 'max-age=3600') def test_http_cached_view_timedelta(self): import datetime from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response deriver = self._makeOne(http_cache=datetime.timedelta(hours=1)) result = deriver(inner_view) self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() when = datetime.datetime.utcnow() + datetime.timedelta(hours=1) result = result(None, request) self.assertEqual(result, response) headers = dict(result.headerlist) expires = parse_httpdate(headers['Expires']) assert_similar_datetime(expires, when) self.assertEqual(headers['Cache-Control'], 'max-age=3600') def test_http_cached_view_tuple(self): import datetime from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response deriver = self._makeOne(http_cache=(3600, {'public':True})) result = deriver(inner_view) self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() when = datetime.datetime.utcnow() + datetime.timedelta(hours=1) result = result(None, request) self.assertEqual(result, response) headers = dict(result.headerlist) expires = parse_httpdate(headers['Expires']) assert_similar_datetime(expires, when) self.assertEqual(headers['Cache-Control'], 'max-age=3600, public') def test_http_cached_view_tuple_seconds_None(self): from pyramid.response import Response response = Response('OK') def inner_view(context, request): return response deriver = self._makeOne(http_cache=(None, {'public':True})) result = deriver(inner_view) self.assertFalse(result is inner_view) self.assertEqual(inner_view.__module__, result.__module__) self.assertEqual(inner_view.__doc__, result.__doc__) request = self._makeRequest() result = result(None, request) self.assertEqual(result, response) headers = dict(result.headerlist) self.assertFalse('Expires' in headers) self.assertEqual(headers['Cache-Control'], 'public') def test_http_cached_view_prevent_auto_set(self): from pyramid.response import Response response = Response() response.cache_control.prevent_auto = True def inner_view(context, request): return response deriver = self._makeOne(http_cache=3600) result = deriver(inner_view) request = self._makeRequest() result = result(None, request) self.assertEqual(result, response) # doesn't blow up headers = dict(result.headerlist) self.assertFalse('Expires' in headers) self.assertFalse('Cache-Control' in headers) def test_http_cached_prevent_http_cache_in_settings(self): self.config.registry.settings['prevent_http_cache'] = True from pyramid.response import Response response = Response() def inner_view(context, request): return response deriver = self._makeOne(http_cache=3600) result = deriver(inner_view) request = self._makeRequest() result = result(None, request) self.assertEqual(result, response) headers = dict(result.headerlist) self.assertFalse('Expires' in headers) self.assertFalse('Cache-Control' in headers) def test_http_cached_view_bad_tuple(self): deriver = self._makeOne(http_cache=(None,)) def view(request): pass self.assertRaises(ConfigurationError, deriver, view) class TestDefaultViewMapper(unittest.TestCase): def setUp(self): self.config = testing.setUp() self.registry = self.config.registry def tearDown(self): del self.registry testing.tearDown() def _makeOne(self, **kw): from pyramid.config.views import DefaultViewMapper kw['registry'] = self.registry return DefaultViewMapper(**kw) def _makeRequest(self): request = DummyRequest() request.registry = self.registry return request def test_view_as_function_context_and_request(self): def view(context, request): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertTrue(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test__view_as_function_with_attr(self): def view(context, request): """ """ mapper = self._makeOne(attr='__name__') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertRaises(TypeError, result, None, request) def test_view_as_function_requestonly(self): def view(request): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_function_requestonly_with_attr(self): def view(request): """ """ mapper = self._makeOne(attr='__name__') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertRaises(TypeError, result, None, request) def test_view_as_newstyle_class_context_and_request(self): class view(object): def __init__(self, context, request): pass def __call__(self): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_newstyle_class_context_and_request_with_attr(self): class view(object): def __init__(self, context, request): pass def index(self): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_newstyle_class_requestonly(self): class view(object): def __init__(self, request): pass def __call__(self): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_newstyle_class_requestonly_with_attr(self): class view(object): def __init__(self, request): pass def index(self): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_oldstyle_class_context_and_request(self): class view: def __init__(self, context, request): pass def __call__(self): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_oldstyle_class_context_and_request_with_attr(self): class view: def __init__(self, context, request): pass def index(self): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_oldstyle_class_requestonly(self): class view: def __init__(self, request): pass def __call__(self): return 'OK' mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_oldstyle_class_requestonly_with_attr(self): class view: def __init__(self, request): pass def index(self): return 'OK' mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_instance_context_and_request(self): class View: def __call__(self, context, request): return 'OK' view = View() mapper = self._makeOne() result = mapper(view) self.assertTrue(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_instance_context_and_request_and_attr(self): class View: def index(self, context, request): return 'OK' view = View() mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_instance_requestonly(self): class View: def __call__(self, request): return 'OK' view = View() mapper = self._makeOne() result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') def test_view_as_instance_requestonly_with_attr(self): class View: def index(self, request): return 'OK' view = View() mapper = self._makeOne(attr='index') result = mapper(view) self.assertFalse(result is view) request = self._makeRequest() self.assertEqual(result(None, request), 'OK') class Test_preserve_view_attrs(unittest.TestCase): def _callFUT(self, view, wrapped_view): from pyramid.config.views import preserve_view_attrs return preserve_view_attrs(view, wrapped_view) def test_it_same(self): def view(context, request): """ """ result = self._callFUT(view, view) self.assertTrue(result is view) def test_it_view_is_None(self): def view(context, request): """ """ result = self._callFUT(None, view) self.assertTrue(result is view) def test_it_different_with_existing_original_view(self): def view1(context, request): pass view1.__original_view__ = 'abc' def view2(context, request): pass result = self._callFUT(view1, view2) self.assertEqual(result.__original_view__, 'abc') self.assertFalse(result is view1) def test_it_different(self): class DummyView1: """ 1 """ __name__ = '1' __module__ = '1' def __call__(self, context, request): """ """ def __call_permissive__(self, context, request): """ """ def __predicated__(self, context, request): """ """ def __permitted__(self, context, request): """ """ class DummyView2: """ 2 """ __name__ = '2' __module__ = '2' def __call__(self, context, request): """ """ def __call_permissive__(self, context, request): """ """ def __predicated__(self, context, request): """ """ def __permitted__(self, context, request): """ """ view1 = DummyView1() view2 = DummyView2() result = self._callFUT(view2, view1) self.assertEqual(result, view1) self.assertTrue(view1.__original_view__ is view2) self.assertTrue(view1.__doc__ is view2.__doc__) self.assertTrue(view1.__module__ is view2.__module__) self.assertTrue(view1.__name__ is view2.__name__) self.assertTrue(getattr(view1.__call_permissive__, im_func) is getattr(view2.__call_permissive__, im_func)) self.assertTrue(getattr(view1.__permitted__, im_func) is getattr(view2.__permitted__, im_func)) self.assertTrue(getattr(view1.__predicated__, im_func) is getattr(view2.__predicated__, im_func)) class TestStaticURLInfo(unittest.TestCase): def _getTargetClass(self): from pyramid.config.views import StaticURLInfo return StaticURLInfo def _makeOne(self): return self._getTargetClass()() def _makeRequest(self): request = DummyRequest() request.registry = DummyRegistry() return request def test_verifyClass(self): from pyramid.interfaces import IStaticURLInfo from zope.interface.verify import verifyClass verifyClass(IStaticURLInfo, self._getTargetClass()) def test_verifyObject(self): from pyramid.interfaces import IStaticURLInfo from zope.interface.verify import verifyObject verifyObject(IStaticURLInfo, self._makeOne()) def test_generate_missing(self): inst = self._makeOne() request = self._makeRequest() self.assertRaises(ValueError, inst.generate, 'path', request) def test_generate_registration_miss(self): inst = self._makeOne() inst.registrations = [ (None, 'spec', 'route_name'), ('http://example.com/foo/', 'package:path/', None)] request = self._makeRequest() result = inst.generate('package:path/abc', request) self.assertEqual(result, 'http://example.com/foo/abc') def test_generate_slash_in_name1(self): inst = self._makeOne() inst.registrations = [('http://example.com/foo/', 'package:path/', None)] request = self._makeRequest() result = inst.generate('package:path/abc', request) self.assertEqual(result, 'http://example.com/foo/abc') def test_generate_slash_in_name2(self): inst = self._makeOne() inst.registrations = [('http://example.com/foo/', 'package:path/', None)] request = self._makeRequest() result = inst.generate('package:path/', request) self.assertEqual(result, 'http://example.com/foo/') def test_generate_quoting(self): from pyramid.interfaces import IStaticURLInfo config = testing.setUp() try: config.add_static_view('images', path='mypkg:templates') request = testing.DummyRequest() request.registry = config.registry inst = config.registry.getUtility(IStaticURLInfo) result = inst.generate('mypkg:templates/foo%2Fbar', request) self.assertEqual(result, 'http://example.com/images/foo%252Fbar') finally: testing.tearDown() def test_generate_route_url(self): inst = self._makeOne() inst.registrations = [(None, 'package:path/', '__viewname/')] def route_url(n, **kw): self.assertEqual(n, '__viewname/') self.assertEqual(kw, {'subpath':'abc', 'a':1}) return 'url' request = self._makeRequest() request.route_url = route_url result = inst.generate('package:path/abc', request, a=1) self.assertEqual(result, 'url') def test_generate_url_unquoted_local(self): inst = self._makeOne() inst.registrations = [(None, 'package:path/', '__viewname/')] def route_url(n, **kw): self.assertEqual(n, '__viewname/') self.assertEqual(kw, {'subpath':'abc def', 'a':1}) return 'url' request = self._makeRequest() request.route_url = route_url result = inst.generate('package:path/abc def', request, a=1) self.assertEqual(result, 'url') def test_generate_url_quoted_remote(self): inst = self._makeOne() inst.registrations = [('http://example.com/', 'package:path/', None)] request = self._makeRequest() result = inst.generate('package:path/abc def', request, a=1) self.assertEqual(result, 'http://example.com/abc%20def') def test_generate_url_with_custom_query(self): inst = self._makeOne() registrations = [('http://example.com/', 'package:path/', None)] inst.registrations = registrations request = self._makeRequest() result = inst.generate('package:path/abc def', request, a=1, _query='(openlayers)') self.assertEqual(result, 'http://example.com/abc%20def?(openlayers)') def test_generate_url_with_custom_anchor(self): inst = self._makeOne() inst.registrations = [('http://example.com/', 'package:path/', None)] request = self._makeRequest() uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = inst.generate('package:path/abc def', request, a=1, _anchor=uc) self.assertEqual(result, 'http://example.com/abc%20def#La%20Pe%C3%B1a') def test_generate_url_cachebust(self): def cachebust(request, subpath, kw): kw['foo'] = 'bar' return 'foo' + '/' + subpath, kw inst = self._makeOne() inst.registrations = [(None, 'package:path/', '__viewname')] inst.cache_busters = [('package:path/', cachebust, False)] request = self._makeRequest() called = [False] def route_url(n, **kw): called[0] = True self.assertEqual(n, '__viewname') self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar', 'pathspec': 'package:path/abc', 'rawspec': 'package:path/abc'}) request.route_url = route_url inst.generate('package:path/abc', request) self.assertTrue(called[0]) def test_generate_url_cachebust_abspath(self): here = os.path.dirname(__file__) + os.sep def cachebust(pathspec, subpath, kw): kw['foo'] = 'bar' return 'foo' + '/' + subpath, kw inst = self._makeOne() inst.registrations = [(None, here, '__viewname')] inst.cache_busters = [(here, cachebust, False)] request = self._makeRequest() called = [False] def route_url(n, **kw): called[0] = True self.assertEqual(n, '__viewname') self.assertEqual(kw, {'subpath': 'foo/abc', 'foo': 'bar', 'pathspec': here + 'abc', 'rawspec': here + 'abc'}) request.route_url = route_url inst.generate(here + 'abc', request) self.assertTrue(called[0]) def test_generate_url_cachebust_nomatch(self): def fake_cb(*a, **kw): raise AssertionError inst = self._makeOne() inst.registrations = [(None, 'package:path/', '__viewname')] inst.cache_busters = [('package:path2/', fake_cb, False)] request = self._makeRequest() called = [False] def route_url(n, **kw): called[0] = True self.assertEqual(n, '__viewname') self.assertEqual(kw, {'subpath': 'abc', 'pathspec': 'package:path/abc', 'rawspec': 'package:path/abc'}) request.route_url = route_url inst.generate('package:path/abc', request) self.assertTrue(called[0]) def test_generate_url_cachebust_with_overrides(self): config = testing.setUp() try: request = testing.DummyRequest() config.add_static_view('static', 'path') config.override_asset( 'pyramid.tests.test_config:path/', 'pyramid.tests.test_config:other_path/') def cb(val): def cb_(request, subpath, kw): kw['_query'] = {'x': val} return subpath, kw return cb_ config.add_cache_buster('path', cb('foo')) result = request.static_url('path/foo.png') self.assertEqual(result, 'http://example.com/static/foo.png?x=foo') config.add_cache_buster('other_path', cb('bar'), explicit=True) result = request.static_url('path/foo.png') self.assertEqual(result, 'http://example.com/static/foo.png?x=bar') finally: testing.tearDown() def test_add_already_exists(self): config = DummyConfig() inst = self._makeOne() inst.registrations = [('http://example.com/', 'package:path/', None)] inst.add(config, 'http://example.com', 'anotherpackage:path') expected = [('http://example.com/', 'anotherpackage:path/', None)] self.assertEqual(inst.registrations, expected) def test_add_package_root(self): config = DummyConfig() inst = self._makeOne() inst.add(config, 'http://example.com', 'package:') expected = [('http://example.com/', 'package:', None)] self.assertEqual(inst.registrations, expected) def test_add_url_withendslash(self): config = DummyConfig() inst = self._makeOne() inst.add(config, 'http://example.com/', 'anotherpackage:path') expected = [('http://example.com/', 'anotherpackage:path/', None)] self.assertEqual(inst.registrations, expected) def test_add_url_noendslash(self): config = DummyConfig() inst = self._makeOne() inst.add(config, 'http://example.com', 'anotherpackage:path') expected = [('http://example.com/', 'anotherpackage:path/', None)] self.assertEqual(inst.registrations, expected) def test_add_url_noscheme(self): config = DummyConfig() inst = self._makeOne() inst.add(config, '//example.com', 'anotherpackage:path') expected = [('//example.com/', 'anotherpackage:path/', None)] self.assertEqual(inst.registrations, expected) def test_add_viewname(self): from pyramid.security import NO_PERMISSION_REQUIRED from pyramid.static import static_view config = DummyConfig() inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1) expected = [(None, 'anotherpackage:path/', '__view/')] self.assertEqual(inst.registrations, expected) self.assertEqual(config.route_args, ('__view/', 'view/*subpath')) self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED) self.assertEqual(config.view_kw['view'].__class__, static_view) def test_add_viewname_with_route_prefix(self): config = DummyConfig() config.route_prefix = '/abc' inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path',) expected = [(None, 'anotherpackage:path/', '__/abc/view/')] self.assertEqual(inst.registrations, expected) self.assertEqual(config.route_args, ('__/abc/view/', 'view/*subpath')) def test_add_viewname_with_permission(self): config = DummyConfig() inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1, permission='abc') self.assertEqual(config.view_kw['permission'], 'abc') def test_add_viewname_with_context(self): config = DummyConfig() inst = self._makeOne() 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 = DummyConfig() inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1, for_=DummyContext) self.assertEqual(config.view_kw['context'], DummyContext) def test_add_viewname_with_renderer(self): config = DummyConfig() inst = self._makeOne() inst.add(config, 'view', 'anotherpackage:path', cache_max_age=1, renderer='mypackage:templates/index.pt') self.assertEqual(config.view_kw['renderer'], 'mypackage:templates/index.pt') def test_add_cachebust_prevented(self): config = DummyConfig() config.registry.settings['pyramid.prevent_cachebust'] = True inst = self._makeOne() cachebust = DummyCacheBuster('foo') inst.add_cache_buster(config, 'mypackage:path', cachebust) self.assertEqual(inst.cache_busters, []) def test_add_cachebuster(self): config = DummyConfig() inst = self._makeOne() inst.add_cache_buster(config, 'mypackage:path', DummyCacheBuster('foo')) cachebust = inst.cache_busters[-1][1] subpath, kw = cachebust(None, 'some/path', {}) self.assertEqual(subpath, 'some/path') self.assertEqual(kw['x'], 'foo') def test_add_cachebuster_abspath(self): here = os.path.dirname(__file__) config = DummyConfig() inst = self._makeOne() cb = DummyCacheBuster('foo') inst.add_cache_buster(config, here, cb) self.assertEqual(inst.cache_busters, [(here + '/', cb, False)]) def test_add_cachebuster_overwrite(self): config = DummyConfig() inst = self._makeOne() cb1 = DummyCacheBuster('foo') cb2 = DummyCacheBuster('bar') inst.add_cache_buster(config, 'mypackage:path/', cb1) inst.add_cache_buster(config, 'mypackage:path', cb2) self.assertEqual(inst.cache_busters, [('mypackage:path/', cb2, False)]) def test_add_cachebuster_overwrite_explicit(self): config = DummyConfig() inst = self._makeOne() cb1 = DummyCacheBuster('foo') cb2 = DummyCacheBuster('bar') inst.add_cache_buster(config, 'mypackage:path/', cb1) inst.add_cache_buster(config, 'mypackage:path', cb2, True) self.assertEqual(inst.cache_busters, [('mypackage:path/', cb1, False), ('mypackage:path/', cb2, True)]) def test_add_cachebuster_for_more_specific_path(self): config = DummyConfig() inst = self._makeOne() cb1 = DummyCacheBuster('foo') cb2 = DummyCacheBuster('bar') cb3 = DummyCacheBuster('baz') cb4 = DummyCacheBuster('xyz') cb5 = DummyCacheBuster('w') inst.add_cache_buster(config, 'mypackage:path', cb1) inst.add_cache_buster(config, 'mypackage:path/sub', cb2, True) inst.add_cache_buster(config, 'mypackage:path/sub/other', cb3) inst.add_cache_buster(config, 'mypackage:path/sub/other', cb4, True) inst.add_cache_buster(config, 'mypackage:path/sub/less', cb5, True) self.assertEqual( inst.cache_busters, [('mypackage:path/', cb1, False), ('mypackage:path/sub/other/', cb3, False), ('mypackage:path/sub/', cb2, True), ('mypackage:path/sub/less/', cb5, True), ('mypackage:path/sub/other/', cb4, True)]) class Test_view_description(unittest.TestCase): def _callFUT(self, view): from pyramid.config.views import view_description return view_description(view) def test_with_text(self): def view(): pass view.__text__ = 'some text' result = self._callFUT(view) self.assertEqual(result, 'some text') def test_without_text(self): def view(): pass result = self._callFUT(view) self.assertEqual(result, 'function pyramid.tests.test_config.test_views.view') class DummyRegistry: utility = None def __init__(self): self.settings = {} def queryUtility(self, type_or_iface, name=None, default=None): return self.utility or default from zope.interface import implementer from pyramid.interfaces import ( IResponse, IRequest, ) @implementer(IResponse) class DummyResponse(object): content_type = None default_content_type = None body = None class DummyRequest: subpath = () matchdict = None request_iface = IRequest def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ self.params = {} self.cookies = {} self.response = DummyResponse() class DummyContext: pass class DummyAccept(object): def __init__(self, *matches): self.matches = list(matches) def best_match(self, offered): if self.matches: for match in self.matches: if match in offered: self.matches.remove(match) return match def __contains__(self, val): return val in self.matches class DummyLogger: def __init__(self): self.messages = [] def info(self, msg): self.messages.append(msg) warn = info debug = info class DummySecurityPolicy: def __init__(self, permitted=True): self.permitted = permitted def effective_principals(self, request): return [] def permits(self, context, principals, permission): return self.permitted class DummyConfig: def __init__(self): self.registry = DummyRegistry() route_prefix = '' def add_route(self, *args, **kw): self.route_args = args self.route_kw = kw def add_view(self, *args, **kw): self.view_args = args self.view_kw = kw def action(self, discriminator, callable, introspectables=()): callable() def introspectable(self, *arg): return {} from zope.interface import implementer from pyramid.interfaces import IMultiView @implementer(IMultiView) class DummyMultiView: def __init__(self): self.views = [] self.name = 'name' def add(self, view, order, accept=None, phash=None): self.views.append((view, accept, phash)) def __call__(self, context, request): return 'OK1' def __permitted__(self, context, request): """ """ class DummyCacheBuster(object): def __init__(self, token): self.token = token def __call__(self, request, subpath, kw): kw['x'] = self.token return subpath, kw def parse_httpdate(s): import datetime # cannot use %Z, must use literal GMT; Jython honors timezone # but CPython does not return datetime.datetime.strptime(s, "%a, %d %b %Y %H:%M:%S GMT") def assert_similar_datetime(one, two): for attr in ('year', 'month', 'day', 'hour', 'minute'): one_attr = getattr(one, attr) two_attr = getattr(two, attr) if not one_attr == two_attr: # pragma: no cover raise AssertionError('%r != %r in %s' % (one_attr, two_attr, attr)) class DummyStaticURLInfo: def __init__(self): self.added = [] def add(self, config, name, spec, **kw): self.added.append((config, name, spec, kw)) class DummyViewDefaultsClass(object): __view_defaults__ = { 'containment':'pyramid.tests.test_config.IDummy' } def __init__(self, request): pass def __call__(self): return 'OK' class DummyPredicate(object): def __init__(self, val, config): self.val = val def text(self): return 'dummy' phash = text class DummyIntrospector(object): def __init__(self, getval=None): self.related = [] self.introspectables = [] self.getval = getval def add(self, introspectable): self.introspectables.append(introspectable) def get(self, name, discrim): return self.getval def relate(self, a, b): self.related.append((a, b)) pyramid-1.6/pyramid/tests/test_decorator.py0000644000076500000240000000203612524266531021763 0ustar michaelstaff00000000000000import unittest class TestReify(unittest.TestCase): def _makeOne(self, wrapped): from pyramid.decorator import reify return reify(wrapped) def test___get__withinst(self): def wrapped(inst): return 'a' decorator = self._makeOne(wrapped) inst = Dummy() result = decorator.__get__(inst) self.assertEqual(result, 'a') self.assertEqual(inst.__dict__['wrapped'], 'a') def test___get__noinst(self): def wrapped(inst): return 'a' # pragma: no cover decorator = self._makeOne(wrapped) result = decorator.__get__(None) self.assertEqual(result, decorator) def test_dunder_attrs_copied(self): from pyramid.util import viewdefaults decorator = self._makeOne(viewdefaults) self.assertEqual(decorator.__doc__, viewdefaults.__doc__) self.assertEqual(decorator.__name__, viewdefaults.__name__) self.assertEqual(decorator.__module__, viewdefaults.__module__) class Dummy(object): pass pyramid-1.6/pyramid/tests/test_docs.py0000644000076500000240000000223412234375161020727 0ustar michaelstaff00000000000000import unittest if 0: # no released version of manuel actually works with :lineno: # settings yet class ManuelDocsCase(unittest.TestCase): def __new__(self, test): return getattr(self, test)() @classmethod def test_docs(cls): import os import pkg_resources import manuel.testing import manuel.codeblock import manuel.capture import manuel.ignore m = manuel.ignore.Manuel() m += manuel.codeblock.Manuel() m += manuel.capture.Manuel() docs = [] egg_path = pkg_resources.get_distribution('pyramid').location path = os.path.join(egg_path, 'docs') for root, dirs, files in os.walk(path): for ignore in ('.svn', '.build', '.hg', '.git', 'CVS'): if ignore in dirs: dirs.remove(ignore) for filename in files: if filename.endswith('.rst'): docs.append(os.path.join(root, filename)) print(path) return manuel.testing.TestSuite(m, *docs) pyramid-1.6/pyramid/tests/test_encode.py0000644000076500000240000000503612520062551021231 0ustar michaelstaff00000000000000import unittest from pyramid.compat import ( text_, native_, ) class UrlEncodeTests(unittest.TestCase): def _callFUT(self, query, doseq=False): from pyramid.encode import urlencode return urlencode(query, doseq) def test_ascii_only(self): result = self._callFUT([('a',1), ('b',2)]) self.assertEqual(result, 'a=1&b=2') def test_unicode_key(self): la = text_(b'LaPe\xc3\xb1a', 'utf-8') result = self._callFUT([(la, 1), ('b',2)]) self.assertEqual(result, 'LaPe%C3%B1a=1&b=2') def test_unicode_val_single(self): la = text_(b'LaPe\xc3\xb1a', 'utf-8') result = self._callFUT([('a', la), ('b',2)]) self.assertEqual(result, 'a=LaPe%C3%B1a&b=2') def test_unicode_val_multiple(self): la = [text_(b'LaPe\xc3\xb1a', 'utf-8')] * 2 result = self._callFUT([('a', la), ('b',2)], doseq=True) self.assertEqual(result, 'a=LaPe%C3%B1a&a=LaPe%C3%B1a&b=2') def test_int_val_multiple(self): s = [1, 2] result = self._callFUT([('a', s)], doseq=True) self.assertEqual(result, 'a=1&a=2') def test_with_spaces(self): result = self._callFUT([('a', '123 456')], doseq=True) self.assertEqual(result, 'a=123+456') def test_dict(self): result = self._callFUT({'a':1}) self.assertEqual(result, 'a=1') def test_None_value(self): result = self._callFUT([('a', None)]) self.assertEqual(result, 'a=') def test_None_value_with_prefix(self): result = self._callFUT([('a', '1'), ('b', None)]) self.assertEqual(result, 'a=1&b=') def test_None_value_with_prefix_values(self): result = self._callFUT([('a', '1'), ('b', None), ('c', None)]) self.assertEqual(result, 'a=1&b=&c=') class URLQuoteTests(unittest.TestCase): def _callFUT(self, val, safe=''): from pyramid.encode import url_quote return url_quote(val, safe) def test_it_bytes(self): la = b'La/Pe\xc3\xb1a' result = self._callFUT(la) self.assertEqual(result, 'La%2FPe%C3%B1a') def test_it_native(self): la = native_(b'La/Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) self.assertEqual(result, 'La%2FPe%C3%B1a') def test_it_with_safe(self): la = b'La/Pe\xc3\xb1a' result = self._callFUT(la, '/') self.assertEqual(result, 'La/Pe%C3%B1a') def test_it_with_nonstr_nonbinary(self): la = None result = self._callFUT(la, '/') self.assertEqual(result, 'None') pyramid-1.6/pyramid/tests/test_events.py0000644000076500000240000002362512517346416021317 0ustar michaelstaff00000000000000import unittest from pyramid import testing class NewRequestEventTests(unittest.TestCase): def _getTargetClass(self): from pyramid.events import NewRequest return NewRequest def _makeOne(self, request): return self._getTargetClass()(request) def test_class_conforms_to_INewRequest(self): from pyramid.interfaces import INewRequest from zope.interface.verify import verifyClass klass = self._getTargetClass() verifyClass(INewRequest, klass) def test_instance_conforms_to_INewRequest(self): from pyramid.interfaces import INewRequest from zope.interface.verify import verifyObject request = DummyRequest() inst = self._makeOne(request) verifyObject(INewRequest, inst) def test_ctor(self): request = DummyRequest() inst = self._makeOne(request) self.assertEqual(inst.request, request) class NewResponseEventTests(unittest.TestCase): def _getTargetClass(self): from pyramid.events import NewResponse return NewResponse def _makeOne(self, request, response): return self._getTargetClass()(request, response) def test_class_conforms_to_INewResponse(self): from pyramid.interfaces import INewResponse from zope.interface.verify import verifyClass klass = self._getTargetClass() verifyClass(INewResponse, klass) def test_instance_conforms_to_INewResponse(self): from pyramid.interfaces import INewResponse from zope.interface.verify import verifyObject request = DummyRequest() response = DummyResponse() inst = self._makeOne(request, response) verifyObject(INewResponse, inst) def test_ctor(self): request = DummyRequest() response = DummyResponse() inst = self._makeOne(request, response) self.assertEqual(inst.request, request) self.assertEqual(inst.response, response) class ApplicationCreatedEventTests(unittest.TestCase): def _getTargetClass(self): from pyramid.events import ApplicationCreated return ApplicationCreated def _makeOne(self, context=object()): return self._getTargetClass()(context) def test_class_conforms_to_IApplicationCreated(self): from pyramid.interfaces import IApplicationCreated from zope.interface.verify import verifyClass verifyClass(IApplicationCreated, self._getTargetClass()) def test_object_conforms_to_IApplicationCreated(self): from pyramid.interfaces import IApplicationCreated from zope.interface.verify import verifyObject verifyObject(IApplicationCreated, self._makeOne()) class WSGIApplicationCreatedEventTests(ApplicationCreatedEventTests): def _getTargetClass(self): from pyramid.events import WSGIApplicationCreatedEvent return WSGIApplicationCreatedEvent def test_class_conforms_to_IWSGIApplicationCreatedEvent(self): from pyramid.interfaces import IWSGIApplicationCreatedEvent from zope.interface.verify import verifyClass verifyClass(IWSGIApplicationCreatedEvent, self._getTargetClass()) def test_object_conforms_to_IWSGIApplicationCreatedEvent(self): from pyramid.interfaces import IWSGIApplicationCreatedEvent from zope.interface.verify import verifyObject verifyObject(IWSGIApplicationCreatedEvent, self._makeOne()) class ContextFoundEventTests(unittest.TestCase): def _getTargetClass(self): from pyramid.events import ContextFound return ContextFound def _makeOne(self, request=None): if request is None: request = DummyRequest() return self._getTargetClass()(request) def test_class_conforms_to_IContextFound(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IContextFound verifyClass(IContextFound, self._getTargetClass()) def test_instance_conforms_to_IContextFound(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IContextFound verifyObject(IContextFound, self._makeOne()) class AfterTraversalEventTests(ContextFoundEventTests): def _getTargetClass(self): from pyramid.events import AfterTraversal return AfterTraversal def test_class_conforms_to_IAfterTraversal(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IAfterTraversal verifyClass(IAfterTraversal, self._getTargetClass()) def test_instance_conforms_to_IAfterTraversal(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IAfterTraversal verifyObject(IAfterTraversal, self._makeOne()) class TestSubscriber(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self, *ifaces, **predicates): from pyramid.events import subscriber return subscriber(*ifaces, **predicates) def test_register_single(self): from zope.interface import Interface class IFoo(Interface): pass class IBar(Interface): pass dec = self._makeOne(IFoo) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.subscribed, [(foo, IFoo)]) def test_register_multi(self): from zope.interface import Interface class IFoo(Interface): pass class IBar(Interface): pass dec = self._makeOne(IFoo, IBar) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.subscribed, [(foo, IFoo), (foo, IBar)]) def test_register_none_means_all(self): from zope.interface import Interface dec = self._makeOne() def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.subscribed, [(foo, Interface)]) def test_register_objectevent(self): from zope.interface import Interface class IFoo(Interface): pass class IBar(Interface): pass dec = self._makeOne([IFoo, IBar]) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.subscribed, [(foo, [IFoo, IBar])]) def test___call__(self): dec = self._makeOne() dummy_venusian = DummyVenusian() dec.venusian = dummy_venusian def foo(): pass dec(foo) self.assertEqual(dummy_venusian.attached, [(foo, dec.register, 'pyramid')]) def test_regsister_with_predicates(self): from zope.interface import Interface dec = self._makeOne(a=1) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.subscribed, [(foo, Interface, {'a':1})]) class TestBeforeRender(unittest.TestCase): def _makeOne(self, system, val=None): from pyramid.events import BeforeRender return BeforeRender(system, val) def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IBeforeRender event = self._makeOne({}) verifyObject(IBeforeRender, event) def test_setitem_success(self): event = self._makeOne({}) event['a'] = 1 self.assertEqual(event, {'a':1}) def test_setdefault_fail(self): event = self._makeOne({}) result = event.setdefault('a', 1) self.assertEqual(result, 1) self.assertEqual(event, {'a':1}) def test_setdefault_success(self): event = self._makeOne({}) event['a'] = 1 result = event.setdefault('a', 2) self.assertEqual(result, 1) self.assertEqual(event, {'a':1}) def test_update_success(self): event = self._makeOne({'a':1}) event.update({'b':2}) self.assertEqual(event, {'a':1, 'b':2}) def test__contains__True(self): system = {'a':1} event = self._makeOne(system) self.assertTrue('a' in event) def test__contains__False(self): system = {} event = self._makeOne(system) self.assertFalse('a' in event) def test__getitem__success(self): system = {'a':1} event = self._makeOne(system) self.assertEqual(event['a'], 1) def test__getitem__fail(self): system = {} event = self._makeOne(system) self.assertRaises(KeyError, event.__getitem__, 'a') def test_get_success(self): system = {'a':1} event = self._makeOne(system) self.assertEqual(event.get('a'), 1) def test_get_fail(self): system = {} event = self._makeOne(system) self.assertEqual(event.get('a'), None) def test_rendering_val(self): system = {} val = {} event = self._makeOne(system, val) self.assertTrue(event.rendering_val is val) class DummyConfigurator(object): def __init__(self): self.subscribed = [] def add_subscriber(self, wrapped, ifaces, **predicates): if not predicates: self.subscribed.append((wrapped, ifaces)) else: self.subscribed.append((wrapped, ifaces, predicates)) class DummyRegistry(object): pass class DummyVenusian(object): def __init__(self): self.attached = [] def attach(self, wrapped, fn, category=None): self.attached.append((wrapped, fn, category)) class Dummy: pass class DummyRequest: pass class DummyResponse: pass pyramid-1.6/pyramid/tests/test_exceptions.py0000644000076500000240000000627712520062551022165 0ustar michaelstaff00000000000000import unittest class TestBWCompat(unittest.TestCase): def test_bwcompat_notfound(self): from pyramid.exceptions import NotFound as one from pyramid.httpexceptions import HTTPNotFound as two self.assertTrue(one is two) def test_bwcompat_forbidden(self): from pyramid.exceptions import Forbidden as one from pyramid.httpexceptions import HTTPForbidden as two self.assertTrue(one is two) class TestBadCSRFToken(unittest.TestCase): def test_response_equivalence(self): from pyramid.exceptions import BadCSRFToken from pyramid.httpexceptions import HTTPBadRequest self.assertTrue(isinstance(BadCSRFToken(), HTTPBadRequest)) class TestNotFound(unittest.TestCase): def _makeOne(self, message): from pyramid.exceptions import NotFound return NotFound(message) def test_it(self): from pyramid.interfaces import IExceptionResponse e = self._makeOne('notfound') self.assertTrue(IExceptionResponse.providedBy(e)) self.assertEqual(e.status, '404 Not Found') self.assertEqual(e.message, 'notfound') def test_response_equivalence(self): from pyramid.exceptions import NotFound from pyramid.httpexceptions import HTTPNotFound self.assertTrue(NotFound is HTTPNotFound) class TestForbidden(unittest.TestCase): def _makeOne(self, message): from pyramid.exceptions import Forbidden return Forbidden(message) def test_it(self): from pyramid.interfaces import IExceptionResponse e = self._makeOne('forbidden') self.assertTrue(IExceptionResponse.providedBy(e)) self.assertEqual(e.status, '403 Forbidden') self.assertEqual(e.message, 'forbidden') def test_response_equivalence(self): from pyramid.exceptions import Forbidden from pyramid.httpexceptions import HTTPForbidden self.assertTrue(Forbidden is HTTPForbidden) class TestConfigurationConflictError(unittest.TestCase): def _makeOne(self, conflicts): from pyramid.exceptions import ConfigurationConflictError return ConfigurationConflictError(conflicts) def test_str(self): conflicts = {'a':('1', '2', '3'), 'b':('4', '5', '6')} exc = self._makeOne(conflicts) self.assertEqual(str(exc), """\ Conflicting configuration actions For: a 1 2 3 For: b 4 5 6""") class TestConfigurationExecutionError(unittest.TestCase): def _makeOne(self, etype, evalue, info): from pyramid.exceptions import ConfigurationExecutionError return ConfigurationExecutionError(etype, evalue, info) def test_str(self): exc = self._makeOne('etype', 'evalue', 'info') self.assertEqual(str(exc), 'etype: evalue\n in:\n info') class TestCyclicDependencyError(unittest.TestCase): def _makeOne(self, cycles): from pyramid.exceptions import CyclicDependencyError return CyclicDependencyError(cycles) def test___str__(self): exc = self._makeOne({'a':['c', 'd'], 'c':['a']}) result = str(exc) self.assertTrue("'a' sorts before ['c', 'd']" in result) self.assertTrue("'c' sorts before ['a']" in result) pyramid-1.6/pyramid/tests/test_httpexceptions.py0000644000076500000240000003273012575217552023073 0ustar michaelstaff00000000000000import unittest from pyramid.compat import ( bytes_, text_, ) class Test_exception_response(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.httpexceptions import exception_response return exception_response(*arg, **kw) def test_status_400(self): from pyramid.httpexceptions import HTTPBadRequest self.assertTrue(isinstance(self._callFUT(400), HTTPBadRequest)) def test_status_404(self): from pyramid.httpexceptions import HTTPNotFound self.assertTrue(isinstance(self._callFUT(404), HTTPNotFound)) def test_status_500(self): from pyramid.httpexceptions import HTTPInternalServerError self.assertTrue(isinstance(self._callFUT(500), HTTPInternalServerError)) def test_status_201(self): from pyramid.httpexceptions import HTTPCreated self.assertTrue(isinstance(self._callFUT(201), HTTPCreated)) def test_extra_kw(self): resp = self._callFUT(404, headers=[('abc', 'def')]) self.assertEqual(resp.headers['abc'], 'def') class Test_default_exceptionresponse_view(unittest.TestCase): def _callFUT(self, context, request): from pyramid.httpexceptions import default_exceptionresponse_view return default_exceptionresponse_view(context, request) def test_call_with_exception(self): context = Exception() result = self._callFUT(context, None) self.assertEqual(result, context) def test_call_with_nonexception(self): request = DummyRequest() context = Exception() request.exception = context result = self._callFUT(None, request) self.assertEqual(result, context) class Test__no_escape(unittest.TestCase): def _callFUT(self, val): from pyramid.httpexceptions import _no_escape return _no_escape(val) def test_null(self): self.assertEqual(self._callFUT(None), '') def test_not_basestring(self): self.assertEqual(self._callFUT(42), '42') def test_unicode(self): class DummyUnicodeObject(object): def __unicode__(self): return text_('42') duo = DummyUnicodeObject() self.assertEqual(self._callFUT(duo), text_('42')) class TestHTTPException(unittest.TestCase): def _getTargetClass(self): from pyramid.httpexceptions import HTTPException return HTTPException def _getTargetSubclass(self, code='200', title='OK', explanation='explanation', empty_body=False): cls = self._getTargetClass() class Subclass(cls): pass Subclass.empty_body = empty_body Subclass.code = code Subclass.title = title Subclass.explanation = explanation return Subclass def _makeOne(self, *arg, **kw): cls = self._getTargetClass() return cls(*arg, **kw) def test_implements_IResponse(self): from pyramid.interfaces import IResponse cls = self._getTargetClass() self.assertTrue(IResponse.implementedBy(cls)) def test_provides_IResponse(self): from pyramid.interfaces import IResponse inst = self._getTargetClass()() self.assertTrue(IResponse.providedBy(inst)) def test_implements_IExceptionResponse(self): from pyramid.interfaces import IExceptionResponse cls = self._getTargetClass() self.assertTrue(IExceptionResponse.implementedBy(cls)) def test_provides_IExceptionResponse(self): from pyramid.interfaces import IExceptionResponse inst = self._getTargetClass()() self.assertTrue(IExceptionResponse.providedBy(inst)) def test_ctor_sets_detail(self): exc = self._makeOne('message') self.assertEqual(exc.detail, 'message') def test_ctor_sets_comment(self): exc = self._makeOne(comment='comment') self.assertEqual(exc.comment, 'comment') def test_ctor_calls_Exception_ctor(self): exc = self._makeOne('message') self.assertEqual(exc.message, 'message') def test_ctor_calls_Response_ctor(self): exc = self._makeOne('message') self.assertEqual(exc.status, '520 Unknown Error') def test_ctor_extends_headers(self): exc = self._makeOne(headers=[('X-Foo', 'foo')]) self.assertEqual(exc.headers.get('X-Foo'), 'foo') def test_ctor_sets_body_template_obj(self): exc = self._makeOne(body_template='${foo}') self.assertEqual( exc.body_template_obj.substitute({'foo':'foo'}), 'foo') def test_ctor_with_empty_body(self): cls = self._getTargetSubclass(empty_body=True) exc = cls() self.assertEqual(exc.content_type, None) self.assertEqual(exc.content_length, None) def test_ctor_with_body_doesnt_set_default_app_iter(self): exc = self._makeOne(body=b'123') self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_unicode_body_doesnt_set_default_app_iter(self): exc = self._makeOne(unicode_body=text_('123')) self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_app_iter_doesnt_set_default_app_iter(self): exc = self._makeOne(app_iter=[b'123']) self.assertEqual(exc.app_iter, [b'123']) def test_ctor_with_body_sets_default_app_iter_html(self): cls = self._getTargetSubclass() exc = cls('detail') environ = _makeEnviron() environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] self.assertTrue(body.startswith(b'' in body) def test__default_app_iter_with_comment_html2(self): cls = self._getTargetSubclass() exc = cls(comment='comment & comment') environ = _makeEnviron() environ['HTTP_ACCEPT'] = 'text/html' start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] self.assertTrue(b'' in body) def test_custom_body_template(self): cls = self._getTargetSubclass() exc = cls(body_template='${REQUEST_METHOD}') environ = _makeEnviron() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] self.assertEqual(body, b'200 OK\n\nGET') def test_custom_body_template_with_custom_variable_doesnt_choke(self): cls = self._getTargetSubclass() exc = cls(body_template='${REQUEST_METHOD}') environ = _makeEnviron() class Choke(object): def __str__(self): raise ValueError environ['gardentheory.user'] = Choke() start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] self.assertEqual(body, b'200 OK\n\nGET') def test_body_template_unicode(self): cls = self._getTargetSubclass() la = text_(b'/La Pe\xc3\xb1a', 'utf-8') environ = _makeEnviron(unicodeval=la) exc = cls(body_template='${unicodeval}') start_response = DummyStartResponse() body = list(exc(environ, start_response))[0] self.assertEqual(body, b'200 OK\n\n/La Pe\xc3\xb1a') class TestRenderAllExceptionsWithoutArguments(unittest.TestCase): def _doit(self, content_type): from pyramid.httpexceptions import status_map L = [] self.assertTrue(status_map) for v in status_map.values(): environ = _makeEnviron() start_response = DummyStartResponse() exc = v() exc.content_type = content_type result = list(exc(environ, start_response))[0] if exc.empty_body: self.assertEqual(result, b'') else: self.assertTrue(bytes_(exc.status) in result) L.append(result) self.assertEqual(len(L), len(status_map)) def test_it_plain(self): self._doit('text/plain') def test_it_html(self): self._doit('text/html') class Test_HTTPMove(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.httpexceptions import _HTTPMove return _HTTPMove(*arg, **kw) def test_it_location_none_valueerrors(self): # Constructing a HTTPMove instance with location=None should # throw a ValueError from __init__ so that a more-confusing # exception won't be thrown later from .prepare(environ) self.assertRaises(ValueError, self._makeOne, location=None) def test_it_location_not_passed(self): exc = self._makeOne() self.assertEqual(exc.location, '') def test_it_location_passed(self): exc = self._makeOne(location='foo') self.assertEqual(exc.location, 'foo') def test_it_location_firstarg(self): exc = self._makeOne('foo') self.assertEqual(exc.location, 'foo') def test_it_call_with_default_body_tmpl(self): exc = self._makeOne(location='foo') environ = _makeEnviron() start_response = DummyStartResponse() app_iter = exc(environ, start_response) self.assertEqual(app_iter[0], (b'520 Unknown Error\n\nThe resource has been moved to foo; ' b'you should be redirected automatically.\n\n')) class TestHTTPForbidden(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.httpexceptions import HTTPForbidden return HTTPForbidden(*arg, **kw) def test_it_result_not_passed(self): exc = self._makeOne() self.assertEqual(exc.result, None) def test_it_result_passed(self): exc = self._makeOne(result='foo') self.assertEqual(exc.result, 'foo') class TestHTTPMethodNotAllowed(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.httpexceptions import HTTPMethodNotAllowed return HTTPMethodNotAllowed(*arg, **kw) def test_it_with_default_body_tmpl(self): exc = self._makeOne() environ = _makeEnviron() start_response = DummyStartResponse() app_iter = exc(environ, start_response) self.assertEqual(app_iter[0], (b'405 Method Not Allowed\n\nThe method GET is not ' b'allowed for this resource. \n\n\n')) class DummyRequest(object): exception = None class DummyStartResponse(object): def __call__(self, status, headerlist): self.status = status self.headerlist = headerlist def _makeEnviron(**kw): environ = {'REQUEST_METHOD':'GET', 'wsgi.url_scheme':'http', 'SERVER_NAME':'localhost', 'SERVER_PORT':'80'} environ.update(kw) return environ pyramid-1.6/pyramid/tests/test_i18n.py0000644000076500000240000004066512520062551020562 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- # import os here = os.path.dirname(__file__) localedir = os.path.join(here, 'pkgs', 'localeapp', 'locale') import unittest from pyramid import testing class TestTranslationString(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.i18n import TranslationString return TranslationString(*arg, **kw) def test_it(self): # this is part of the API, we don't actually need to test much more # than that it's importable ts = self._makeOne('a') self.assertEqual(ts, 'a') class TestTranslationStringFactory(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.i18n import TranslationStringFactory return TranslationStringFactory(*arg, **kw) def test_it(self): # this is part of the API, we don't actually need to test much more # than that it's importable factory = self._makeOne('a') self.assertEqual(factory('').domain, 'a') class TestLocalizer(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.i18n import Localizer return Localizer(*arg, **kw) def test_ctor(self): localizer = self._makeOne('en_US', None) self.assertEqual(localizer.locale_name, 'en_US') self.assertEqual(localizer.translations, None) def test_translate(self): translations = DummyTranslations() localizer = self._makeOne(None, translations) self.assertEqual(localizer.translate('123', domain='1', mapping={}), '123') self.assertTrue(localizer.translator) def test_pluralize(self): translations = DummyTranslations() localizer = self._makeOne(None, translations) result = localizer.pluralize('singular', 'plural', 1, domain='1', mapping={}) self.assertEqual(result, 'singular') self.assertTrue(localizer.pluralizer) def test_pluralize_pluralizer_already_added(self): translations = DummyTranslations() localizer = self._makeOne(None, translations) def pluralizer(*arg, **kw): return arg, kw localizer.pluralizer = pluralizer result = localizer.pluralize('singular', 'plural', 1, domain='1', mapping={}) self.assertEqual( result, (('singular', 'plural', 1), {'domain': '1', 'mapping': {}}) ) self.assertTrue(localizer.pluralizer is pluralizer) def test_pluralize_default_translations(self): # test that even without message ids loaded that # "localizer.pluralize" "works" instead of raising an inscrutable # "translations object has no attr 'plural' error; see # see https://github.com/Pylons/pyramid/issues/235 from pyramid.i18n import Translations translations = Translations() translations._catalog = {} localizer = self._makeOne(None, translations) result = localizer.pluralize('singular', 'plural', 2, domain='1', mapping={}) self.assertEqual(result, 'plural') class Test_negotiate_locale_name(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request): from pyramid.i18n import negotiate_locale_name return negotiate_locale_name(request) def _registerImpl(self, impl): from pyramid.threadlocal import get_current_registry registry = get_current_registry() from pyramid.interfaces import ILocaleNegotiator registry.registerUtility(impl, ILocaleNegotiator) def test_no_registry_on_request(self): self._registerImpl(dummy_negotiator) request = DummyRequest() result = self._callFUT(request) self.assertEqual(result, 'bogus') def test_with_registry_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() self._registerImpl(dummy_negotiator) request = DummyRequest() request.registry = registry result = self._callFUT(request) self.assertEqual(result, 'bogus') def test_default_from_settings(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() settings = {'default_locale_name':'settings'} registry.settings = settings request = DummyRequest() request.registry = registry result = self._callFUT(request) self.assertEqual(result, 'settings') def test_use_default_locale_negotiator(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = DummyRequest() request.registry = registry request._LOCALE_ = 'locale' result = self._callFUT(request) self.assertEqual(result, 'locale') def test_default_default(self): request = DummyRequest() result = self._callFUT(request) self.assertEqual(result, 'en') class Test_get_locale_name(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request): from pyramid.i18n import get_locale_name return get_locale_name(request) def test_name_on_request(self): request = DummyRequest() request.locale_name = 'ie' result = self._callFUT(request) self.assertEqual(result, 'ie') class Test_make_localizer(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, locale, tdirs): from pyramid.i18n import make_localizer return make_localizer(locale, tdirs) def test_locale_from_mo(self): from pyramid.i18n import Localizer localedirs = [localedir] locale_name = 'de' result = self._callFUT(locale_name, localedirs) self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Approve', 'deformsite'), 'Genehmigen') self.assertEqual(result.translate('Approve'), 'Approve') self.assertTrue(hasattr(result, 'pluralize')) def test_locale_from_mo_bad_mo(self): from pyramid.i18n import Localizer localedirs = [localedir] locale_name = 'be' result = self._callFUT(locale_name, localedirs) self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Approve', 'deformsite'), 'Approve') def test_locale_from_mo_mo_isdir(self): from pyramid.i18n import Localizer localedirs = [localedir] locale_name = 'gb' result = self._callFUT(locale_name, localedirs) self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Approve', 'deformsite'), 'Approve') def test_territory_fallback(self): from pyramid.i18n import Localizer localedirs = [localedir] locale_name = 'de_DE' result = self._callFUT(locale_name, localedirs) self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Submit', 'deformsite'), 'different') # prefer translations from de_DE locale self.assertEqual(result.translate('Approve', 'deformsite'), 'Genehmigen') # missing from de_DE locale, but in de class Test_get_localizer(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request): from pyramid.i18n import get_localizer return get_localizer(request) def test_it(self): request = DummyRequest() request.localizer = 'localizer' self.assertEqual(self._callFUT(request), 'localizer') class Test_default_locale_negotiator(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request): from pyramid.i18n import default_locale_negotiator return default_locale_negotiator(request) def test_from_none(self): request = DummyRequest() result = self._callFUT(request) self.assertEqual(result, None) def test_from_request_attr(self): request = DummyRequest() request._LOCALE_ = 'foo' result = self._callFUT(request) self.assertEqual(result, 'foo') def test_from_params(self): request = DummyRequest() request.params['_LOCALE_'] = 'foo' result = self._callFUT(request) self.assertEqual(result, 'foo') def test_from_cookies(self): request = DummyRequest() request.cookies['_LOCALE_'] = 'foo' result = self._callFUT(request) self.assertEqual(result, 'foo') class TestTranslations(unittest.TestCase): def _getTargetClass(self): from pyramid.i18n import Translations return Translations def _makeOne(self): messages1 = [ ('foo', 'Voh'), (('foo1', 1), 'Voh1'), ] messages2 = [ ('foo', 'VohD'), (('foo1', 1), 'VohD1'), ] klass = self._getTargetClass() translations1 = klass(None, domain='messages') translations1._catalog = dict(messages1) translations1.plural = lambda *arg: 1 translations2 = klass(None, domain='messages1') translations2._catalog = dict(messages2) translations2.plural = lambda *arg: 1 translations = translations1.add(translations2, merge=False) return translations def test_load_locales_None(self): import gettext klass = self._getTargetClass() result = klass.load(localedir, None, domain=None) self.assertEqual(result.__class__, gettext.NullTranslations) def test_load_domain_None(self): import gettext locales = ['de', 'en'] klass = self._getTargetClass() result = klass.load(localedir, locales, domain=None) self.assertEqual(result.__class__, gettext.NullTranslations) def test_load_found_locale_and_domain(self): locales = ['de', 'en'] klass = self._getTargetClass() result = klass.load(localedir, locales, domain='deformsite') self.assertEqual(result.__class__, klass) def test_load_found_locale_and_domain_locale_is_string(self): locales = 'de' klass = self._getTargetClass() result = klass.load(localedir, locales, domain='deformsite') self.assertEqual(result.__class__, klass) def test___repr__(self): inst = self._makeOne() result = repr(inst) self.assertEqual(result, '') def test_merge_not_gnutranslations(self): inst = self._makeOne() self.assertEqual(inst.merge(None), inst) def test_merge_gnutranslations(self): inst = self._makeOne() inst2 = self._makeOne() inst2._catalog['a'] = 'b' inst.merge(inst2) self.assertEqual(inst._catalog['a'], 'b') def test_merge_gnutranslations_not_translations(self): import gettext t = gettext.GNUTranslations() t._catalog = {'a':'b'} inst = self._makeOne() inst.merge(t) self.assertEqual(inst._catalog['a'], 'b') def test_add_different_domain_merge_true_notexisting(self): inst = self._makeOne() inst2 = self._makeOne() inst2.domain = 'domain2' inst.add(inst2) self.assertEqual(inst._domains['domain2'], inst2) def test_add_different_domain_merge_true_existing(self): inst = self._makeOne() inst2 = self._makeOne() inst3 = self._makeOne() inst2.domain = 'domain2' inst2._catalog['a'] = 'b' inst3.domain = 'domain2' inst._domains['domain2'] = inst3 inst.add(inst2) self.assertEqual(inst._domains['domain2'], inst3) self.assertEqual(inst3._catalog['a'], 'b') def test_add_same_domain_merge_true(self): inst = self._makeOne() inst2 = self._makeOne() inst2._catalog['a'] = 'b' inst.add(inst2) self.assertEqual(inst._catalog['a'], 'b') def test_dgettext(self): t = self._makeOne() self.assertEqual(t.dgettext('messages', 'foo'), 'Voh') self.assertEqual(t.dgettext('messages1', 'foo'), 'VohD') def test_ldgettext(self): t = self._makeOne() self.assertEqual(t.ldgettext('messages', 'foo'), b'Voh') self.assertEqual(t.ldgettext('messages1', 'foo'), b'VohD') def test_dugettext(self): t = self._makeOne() self.assertEqual(t.dugettext('messages', 'foo'), 'Voh') self.assertEqual(t.dugettext('messages1', 'foo'), 'VohD') def test_dngettext(self): t = self._makeOne() self.assertEqual(t.dngettext('messages', 'foo1', 'foos1', 1), 'Voh1') self.assertEqual(t.dngettext('messages1', 'foo1', 'foos1', 1), 'VohD1') def test_ldngettext(self): t = self._makeOne() self.assertEqual(t.ldngettext('messages', 'foo1', 'foos1', 1), b'Voh1') self.assertEqual(t.ldngettext('messages1', 'foo1', 'foos1', 1),b'VohD1') def test_dungettext(self): t = self._makeOne() self.assertEqual(t.dungettext('messages', 'foo1', 'foos1', 1), 'Voh1') self.assertEqual(t.dungettext('messages1', 'foo1', 'foos1', 1), 'VohD1') def test_default_germanic_pluralization(self): t = self._getTargetClass()() t._catalog = {} result = t.dungettext('messages', 'foo1', 'foos1', 2) self.assertEqual(result, 'foos1') class TestLocalizerRequestMixin(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self): from pyramid.i18n import LocalizerRequestMixin request = LocalizerRequestMixin() request.registry = self.config.registry request.cookies = {} request.params = {} return request def test_default_localizer(self): # `localizer` returns a default localizer for `en` from pyramid.i18n import Localizer request = self._makeOne() self.assertEqual(request.localizer.__class__, Localizer) self.assertEqual(request.locale_name, 'en') def test_custom_localizer_for_default_locale(self): from pyramid.interfaces import ILocalizer dummy = object() self.config.registry.registerUtility(dummy, ILocalizer, name='en') request = self._makeOne() self.assertEqual(request.localizer, dummy) def test_custom_localizer_for_custom_locale(self): from pyramid.interfaces import ILocalizer dummy = object() self.config.registry.registerUtility(dummy, ILocalizer, name='ie') request = self._makeOne() request._LOCALE_ = 'ie' self.assertEqual(request.localizer, dummy) def test_localizer_from_mo(self): from pyramid.interfaces import ITranslationDirectories from pyramid.i18n import Localizer localedirs = [localedir] self.config.registry.registerUtility( localedirs, ITranslationDirectories) request = self._makeOne() request._LOCALE_ = 'de' result = request.localizer self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Approve', 'deformsite'), 'Genehmigen') self.assertEqual(result.translate('Approve'), 'Approve') self.assertTrue(hasattr(result, 'pluralize')) def test_localizer_from_mo_bad_mo(self): from pyramid.interfaces import ITranslationDirectories from pyramid.i18n import Localizer localedirs = [localedir] self.config.registry.registerUtility( localedirs, ITranslationDirectories) request = self._makeOne() request._LOCALE_ = 'be' result = request.localizer self.assertEqual(result.__class__, Localizer) self.assertEqual(result.translate('Approve', 'deformsite'), 'Approve') class DummyRequest(object): def __init__(self): self.params = {} self.cookies = {} def dummy_negotiator(request): return 'bogus' class DummyTranslations(object): def ugettext(self, text): return text gettext = ugettext def ungettext(self, singular, plural, n): return singular ngettext = ungettext pyramid-1.6/pyramid/tests/test_integration.py0000644000076500000240000006561712524266531022342 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- import datetime import locale import os import unittest from pyramid.wsgi import wsgiapp from pyramid.view import view_config from pyramid.static import static_view from pyramid.compat import ( text_, url_quote, ) from zope.interface import Interface # 5 years from now (more or less) fiveyrsfuture = datetime.datetime.utcnow() + datetime.timedelta(5*365) defaultlocale = locale.getdefaultlocale()[1] class INothing(Interface): pass @view_config(for_=INothing) @wsgiapp def wsgiapptest(environ, start_response): """ """ return '123' class WGSIAppPlusViewConfigTests(unittest.TestCase): def test_it(self): from venusian import ATTACH_ATTR import types self.assertTrue(getattr(wsgiapptest, ATTACH_ATTR)) self.assertTrue(type(wsgiapptest) is types.FunctionType) context = DummyContext() request = DummyRequest() result = wsgiapptest(context, request) self.assertEqual(result, '123') def test_scanned(self): from pyramid.interfaces import IRequest from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.config import Configurator from pyramid.tests import test_integration config = Configurator() config.scan(test_integration) config.commit() reg = config.registry view = reg.adapters.lookup( (IViewClassifier, IRequest, INothing), IView, name='') self.assertEqual(view.__original_view__, wsgiapptest) class IntegrationBase(object): root_factory = None package = None def setUp(self): from pyramid.config import Configurator config = Configurator(root_factory=self.root_factory, package=self.package) config.include(self.package) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) self.config = config def tearDown(self): self.config.end() here = os.path.dirname(__file__) class StaticAppBase(IntegrationBase): def test_basic(self): res = self.testapp.get('/minimal.txt', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/minimal.txt')) def test_hidden(self): res = self.testapp.get('/static/.hiddenfile', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/static/.hiddenfile')) 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 def test_highchars_in_pathelement(self): path = os.path.join( here, text_('fixtures/static/héhé/index.html', 'utf-8')) pathdir = os.path.dirname(path) body = b'hehe\n' try: os.makedirs(pathdir) with open(path, 'wb') as fp: fp.write(body) url = url_quote('/static/héhé/index.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: os.unlink(path) os.rmdir(pathdir) def test_highchars_in_filename(self): path = os.path.join( here, text_('fixtures/static/héhé.html', 'utf-8')) body = b'hehe file\n' with open(path, 'wb') as fp: fp.write(body) try: url = url_quote('/static/héhé.html') res = self.testapp.get(url, status=200) self.assertEqual(res.body, body) finally: os.unlink(path) def test_not_modified(self): self.testapp.extra_environ = { 'HTTP_IF_MODIFIED_SINCE':httpdate(fiveyrsfuture)} res = self.testapp.get('/minimal.txt', status=304) self.assertEqual(res.body, b'') def test_file_in_subdir(self): fn = os.path.join(here, 'fixtures/static/index.html') res = self.testapp.get('/static/index.html', status=200) _assertBody(res.body, fn) def test_directory_noslash_redir(self): res = self.testapp.get('/static', status=301) self.assertEqual(res.headers['Location'], 'http://localhost/static/') def test_directory_noslash_redir_preserves_qs(self): res = self.testapp.get('/static?a=1&b=2', status=301) self.assertEqual(res.headers['Location'], 'http://localhost/static/?a=1&b=2') def test_directory_noslash_redir_with_scriptname(self): self.testapp.extra_environ = {'SCRIPT_NAME':'/script_name'} res = self.testapp.get('/static', status=301) self.assertEqual(res.headers['Location'], 'http://localhost/script_name/static/') def test_directory_withslash(self): fn = os.path.join(here, 'fixtures/static/index.html') res = self.testapp.get('/static/', status=200) _assertBody(res.body, fn) def test_range_inclusive(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1-2'} res = self.testapp.get('/static/index.html', status=206) self.assertEqual(res.body, b'ht') def test_range_tilend(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=-5'} res = self.testapp.get('/static/index.html', status=206) self.assertEqual(res.body, b'html>') def test_range_notbytes(self): self.testapp.extra_environ = {'HTTP_RANGE':'kHz=-5'} res = self.testapp.get('/static/index.html', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/static/index.html')) def test_range_multiple(self): res = self.testapp.get('/static/index.html', [('HTTP_RANGE', 'bytes=10-11,11-12')], status=200) _assertBody(res.body, os.path.join(here, 'fixtures/static/index.html')) def test_range_oob(self): self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1000-1002'} self.testapp.get('/static/index.html', status=416) def test_notfound(self): self.testapp.get('/static/wontbefound.html', status=404) def test_oob_dotdotslash(self): self.testapp.get('/static/../../test_integration.py', status=404) def test_oob_dotdotslash_encoded(self): self.testapp.get('/static/%2E%2E%2F/test_integration.py', status=404) def test_oob_slash(self): self.testapp.get('/%2F/test_integration.py', status=404) class TestEventOnlySubscribers(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.eventonly' def test_sendfoo(self): res = self.testapp.get('/sendfoo', status=200) self.assertEqual(sorted(res.body.split()), [b'foo', b'fooyup']) def test_sendfoobar(self): res = self.testapp.get('/sendfoobar', status=200) self.assertEqual(sorted(res.body.split()), [b'foobar', b'foobar2', b'foobaryup', b'foobaryup2']) class TestStaticAppUsingAbsPath(StaticAppBase, unittest.TestCase): package = 'pyramid.tests.pkgs.static_abspath' class TestStaticAppUsingAssetSpec(StaticAppBase, unittest.TestCase): package = 'pyramid.tests.pkgs.static_assetspec' class TestStaticAppNoSubpath(unittest.TestCase): staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=False) def _makeRequest(self, extra): from pyramid.request import Request from io import BytesIO kw = {'PATH_INFO':'', 'SCRIPT_NAME':'', 'SERVER_NAME':'localhost', 'SERVER_PORT':'80', 'REQUEST_METHOD':'GET', 'wsgi.version':(1,0), 'wsgi.url_scheme':'http', 'wsgi.input':BytesIO()} kw.update(extra) request = Request(kw) return request def test_basic(self): request = self._makeRequest({'PATH_INFO':'/minimal.txt'}) context = DummyContext() result = self.staticapp(context, request) self.assertEqual(result.status, '200 OK') _assertBody(result.body, os.path.join(here, 'fixtures/minimal.txt')) class TestStaticAppWithRoutePrefix(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.static_routeprefix' def test_includelevel1(self): res = self.testapp.get('/static/minimal.txt', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/minimal.txt')) def test_includelevel2(self): res = self.testapp.get('/prefix/static/index.html', status=200) _assertBody(res.body, os.path.join(here, 'fixtures/static/index.html')) class TestFixtureApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.fixtureapp' def test_another(self): res = self.testapp.get('/another.html', status=200) self.assertEqual(res.body, b'fixture') def test_root(self): res = self.testapp.get('/', status=200) self.assertEqual(res.body, b'fixture') def test_dummyskin(self): self.testapp.get('/dummyskin.html', status=404) def test_error(self): res = self.testapp.get('/error.html', status=200) self.assertEqual(res.body, b'supressed') def test_protected(self): self.testapp.get('/protected.html', status=403) class TestStaticPermApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.staticpermapp' root_factory = 'pyramid.tests.pkgs.staticpermapp:RootFactory' def test_allowed(self): result = self.testapp.get('/allowed/index.html', status=200) _assertBody(result.body, os.path.join(here, 'fixtures/static/index.html')) def test_denied_via_acl_global_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'bob'} self.testapp.get('/protected/index.html', status=403) def test_allowed_via_acl_global_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'fred'} result = self.testapp.get('/protected/index.html', status=200) _assertBody(result.body, os.path.join(here, 'fixtures/static/index.html')) def test_denied_via_acl_local_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'fred'} self.testapp.get('/factory_protected/index.html', status=403) def test_allowed_via_acl_local_root_factory(self): self.testapp.extra_environ = {'REMOTE_USER':'bob'} result = self.testapp.get('/factory_protected/index.html', status=200) _assertBody(result.body, os.path.join(here, 'fixtures/static/index.html')) class TestCCBug(IntegrationBase, unittest.TestCase): # "unordered" as reported in IRC by author of # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/ package = 'pyramid.tests.pkgs.ccbugapp' def test_rdf(self): res = self.testapp.get('/licenses/1/v1/rdf', status=200) self.assertEqual(res.body, b'rdf') def test_juri(self): res = self.testapp.get('/licenses/1/v1/juri', status=200) self.assertEqual(res.body, b'juri') class TestHybridApp(IntegrationBase, unittest.TestCase): # make sure views registered for a route "win" over views registered # without one, even though the context of the non-route view may # be more specific than the route view. package = 'pyramid.tests.pkgs.hybridapp' def test_root(self): res = self.testapp.get('/', status=200) self.assertEqual(res.body, b'global') def test_abc(self): res = self.testapp.get('/abc', status=200) self.assertEqual(res.body, b'route') def test_def(self): res = self.testapp.get('/def', status=200) self.assertEqual(res.body, b'route2') def test_ghi(self): res = self.testapp.get('/ghi', status=200) self.assertEqual(res.body, b'global') def test_jkl(self): self.testapp.get('/jkl', status=404) def test_mno(self): self.testapp.get('/mno', status=404) def test_pqr_global2(self): res = self.testapp.get('/pqr/global2', status=200) self.assertEqual(res.body, b'global2') def test_error(self): res = self.testapp.get('/error', status=200) self.assertEqual(res.body, b'supressed') def test_error2(self): res = self.testapp.get('/error2', status=200) self.assertEqual(res.body, b'supressed2') def test_error_sub(self): res = self.testapp.get('/error_sub', status=200) self.assertEqual(res.body, b'supressed2') class TestRestBugApp(IntegrationBase, unittest.TestCase): # test bug reported by delijati 2010/2/3 (http://pastebin.com/d4cc15515) package = 'pyramid.tests.pkgs.restbugapp' def test_it(self): res = self.testapp.get('/pet', status=200) self.assertEqual(res.body, b'gotten') class TestForbiddenAppHasResult(IntegrationBase, unittest.TestCase): # test that forbidden exception has ACLDenied result attached package = 'pyramid.tests.pkgs.forbiddenapp' def test_it(self): res = self.testapp.get('/x', status=403) message, result = [x.strip() for x in res.body.split(b'\n')] self.assertTrue(message.endswith(b'failed permission check')) self.assertTrue( result.startswith(b"ACLDenied permission 'private' via ACE " b"'' in ACL " b"'' on context")) self.assertTrue( result.endswith(b"for principals ['system.Everyone']")) class TestViewDecoratorApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.viewdecoratorapp' def test_first(self): res = self.testapp.get('/first', status=200) self.assertTrue(b'OK' in res.body) def test_second(self): res = self.testapp.get('/second', status=200) self.assertTrue(b'OK2' in res.body) class TestNotFoundView(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.notfoundview' def test_it(self): res = self.testapp.get('/wontbefound', status=200) self.assertTrue(b'generic_notfound' in res.body) res = self.testapp.get('/bar', status=302) self.assertEqual(res.location, 'http://localhost/bar/') res = self.testapp.get('/bar/', status=200) self.assertTrue(b'OK bar' in res.body) res = self.testapp.get('/foo', status=302) self.assertEqual(res.location, 'http://localhost/foo/') res = self.testapp.get('/foo/', status=200) self.assertTrue(b'OK foo2' in res.body) res = self.testapp.get('/baz', status=200) self.assertTrue(b'baz_notfound' in res.body) class TestForbiddenView(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.forbiddenview' def test_it(self): res = self.testapp.get('/foo', status=200) self.assertTrue(b'foo_forbidden' in res.body) res = self.testapp.get('/bar', status=200) self.assertTrue(b'generic_forbidden' in res.body) class TestViewPermissionBug(IntegrationBase, unittest.TestCase): # view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html package = 'pyramid.tests.pkgs.permbugapp' def test_test(self): res = self.testapp.get('/test', status=200) self.assertTrue(b'ACLDenied' in res.body) def test_x(self): self.testapp.get('/x', status=403) class TestDefaultViewPermissionBug(IntegrationBase, unittest.TestCase): # default_view_permission bug as reported by Wiggy at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003602.html package = 'pyramid.tests.pkgs.defpermbugapp' def test_x(self): res = self.testapp.get('/x', status=403) self.assertTrue(b'failed permission check' in res.body) def test_y(self): res = self.testapp.get('/y', status=403) self.assertTrue(b'failed permission check' in res.body) def test_z(self): res = self.testapp.get('/z', status=200) self.assertTrue(b'public' in res.body) from pyramid.tests.pkgs.exceptionviewapp.models import \ AnException, NotAnException excroot = {'anexception':AnException(), 'notanexception':NotAnException()} class TestExceptionViewsApp(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.exceptionviewapp' root_factory = lambda *arg: excroot def test_root(self): res = self.testapp.get('/', status=200) self.assertTrue(b'maybe' in res.body) def test_notanexception(self): res = self.testapp.get('/notanexception', status=200) self.assertTrue(b'no' in res.body) def test_anexception(self): res = self.testapp.get('/anexception', status=200) self.assertTrue(b'yes' in res.body) def test_route_raise_exception(self): res = self.testapp.get('/route_raise_exception', status=200) self.assertTrue(b'yes' in res.body) def test_route_raise_exception2(self): res = self.testapp.get('/route_raise_exception2', status=200) self.assertTrue(b'yes' in res.body) def test_route_raise_exception3(self): res = self.testapp.get('/route_raise_exception3', status=200) self.assertTrue(b'whoa' in res.body) def test_route_raise_exception4(self): res = self.testapp.get('/route_raise_exception4', status=200) self.assertTrue(b'whoa' in res.body) def test_raise_httpexception(self): res = self.testapp.get('/route_raise_httpexception', status=200) self.assertTrue(b'caught' in res.body) class TestConflictApp(unittest.TestCase): package = 'pyramid.tests.pkgs.conflictapp' def _makeConfig(self): from pyramid.config import Configurator config = Configurator() return config def test_autoresolved_view(self): config = self._makeConfig() config.include(self.package) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/') self.assertTrue(b'a view' in res.body) res = self.testapp.get('/route') self.assertTrue(b'route view' in res.body) def test_overridden_autoresolved_view(self): from pyramid.response import Response config = self._makeConfig() config.include(self.package) def thisview(request): return Response('this view') config.add_view(thisview) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/') self.assertTrue(b'this view' in res.body) def test_overridden_route_view(self): from pyramid.response import Response config = self._makeConfig() config.include(self.package) def thisview(request): return Response('this view') config.add_view(thisview, route_name='aroute') app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/route') self.assertTrue(b'this view' in res.body) def test_nonoverridden_authorization_policy(self): config = self._makeConfig() config.include(self.package) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/protected', status=403) self.assertTrue(b'403 Forbidden' in res.body) def test_overridden_authorization_policy(self): config = self._makeConfig() config.include(self.package) from pyramid.testing import DummySecurityPolicy config.set_authorization_policy(DummySecurityPolicy('fred')) config.set_authentication_policy(DummySecurityPolicy(permissive=True)) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) res = self.testapp.get('/protected', status=200) self.assertTrue('protected view' in res) class ImperativeIncludeConfigurationTest(unittest.TestCase): def setUp(self): from pyramid.config import Configurator config = Configurator() from pyramid.tests.pkgs.includeapp1.root import configure configure(config) app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) self.config = config def tearDown(self): self.config.end() def test_root(self): res = self.testapp.get('/', status=200) self.assertTrue(b'root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) self.assertTrue(b'two' in res.body) def test_three(self): res = self.testapp.get('/three', status=200) self.assertTrue(b'three' in res.body) class SelfScanAppTest(unittest.TestCase): def setUp(self): from pyramid.tests.test_config.pkgs.selfscan import main config = main() app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) self.config = config def tearDown(self): self.config.end() def test_root(self): res = self.testapp.get('/', status=200) self.assertTrue(b'root' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) self.assertTrue(b'two' in res.body) class WSGIApp2AppTest(unittest.TestCase): def setUp(self): from pyramid.tests.pkgs.wsgiapp2app import main config = main() app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) self.config = config def tearDown(self): self.config.end() def test_hello(self): res = self.testapp.get('/hello', status=200) self.assertTrue(b'Hello' in res.body) class SubrequestAppTest(unittest.TestCase): def setUp(self): from pyramid.tests.pkgs.subrequestapp import main config = main() app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) self.config = config def tearDown(self): self.config.end() def test_one(self): res = self.testapp.get('/view_one', status=200) self.assertTrue(b'This came from view_two' in res.body) def test_three(self): res = self.testapp.get('/view_three', status=500) self.assertTrue(b'Bad stuff happened' in res.body) def test_five(self): res = self.testapp.get('/view_five', status=200) self.assertTrue(b'Value error raised' in res.body) class RendererScanAppTest(IntegrationBase, unittest.TestCase): package = 'pyramid.tests.pkgs.rendererscanapp' def test_root(self): res = self.testapp.get('/one', status=200) self.assertTrue(b'One!' in res.body) def test_two(self): res = self.testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) def test_rescan(self): self.config.scan('pyramid.tests.pkgs.rendererscanapp') app = self.config.make_wsgi_app() from webtest import TestApp testapp = TestApp(app) res = testapp.get('/one', status=200) self.assertTrue(b'One!' in res.body) res = testapp.get('/two', status=200) self.assertTrue(b'Two!' in res.body) class UnicodeInURLTest(unittest.TestCase): def _makeConfig(self): from pyramid.config import Configurator config = Configurator() return config def _makeTestApp(self, config): from webtest import TestApp app = config.make_wsgi_app() return TestApp(app) def test_unicode_in_url_404(self): request_path = '/avalia%C3%A7%C3%A3o_participante' request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') config = self._makeConfig() testapp = self._makeTestApp(config) res = testapp.get(request_path, status=404) # Pyramid default 404 handler outputs: # u'404 Not Found\n\nThe resource could not be found.\n\n\n' # u'/avalia\xe7\xe3o_participante\n\n' self.assertTrue(request_path_unicode in res.text) def test_unicode_in_url_200(self): request_path = '/avalia%C3%A7%C3%A3o_participante' request_path_unicode = b'/avalia\xc3\xa7\xc3\xa3o_participante'.decode('utf-8') def myview(request): return 'XXX' config = self._makeConfig() config.add_route('myroute', request_path_unicode) config.add_view(myview, route_name='myroute', renderer='json') testapp = self._makeTestApp(config) res = testapp.get(request_path, status=200) self.assertEqual(res.text, '"XXX"') class AcceptContentTypeTest(unittest.TestCase): def setUp(self): def hello_view(request): return {'message': 'Hello!'} from pyramid.config import Configurator config = Configurator() config.add_route('hello', '/hello') config.add_view(hello_view, route_name='hello', accept='text/plain', renderer='string') config.add_view(hello_view, route_name='hello', accept='application/json', renderer='json') app = config.make_wsgi_app() from webtest import TestApp self.testapp = TestApp(app) def tearDown(self): import pyramid.config pyramid.config.global_registries.empty() def test_ordering(self): res = self.testapp.get('/hello', headers={'Accept': 'application/json; q=1.0, text/plain; q=0.9'}, status=200) self.assertEqual(res.content_type, 'application/json') res = self.testapp.get('/hello', headers={'Accept': 'text/plain; q=0.9, application/json; q=1.0'}, status=200) self.assertEqual(res.content_type, 'application/json') def test_wildcards(self): res = self.testapp.get('/hello', headers={'Accept': 'application/*'}, status=200) self.assertEqual(res.content_type, 'application/json') res = self.testapp.get('/hello', headers={'Accept': 'text/*'}, status=200) self.assertEqual(res.content_type, 'text/plain') class DummyContext(object): pass class DummyRequest: subpath = ('__init__.py',) traversed = None environ = {'REQUEST_METHOD':'GET', 'wsgi.version':(1,0)} def get_response(self, application): return application(None, None) def httpdate(ts): return ts.strftime("%a, %d %b %Y %H:%M:%S GMT") def read_(filename): with open(filename, 'rb') as fp: val = fp.read() return val def _assertBody(body, filename): if defaultlocale is None: # pragma: no cover # If system locale does not have an encoding then default to utf-8 filename = filename.encode('utf-8') # strip both \n and \r for windows body = body.replace(b'\r', b'') body = body.replace(b'\n', b'') data = read_(filename) data = data.replace(b'\r', b'') data = data.replace(b'\n', b'') assert(body == data) pyramid-1.6/pyramid/tests/test_location.py0000644000076500000240000000250112234375161021604 0ustar michaelstaff00000000000000import unittest class TestInside(unittest.TestCase): def _callFUT(self, one, two): from pyramid.location import inside return inside(one, two) def test_inside(self): o1 = Location() o2 = Location(); o2.__parent__ = o1 o3 = Location(); o3.__parent__ = o2 o4 = Location(); o4.__parent__ = o3 self.assertEqual(self._callFUT(o1, o1), True) self.assertEqual(self._callFUT(o2, o1), True) self.assertEqual(self._callFUT(o3, o1), True) self.assertEqual(self._callFUT(o4, o1), True) self.assertEqual(self._callFUT(o1, o4), False) self.assertEqual(self._callFUT(o1, None), False) class TestLineage(unittest.TestCase): def _callFUT(self, context): from pyramid.location import lineage return lineage(context) def test_lineage(self): o1 = Location() o2 = Location(); o2.__parent__ = o1 o3 = Location(); o3.__parent__ = o2 o4 = Location(); o4.__parent__ = o3 result = list(self._callFUT(o3)) self.assertEqual(result, [o3, o2, o1]) result = list(self._callFUT(o1)) self.assertEqual(result, [o1]) from pyramid.interfaces import ILocation from zope.interface import implementer @implementer(ILocation) class Location(object): __name__ = __parent__ = None pyramid-1.6/pyramid/tests/test_paster.py0000644000076500000240000001620612520062551021273 0ustar michaelstaff00000000000000import os import unittest here = os.path.dirname(__file__) class Test_get_app(unittest.TestCase): def _callFUT(self, config_file, section_name, **kw): from pyramid.paster import get_app return get_app(config_file, section_name, **kw) def test_it(self): app = DummyApp() loadapp = DummyLoadWSGI(app) result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp=loadapp) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) def test_it_with_hash(self): app = DummyApp() loadapp = DummyLoadWSGI(app) result = self._callFUT( '/foo/bar/myapp.ini#myapp', None, loadapp=loadapp ) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'myapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) def test_it_with_hash_and_name_override(self): app = DummyApp() loadapp = DummyLoadWSGI(app) result = self._callFUT( '/foo/bar/myapp.ini#myapp', 'yourapp', loadapp=loadapp ) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'yourapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) def test_it_with_options(self): app = DummyApp() loadapp = DummyLoadWSGI(app) options = {'a':1} result = self._callFUT( '/foo/bar/myapp.ini#myapp', 'yourapp', loadapp=loadapp, options=options, ) self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(loadapp.section_name, 'yourapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(loadapp.kw, {'global_conf':options}) self.assertEqual(result, app) def test_it_with_dummyapp_requiring_options(self): options = {'bar': 'baz'} app = self._callFUT( os.path.join(here, 'fixtures', 'dummy.ini'), 'myapp', options=options) self.assertEqual(app.settings['foo'], 'baz') class Test_get_appsettings(unittest.TestCase): def _callFUT(self, config_file, section_name, **kw): from pyramid.paster import get_appsettings return get_appsettings(config_file, section_name, **kw) def test_it(self): values = {'a':1} appconfig = DummyLoadWSGI(values) result = self._callFUT('/foo/bar/myapp.ini', 'myapp', appconfig=appconfig) self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(appconfig.section_name, 'myapp') self.assertEqual(appconfig.relative_to, os.getcwd()) self.assertEqual(result, values) def test_it_with_hash(self): values = {'a':1} appconfig = DummyLoadWSGI(values) result = self._callFUT('/foo/bar/myapp.ini#myapp', None, appconfig=appconfig) self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(appconfig.section_name, 'myapp') self.assertEqual(appconfig.relative_to, os.getcwd()) self.assertEqual(result, values) def test_it_with_hash_and_name_override(self): values = {'a':1} appconfig = DummyLoadWSGI(values) result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp', appconfig=appconfig) self.assertEqual(appconfig.config_name, 'config:/foo/bar/myapp.ini') self.assertEqual(appconfig.section_name, 'yourapp') self.assertEqual(appconfig.relative_to, os.getcwd()) self.assertEqual(result, values) def test_it_with_dummyapp_requiring_options(self): options = {'bar': 'baz'} result = self._callFUT( os.path.join(here, 'fixtures', 'dummy.ini'), 'myapp', options=options) self.assertEqual(result['foo'], 'baz') class Test_setup_logging(unittest.TestCase): def _callFUT(self, config_file): from pyramid.paster import setup_logging dummy_cp = DummyConfigParserModule return setup_logging(config_file, self.fileConfig, dummy_cp) def test_it(self): config_file, dict = self._callFUT('/abc') # os.path.abspath is a sop to Windows self.assertEqual(config_file, os.path.abspath('/abc')) self.assertEqual(dict['__file__'], os.path.abspath('/abc')) self.assertEqual(dict['here'], os.path.abspath('/')) def fileConfig(self, config_file, dict): return config_file, dict class Test_bootstrap(unittest.TestCase): def _callFUT(self, config_uri, request=None): from pyramid.paster import bootstrap return bootstrap(config_uri, request) def setUp(self): import pyramid.paster self.original_get_app = pyramid.paster.get_app self.original_prepare = pyramid.paster.prepare self.app = app = DummyApp() self.root = root = Dummy() class DummyGetApp(object): def __call__(self, *a, **kw): self.a = a self.kw = kw return app self.get_app = pyramid.paster.get_app = DummyGetApp() class DummyPrepare(object): def __call__(self, *a, **kw): self.a = a self.kw = kw return {'root':root, 'closer':lambda: None} self.getroot = pyramid.paster.prepare = DummyPrepare() def tearDown(self): import pyramid.paster pyramid.paster.get_app = self.original_get_app pyramid.paster.prepare = self.original_prepare def test_it_request_with_registry(self): request = DummyRequest({}) request.registry = dummy_registry result = self._callFUT('/foo/bar/myapp.ini', request) self.assertEqual(result['app'], self.app) self.assertEqual(result['root'], self.root) self.assertTrue('closer' in result) class Dummy: pass class DummyRegistry(object): settings = {} dummy_registry = DummyRegistry() class DummyLoadWSGI: def __init__(self, result): self.result = result def __call__(self, config_name, name=None, relative_to=None, **kw): self.config_name = config_name self.section_name = name self.relative_to = relative_to self.kw = kw return self.result class DummyApp: def __init__(self): self.registry = dummy_registry def make_dummyapp(global_conf, **settings): app = DummyApp() app.settings = settings app.global_conf = global_conf return app class DummyRequest: application_url = 'http://example.com:5432' script_name = '' def __init__(self, environ): self.environ = environ self.matchdict = {} class DummyConfigParser(object): def read(self, x): pass def has_section(self, name): return True class DummyConfigParserModule(object): ConfigParser = DummyConfigParser pyramid-1.6/pyramid/tests/test_path.py0000644000076500000240000005045412634703400020735 0ustar michaelstaff00000000000000import unittest import os from pyramid.compat import PY3 here = os.path.abspath(os.path.dirname(__file__)) class TestCallerPath(unittest.TestCase): def tearDown(self): from pyramid.tests import test_path if hasattr(test_path, '__abspath__'): del test_path.__abspath__ def _callFUT(self, path, level=2): from pyramid.path import caller_path return caller_path(path, level) def test_isabs(self): result = self._callFUT('/a/b/c') self.assertEqual(result, '/a/b/c') def test_pkgrelative(self): import os result = self._callFUT('a/b/c') self.assertEqual(result, os.path.join(here, 'a/b/c')) def test_memoization_has_abspath(self): import os from pyramid.tests import test_path test_path.__abspath__ = '/foo/bar' result = self._callFUT('a/b/c') self.assertEqual(result, os.path.join('/foo/bar', 'a/b/c')) def test_memoization_success(self): import os from pyramid.tests import test_path result = self._callFUT('a/b/c') self.assertEqual(result, os.path.join(here, 'a/b/c')) self.assertEqual(test_path.__abspath__, here) class TestCallerModule(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.path import caller_module return caller_module(*arg, **kw) def test_it_level_1(self): from pyramid.tests import test_path result = self._callFUT(1) self.assertEqual(result, test_path) def test_it_level_2(self): from pyramid.tests import test_path result = self._callFUT(2) self.assertEqual(result, test_path) def test_it_level_3(self): from pyramid.tests import test_path result = self._callFUT(3) self.assertNotEqual(result, test_path) def test_it_no___name__(self): class DummyFrame(object): f_globals = {} class DummySys(object): def _getframe(self, level): return DummyFrame() modules = {'__main__':'main'} dummy_sys = DummySys() result = self._callFUT(3, sys=dummy_sys) self.assertEqual(result, 'main') class TestCallerPackage(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.path import caller_package return caller_package(*arg, **kw) def test_it_level_1(self): from pyramid import tests result = self._callFUT(1) self.assertEqual(result, tests) def test_it_level_2(self): from pyramid import tests result = self._callFUT(2) self.assertEqual(result, tests) def test_it_level_3(self): import unittest result = self._callFUT(3) self.assertEqual(result, unittest) def test_it_package(self): import pyramid.tests def dummy_caller_module(*arg): return pyramid.tests result = self._callFUT(1, caller_module=dummy_caller_module) self.assertEqual(result, pyramid.tests) class TestPackagePath(unittest.TestCase): def _callFUT(self, package): from pyramid.path import package_path return package_path(package) def test_it_package(self): from pyramid import tests package = DummyPackageOrModule(tests) result = self._callFUT(package) self.assertEqual(result, package.package_path) def test_it_module(self): from pyramid.tests import test_path module = DummyPackageOrModule(test_path) result = self._callFUT(module) self.assertEqual(result, module.package_path) def test_memoization_success(self): from pyramid.tests import test_path module = DummyPackageOrModule(test_path) self._callFUT(module) self.assertEqual(module.__abspath__, module.package_path) def test_memoization_fail(self): from pyramid.tests import test_path module = DummyPackageOrModule(test_path, raise_exc=TypeError) result = self._callFUT(module) self.assertFalse(hasattr(module, '__abspath__')) self.assertEqual(result, module.package_path) class TestPackageOf(unittest.TestCase): def _callFUT(self, package): from pyramid.path import package_of return package_of(package) def test_it_package(self): from pyramid import tests package = DummyPackageOrModule(tests) result = self._callFUT(package) self.assertEqual(result, tests) def test_it_module(self): import pyramid.tests.test_path from pyramid import tests package = DummyPackageOrModule(pyramid.tests.test_path) result = self._callFUT(package) self.assertEqual(result, tests) class TestPackageName(unittest.TestCase): def _callFUT(self, package): from pyramid.path import package_name return package_name(package) def test_it_package(self): from pyramid import tests package = DummyPackageOrModule(tests) result = self._callFUT(package) self.assertEqual(result, 'pyramid.tests') def test_it_namespace_package(self): from pyramid import tests package = DummyNamespacePackage(tests) result = self._callFUT(package) self.assertEqual(result, 'pyramid.tests') def test_it_module(self): from pyramid.tests import test_path module = DummyPackageOrModule(test_path) result = self._callFUT(module) self.assertEqual(result, 'pyramid.tests') def test_it_None(self): result = self._callFUT(None) self.assertEqual(result, '__main__') def test_it_main(self): import __main__ result = self._callFUT(__main__) self.assertEqual(result, '__main__') class TestResolver(unittest.TestCase): def _getTargetClass(self): from pyramid.path import Resolver return Resolver def _makeOne(self, package): return self._getTargetClass()(package) def test_get_package_caller_package(self): import pyramid.tests from pyramid.path import CALLER_PACKAGE self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package(), pyramid.tests) def test_get_package_name_caller_package(self): from pyramid.path import CALLER_PACKAGE self.assertEqual(self._makeOne(CALLER_PACKAGE).get_package_name(), 'pyramid.tests') def test_get_package_string(self): import pyramid.tests self.assertEqual(self._makeOne('pyramid.tests').get_package(), pyramid.tests) def test_get_package_name_string(self): self.assertEqual(self._makeOne('pyramid.tests').get_package_name(), 'pyramid.tests') class TestAssetResolver(unittest.TestCase): def _getTargetClass(self): from pyramid.path import AssetResolver return AssetResolver def _makeOne(self, package='pyramid.tests'): return self._getTargetClass()(package) def test_ctor_as_package(self): import sys tests = sys.modules['pyramid.tests'] inst = self._makeOne(tests) self.assertEqual(inst.package, tests) def test_ctor_as_str(self): import sys tests = sys.modules['pyramid.tests'] inst = self._makeOne('pyramid.tests') self.assertEqual(inst.package, tests) def test_resolve_abspath(self): from pyramid.path import FSAssetDescriptor inst = self._makeOne(None) r = inst.resolve(os.path.join(here, 'test_asset.py')) self.assertEqual(r.__class__, FSAssetDescriptor) self.assertTrue(r.exists()) def test_resolve_absspec(self): from pyramid.path import PkgResourcesAssetDescriptor inst = self._makeOne(None) r = inst.resolve('pyramid.tests:test_asset.py') self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) self.assertTrue(r.exists()) def test_resolve_relspec_with_pkg(self): from pyramid.path import PkgResourcesAssetDescriptor inst = self._makeOne('pyramid.tests') r = inst.resolve('test_asset.py') self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) self.assertTrue(r.exists()) def test_resolve_relspec_no_package(self): inst = self._makeOne(None) self.assertRaises(ValueError, inst.resolve, 'test_asset.py') def test_resolve_relspec_caller_package(self): from pyramid.path import PkgResourcesAssetDescriptor from pyramid.path import CALLER_PACKAGE inst = self._makeOne(CALLER_PACKAGE) r = inst.resolve('test_asset.py') self.assertEqual(r.__class__, PkgResourcesAssetDescriptor) self.assertTrue(r.exists()) class TestPkgResourcesAssetDescriptor(unittest.TestCase): def _getTargetClass(self): from pyramid.path import PkgResourcesAssetDescriptor return PkgResourcesAssetDescriptor def _makeOne(self, pkg='pyramid.tests', path='test_asset.py'): return self._getTargetClass()(pkg, path) def test_class_conforms_to_IAssetDescriptor(self): from pyramid.interfaces import IAssetDescriptor from zope.interface.verify import verifyClass verifyClass(IAssetDescriptor, self._getTargetClass()) def test_instance_conforms_to_IAssetDescriptor(self): from pyramid.interfaces import IAssetDescriptor from zope.interface.verify import verifyObject verifyObject(IAssetDescriptor, self._makeOne()) def test_absspec(self): inst = self._makeOne() self.assertEqual(inst.absspec(), 'pyramid.tests:test_asset.py') def test_abspath(self): inst = self._makeOne() self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py')) def test_stream(self): inst = self._makeOne() inst.pkg_resources = DummyPkgResource() inst.pkg_resources.resource_stream = lambda x, y: '%s:%s' % (x, y) s = inst.stream() self.assertEqual(s, '%s:%s' % ('pyramid.tests', 'test_asset.py')) def test_isdir(self): inst = self._makeOne() inst.pkg_resources = DummyPkgResource() inst.pkg_resources.resource_isdir = lambda x, y: '%s:%s' % (x, y) self.assertEqual(inst.isdir(), '%s:%s' % ('pyramid.tests', 'test_asset.py')) def test_listdir(self): inst = self._makeOne() inst.pkg_resources = DummyPkgResource() inst.pkg_resources.resource_listdir = lambda x, y: '%s:%s' % (x, y) self.assertEqual(inst.listdir(), '%s:%s' % ('pyramid.tests', 'test_asset.py')) def test_exists(self): inst = self._makeOne() inst.pkg_resources = DummyPkgResource() inst.pkg_resources.resource_exists = lambda x, y: '%s:%s' % (x, y) self.assertEqual(inst.exists(), '%s:%s' % ('pyramid.tests', 'test_asset.py')) class TestFSAssetDescriptor(unittest.TestCase): def _getTargetClass(self): from pyramid.path import FSAssetDescriptor return FSAssetDescriptor def _makeOne(self, path=os.path.join(here, 'test_asset.py')): return self._getTargetClass()(path) def test_class_conforms_to_IAssetDescriptor(self): from pyramid.interfaces import IAssetDescriptor from zope.interface.verify import verifyClass verifyClass(IAssetDescriptor, self._getTargetClass()) def test_instance_conforms_to_IAssetDescriptor(self): from pyramid.interfaces import IAssetDescriptor from zope.interface.verify import verifyObject verifyObject(IAssetDescriptor, self._makeOne()) def test_absspec(self): inst = self._makeOne() self.assertRaises(NotImplementedError, inst.absspec) def test_abspath(self): inst = self._makeOne() self.assertEqual(inst.abspath(), os.path.join(here, 'test_asset.py')) def test_stream(self): inst = self._makeOne() s = inst.stream() val = s.read() s.close() self.assertTrue(b'asset' in val) def test_isdir_False(self): inst = self._makeOne() self.assertFalse(inst.isdir()) def test_isdir_True(self): inst = self._makeOne(here) self.assertTrue(inst.isdir()) def test_listdir(self): inst = self._makeOne(here) self.assertTrue(inst.listdir()) def test_exists(self): inst = self._makeOne() self.assertTrue(inst.exists()) class TestDottedNameResolver(unittest.TestCase): def _makeOne(self, package=None): from pyramid.path import DottedNameResolver return DottedNameResolver(package) def config_exc(self, func, *arg, **kw): try: func(*arg, **kw) except ValueError as e: return e else: raise AssertionError('Invalid not raised') # pragma: no cover def test_zope_dottedname_style_resolve_builtin(self): typ = self._makeOne() if PY3: result = typ._zope_dottedname_style('builtins.str', None) else: result = typ._zope_dottedname_style('__builtin__.str', None) self.assertEqual(result, str) def test_zope_dottedname_style_resolve_absolute(self): typ = self._makeOne() result = typ._zope_dottedname_style( 'pyramid.tests.test_path.TestDottedNameResolver', None) self.assertEqual(result, self.__class__) def test_zope_dottedname_style_irrresolveable_absolute(self): typ = self._makeOne() self.assertRaises(ImportError, typ._zope_dottedname_style, 'pyramid.test_path.nonexisting_name', None) def test__zope_dottedname_style_resolve_relative(self): import pyramid.tests typ = self._makeOne() result = typ._zope_dottedname_style( '.test_path.TestDottedNameResolver', pyramid.tests) self.assertEqual(result, self.__class__) def test__zope_dottedname_style_resolve_relative_leading_dots(self): import pyramid.tests.test_path typ = self._makeOne() result = typ._zope_dottedname_style( '..tests.test_path.TestDottedNameResolver', pyramid.tests) self.assertEqual(result, self.__class__) def test__zope_dottedname_style_resolve_relative_is_dot(self): import pyramid.tests typ = self._makeOne() result = typ._zope_dottedname_style('.', pyramid.tests) self.assertEqual(result, pyramid.tests) def test__zope_dottedname_style_irresolveable_relative_is_dot(self): typ = self._makeOne() e = self.config_exc(typ._zope_dottedname_style, '.', None) self.assertEqual( e.args[0], "relative name '.' irresolveable without package") def test_zope_dottedname_style_resolve_relative_nocurrentpackage(self): typ = self._makeOne() e = self.config_exc(typ._zope_dottedname_style, '.whatever', None) self.assertEqual( e.args[0], "relative name '.whatever' irresolveable without package") def test_zope_dottedname_style_irrresolveable_relative(self): import pyramid.tests typ = self._makeOne() self.assertRaises(ImportError, typ._zope_dottedname_style, '.notexisting', pyramid.tests) def test__zope_dottedname_style_resolveable_relative(self): import pyramid typ = self._makeOne() result = typ._zope_dottedname_style('.tests', pyramid) from pyramid import tests self.assertEqual(result, tests) def test__zope_dottedname_style_irresolveable_absolute(self): typ = self._makeOne() self.assertRaises( ImportError, typ._zope_dottedname_style, 'pyramid.fudge.bar', None) def test__zope_dottedname_style_resolveable_absolute(self): typ = self._makeOne() result = typ._zope_dottedname_style( 'pyramid.tests.test_path.TestDottedNameResolver', None) self.assertEqual(result, self.__class__) def test__pkg_resources_style_resolve_absolute(self): typ = self._makeOne() result = typ._pkg_resources_style( 'pyramid.tests.test_path:TestDottedNameResolver', None) self.assertEqual(result, self.__class__) def test__pkg_resources_style_irrresolveable_absolute(self): typ = self._makeOne() self.assertRaises(ImportError, typ._pkg_resources_style, 'pyramid.tests:nonexisting', None) def test__pkg_resources_style_resolve_relative(self): import pyramid.tests typ = self._makeOne() result = typ._pkg_resources_style( '.test_path:TestDottedNameResolver', pyramid.tests) self.assertEqual(result, self.__class__) def test__pkg_resources_style_resolve_relative_is_dot(self): import pyramid.tests typ = self._makeOne() result = typ._pkg_resources_style('.', pyramid.tests) self.assertEqual(result, pyramid.tests) def test__pkg_resources_style_resolve_relative_nocurrentpackage(self): typ = self._makeOne() self.assertRaises(ValueError, typ._pkg_resources_style, '.whatever', None) def test__pkg_resources_style_irrresolveable_relative(self): import pyramid typ = self._makeOne() self.assertRaises(ImportError, typ._pkg_resources_style, ':notexisting', pyramid) def test_resolve_not_a_string(self): typ = self._makeOne() e = self.config_exc(typ.resolve, None) self.assertEqual(e.args[0], 'None is not a string') def test_resolve_using_pkgresources_style(self): typ = self._makeOne() result = typ.resolve( 'pyramid.tests.test_path:TestDottedNameResolver') self.assertEqual(result, self.__class__) def test_resolve_using_zope_dottedname_style(self): typ = self._makeOne() result = typ.resolve( 'pyramid.tests.test_path:TestDottedNameResolver') self.assertEqual(result, self.__class__) def test_resolve_missing_raises(self): typ = self._makeOne() self.assertRaises(ImportError, typ.resolve, 'cant.be.found') def test_resolve_caller_package(self): from pyramid.path import CALLER_PACKAGE typ = self._makeOne(CALLER_PACKAGE) self.assertEqual(typ.resolve('.test_path.TestDottedNameResolver'), self.__class__) def test_maybe_resolve_caller_package(self): from pyramid.path import CALLER_PACKAGE typ = self._makeOne(CALLER_PACKAGE) self.assertEqual(typ.maybe_resolve('.test_path.TestDottedNameResolver'), self.__class__) def test_ctor_string_module_resolveable(self): import pyramid.tests typ = self._makeOne('pyramid.tests.test_path') self.assertEqual(typ.package, pyramid.tests) def test_ctor_string_package_resolveable(self): import pyramid.tests typ = self._makeOne('pyramid.tests') self.assertEqual(typ.package, pyramid.tests) def test_ctor_string_irresolveable(self): self.assertRaises(ValueError, self._makeOne, 'cant.be.found') def test_ctor_module(self): import pyramid.tests import pyramid.tests.test_path typ = self._makeOne(pyramid.tests.test_path) self.assertEqual(typ.package, pyramid.tests) def test_ctor_package(self): import pyramid.tests typ = self._makeOne(pyramid.tests) self.assertEqual(typ.package, pyramid.tests) def test_ctor_None(self): typ = self._makeOne(None) self.assertEqual(typ.package, None) class DummyPkgResource(object): pass class DummyPackageOrModule: def __init__(self, real_package_or_module, raise_exc=None): self.__dict__['raise_exc'] = raise_exc self.__dict__['__name__'] = real_package_or_module.__name__ import os self.__dict__['package_path'] = os.path.dirname( os.path.abspath(real_package_or_module.__file__)) self.__dict__['__file__'] = real_package_or_module.__file__ def __setattr__(self, key, val): if self.raise_exc is not None: raise self.raise_exc self.__dict__[key] = val class DummyNamespacePackage: """Has no __file__ attribute. """ def __init__(self, real_package_or_module): self.__name__ = real_package_or_module.__name__ import os self.package_path = os.path.dirname( os.path.abspath(real_package_or_module.__file__)) pyramid-1.6/pyramid/tests/test_registry.py0000644000076500000240000003152512524266531021656 0ustar michaelstaff00000000000000import unittest class TestRegistry(unittest.TestCase): def _getTargetClass(self): from pyramid.registry import Registry return Registry def _makeOne(self): return self._getTargetClass()() def test___nonzero__(self): registry = self._makeOne() self.assertEqual(registry.__nonzero__(), True) def test__lock(self): registry = self._makeOne() self.assertTrue(registry._lock) def test_clear_view_cache_lookup(self): registry = self._makeOne() registry._view_lookup_cache[1] = 2 registry._clear_view_lookup_cache() self.assertEqual(registry._view_lookup_cache, {}) 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) L = [] def f(event): L.append(event) registry.registerHandler(f, [IDummyEvent]) self.assertEqual(registry.has_listeners, True) event = DummyEvent() registry.notify(event) self.assertEqual(L, [event]) def test_registerSubscriptionAdapter(self): registry = self._makeOne() self.assertEqual(registry.has_listeners, False) from zope.interface import Interface registry.registerSubscriptionAdapter(DummyEvent, [IDummyEvent], Interface) self.assertEqual(registry.has_listeners, True) def test__get_settings(self): registry = self._makeOne() registry._settings = 'foo' self.assertEqual(registry.settings, 'foo') def test__set_settings(self): registry = self._makeOne() registry.settings = 'foo' self.assertEqual(registry._settings, 'foo') class TestIntrospector(unittest.TestCase): def _getTargetClass(slf): from pyramid.registry import Introspector return Introspector def _makeOne(self): return self._getTargetClass()() def test_conformance(self): from zope.interface.verify import verifyClass from zope.interface.verify import verifyObject from pyramid.interfaces import IIntrospector verifyClass(IIntrospector, self._getTargetClass()) verifyObject(IIntrospector, self._makeOne()) def test_add(self): inst = self._makeOne() intr = DummyIntrospectable() inst.add(intr) self.assertEqual(intr.order, 0) category = {'discriminator':intr, 'discriminator_hash':intr} self.assertEqual(inst._categories, {'category':category}) def test_get_success(self): inst = self._makeOne() intr = DummyIntrospectable() inst.add(intr) self.assertEqual(inst.get('category', 'discriminator'), intr) def test_get_success_byhash(self): inst = self._makeOne() intr = DummyIntrospectable() inst.add(intr) self.assertEqual(inst.get('category', 'discriminator_hash'), intr) def test_get_fail(self): inst = self._makeOne() intr = DummyIntrospectable() inst.add(intr) self.assertEqual(inst.get('category', 'wontexist', 'foo'), 'foo') def test_get_category(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr2) inst.add(intr) expected = [ {'introspectable':intr2, 'related':[]}, {'introspectable':intr, 'related':[]}, ] self.assertEqual(inst.get_category('category'), expected) def test_get_category_returns_default_on_miss(self): inst = self._makeOne() self.assertEqual(inst.get_category('category', '123'), '123') def test_get_category_with_sortkey(self): import operator inst = self._makeOne() intr = DummyIntrospectable() intr.foo = 2 intr2 = DummyIntrospectable() intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' intr2.foo = 1 inst.add(intr) inst.add(intr2) expected = [ {'introspectable':intr2, 'related':[]}, {'introspectable':intr, 'related':[]}, ] self.assertEqual( inst.get_category('category', sort_key=operator.attrgetter('foo')), expected) def test_categorized(self): import operator inst = self._makeOne() intr = DummyIntrospectable() intr.foo = 2 intr2 = DummyIntrospectable() intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' intr2.foo = 1 inst.add(intr) inst.add(intr2) expected = [('category', [ {'introspectable':intr2, 'related':[]}, {'introspectable':intr, 'related':[]}, ])] self.assertEqual( inst.categorized(sort_key=operator.attrgetter('foo')), expected) def test_categories(self): inst = self._makeOne() inst._categories['a'] = 1 inst._categories['b'] = 2 self.assertEqual(list(inst.categories()), ['a', 'b']) def test_remove(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.category_name = 'category2' intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr) inst.add(intr2) inst.relate(('category', 'discriminator'), ('category2', 'discriminator2')) inst.remove('category', 'discriminator') self.assertEqual(inst._categories, {'category': {}, 'category2': {'discriminator2': intr2, 'discriminator2_hash': intr2} }) self.assertEqual(inst._refs.get(intr), None) self.assertEqual(inst._refs[intr2], []) def test_remove_fail(self): inst = self._makeOne() self.assertEqual(inst.remove('a', 'b'), None) def test_relate(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.category_name = 'category2' intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr) inst.add(intr2) inst.relate(('category', 'discriminator'), ('category2', 'discriminator2')) self.assertEqual(inst._categories, {'category': {'discriminator':intr, 'discriminator_hash':intr}, 'category2': {'discriminator2': intr2, 'discriminator2_hash': intr2} }) self.assertEqual(inst._refs[intr], [intr2]) self.assertEqual(inst._refs[intr2], [intr]) def test_relate_fail(self): inst = self._makeOne() intr = DummyIntrospectable() inst.add(intr) self.assertRaises( KeyError, inst.relate, ('category', 'discriminator'), ('category2', 'discriminator2') ) def test_unrelate(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.category_name = 'category2' intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr) inst.add(intr2) inst.relate(('category', 'discriminator'), ('category2', 'discriminator2')) inst.unrelate(('category', 'discriminator'), ('category2', 'discriminator2')) self.assertEqual(inst._categories, {'category': {'discriminator':intr, 'discriminator_hash':intr}, 'category2': {'discriminator2': intr2, 'discriminator2_hash': intr2} }) self.assertEqual(inst._refs[intr], []) self.assertEqual(inst._refs[intr2], []) def test_related(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.category_name = 'category2' intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr) inst.add(intr2) inst.relate(('category', 'discriminator'), ('category2', 'discriminator2')) self.assertEqual(inst.related(intr), [intr2]) def test_related_fail(self): inst = self._makeOne() intr = DummyIntrospectable() intr2 = DummyIntrospectable() intr2.category_name = 'category2' intr2.discriminator = 'discriminator2' intr2.discriminator_hash = 'discriminator2_hash' inst.add(intr) inst.add(intr2) inst.relate(('category', 'discriminator'), ('category2', 'discriminator2')) del inst._categories['category'] self.assertRaises(KeyError, inst.related, intr) class TestIntrospectable(unittest.TestCase): def _getTargetClass(slf): from pyramid.registry import Introspectable return Introspectable def _makeOne(self, *arg, **kw): return self._getTargetClass()(*arg, **kw) def _makeOnePopulated(self): return self._makeOne('category', 'discrim', 'title', 'type') def test_conformance(self): from zope.interface.verify import verifyClass from zope.interface.verify import verifyObject from pyramid.interfaces import IIntrospectable verifyClass(IIntrospectable, self._getTargetClass()) verifyObject(IIntrospectable, self._makeOnePopulated()) def test_relate(self): inst = self._makeOnePopulated() inst.relate('a', 'b') self.assertEqual(inst._relations, [(True, 'a', 'b')]) def test_unrelate(self): inst = self._makeOnePopulated() inst.unrelate('a', 'b') self.assertEqual(inst._relations, [(False, 'a', 'b')]) def test_discriminator_hash(self): inst = self._makeOnePopulated() self.assertEqual(inst.discriminator_hash, hash(inst.discriminator)) def test___hash__(self): inst = self._makeOnePopulated() self.assertEqual(hash(inst), hash((inst.category_name,) + (inst.discriminator,))) def test___repr__(self): inst = self._makeOnePopulated() self.assertEqual( repr(inst), "") def test___nonzero__(self): inst = self._makeOnePopulated() self.assertEqual(inst.__nonzero__(), True) def test___bool__(self): inst = self._makeOnePopulated() self.assertEqual(inst.__bool__(), True) def test_register(self): introspector = DummyIntrospector() action_info = object() inst = self._makeOnePopulated() inst._relations.append((True, 'category1', 'discrim1')) inst._relations.append((False, 'category2', 'discrim2')) inst.register(introspector, action_info) self.assertEqual(inst.action_info, action_info) self.assertEqual(introspector.intrs, [inst]) self.assertEqual(introspector.relations, [(('category', 'discrim'), ('category1', 'discrim1'))]) self.assertEqual(introspector.unrelations, [(('category', 'discrim'), ('category2', 'discrim2'))]) class DummyIntrospector(object): def __init__(self): self.intrs = [] self.relations = [] self.unrelations = [] def add(self, intr): self.intrs.append(intr) def relate(self, *pairs): self.relations.append(pairs) def unrelate(self, *pairs): self.unrelations.append(pairs) class DummyModule: __path__ = "foo" __name__ = "dummy" __file__ = '' class DummyIntrospectable(object): category_name = 'category' discriminator = 'discriminator' title = 'title' type_name = 'type' order = None action_info = None discriminator_hash = 'discriminator_hash' def __hash__(self): return hash((self.category_name,) + (self.discriminator,)) from zope.interface import Interface from zope.interface import implementer class IDummyEvent(Interface): pass @implementer(IDummyEvent) class DummyEvent(object): pass pyramid-1.6/pyramid/tests/test_renderers.py0000644000076500000240000006542112575217552022006 0ustar michaelstaff00000000000000import unittest from pyramid.testing import cleanUp from pyramid import testing from pyramid.compat import text_ class TestJSON(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self, **kw): from pyramid.renderers import JSON return JSON(**kw) def test_it(self): renderer = self._makeOne()(None) result = renderer({'a':1}, {}) self.assertEqual(result, '{"a": 1}') def test_with_request_content_type_notset(self): request = testing.DummyRequest() renderer = self._makeOne()(None) renderer({'a':1}, {'request':request}) self.assertEqual(request.response.content_type, 'application/json') def test_with_request_content_type_set(self): request = testing.DummyRequest() request.response.content_type = 'text/mishmash' renderer = self._makeOne()(None) renderer({'a':1}, {'request':request}) self.assertEqual(request.response.content_type, 'text/mishmash') def test_with_custom_adapter(self): request = testing.DummyRequest() from datetime import datetime def adapter(obj, req): self.assertEqual(req, request) return obj.isoformat() now = datetime.utcnow() renderer = self._makeOne() renderer.add_adapter(datetime, adapter) result = renderer(None)({'a':now}, {'request':request}) self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_adapter2(self): request = testing.DummyRequest() from datetime import datetime def adapter(obj, req): self.assertEqual(req, request) return obj.isoformat() now = datetime.utcnow() renderer = self._makeOne(adapters=((datetime, adapter),)) result = renderer(None)({'a':now}, {'request':request}) self.assertEqual(result, '{"a": "%s"}' % now.isoformat()) def test_with_custom_serializer(self): class Serializer(object): def __call__(self, obj, **kw): self.obj = obj self.kw = kw return 'foo' serializer = Serializer() renderer = self._makeOne(serializer=serializer, baz=5) obj = {'a':'b'} result = renderer(None)(obj, {}) self.assertEqual(result, 'foo') self.assertEqual(serializer.obj, obj) self.assertEqual(serializer.kw['baz'], 5) self.assertTrue('default' in serializer.kw) def test_with_object_adapter(self): request = testing.DummyRequest() outerself = self class MyObject(object): def __init__(self, x): self.x = x def __json__(self, req): outerself.assertEqual(req, request) return {'x': self.x} objects = [MyObject(1), MyObject(2)] renderer = self._makeOne()(None) result = renderer(objects, {'request':request}) self.assertEqual(result, '[{"x": 1}, {"x": 2}]') def test_with_object_adapter_no___json__(self): class MyObject(object): def __init__(self, x): self.x = x objects = [MyObject(1), MyObject(2)] renderer = self._makeOne()(None) self.assertRaises(TypeError, renderer, objects, {}) class Test_string_renderer_factory(unittest.TestCase): def _callFUT(self, name): from pyramid.renderers import string_renderer_factory return string_renderer_factory(name) def test_it_unicode(self): renderer = self._callFUT(None) value = text_('La Pe\xc3\xb1a', 'utf-8') result = renderer(value, {}) self.assertEqual(result, value) def test_it_str(self): renderer = self._callFUT(None) value = 'La Pe\xc3\xb1a' result = renderer(value, {}) self.assertEqual(result, value) def test_it_other(self): renderer = self._callFUT(None) value = None result = renderer(value, {}) self.assertEqual(result, 'None') def test_with_request_content_type_notset(self): request = testing.DummyRequest() renderer = self._callFUT(None) renderer('', {'request':request}) self.assertEqual(request.response.content_type, 'text/plain') def test_with_request_content_type_set(self): request = testing.DummyRequest() request.response.content_type = 'text/mishmash' renderer = self._callFUT(None) renderer('', {'request':request}) self.assertEqual(request.response.content_type, 'text/mishmash') class TestRendererHelper(unittest.TestCase): def setUp(self): self.config = cleanUp() def tearDown(self): cleanUp() def _makeOne(self, *arg, **kw): from pyramid.renderers import RendererHelper return RendererHelper(*arg, **kw) def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IRendererInfo helper = self._makeOne() verifyObject(IRendererInfo, helper) def test_settings_registry_settings_is_None(self): class Dummy(object): settings = None helper = self._makeOne(registry=Dummy) self.assertEqual(helper.settings, {}) def test_settings_registry_name_is_None(self): class Dummy(object): settings = None helper = self._makeOne(registry=Dummy) self.assertEqual(helper.name, None) self.assertEqual(helper.type, '') def test_settings_registry_settings_is_not_None(self): class Dummy(object): settings = {'a':1} helper = self._makeOne(registry=Dummy) self.assertEqual(helper.settings, {'a':1}) def _registerRendererFactory(self): from pyramid.interfaces import IRendererFactory def renderer(*arg): def respond(*arg): return arg renderer.respond = respond return respond self.config.registry.registerUtility(renderer, IRendererFactory, name='.foo') return renderer def _registerResponseFactory(self): from pyramid.interfaces import IResponseFactory class ResponseFactory(object): pass self.config.registry.registerUtility( lambda r: ResponseFactory(), IResponseFactory ) def test_render_to_response(self): self._registerRendererFactory() self._registerResponseFactory() request = Dummy() helper = self._makeOne('loo.foo') response = helper.render_to_response('values', {}, request=request) self.assertEqual(response.app_iter[0], 'values') self.assertEqual(response.app_iter[1], {}) def test_get_renderer(self): factory = self._registerRendererFactory() helper = self._makeOne('loo.foo') self.assertEqual(helper.get_renderer(), factory.respond) def test_render_view(self): self._registerRendererFactory() self._registerResponseFactory() request = Dummy() helper = self._makeOne('loo.foo') view = 'view' context = 'context' request = testing.DummyRequest() response = 'response' response = helper.render_view(request, response, view, context) self.assertEqual(response.app_iter[0], 'response') self.assertEqual(response.app_iter[1], {'renderer_info': helper, 'renderer_name': 'loo.foo', 'request': request, 'context': 'context', 'view': 'view', 'req': request,} ) def test_render_explicit_registry(self): factory = self._registerRendererFactory() class DummyRegistry(object): def __init__(self): self.responses = [factory, lambda *arg: {}, None] def queryUtility(self, iface, name=None): self.queried = True return self.responses.pop(0) def notify(self, event): self.event = event reg = DummyRegistry() helper = self._makeOne('loo.foo', registry=reg) result = helper.render('value', {}) self.assertEqual(result[0], 'value') self.assertEqual(result[1], {}) self.assertTrue(reg.queried) self.assertEqual(reg.event, {}) self.assertEqual(reg.event.__class__.__name__, 'BeforeRender') def test_render_system_values_is_None(self): self._registerRendererFactory() request = Dummy() context = Dummy() request.context = context helper = self._makeOne('loo.foo') result = helper.render('values', None, request=request) system = {'request':request, 'context':context, 'renderer_name':'loo.foo', 'view':None, 'renderer_info':helper, 'req':request, } self.assertEqual(result[0], 'values') self.assertEqual(result[1], system) def test__make_response_request_is_None(self): request = None helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.body, b'abc') def test__make_response_request_is_None_response_factory_exists(self): self._registerResponseFactory() request = None helper = self._makeOne('loo.foo') response = helper._make_response(b'abc', request) self.assertEqual(response.__class__.__name__, 'ResponseFactory') self.assertEqual(response.body, b'abc') def test__make_response_result_is_unicode(self): from pyramid.response import Response request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') la = text_('/La Pe\xc3\xb1a', 'utf-8') response = helper._make_response(la, request) self.assertEqual(response.body, la.encode('utf-8')) def test__make_response_result_is_str(self): from pyramid.response import Response request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') la = text_('/La Pe\xc3\xb1a', 'utf-8') response = helper._make_response(la.encode('utf-8'), request) self.assertEqual(response.body, la.encode('utf-8')) def test__make_response_result_is_iterable(self): from pyramid.response import Response request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') la = text_('/La Pe\xc3\xb1a', 'utf-8') response = helper._make_response([la.encode('utf-8')], request) self.assertEqual(response.body, la.encode('utf-8')) def test__make_response_result_is_other(self): self._registerResponseFactory() request = None helper = self._makeOne('loo.foo') result = object() response = helper._make_response(result, request) self.assertEqual(response.body, result) def test__make_response_result_is_None_no_body(self): from pyramid.response import Response request = testing.DummyRequest() request.response = Response() helper = self._makeOne('loo.foo') response = helper._make_response(None, request) self.assertEqual(response.body, b'') def test__make_response_result_is_None_existing_body_not_molested(self): from pyramid.response import Response request = testing.DummyRequest() response = Response() response.body = b'abc' request.response = response helper = self._makeOne('loo.foo') response = helper._make_response(None, request) self.assertEqual(response.body, b'abc') def test_with_alternate_response_factory(self): from pyramid.interfaces import IResponseFactory class ResponseFactory(object): def __init__(self): pass self.config.registry.registerUtility( lambda r: ResponseFactory(), IResponseFactory ) request = testing.DummyRequest() helper = self._makeOne('loo.foo') response = helper._make_response(b'abc', request) self.assertEqual(response.__class__, ResponseFactory) self.assertEqual(response.body, b'abc') def test__make_response_with_real_request(self): # functional from pyramid.request import Request request = Request({}) request.registry = self.config.registry request.response.status = '406 You Lose' helper = self._makeOne('loo.foo') response = helper._make_response('abc', request) self.assertEqual(response.status, '406 You Lose') self.assertEqual(response.body, b'abc') def test_clone_noargs(self): helper = self._makeOne('name', 'package', 'registry') cloned_helper = helper.clone() self.assertEqual(cloned_helper.name, 'name') self.assertEqual(cloned_helper.package, 'package') self.assertEqual(cloned_helper.registry, 'registry') self.assertFalse(helper is cloned_helper) def test_clone_allargs(self): helper = self._makeOne('name', 'package', 'registry') cloned_helper = helper.clone(name='name2', package='package2', registry='registry2') self.assertEqual(cloned_helper.name, 'name2') self.assertEqual(cloned_helper.package, 'package2') self.assertEqual(cloned_helper.registry, 'registry2') self.assertFalse(helper is cloned_helper) def test_renderer_absolute_file(self): registry = self.config.registry settings = {} registry.settings = settings from pyramid.interfaces import IRendererFactory import os here = os.path.dirname(os.path.abspath(__file__)) fixture = os.path.join(here, 'fixtures/minimal.pt') def factory(info, **kw): return info self.config.registry.registerUtility( factory, IRendererFactory, name='.pt') result = self._makeOne(fixture).renderer self.assertEqual(result.registry, registry) self.assertEqual(result.type, '.pt') self.assertEqual(result.package, None) self.assertEqual(result.name, fixture) self.assertEqual(result.settings, settings) def test_renderer_with_package(self): import pyramid registry = self.config.registry settings = {} registry.settings = settings from pyramid.interfaces import IRendererFactory import os here = os.path.dirname(os.path.abspath(__file__)) fixture = os.path.join(here, 'fixtures/minimal.pt') def factory(info, **kw): return info self.config.registry.registerUtility( factory, IRendererFactory, name='.pt') result = self._makeOne(fixture, pyramid).renderer self.assertEqual(result.registry, registry) self.assertEqual(result.type, '.pt') self.assertEqual(result.package, pyramid) self.assertEqual(result.name, fixture) self.assertEqual(result.settings, settings) def test_renderer_missing(self): inst = self._makeOne('foo') self.assertRaises(ValueError, getattr, inst, 'renderer') class TestNullRendererHelper(unittest.TestCase): def setUp(self): self.config = cleanUp() def tearDown(self): cleanUp() def _makeOne(self, *arg, **kw): from pyramid.renderers import NullRendererHelper return NullRendererHelper(*arg, **kw) def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IRendererInfo helper = self._makeOne() verifyObject(IRendererInfo, helper) def test_render_view(self): helper = self._makeOne() self.assertEqual(helper.render_view(None, True, None, None), True) def test_render(self): helper = self._makeOne() self.assertEqual(helper.render(True, None, None), True) def test_render_to_response(self): helper = self._makeOne() self.assertEqual(helper.render_to_response(True, None, None), True) def test_clone(self): helper = self._makeOne() self.assertTrue(helper.clone() is helper) class Test_render(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, renderer_name, value, request=None, package=None): from pyramid.renderers import render return render(renderer_name, value, request=request, package=package) def _registerRenderer(self): renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') renderer.string_response = 'abc' return renderer def test_it_no_request(self): renderer = self._registerRenderer() result = self._callFUT('abc/def.pt', dict(a=1)) self.assertEqual(result, 'abc') renderer.assert_(a=1) renderer.assert_(request=None) def test_it_with_request(self): renderer = self._registerRenderer() request = testing.DummyRequest() result = self._callFUT('abc/def.pt', dict(a=1), request=request) self.assertEqual(result, 'abc') renderer.assert_(a=1) renderer.assert_(request=request) def test_it_with_package(self): import pyramid.tests renderer = self._registerRenderer() request = testing.DummyRequest() result = self._callFUT('abc/def.pt', dict(a=1), request=request, package=pyramid.tests) self.assertEqual(result, 'abc') renderer.assert_(a=1) renderer.assert_(request=request) def test_response_preserved(self): request = testing.DummyRequest() response = object() # should error if mutated request.response = response # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) self.assertEqual(result, '{"a": 1}') self.assertEqual(request.response, response) def test_no_response_to_preserve(self): from pyramid.decorator import reify class DummyRequestWithClassResponse(object): _response = DummyResponse() _response.content_type = None _response.default_content_type = None @reify def response(self): return self._response request = DummyRequestWithClassResponse() # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) self.assertEqual(result, '{"a": 1}') self.assertFalse('response' in request.__dict__) class Test_render_to_response(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, renderer_name, value, request=None, package=None, response=None): from pyramid.renderers import render_to_response return render_to_response(renderer_name, value, request=request, package=package, response=response) def test_it_no_request(self): renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') renderer.string_response = 'abc' response = self._callFUT('abc/def.pt', dict(a=1)) self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=None) def test_it_with_request(self): renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') renderer.string_response = 'abc' request = testing.DummyRequest() response = self._callFUT('abc/def.pt', dict(a=1), request=request) self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=request) def test_it_with_package(self): import pyramid.tests renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') renderer.string_response = 'abc' request = testing.DummyRequest() response = self._callFUT('abc/def.pt', dict(a=1), request=request, package=pyramid.tests) self.assertEqual(response.body, b'abc') renderer.assert_(a=1) renderer.assert_(request=request) def test_response_preserved(self): request = testing.DummyRequest() response = object() # should error if mutated request.response = response # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) self.assertEqual(result.body, b'{"a": 1}') self.assertNotEqual(request.response, result) self.assertEqual(request.response, response) def test_no_response_to_preserve(self): from pyramid.decorator import reify class DummyRequestWithClassResponse(object): _response = DummyResponse() _response.content_type = None _response.default_content_type = None @reify def response(self): return self._response request = DummyRequestWithClassResponse() # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request) self.assertEqual(result.body, b'{"a": 1}') self.assertFalse('response' in request.__dict__) def test_custom_response_object(self): class DummyRequestWithClassResponse(object): pass request = DummyRequestWithClassResponse() response = DummyResponse() # use a json renderer, which will mutate the response result = self._callFUT('json', dict(a=1), request=request, response=response) self.assertTrue(result is response) self.assertEqual(result.body, b'{"a": 1}') self.assertFalse('response' in request.__dict__) class Test_temporary_response(unittest.TestCase): def _callFUT(self, request): from pyramid.renderers import temporary_response return temporary_response(request) def test_restores_response(self): request = testing.DummyRequest() orig_response = request.response with self._callFUT(request): request.response = object() self.assertEqual(request.response, orig_response) def test_restores_response_on_exception(self): request = testing.DummyRequest() orig_response = request.response try: with self._callFUT(request): request.response = object() raise RuntimeError() except RuntimeError: self.assertEqual(request.response, orig_response) else: # pragma: no cover self.fail("RuntimeError not raised") def test_restores_response_to_none(self): request = testing.DummyRequest(response=None) with self._callFUT(request): request.response = object() self.assertEqual(request.response, None) def test_deletes_response(self): request = testing.DummyRequest() with self._callFUT(request): request.response = object() self.assertTrue('response' not in request.__dict__) def test_does_not_delete_response_if_no_response_to_delete(self): request = testing.DummyRequest() with self._callFUT(request): pass self.assertTrue('response' not in request.__dict__) class Test_get_renderer(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, renderer_name, **kw): from pyramid.renderers import get_renderer return get_renderer(renderer_name, **kw) def test_it_no_package(self): renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') result = self._callFUT('abc/def.pt') self.assertEqual(result, renderer) def test_it_with_package(self): import pyramid.tests renderer = self.config.testing_add_renderer( 'pyramid.tests:abc/def.pt') result = self._callFUT('abc/def.pt', package=pyramid.tests) self.assertEqual(result, renderer) class TestJSONP(unittest.TestCase): def _makeOne(self, param_name='callback'): from pyramid.renderers import JSONP return JSONP(param_name) def test_render_to_jsonp(self): renderer_factory = self._makeOne() renderer = renderer_factory(None) request = testing.DummyRequest() request.GET['callback'] = 'callback' result = renderer({'a':'1'}, {'request':request}) self.assertEqual(result, '/**/callback({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') def test_render_to_jsonp_with_dot(self): renderer_factory = self._makeOne() renderer = renderer_factory(None) request = testing.DummyRequest() request.GET['callback'] = 'angular.callbacks._0' result = renderer({'a':'1'}, {'request':request}) self.assertEqual(result, '/**/angular.callbacks._0({"a": "1"});') self.assertEqual(request.response.content_type, 'application/javascript') def test_render_to_json(self): renderer_factory = self._makeOne() renderer = renderer_factory(None) request = testing.DummyRequest() result = renderer({'a':'1'}, {'request':request}) self.assertEqual(result, '{"a": "1"}') self.assertEqual(request.response.content_type, 'application/json') def test_render_without_request(self): renderer_factory = self._makeOne() renderer = renderer_factory(None) result = renderer({'a':'1'}, {}) self.assertEqual(result, '{"a": "1"}') def test_render_to_jsonp_invalid_callback(self): from pyramid.httpexceptions import HTTPBadRequest renderer_factory = self._makeOne() renderer = renderer_factory(None) request = testing.DummyRequest() request.GET['callback'] = '78mycallback' self.assertRaises(HTTPBadRequest, renderer, {'a':'1'}, {'request':request}) class Dummy: pass class DummyResponse: status = '200 OK' default_content_type = 'text/html' content_type = default_content_type headerlist = () app_iter = () body = b'' # compat for renderer that will set unicode on py3 def _set_text(self, val): # pragma: no cover self.body = val.encode('utf8') text = property(fset=_set_text) pyramid-1.6/pyramid/tests/test_request.py0000644000076500000240000005246412524266531021503 0ustar michaelstaff00000000000000from collections import deque import unittest from pyramid import testing from pyramid.compat import ( PY3, text_, bytes_, native_, ) from pyramid.security import ( AuthenticationAPIMixin, AuthorizationAPIMixin, ) class TestRequest(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _getTargetClass(self): from pyramid.request import Request return Request def _makeOne(self, environ=None): if environ is None: environ = {} return self._getTargetClass()(environ) def _registerResourceURL(self): from pyramid.interfaces import IResourceURL from zope.interface import Interface class DummyResourceURL(object): def __init__(self, context, request): self.physical_path = '/context/' self.virtual_path = '/context/' self.config.registry.registerAdapter( DummyResourceURL, (Interface, Interface), IResourceURL) def test_class_conforms_to_IRequest(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IRequest verifyClass(IRequest, self._getTargetClass()) def test_instance_conforms_to_IRequest(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IRequest verifyObject(IRequest, self._makeOne()) def test_ResponseClass_is_pyramid_Response(self): from pyramid.response import Response cls = self._getTargetClass() self.assertEqual(cls.ResponseClass, Response) def test_implements_security_apis(self): apis = (AuthenticationAPIMixin, AuthorizationAPIMixin) r = self._makeOne() self.assertTrue(isinstance(r, apis)) def test_charset_defaults_to_utf8(self): r = self._makeOne({'PATH_INFO':'/'}) self.assertEqual(r.charset, 'UTF-8') def test_exception_defaults_to_None(self): r = self._makeOne({'PATH_INFO':'/'}) self.assertEqual(r.exception, None) def test_matchdict_defaults_to_None(self): r = self._makeOne({'PATH_INFO':'/'}) self.assertEqual(r.matchdict, None) def test_matched_route_defaults_to_None(self): r = self._makeOne({'PATH_INFO':'/'}) self.assertEqual(r.matched_route, None) def test_params_decoded_from_utf_8_by_default(self): environ = { 'PATH_INFO':'/', 'QUERY_STRING':'la=La%20Pe%C3%B1a' } request = self._makeOne(environ) request.charset = None self.assertEqual(request.GET['la'], text_(b'La Pe\xf1a')) def test_tmpl_context(self): from pyramid.request import TemplateContext inst = self._makeOne() result = inst.tmpl_context self.assertEqual(result.__class__, TemplateContext) def test_session_configured(self): from pyramid.interfaces import ISessionFactory inst = self._makeOne() def factory(request): return 'orangejuice' self.config.registry.registerUtility(factory, ISessionFactory) inst.registry = self.config.registry self.assertEqual(inst.session, 'orangejuice') self.assertEqual(inst.__dict__['session'], 'orangejuice') def test_session_not_configured(self): inst = self._makeOne() inst.registry = self.config.registry self.assertRaises(AttributeError, getattr, inst, 'session') def test_setattr_and_getattr_dotnotation(self): inst = self._makeOne() inst.foo = 1 self.assertEqual(inst.foo, 1) def test_setattr_and_getattr(self): environ = {} inst = self._makeOne(environ) setattr(inst, 'bar', 1) self.assertEqual(getattr(inst, 'bar'), 1) self.assertEqual(environ, {}) # make sure we're not using adhoc attrs def test_add_response_callback(self): inst = self._makeOne() self.assertEqual(len(inst.response_callbacks), 0) def callback(request, response): """ """ inst.add_response_callback(callback) self.assertEqual(list(inst.response_callbacks), [callback]) inst.add_response_callback(callback) self.assertEqual(list(inst.response_callbacks), [callback, callback]) def test__process_response_callbacks(self): inst = self._makeOne() def callback1(request, response): request.called1 = True response.called1 = True def callback2(request, response): request.called2 = True response.called2 = True 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(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(len(inst.finished_callbacks), 0) def callback(request): """ """ inst.add_finished_callback(callback) self.assertEqual(list(inst.finished_callbacks), [callback]) inst.add_finished_callback(callback) self.assertEqual(list(inst.finished_callbacks), [callback, callback]) def test__process_finished_callbacks(self): inst = self._makeOne() def callback1(request): request.called1 = True def callback2(request): request.called2 = True 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(len(inst.finished_callbacks), 0) def test_resource_url(self): self._registerResourceURL() environ = { 'PATH_INFO':'/', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', 'wsgi.url_scheme':'http', } inst = self._makeOne(environ) root = DummyContext() result = inst.resource_url(root) self.assertEqual(result, 'http://example.com/context/') def test_route_url(self): environ = { 'PATH_INFO':'/', 'SERVER_NAME':'example.com', 'SERVER_PORT':'5432', 'QUERY_STRING':'la=La%20Pe%C3%B1a', 'wsgi.url_scheme':'http', } from pyramid.interfaces import IRoutesMapper inst = self._makeOne(environ) mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) self.config.registry.registerUtility(mapper, IRoutesMapper) result = inst.route_url('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, _anchor=text_("foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') def test_route_path(self): environ = { 'PATH_INFO':'/', 'SERVER_NAME':'example.com', 'SERVER_PORT':'5432', 'QUERY_STRING':'la=La%20Pe%C3%B1a', 'wsgi.url_scheme':'http', } from pyramid.interfaces import IRoutesMapper inst = self._makeOne(environ) mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) self.config.registry.registerUtility(mapper, IRoutesMapper) result = inst.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, _anchor=text_("foo")) self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') def test_static_url(self): from pyramid.interfaces import IStaticURLInfo environ = { 'PATH_INFO':'/', 'SERVER_NAME':'example.com', 'SERVER_PORT':'5432', 'QUERY_STRING':'', 'wsgi.url_scheme':'http', } request = self._makeOne(environ) info = DummyStaticURLInfo('abc') self.config.registry.registerUtility(info, IStaticURLInfo) result = request.static_url('pyramid.tests:static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {}) ) def test_is_response_false(self): request = self._makeOne() request.registry = self.config.registry self.assertEqual(request.is_response('abc'), False) def test_is_response_true_ob_is_pyramid_response(self): from pyramid.response import Response r = Response('hello') request = self._makeOne() request.registry = self.config.registry self.assertEqual(request.is_response(r), True) def test_is_response_false_adapter_is_not_self(self): from pyramid.interfaces import IResponse request = self._makeOne() request.registry = self.config.registry def adapter(ob): return object() class Foo(object): pass foo = Foo() request.registry.registerAdapter(adapter, (Foo,), IResponse) self.assertEqual(request.is_response(foo), False) def test_is_response_adapter_true(self): from pyramid.interfaces import IResponse request = self._makeOne() request.registry = self.config.registry class Foo(object): pass foo = Foo() def adapter(ob): return ob request.registry.registerAdapter(adapter, (Foo,), IResponse) self.assertEqual(request.is_response(foo), True) def test_json_body_invalid_json(self): request = self._makeOne({'REQUEST_METHOD':'POST'}) request.body = b'{' self.assertRaises(ValueError, getattr, request, 'json_body') def test_json_body_valid_json(self): request = self._makeOne({'REQUEST_METHOD':'POST'}) request.body = b'{"a":1}' self.assertEqual(request.json_body, {'a':1}) def test_json_body_alternate_charset(self): import json request = self._makeOne({'REQUEST_METHOD':'POST'}) inp = text_( b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8' ) if PY3: body = bytes(json.dumps({'a':inp}), 'utf-16') else: body = json.dumps({'a':inp}).decode('utf-8').encode('utf-16') request.body = body request.content_type = 'application/json; charset=utf-16' self.assertEqual(request.json_body, {'a':inp}) def test_json_body_GET_request(self): request = self._makeOne({'REQUEST_METHOD':'GET'}) self.assertRaises(ValueError, getattr, request, 'json_body') def test_set_property(self): request = self._makeOne() opts = [2, 1] def connect(obj): return opts.pop() request.set_property(connect, name='db') self.assertEqual(1, request.db) self.assertEqual(2, request.db) def test_set_property_reify(self): request = self._makeOne() opts = [2, 1] def connect(obj): return opts.pop() request.set_property(connect, name='db', reify=True) self.assertEqual(1, request.db) self.assertEqual(1, request.db) class Test_route_request_iface(unittest.TestCase): def _callFUT(self, name): from pyramid.request import route_request_iface return route_request_iface(name) def test_it(self): iface = self._callFUT('routename') self.assertEqual(iface.__name__, 'routename_IRequest') self.assertTrue(hasattr(iface, 'combined')) self.assertEqual(iface.combined.__name__, 'routename_combined_IRequest') def test_it_routename_with_spaces(self): # see https://github.com/Pylons/pyramid/issues/232 iface = self._callFUT('routename with spaces') self.assertEqual(iface.__name__, 'routename with spaces_IRequest') self.assertTrue(hasattr(iface, 'combined')) self.assertEqual(iface.combined.__name__, 'routename with spaces_combined_IRequest') class Test_add_global_response_headers(unittest.TestCase): def _callFUT(self, request, headerlist): from pyramid.request import add_global_response_headers return add_global_response_headers(request, headerlist) def test_it(self): request = DummyRequest() response = DummyResponse() self._callFUT(request, [('c', 1)]) self.assertEqual(len(request.response_callbacks), 1) request.response_callbacks[0](None, response) self.assertEqual(response.headerlist, [('c', 1)] ) class Test_call_app_with_subpath_as_path_info(unittest.TestCase): def _callFUT(self, request, app): from pyramid.request import call_app_with_subpath_as_path_info return call_app_with_subpath_as_path_info(request, app) def test_it_all_request_and_environment_data_missing(self): request = DummyRequest({}) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '') self.assertEqual(request.environ['PATH_INFO'], '/') def test_it_with_subpath_and_path_info(self): request = DummyRequest({'PATH_INFO':'/hello'}) request.subpath = ('hello',) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '') self.assertEqual(request.environ['PATH_INFO'], '/hello') def test_it_with_subpath_and_path_info_path_info_endswith_slash(self): request = DummyRequest({'PATH_INFO':'/hello/'}) request.subpath = ('hello',) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '') self.assertEqual(request.environ['PATH_INFO'], '/hello/') def test_it_with_subpath_and_path_info_extra_script_name(self): request = DummyRequest({'PATH_INFO':'/hello', 'SCRIPT_NAME':'/script'}) request.subpath = ('hello',) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '/script') self.assertEqual(request.environ['PATH_INFO'], '/hello') def test_it_with_extra_slashes_in_path_info(self): request = DummyRequest({'PATH_INFO':'//hello/', 'SCRIPT_NAME':'/script'}) request.subpath = ('hello',) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '/script') self.assertEqual(request.environ['PATH_INFO'], '/hello/') def test_subpath_path_info_and_script_name_have_utf8(self): encoded = native_(text_(b'La Pe\xc3\xb1a')) decoded = text_(bytes_(encoded), 'utf-8') request = DummyRequest({'PATH_INFO':'/' + encoded, 'SCRIPT_NAME':'/' + encoded}) request.subpath = (decoded, ) response = self._callFUT(request, 'app') self.assertTrue(request.copied) self.assertEqual(response, 'app') self.assertEqual(request.environ['SCRIPT_NAME'], '/' + encoded) self.assertEqual(request.environ['PATH_INFO'], '/' + encoded) class Test_apply_request_extensions(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, request, extensions=None): from pyramid.request import apply_request_extensions return apply_request_extensions(request, extensions=extensions) def test_it_with_registry(self): from pyramid.interfaces import IRequestExtensions extensions = Dummy() extensions.methods = {'foo': lambda x, y: y} extensions.descriptors = {'bar': property(lambda x: 'bar')} self.config.registry.registerUtility(extensions, IRequestExtensions) request = DummyRequest() request.registry = self.config.registry self._callFUT(request) self.assertEqual(request.bar, 'bar') self.assertEqual(request.foo('abc'), 'abc') def test_it_override_extensions(self): from pyramid.interfaces import IRequestExtensions ignore = Dummy() ignore.methods = {'x': lambda x, y, z: 'asdf'} ignore.descriptors = {'bar': property(lambda x: 'asdf')} self.config.registry.registerUtility(ignore, IRequestExtensions) request = DummyRequest() request.registry = self.config.registry extensions = Dummy() extensions.methods = {'foo': lambda x, y: y} extensions.descriptors = {'bar': property(lambda x: 'bar')} self._callFUT(request, extensions=extensions) self.assertRaises(AttributeError, lambda: request.x) self.assertEqual(request.bar, 'bar') self.assertEqual(request.foo('abc'), 'abc') class Dummy(object): pass class Test_subclassing_Request(unittest.TestCase): def test_subclass(self): from pyramid.interfaces import IRequest from pyramid.request import Request class RequestSub(Request): pass self.assertTrue(hasattr(Request, '__provides__')) self.assertTrue(hasattr(Request, '__implemented__')) self.assertTrue(hasattr(Request, '__providedBy__')) self.assertFalse(hasattr(RequestSub, '__provides__')) self.assertTrue(hasattr(RequestSub, '__providedBy__')) self.assertTrue(hasattr(RequestSub, '__implemented__')) self.assertTrue(IRequest.implementedBy(RequestSub)) # The call to implementedBy will add __provides__ to the class self.assertTrue(hasattr(RequestSub, '__provides__')) def test_subclass_with_implementer(self): from pyramid.interfaces import IRequest from pyramid.request import Request from pyramid.util import InstancePropertyHelper from zope.interface import implementer @implementer(IRequest) class RequestSub(Request): pass self.assertTrue(hasattr(Request, '__provides__')) self.assertTrue(hasattr(Request, '__implemented__')) self.assertTrue(hasattr(Request, '__providedBy__')) self.assertTrue(hasattr(RequestSub, '__provides__')) self.assertTrue(hasattr(RequestSub, '__providedBy__')) self.assertTrue(hasattr(RequestSub, '__implemented__')) req = RequestSub({}) helper = InstancePropertyHelper() helper.apply_properties(req, {'b': 'b'}) self.assertTrue(IRequest.providedBy(req)) self.assertTrue(IRequest.implementedBy(RequestSub)) def test_subclass_mutate_before_providedBy(self): from pyramid.interfaces import IRequest from pyramid.request import Request from pyramid.util import InstancePropertyHelper class RequestSub(Request): pass req = RequestSub({}) helper = InstancePropertyHelper() helper.apply_properties(req, {'b': 'b'}) self.assertTrue(IRequest.providedBy(req)) self.assertTrue(IRequest.implementedBy(RequestSub)) class DummyRequest(object): def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ def add_response_callback(self, callback): self.response_callbacks = [callback] def get_response(self, app): return app def copy(self): self.copied = True return self class DummyResponse: def __init__(self): self.headerlist = [] class DummyContext: pass class DummyRoutesMapper: raise_exc = None def __init__(self, route=None, raise_exc=False): self.route = route def get_route(self, route_name): return self.route class DummyRoute: pregenerator = None def __init__(self, result='/1/2/3'): self.result = result def generate(self, kw): self.kw = kw return self.result class DummyStaticURLInfo: def __init__(self, result): self.result = result def generate(self, path, request, **kw): self.args = path, request, kw return self.result pyramid-1.6/pyramid/tests/test_response.py0000644000076500000240000001520112524266531021635 0ustar michaelstaff00000000000000import io import mimetypes import os import unittest from pyramid import testing 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() self.assertTrue(IResponse.implementedBy(cls)) def test_provides_IResponse(self): from pyramid.interfaces import IResponse inst = self._getTargetClass()() self.assertTrue(IResponse.providedBy(inst)) class TestFileResponse(unittest.TestCase): def _makeOne(self, file, **kw): from pyramid.response import FileResponse return FileResponse(file, **kw) def _getPath(self, suffix='txt'): here = os.path.dirname(__file__) return os.path.join(here, 'fixtures', 'minimal.%s' % (suffix,)) 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_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 return FileIter(file, block_size) def test___iter__(self): f = io.BytesIO(b'abc') inst = self._makeOne(f, 1) self.assertEqual(inst.__iter__(), inst) def test_iteration(self): data = b'abcdef' f = io.BytesIO(b'abcdef') inst = self._makeOne(f, 1) r = b'' for x in inst: self.assertEqual(len(x), 1) r+=x self.assertEqual(r, data) def test_close(self): f = io.BytesIO(b'abc') inst = self._makeOne(f, 1) inst.close() self.assertTrue(f.closed) class Test_patch_mimetypes(unittest.TestCase): def _callFUT(self, module): from pyramid.response import init_mimetypes return init_mimetypes(module) def test_has_init(self): class DummyMimetypes(object): def init(self): self.initted = True module = DummyMimetypes() result = self._callFUT(module) self.assertEqual(result, True) self.assertEqual(module.initted, True) def test_missing_init(self): class DummyMimetypes(object): pass module = DummyMimetypes() result = self._callFUT(module) self.assertEqual(result, False) class TestResponseAdapter(unittest.TestCase): def setUp(self): registry = Dummy() self.config = testing.setUp(registry=registry) def tearDown(self): self.config.end() def _makeOne(self, *types_or_ifaces): from pyramid.response import response_adapter return response_adapter(*types_or_ifaces) def test_register_single(self): from zope.interface import Interface class IFoo(Interface): pass dec = self._makeOne(IFoo) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.adapters, [(foo, IFoo)]) def test_register_multi(self): from zope.interface import Interface class IFoo(Interface): pass class IBar(Interface): pass dec = self._makeOne(IFoo, IBar) def foo(): pass config = DummyConfigurator() scanner = Dummy() scanner.config = config dec.register(scanner, None, foo) self.assertEqual(config.adapters, [(foo, IFoo), (foo, IBar)]) def test___call__(self): from zope.interface import Interface class IFoo(Interface): pass dec = self._makeOne(IFoo) dummy_venusian = DummyVenusian() dec.venusian = dummy_venusian def foo(): pass dec(foo) 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 class DummyConfigurator(object): def __init__(self): self.adapters = [] def add_response_adapter(self, wrapped, type_or_iface): self.adapters.append((wrapped, type_or_iface)) class DummyVenusian(object): def __init__(self): self.attached = [] def attach(self, wrapped, fn, category=None): self.attached.append((wrapped, fn, category)) pyramid-1.6/pyramid/tests/test_router.py0000644000076500000240000016361712642137120021326 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TestRouter(unittest.TestCase): def setUp(self): self.config = testing.setUp() self.registry = self.config.registry def tearDown(self): testing.tearDown() def _registerRouteRequest(self, name): from pyramid.interfaces import IRouteRequest from pyramid.request import route_request_iface iface = route_request_iface(name) self.registry.registerUtility(iface, IRouteRequest, name=name) return iface def _connectRoute(self, name, path, factory=None): from pyramid.interfaces import IRoutesMapper from pyramid.urldispatch import RoutesMapper mapper = self.registry.queryUtility(IRoutesMapper) if mapper is None: mapper = RoutesMapper() self.registry.registerUtility(mapper, IRoutesMapper) return mapper.connect(name, path, factory) def _registerLogger(self): from pyramid.interfaces import IDebugLogger logger = DummyLogger() self.registry.registerUtility(logger, IDebugLogger) return logger def _registerSettings(self, **kw): settings = {'debug_authorization':False, 'debug_notfound':False, 'debug_routematch':False} settings.update(kw) self.registry.settings = settings def _registerTraverserFactory(self, context, view_name='', subpath=None, traversed=None, virtual_root=None, virtual_root_path=None, raise_error=None, **kw): from pyramid.interfaces import ITraverser if virtual_root is None: virtual_root = context if subpath is None: subpath = [] if traversed is None: traversed = [] if virtual_root_path is None: virtual_root_path = [] class DummyTraverserFactory: def __init__(self, root): self.root = root def __call__(self, request): if raise_error: raise raise_error values = {'root':self.root, 'context':context, 'view_name':view_name, 'subpath':subpath, 'traversed':traversed, 'virtual_root':virtual_root, 'virtual_root_path':virtual_root_path} kw.update(values) return kw self.registry.registerAdapter(DummyTraverserFactory, (None,), ITraverser, name='') def _registerView(self, app, name, classifier, req_iface, ctx_iface): from pyramid.interfaces import IView self.registry.registerAdapter( app, (classifier, req_iface, ctx_iface), IView, name) def _registerEventListener(self, iface): L = [] def listener(event): L.append(event) self.registry.registerHandler(listener, (iface,)) return L def _registerRootFactory(self, val): rootfactory = DummyRootFactory(val) from pyramid.interfaces import IRootFactory self.registry.registerUtility(rootfactory, IRootFactory) return rootfactory def _getTargetClass(self): from pyramid.router import Router return Router def _makeOne(self): klass = self._getTargetClass() return klass(self.registry) def _makeEnviron(self, **extras): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'localhost', 'SERVER_PORT':'8080', 'REQUEST_METHOD':'GET', 'PATH_INFO':'/', } environ.update(extras) return environ def test_ctor_registry_has_no_settings(self): self.registry.settings = None router = self._makeOne() self.assertEqual(router.debug_notfound, False) self.assertEqual(router.debug_routematch, False) self.assertFalse('debug_notfound' in router.__dict__) self.assertFalse('debug_routematch' in router.__dict__) def test_root_policy(self): context = DummyContext() self._registerTraverserFactory(context) rootfactory = self._registerRootFactory('abc') router = self._makeOne() self.assertEqual(router.root_policy, rootfactory) def test_request_factory(self): from pyramid.interfaces import IRequestFactory class DummyRequestFactory(object): pass self.registry.registerUtility(DummyRequestFactory, IRequestFactory) router = self._makeOne() self.assertEqual(router.request_factory, DummyRequestFactory) def test_tween_factories(self): from pyramid.interfaces import ITweens from pyramid.config.tweens import Tweens from pyramid.response import Response from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IResponse tweens = Tweens() self.registry.registerUtility(tweens, ITweens) L = [] def tween_factory1(handler, registry): L.append((handler, registry)) def wrapper(request): request.environ['handled'].append('one') return handler(request) wrapper.name = 'one' wrapper.child = handler return wrapper def tween_factory2(handler, registry): L.append((handler, registry)) def wrapper(request): request.environ['handled'] = ['two'] return handler(request) wrapper.name = 'two' wrapper.child = handler return wrapper tweens.add_implicit('one', tween_factory1) tweens.add_implicit('two', tween_factory2) router = self._makeOne() self.assertEqual(router.handle_request.name, 'two') self.assertEqual(router.handle_request.child.name, 'one') self.assertEqual(router.handle_request.child.child.__name__, 'handle_request') context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() view = DummyView('abc') self._registerView(self.config.derive_view(view), '', IViewClassifier, None, None) start_response = DummyStartResponse() def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) app_iter = router(environ, start_response) self.assertEqual(app_iter, [b'abc']) self.assertEqual(start_response.status, '200 OK') self.assertEqual(environ['handled'], ['two', 'one']) def test_call_traverser_default(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() logger = self._registerLogger() router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue('/' in why.args[0], why) self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_traverser_raises_notfound_class(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=HTTPNotFound) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(HTTPNotFound, router, environ, start_response) def test_traverser_raises_notfound_instance(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=HTTPNotFound('foo')) router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue('foo' in why.args[0], why) def test_traverser_raises_forbidden_class(self): from pyramid.httpexceptions import HTTPForbidden environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=HTTPForbidden) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(HTTPForbidden, router, environ, start_response) def test_traverser_raises_forbidden_instance(self): from pyramid.httpexceptions import HTTPForbidden environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=HTTPForbidden('foo')) router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) self.assertTrue('foo' in why.args[0], why) def test_call_no_view_registered_no_isettings(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) logger = self._registerLogger() router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue('/' in why.args[0], why) self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_false(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) logger = self._registerLogger() self._registerSettings(debug_notfound=False) router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue('/' in why.args[0], why) self.assertFalse('debug_notfound' in why.args[0]) self.assertEqual(len(logger.messages), 0) def test_call_no_view_registered_debug_notfound_true(self): from pyramid.httpexceptions import HTTPNotFound environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context) self._registerSettings(debug_notfound=True) logger = self._registerLogger() router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue( "debug_notfound of url http://localhost:8080/; " in why.args[0]) self.assertTrue("view_name: '', subpath: []" in why.args[0]) self.assertTrue('http://localhost:8080' in why.args[0], why) self.assertEqual(len(logger.messages), 1) message = logger.messages[0] self.assertTrue('of url http://localhost:8080' in message) self.assertTrue("path_info: " in message) self.assertTrue('DummyContext' in message) self.assertTrue("view_name: ''" in message) self.assertTrue("subpath: []" in message) def test_call_view_returns_non_iresponse(self): from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() view = DummyView('abc') self._registerView(self.config.derive_view(view), '', IViewClassifier, None, None) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) def test_call_view_returns_adapted_response(self): from pyramid.response import Response from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IResponse context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() view = DummyView('abc') self._registerView(self.config.derive_view(view), '', IViewClassifier, None, None) router = self._makeOne() start_response = DummyStartResponse() def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) app_iter = router(environ, start_response) self.assertEqual(app_iter, [b'abc']) self.assertEqual(start_response.status, '200 OK') def test_call_with_request_extensions(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequestExtensions from pyramid.interfaces import IRequest from pyramid.request import Request from pyramid.util import InstancePropertyHelper context = DummyContext() self._registerTraverserFactory(context) class Extensions(object): def __init__(self): self.methods = {} self.descriptors = {} extensions = Extensions() ext_method = lambda r: 'bar' name, fn = InstancePropertyHelper.make_property(ext_method, name='foo') extensions.descriptors[name] = fn request = Request.blank('/') request.request_iface = IRequest request.registry = self.registry def request_factory(environ): return request self.registry.registerUtility(extensions, IRequestExtensions) environ = self._makeEnviron() response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) self._registerView(self.config.derive_view(view), '', IViewClassifier, None, None) router = self._makeOne() router.request_factory = request_factory start_response = DummyStartResponse() router(environ, start_response) self.assertEqual(view.request.foo, 'bar') def test_call_view_registered_nonspecific_default_path(self): from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() self._registerView(self.config.derive_view(view), '', IViewClassifier, None, None) self._registerRootFactory(context) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['Hello world']) self.assertEqual(start_response.headers, ()) self.assertEqual(start_response.status, '200 OK') request = view.request self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, []) self.assertEqual(request.context, context) self.assertEqual(request.root, context) def test_call_view_registered_nonspecific_nondefault_path_and_subpath(self): from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context, view_name='foo', subpath=['bar'], traversed=['context']) self._registerRootFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() self._registerView(view, 'foo', IViewClassifier, None, None) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['Hello world']) self.assertEqual(start_response.headers, ()) self.assertEqual(start_response.status, '200 OK') request = view.request self.assertEqual(request.view_name, 'foo') self.assertEqual(request.subpath, ['bar']) self.assertEqual(request.context, context) self.assertEqual(request.root, context) def test_call_view_registered_specific_success(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context) self._registerRootFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['Hello world']) self.assertEqual(start_response.headers, ()) self.assertEqual(start_response.status, '200 OK') request = view.request self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, []) self.assertEqual(request.context, context) self.assertEqual(request.root, context) def test_call_view_registered_specific_fail(self): from zope.interface import Interface from zope.interface import directlyProvides from pyramid.httpexceptions import HTTPNotFound from pyramid.interfaces import IViewClassifier class IContext(Interface): pass class INotContext(Interface): pass from pyramid.interfaces import IRequest context = DummyContext() directlyProvides(context, INotContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = DummyView(response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(HTTPNotFound, router, environ, start_response) def test_call_view_raises_forbidden(self): from zope.interface import Interface from zope.interface import directlyProvides from pyramid.httpexceptions import HTTPForbidden class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = DummyView(response, raise_exception=HTTPForbidden("unauthorized")) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) self.assertEqual(why.args[0], 'unauthorized') def test_call_view_raises_notfound(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.httpexceptions import HTTPNotFound context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() view = DummyView(response, raise_exception=HTTPNotFound("notfound")) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertEqual(why.args[0], 'notfound') def test_call_view_raises_response_cleared(self): from zope.interface import Interface from zope.interface import directlyProvides from pyramid.interfaces import IExceptionViewClassifier class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) def view(context, request): request.response.a = 1 raise KeyError def exc_view(context, request): self.assertFalse(hasattr(request.response, 'a')) request.response.body = b'OK' return request.response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) self._registerView(exc_view, '', IExceptionViewClassifier, IRequest, KeyError) router = self._makeOne() start_response = DummyStartResponse() itera = router(environ, start_response) self.assertEqual(itera, [b'OK']) def test_call_request_has_response_callbacks(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse('200 OK') def view(context, request): def callback(request, response): response.called_back = True request.add_response_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() router(environ, start_response) self.assertEqual(response.called_back, True) def test_call_request_has_finished_callbacks_when_view_succeeds(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse('200 OK') def view(context, request): def callback(request): request.environ['called_back'] = True request.add_finished_callback(callback) return response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() router(environ, start_response) self.assertEqual(environ['called_back'], True) def test_call_request_has_finished_callbacks_when_view_raises(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) def view(context, request): def callback(request): request.environ['called_back'] = True request.add_finished_callback(callback) raise NotImplementedError environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, IContext) router = self._makeOne() start_response = DummyStartResponse() exc_raised(NotImplementedError, router, environ, start_response) self.assertEqual(environ['called_back'], True) def test_call_request_factory_raises(self): # making sure finally doesnt barf when a request cannot be created environ = self._makeEnviron() router = self._makeOne() def dummy_request_factory(environ): raise NotImplementedError router.request_factory = dummy_request_factory start_response = DummyStartResponse() exc_raised(NotImplementedError, router, environ, start_response) def test_call_eventsends(self): from pyramid.interfaces import INewRequest from pyramid.interfaces import INewResponse from pyramid.interfaces import IContextFound from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, None, None) request_events = self._registerEventListener(INewRequest) context_found_events = self._registerEventListener(IContextFound) response_events = self._registerEventListener(INewResponse) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(len(request_events), 1) self.assertEqual(request_events[0].request.environ, environ) self.assertEqual(len(context_found_events), 1) self.assertEqual(context_found_events[0].request.environ, environ) self.assertEqual(context_found_events[0].request.context, context) self.assertEqual(len(response_events), 1) self.assertEqual(response_events[0].response, response) self.assertEqual(response_events[0].request.context, context) self.assertEqual(result, response.app_iter) def test_call_newrequest_evllist_exc_can_be_caught_by_exceptionview(self): from pyramid.interfaces import INewRequest from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron() def listener(event): raise KeyError self.registry.registerHandler(listener, (INewRequest,)) exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] exception_view = DummyView(exception_response) environ = self._makeEnviron() self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, KeyError) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, exception_response.app_iter) def test_call_pushes_and_pops_threadlocal_manager(self): from pyramid.interfaces import IViewClassifier context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, None, None) router = self._makeOne() start_response = DummyStartResponse() router.threadlocal_manager = DummyThreadLocalManager() router(environ, start_response) self.assertEqual(len(router.threadlocal_manager.pushed), 1) self.assertEqual(len(router.threadlocal_manager.popped), 1) def test_call_route_matches_and_has_factory(self): from pyramid.interfaces import IViewClassifier logger = self._registerLogger() self._registerSettings(debug_routematch=True) self._registerRouteRequest('foo') root = object() def factory(request): return root route = self._connectRoute('foo', 'archives/:action/:article', factory) route.predicates = [DummyPredicate()] context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') self._registerView(view, '', IViewClassifier, None, None) self._registerRootFactory(context) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['Hello world']) self.assertEqual(start_response.headers, ()) self.assertEqual(start_response.status, '200 OK') request = view.request self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, []) self.assertEqual(request.context, context) self.assertEqual(request.root, root) matchdict = {'action':'action1', 'article':'article1'} self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') self.assertEqual(len(logger.messages), 1) self.assertTrue( logger.messages[0].startswith( "route matched for url http://localhost:8080" "/archives/action1/article1; " "route_name: 'foo', " "path_info: ") ) self.assertTrue( "predicates: 'predicate'" in logger.messages[0] ) def test_call_route_match_miss_debug_routematch(self): from pyramid.httpexceptions import HTTPNotFound logger = self._registerLogger() self._registerSettings(debug_routematch=True) self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article') context = DummyContext() self._registerTraverserFactory(context) environ = self._makeEnviron(PATH_INFO='/wontmatch') self._registerRootFactory(context) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(HTTPNotFound, router, environ, start_response) self.assertEqual(len(logger.messages), 1) self.assertEqual( logger.messages[0], 'no route matched for url http://localhost:8080/wontmatch') def test_call_route_matches_doesnt_overwrite_subscriber_iface(self): from pyramid.interfaces import INewRequest from pyramid.interfaces import IViewClassifier from zope.interface import alsoProvides from zope.interface import Interface self._registerRouteRequest('foo') class IFoo(Interface): pass def listener(event): alsoProvides(event.request, IFoo) self.registry.registerHandler(listener, (INewRequest,)) root = object() def factory(request): return root self._connectRoute('foo', 'archives/:action/:article', factory) context = DummyContext() self._registerTraverserFactory(context) response = DummyResponse() response.app_iter = ['Hello world'] view = DummyView(response) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') self._registerView(view, '', IViewClassifier, None, None) self._registerRootFactory(context) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['Hello world']) self.assertEqual(start_response.headers, ()) self.assertEqual(start_response.status, '200 OK') request = view.request self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, []) self.assertEqual(request.context, context) self.assertEqual(request.root, root) matchdict = {'action':'action1', 'article':'article1'} self.assertEqual(request.matchdict, matchdict) self.assertEqual(request.matched_route.name, 'foo') self.assertTrue(IFoo.providedBy(request)) def test_root_factory_raises_notfound(self): from pyramid.interfaces import IRootFactory from pyramid.httpexceptions import HTTPNotFound from zope.interface import Interface from zope.interface import directlyProvides def rootfactory(request): raise HTTPNotFound('from root factory') self.registry.registerUtility(rootfactory, IRootFactory) class IContext(Interface): pass context = DummyContext() directlyProvides(context, IContext) environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPNotFound, router, environ, start_response) self.assertTrue('from root factory' in why.args[0]) def test_root_factory_raises_forbidden(self): from pyramid.interfaces import IRootFactory from pyramid.httpexceptions import HTTPForbidden from zope.interface import Interface from zope.interface import directlyProvides def rootfactory(request): raise HTTPForbidden('from root factory') self.registry.registerUtility(rootfactory, IRootFactory) class IContext(Interface): pass context = DummyContext() directlyProvides(context, IContext) environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() why = exc_raised(HTTPForbidden, router, environ, start_response) self.assertTrue('from root factory' in why.args[0]) def test_root_factory_exception_propagating(self): from pyramid.interfaces import IRootFactory from zope.interface import Interface from zope.interface import directlyProvides def rootfactory(request): raise RuntimeError() self.registry.registerUtility(rootfactory, IRootFactory) class IContext(Interface): pass context = DummyContext() directlyProvides(context, IContext) environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) def test_traverser_exception_propagating(self): environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=RuntimeError()) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) def test_call_view_exception_propagating(self): from zope.interface import Interface from zope.interface import directlyProvides class IContext(Interface): pass from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequestFactory from pyramid.interfaces import IExceptionViewClassifier def rfactory(environ): return request self.registry.registerUtility(rfactory, IRequestFactory) from pyramid.request import Request request = Request.blank('/') context = DummyContext() directlyProvides(context, IContext) self._registerTraverserFactory(context, subpath=['']) response = DummyResponse() response.app_iter = ['OK'] error = RuntimeError() view = DummyView(response, raise_exception=error) environ = self._makeEnviron() def exception_view(context, request): self.assertEqual(request.exc_info[0], RuntimeError) return response self._registerView(view, '', IViewClassifier, IRequest, IContext) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ['OK']) # exc_info and exception should still be around on the request after # the excview tween has run (see # https://github.com/Pylons/pyramid/issues/1223) self.assertEqual(request.exception, error) self.assertEqual(request.exc_info[:2], (RuntimeError, error,)) def test_call_view_raises_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest response = DummyResponse() exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=RuntimeError) def exception_view(context, request): self.assertEqual(request.exception.__class__, RuntimeError) return exception_response environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_call_view_raises_super_exception_sub_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class SuperException(Exception): pass class SubException(SuperException): pass response = DummyResponse() exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=SuperException) exception_view = DummyView(exception_response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, SubException) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(SuperException, router, environ, start_response) def test_call_view_raises_sub_exception_super_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class SuperException(Exception): pass class SubException(SuperException): pass response = DummyResponse() exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=SubException) exception_view = DummyView(exception_response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, SuperException) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_call_view_raises_exception_another_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class MyException(Exception): pass class AnotherException(Exception): pass response = DummyResponse() exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=MyException) exception_view = DummyView(exception_response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, AnotherException) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(MyException, router, environ, start_response) def test_root_factory_raises_exception_view(self): from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRequest from pyramid.interfaces import IExceptionViewClassifier def rootfactory(request): raise RuntimeError() self.registry.registerUtility(rootfactory, IRootFactory) exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] exception_view = DummyView(exception_response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() app_iter = router(environ, start_response) self.assertEqual(app_iter, ["Hello, world"]) def test_traverser_raises_exception_view(self): from pyramid.interfaces import IRequest from pyramid.interfaces import IExceptionViewClassifier environ = self._makeEnviron() context = DummyContext() self._registerTraverserFactory(context, raise_error=RuntimeError()) exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] exception_view = DummyView(exception_response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_exception_view_returns_non_iresponse(self): from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier environ = self._makeEnviron() response = DummyResponse() view = DummyView(response, raise_exception=RuntimeError) self._registerView(self.config.derive_view(view), '', IViewClassifier, IRequest, None) exception_view = DummyView(None) self._registerView(self.config.derive_view(exception_view), '', IExceptionViewClassifier, IRequest, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(ValueError, router, environ, start_response) def test_call_route_raises_route_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, req_iface, RuntimeError) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_call_view_raises_exception_route_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, IRequest, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, req_iface, RuntimeError) environ = self._makeEnviron() start_response = DummyStartResponse() router = self._makeOne() self.assertRaises(RuntimeError, router, environ, start_response) def test_call_route_raises_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_call_route_raises_super_exception_sub_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class SuperException(Exception): pass class SubException(SuperException): pass req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=SuperException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, SubException) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() self.assertRaises(SuperException, router, environ, start_response) def test_call_route_raises_sub_exception_super_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class SuperException(Exception): pass class SubException(SuperException): pass req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=SubException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, SuperException) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() result = router(environ, start_response) self.assertEqual(result, ["Hello, world"]) def test_call_route_raises_exception_another_exception_view(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest class MyException(Exception): pass class AnotherException(Exception): pass req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=MyException) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, AnotherException) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() self.assertRaises(MyException, router, environ, start_response) def test_call_route_raises_exception_view_specializing(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import IRequest req_iface = self._registerRouteRequest('foo') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, IRequest, RuntimeError) response_spec = DummyResponse() response_spec.app_iter = ["Hello, special world"] exception_view_spec = DummyView(response_spec) self._registerView(exception_view_spec, '', IExceptionViewClassifier, req_iface, RuntimeError) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() result = router(environ, start_response) self.assertEqual(result, ["Hello, special world"]) def test_call_route_raises_exception_view_another_route(self): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier req_iface = self._registerRouteRequest('foo') another_req_iface = self._registerRouteRequest('bar') self._connectRoute('foo', 'archives/:action/:article', None) view = DummyView(DummyResponse(), raise_exception=RuntimeError) self._registerView(view, '', IViewClassifier, req_iface, None) response = DummyResponse() response.app_iter = ["Hello, world"] exception_view = DummyView(response) self._registerView(exception_view, '', IExceptionViewClassifier, another_req_iface, RuntimeError) environ = self._makeEnviron(PATH_INFO='/archives/action1/article1') start_response = DummyStartResponse() router = self._makeOne() self.assertRaises(RuntimeError, router, environ, start_response) def test_call_view_raises_exception_view_route(self): from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IExceptionViewClassifier req_iface = self._registerRouteRequest('foo') response = DummyResponse() exception_response = DummyResponse() exception_response.app_iter = ["Hello, world"] view = DummyView(response, raise_exception=RuntimeError) exception_view = DummyView(exception_response) environ = self._makeEnviron() self._registerView(view, '', IViewClassifier, IRequest, None) self._registerView(exception_view, '', IExceptionViewClassifier, req_iface, RuntimeError) router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(RuntimeError, router, environ, start_response) def test_call_view_raises_predicate_mismatch(self): from pyramid.exceptions import PredicateMismatch from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequest view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) self._registerView(view, '', IViewClassifier, IRequest, None) environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(PredicateMismatch, router, environ, start_response) def test_call_view_predicate_mismatch_doesnt_hide_views(self): from pyramid.exceptions import PredicateMismatch from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequest, IResponse from pyramid.response import Response class BaseContext: pass class DummyContext(BaseContext): pass context = DummyContext() self._registerTraverserFactory(context) view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) self._registerView(view, '', IViewClassifier, IRequest, DummyContext) good_view = DummyView('abc') self._registerView(self.config.derive_view(good_view), '', IViewClassifier, IRequest, BaseContext) router = self._makeOne() def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) environ = self._makeEnviron() start_response = DummyStartResponse() app_iter = router(environ, start_response) self.assertEqual(app_iter, [b'abc']) def test_call_view_multiple_predicate_mismatches_dont_hide_views(self): from pyramid.exceptions import PredicateMismatch from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequest, IResponse from pyramid.response import Response from zope.interface import Interface, implementer class IBaseContext(Interface): pass class IContext(IBaseContext): pass @implementer(IContext) class DummyContext: pass context = DummyContext() self._registerTraverserFactory(context) view1 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) self._registerView(view1, '', IViewClassifier, IRequest, DummyContext) view2 = DummyView(DummyResponse(), raise_exception=PredicateMismatch) self._registerView(view2, '', IViewClassifier, IRequest, IContext) good_view = DummyView('abc') self._registerView(self.config.derive_view(good_view), '', IViewClassifier, IRequest, IBaseContext) router = self._makeOne() def make_response(s): return Response(s) router.registry.registerAdapter(make_response, (str,), IResponse) environ = self._makeEnviron() start_response = DummyStartResponse() app_iter = router(environ, start_response) self.assertEqual(app_iter, [b'abc']) def test_call_view_predicate_mismatch_doesnt_find_unrelated_views(self): from pyramid.exceptions import PredicateMismatch from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IRequest from zope.interface import Interface, implementer class IContext(Interface): pass class IOtherContext(Interface): pass @implementer(IContext) class DummyContext: pass context = DummyContext() self._registerTraverserFactory(context) view = DummyView(DummyResponse(), raise_exception=PredicateMismatch) self._registerView(view, '', IViewClassifier, IRequest, DummyContext) please_dont_call_me_view = DummyView('abc') self._registerView(self.config.derive_view(please_dont_call_me_view), '', IViewClassifier, IRequest, IOtherContext) router = self._makeOne() environ = self._makeEnviron() router = self._makeOne() start_response = DummyStartResponse() self.assertRaises(PredicateMismatch, router, environ, start_response) class DummyPredicate(object): def __call__(self, info, request): return True def text(self): return 'predicate' class DummyContext: pass class DummyView: def __init__(self, response, raise_exception=None): self.response = response self.raise_exception = raise_exception def __call__(self, context, request): self.context = context self.request = request if not self.raise_exception is None: raise self.raise_exception return self.response class DummyRootFactory: def __init__(self, root): self.root = root def __call__(self, environ): return self.root class DummyStartResponse: status = () headers = () def __call__(self, status, headers): self.status = status self.headers = headers from pyramid.interfaces import IResponse from zope.interface import implementer @implementer(IResponse) class DummyResponse(object): headerlist = () app_iter = () environ = None def __init__(self, status='200 OK'): self.status = status def __call__(self, environ, start_response): self.environ = environ start_response(self.status, self.headerlist) return self.app_iter class DummyThreadLocalManager: def __init__(self): self.pushed = [] self.popped = [] def push(self, val): self.pushed.append(val) def pop(self): self.popped.append(True) class DummyAuthenticationPolicy: pass class DummyLogger: def __init__(self): self.messages = [] def info(self, msg): self.messages.append(msg) warn = info debug = info def exc_raised(exc, func, *arg, **kw): try: func(*arg, **kw) except exc as e: return e else: raise AssertionError('%s not raised' % exc) # pragma: no cover pyramid-1.6/pyramid/tests/test_scaffolds/0000755000076500000240000000000012642137501021365 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/__init__.py0000644000076500000240000000001212234375161023472 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/0000755000076500000240000000000012642137501024714 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/0000755000076500000240000000000012642137501026435 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/.badfile0000644000076500000240000000000012234375161030015 0ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/__init__.py_tmpl0000644000076500000240000000102712234375161031605 0ustar michaelstaff00000000000000from pyramid.config import Configurator from {{package}}.resources import Root def main(global_config, **settings): """ This function returns a Pyramid WSGI application. """ config = Configurator(root_factory=Root, settings=settings) config.add_view('{{package}}.views.my_view', context='{{package}}:resources.Root', renderer='{{package}}:templates/mytemplate.pt') config.add_static_view('static', '{{package}}:static', cache_max_age=3600) return config.make_wsgi_app() pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/resources.py0000644000076500000240000000012412234375161031021 0ustar michaelstaff00000000000000class Root(object): def __init__(self, request): self.request = request pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/0000755000076500000240000000000012642137501027724 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/favicon.ico0000644000076500000240000000257612234375161032062 0ustar michaelstaff00000000000000h( " A>;)'%+¯ô=¿ö420520ÿÿÿ;96¸èú „„†µäô—àúGDA/-+LHF+©é\XUtØùååæììì,BO/Vi531WSP+®ñ·çø§§©OÏù--..-.QNK       #!!!!!!"   $$$$$$$$$$$$$$$$À€€Àpyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/footerbg.png0000644000076500000240000000051512234375161032245 0ustar michaelstaff00000000000000‰PNG  IHDRfÍÉÙIDAT8…“É‘Ã0í¬œZ샔%[®Ú‡Šâ58äù| X0`©‡˜Ôáÿo©„qNG@Å™Ï;mH#©¡¦8‰áÎáàKkƒÜ8FèOž-«&19çÀZ#¸ÆA¢dF\gƒ/8ñŠML—¦Gpº8ûdk躓à€éâ}œ}ºôts¾ìóœùe.¾uõ9dë=|úî÷¢­üð9àëðèè­óÉM÷êíÂþÖÓ±}ÈæÕ»æ™ëÁk>öÿ½´þÎÆÕ;«t.k~äí!;?ÀÛŸîZ¢Ôq‚–ÜYÁ:ßîw·óuËmMŽ|¬<äÈì¡ñ+3ëÝ9ðípâ-nù³V=F|˜IEND®B`‚pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/headerbg.png0000644000076500000240000000031312234375161032173 0ustar michaelstaff00000000000000‰PNG  IHDR4b·Ît’IDAT8í“AÄ C“\«—›SÏn`b‹íºà)4ÁoŸàû9DRp‘hµÈ¯«×uìô½?÷Þ²‡ ”@»÷¬\SÍ=ýÌ®Š3}½÷­¾ÚOÜÕÜåo¼±FŸ5´9,×cå©ïß»'çãmZó&kÌéä… ´q~øx²Xò5†èßE3˜,óçû÷”01]îsÖIEND®B`‚pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/ie6.css0000644000076500000240000000136612234375161031132 0ustar michaelstaff00000000000000* html img, * html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) );} #wrap{display:table;height:100%} pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/middlebg.png0000644000076500000240000000535512234375161032214 0ustar michaelstaff00000000000000‰PNG  IHDR¬º—ƹ CiCCPICC Profilex–wTSYÀï{/½ÐB‘z MJ‘z‘^E%$B °WDWiŠ"‹".¸ºY+¢XX°/È" ¬‹«ˆŠe_ôeÿØý¾³óǜߛ;sïÜ™¹ç<(¾BQ&¬@†H"óñ`ÆÄÆ1ñÝD€ÖpyÙYAáÞ?/3u’±L Ïúuÿ¸Åò a2?›þ¥ÈËKÐBй|A6å<”Ós%Y2û$ÊôÄ4ËÑQV•qò6ÿìó…ÝdÌÏñQYÎYü ¾Œ;PÞ’# Œ¢œŸ#ä¢|eýti†å7(Ó3Ül0™]"ॠl…2EÆAyJò,NœÅÁ24O8™YËÅÂä Ó˜g´vtd3}¹é‰„Âå¥qÅ|&'3#‹+ZÀ—;Ë¢€’¬¶L´ÈöÖŽöö, ´ü_å_¿zý;ÈzûÅãeèçžAŒ®o¶o±ßl™Õ°§ÐÚìøfK, eª÷¾Ùô Ÿ@óY÷aÈæ%E"Ér²´ÌÍ͵ x²‚~•ÿéðÕóŸaÖy²ó¾ÖŽé)HâJÓ%LYQy™é™R13;‹Ë0Ybtëÿ8+­Yy˜‡ ’b=* 2¡(m·ˆ/”3EL¡èŸ:üÃfå Ã/s­æ# /± 7èù½ `hd€ÄïGW ¯} $FÙË‹Öý2÷(£ëŸõß\„~ÂÙÂd¦ÌÌ ‹`ò¤â£oB¦°€ä¨- Œ Øà Ü€ðÁ ĂŀR@ƒ\° ¬ù ì{@9¨5 4€ œÀepÜ}à>#à˜¯Á Axˆ Ñ 5H2€Ì ˆ ͇¼ @( Š… dHI¡UÐF¨*†Ê¡ƒPô#t º]…z »Ð4ý ½ƒ˜ÓaMض„Ù°;GÀ‹àdx)¼΃·Ã¥p5| n†/À×á>x~O!!# Da!l„ƒ#qH"FÖ H R4 mH'r D&·††abXgŒ/&ÃÃ,ŬÁlÔcŽ`š1˜[˜!Ì$æ#–ŠÕÀša°~Øl26›-ÁÖb›°—°}ØìkÇÀáp¾¸X\*n%nn®w׃ÆMáñx5¼ÞŒçâ%ø||þþ¾?‚C ´ 6oBADØ@(!%œ%ôF 3D¢щLä—‹ˆ5Ä6â âq†¤H2"¹"H©¤õ¤RRééé%™LÖ%;’CÉBò:r)ù8ù yˆü–¢D1¥p(ñ)e;å0å<å.å%•J5¤ºQã¨êvjõ"õõMÎBÎOŽ/·V®B®Y®W›¦°©iŠi…é 3ØÌÞLh¶Ï¬Çkîh.2¯6`QXî¬V=kÈ‚ah±Á¢Åâ¹¥¾eœåNËNËVvVéV5V÷­•¬ý­7X·Yÿicjó©°¹=—:×{îÚ¹­s_ØšÙ l÷ÛÞ±£ÙÙm¶k·û`ï`/¶o°wÐwHp¨t`ÓÙ!ìmì+ŽXGÇµŽ§ß:Ù;IœN8ýáÌrNs>ê<6Ïhž`^ͼa]®ËA—ÁùÌù óÌtÕqåºV»>vÓsã»Õºº›¸§ºsîaå!öhò˜æ8qVsÎ{"ž>žžÝ^J^‘^å^¼u½“½ë½'}ì|Vúœ÷ÅúøîôðÓôãùÕùMú;ø¯öï „”<4 ¶ÁAþA»‚,0X ZÐ ‚ý‚w? 1 Yòs(.4$´"ôI˜uت°ÎpZø’ð£á¯#<"Š"îGGJ#Û£ä£â£ê¢¦£=£‹£c,cVÇ\UƶÆáã¢âjã¦z-ܳp$Þ.>?¾‘Ñ¢e‹®.V_œ¾øÌù%Ü%'° Ñ GÞsƒ¹ÕÜ©D¿ÄÊÄI‡·—÷ŒïÆßÍ¸Š£I.IÅIcÉ.É»’ÇS\SJR&„a¹ðEªojUêtZpÚá´OéÑ鄌„ŒS"%Qš¨#S+sYfO–YV~ÖàR§¥{–NŠĵÙPö¢ìV ý™ê’K7I‡ræçTä¼ÉÊ=¹Lq™hY×rÓå[—®ð^ñýJÌJÞÊöU:«Ö¯Zí¾úàhMâšöµzkóÖŽ¬óYwd=i}Úú_6Xm(Þðjcôƶ<ͼuyÛ|6ÕçËå‹ó6;o®Ú‚Ù"ÜÒ½uîÖ²­ ø× ­ K ßoãm»öõw¥ß}Úž´½»È¾hÿÜÑŽþ®;+¯(Þ´«y7swÁîW{–ì¹Zb[Rµ—´Wºw°4°´µL¿lGÙûò”ò¾ ŠÆJÊ­•Óûøûz÷»ío¨Ò¬*¬zw@xàÎAŸƒÍÕ†Õ%‡p‡r=©‰ªéüžý}]­zmaí‡Ã¢ÃƒGÂŽtÔ9ÔÕÕ8ZT×KëÇÅ»ùƒç­ ¬†ƒŒÆÂãà¸ôøÓ~ì?p¢ý$ûdÃO?U6Ñš š¡æåÍ“-)-ƒ­±­=§üOµ·9·5ýlñóáÓ:§+Î(Ÿ):K:›wöÓ¹ç¦ÎgŸ¸|a¸}Iûý‹1ow„vt_ ¸tå²÷å‹î箸\9}Õéê©kìk-×í¯7wÙu5ýb÷KS·}wó ‡­7o¶õÌë9ÛëÚ{á–ç­Ë·ýn_ï[Ð×ÓÙg ~`ðÿÎØÝô»/îåÜ›¹¿îöAÁC…‡%4Uÿjòkã ýà™!Ï¡®Çáïó†Ÿý–ýÛû‘¼'Ô'%£Ú£uc6c§Ç½Ço>]øtäYÖ³™‰ü߯|nüü§?Üþ蚌™y!~ñéÏm/Õ^~eûª}*dêÑëŒ×3ÓoÔÞyË~Ûù.úÝèLî{üûÒ&Ú>||ð)ãÓ§¿›óüìÎçŠ pHYs  šœPIDAT(cøøñã& €ÿÿÿ‡²H#SØ4½ø¹8]E„¶A¢ÍTô¶àÄ&†ÍDˆaSB¬ë±9§%Hr3NÏ $,Â&…¨½´›IEND®B`‚pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/pylons.css0000644000076500000240000001045212234375161031767 0ustar michaelstaff00000000000000html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ vertical-align:baseline;background:transparent;} body{line-height:1;} ol,ul{list-style:none;} blockquote,q{quotes:none;} blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} :focus{outline:0;} ins{text-decoration:none;} del{text-decoration:line-through;} table{border-collapse:collapse;border-spacing:0;} sub{vertical-align:sub;font-size:smaller;line-height:normal;} sup{vertical-align:super;font-size:smaller;line-height:normal;} ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} li{display:list-item;} ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} .hidden{display:none;} p{line-height:1.5em;} h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} html,body{width:100%;height:100%;} body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "NobileRegular","Lucida Grande",Lucida,Verdana,sans-serif;} a{color:#1b61d6;text-decoration:none;} a:hover{color:#e88f00;text-decoration:underline;} body h1, body h2, body h3, body h4, body h5, body h6{font-family:"NeutonRegular","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} #wrap{min-height:100%;} #header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} #header{background:#000000;top:0;font-size:14px;} #footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} .header,.footer{width:750px;margin-right:auto;margin-left:auto;} .wrapper{width:100%} #top,#top-small,#bottom{width:100%;} #top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} #top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} #bottom{color:#222;background-color:#ffffff;} .top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} .top{padding-top:40px;} .top-small{padding-top:10px;} #middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} .app-welcome{margin-top:25px;} .app-name{color:#000000;font-weight:bold;} .bottom{padding-top:50px;} #left{width:350px;float:left;padding-right:25px;} #right{width:350px;float:right;padding-left:25px;} .align-left{text-align:left;} .align-right{text-align:right;} .align-center{text-align:center;} ul.links{margin:0;padding:0;} ul.links li{list-style-type:none;font-size:14px;} form{border-style:none;} fieldset{border-style:none;} input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} input[type=text],input[type=password]{width:205px;} input[type=submit]{background-color:#ddd;font-weight:bold;} /*Opera Fix*/ body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/pyramid-small.png0000644000076500000240000001560412234375161033216 0ustar michaelstaff00000000000000‰PNG  IHDRÜ27µsKIDATxœí{˜Õ÷?çTU_æ>0ÃÜp„ŠD(ÞˆšhVØ,."»hbÜUß}³kÖ˜¼‰˜ˆ»„}ˆó¬1’Å Äý0sÙ™°íÛ„ilÛÎe·Yá8yIkkûÅÀÚS=ž!|ú‘3ÂM™2%lãLU6(ÇùDm9®p”"³œÌ(¶Efa.¥’6\Åá†äŒp0,d;#lÁ'òH:J H CÒÞÇ02Iôd]ZŸÂWì(gì€4„Ó¥À?Åèé¿ùŽ]Ü|›àù\ ,g„Êr‚*'¢N*<òxéîŽs×-—pVþFîÌA ÙCx)ï<Òy'2 :GåŸÀ ü°€¿ÆU@!tÞlÞ~ ´°3 pp!ð$ðǶ栟‡"•pïsÀ€CÀ†ösÂÈ¡ ׅ턲xiDF¡®&¨ô]ëìŽ3ãÒÑÌ›y>Öá5Ì»Zò‹W È )"y~z[‚äQá#¢Ð»Ž°Š[ü¨è£^#ð°| !¸ øw7ÿ9àR kí(÷¼B šv¬ 8þŒ–rÛ2Ø §NÇq~#|*±Ÿ¬ãQ)w¨«-áþ;'aÒI¼[ð÷×·°iW˜?n1´A'ÈàyÑÿ ýÜîG?«œyñrJ8”Êê¼H?iDâ´PÐäŸîºˆª2I¬³ åHò’n>ÎKÊ8Ö¦!ídÜ Met‘èã$`#°8­l!pšdÃÝýÕÀ®“Õé§ «€ï¢Uô:©Ÿãî6]ú :d®:êêòø¦'¿”)$†ÔI ‰”Ú¢“‘ØÚÜù7ã˜òÙâm º@YÄ¢ÔU:üãM],Ó41M Ë2±,ËMX@ ‘‚ Ë:Yk8³-"ø/à{n¾˜y2:û”¢X€¶±Þ8µCäÔ†ð—x:ãæ}RÈ'á¢QÅ5Ó«¸ùújâ](倊!”ä‹J®šàÐôyÁò5‚Ád[Bÿç 3‘Ü÷õ§ð ÃýÀKh½8Ï-@pÜÔ—Zc‘T±¼7³ ù ã$£$Unj"Ú©BK\´ûúè[ºý¨´~*j´$Ú Ä2œ[áÖ‰¢m¥Þl2ÿõô瞘À(´—±­9Ä8VUdAÎ$œ‡„ä1MÌDÞ hi¤%Q€` €”cÎ.æ¾[ë0T åDAÅ@Ùhs( *;–Ï-W+.9/€4B„BAB¡¡Pˆp(D(&‡ ‡CÉ”&‘ƒçÓt¸yÏN OëÑ^̪^Ο¼ãÖ½ßW~6Úíý.0ÈC«µ€ßS|uÇ»åÝ:ÞCKáK{éÿónß«Ð/:àhßïÝck/øÎ©E«…ï»ã[öØÞÜK?ån ©d‚Üâö¹ÁmÿOÀ:·}ÅiJºœI¸®®.By…zU—I)¤“ôåµ$Ï’üÃWë(+5ˆEõ‹U @Ù(Bèû.‰c0wÏ<ô\˜Ö0 é“jÚn“R‚ÀuܤØö K7€2ôD8èÝ´£åFwÿ:àçYΟ&Àƒ¾r‰&cMØ»ù¾ã–»À"·ØY£¿®D»Ò_Îи--§cÐ/‘(Žo½à¶µxh@K¿n·Î…À³èøØO3ôp¯³íAÌ„°{î-¾²f÷?ç¶ÿS´$>íCÂ4$á~ÁûÉ%¤&‡ô•Åm˜?³Œ có‰wÛ>w•a¢dGJÀ ¦$5e‚¯]àg¯0M‘ôˆ¦lÜr¡ çÄAõ7héZ"xx¸=碥Fú€ÂèI ðZzˆ¢'tM´Ùh/>FK2ÐoûçÑRu'šð!’®ø à_ÑRèXÚÚÝ­‰žÌÀ{€•nÙW€oºc]„&b-ðÀ¢oý_ÿÏísðŠ;?Iu9a~H’loßGKi&ôCÀh•ô´CNm8SJòóóQ(—\®Äq&š|‚X¦_Ϭ«†aÇHPàDAuC¼:÷ãÄÚô>1Šna0¹\°iT9O¯-¦¤P«–yyyäå… ‡B˜–…ÿc×h,“Ù1 db®DOÆ{Ýý?¯ùŽo~̦¢Õ¾ô7û…Àùnþy’“’vŽ7¡ß¾Œ~ã§ã?2”Å€_£¥ãOÑiÚæôóÛÂh"ÍJ«ó-´:øUà³nÙͤÆ¢_,¢íÇ™À’ cê £ÉZ5þpÔwü5´:º ¸èÛÎ rªR†AAA>Ji/¥!µÄѤ8JRQä֙ŧ 'v køa°£œã`·c·´ â¸î}´àÐmÀ_6ÀËkjùŸwBäA¡`‚‚JKK1b•” î滑Æ¡WIA«rõÀ ´ªÚ9qI—´‡¥è FOÂtÂ} ­;wÐsù‘ßV‰¡%J&²õ…U$%ÞzÎoý:ÃqÐÒùw¬o+2ÔY†–Œ…èɉâ«hõÖF‡Žf¨sMîœ,Õ:Qä”p¦iR\\Œã(M0‰K:‰eš!‰Åº˜÷ùc«Dìû (×ß ´ú®ï$†–!.Ù”Û†°?øë=ÌyìZ"6í´··ÓÜÜLãæF‚ eeeÔÔTŸ,)7½) -ÅîG;ÒñÚ›Xü5ÚéáÅŸBhÂv4fhÛǤª«½¡ý‚¨GK›*’o²>Îým–òÝè—I)Z•Íä´øØ&\_«sÒ"éØÙ‚vdûîXŠz©sJ[ÂY&%%%8¶:îf¹*^KK ï°™[¯ÙM}É¢‘ˆJmL9¨x’pB¸«T\òuÇ¡®|y;w.«G!É€mÛ477³¿¹™x|À„óOª½h±­‚BÛ\¯oõÒÆqô*”ïç‡B¡©kQTT49‰Œ(**ZÞÚÚ I 1}úôð[o½%”R˜¦¹?‹¹7,#„eYÅãñ»Ð^njĒRJÛ¶¥Âoù¯³=ý]$í®löW”¤·6Û8³aI’~HïAñn7vÈéJ+`QZZ‚m;˜¦‰aìÙ³‡õë×óç¶ñÅ©qæ^! €ö8_…a$q¤ vâ K!=Ò è´áÚ±ðw—·ð“Õ•„ƒ*ÕQC2‹FûíBVJy±"ãöÛo/xê©§„ã8äåå½ßÞÞþ÷ÍÍÍp8l{±+ íy3}Ép“ ˜Ë–-;úµ¯}­;‹îCO,9~üøÙo¿ý¶QPPptݺu…hÇJâJ}ôÑ’3f„ººº¨«««B;g2EΜ9órÇqn¡P¨«®®îƒúúú=ÕÕÕÇÚÛÛ+V¬¸!‹'NœØÌQJż¶¾ûÝï6,X°€ë®»nüÊ•+g ÉG«²öO~ò“a÷ÜsiÛ6åååE---gùöOÜ[Ì›ÛòÉ ø–u¹a˜+ ”*GÛAôà ¦% ýÀ-/Ýwß}…¿üå/ó#‘£F Ì®¬¬ôŒÂô‡îßO™LóçÏ<òÈ–ÆÆÆñ;wîœÚÔÔô?Ѧ¦¦K.¸à‚??ÞB«bÞùÂ4Ͱ·HÀ0 m¦;oŒGyäÜ—^zižã8Œ3æ£'Ÿ|rÅå—_ÞŠûzÚ¼y³õ«_ýêºX, ‡Ã^ˆÁ›üƈ#ª½ÆjkkkÐö—w\râĉE–eY¶msî¹çŽF{L=Ò*Àž3g<ð@éáÇ©ªª*ß·oßL·N7ÐýôÓO}å+_1lÛ¦¸¸¸àرcUî±ØÅ_l¾ûî»Þµy÷!NK%ä˜p¡`ˆa¥ÃXµj+_y…ÖcdžI~ždñ½6UÝЭ0Œ6Ž·Ei9;÷±«¥‚ãñQXã(¯¾€Q]€yI€[7òûèÞûGÂ]pV~+£J5 ó `€aÀÿr7óžj ù¸‰e$%›”Û¶©®ªªFÇÃ|2²TÚ˲‚ÂñBÏ18ƒ9kÖ¬w.\8þøñãÅK—.]__ôСC#¤”ö¼yóÞuÛõ·-ðM,¥Å†M†É¶lÙ²KÇ!G–.]úÜ´iÓ"$ #Ž;–ˆþ;Žã¸Ç„r'!ylý¹~”Ti$mÛöëåÞêïå#Ca†!B¡P½J$ÑFCCC¡GÚººº³Ð×(`¯X±Âœ0aB<‰PPP0¹±±qFMMM­¢v¢UÚ. »¾¾~øÖ­[½0ÌiÏ)á"‘O>ù$o®[ç®›4±Å?ÞjpNíqÞú}ˆÍE4·Uc„ÏeDÍxªÎ=ŸË¯:›êš* óSÕþ‹'}ø[:£°¿ùû÷î¢i÷G¼½kÑ)ÛY¸›1åðÀuøÖ‹µØŽp=¤Ú;ª”ðTR…ž$ý ˜ Û¶mw¢cÛ¶·ìi oWçî»ïþð‰'žh>räHåo~ó›q›6m:¨”ÕÕÕ»n¿ýömô\=!DZ=õ, áľ}ûÌŒ6lXË´iÓ§µc´´´Xñx<‰G ¹m½:@@‚éäWJ©LäOyùH)½c¸õ£¾±Û¶ƒi×ãõkÔÕÕ‰ššš=åuË—/¿æ›ßüfIò'^cÆŒ¹rëÖ­ùeeeE<MÌn·Ž§æ:îõæ 9#\Ie%ï½ÿñxÓÔÝÚŽƒ)b<óëc¬Þ0‰Ë®šÅ¤K§ñűc>¬ ßm‡pÎÈ ÎYS?ÜJ¤#ÆÖ­ÛøÓ{ï²|í*>n\K@Dé6ò0¤V-…”H¥PN<}` ”R‰ Õ›„骫«™4iÒV­Zõ¥ÆÆÆqÛ·oï˜:uêz˲¼ñeퟤtM!\ ˆI)€¶¶¶Â––9bÄïÅ gÑ¢E—z„s_)dqI¦Ü>3Îñ% )QJ ¡p'ç¶íÒ»f¯Ž1sæÌ7/rG.^¼xæœ9sÖÖÖúíDV¬X1bíÚµó¼>ƒÁ`:Fè‘-%¹¶j‚€ 2 sF¸cÍÇœ²ò"ýC¬Ž“X¼W»óÙ¾¿‰ ü˜Â¢§(/+㬳΢¡¡††FMMM eeeX–•Ò®mÛ=z„ææìرƒmÛ¶±uë6öíû˜Ã‡ÐÖÞN4'f—0%–¡5Fá~­ ¤ÂQŽ7ÉNáuûí·¿³f͚Ϸµµ•ƒÁ¶¯ýëëIÚBéý'ÊôïÙYY™ª««k:|øpukkkùœ9s¾´lÙ²ÿ.//·×¯__ðï|çêµk×^#„PJ)F ’݃\÷¤]õ6‹´MW¯ÝωS…[&üª+É ¾÷\â .üà /¼°¾©©iòþýûÏ4iÒ?Í;÷…/|á ÛºººÔsÏ=7îå—_¾9‹ ·E"‘Ññx\’$˜§*§ýâñ4¡”b°H7(„Súï„Ð+ Š€â+VŒ¹çÞÿkÚ¶üM€’pPûö£±(‡¦å@ lü3+W¾Œ”’P(Dqq1UU•ÔÖÖRWW‡eYìÚ½›÷îåÀ´¶§;ÚR`¦e%<¡†!±,3é™ô–’I‰£Ç„sǶmÛ°m[2p•€Ù³güàƒnܼyód€úúú÷/»ì²#dVsE<·Çé«çÁ|iÞ¼yãÛÛÛ‡½ñÆ_<ï¼ó&‡Ãá¶£GVÄb±ð¤I“VïÙ³ç쌎D"y¤®»»;q_âñ8î1ÿ˜„mÛŽ7–X,&HµqÇ(¼ûå!eâ»m˜nÞõxu ž}öÙŸÍš5+oß¾}çµ´´4,Y²ä[?þx»ã8†mÛ!!Dlîܹß[·nÝ‘Hdt4õIŽo›ž÷ۜ괖pJ© z`¡›ŠÜF{÷ À>|øp@H©?PH,,Ö;©.{76—táC{G[š¶Ò¸y Ê•Ò00Mýí›aš¾u’Þâe\{M¦Í#œr{`„ …BÝ•••[#‘HiUUÕ>!᫦¦fÏæÍ›'K)ã·ÜrËëøÔ¥ôþóòòºªªª¶tvvTWWïÎÖÿÌ™3÷?öØc>üðÃ7îÝ»÷3‘H¤¬½½½xøðữ¾úêÕÏ>ûìÚ+®¸b¶ã8”””"Õ¶r†1bD ÊËË»ÇS$\QQQguuõ–ŽŽŽ‚ÊÊÊýn”±H)©ªªÚnšfgUU•÷)M¢üüüÎÊÊÊ-]]]ù•••“êåT€š|ôèÑ‘#GŽÜ°k×®GûÓ ô/õÚk¯ kjj*ª¨¨è¸æškƒv>Šh4*ÂápÒzýd:–^Ç0 2Ž¥:*JÛ¶q¯ÇëK‘Û·o7V­Z5¼µµÕ;vì¡n¸á š@ÑX,fwtt8¦iÆòóóÝ·ÄHÆýêeBuM øŸtd•p®Zh‘T Ð’,ä–ëoc4¡l´Qêíû€¨­­mµLëx4Ë—=ˆÖ“@é_ϳ¨õP„Éã25ï'\*Ù¼2½oÑh„â’â ýœè‚‰¬0nºé¦/=zt¤+Ý^ìϘN´ÿk¯½¶åÚk¯=HÚä5 ‚x?Ÿ ŽÊp<= ã'œ¨sÎ9ǹãŽ;"$½Ëžªk[–/..N'U6©Ö#ä3XHN)å-T  UAO%´H®ðts…[!± *‘2®¡¡Á.--iÚßÜ\•bOùåÿb@¥ÌGL™~®¿<-Ÿ$o²=Ó09~¼•˦Oû€Ìމ\@òú믿výúõÐÐа桇úà'×È6É{#\‚tô´ËÒ¥V62§ª¤Ó%šG*oëý>‡§§§_¤G8oëOôüxyãÊ+¯|mÙ²§§ç…ót°8Eí“Y÷¥ï«Dfòô8?퓟Ôvô:ή®Nº»ºwÝ{ï½ïqŠÖÞ½øâ‹åßþö·oÚ´iÓu€(,,ÜõøãÿœT;êLB5“ž¤ó“ÆŸÒ‰–t§DºARjy¤ð_eûŽ¥í MJ?áü«4R¤››â?þñ÷ÒÊ•oµ·µMËÏ×q¶LÒÍS“v˜ÌRž‰¬ú‡aµÓD¦J7Oõt¸hçÎL™2uY]]]¹—&béÒ¥UwÝu×â®®®r€¼¼¼= ,øþ•W^yøŒçtA&ÂyÛLR.pžzÙ›'ÒOºœBø$œ§z‹j yʤJú =É'Ÿyæ™ò{î¹gq8™Ÿ_€B%m-™Ý.“=ˆ—J ÏÉ"S—FL©LISÓ€6mÚ´„S#IÄ®]»¬qãÆý¼³³³fäÈ‘¯/Z´èg³gÏ>“É'N¸tâe’nqúV7=$I™$ÉÖŸ”N¶L¶›Ÿtþ­\¸páÈY´è!Ë4JJK°L+%4 ÓHÒ“\ÉV{ÔK'«ŸhRÒÞÖFSS–e=¿aÆ%………§äMçÝ“ùóçO.--íX²d‰÷·ÇÏ45²¯ëM'ôT ³IºtÂõ©fæ‚p‚äO°ù¥œ‘a¿¿Ž’Þ _}õÕ‚o|ãó[ZZf†B¡a÷ÃÔžÒ)Tž=—‰l’¤C¯ÂhkoçСCD"‘M&LøÅ믿¾zîé‰Â»/gÑ<ôvÝ*-ß›¥/ÒeS7’r°C$œ'á¼”‰\þ2ßçžþ9è•h™>WK–,±lÙ²©üŒmÛ%êáËûuA¢ghÑqœx Ø___¿áG?úÑúóÏ?ßûíÂ3u’ÿoA:á¼mÔËÞV—du¢äJÂyΓtÉ•M}Ìd³ù½’™H–-ÈžéÛ±lèÍHÿmˆhÿ{)<àm{#œ—O'WºÝ–SÂyq¸¾.‚´ã‰•æô­J¦çéGùÎ<ôóçûCºlªf¦ör?ἕÜÞ@=ˆ u¼ûk³ nýÁÉ ]ºÔÊ{Ëiü̓iWú<Òù ç»B´!r áD‘-DàÏŸñ2.§È´–Ò?tÕ1lô²MÏÓò! úç¹ì¯3[꫟AA& çå='ˆ_ÒõF¶L$êXC¤B6œH¨ SY ØŸþN*?ççüpNgŸ³}‡98°M8L2QH("i…ÊÚ4©»~|»gzf'íjvV»ª·^­éé®®ªîéíþÔ·¾õ-…eÄsÍ5×Ä»ºô´X“·H}®vÔ03ŒaÐ8Üõ;Õ1¥Q ”:f0¯*Ôv0ëL&ù|&“ÙýðÃî:Z,‹Åb±¨á®€ep\sÍ5ñLÆ™éjo¥6ꔺ˜¦E)eדÀƒR*‰1{ æicxÈdÜ?x^r‹ñ‹Åb±X† +ðFW]uÕ—èÕà]‹Qg+­æ*¥0Æ`Œîê*”R(¥0€qÝÃF™Ê¨ß×üìw¿»oãp×Ïb±X,Ëé…î#„s®»®i\Æ»ÁxÜœ¥§Àó¼a®Ùéˆxç¹cÌK(õË„›üÖc¿ýmçp×Íb±X,Ëéî§8«W¯ŽhÝx)Žú¸Vêu(ñY·Öõá!ðB2Æx 6»˜¯¶6œøþÝw?Ö5ÌU³X,‹Å2Êq†»–Ò¬^½z‚ŽÄ?­õJ©å@t¸ëdÉ¢”R ®N¥âsæÍ]°ãå—·íîJY,‹Åb½X‹û)ÊåW]{™ç¹Ÿ×J_ ÖÂ~*£”Æo†/Ä£|ã¾ûîKw,‹Åb±Œ>¬ÅýdõåW¾¯i­—yžgEû)Ž1­u›1fUÊõÚçÌšñøÎ;SÃ]/‹Åb±X,£ +ÜO-Ôe—_y‡VúËÚÑSìÀÓ‘ƒB2¦Ñ磜ö)“ç?ùÊ+Ûû†»^‹Åb±XFV¸ŸB\ºúÊÛ´V_QJµ+ÚGÆF)¥Î‹D¼‰³gÍ|dçΉᮖÅb±X,–Ñî§«/¿ò=Zó”cŒí#ƒAiçlƒš6cÚ’‡;;·Zñn±X,‹å¤±Âý`Õå—¿ÞxêkJéñÖŸ}´`PJŸ‰“Ò»vìøÝpׯb±X,ËÈÇ ÷aæÂÕWž©1wi­çZÑ>ºP œ5{Îìõ»vìØ2Üõ±X,‹Å2²±Â}¹æškân&óeí8WXÑ>:QJÇ ,;{ÖC;wî<<Üõ±X,‹Å2r±Â}™2}æÛµŠ|Òã` ì2Êc ZéIž¡cL[ë}Hc±X,‹Å2¬p&.½ôÊÀÿUZMµÖöÑü¾j~C,þRgç·»>‹Åb±XF&V¸jÆœ9wjÍ[l™Ó¥TÔY3§ÿº³³³w¸ëc±X,‹eäa…û0pÁ«kG}k­í§:Pú@ç®k‡».‹Åb±XFz¸+p:â8úv0sŒgýÚO—Åx­u#F½ó /œˆÅb±X,ˉ wN7Î;ïÒ9F¹×+ ÖÚ~:áyƘ3ŽÜ3Üõ±X,‹Å2²°÷:ãÄôÐs‡Ûl—ú/¾Õ=¦áº .¸ ‹Åb±X,–`-îuäœsΉ‚¹\iÝàyvPêéˆg<<¸(v¦/w},‹Åb±Œ¬p¯#±ÖÖY&mÎ6žÁJ=M1 `q$9+Ü-‹Åb± ë*SO2™ù`¦ZßöÑRªä‚Œw.òÍb±X,‹¥*¬Å½~hãéåJ«˜±n2#¥ªÐÚUõ¦˜×N9çœÆ}O?mcº[,‹Åb© +ÜëÄ9çœÓf±RϺɜҔç5ùåŒÁÀ¢¹ öAg-²´X,‹Å2ú±Â½N8ŽÓh0óŒ„îêŒZª²ˆW`0¿Ï@ÊõD¸Mg>V¸[,‹¥z•Ý,ƒxf–QˆîuÂÇc‘ ãÍiìÕìyiâÑÚ ­¨…P¯w™J)0F5ª’Åb±XF?qàÓÀ" Sd¿ú€ï¿`Þ¯ó¸þ§D€µÀ[€v`2I˜ Ü4Ow°\˱½N脊ÇmÃèÓÒâîy†hD3kj;^éAk(¥"Œ{-OF|ŸìïgŒÁ£=ÏL:©Œ,‹År:Öç•IÓˆö ÷‹€w#Öüqˆ`?‚ˆó€ÃÀç€Và àI`°øß~ßÁ ÷!ÇF•©™hFL#Æ ûD@ñôö¦¸ìâ¥üí'°pZо¤óùûUè_­ËVEþ ×u•­•blÿ;Åb±X,–¢ Q!MH "ïnÞ ¼„ö?ó·ýÐÊw)0iHœÊ#9ˆr-ÄZÜëD4ÑFeN·PJ)zúR,œ7‘¿ï ¦6üžO¿µ—5Fw/4DG¼ê¯ËÀ­åµ½æƒ±Öçc£”ñâ5«Ô©ƒÆ„–fÿ³Õ_oð—ÒíÆCü½ÀqàUàpØOñ®a‹ÅbLC\;3ºÇUzUã_Œ]þŽ"ÏãuÀ3þþK{ÛnÄ"¾¿~>2'ÉäA–k V¸×cÔh’íÕØD2Ø–>üîULÔBú•$ËôqûU­|õšT"N}ÜcŠQùÂ2|W £âv˜\̦ûßÇcýÏ&äeñ?µ¿”z¹xþâ"Ö>D´ï6 /’uˆ˜ŒUÉb±Œ¦ â5À,ä9sqù69Ñi©ž8ò¬Vô7®8À&äº_ <,~…üV¸×+ÜëÈHWiýh…ôžgȸ.·ß²’+VÍ'ÓÛƒç¼T”[¯H²}_#¿|TÑÚ8tu†…w¬FÈô˜pzE¾÷Ì(àMH7jóäÝŒ4¦+€‹ü^à!àgÈ‹ùÕ!(Ûb±œÚLA|ª¯,Ø>X\¼ Ti© bHy¸xòZ[ ¼~ëuZa}ÜëÉ)àk^/¿p¥}‰4¯¿d!7ßxn2çfC&1;nH°t–!xÅL)^ç¢õ¦túRªºTÞÅòLÉÅ–¼óÄü¥^Äëþ»+Ï¿!7;Ð×b9}ˆwÒ_´‡™ü-"ä-µÁAzAŸ&ïvÛ±Ööºa…{ÝH`ð0fx–*áÁ¥ ½})Î:c Ÿúèe´D!“L ÞH&`zGš?gŠŽvH¥ â9RMþô_ ?]±4¡´yé•Ô¹ßB°0 z•B)åï—ë‹7*\¶‡sà8Ò=ûMà׈EÞb±Œ~Î>TEºËç‚•µA#Ñfžzñ¾qOr†±^§V¸2Dö_{ì`èKdhiˆòÇﻘÉ›I§’(<0AhXJ“LDX>?Í7¸¸.d2¾¨.q…u*›.ï¼*,’ªÌR¹NZëŠi¤î³Œ¥†Dù¿´ ou,Ës)2Ƚ X‰u ¹^1òÅxÓ} ° 2öÈøûFcÀ…S{3ב¡ˆ(Óßï¼ú2NæØRxžÁó<Þú¦³8ï¬idzXîÏ€Ñ~yIi®½(Åã¹ï)C¤Qöôk2iDÖ7—$_‡S©ý¼07É£xcÅøÿ—lÌTÓÆñ{‚ÿ­|¯9mHø²iÀg_L‹Å2ú˜<€´“A™¢ºŒ6’À/w˜½¡í¯_žÿ,þ ñÿ<[ךž¦Xá^O©ÔÊZ¾KäY•µ|€õ©”§RоdŠË/šÇm´/Õ‡çy!˜àx%"±²Çã†;Þ˜b×[_æF¨8¦H]_w¥Šïƒ*¹¿|™¥÷—>°_(È~ô£ƒLæqèBb§—ip%ÄŠÖŠ„w›ÈÀ{p»Ÿ×Hw®Åb]ô m6”ì@è¾XdûVà#¡ï_-Øÿ§œnoµa ÷:‘œ"j´ª(&¡ãªu_HÈÄjy%´Vôö¦9cÁD>ó‘ פI%RAe±îúî2ûûV$ó¦»üÙÛ2|ükQNôb‘œx/W~©}•Ï©øõ)%èË)úbe•®—|c†Õ9¼Îüyf/ò &òH“ÿBÕH÷lb=Ÿœt‹Ÿ‡ˆùjPÈ ©ÈL6l¤Å2ºH¤˜g±Õ+Úë„îu¤˜»EwJ¡OÂÆä—WÓßU$X/Öû›+¥H&3Œçãï?—É“šHu'AûÖåÀÊL•jÊ„\ç”G²OqöB¾þþÇÏí¦ýz;„ZB$|7áíácŒÉ}/8çR×£’0?ÃÂîjðŒw: —Úü÷Ò ÷Ò=» ívÕGù2ˆê‡(Ûb±œúüx ¸¨BºÀ½XQiEØÁ©õ¤H0’“ ¹X.Ÿbyi¥Kî×JݯPÙ}á%||&í’q=>𶜿b ™Þ¤¸Çxžø·>ãùϼ¡£"â=M&i¸ñ’7^ ½ q§ —_x~ŽÒhÊH“· 0Uáº䥵ӯ Gk­ÑJe— 7­šÐä-¹¡pP8h%Ÿ*(³ðÚ>&÷“1àbaûðFànªëúŽŸB&f±X,£‡ƒÀ_P~‡n$äsu©‘ÅR'¬p&*Em f׬tü@"­”KS*oŒÉ-Åê $’.—ž;ƒ¯šI¥ð<L ÿè/›Œ ¬ðâæn”ñH{Š˜V¼÷ê ¦+™ü(1ýb½” SM„—ÜR,ÒLÁ¿øVJçÊ G“ñ×µ–%»ˆ¿eÐàIà½Àÿ¡ºÁfKK½Åb]<|qÅ;‚¸Ã$£ÀSHœ÷ïr:™H,§ÖU¦^$À89÷‹b®aÂn(åŒÿwv¿$ª~ß—H³lñD>sÇy4Ç • ´TN çLû®o€Ñ„Ý̕ր"•2Lé0Üy³Ç_~'ÂñC,šßn´ämÔºð<2ø´ûUä×/ûl)EvlRJgËö<‰°cŒ¡„›½¥2Kº |œòÏ1î¿B–Y,–ÑÃOwºyHÏš:]À¡a¬—Å2dXá^WLö3'ªË„¤|”“ʾÝʰè Êêó.Ü–L¹Œimàß³œéÓ›Hu§D˜~íá:d¨Ê&±Z‡2 ŽQN:\p¦Ë{Ö8|é?žHÞÔÙQ«ë\í¹ôObòEy òV(PA#!eÇ2\$fû<àÍÒ.®Ã w‹e4r ]øôpWÄb©ÖU¦Žä{äœ> ·«"ûú;Š”ö‡Ïù™ûSéþÇÙ–u)²=ŒÄk‡÷Ü|ç/ŸHª' x~eƒŠø“.eÝf‚™XµÔËàûËhäVÔ`4Æ7¥¸ñ¢4×®„¾¤øŽcü¥œÜ1h±Â‡|] ýñå¼Âî1¾‡zÖ¯]ƒ’|%oGòÒ¹¼ƒ‰–ÂKá€B—Yü<ý¼‚ßÔößž4ÝÀ(ïç bu_Mu¶X,‹ÅrÊb-îu$ì“s…éŸN…þï·ï¤¬Ì…å”4·—È+U%™ryÛ ¹åºyd Œçf9*M†`=ˆ#ªÕ®B1rüI™|ÿ2ˆD ¹Áãð±k7šÊœÅël ¾÷?§ð)«Ð9—¬?ð—ÉsÛQá¯è¢å„\¤‚ØBÆP*$¥e@üø7Äu¦KéÀ¶!¯‘Åb±X,C„îu#A0{°…ó‚æa(çÂRé»êç…HGUê 7ŽPB¥(¬b v»{3,š;–w¼e±ˆ!ðÐYŸö°XŸQÐuÍŒ\‡)¨“lO»0¾ÍðÁë]¶îuèê…x,—.ïS0& ÿ#”6×,!ÚÚOt wò¾ãl ûßk•/äà ­<<¿C^ Àr2ü'ðAd*îRt3±ÂÝb±X,#+܇œu·: zU~Ô…_Šì¯lu®à÷­©´Ç¸1 ÜñŽ%tŒ“î ÏkQ*ŽúW™¬…BŠØÏE!iP$“pÆxç•ÿï×"’#¡»¶”¥½ÔIî/tv ê×#Q&¯jz8Ä•¦_2Ëɳx¸¬LšÄþÁºÔÈb±X,–!À ÷:ˆZcrQPªî%¾©B³zx›*bËWR4äÆÑ/¿Âº¨À_ñ®7/àÒ×ú~íO{6«‚A©aK|Ð@ØÚNÖZžmÌ„º<™¤âÆ‹]vîpÏZˆFB5ÍZØs¶óP»¨È9…NÈÿPy×4\­°;S~š~ü‚4á²åtZñîKm8Š <-'ÜA\edÀ…Åb±X,#+ÜëDˆdµ¬ hŠ˜ÊýµBx“b2?ýõhQ”Î;¯ΛÂF„¡;áò–+gqËu3É$Ò¾?{vDlá®(ˆÿîùb8‚2:\P¿:øUE¡H»šxÔðÁë ûFxv47–䯄s,s] …{‘ߣŸp/Ù3Q覓ÿ]+…1^nÀ±µ¾×Š$þ­íXá^kÚ€9H(¾qH‹¼™õöÅ–Z)Àä·l%7à8 Gâyö#wZ€ÀT`¼ÿ]=ȹîvSݼe âb6ù}›ýí=È5î^RCPöh@#×mòû‘er¿îö’›1Úb)‹îuED¬vræðBw‹@ær”ý¡õ57œO^~…ŽÞÁÖâ:9›°_£@)ziÎ^2ŽÛß2›ˆ2¤\7dåœÀC"ݘ|!o‚m"ÜÊÅwT&Ü=*׈…Z)He ½Þ·þúǺ!í=ŠŸ²Ê¥)lÏ”s)Õ{Q˜¦Ê<ƒßW: ÂMZN–jDZ#å›K³€³(/€¢ˆP©Wè¹82­{ ¥g‹""jÕ+z "¶ ó ºÂ6/WÈc)p5°˜ø?7ÿ} ¾‘¤±¸88˜ŒUhD®KŒ`àœw0O°™„çidҮ݃¬Ç`˜ã×7°l„‰;*äÑ œ \ \èçÙŒœ{ðäK½HƒåYÄ ìNþ\Û•H$¦ó‘FC3òû+ûàà7Ôw ÉL`9ů3ȵ~©_53-× …üM¬.@ê8ùŽ“Ó]idð[Ð[ <<y‹¥(V¸×‰8É›0Hû‘Eòr1#4ôwù(eÏϯ=Љÿb;Ãù¤S†ö–8yÛ|&uÄIõ¦é7*3‹ µ&Â0Uv]a Ux»’%¢]‘?@V‘JÁÒ9†w__þ…Âõ  &³R¥Ï-lU/ÚK¡ wCjlå‰õÂtyç婵Î^3ãyYWKM¨Å‹yð¯ˆ-dzÀõÔÇBödöÇñeÒ(àKH„j¸øP™ýÿf³˜èž¼ ¸ NÅÞ!ÍÈ5Ì > X\ƒˆö¹ˆXuÊäÓZ_ˆ§$°¸ø6°uu(W_¦ôùÿòkd5—FÄs åÃ6·#–Ü%À[€—€ïßB⛄&à*¿ìóÈYö«)ûMÈX“Ÿw! §¡f rËñ ä|Ž}uˆ# ¶w ×qÒ¸¬†yˆÈÿ°¸ø&Ò£a±äa…û0 xà¹ï¡=%Ü. ˜5¸ÿäMù‘RBéµÊêcÝ23®°0/Ï3(m¸yÍL–-Cº/«]!ŸFl#”ÎOƒBár1äÃç›s2¾ˆ[°‘0‘¯;žÝá¡õ2¸T²*g9_ÎP'Ô¦(7¨U[è^ÓßÝ&´O4äŽw¬X"ªy–õQ>”Ï3ˆø½¶B>ËeÀÃUÕlð(Äâ9½Bº£ˆµ³ZëvÃ¥h*±}%ðUàµU”1PKû"亿±æW#Ô+¡+ñ2y#ðÏÀZ!ׇܥD[«¿¿Ð½dðYàfD”ro.Gziþ‚êÝ•"!UßNΪ>¢~Ÿ^ürO%šò÷1 î\ÃàcHƒ¶cy÷kð^4ÈônX,€îu#™í;¹+òq§¼•\)•5l‹ø.ÜALøÜvSDˆëP>Z+L­H¥\®»l*o½~:™dσ¼€‡…âÜ„…z˜@Ü#JÛøe«®r'ï[ã¥- rãYQdÒâ"óþkáh¯ÃK†Æ¸œ[~ýK\ÓbÖõ¢»_C©H>ÉKÿu•©1íU¤é¡¼+I/ðÄbZî…ߌÌÄúbÑ*Z€7T‘îà¹äÛëRâ8†ˆ¢°ø~b‰Ÿ2€rªa1ð~àFĽ`(Y | ¹¦ŸEzN†‚.D”—î þ¾°p¿½ójT‡ñ÷ Äm©ïEDûü•}!byÿà+HCf(¨fxÿP‡ˆ#O ÷W-9 é%Zü9°½Æù[F(V¸×‹8]«PhÇw›(ŒôB¡°TäŒó*d”»n@RQ6…ÖKä£Ã¯Á±b´·ÏãŒym¼ëMÓˆG©„ñ³ ? MpR²^èçžÅC&\R`™4ä“·.£p³>î’Wîd“?Þõzøûÿp8Þ±Hp¯ ¬ûù¦—ì‘u óH^Y÷¨à\mT™ZCºì+qÊ–àGwƒeÒ]ƒXŸwUQî`Y\\!þ› Ä주p“ßLý#D„M@Õòzà£Ô÷]t5âztCã:ÓGy×­Frç«€·".ck\¹ˆpNÿUd¿|ø;*»‡ ”vÄâþšÑù´kBÎíä»iÕ’(p+0qÁ98DåXFå|ç,5ÆÃ`ð²®ÙTC‹ÒÁ¢ý%¼M¬åÚ_—ƒ*û©•Ê?ÆÑÙ|t(_í„Öµ–cu.G+Ò76ÆûnžÍ„ñqR‰‚÷P Ô!_ßTî7P”?ƒªò…¸XÓ5íïÑ~ÃÁ -‘кïB£‚t¥’ÉKf)n]¥¥¤´ãŸÖ8y‹“[YtDg×Ç?6X‚<'o½09® ¯Hñ¼Âùˆn7T7ŽÐRc¨lír‘]I@ì~^E™Õˆê“e ¥ÝV:Ëÿ@„Q‚ò7_œÜ»aC'Úgðbädš¿ç"¢¶šß@ F¥ûìßÀЈö€YÀß#">Œ|ø"µí ˆ%ú†!Ê8|ø8C'ÚÃ\ŽüŽÇFð9í±÷a@œ îY =þݹM…Vä"2ûùcæÐz¸Œ"Vdc@+w¼a+–´és}—–{GÞë2ô%ÏâÒA9JçüÜ Î7üÝøV÷l}³ >5é”áuËa×a‡ûŸ†˜“;ŸR1é gV ׯŸ›‹É?6›¯êß;dh0¾Êž‡¢>–š2ÈXŽ.ª³Ž{ˆû㔟‰5‚XÝÊЄákD¬Ñ•XËÀ#y¤(/ÜüýmÀ'ñ7T¼€D~¹®Lš^DÜF¢mD~Ï>䯘ŒˆÓiTßÈX|øSj+†ª½¾K16±B~.âæÒè{{)r?‚œk×­ˆz%ñ 9ÿêpD›jiA¬Ò/!ƒ.GÍÀß ƒ^jüìFƤô Ïr]ÇRþyò›mÂZ|N{¬p¯É$FGDü*…ÖN^T™¢~îaÑWÌwºˆKFéÈ(ýdVp äìKz\qa×®êÀM{¾{GÈ&åZÉÖ¥´ˆwå¢ *šuÏ),_ˆ*óÖKᕃŠ-{ñþçž=ß‚khB' ³îBábJ¸Ïôû-rAÙƒÁÁ…3²¢š\Æ<«ãkÞLåaTéâyàQÊ‹Ia½X_e¾á"$¢L9º€Ÿ0ð •„eâ~ô6ÄMf0T+ðÒH”•kÉoN'küoÿýÀ!Dð$é_ÿ/{6b|Ò+R‰÷#á"Xe}«!IåÆÜdÄÒ¾ Äþ>äüÄç°¿},¹s\M.Ž}%Þ ýO¤·á‹”Ò ¼~ ñ«¢'1Ý/E VšË1ïfäÏ¡þ7âÞU-.r=ïAB­îAîã¢Áš‘hJ¯E&‘{ÅTihÖ3´¥åÄ ÷zâ¿jÄ墿_¹¿Á—¥aËp¾P,&γ‚1ä…ž‹oÄÒݯ(ÕOàö%]Îjåí×O!¢錗«C1¡®ÈùŸ‡lnx¢©@¤‡Ý`ŸÛC‘räú¨lViWÑÒ7¯R|í¿4=IñwÏ»fAzÿøÂk×½°N½e"Ø„¿ço–HB&¤k'`ª3‘(•ØHõ.IÄêþ:Ê»ªL®`h„ûeTvaØ <4ˆ¼3”ov;ˆÕúC”à‘(<ë‘ØáI?ßvÄ2^­ËH¼í~žÿ…éøÖÕBÜœö#½w#ÂøŠ ÇÅñþÒ0¨Ê ÔàN$ìe!i°ü‡¾Ô=û]$|à'!]‰Fdêï?A„b! þïÆëá"i‚²—ŒD>©&Л–T‘öTæíÈüÕ`ëý‘ó.w/oòÓ| Åù$¤d¡E¿Ëiîu#Ž Þ—Êà8N¿8îiSn›1~˜ðþÐ1yQdò ç-žç¾ß¹¿MkE*mho‹óî7MaRGŒt_&Ôp𠕨?øT“×Þï5]ä½m4Ç·¼‡-íás ¹È9„\WŒRþÌ« ”G2©X:Þ|‘æ‡ÿ#þÿŽ"/Nzî܃ÿó¯eéž r=-ðá(?¹|ƒ1ºNÄÁó{ <ãáXå~²hà*GÃèCÉ@¬}Dâ2—ãFàëä\jÁÄšZ©þn¤Û} ³X‡q ø%öïB¢ïü 7XÌ‚tEUÃ.¤2ñH&ؾ IDATþ'ç~dÆÄíˆeùfÊ7“ÏAÜž¾we†IQ>ÚÐ<äž-ü}{€BBVVjdD~ÿ'——RÙp1Ò˜)Ö“t‰Ý•ï©£ˆùH#àÏ©¯¼i =ÆÈµ/@¢ïT#ž»þ…Ò  bBæx ¹¶ÌаŒP¬p¯I0"Dµ?ÀÑ/_<‚„| X˜Ã‰| Ý;²~Ù*°i üh‹9K}n¿á-Wv°|q3©„KÎ $0‡ý×ýÊ„© Y÷p÷àðª’ö@øT©u9$å*.=vrx|“GC,×ó­cž•»xÏEá¾|W¦KL1×¥ÜáE÷9ÚÁhWœŒÁúÊœ47ï©"ÝFÄ‚>¶"⤒p_ \2ˆü˱‚Ê‘C5.3ÌB)Öpø9"HÖVÈc † 1ðÿ­{ׂ¹ˆ³-ˆEøÇÔf¼B¥A³Å,ÔG‘(,ÿRáØB^þ çq}…´íˆ Q!‡ëô­” 9q:i8Tjh^‚4’*Ý;§"1ÄÒ^MÈÌcÀ_"Q§;xú826`'ÒøªÁá–ˆîu$ø v´Æ‰8㔥ü²+…< §)6@³XxCô$ ׯjçÚKÇâe¼")Mþy+aaø°÷·)ß¿]7€v0ALJcÀ¸~Z1Š*W¢®dõrè]è…ªúÖpOi"ŽæÍçkŽðâ+Š–¥ŽÁ‰HD9Ôàe; DDëý(u .}y×™Báî_S椹±LŽ«.ƒXR3 ü‘P}åʇXÝk)¢×PÙgÿ!ªŸ\g  …i€ÿ‡X»k\žap¿O5ìB¬ø?¥¼ÛÓ Ä¾©Få¤w§±®þû Ë:Šø\Ϧò¸ˆbei´ †$Òp˜ƒôZ”c*2.d$ ÷+°•èþð•û]dpðW)îÞd9 ±Â½Ž?ÊŠˆÉ2ƒS ¬áEÅvÁ¾âô|«2…ÇøŸÉ¤aÁÌ8o¸|,G‘._!$ÒûÙŠ øÀbX»=ŒIC¦“8‚!&n¼„¬{iÀÅÆx.ö­Óžˆþ@ê*oþœF)’hÚšàª9Üý›&N$4-M'B,%LjÇâòo ÓFú]» êÉ6ò¯}±k¾Î…ÛGãyâ cŒ±Â}phD(ÿ-•géÎìÀÃ? þוfR½%{YN˜ Èl©åºc\ÄMb°ÛT YŒ!VÙZ‹öz ò¼ºLšéÈÌ¥µî.‹RóÄõèdØ…4dÿ•E:ù ð³“,ûby¿é½(…ƒÜÛ È=8RhF& «4æÄ ûû5.ÿçÈýùåçk¡Xá^gŒ18‡ˆ Í^šo9û¬çìØÅEwq—BÇ—™ZC*cÚ4o]3qcâ¤.yÖóìá`çQ ØMZ¸Ic¼Æë7ñúÀK`L÷È>¼#{}c¹KÖ0¥²sÂb?+šÐá“Úw©ñ­øAµ ôuiΜò*o=<óóÇpœP½ý뢵&‰à8±XŒææfš›šhnn¢¹¹…ÖÖ¢Ñ(±XŒx<ê­ñÆóü߯¼>|͵Öx‘‚ –0 é’¿ƒêbOw#~»ƒžFDÿ5”Có³©p_€„ ,ÇzDˆ—uá?„…ì:‰2‡“È€×rÂ]#.‘O6#÷h-ÊüOäïãœ*ÓoGÜ0jQöïßìJQˆæ"½ICÕÃ2œ‹ ­Ä}Àÿ¡ö>üby b$°œæXá^OŒ¸eh¥ˆDÒ‰&SiÒ¥ÿöD9áVw%H¬ñjCižª¼€ôP”‹[¾±¯Käýµ›½õ0"«î?Eâ«×‚4Ò蹑ò‘ˆ&"½S#E¸+äœ*M²Ô‡ŒO¨6 Ò@9|yþUнoåXá^'€ãûRƒSµ ¢½äÒåù¨‡|¹Uh)ßöÂÏ¢þÙ!7õ¾\öÚf®¾¸ 7í[¯M¯=ün×`\ŒÛ ™pbÜ^p»1^JºñKdõ«¦Ÿ!Ç(ü^TDD¸/ÜsñÛ ÂOªp¯„ç÷ÄBo 0n˜¬å=M&­ˆE ñæcì>4†'¶â½Äœ©Ê]±ÞÞ^zzz0æUœH„X4J<§µµ•¶¶6ÆËøñãioo§¹© ”4ÀLv0l~ŠXÜ# ž18§nçlÄ·y±'7©K˜bIŸŠø/Cb/CÂ>„ï#–Ì“l¸q'('ÜAüÒïâäDØ8à ÒA„ûPLúTŒß#bp¤„ŠœS&Í,¤ÑT/áþ2ƒ÷k/†‡¸§rŒõƒˆ·–f ±X í8x¾KMð»y¾{Íi6ÓùÀ7At'–ÄJÕ‡´òÑÞ†ˆŽvÄÊUMŒè0qoù$µ Ñè!×|™n¼ ‘nì“îg!Qjʱ¡‰_ŒðkF®‹L˜*ûçO roG-y€Ú¸W…Ù†X³+ ÷ç¨}/ÊAÄ‚_N¸kdvÛ‘Â%ÈdYåH nJåB€Ö‚cH¬w+ÜOs¬p´£‰D£/=7©P¾è rùv}É“üYט’~ï¾͸†¶VÍ_×θö™¾HNBº“9™Ãx™CàöŠˆÏºÅ˜‰C‘`ªÅü<ƒé¢Ñø¹ã¯÷Ï;OŸg ,õ€—ï½½çÎ=Á×EøÄ÷â¤]ˆ Pö3ۖɸd2½ôöörøða6mÞL,câĉLž<‰)“'3~ÂÚZ[¥‡EëþÑkN49Q>”ô „¿F^rµb"t./“F#!Àà}\/£üྠbý®×ÑNFþ„9ÝTÙHe—ˆZ4kíOß…÷JaLŸ§öD{‘†C%&!Úc$Äs¿”ʳÓv÷ס. qû+¹|YF9V¸×‹GÜ("Ú!‰àùÂ=4l²âàST1Wš°Ð/-ܳŸZsÅÊfæÍhÄM$Å’ž>„I€ÌqŒ×+á³29,Ö–6ÈÊHv¾…=;骂à9®‚2*óˆe?4pÕ„ÜlÂû² å`€dÂð¦×æ‘“ùå:Es…Gp5¢:lA/ÜžL$èììd×ÎÄ7v,Ó¦OcÚÔ©L:•ˆ#rÆór½–Z°qùµw#éBÂ=®¢ü3ób`1ƒ ÓØŽøÉ—ã"öêÅ‹Ô.AæØfÜäp»ÁëCŒ?þ`Ò¡Âx!á^ä0*ö±˜˜G|ܳá!}z…?à_ÌCÚshˆ¥ùë7â•ÃXû2´5T¡˜h/e!/p˜/ìÕÀqȤÓìß¿Ÿýû÷óBôñ‹;–î®.´Ö§ÓàÔ¡ä8ªð_º¸æ !ÙÞƒóRŒnA&(R9÷#ˆå¿¸H8Ì¡îþ ¹ÖS¥ÙH¸¼iþöfrâÝ -S«È·ÜÀÊZ²q«5idD%jÙ#f?Ùð`%i«°ÿTa†¿Tâ·C]‘‘߮ҽlÅXá^7 Ž#"ÚÓºLt˜Ð!a_2}‘¯ýüµvȸivï=ĘØAV/îÆé:B*‘D~„g1BÑŽXÇî*h#„?)¾-õÜ">ìRàÂí.}Iͤ¶>wCïùV G{ 1jrn7Ù(;Åcµ›2פðØB‘¯ù \Ïåð‘Ã9z×ue†\oh¯õ(Æ V§û‰Ncð1Í«eÒ^N¸+Äj>‰G—¸šòþÕ.ð+êg¥L"ƒ‰Ob1_ŒŒ—X ž‚ˆôFê'¸kEŠ¡ ;éRÝßÂPùë@å„yc…ý§ ã‘Þr$€gëP——¡iðYFV¸×tªãDˆF£"àBñÐó––p‡ÉV ¶I—õ§ö<úúúØ»o7[¶î$Õû*ÿô'šÉ­è ,Éu~~šœ«L¾› ¡Jÿmý,òÙï¿m㇊ ¬ö8(åÐÝå¼ùG¸óÚ8þ³"Aç„)bm/¡©‹¥ËÛV$¯ Á¥)Ðu½ò!n,¥8Œ¸ÄÜDVYËÉ…š(?F¬îåÜ)– ƒÇþcùŽõ)'¦62ôQ+¸œ\ÈÉZCë^‹Œ/x •d4 É=ø‡&ïá"÷rN‰16IÔp1i–£“Ú0.‡¡¾Ï<Ë)ˆîuÆ@Öâ®´&/hc%ážu“Qy"W„nnR!×u9zô(;vì`÷îÝìÙ÷*==)þþcí\x–"8EHšÐgÈÂLTšg‘/vlÈBo‚è6ÊŸ‡‹Q©d”[Î;ÈK{&óíGZÃó|òŠÌ³¾÷æý¶»¶á¼CÁ H¸½rS»A¬î¿ úÙI—"Ó”ã—Ô/L!ÈïUÏòÂDËú›×£)ÃTËÀ ‚§lšáF!=g• }o_!§ÀËÛ2œXá^GŒñ0ÆC;ÑX'kq(1ð4›]å¥Fi‘þN$B:bÇÎlÛ¶={öÐu¢ c ½ ޾™w^¯ðÒæÔôÐ0ŸøyC¾'äÚ-Û‚°™&ˆó—´çÐ÷øä5GXßÙÁ3» ÍñPxI“›Õ4ãÆ˜²OÉÜïv•ñ×C ,0ÙüÀȽpÊ¿»jƯBü‘'!>®ˆrpÁ2Hä.àbí݇¸Ä¼Ê©1Ez/5æõ”~v*dë4ª›dÆA„~¹ˆ;ˆ›L­\+ªiPx Ï5_ |i†òO›?Ê!b´ˆJ Z‰½ÔoN‹°Â½þˆhM4ÅU:$ÆC¾í û¨(ñ:ç;â8­4½}}ìØ¶Í›·°{÷n‰ÆGÓݯYå3ïj ª É4#çÑZÔ&Ï]†œŸ|v£JLå€JùŸ.½ ×|œÏ\ßÀßmåX/Ä"AY*ßÕÅø£‚ –÷=”¾X/F~”þÐy^òà(âÖ²i†M±°5+ˆ5g4¤ÓÏY+¼^tÛÒ¥K×nÚ´iƒçyËËd½páÂ…«7oÞüDèЧ o»ë®»&}ò“Ÿ\ÓÕU:Túœ9s6ýêW¿š´lÙ²Õä®›*X7oÃûÃ×YΧ>õ©e_ùÊW¢©Té÷X,ûþ÷¿ÃM7ÝöÕ¯æÉaЬ—Úf\×ÅquðàAõ¾÷½ï‚{î¹ç&×uOÚ®dÂãÿMJ'ž1:ô›÷Ckí\~ùåSxàYHCÒ -nè3X/v^L:5²ÿ~e*¸Ã544¨¾¾>¥²“SÔ„ÓëÉ2t8TaèC3VÁb)‰îuÆ“…3)X*Ìcž•]d`$A;½½½lÙ¾• 6²{÷n2™L6¯ˆVô%=ƶhþêýÌœÉFŽh/Fø•äÛi 7åN0làæôôŹxÁ!þäÊñ«8®§ˆè.Òë1Ð}ò½Ô—Ó†°{L „*⋬°ø,©¥>ƒu‡\8ÀÂÏðRl[Ñå©§žŠ¾á oè|ðÁË ÷ȸqãÞìØ±±íííAC¤Ø¯=º0•J•Œ¹­”ò®»îº½Ë–-[@mD™3f̘±*~ªx¹ª©©©±&™tGïÚµ+~ûí·_ó裮r]w@¾ÏÑh4ÙÐÐÐÛØØØÝÖÖÖÝÒÒÒÝÚÚÚÓÒÒÒ×ÐЊÅb™††† Àý÷ß¿rÿþý³Jå‹Å¢W_}õJľÒ9Šøl£óŸÿùŸÇ¿ÿýïo;zôhɃÛÚÚZ¾ð…/,ÆÌh—]2¡Ï ¹F­)Xï÷ùÅ/~1ò™Ï|Fgߥ±b³<šÊñÛAqÛð`–ºb…{1þ?ÇqˆÄb(­ ÞéY?u¥roy¥òÜ-´Ö8Ž&™L²mûv6lØÈ¶íÛH&“8ÚɦSÒð<ÃßÒÄê©^3²E{@¾gJŽ`W#穌";@5χÞÁ3ŠÞ>‡[Ï;Äó“øéºÍŽÉ^ó~e!¿ªz_9yåöGa¬¢hhhˆõõõµ“ßa1¬ >£þêH‘õbß Å¹ö?s-¶|ªÝ–·½¹¹Ù¹ñÆ·=ú裉t:]òžqãÆ%O=õÔØ+¯¼òp©4étÚ<òÈ# ’ÉdÉpmmmG®¾úê]ÈyÔB ¤¿Ç0°ôEïÚµ+zÛm·]÷Øc]ZíA Ý'NÜ?}úô=Ë–-ë\ºté¡E‹;묳NtttdÈÍÞ ïì³Ïž[N¸¤¯DpϦ×ÍÍÍãÇ)ûnmiii>ãŒ3ÎfSüú÷mÐ Ä|XÜg ¾{ï~÷»Í÷¾÷½/¼ðBÙÊŸy晓֯_?³ ÏÂÆHvŸRªÚ{o´ˆXEuúhÔ<È-#+Üëˆ1bqDbÑÕßÍ¢¸Õ]FH&Sl|é%ž~úöìÙK2™@k-dBægÏ@WŸÇ‡ojå7Eq3§ŽL¬*˜Ê`ayfBÂXhr“4‰qÌ%Jc$Íç®;ÄÁ®É<¸IÑêOi10kz¾?(¿Ô€Vå߯;u~“BŒ1‰üˆèðzð=Ä.¸à‚ùk×®-ë°xñâ™ÀÈ·œ‡{x)Õ4+çvQì{à7_k¼[o½µóË_þòÎ;v” yâĉñßùÎwμòÊ+.‘D½ð ­Ï>ûìÒr…-\¸pëš5k3zQ€êîîV7ÝtÓëÖ­»¨šZ[[¬Zµê‰n¸aãe—]öê¼yóúûÐ,…¿»J&“*NU­¼ûÎTò‘ñyžÉåþйqèx<®§L™2±’p¿è¢‹–#3 ‡{ Ý‚²ßÉNŸBzaŠ.7ÜpÃÔ_ÿú×Ý„8õ¯¡:ßõ(£Ã$fAXá^Oü׊ÖÑHÔ\šß3¬u¾hŒD"¸—Ý»wóä“kÙ¼e }½}(­ÅÊ䢧ÏcÙ¼¹%BÔä0RJÎÉÑ sˆ7óÍE\ÿšhÝ*2®2Ü{ÑCD¼èD:ÂÄÖn>yõ Öïiçx|ös—óù½ç…è,Q±‚ã_÷ eJzO Œw’Â%xᇗ8ò²‰Ù^ŠùIæ™={ö´§žzªì‹:7 ñˉî@ÄœêxãÇ×+W®|±³³s¾ëº%Ÿ¡k×®=óàÁƒutt÷Ýwß´ƒN+u|$I]qÅ/2:…ú«¿ú«åO?ýôÊJ #‘Húì³Ï~êÎ;ï|ø¦›nÚG¾Ïþ©>ûf-(×@ ãc´çU5³[`Qvè•û^ØH »ê8W\qÅÌûî»/šN—Ö¼Ó¦Më¸ûî»W­X±â(Ò& Éжà™Ð¯\ÇqT§:Ø#ê¢Å´02B[ZFV¸ŽÖDcQ_C ô}Ý#‘Z+öîÝǺuëxö¹çéêêB)…ã”6¥2í­š¿|SÇ;$û†Ø°d¯B"Ýå(ðg;íIŽÍй§—›LÓ0sŒm„¦¸îyñÏóç&ʺþP°2Â3ÓêÐc< ZÓÝ×ÀYÓŽñÑ×5ñ·÷Å1€õ„d‹Î³Â–Q _ÎZo Iç½u|^Ì$ü=<¥{Œþ<^°­œEh°‚0ô«W‘XT}á`¾‘Œwûí·o¸ÿþû/9räȤR‰^yå•?úÑf~ìc{™"ç~ÿý÷ŸYn°ä„ öèCÚÄ(´¶ÿö·¿÷ƒüà ÏóÊZÁzn¹å–_ßu×]ÇãA¨Qw=†ƒB!æò“A"\Ub"òŒ=g¶ŒR¬p¯ b ž÷hT&úS*'Û ¾¨Féíëcà xøá‡yå•W€ÜàÕbÖM¥Àõ ©´áη5qÅù’}µ²ëæŠV€ÖàD5ÚQàÈ“„cÝ.{gؾ³Ûúxig’—¶w³igо>hk„¥Ó`ñ4‡ESaé4—y“aB+´6@Ä‘r\Ò!1œLUçã½ëÓ]…t‚qð ô¥o?ïU^Ø=•_>ïÐÚ@ÖW^®;ä½[ µVÐ0(Óe/æ Æ¦L™2öÀ¾= é/¶Ã¢»Pœ~ÛÙÚ|–ÚVŒÑ"¤ë¹òÊ+.Y²dóc=VR¸§Óé¦x`‘/Üèõë×7mÚ´iQ¹BÎ?ÿüçg̘‘bôýNæë_ÿú²ýû÷Ï,—(‰¤n½õÖ_ûÛß~,8®u³ C•nBáô”ÿM#ÈŒ¹P¤`Á‚s* ÷3fLúüç?2'AÒXöƒAšÜxLø»RêHç1y>wW‘¶VX ÿiŽîuÅ€ñp"¢‘¨¸]hñ:PÈŒ§(غe+¿{ðA¶lÝJ2‘$qrÇ—Àó «×ã=×·ð‘[cdR^6ßZTZ)¢1 æ›×ÓGNxtîKòòîÏoîá™=ìÚ—áб ‡Žy44gÆôÙ,9c!7¾ã¦M›Æ¶mÛøÃžá¡í;øáÓ¯é;Âäv‡q-Š…“aùlÍkfxÌè2­Ý£­Qô¶ñ ™´ëP:§¯œ@ò›äÖq㟻î{NtðÔ%e^‡°ÛKx,jIËz."P0y–ça* æÏ¬ U%ü"+õ9ÜIF;Ð7ÝtÓ3ëÖ­;7•J5MdŒZ·nݲ§žzê±óÎ;¯‹œ¥Xýë_?ãØ±cãKÐÒÒrüï|gyG呉Z¿~}óo~ó›K*¤3çž{îSV´Ÿ¶T2>¨j\‚ZZZšZ[[çѶѼÁ·ôìkÞûÞ÷NûÖ·¾åùc~J1Ò¤IS8PrzÑT¦Ò2Š±Â½^$ÀDLvpj4ÍF# f<=~ì8>ø O<ù$ÇŽóÝbtQ {!]½KçÄøÄí 48Šd‚ú“uw‘:¥AÐ6о—]ûRlîLñâ¶^ž{©‡-;ì>àâgÌØ©,9ã Ö,=ƒåË^Â…ó™=kMMM466úâÕ¥··®®.^~y[¶nå…õxqÃFžÜ¼…‡¶Æd’4:)fUÌŸª9k¶fé4܉Ú24Ǥ¾®Q£ðüÅå«_'8%Ÿ!І§ô¥£LjKñÙ«»ù㟵²÷¸¡)\šü¨ŸzaH‹{ñþã[Üý‰°‚xÑ#‘ª,lALmF—øro¹å–_ýêWwlß¾ýŒR‰Ž;6áG?úѼóÎ;ïyrçï­[·n^&“‰–:nÑ¢E›.»ì²W©ý½QµUÔ³…ÕöwSßùÎwœ8q¢d£`̘1‡?üá?ê­É5¨&^ºëº'{ÎFk]Íß¡rjy}‡óo2p‰+‹çy'{o@U;Øu]—ÒaMÃ.ˆa"Ë—/×Ñh4YªaîÓzÝu×½ç›ßüæ#È`ß$bÍO >òÉж td8´§€ûÈX¡á˜˜Ìr a…{ þRµ–™S '*‚rËæ-Üsï}lܸÏóÐþ ÕJO&$S†±mŸ{O 3&i’=¦¬h÷ ¾âîâÈÀQE|_\—®>xõhŠ=¯$Øðr‚ /w³öù.¶ìréM(š[ÇÐÑ1™Ž‰“yÓê3Y~ÖkXqöÙÌš5‹±cÇÇK–¯µCKK ---L™2…‹.º€ÞÞ^Ž9¶íÛyöÙçxþ… lÚ´‰ßï}•_®?Dª·‹ö¸Ë’i^;ßaÑÅâiÓÆ)Æ5§iŠËùày¸žGÊázžQ~ïFØp’oDQÊГŠpÞìc|âŠ(úó\£È;¤P˜—šx©Øvå[é• ¿­Gº ­Jú†±‘|žE™4iRzõêÕÏ–î©Tªá÷¿ÿýbàü?ÿçž{®içÎ%ÝDÇÉ\zé¥/¶·¥ã$ IDAT·1¼kÉ@ò’ûóÉ'Ÿœïy^Ùîþyóæm½í¶ÛöRß&UA;Ü £Á¸«Ô¬ìêÆÅæ•=ØòÛ˜+,¯T>fÖ¬YÇz*wÖ¯_¿ xÕ…y†Ï3pÑ D|qßÉûN~O@6òoΕc9M±Â½žø¾Žvˆøî2ÝÝÝ<þÄÜÿß÷³oÿ>”ªtÕ3Ï2®áConáÚ‹#¤ËŒƒ7Fôc4"¾éDxŠDÒãða—½ûzÙ¹'Ŧí=¼´£·ö²}w†¤i¢£c3¦-áš7Ìeñ¢…,\¸€3Ï\ʼysin®MÏ]SSMMMLŸ>Õ«Vpôè¶mÝÎK›6±eÛ66oÞJgç.¾ÿÜ~Žüî­‘.N‰°xJ„ÅÓOÕLç1uœaL£¢1.ÁPÒCÚ5¸žƒA¡‚–K1ÈÄXsæq~·)Æ=ëZâPhqÏ}WßÛ?Mà¥òäú¨Ò±§%7ß|óæ_þò—Ê Rݵk×Ìgžy¦yÅŠÝ€zâ‰'&”s“éèèxeÍš5;‡¢¾mH Geß¾}Ë¥ÑZ»Ë—/ßA E¥ëº¤R)û¾óÎ{ ÕþæÕŒò,tC,u|¶WìÜsÏ=ÖÖÖväĉÊ•ñòË/ÏݰaC|éÒ¥=ôoŒ„¿GÍÕR°? ·Y87¤Œ1ÉéÓ§_¶gÏžj^¸öå2б²º‘À˜žç‹Einjfë¶-üè‡?aý‹/’N§qœ¹çLuœèñxÇu-ü¯·7àºà¹&;€S)ÐŽ%ÅÑà€IuÙ²'Ás›ºÙ¸µ‡M;’ìÚçrôD†=M­,Y²‚K¯YÎ/fñâ…Ìœ9“ñãÇ1fLæ©)cÇŽãÜóÆqîyçJ¥8qâdçÎ]lÞ²M›7óÜó/ò«û·â%ÑÚmšYã=Lv8s¦fét˜>Î0¦Iu<2ž"ãŠX7PJ“r¡!êñ—×u±÷x;ÏîV´Ä+G)½.ß1yñƒŒ1wH,nõ¤Úºôó,…yýë_påÊ•OÝ{ï½×—JtøðáÉ÷Þ{ïô+Vl܇zhN2™l*•þ‚ .xöŠ+®84$5€EvÜ)ôÚµkÛ»ººÚÊ&ÒÚ]ºtéj‰èرc‘d2Yq&ÌXÁ«ã>d÷ª ]ÙU¹¯Pþ™0û³r¥ŠŸk±–KÞþÉ“'«¹sçîØ³gÏ‚rÑŸŽ=:åK_úÒYßþö·å[¬±P¸^¸ÍŸl„FɳÜïÙ³' ¼‘ ƒSµÖjñâÅã7lØ0 ÿüH<Õ^3Ë©îuEþNµ£YûÔZ~òãŸðòŽ~Å`ÕêP ºº ç.iàsïn"7¥ˆ7*p4x‰”áx·Ç«ÓlÞÕǺ]<ób_Ns¢×E;ÍŒ0…¥+–pÁÊóX¾|óçÍ££c---Ùè7§ ±XŒ &0a–,YÂ5׈˜ïêêbÿWÙºu/¼°žuO?ÃKÛwðÔÓG0k{Q&ÍøÆ g΀e³¢,›¡™=Q1±MÓ7D# Qd ¤Ý(“Û]>{Mwü¨‰ã}Ð…<«z¿©ý·çV¬«`0rÞ3{$ Zãy^U"…‘}žåpÖ¬Y³ñÁ|]"‘(:ªçy‘‡~xÁ§>õ©{öì‰oÚ´i%,~ñx¼çª«®Ú‚¼À‡"FyÕ¿A ü½û•}àÀ†T*UÚΧ­­­R¨À ¶mÛÖT©Á⤄{ÝEJåY‘¡òq¯qT™RÇî50Ÿ…ëŶy—\rÉKO<ñĪr³$»®{ôÑGW;vì‰ööö BQοÜþ°ÿ{^Ãõÿñ—¾úê« JÕ#@k­gÏž=˜ƒÊ—>9†ió#dN¸KzÜfçž$/mëá¹Íݼ¸µ—Í»\Ò¦ñãÆÑÞ>Ÿ×œ;%‹qæ™g²dÉb-ZD{û˜ŸiýˆÅbŒ?žñãdzôŒ%¼ñ1~;f×®]³K¥ëèèØó–·¼e7¹n­©‡¸*™_OOãºnÅL‰D`ɨEÙæ¡‡šrüøñ².ä‹¥A ÷jŽ5>'YÖ Ë‚F àÞ¢s/ZT¨ŒB¥_ªì`»û¶·½mû¿þë¿8tèЬr…ìÞ½{Ñ]wÝ5÷ÓŸþô6òÿvKYÚ ëWl §ñî¾ûî+Òét+Uà‹î`6ê ¯`ò½Àš¾.91ŸÒƘ~‘vBÇYa?ŒXá^G\Ï •bݺ§ÑZûV~ï{žAo\å°kï þó›w$Ù¾»—{ú8|ššÛyÍk–³ìÂ…Üô®E,Y¼ˆyóæ2aÂÆWvðèh¡££ƒŽŽ.ºP¿öõöräèQ<Èö—_fëÖí2vË6~z÷V¼¾ŒoSÌcÁ´(ó'iMëå‚ÙšÿÙæ`tÖŠLógT Ï• )]äòzfH^žõ¤êéÝÙçY3sæÌÔE]ôÌž={JΤºoß¾™÷ÜsÏ”7N8qâDG±4Žã¸«V­úÃøñã]×uã8C)Ü+æ=VYÇ]­uYS©çyzÇŽ%ádËVû÷ïw~ðƒ¬r]·ÚîÓî!Q^–¡´]eÑÙäµ,»Ú¼ î«“)¿ò´©â’Žä’·»Ø!á/‹/îYµjÕã?ÿùÏË ÷d2Ù|×]w½aõêÕw­\¹òÅ-êÅÜhÂi¼ßÕí·ß~aggçåê=ct*•rBÇ÷‹dS°ûƒ;ÁÌÚáýnèÓ\ÿºšÐ¾lyVÔ-V¸×#"å¬ìƒ7ê$z»øéáß~áqäDÐ,\¼”w}àZÎ;÷\,˜ÇĉŒ;–ÆÆ²ƒâO›š˜ÖÔÄ´iÓ8묳èîîæØ±c:|„-[¶±ö©u<úûÇøÑ[ˆ9†ñ-Ç9†ë6¡bMî/¯{h[¿é^Ãn4¹ ˜B¿ýH´é«ÉçY ï¶ÛnÛpï½÷:~üøäb 2™LÃÃ?<§³³³ä Öæææ#7ß|óÀ"ѳ”Õ\¸O˜0¡/–eÒó¿ª3Í?Rù>_ )vLèØR|Z71G‚ucLÄ£ý¥–»Ÿ6X‹{=1¦&s™*ZÛÆÐÜâây®ëâf\v¼¼ƒíÛ¶áyžçÑØØÄøñã˜0aÂÿß޹ǹQ\ùþT·¤Ñ¼5¶gŒß0b &ĹöšÇ5á¹`‚!<Øò!Þûá³ Öönn6$$7χÜÝ% ×$!„…½fI ÄNl60ÄÆ&ÄãÏÃöxÞ3šI­®û‡ÔšR©^Ý#ÍŸ¯?²º«Nªni¤_®†ùóçÃâÅ‹aÑ¢E°páB˜5kÔÕÕA]]ÔÔ˜^³5µp]úúú ¯¯Ï[†ššš ¹¹ššš ½½º»»¡··R)B!ˆemÙPQQ!ÛË e[`YÌŸŠ÷lJŒø.µ’¼öÌwQ™`è 7ÜðÎûï¿ÿ_d) d-ËJ­^½z÷dº¼KÒ9sæ¤fÍšu´µµUz=@GGǼ‡~ø³7n|Se§À~ñÅëxà›zzzf ŽR;›b0¥ñ1)+z×EhO˜í1Ã\<Ï?Ëú/؇ÃäÞ{ïýÏ={öœÓÝÝ=OÓ_ä•W^¹þÊ+¯´^~ùå_Ù™Ÿ×½üpS± ?üáç?úè£wvtt|F}”‚QOŒDÇ*+çÇÈ—‹ì„Câž½ 9ñÞ¯„J)%‘׃Â}¼ ð–,\Á$êµ, X„æüRJfo@”J¥ ··:;»àƒöB:‰Î‡ÃaˆÅbÐÐÐõõõÐÐÐ .„ÓN; æÍ›sæÌ††¨©©H„¿¡Üäcddúúú ££Ž9ÍÍÍðñÇCkk+tvvBWWtuuÁÀÀ¤ˆeA$P( ¡P*+«À¶C`Û6+9'„€g¿M€ð¯‡ziHÈFä@f}~&~H]ÊF/¦*&y¦l”çËÝwß}ðŸþéŸö666žë·íܹsï¸ãŽ?CéÏ“÷í¨4¢”Bö–òEÏ%—\²{×®]A·’O¥RÑM›6]þÌ3Ï||ûí··ù˜Gyä´'žxâÆîîî٦㢔Bæ&›c;ælš€Î†2¹×Åúû'žo­aFûµ%Y¡¬|sQJ½Õ¨LóÎ¥~´ʈA6Z,t¥(£@®»îºc[¶lÙô/ÿò/N§•_Š©Tªâ׿þõW–.]:ïÞ{ïÝüÕ¯~µ‰ñÅŠ[>§ìÛ·/úÀüÅ–-[næïyàN›6m¥4ÔÓÓs–ð`(µ‰D„9f^¸»’:Ó_úDÇ¢³=?n토pGLb¬Þ*¥£9ÔüÇ …ŒP'„€KÓy«—X–Ô"`“PæT’‰œ€Ì]DÝ4¤Ó. $ ñàAøÓG7íB(‚šš¨¬¬„êêj¨¯¯‡E‹Á‚ `Þ¼y0wî\X°`Ìž=*++¥‘äRB)…¾¾>hoo‡––hkkƒÖÖVhii¦¦&èéé¡¡!„ÁÁ8¸Ô…p8 ‘p"‘T×Ô‚m‡ d[`Ùöh‹·ð=Íœ[/ý4ïÕ’oNܾ^b>û#"eþ!ŸjkkÓ^xáÏÃÄx¬X±b×i§6 ¥ŸÜLô›Î½óÎ;÷?õÔS-º•:úûûëׯ_sww÷ ÷ÝwßÈ?§lä.Çï~÷»ºÇ{lù¶mÛ.òukx×uíáááÉó“Ç'Áuüë©}ŸfÓ}Lú2Y)HmgÚ‘üÇܵuëÖ 8p¾I·ûöí»ðßøÆ7n|ó‹_üâ»wß}÷ÇÓ¦Mó–x-Ëo~ó›ØO~ò“O¿ùæ›+=ºÔqœ‚‹ÓÂápÿ—¾ô¥ŸoÙ²å¿Ê„;ÇqØ éL#î~~ÐùäQFâ½h»¢=€Â}œ8Ö×çN¯ªÙû—ìì>[&+gE¾KÝÜ <Ÿm¡—jFs¬fs-ÁM§ÁI§¡`ººº ñàAx÷ÝwÁ²,°m;oýôyóæÁÂ… aÉ’%°páB˜;w.TUUAEE”••e¿t"NÃÈÈ C?´¶¶À¡CM°ÿ~hjj‚#GŽ@ww7ôôô€ã8™ÕY²©A¡p"ᄘQ_™Ë)÷VðaÏSî“&{žˆ r.\9F¶–»à¦LB;`ÎùÔ_ßÜϸ§òq³fÍš?¿üòˇUi1³gÏž¥ÝÝݳEÑüÊÊÊ®d2Y)[“›Rj …`lpãë>J¸¤Û¢ömºbQö— ^ôÈ¿RéÇä"`ï»AâK&8Eû4‹Ñ¿ÿû¿~ݺu3:;;êúœùÖ[o]»k×®Õ?þxãܹs›çÎÛ^WW×W^^îô÷÷GŽ92£¹¹yþÑ£GOÇã³Òé´tÉ·3Ï<ó•»ï¾ûÃÍ›7AfC)µ²«[ù‰Ž ]qu&Â\61ЂÑvsP¸—J© £Ë*…|ðÁ¹Oýä_-þC]˜#À¤\'óY äÄ9g›—.¤œT*ánƒ÷Þ{?Å'™´›†úz˜¿`œzê©0þ|X°`Ìš5+—G_UUÑhl{4}Ôqœœ8ïë냶¶6h?rZ†Ã‡Ccc#´µµAOO¤Ói „€mÛYnA(‚h4 –Ûʤ¸°~"ÄÿªjIÎ{î9÷·»g#8ï¢;ª²ûü„æ°L".™]¹re÷§>õ©ü÷yóæí¿êª«ŽÂøœ꺮ÑÅ©¥|ÝÖ­[÷‡;v|¦¹¹Y»ÚK<mß¾ýŠ7ß|ó¢Y³f5MŸ>ýx8Néïï¯êêêšÙÛÛ;Ëqi Cmmí‘[o½õÅçž{îjY¤ŸRjŒŒ„alÇk¼$è…SàþuF¼Ž»*²ëa´Ô£é ¸¾”Ñ_…{ã7Ù¿ÿ³ßûÞ÷¾Çu÷ð ©Tªº½½ýÜöööswîÜÉöaež9sæ;ßÿþ÷_¨©©I…B¡”¦?oª:Ç¢sbU× Ó îc$1*€JæQ}Ž@¢]tÑì}úË¡~RMtâ\VžMxô ´öl´?“ãmMlˆ„#å£ÖÍæÑ»4sQlwo/ttvÂÛo¿ Žã€mÛP[[ Ó¦MƒX,Ó§O‡¹sç¢E‹ ¶¶6³ŽúÁƒpôèÑÜÅ¡===088™*…ÂagóÏ-+“Úb[£é-yË- 4?½ˆ9N‘ –‰káPEÑxOÑ6?‰¢i`jç¸Ã… Ç ¢¢‚\}õÕïïØ±ãÂd2Y¥³/++¼ì²ËÞÎ^”:^ï£~JymÂç>÷¹þûî»ïßî¿ÿþ¹###Fws§¼¥¥åŒ–––3üôU]]Ýñ·û·Ï\sÍ5-/½ôÒj•m___Æø:¸!wfU™¢ç¸Þ͸¯-1ÕM1ºk3!„fSed}™DÛó¶~øá?E£Ñ=öØc·uvvú]í%ox¦†Ó¦MÛóØcýàâ‹/îknn…B¡•ßD"á½Yq­Êmå·ƒ ÌDìƒÄui wC²Ý€*¨Œ0¯€dÄy2ö0d–=Êû©R⺂4S!n’‚À}ÈÄ©¢Nå›BæR¶eØ(ó&x@]œtÜtŽë€ÖÖVpB¡LÎ}(‚T*Éd\×…H¤ "‘p6½¥l+U÷î4J ÄÊ<{¹á¼˜&^‰"ú-Óâc‹XÃۜש‰Æˆ{!îÚµk›~øÃ6·´´h£É ‡n»í¶`tå‰Rc-QTÖ#½nݺßzë­_|ñÅS©TIn6‹ÅÚÖ­[÷ìúõë?:~ü¸Fã*ûx<^žL&!‰”&)0ú:=êíÓvÜ#îܧ‚Feµ©4¿~D:¿Ÿ·}ÿý÷7ÖÔÔüóC=ôß:;;ù‡oêëëÿðÀsíµ×vAV„Û¶íê„{öÂV~âÁ¯gÏn«Îì!²IRDNXážéÍC¯€Œ@¯€ŒH¯‚Q‘žk–}&Ì>»L™HÀ Ôé'•(ªËv˜—b!0ã׆—ùã}8’Øf_Õž?xv¥;‚P8”‹dÓ‚Qúc/ %…J:Ñ&‚6y~$õ…íEb›(ûæ·ù2‘].7žïuœòÂÝq"ºÅu]ö¦6'îœ9s:mÛN9Ž#îµµµGï½÷Þw¡p¢_Jh*•"ª ¥ÔN¥R¥ÝèOúÓmëׯïÚ¸qã•---gÕa,k¿à‚ ~ÿä“O¾vÊ)§$™UWW+oþÔÝÝ]700`•——›.?ÉCÇÕëN§Ku~©ëºÚÕ’É$+B‹Ù·÷÷.%N[àÿ³ÎÄqmÀÀq›É\2î|¿'x׬YstÕªUÿçë_ÿúÞ-[¶\ÞÝݽh¬«£Bh,Ûá…þÛÆ[UUÅ®ÕÕÕiBˆ*Ç’Éd%äŸã BÝ$˜`"âu“#Ä''„pÏþ!U0/z…Ñ<ô0òE9d-ÐÞ^WœÍ¨æu]×Í^ðI)5^yE–JC)'6}¤Ö˜¶ãŧ.o;¯œiËm2v’(µ"åDT&·Þ§dÜŠ4™<[ɘ =[gY@˜÷@ r\ÇX,6P]]}Dö~v'TSSÓ 'HŽ{²gÏžStë=¯\¹ò·+W®ìƒq~ýkjj†c±X["‘¨ÌæÿæA)%‘Hd¨ªª*ãôº}ë[ßÚ{Ë-·ø‡ø‡ó·oß¾¢½½}‰ÉÝN=lÛNÎ;÷à .¸`×í·ß¾ûòË/ïšN§Á¶íÜø/^ܺoß¾vÑšÑétÚ¶m;ÕÑÑjhhPF4UTTT¤êêê:(¥¶mÛ€t:mÇb±ã•••Þù-jŽ{]]]oUUÕ1˲\>m‡RJ\×µb±XJã^QQ‘¨­­=â8N™è½•N§íººº®P(”Оˆ®­­¬®®î „8¢ÏÇqB±X¬;‰8 ?ϲþeÑãQ___ŸúÅ/~±í7ÞøÃÓO?}æo¼±¼µµõÓÉdÒ× #‘HßÌ™3ÿxþùç¿q×]w½ùå—÷‚$ÿ|úôéÇ¢ÑhŒNús㢔†ÊËË»{zzH]]/ÔUk·‹î¨ªŠ¸‹&8ªÉXA9æ·cL3ÃÉ3Óe/õrÒ½ucÕ{T*‡‚ááaXvþ²_oyýõŸÂî(ß·o_u(~§R)kþüùCŸùÌg”) Ÿ ¬ßþö·±ë®»îôööΗUWWùå/ù¿.»ì².ß 9räHx÷îÝ5©TÊ}qRJI8v?ÿùÏ÷ÔÔÔ<ÁرcGõ¦M›Nþýïÿ©Ã‡Ÿ2888-•JU$“É0¥Ô²,+‡G"‘ÈP]]]ǼyóšW¬XñÑu×]×|î¹çä-+˜GSSStïÞ½Õ"ážJ¥¬ÚÚÚÔòåË{ËËËÿMÆãqûwÞ©íïï‰þ.Ç!±X,uþùç÷WVVýüîÝ»·òÀU¡PˆòÇH)%étÎ9çœþ“O>Yuc :;;Ão¿ývLt~2ÇÞÐÐX¶lYÀërmãÖ[nýN"‘¨…ByK#ð_ÞS0±njk÷!šÙaëÖ>ùÑ#H½lb‘«×ü Šô›œ'B$ 8t茌ŒÐÏ/ÿÜÿÛ¼yó/a w0ÿ 9Q> ­¯|å+ñÜsÏýµ*å’K.yþµ×^ûw6<Θ¼n:¶d2 ]]]ßH¾ IDATá={öT{k¢‡™g‘0¦Œêlkœ­JÈS® ¤¬¬,Gâ”Æg°Þó7÷áÕ¶y¹ Ê«°)°clMû`õ¸Ð¿'€=#_]¤:o<º±hë¡ ^—ÛÎGÛ…mEÑýÑÆÎ‚”ã@:íqgΜ٠S_¸# ßúÖ·.9vìØ•Íi§öî]wÝÕùŸ1‚ŒSnR§ ²è¼JЋ¢Ñ²2“‡*_ݤÜįllü1úúCÑ>6&T¸gsÒ dÄ8+Ô+`4=£ùèÞ¬0(çº?T^p›´ñìLÚäê¦M›–,F{»]wu-Zçgz”/ÓH$v²r~IIÑÁŠoÈkÃogl<ÿâHw¾,æÆî-)éC´ëÆ*·Ø'ÊãÖMØ¢4oƒR†tæ&UéSN9¥|è!“òíoû´wÞyç2PLä+**ޝ]»öwY›©œ"… ãû[ŒÏI™@—í›Ö‰¢î®¢Þh—Ù˜´UM4t~Eöº2Ós‡ŒqîÌ Œ,ÈêåYŽÑ†üµÑ½¼tï ^¤›þ!SÆ—Nسõ²m‘Ôçê¦M›–¨‰Å:ZÛÚ2weš°B‚\ f „¶²Üqv_æË$ï\–#¿Ù=娏²s}«¨Lzî8±.›a¤_QÇ¿†Þ±º® CÃÃ@ RV6pÞyçoŸÈ®]»*žx≓ÉdµÊð¬³Îúýƒ>¸ðuGX!éW¼«Ú˜èDv¼°•‰wQ™nÛoA¨ÈF'øuÈÆ+ª×ù@ŠDÉ„;s3#o—(dº÷ðr@EQs—«ãß(&k)²b_4kW n••­Ì=餓óæÌ>üá‡û€R\š†Ê5Uå•Ëöu¹íÒ‹E}ø±aíø¨sª¸ùÇ-ðiy'|Y¡O3?ùÇ!³®?’wŒ…ýyå–E •r >4”ºP«m9ãŒ3ø¼GdêA€~ík_»öرcŸRVVVùÆ7¾ñäßûANtDß~Ŷʯ¬IªHµ¬¡.²S‰kQ=êLÐEÜUþdçC¤Ó„`šÌØ)špÏ^@‚ÑtvùÅä u ™//ïF l¸ÓÛv™}ÖŽpul¹·ÏGØùp*uEáUÑv‘8gíEXçw^ãÖ­[Gœt:šN¥!l3©ú4+ø²­GÓ[x¡H Ûä "u¶ ›Ò’ëƒ0íˆÄ·ÄF%ºórãÎGt\D0¯^7V"ð-*3éŒáÛ*ÒiDýBÀ"  @b8Ô¥pÊÉ'ÿ©¾¾>(ܧ:äú믿ä½÷Þ»RuA*!ĹôÒK_Z³fÍ1Àh;‚ˆËg¡Jœû›DÜE¾uÂ\¶íí‹–TT=D9î )gëÛ× u“s(š˜ %"°pg„º·ÚKd³Í<³B˜}¶œ½‹À¨ð&\™(ê-ÕÀõ§²+8,I¾½*B ëÇ=ÿüóTTTvôõõÍwÝ´ðFLºˆ°jÛÛ7i«ó!²QGáÙù‘¿~MóÛƒ´5õÃþ™ú˯ïï×u! %Ï>ûìýÙ¥ñCn sÇw,ÿÿø[t7[š3gÎ7lØð&àÅÈ"¢T~¢í²r•Xgëe‘l•pæŸM¢¶ª¼zѸDÇ¢«3ó<0Ú^$Œ„;s©—úâ vÙZéÞ›’@~ô;Ï-ŒŠw>ÒͶ÷lMÄ·*z.òÁNt‚Ýø÷ÈëëÒK/íœ^?£±·¯o¾ëRH§Ó`1ë¹—:O]—³.òß ê3Åšþt}™¦Æ¶)N*LýèÒsdǧšœ3°lF†‡¡¯¿\J¡®¶¶é¢‹.jÿ~Èä€ý«¿ú«Ï?ÿüóH$b*ãP(4tà 7ü{öæ@øz#È(üwt1|ÅÎO´]U&Ì¢6"1=ñ®³5™ˆ¨ÎƒÝD¨¯/’E(Ü¡N`4ÍÅìl]® ˆE0påÀ•c+zQYÁm‚îÍ!«WµS vS1ŸëçœOŸó‡æ¦¦Ï§tÔqGÂL »‚ ÍÌ£]æ§@~[’oS'†<›üôÆ–‡8Í…µåÛ@ _O˜ ß–°õÜ8%mXÙ•jDç‰ú󎋎ˆî˯¥ïÙ Î9°ÞÞ^ æÏ›÷ÁòåË»s§"¸×\sÍ^}õÕ›‰DʘBO=õÔmßùÎwvg‹ðË A )Öß…ÎN`ÊÊdúEöìm«Ä¾N4«=¿/Š‚û¯ãdmùñªöóÊewÔEü‘îÙÔ/EÅ»+©·ï=Lfª4ÛŽÍC'PxA)û&aë)³ÏþìC8{")Í6ûÌ׳ý°vü>‹J¸óuîßüÍ×wncÛÁÎãg¦R)ˆ8‡GsÝ…Ñ\FPò6º\nv¿ _m* aNª.Ò'ŠýEÉ•ã{áD@_¯Í‹LPôçÉ‚D" GŽ´“†²²HüÒKW¿UWWçæ:O5ȶmÛªî»ï¾5»wïþK×uùûIÐÐаçG?úÑO³·eÇ×AJK©„;¿¯‹¸ËžME8€úî¤Þç k«Š°ë¢qÊ|ÉŽ'BÙèzFÅ2«´Ø„åáE«è@ԗמßA4¾Îo^E—ùòe¿lÙ²OŸ}öößmÛ¶$í¦íd* áH8¿fŶ©ÂÔ¹/_¡€e› ®Üwc“[UoÒŸ.=I˜b”Ý´m Žwv@|(.u¡®®î£»îºË[?ð&? “ 6|úg?ûÙÚãÇ:èPFõ«_}fõêÕ½€¢AŠMÏO?Â]T.‹j‹¶ƒ wQ?‘uÙDmE>Šõ½T0.Œ¶/âîEU‘e•Ðæ÷E/¾Lè{Ûgk*¸ù¶º1ËŽ u² ƒÈ‡±Ï{î¹ç÷ï¾»ë²þþþSRÉ8"‘ükÛDé)~·µ¶T&¸¾õÂ2ö”â:ÿ´¦í˜õ'jkZoÜ•«à<3Û¶!‘HÀ±£Ç í¤! 'V®\ùúœ9sR€Bn²ã}&º?þøÉ?þñÿ²±±q•ã8U&mÛ¾âŠ+þõ‘Gù°´ÃD?BPe+½*UäY'ØEeºH9_&ÚµS uU½È^´-³aÑé9Ä'£ ‰«Å([/¤|½Hà«„½ŸVdË^Èj:&ÑD…-W ~Ù¤@<Ž<®¼òÊãçœsÎkoüç÷¤Ý4ŒŒŒd„;aD Hn[d£µ•øå#˼r>oœK—Q]´É ö¼|ðَܸ ÆÊÕ³eÞøu~D©1Fç”í#7v¶ždχÇÃàà ̘QÿÁƒnx 0·}Ò³sçÎÊM›6Ííµ×>ûÑG­ŠÇãsLÛBœÏ~ö³?þùç·–rŒò ¡˜¢ÎÔWÐÈ».Ú$êÎnÝçSeDã7©ÙóÇ(k#³CŠ {q*/fÙr‹)7ÞlŽ;0mçïƒ}fÇ%zx>dëÁS-_PxlüñˆöÁ Œ'góÈ#¿~ÓM7×ÞÞ~A"9CÃ!¨ª¬5ÌÁêå_@ÉÖƒ ¾0:,¬ÒFÓ^;¹Øgv@9ž‚6>Æ/ò#<§9A.™ ä¥ÈØÐÓÛÍMMvÓPVVÖwóÍ7=»dɩÀl“rï½÷^õÇ?þqM*•ªñÕgéÒ¥/ìØ±ã9."RH)þNüF’uå~Å»êY%êE"[VçÇFg+*×#¿Œ!B¥”òeþ…‘‰T^ ³6¬ –µ#PøR‹ïSÛ¿h¬²r"/²!Ë—/øò—¿üó'Ÿüñ§’©dõðÐD"(++JižÏ°ñ;†ú@§ |ê£ÿÙza=!ò%Å¢~lçDÖ_P!L¥àãƒaxx€,Y²ä•õë×ïL‘™ XõõõºµÙ YVò¼óÎûÙ¯~õ«ç 󷎯5‚L*]à§Nu={Û²}p—•«|èìùq¨&'ªr?ítuHøå YAËG¥yD6"q.‹jëET¯kÏ— \Ô†ŸD°íT"]w|^ûè£î~õÕ×_ûè£?]ç8i2Ð?ái!…ÂñžsH ·)#™maþº(MbUÐ*O>XÞ8c¯˜(è„4HŽÉï±(s÷©¤^ô@þñúøtww¥Nš9s×£ßùÎ Ñh/H¸W]uÕÛ·o?ǘ4ˆD"+V¬xvëÖ­/—zp‚äúyj"Pev:±®²m{û~nÈ$óË߀ 8™çíE>Tðº¿çJ{Q§É‹éç d:ô;ƒ-{¤z¸ÌCæG´-j'ó#²= Æ÷È#=7cÆô÷(¥L&a`` {GÕL.¸÷œyî‘©µ!v…u£>(ç¸>ù¾ó Š2ú›ÅŒÑâÊ,È?K äólÿ¢íÂ2’7.‹ˆÆï¯Þ²,…lhiiÖÖp] ee‘Þo¼áÿ^tÑE}Þ»ø˜˜‡{ÓM7µOŸ>ýÏ`@MMÍ[n¹åÑ­[·nšcÇ>¦âƒýŽT}‡j¿S>uíLü™ÚðÛü³ªpå Øn›÷%²c÷EíUýˆÚˆöem²Q6‚+Ê o9H êH5»ÍÖS® ðh4oËæ­{oß›U²uì˜øüxÑqðÇÂŽ‹÷Ç׉lùãEõ ŽýÚk¯=ÞÙÙùÝ|ðöõõÇ ®®,+¡O»ª/úTG­umdí3Û£ö:þûS·õ•:cP/;fYõ¶7q± µµ öïߎ“‚P(4tñÅ?ùè£ßÛ“5Ç®)Bmm-œ~úéï·´´\L)Þ¨.÷œ}öÙ›6lذéšk®éLA± ÓÅìǤLT®«¼H‰cþÙT°Ëêek½‹ÊTõ<²ãôûÚàw^ ~!1°H*!*Á:Á®úe}ÈD³ªŽ_e†g'š€ð~d“Ñd@d'¢`Rt÷w´ïß¿ÿ?õ“ŸüÁ“ãq ‹Å ΤÍÉS®^zQeÖÖ5i¤¿ yåʱHÆ`*Ðue–eëºÐ|ø46ÇIeÛβeËžÞ´iÓf@¦"îW\±{Û¶m=©Tªž­°m{pöìÙ;Ö®]û↠>ª®®–}ù!"F¦ ŠõwdêGe§›.²m*ØEe*ñí•óÓ‰€èXT>T¿^x×ÔâA²÷F™EÑlÑë³4vAÚûÙç·Ùcá×yçU´Í‹}Ó2SȺuë–>ýôÓÏ´, ¢Ñ(LŸ>ÊÊÊòY{Ù¶Ê;o[7~‘å¸teõ¹}ϼmÈvÛ¶add„ææfH§Ó`[ÖÈÒ¥K7nß¾}cyy9~PMQ¬E‹}÷øñãË F;êëëÿ¸jÕª-?øÁvÕ××;=F9Á VØtBžŸp˜D¦UâY%æùœwQtÝçëÙ}Õ]Xe¾eõ²1¨ž0]¦H°ÂÀ\´äße5¨0çý©úA=¶ƒ<ƒá¾©IÐ~íÚµ+6¿úê½ñx|>@4…Xm-TUW!$wѪ0 œÙÉ/SD‹Y{‘­È_þòüeÛ˜úÓö§ic46Õ˜ø²ìäÉË»ïëëƒ?ïßÇŽË^›`œ{î¹Oÿê•W~^__ëµOqV¯^}åûï¿ñÉ'Ÿüþ…^¸ëþûïo̾®ø„ ÅC/E?~êüFÝùm•PçŸuÂ]$¦UBÚ±/Õƒm+ó+›ê¸Q´ÍáÔ‰a•°ö#ÄuÂ_ÕFe/{‰t‘ø6ä2;*¨g·ù™¾ òío{áã?úÑïìêº Ù­¬¬„X,g”ˆK‘P¦ªzAÔY&®óê} e¥0fÆDÌ«Žß­î\åìH推CCCÐÜÜ ÍÍÍ022„ˆF£í«V®üç_mÞüZ¶)~PMq:::B###dþüùºœ œRwÖÿ]ÌoËúÖ•‰„¼LÜ«"îì¾LÌ›î›s?íuc‘KÁ6Š÷âÀ w¯L'–u¢@q„¾j_46ð±¯Ú6í*[ÓöB»_ÜT÷Àß¼­©éЗÒét€P8 555‹Å ‰ä.^Í[:R”"x–•©ÚË·i™Î·Ÿú ¶Þ/–eåÎu2™„¶¶68|ø0 @:˲ܺºiïÝxãÚ?þøã{AñC©…{Ð6~¢ðºr“g?x]êŒßˆº‰°õ§:¦¼mîÅA&ÜÌÒStÂ[%úMĹÉd4u2{Þ†·õ¶½èö[¯À÷÷÷[×\sÍ•ï¾ûîÍCÃçxQöH8 µµµPUUåååyË%ÊD¼2"n(nƒâb—ew2/ˆ¨žIgaËò^Tƒã`Åz:†øÐt?ííí¹õÙ)¥‡{/^üÒ÷¿ÿýŸ_vÙe}€ ‚L$¢ïV`TիļnÛ¤L%ÖƒFÝä©2|H¨ËÒtDýðõÂcEÑ^H´-3ñò…³Wç'¯‹Èë"ð¼O¿kÉ£pŸ@XážÛ¹0 o~Ÿ·W }Ó‰n,¢gÞŽ¯“µùà "ÒEeº72;>ºaÆEÏ?ÿüU---_H$'yÂÜ[Ý$Cyy9”••A$H$¡P(A6M?ñöUâZäÇïD@æ;ˆp× {I?®ë‚ëºà¤R0<2###Ç¡¿¿††† ™L‚ãŒ.bYÖÈÌ™3·®X±âÕo~ó›ï/[¶lpýnARâGôñß­A£Id]d/›èÒITv~¢ñ¦‚ÝᆰoÑñä•¡p/žp·AœܳH”—rßÏX@`cR&Ú— ó "Þ¯ÈWú:~ü¸õì³Ïžôì³Ï^xèСUñxüôt:]ÉÞ$ÆÝ죔Êïx÷諒PJsBž­¶m{(‰khhxëâ‹/þÍý÷ßÿáé§Ÿžòê‹2A¤øùŒ6‰º‹êtb]TæW¸›lãì—žh_Ýçë³Cá^dwo[ÙÖ¥ºø‰Èƒm¾LÔ—èXLëARnª‹7âã?ŽüÝßýÝÒ>øàÌŽŽŽ3†††Nv'æ8N•ëºQÙ)Ô¶í˲†lÛî///?RWW÷çÅ‹ïùò—¿üÁwÞy|¢ˆ ‚ " MÚ˜ˆye´YQ®K=Q¥£ðé6~/^•Õ‰|ÉìEãµïÅ‚¸‹"צ‘nÐ {¿ Ϻ2ѾŸh»ªÜ„b„Œ d#ñÛ·o¯Þµk×´¶¶¶ÚžžžÊ¡¡¡òT*ʾ¾ˆBˆ …Ò‘H$‹Åâ K–,é^µjUßYg•€ÑŸ^ñCAdòô³Z—£*²­‹ÈûIQQ]PêçbS?i2¢}ÑØ…{±à…;@¡xæËDuAÄ|PŸ&Ï~ÊDûªr•Ø.vîˆ&ljÈ1úàAAÆ £“RöaZ$uFiçëuÂÞo.bîW°›ŒSÔP¸ ]*ûGË@¶Lµ ÿ‡'Ú–Õ³ðåª?f¶N4vÖÖëCV'«÷Ì,™?G2¿ø ‚|Ò`¿‹¡nb#j£î¢rÙ³·­ò&ý˜ü BVïë5¡”ïcÇ$Z$Êu¢ÝD¼‹ìEÂWõ†QM&X?¼ˆW‰oÞŸÌN'öuoNS!®úcFAO:¦ß{ňÐö&B]dg™×EÛeö¦“]™l¿Ô¿† DÂ]Ý• u€B‘Ì—±¹Âl Œ—«‰hÝD‚µWM$x¿¢ êMĹ â‚ R\tÂÛDlú­u×m넽©¨— x]{L”™Œ¶V¸ë"Ø|½ßè»LxûÁ4F„,Ú¯²åûÕû‰ºcÎ9‚ ‚”“èx1Úðõ*Qob'Ùº_áM"æºr•M`# õ K•‘‰tÕ>€\ÈËf¹¢È>ßÞ/¦¹ï²¾d¤úù(E:Š~AäD¥"p¬"Ô$òΖëD½Jä›îö¥8¿yÚ sÜ‹ƒßu¾UQj™­.²­ºèT–’Ã×ÉÆ¨»èÕô‚W•¨Ø UAäD¡TB¯Xé A½itÜÄÎä?Ñòb—P´^¸ëÒeØ}àõ{Ñ‚¬oÖ—(ÍE&ôAb£³ Sá\LÍ_€ ‚ ' ¢ `1àx w¾Ì϶*ZoM×EóA)%(àÇŠŸˆ{Q/» T–û.Ò*ÑíùQõËd¢a2)懊G©ÊBAÉLQ„£ÿ õ"?û*q.+7í¦ãQÔó‚½8¨V•Ñ•ÉêeÛì¾Làˆß(*ï•«úUWUæÙØ1ZŽ ‚ G)£ô&¢ØD¸óûªh¼iTŸ·Õõå÷<ÛcŽ{qšã®ɲ;Hû‰–³ÑvUSá,šHðu²z•O¿iC‚ ‚ ßÁc‰ÄÀñ¢2•°7ë%Ñ,(ÞÇŽJ¸ËÄ®L¼{¨D´jU]Û^µÂ¨Óœ|¾?AD8FÛAdâ*MÛ5êÎïÈû­3í[5dÑEÜMS`tmDbŸ·—µ0› ˆ„·lb¡zÓªD¶_Ñ^ŠœwAA‚SÌt Qy?i.:•½ ²è|P¿¨wJŒé:î²ra/k#Šxó‘u•æûQ tѱ™ˆlÕ˜Líe`TAA‚Q,ÁXÌ”š ©4A¢á²ö¦i6AA‘>xÂÝ4‚®«W ~U„\U¯»˜Õ$ Çd•LÓjü ;Ï‚ ‚ä# Ëw1lÇš6£Û7Mg1íc‰Þ#ã€éÅ©¼8g1Y_ÕÏR‹ª4“œz•¨– c¿åÅ~CcJ ‚ ‚ø§”Â]էߺ ÑwY™It^%ÒýDâMƆŒ#A.N•ÙùI£‘áGÀÓÖä—ÞW±Áˆ;‚ ‚ø§ÔQb¿AmL…¾i4ݯ`Wµñ;Ù˜"B(¥è£Îª¼pS;Yꊮί­¬?mLý Y*‚ ‚ rJuŸè¼wU[¿bÜÔÖO_¦¾ðFLc„¸ûÉeW!KYÑ­þ¢J‘ÙÊ-'ÉÖÉi/õ›m"~òCA©Šß(p±ú«}HûXÊMü›ˆv?¾‘ÀFÜóÊd¶ Y½[S›REØÇ3ÒQuA) ã-"ý f“v&:ˆMÐ4?6ÊrŒº'¨p}?vA„¼ŸúbµAAdòSÌèúXm‚Fáýú2ñ;Ö‹lQ´‘b wQ¹ßȺ;¿¶*‚Špï‚ òɤâÝ´ßÕ\üŠö “b¤þŠ÷± î¹rU[?é2~ËLëƒÖ•ü‚ RZ&RM ÚÖpöe×ÉwN< ÷àø½sªWL½l%Õ‹"ºSvQæXVZ‘!È ÅÛø†EA©E1#êAÚ•:Õf,ý"ã„q'à?Ú]¬›•êBÒb mŒ#‚ ȉM±ìX¢ò:¿¼”…0âÝSýDÞMËù:Q¹(/ë_„iDÝTã AAJ_ê%"ƒ¶AÆ]Ä=ÏÎÄW‘Ú™´K|¼"è©GAÉÇd[ó=ˆbçÉ›¶S{Œ¶ þLêè¶îMÅŠœ«Pý c¼n¤äoNAA&jyI{?~0?ÁˆRe‚¤ÇÈìx[!/³µ3}ƒŒ‡`Çè:‚ ‚L]ÆCtNô®A}¢`Ÿ$YUÆ« o+²½Ð~}ÊPMŠMUjA™<Œ÷÷x1û+E„¾”~‘€°9îJS_AÇPBûR1YÆ ‚ Hñ™L‚´Ôè›Ö ¶IDAT¼ië»Ìo;&Â]Ú®„öãío²÷‹ ‚ ÈÄ1Q‚³TýŽ[ÊŠõâT¸ ýL@ÛR3™Ç† ‚ ÈÄ1™éXÇV”cCÑ^|r´â=Ïß$ó… ‚ òI¤Xâ¸h"{éŠã" ù@}O“i,‚ ‚Lm&“p-éXP¤/ë8ûb2•ÆŠ ‚ È'—)%tQ˜O.¦Œ bAA¢ƒBúÄæÿpˆ®k@IEND®B`‚pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/transparent.gif0000644000076500000240000000006112234375161032754 0ustar michaelstaff00000000000000GIF89a‘ÿÿÿÿÿÿ!ù,T;pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/0000755000076500000240000000000012642137501030433 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl0000644000076500000240000000653612520062551034224 0ustar michaelstaff00000000000000 The Pyramid Web Framework
pyramid

Welcome to ${project}, an application generated by
the Pyramid Web Framework.

pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/test_no_content.py_tmpl0000644000076500000240000000000012234375161033241 0ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/tests.py_tmpl0000644000076500000240000000061612234375161031213 0ustar michaelstaff00000000000000import unittest from pyramid import testing class ViewTests(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def test_my_view(self): from {{package}}.views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['project'], '{{project}}') pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/+package+/views.py_tmpl0000644000076500000240000000007312234375161031203 0ustar michaelstaff00000000000000def my_view(request): return {'project':'{{project}}'} pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/CHANGES.txt_tmpl0000644000076500000240000000003412234375161027561 0ustar michaelstaff000000000000000.0 --- - Initial version pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl0000644000076500000240000000146112234375161031000 0ustar michaelstaff00000000000000[app:main] use = egg:{{project}} pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = true pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_{{package_logger}}] level = DEBUG handlers = qualname = {{package}} [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 pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/MANIFEST.in_tmpl0000644000076500000240000000020612234375161027507 0ustar michaelstaff00000000000000include *.txt *.ini *.cfg *.rst recursive-include {{package}} *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl0000644000076500000240000000141212234375161030640 0ustar michaelstaff00000000000000[app:main] use = egg:{{project}} pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.debug_templates = false pyramid.default_locale_name = en [server:main] use = egg:pyramid#wsgiref host = 0.0.0.0 port = 6543 # Begin logging configuration [loggers] keys = root, {{package_logger}} [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_{{package_logger}}] level = WARN handlers = qualname = {{package}} [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 pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/README.txt_tmpl0000644000076500000240000000002312234375161027444 0ustar michaelstaff00000000000000{{project}} README pyramid-1.6/pyramid/tests/test_scaffolds/fixture_scaffold/setup.py_tmpl0000644000076500000240000000177012520062551027464 0ustar michaelstaff00000000000000import 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 = ['pyramid', 'pyramid_debugtoolbar'] setup(name='{{project}}', version='0.0', description='{{project}}', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pylons", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web pyramid pylons', packages=find_packages(), include_package_data=True, zip_safe=False, install_requires=requires, tests_require=requires, test_suite="{{package}}", entry_points = """\ [paste.app_factory] main = {{package}}:main """, ) pyramid-1.6/pyramid/tests/test_scaffolds/test_copydir.py0000644000076500000240000004246412520062551024456 0ustar michaelstaff00000000000000# -*- coding: utf-8 -*- import unittest import os import pkg_resources class Test_copy_dir(unittest.TestCase): def setUp(self): import tempfile from pyramid.compat import NativeIO self.dirname = tempfile.mkdtemp() self.out = NativeIO() self.fixturetuple = ('pyramid.tests.test_scaffolds', 'fixture_scaffold') def tearDown(self): import shutil shutil.rmtree(self.dirname, ignore_errors=True) self.out.close() def _callFUT(self, *arg, **kw): kw['out_'] = self.out from pyramid.scaffolds.copydir import copy_dir return copy_dir(*arg, **kw) def test_copy_source_as_pkg_resource(self): vars = {'package':'mypackage'} self._callFUT(self.fixturetuple, self.dirname, vars, 1, False, template_renderer=dummy_template_renderer) result = self.out.getvalue() self.assertTrue('Creating' in result) self.assertTrue( 'Copying fixture_scaffold/+package+/__init__.py_tmpl to' in result) source = pkg_resources.resource_filename( 'pyramid.tests.test_scaffolds', 'fixture_scaffold/+package+/__init__.py_tmpl') target = os.path.join(self.dirname, 'mypackage', '__init__.py') with open(target, 'r') as f: tcontent = f.read() with open(source, 'r') as f: scontent = f.read() self.assertEqual(scontent, tcontent) def test_copy_source_as_dirname(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) self._callFUT(source, self.dirname, vars, 1, False, template_renderer=dummy_template_renderer) result = self.out.getvalue() self.assertTrue('Creating' in result) self.assertTrue('Copying __init__.py_tmpl to' in result) source = pkg_resources.resource_filename( 'pyramid.tests.test_scaffolds', 'fixture_scaffold/+package+/__init__.py_tmpl') target = os.path.join(self.dirname, 'mypackage', '__init__.py') with open(target, 'r') as f: tcontent = f.read() with open(source, 'r') as f: scontent = f.read() self.assertEqual(scontent, tcontent) def test_content_is_same_message(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) self._callFUT(source, self.dirname, vars, 2, False, template_renderer=dummy_template_renderer) self._callFUT(source, self.dirname, vars, 2, False, template_renderer=dummy_template_renderer) result = self.out.getvalue() self.assertTrue('%s already exists (same content)' % \ os.path.join(self.dirname, 'mypackage', '__init__.py') in result) def test_direxists_message(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) # if not os.path.exists(self.dirname): # os.mkdir(self.dirname) self._callFUT(source, self.dirname, vars, 2, False, template_renderer=dummy_template_renderer) result = self.out.getvalue() self.assertTrue('Directory %s exists' % self.dirname in result, result) def test_overwrite_false(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) self._callFUT(source, self.dirname, vars, 1, False, overwrite=False, template_renderer=dummy_template_renderer) # toplevel file toplevel = os.path.join(self.dirname, 'mypackage', '__init__.py') with open(toplevel, 'w') as f: f.write('These are the words you are looking for.') # sub directory file sub = os.path.join(self.dirname, 'mypackage', 'templates', 'mytemplate.pt') with open(sub, 'w') as f: f.write('These are the words you are looking for.') self._callFUT(source, self.dirname, vars, 1, False, overwrite=False, template_renderer=dummy_template_renderer) with open(toplevel, 'r') as f: tcontent = f.read() self.assertEqual('These are the words you are looking for.', tcontent) with open(sub, 'r') as f: tcontent = f.read() self.assertEqual('These are the words you are looking for.', tcontent) def test_overwrite_true(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) self._callFUT(source, self.dirname, vars, 1, False, overwrite=True, template_renderer=dummy_template_renderer) # toplevel file toplevel = os.path.join(self.dirname, 'mypackage', '__init__.py') with open(toplevel, 'w') as f: f.write('These are not the words you are looking for.') # sub directory file sub = os.path.join(self.dirname, 'mypackage', 'templates', 'mytemplate.pt') with open(sub, 'w') as f: f.write('These are not the words you are looking for.') self._callFUT(source, self.dirname, vars, 1, False, overwrite=True, template_renderer=dummy_template_renderer) with open(toplevel, 'r') as f: tcontent = f.read() self.assertNotEqual('These are not the words you are looking for.', tcontent) with open(sub, 'r') as f: tcontent = f.read() self.assertNotEqual('These are not the words you are looking for.', tcontent) def test_detect_SkipTemplate(self): vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) def dummy_template_renderer(*args, **kwargs): from pyramid.scaffolds.copydir import SkipTemplate raise SkipTemplate self._callFUT(source, self.dirname, vars, 1, False, template_renderer=dummy_template_renderer) def test_query_interactive(self): from pyramid.scaffolds import copydir vars = {'package':'mypackage'} source = pkg_resources.resource_filename(*self.fixturetuple) self._callFUT(source, self.dirname, vars, 1, False, overwrite=False, template_renderer=dummy_template_renderer) target = os.path.join(self.dirname, 'mypackage', '__init__.py') with open(target, 'w') as f: f.write('These are not the words you are looking for.') # We need query_interactive to return False in order to force # execution of a branch original_code_object = copydir.query_interactive copydir.query_interactive = lambda *args, **kwargs: False self._callFUT(source, self.dirname, vars, 1, False, interactive=True, overwrite=False, template_renderer=dummy_template_renderer) copydir.query_interactive = original_code_object class Test_raise_SkipTemplate(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.scaffolds.copydir import skip_template return skip_template(*arg, **kw) def test_raise_SkipTemplate(self): from pyramid.scaffolds.copydir import SkipTemplate self.assertRaises(SkipTemplate, self._callFUT, True, "exc-message") class Test_makedirs(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.scaffolds.copydir import makedirs return makedirs(*arg, **kw) def test_makedirs_parent_dir(self): import shutil import tempfile tmpdir = tempfile.mkdtemp() target = os.path.join(tmpdir, 'nonexistent_subdir') self._callFUT(target, 2, None) shutil.rmtree(tmpdir) def test_makedirs_no_parent_dir(self): import shutil import tempfile tmpdir = tempfile.mkdtemp() target = os.path.join(tmpdir, 'nonexistent_subdir', 'non2') self._callFUT(target, 2, None) shutil.rmtree(tmpdir) class Test_support_functions(unittest.TestCase): def _call_html_quote(self, *arg, **kw): from pyramid.scaffolds.copydir import html_quote return html_quote(*arg, **kw) def _call_url_quote(self, *arg, **kw): from pyramid.scaffolds.copydir import url_quote return url_quote(*arg, **kw) def _call_test(self, *arg, **kw): from pyramid.scaffolds.copydir import test return test(*arg, **kw) def test_html_quote(self): import string s = None self.assertEqual(self._call_html_quote(s), '') s = string.ascii_letters self.assertEqual(self._call_html_quote(s), s) s = "Λεμεσός" self.assertEqual(self._call_url_quote(s), "%CE%9B%CE%B5%CE%BC%CE%B5%CF%83%CF%8C%CF%82") def test_url_quote(self): import string s = None self.assertEqual(self._call_url_quote(s), '') s = string.ascii_letters self.assertEqual(self._call_url_quote(s), s) s = "Λεμεσός" self.assertEqual(self._call_url_quote(s), "%CE%9B%CE%B5%CE%BC%CE%B5%CF%83%CF%8C%CF%82") def test_test(self): conf = True true_cond = "faked" self.assertEqual(self._call_test( conf, true_cond, false_cond=None), "faked") conf = False self.assertEqual(self._call_test( conf, true_cond, false_cond="alsofaked"), "alsofaked") class Test_should_skip_file(unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.scaffolds.copydir import should_skip_file return should_skip_file(*arg, **kw) def test_should_skip_dot_hidden_file(self): self.assertEqual( self._callFUT('.a_filename'), 'Skipping hidden file %(filename)s') def test_should_skip_backup_file(self): for name in ('a_filename~', 'a_filename.bak'): self.assertEqual( self._callFUT(name), 'Skipping backup file %(filename)s') def test_should_skip_bytecompiled_file(self): for name in ('afilename.pyc', 'afilename.pyo'): extension = os.path.splitext(name)[1] self.assertEqual( self._callFUT(name), 'Skipping %s file ' % extension + '%(filename)s') def test_should_skip_jython_class_file(self): self.assertEqual( self._callFUT('afilename$py.class'), 'Skipping $py.class file %(filename)s') def test_should_skip_version_control_directory(self): for name in ('CVS', '_darcs'): self.assertEqual( self._callFUT(name), 'Skipping version control directory %(filename)s') def test_valid_file_is_not_skipped(self): self.assertEqual( self._callFUT('a_filename'), None) class RawInputMockObject( object ): count = 0 def __init__( self, fake_input ): self.input= fake_input self.count = 0 def __call__( self, prompt ): # Don't cycle endlessly. self.count += 1 if self.count > 1: return 'y' else: return self.input class Test_query_interactive(unittest.TestCase): def setUp(self): import tempfile from pyramid.compat import NativeIO self.dirname = tempfile.mkdtemp() self.out = NativeIO() self.fixturetuple = ('pyramid.tests.test_scaffolds', 'fixture_scaffold') self.src_content = """\ These are not the droids that you are looking for.""" self.dest_content = """\ These are the droids for whom you are looking; now you have found them.""" self.src_fn = os.path.join(self.dirname, 'mypackage', '__init__.py') self.dest_fn = os.path.join(self.dirname, 'mypackage', '__init__.py') # query_interactive is only normally executed when the destination # is discovered to be already occupied by existing files, so ... # create the required occupancy. from pyramid.scaffolds.copydir import copy_dir copy_dir(self.fixturetuple, self.dirname, {'package':'mypackage'}, 0, False, template_renderer=dummy_template_renderer) def tearDown(self): import shutil shutil.rmtree(self.dirname, ignore_errors=True) self.out.close() def _callFUT(self, *arg, **kw): from pyramid.scaffolds.copydir import query_interactive return query_interactive(*arg, **kw) def test_query_interactive_0y(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("y") self._callFUT(self.src_fn, self.dest_fn, self.src_content, self.dest_content, simulate=False, out_=self.out) self.assertTrue("Replace" in self.out.getvalue()) def test_query_interactive_1n(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("n") self._callFUT(self.src_fn, self.dest_fn, self.src_content, '\n'.join(self.dest_content.split('\n')[:-1]), simulate=False, out_=self.out) self.assertTrue("Replace" in self.out.getvalue()) def test_query_interactive_2b(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("b") with open(os.path.join( self.dirname, 'mypackage', '__init__.py.bak'), 'w') as fp: fp.write("") fp.close() self._callFUT(self.src_fn, self.dest_fn, self.dest_content, self.src_content, simulate=False, out_=self.out) self.assertTrue("Backing up" in self.out.getvalue()) def test_query_interactive_3d(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("d") self._callFUT(self.src_fn, self.dest_fn, self.dest_content, self.src_content, simulate=False, out_=self.out) output = self.out.getvalue() # The useful text in self.out gets wiped out on the second # call to raw_input, otherwise the test could be made # more usefully precise... # print("3d", output) # self.assertTrue("@@" in output, output) self.assertTrue("Replace" in output) def test_query_interactive_4dc(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("dc") self._callFUT(self.src_fn, self.dest_fn, self.dest_content, self.src_content, simulate=False, out_=self.out) output = self.out.getvalue() # The useful text in self.out gets wiped out on the second # call to raw_input, otherwise, the test could be made # more usefully precise... # print("4dc", output) # self.assertTrue("***" in output, output) self.assertTrue("Replace" in output) def test_query_interactive_5allbad(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("all z") self._callFUT(self.src_fn, self.dest_fn, self.src_content, self.dest_content, simulate=False, out_=self.out) output = self.out.getvalue() # The useful text in self.out gets wiped out on the second # call to raw_input, otherwise the test could be made # more usefully precise... # print("5allbad", output) # self.assertTrue("Responses" in output, output) self.assertTrue("Replace" in output) def test_query_interactive_6all(self): from pyramid.scaffolds import copydir copydir.input_ = RawInputMockObject("all b") self._callFUT(self.src_fn, self.dest_fn, self.src_content, self.dest_content, simulate=False, out_=self.out) output = self.out.getvalue() # The useful text in self.out gets wiped out on the second # call to raw_input, otherwise the test could be made # more usefully precise... # print("6all", output) # self.assertTrue("Responses" in output, output) self.assertTrue("Replace" in output) def dummy_template_renderer(content, v, filename=None): return content pyramid-1.6/pyramid/tests/test_scaffolds/test_init.py0000644000076500000240000000123312575217552023752 0ustar michaelstaff00000000000000import unittest class TestPyramidTemplate(unittest.TestCase): def _makeOne(self): from pyramid.scaffolds import PyramidTemplate return PyramidTemplate('name') def test_pre(self): inst = self._makeOne() vars = {'package':'one'} inst.pre('command', 'output dir', vars) self.assertTrue(vars['random_string']) self.assertEqual(vars['package_logger'], 'one') def test_pre_root(self): inst = self._makeOne() vars = {'package':'root'} inst.pre('command', 'output dir', vars) self.assertTrue(vars['random_string']) self.assertEqual(vars['package_logger'], 'app') pyramid-1.6/pyramid/tests/test_scaffolds/test_template.py0000644000076500000240000001276112520062551024615 0ustar michaelstaff00000000000000import unittest from pyramid.compat import bytes_ class TestTemplate(unittest.TestCase): def _makeOne(self, name='whatever'): from pyramid.scaffolds.template import Template return Template(name) def test_render_template_success(self): inst = self._makeOne() result = inst.render_template('{{a}} {{b}}', {'a':'1', 'b':'2'}) self.assertEqual(result, bytes_('1 2')) def test_render_template_expr_failure(self): inst = self._makeOne() self.assertRaises(AttributeError, inst.render_template, '{{a.foo}}', {'a':'1', 'b':'2'}) def test_render_template_expr_success(self): inst = self._makeOne() result = inst.render_template('{{a.lower()}}', {'a':'A'}) self.assertEqual(result, b'a') def test_render_template_expr_success_via_pipe(self): inst = self._makeOne() result = inst.render_template('{{b|c|a.lower()}}', {'a':'A'}) self.assertEqual(result, b'a') def test_render_template_expr_success_via_pipe2(self): inst = self._makeOne() result = inst.render_template('{{b|a.lower()|c}}', {'a':'A'}) self.assertEqual(result, b'a') def test_render_template_expr_value_is_None(self): inst = self._makeOne() result = inst.render_template('{{a}}', {'a':None}) self.assertEqual(result, b'') def test_render_template_with_escaped_double_braces(self): inst = self._makeOne() result = inst.render_template('{{a}} {{b}} \{\{a\}\} \{\{c\}\}', {'a':'1', 'b':'2'}) self.assertEqual(result, bytes_('1 2 {{a}} {{c}}')) def test_render_template_with_breaking_escaped_braces(self): inst = self._makeOne() result = inst.render_template('{{a}} {{b}} \{\{a\} \{b\}\}', {'a':'1', 'b':'2'}) self.assertEqual(result, bytes_('1 2 \{\{a\} \{b\}\}')) def test_render_template_with_escaped_single_braces(self): inst = self._makeOne() result = inst.render_template('{{a}} {{b}} \{a\} \{b', {'a':'1', 'b':'2'}) self.assertEqual(result, bytes_('1 2 \{a\} \{b')) def test_module_dir(self): import sys import pkg_resources package = sys.modules['pyramid.scaffolds.template'] path = pkg_resources.resource_filename(package.__name__, '') inst = self._makeOne() result = inst.module_dir() self.assertEqual(result, path) def test_template_dir__template_dir_is_None(self): inst = self._makeOne() self.assertRaises(AssertionError, inst.template_dir) def test_template_dir__template_dir_is_tuple(self): inst = self._makeOne() inst._template_dir = ('a', 'b') self.assertEqual(inst.template_dir(), ('a', 'b')) def test_template_dir__template_dir_is_not_None(self): import os import sys import pkg_resources package = sys.modules['pyramid.scaffolds.template'] path = pkg_resources.resource_filename(package.__name__, '') inst = self._makeOne() inst._template_dir ='foo' result = inst.template_dir() self.assertEqual(result, os.path.join(path, 'foo')) def test_write_files_path_exists(self): import os import sys import pkg_resources package = sys.modules['pyramid.scaffolds.template'] path = pkg_resources.resource_filename(package.__name__, '') inst = self._makeOne() inst._template_dir = 'foo' inst.exists = lambda *arg: True copydir = DummyCopydir() inst.copydir = copydir command = DummyCommand() inst.write_files(command, 'output dir', {'a':1}) self.assertEqual(copydir.template_dir, os.path.join(path, 'foo')) self.assertEqual(copydir.output_dir, 'output dir') self.assertEqual(copydir.vars, {'a':1}) self.assertEqual(copydir.kw, {'template_renderer':inst.render_template, 'indent':1, 'verbosity':1, 'simulate':False, 'overwrite':False, 'interactive':False, }) def test_write_files_path_missing(self): L = [] inst = self._makeOne() inst._template_dir = 'foo' inst.exists = lambda *arg: False inst.out = lambda *arg: None inst.makedirs = lambda dir: L.append(dir) copydir = DummyCopydir() inst.copydir = copydir command = DummyCommand() inst.write_files(command, 'output dir', {'a':1}) self.assertEqual(L, ['output dir']) def test_run(self): L = [] inst = self._makeOne() inst._template_dir = 'foo' inst.exists = lambda *arg: False inst.out = lambda *arg: None inst.makedirs = lambda dir: L.append(dir) copydir = DummyCopydir() inst.copydir = copydir command = DummyCommand() inst.run(command, 'output dir', {'a':1}) self.assertEqual(L, ['output dir']) def test_check_vars(self): inst = self._makeOne() self.assertRaises(RuntimeError, inst.check_vars, 'one', 'two') class DummyCopydir(object): def copy_dir(self, template_dir, output_dir, vars, **kw): self.template_dir = template_dir self.output_dir = output_dir self.vars = vars self.kw = kw class DummyOptions(object): simulate = False overwrite = False interactive = False class DummyCommand(object): options = DummyOptions() verbosity = 1 pyramid-1.6/pyramid/tests/test_scripting.py0000644000076500000240000001717112524266531022011 0ustar michaelstaff00000000000000import unittest class Test_get_root(unittest.TestCase): def _callFUT(self, app, request=None): from pyramid.scripting import get_root return get_root(app, request) def _makeRegistry(self): return DummyRegistry([DummyFactory]) def test_it_norequest(self): registry = self._makeRegistry() app = DummyApp(registry=registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, app.registry) self.assertEqual(len(app.threadlocal_manager.popped), 0) closer() self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_withrequest(self): registry = self._makeRegistry() app = DummyApp(registry=registry) request = DummyRequest({}) root, closer = self._callFUT(app, request) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'], request) self.assertEqual(len(app.threadlocal_manager.popped), 0) closer() self.assertEqual(len(app.threadlocal_manager.popped), 1) def test_it_requestfactory_overridden(self): registry = self._makeRegistry() app = DummyApp(registry=registry) root, closer = self._callFUT(app) self.assertEqual(len(app.threadlocal_manager.pushed), 1) pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['request'].environ['path'], '/') class Test_prepare(unittest.TestCase): def _callFUT(self, request=None, registry=None): from pyramid.scripting import prepare return prepare(request, registry) def _makeRegistry(self, L=None): if L is None: L = [None, DummyFactory] return DummyRegistry(L) def setUp(self): from pyramid.threadlocal import manager self.manager = manager self.default = manager.get() def test_it_no_valid_apps(self): from pyramid.exceptions import ConfigurationError self.assertRaises(ConfigurationError, self._callFUT) def test_it_norequest(self): registry = self._makeRegistry([DummyFactory, None, DummyFactory]) info = self._callFUT(registry=registry) root, closer, request = info['root'], info['closer'], info['request'] pushed = self.manager.get() self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (pushed['request'],)) closer() self.assertEqual(self.default, self.manager.get()) self.assertEqual(request.context, root) def test_it_withrequest_hasregistry(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() info = self._callFUT(request=request) root, closer, request = info['root'], info['closer'], info['request'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() self.assertEqual(self.default, self.manager.get()) self.assertEqual(request.context, root) self.assertEqual(request.registry, registry) def test_it_withrequest_noregistry(self): request = DummyRequest({}) registry = self._makeRegistry() info = self._callFUT(request=request, registry=registry) root, closer, request = info['root'], info['closer'], info['request'] closer() self.assertEqual(request.context, root) # should be set by prepare self.assertEqual(request.registry, registry) def test_it_with_request_and_registry(self): request = DummyRequest({}) registry = request.registry = self._makeRegistry() info = self._callFUT(request=request, registry=registry) root, closer, root = info['root'], info['closer'], info['root'] pushed = self.manager.get() self.assertEqual(pushed['request'], request) self.assertEqual(pushed['registry'], registry) self.assertEqual(pushed['request'].registry, registry) self.assertEqual(root.a, (request,)) closer() self.assertEqual(self.default, self.manager.get()) self.assertEqual(request.context, root) def test_it_with_request_context_already_set(self): request = DummyRequest({}) context = Dummy() request.context = context registry = request.registry = self._makeRegistry() info = self._callFUT(request=request, registry=registry) root, closer, root = info['root'], info['closer'], info['root'] closer() self.assertEqual(request.context, context) def test_it_with_extensions(self): from pyramid.util import InstancePropertyHelper exts = DummyExtensions() ext_method = lambda r: 'bar' name, fn = InstancePropertyHelper.make_property(ext_method, 'foo') exts.descriptors[name] = fn request = DummyRequest({}) registry = request.registry = self._makeRegistry([exts, DummyFactory]) info = self._callFUT(request=request, registry=registry) self.assertEqual(request.foo, 'bar') root, closer = info['root'], info['closer'] closer() class Test__make_request(unittest.TestCase): def _callFUT(self, path='/', registry=None): from pyramid.scripting import _make_request return _make_request(path, registry) def _makeRegistry(self): return DummyRegistry([DummyFactory]) def test_it_with_registry(self): registry = self._makeRegistry() request = self._callFUT('/', registry) self.assertEqual(request.environ['path'], '/') self.assertEqual(request.registry, registry) def test_it_with_no_registry(self): from pyramid.config import global_registries registry = self._makeRegistry() global_registries.add(registry) try: request = self._callFUT('/hello') self.assertEqual(request.environ['path'], '/hello') self.assertEqual(request.registry, registry) finally: global_registries.empty() class Dummy: pass dummy_root = Dummy() class DummyFactory(object): @classmethod def blank(cls, path): req = DummyRequest({'path': path}) return req def __init__(self, *a, **kw): self.a = a self.kw = kw class DummyRegistry(object): def __init__(self, utilities): self.utilities = utilities def queryUtility(self, iface, default=None): # pragma: no cover if self.utilities: return self.utilities.pop(0) return default class DummyApp: def __init__(self, registry=None): self.threadlocal_manager = DummyThreadLocalManager() if registry: self.registry = registry def root_factory(self, environ): return dummy_root class DummyThreadLocalManager: def __init__(self): self.pushed = [] self.popped = [] def push(self, item): self.pushed.append(item) def pop(self): self.popped.append(True) class DummyRequest(object): matchdict = None matched_route = None def __init__(self, environ): self.environ = environ class DummyExtensions: def __init__(self): self.descriptors = {} self.methods = {} pyramid-1.6/pyramid/tests/test_scripts/0000755000076500000240000000000012642137501021110 5ustar michaelstaff00000000000000pyramid-1.6/pyramid/tests/test_scripts/__init__.py0000644000076500000240000000001212234375161023215 0ustar michaelstaff00000000000000# package pyramid-1.6/pyramid/tests/test_scripts/dummy.py0000644000076500000240000001015712621241570022620 0ustar michaelstaff00000000000000class DummyTweens(object): def __init__(self, implicit, explicit): self._implicit = implicit self.explicit = explicit self.name_to_alias = {} def implicit(self): return self._implicit class Dummy: pass dummy_root = Dummy() class DummyRegistry(object): settings = {} def queryUtility(self, iface, default=None, name=''): return default dummy_registry = DummyRegistry() class DummyShell(object): env = {} help = '' called = False def __call__(self, env, help): self.env = env self.help = help self.called = True class DummyInteractor: def __call__(self, banner, local): self.banner = banner self.local = local class DummyApp: def __init__(self): self.registry = dummy_registry class DummyMapper(object): def __init__(self, *routes): self.routes = routes def get_routes(self, include_static=False): return self.routes class DummyRoute(object): def __init__(self, name, pattern, factory=None, matchdict=None, predicate=None): self.name = name self.path = pattern self.pattern = pattern self.factory = factory self.matchdict = matchdict self.predicates = [] if predicate is not None: self.predicates = [predicate] def match(self, route): return self.matchdict class DummyRequest: application_url = 'http://example.com:5432' script_name = '' def __init__(self, environ): self.environ = environ self.matchdict = {} class DummyView(object): def __init__(self, **attrs): self.__request_attrs__ = attrs from zope.interface import implementer from pyramid.interfaces import IMultiView @implementer(IMultiView) class DummyMultiView(object): def __init__(self, *views, **attrs): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs class DummyConfigParser(object): def __init__(self, result): self.result = result def read(self, filename): self.filename = filename def items(self, section): self.section = section if self.result is None: from pyramid.compat import configparser raise configparser.NoSectionError(section) return self.result class DummyConfigParserFactory(object): items = None def __call__(self): self.parser = DummyConfigParser(self.items) return self.parser class DummyCloser(object): def __call__(self): self.called = True class DummyBootstrap(object): def __init__(self, app=None, registry=None, request=None, root=None, root_factory=None, closer=None): self.app = app or DummyApp() if registry is None: registry = DummyRegistry() self.registry = registry if request is None: request = DummyRequest({}) self.request = request if root is None: root = Dummy() self.root = root if root_factory is None: root_factory = Dummy() self.root_factory = root_factory if closer is None: closer = DummyCloser() self.closer = closer def __call__(self, *a, **kw): self.a = a self.kw = kw registry = kw.get('registry', self.registry) request = kw.get('request', self.request) request.registry = registry return { 'app': self.app, 'registry': registry, 'request': request, 'root': self.root, 'root_factory': self.root_factory, 'closer': self.closer, } class DummyEntryPoint(object): def __init__(self, name, module): self.name = name self.module = module def load(self): return self.module class DummyPkgResources(object): def __init__(self, entry_point_values): self.entry_points = [] for name, module in entry_point_values.items(): self.entry_points.append(DummyEntryPoint(name, module)) def iter_entry_points(self, name): return self.entry_points pyramid-1.6/pyramid/tests/test_scripts/pystartup.txt0000644000076500000240000000021212524266531023724 0ustar michaelstaff00000000000000# this file has a .txt extension to avoid coverage reports # since it is not imported but rather the contents are read and exec'd foo = 1 pyramid-1.6/pyramid/tests/test_scripts/test_common.py0000644000076500000240000000245012517346416024022 0ustar michaelstaff00000000000000import os import unittest class Test_logging_file_config(unittest.TestCase): def _callFUT(self, config_file): from pyramid.scripts.common import logging_file_config dummy_cp = DummyConfigParserModule return logging_file_config(config_file, self.fileConfig, dummy_cp) def test_it(self): config_file, dict = self._callFUT('/abc') # use of os.path.abspath here is a sop to Windows self.assertEqual(config_file, os.path.abspath('/abc')) self.assertEqual(dict['__file__'], os.path.abspath('/abc')) self.assertEqual(dict['here'], os.path.abspath('/')) def fileConfig(self, config_file, dict): return config_file, dict class TestParseVars(unittest.TestCase): def test_parse_vars_good(self): from pyramid.scripts.common import parse_vars vars = ['a=1', 'b=2'] result = parse_vars(vars) self.assertEqual(result, {'a': '1', 'b': '2'}) def test_parse_vars_bad(self): from pyramid.scripts.common import parse_vars vars = ['a'] self.assertRaises(ValueError, parse_vars, vars) class DummyConfigParser(object): def read(self, x): pass def has_section(self, name): return True class DummyConfigParserModule(object): ConfigParser = DummyConfigParser pyramid-1.6/pyramid/tests/test_scripts/test_pcreate.py0000644000076500000240000002477212575217552024172 0ustar michaelstaff00000000000000import unittest class TestPCreateCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO self.out_ = NativeIO() def out(self, msg): self.out_.write(msg) def _getTargetClass(self): from pyramid.scripts.pcreate import PCreateCommand return PCreateCommand def _makeOne(self, *args, **kw): effargs = ['pcreate'] effargs.extend(args) tgt_class = kw.pop('target_class', self._getTargetClass()) cmd = tgt_class(effargs, **kw) cmd.out = self.out return cmd def test_run_show_scaffolds_exist(self): cmd = self._makeOne('-l') result = cmd.run() 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 = [] result = cmd.run() self.assertEqual(result, 0) out = self.out_.getvalue() self.assertTrue(out.startswith('No scaffolds available')) def test_run_no_scaffold_no_args(self): cmd = self._makeOne(quiet=True) result = cmd.run() self.assertEqual(result, 2) def test_run_no_scaffold_name(self): cmd = self._makeOne('dummy') result = cmd.run() self.assertEqual(result, 2) out = self.out_.getvalue() self.assertTrue(out.startswith( 'You must provide at least one scaffold name')) def test_no_project_name(self): cmd = self._makeOne('-s', 'dummy') result = cmd.run() self.assertEqual(result, 2) out = self.out_.getvalue() self.assertTrue(out.startswith('You must provide a project name')) def test_unknown_scaffold_name(self): cmd = self._makeOne('-s', 'dummyXX', 'distro') result = cmd.run() self.assertEqual(result, 2) out = self.out_.getvalue() self.assertTrue(out.startswith('Unavailable scaffolds')) def test_known_scaffold_single_rendered(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_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( 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_multiple_rendered(self): import os cmd = self._makeOne('-s', 'dummy1', '-s', 'dummy2', 'Distro') 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( scaffold1.output_dir, os.path.normpath(os.path.join(os.getcwd(), 'Distro')) ) self.assertEqual( scaffold1.vars, {'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', '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( scaffold.output_dir, os.path.normpath(os.path.join(os.getcwd(), '/tmp/foo/Distro')) ) self.assertEqual( scaffold.vars, {'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'}) def test_confirm_override_conflicting_name(self): from pyramid.scripts.pcreate import PCreateCommand class YahInputPCreateCommand(PCreateCommand): def confirm_bad_name(self, pkg_name): return True cmd = self._makeOne('-s', 'dummy', 'Unittest', target_class=YahInputPCreateCommand) 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': 'Unittest', 'egg': 'Unittest', 'package': 'unittest', 'pyramid_version': '0.10.1dev', 'pyramid_docs_branch': 'master'}) def test_force_override_conflicting_name(self): cmd = self._makeOne('-s', 'dummy', 'Unittest', '--ignore-conflicting-name') 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': 'Unittest', 'egg': 'Unittest', 'package': 'unittest', 'pyramid_version': '0.10.1dev', 'pyramid_docs_branch': 'master'}) def test_force_override_site_name(self): from pyramid.scripts.pcreate import PCreateCommand class NayInputPCreateCommand(PCreateCommand): def confirm_bad_name(self, pkg_name): return False cmd = self._makeOne('-s', 'dummy', 'Site', target_class=NayInputPCreateCommand) scaffold = DummyScaffold('dummy') cmd.scaffolds = [scaffold] cmd.pyramid_dist = DummyDist("0.10.1dev") result = cmd.run() self.assertEqual(result, 2) class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pcreate import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['pcreate']) self.assertEqual(result, 2) class DummyScaffold(object): def __init__(self, name): self.name = name def run(self, command, output_dir, vars): self.command = command self.output_dir = output_dir self.vars = vars class DummyDist(object): def __init__(self, version): self.version = version pyramid-1.6/pyramid/tests/test_scripts/test_pdistreport.py0000644000076500000240000000372712520062551025106 0ustar michaelstaff00000000000000import unittest class TestPDistReportCommand(unittest.TestCase): def _callFUT(self, **kw): argv = [] from pyramid.scripts.pdistreport import main return main(argv, **kw) def test_no_dists(self): def platform(): return 'myplatform' pkg_resources = DummyPkgResources() L = [] def out(*args): L.extend(args) result = self._callFUT(pkg_resources=pkg_resources, platform=platform, out=out) self.assertEqual(result, None) self.assertEqual( L, ['Pyramid version:', '1', 'Platform:', 'myplatform', 'Packages:'] ) def test_with_dists(self): def platform(): return 'myplatform' working_set = (DummyDistribution('abc'), DummyDistribution('def')) pkg_resources = DummyPkgResources(working_set) L = [] def out(*args): L.extend(args) result = self._callFUT(pkg_resources=pkg_resources, platform=platform, out=out) self.assertEqual(result, None) self.assertEqual( L, ['Pyramid version:', '1', 'Platform:', 'myplatform', 'Packages:', ' ', 'abc', '1', ' ', '/projects/abc', ' ', 'def', '1', ' ', '/projects/def'] ) class DummyPkgResources(object): def __init__(self, working_set=()): self.working_set = working_set def get_distribution(self, name): return Version('1') class Version(object): def __init__(self, version): self.version = version class DummyDistribution(object): def __init__(self, name): self.project_name = name self.version = '1' self.location = '/projects/%s' % name pyramid-1.6/pyramid/tests/test_scripts/test_prequest.py0000644000076500000240000002214112524266531024376 0ustar michaelstaff00000000000000import unittest class TestPRequestCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.prequest import PRequestCommand return PRequestCommand def _makeOne(self, argv, headers=None): cmd = self._getTargetClass()(argv) cmd.get_app = self.get_app self._headers = headers or [] self._out = [] cmd.out = self.out return cmd def get_app(self, spec, app_name=None, options=None): self._spec = spec self._app_name = app_name self._options = options or {} def helloworld(environ, start_request): self._environ = environ self._path_info = environ['PATH_INFO'] start_request('200 OK', self._headers) return [b'abc'] return helloworld def out(self, msg): self._out.append(msg) def test_command_not_enough_args(self): command = self._makeOne([]) command.run() self.assertEqual(self._out, ['You must provide at least two arguments']) def test_command_two_args(self): command = self._makeOne(['', 'development.ini', '/']) command.run() self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_path_doesnt_start_with_slash(self): command = self._makeOne(['', 'development.ini', 'abc']) command.run() self.assertEqual(self._path_info, '/abc') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_has_bad_config_header(self): command = self._makeOne( ['', '--header=name','development.ini', '/']) command.run() self.assertEqual( self._out[0], ("Bad --header=name option, value must be in the form " "'name:value'")) def test_command_has_good_header_var(self): command = self._makeOne( ['', '--header=name:value','development.ini', '/']) command.run() self.assertEqual(self._environ['HTTP_NAME'], 'value') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_w_basic_auth(self): command = self._makeOne( ['', '--login=user:password', '--header=name:value','development.ini', '/']) command.run() self.assertEqual(self._environ['HTTP_NAME'], 'value') self.assertEqual(self._environ['HTTP_AUTHORIZATION'], 'Basic dXNlcjpwYXNzd29yZA==') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_has_content_type_header_var(self): command = self._makeOne( ['', '--header=content-type:app/foo','development.ini', '/']) command.run() self.assertEqual(self._environ['CONTENT_TYPE'], 'app/foo') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_has_multiple_header_vars(self): command = self._makeOne( ['', '--header=name:value', '--header=name2:value2', 'development.ini', '/']) command.run() self.assertEqual(self._environ['HTTP_NAME'], 'value') self.assertEqual(self._environ['HTTP_NAME2'], 'value2') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_get(self): command = self._makeOne(['', '--method=GET', 'development.ini', '/']) command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'GET') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_post(self): from pyramid.compat import NativeIO command = self._makeOne(['', '--method=POST', 'development.ini', '/']) stdin = NativeIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'POST') self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_put(self): from pyramid.compat import NativeIO command = self._makeOne(['', '--method=PUT', 'development.ini', '/']) stdin = NativeIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PUT') self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_patch(self): from pyramid.compat import NativeIO command = self._makeOne(['', '--method=PATCH', 'development.ini', '/']) stdin = NativeIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PATCH') self.assertEqual(self._environ['CONTENT_LENGTH'], '-1') self.assertEqual(self._environ['wsgi.input'], stdin) self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_propfind(self): from pyramid.compat import NativeIO command = self._makeOne(['', '--method=PROPFIND', 'development.ini', '/']) stdin = NativeIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'PROPFIND') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_method_options(self): from pyramid.compat import NativeIO command = self._makeOne(['', '--method=OPTIONS', 'development.ini', '/']) stdin = NativeIO() command.stdin = stdin command.run() self.assertEqual(self._environ['REQUEST_METHOD'], 'OPTIONS') self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_with_query_string(self): command = self._makeOne(['', 'development.ini', '/abc?a=1&b=2&c']) command.run() self.assertEqual(self._environ['QUERY_STRING'], 'a=1&b=2&c') self.assertEqual(self._path_info, '/abc') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual(self._out, ['abc']) def test_command_display_headers(self): command = self._makeOne( ['', '--display-headers', 'development.ini', '/']) command.run() self.assertEqual(self._path_info, '/') self.assertEqual(self._spec, 'development.ini') self.assertEqual(self._app_name, None) self.assertEqual( self._out, ['200 OK', 'Content-Type: text/html; charset=UTF-8', 'abc']) def test_command_response_has_no_charset(self): command = self._makeOne(['', '--method=GET', 'development.ini', '/'], headers=[('Content-Type', 'image/jpeg')]) command.run() 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 return main(argv, True) def test_it(self): result = self._callFUT(['prequest']) self.assertEqual(result, 2) pyramid-1.6/pyramid/tests/test_scripts/test_proutes.py0000644000076500000240000006103112520062551024220 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_scripts import dummy class DummyIntrospector(object): def __init__(self): self.relations = {} self.introspectables = {} def get(self, name, discrim): pass class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.proutes import PRoutesCommand return PRoutesCommand def _makeOne(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp',) return cmd def _makeRegistry(self): from pyramid.registry import Registry registry = Registry() registry.introspector = DummyIntrospector() return registry def _makeConfig(self, *arg, **kw): from pyramid.config import Configurator config = Configurator(*arg, **kw) return config def test_good_args(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp', 'a=1') route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) cmd._get_mapper = lambda *arg: mapper registry = self._makeRegistry() cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) L = [] cmd.out = lambda msg: L.append(msg) cmd.run() self.assertTrue('' in ''.join(L)) def test_bad_args(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp', 'a') route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) cmd._get_mapper = lambda *arg: mapper self.assertRaises(ValueError, cmd.run) def test_no_routes(self): command = self._makeOne() mapper = dummy.DummyMapper() command._get_mapper = lambda *arg: mapper L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(L, []) def test_no_mapper(self): command = self._makeOne() command._get_mapper = lambda *arg:None L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(L, []) def test_single_route_no_route_registered(self): command = self._makeOne() route = dummy.DummyRoute('a', '/a') mapper = dummy.DummyMapper(route) command._get_mapper = lambda *arg: mapper registry = self._makeRegistry() command.bootstrap = (dummy.DummyBootstrap(registry=registry),) L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) def test_route_with_no_slash_prefix(self): command = self._makeOne() route = dummy.DummyRoute('a', 'a') mapper = dummy.DummyMapper(route) command._get_mapper = lambda *arg: mapper L = [] command.out = L.append registry = self._makeRegistry() command.bootstrap = (dummy.DummyBootstrap(registry=registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) self.assertEqual(L[-1].split(), ['a', '/a', '', '*']) def test_single_route_no_views_registered(self): from zope.interface import Interface from pyramid.interfaces import IRouteRequest registry = self._makeRegistry() def view():pass class IMyRoute(Interface): pass 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) self.assertEqual(L[-1].split()[:3], ['a', '/a', '']) def test_single_route_one_view_registered(self): from zope.interface import Interface from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView registry = self._makeRegistry() def view():pass class IMyRoute(Interface): pass registry.registerAdapter(view, (IViewClassifier, IMyRoute, Interface), IView, '') 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_one_route_with_long_name_one_view_registered(self): from zope.interface import Interface from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView registry = self._makeRegistry() 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.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView registry = self._makeRegistry() def view():pass class IMyRoot(Interface): pass class IMyRoute(Interface): pass registry.registerAdapter(view, (IViewClassifier, IMyRoute, IMyRoot), IView, '') registry.registerUtility(IMyRoute, IRouteRequest, name='a') command = self._makeOne() def factory(request): pass route = dummy.DummyRoute('a', '/a', factory=factory) 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) self.assertEqual(L[-1].split()[:3], ['a', '/a', '']) def test_single_route_multiview_registered(self): from zope.interface import Interface from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView registry = self._makeRegistry() 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] view_module = 'pyramid.tests.test_scripts.dummy' view_str = '' ] self.assertEqual(compare_to, expected) def test_route_static_views(self): from pyramid.renderers import null_renderer as nr config = self._makeConfig(autocommit=True) config.add_static_view('static', 'static', cache_max_age=3600) config.add_static_view(name='static2', path='/var/www/static') config.add_static_view( name='pyramid_scaffold', path='pyramid:scaffolds/starter/+package+/static' ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 5) expected = [ ['__static/', '/static/*subpath', 'pyramid.tests.test_scripts:static/', '*'], ['__static2/', '/static2/*subpath', '/var/www/static/', '*'], ['__pyramid_scaffold/', '/pyramid_scaffold/*subpath', 'pyramid:scaffolds/starter/+package+/static/', '*'], ] for index, line in enumerate(L[2:]): data = line.split() self.assertEqual(data, expected[index]) def test_route_no_view(self): from pyramid.renderers import null_renderer as nr config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b', request_method='POST') command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', '/a/b', '', 'POST', ] self.assertEqual(compare_to, expected) def test_route_as_wsgiapp(self): from pyramid.wsgi import wsgiapp2 config1 = self._makeConfig(autocommit=True) def view1(context, request): return 'view1' config1.add_route('foo', '/a/b', request_method='POST') config1.add_view(view=view1, route_name='foo') config2 = self._makeConfig(autocommit=True) config2.add_route('foo', '/a/b', request_method='POST') config2.add_view( wsgiapp2(config1.make_wsgi_app()), route_name='foo', ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', '/a/b', '', 'POST', ] self.assertEqual(compare_to, expected) def test_route_is_get_view_request_method_not_post(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b', request_method='GET') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', '/a/b', 'pyramid.tests.test_scripts.test_proutes.view1', 'GET' ] self.assertEqual(compare_to, expected) def test_view_request_method_not_post(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', '/a/b', 'pyramid.tests.test_scripts.test_proutes.view1', '!POST,*' ] self.assertEqual(compare_to, expected) def test_view_glob(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' def view2(context, request): return 'view2' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) config.add_route('bar', '/b/a') config.add_view( route_name='bar', view=view2, renderer=nr, request_method=not_('POST') ) command = self._makeOne() command.options.glob = '*foo*' L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', '/a/b', 'pyramid.tests.test_scripts.test_proutes.view1', '!POST,*' ] self.assertEqual(compare_to, expected) def test_good_format(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() command.options.glob = '*foo*' command.options.format = 'method,name' L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = ['!POST,*', 'foo'] self.assertEqual(compare_to, expected) self.assertEqual(L[0].split(), ['Method', 'Name']) def test_bad_format(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() command.options.glob = '*foo*' command.options.format = 'predicates,name,pattern' L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) expected = ( "You provided invalid formats ['predicates'], " "Available formats are ['name', 'pattern', 'view', 'method']" ) result = command.run() self.assertEqual(result, 2) self.assertEqual(L[0], expected) def test_config_format_ini_newlines(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) config_factory = dummy.DummyConfigParserFactory() command.ConfigParser = config_factory config_factory.items = [('format', 'method\nname')] result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = ['!POST,*', 'foo'] self.assertEqual(compare_to, expected) self.assertEqual(L[0].split(), ['Method', 'Name']) def test_config_format_ini_spaces(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) config_factory = dummy.DummyConfigParserFactory() command.ConfigParser = config_factory config_factory.items = [('format', 'method name')] result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = ['!POST,*', 'foo'] self.assertEqual(compare_to, expected) self.assertEqual(L[0].split(), ['Method', 'Name']) def test_config_format_ini_commas(self): from pyramid.renderers import null_renderer as nr from pyramid.config import not_ def view1(context, request): return 'view1' config = self._makeConfig(autocommit=True) config.add_route('foo', '/a/b') config.add_view( route_name='foo', view=view1, renderer=nr, request_method=not_('POST') ) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) config_factory = dummy.DummyConfigParserFactory() command.ConfigParser = config_factory config_factory.items = [('format', 'method,name')] result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = ['!POST,*', 'foo'] self.assertEqual(compare_to, expected) self.assertEqual(L[0].split(), ['Method', 'Name']) def test_static_routes_included_in_list(self): from pyramid.renderers import null_renderer as nr config = self._makeConfig(autocommit=True) config.add_route('foo', 'http://example.com/bar.aspx', static=True) command = self._makeOne() L = [] command.out = L.append command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),) result = command.run() self.assertEqual(result, 0) self.assertEqual(len(L), 3) compare_to = L[-1].split() expected = [ 'foo', 'http://example.com/bar.aspx', '', '*', ] self.assertEqual(compare_to, expected) class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.proutes import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['proutes']) self.assertEqual(result, 2) pyramid-1.6/pyramid/tests/test_scripts/test_pserve.py0000644000076500000240000002564112606630333024036 0ustar michaelstaff00000000000000import atexit import os import tempfile import unittest from pyramid.compat import PY3 if PY3: import builtins as __builtin__ else: import __builtin__ class TestPServeCommand(unittest.TestCase): def setUp(self): from pyramid.compat import NativeIO self.out_ = NativeIO() self.pid_file = None def tearDown(self): if self.pid_file and os.path.exists(self.pid_file): os.remove(self.pid_file) def out(self, msg): self.out_.write(msg) def _get_server(*args, **kwargs): def server(app): return '' return server def _getTargetClass(self): from pyramid.scripts.pserve import PServeCommand return PServeCommand def _makeOne(self, *args): effargs = ['pserve'] effargs.extend(args) cmd = self._getTargetClass()(effargs) cmd.out = self.out return cmd def _makeOneWithPidFile(self, pid): self.pid_file = tempfile.mktemp() inst = self._makeOne() with open(self.pid_file, 'w') as f: f.write(str(pid)) return inst def test_remove_pid_file_verbose(self): inst = self._makeOneWithPidFile(os.getpid()) inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) self._assert_pid_file_removed(verbose=True) def test_remove_pid_file_not_verbose(self): inst = self._makeOneWithPidFile(os.getpid()) inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=0) self._assert_pid_file_removed(verbose=False) def test_remove_pid_not_a_number(self): inst = self._makeOneWithPidFile('not a number') inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) self._assert_pid_file_removed(verbose=True) def test_remove_pid_current_pid_is_not_written_pid(self): inst = self._makeOneWithPidFile(os.getpid()) inst._remove_pid_file('99999', self.pid_file, verbosity=1) self._assert_pid_file_not_removed('') def test_remove_pid_current_pid_is_not_pid_in_file(self): inst = self._makeOneWithPidFile('99999') inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) msg = 'PID file %s contains 99999, not expected PID %s' self._assert_pid_file_not_removed(msg % (self.pid_file, os.getpid())) def test_remove_pid_no_pid_file(self): inst = self._makeOne() self.pid_file = 'some unknown path' inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) self._assert_pid_file_removed(verbose=False) def test_remove_pid_file_unlink_exception(self): inst = self._makeOneWithPidFile(os.getpid()) self._remove_pid_unlink_exception(inst) msg = [ 'Removing PID file %s' % (self.pid_file), 'Cannot remove PID file: (Some OSError - unlink)', 'Stale PID removed'] self._assert_pid_file_not_removed(msg=''.join(msg)) with open(self.pid_file) as f: self.assertEqual(f.read(), '') def test_remove_pid_file_stale_pid_write_exception(self): inst = self._makeOneWithPidFile(os.getpid()) self._remove_pid_unlink_and_write_exceptions(inst) msg = [ 'Removing PID file %s' % (self.pid_file), 'Cannot remove PID file: (Some OSError - unlink)', 'Stale PID left in file: %s ' % (self.pid_file), '(Some OSError - open)'] self._assert_pid_file_not_removed(msg=''.join(msg)) with open(self.pid_file) as f: self.assertEqual(int(f.read()), os.getpid()) def test_record_pid_verbose(self): self._assert_record_pid(verbosity=2, msg='Writing PID %d to %s') def test_record_pid_not_verbose(self): self._assert_record_pid(verbosity=1, msg='') def _remove_pid_unlink_exception(self, inst): old_unlink = os.unlink def fake_unlink(filename): raise OSError('Some OSError - unlink') try: os.unlink = fake_unlink inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) finally: os.unlink = old_unlink def _remove_pid_unlink_and_write_exceptions(self, inst): old_unlink = os.unlink def fake_unlink(filename): raise OSError('Some OSError - unlink') run_already = [] old_open = __builtin__.open def fake_open(*args): if not run_already: run_already.append(True) return old_open(*args) raise OSError('Some OSError - open') try: os.unlink = fake_unlink __builtin__.open = fake_open inst._remove_pid_file(os.getpid(), self.pid_file, verbosity=1) finally: os.unlink = old_unlink __builtin__.open = old_open def _assert_pid_file_removed(self, verbose=False): self.assertFalse(os.path.exists(self.pid_file)) msg = 'Removing PID file %s' % (self.pid_file) if verbose else '' self.assertEqual(self.out_.getvalue(), msg) def _assert_pid_file_not_removed(self, msg): self.assertTrue(os.path.exists(self.pid_file)) self.assertEqual(self.out_.getvalue(), msg) def _assert_record_pid(self, verbosity, msg): old_atexit = atexit.register def fake_atexit(*args): pass self.pid_file = tempfile.mktemp() pid = os.getpid() inst = self._makeOne() inst.options.verbose = verbosity try: atexit.register = fake_atexit inst.record_pid(self.pid_file) finally: atexit.register = old_atexit msg = msg % (pid, self.pid_file) if msg else '' self.assertEqual(self.out_.getvalue(), msg) with open(self.pid_file) as f: self.assertEqual(int(f.read()), pid) def test_run_no_args(self): inst = self._makeOne() result = inst.run() self.assertEqual(result, 2) self.assertEqual(self.out_.getvalue(), 'You must give a config file') def test_run_stop_daemon_no_such_pid_file(self): path = os.path.join(os.path.dirname(__file__), 'wontexist.pid') inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path) inst.run() msg = 'No PID file exists in %s' % path self.assertTrue(msg in self.out_.getvalue()) def test_run_stop_daemon_bad_pid_file(self): path = __file__ inst = self._makeOne('--stop-daemon', '--pid-file=%s' % path) inst.run() msg = 'Not a valid PID file in %s' % path self.assertTrue(msg in self.out_.getvalue()) def test_run_stop_daemon_invalid_pid_in_file(self): fn = tempfile.mktemp() with open(fn, 'wb') as tmp: tmp.write(b'9999999') tmp.close() inst = self._makeOne('--stop-daemon', '--pid-file=%s' % fn) inst.run() msg = 'PID in %s is not valid (deleting)' % fn self.assertTrue(msg in self.out_.getvalue()) def test_get_options_with_command(self): inst = self._makeOne() inst.args = ['foo', 'stop', 'a=1', 'b=2'] result = inst.get_options() self.assertEqual(result, {'a': '1', 'b': '2'}) def test_get_options_no_command(self): inst = self._makeOne() inst.args = ['foo', 'a=1', 'b=2'] result = inst.get_options() self.assertEqual(result, {'a': '1', 'b': '2'}) def test_parse_vars_good(self): from pyramid.tests.test_scripts.dummy import DummyApp inst = self._makeOne('development.ini', 'a=1', 'b=2') inst.loadserver = self._get_server app = DummyApp() def get_app(*args, **kwargs): app.global_conf = kwargs.get('global_conf', None) inst.loadapp = get_app inst.run() self.assertEqual(app.global_conf, {'a': '1', 'b': '2'}) def test_parse_vars_bad(self): inst = self._makeOne('development.ini', 'a') inst.loadserver = self._get_server self.assertRaises(ValueError, inst.run) class Test_read_pidfile(unittest.TestCase): def _callFUT(self, filename): from pyramid.scripts.pserve import read_pidfile return read_pidfile(filename) def test_read_pidfile(self): filename = tempfile.mktemp() try: with open(filename, 'w') as f: f.write('12345') result = self._callFUT(filename) self.assertEqual(result, 12345) finally: os.remove(filename) def test_read_pidfile_no_pid_file(self): result = self._callFUT('some unknown path') self.assertEqual(result, None) def test_read_pidfile_not_a_number(self): result = self._callFUT(__file__) self.assertEqual(result, None) class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pserve import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['pserve']) self.assertEqual(result, 2) class TestLazyWriter(unittest.TestCase): def _makeOne(self, filename, mode='w'): from pyramid.scripts.pserve import LazyWriter return LazyWriter(filename, mode) def test_open(self): filename = tempfile.mktemp() try: inst = self._makeOne(filename) fp = inst.open() self.assertEqual(fp.name, filename) finally: fp.close() os.remove(filename) def test_write(self): filename = tempfile.mktemp() try: inst = self._makeOne(filename) inst.write('hello') finally: with open(filename) as f: data = f.read() self.assertEqual(data, 'hello') inst.close() os.remove(filename) def test_writeline(self): filename = tempfile.mktemp() try: inst = self._makeOne(filename) inst.writelines('hello') finally: with open(filename) as f: data = f.read() self.assertEqual(data, 'hello') inst.close() os.remove(filename) def test_flush(self): filename = tempfile.mktemp() try: inst = self._makeOne(filename) inst.flush() fp = inst.fileobj self.assertEqual(fp.name, filename) finally: fp.close() os.remove(filename) class Test__methodwrapper(unittest.TestCase): def _makeOne(self, func, obj, type): from pyramid.scripts.pserve import _methodwrapper return _methodwrapper(func, obj, type) def test___call__succeed(self): def foo(self, cls, a=1): return 1 class Bar(object): pass wrapper = self._makeOne(foo, Bar, None) result = wrapper(a=1) self.assertEqual(result, 1) def test___call__fail(self): def foo(self, cls, a=1): return 1 class Bar(object): pass wrapper = self._makeOne(foo, Bar, None) self.assertRaises(AssertionError, wrapper, cls=1) pyramid-1.6/pyramid/tests/test_scripts/test_pshell.py0000644000076500000240000003263612621241570024021 0ustar michaelstaff00000000000000import os import unittest from pyramid.tests.test_scripts import dummy class TestPShellCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.pshell import PShellCommand return PShellCommand def _makeOne(self, patch_bootstrap=True, patch_config=True, patch_args=True, patch_options=True): cmd = self._getTargetClass()([]) if patch_bootstrap: self.bootstrap = dummy.DummyBootstrap() cmd.bootstrap = (self.bootstrap,) if patch_config: self.config_factory = dummy.DummyConfigParserFactory() cmd.ConfigParser = self.config_factory if patch_args: self.args = ('/foo/bar/myapp.ini#myapp',) cmd.args = self.args if patch_options: class Options(object): pass self.options = Options() self.options.python_shell = '' self.options.setup = None self.options.list = None cmd.options = self.options # default to None to prevent side-effects from running tests in # unknown environments cmd.pystartup = None return cmd def _makeEntryPoints(self, command, shells): command.pkg_resources = dummy.DummyPkgResources(shells) def test_command_loads_default_shell(self): command = self._makeOne() shell = dummy.DummyShell() self._makeEntryPoints(command, {}) command.default_runner = shell command.run() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') 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, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_command_errors_with_unknown_shell(self): command = self._makeOne() out_calls = [] def out(msg): out_calls.append(msg) command.out = out shell = dummy.DummyShell() self._makeEntryPoints(command, {}) command.default_runner = shell command.options.python_shell = 'unknown_python_shell' result = command.run() self.assertEqual(result, 1) self.assertEqual( out_calls, ['could not find a shell named "unknown_python_shell"'] ) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertTrue(self.bootstrap.closer.called) def test_command_loads_ipython(self): command = self._makeOne() shell = dummy.DummyShell() bad_shell = dummy.DummyShell() self._makeEntryPoints( command, { 'ipython': shell, 'bpython': bad_shell, } ) command.options.python_shell = 'ipython' command.run() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') 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, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_shell_entry_points(self): command = self._makeOne() dshell = dummy.DummyShell() self._makeEntryPoints( command, { 'ipython': dshell, 'bpython': dshell, } ) command.default_runner = None shell = command.make_shell() self.assertEqual(shell, dshell) def test_shell_override(self): command = self._makeOne() ipshell = dummy.DummyShell() bpshell = dummy.DummyShell() dshell = dummy.DummyShell() self._makeEntryPoints(command, {}) command.default_runner = dshell shell = command.make_shell() self.assertEqual(shell, dshell) command.options.python_shell = 'ipython' self.assertRaises(ValueError, command.make_shell) self._makeEntryPoints( command, { 'ipython': ipshell, 'bpython': bpshell, 'python': dshell, } ) command.options.python_shell = 'ipython' shell = command.make_shell() self.assertEqual(shell, ipshell) command.options.python_shell = 'bpython' shell = command.make_shell() self.assertEqual(shell, bpshell) command.options.python_shell = 'python' shell = command.make_shell() self.assertEqual(shell, dshell) def test_shell_ordering(self): command = self._makeOne() ipshell = dummy.DummyShell() bpshell = dummy.DummyShell() dshell = dummy.DummyShell() self._makeEntryPoints( command, { 'ipython': ipshell, 'bpython': bpshell, 'python': dshell, } ) command.default_runner = dshell command.preferred_shells = ['ipython', 'bpython'] shell = command.make_shell() self.assertEqual(shell, ipshell) command.preferred_shells = ['bpython', 'python'] shell = command.make_shell() self.assertEqual(shell, bpshell) command.preferred_shells = ['python', 'ipython'] shell = command.make_shell() self.assertEqual(shell, dshell) def test_command_loads_custom_items(self): command = self._makeOne() model = dummy.Dummy() user = dummy.Dummy() self.config_factory.items = [('m', model), ('User', user)] shell = dummy.DummyShell() command.run(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') 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, 'm':model, 'User': user, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_command_setup(self): command = self._makeOne() def setup(env): env['a'] = 1 env['root'] = 'root override' env['none'] = None self.config_factory.items = [('setup', setup)] shell = dummy.DummyShell() command.run(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, 'a':1, 'none': None, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_command_default_shell_option(self): command = self._makeOne() ipshell = dummy.DummyShell() dshell = dummy.DummyShell() self._makeEntryPoints( command, { 'ipython': ipshell, 'python': dshell, } ) self.config_factory.items = [ ('default_shell', 'bpython python\nipython')] command.run() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertTrue(dshell.called) def test_command_loads_check_variable_override_order(self): command = self._makeOne() model = dummy.Dummy() def setup(env): env['a'] = 1 env['m'] = 'model override' env['root'] = 'root override' self.config_factory.items = [('setup', setup), ('m', model)] shell = dummy.DummyShell() command.run(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, 'a':1, 'm':model, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_command_loads_setup_from_options(self): command = self._makeOne() def setup(env): env['a'] = 1 env['root'] = 'root override' model = dummy.Dummy() self.config_factory.items = [('setup', 'abc'), ('m', model)] command.options.setup = setup shell = dummy.DummyShell() command.run(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':self.bootstrap.app, 'root':'root override', 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, 'a':1, 'm':model, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) def test_command_custom_section_override(self): command = self._makeOne() dummy_ = dummy.Dummy() self.config_factory.items = [('app', dummy_), ('root', dummy_), ('registry', dummy_), ('request', dummy_)] shell = dummy.DummyShell() command.run(shell) self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, '/foo/bar/myapp.ini') self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertEqual(shell.env, { 'app':dummy_, 'root':dummy_, 'registry':dummy_, 'request':dummy_, 'root_factory':self.bootstrap.root_factory, }) 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.txt'))) 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) def test_list_shells(self): command = self._makeOne() dshell = dummy.DummyShell() out_calls = [] def out(msg): out_calls.append(msg) command.out = out self._makeEntryPoints( command, { 'ipython': dshell, 'python': dshell, } ) command.options.list = True result = command.run() self.assertEqual(result, 0) self.assertEqual(out_calls, [ 'Available shells:', ' ipython', ' python', ]) class Test_python_shell_runner(unittest.TestCase): def _callFUT(self, env, help, interact): from pyramid.scripts.pshell import python_shell_runner return python_shell_runner(env, help, interact=interact) def test_it(self): interact = dummy.DummyInteractor() self._callFUT({'foo': 'bar'}, 'a help message', interact) self.assertEqual(interact.local, {'foo': 'bar'}) self.assertTrue('a help message' in interact.banner) class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pshell import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['pshell']) self.assertEqual(result, 2) pyramid-1.6/pyramid/tests/test_scripts/test_ptweens.py0000644000076500000240000000373412234375161024220 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_scripts import dummy class TestPTweensCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.ptweens import PTweensCommand return PTweensCommand def _makeOne(self): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(),) cmd.args = ('/foo/bar/myapp.ini#myapp',) return cmd def test_command_no_tweens(self): command = self._makeOne() command._get_tweens = lambda *arg: None L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual(L, []) def test_command_implicit_tweens_only(self): command = self._makeOne() tweens = dummy.DummyTweens([('name', 'item')], None) command._get_tweens = lambda *arg: tweens L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual( L[0], '"pyramid.tweens" config value NOT set (implicitly ordered tweens ' 'used)') def test_command_implicit_and_explicit_tweens(self): command = self._makeOne() tweens = dummy.DummyTweens([('name', 'item')], [('name2', 'item2')]) command._get_tweens = lambda *arg: tweens L = [] command.out = L.append result = command.run() self.assertEqual(result, 0) self.assertEqual( L[0], '"pyramid.tweens" config value set (explicitly ordered tweens used)') def test__get_tweens(self): command = self._makeOne() registry = dummy.DummyRegistry() self.assertEqual(command._get_tweens(registry), None) class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.ptweens import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['ptweens']) self.assertEqual(result, 2) pyramid-1.6/pyramid/tests/test_scripts/test_pviews.py0000644000076500000240000005101412520062551024034 0ustar michaelstaff00000000000000import unittest from pyramid.tests.test_scripts import dummy class TestPViewsCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.pviews import PViewsCommand return PViewsCommand def _makeOne(self, registry=None): cmd = self._getTargetClass()([]) cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),) cmd.args = ('/foo/bar/myapp.ini#myapp',) return cmd def _makeRequest(self, url, registry): from pyramid.request import Request request = Request.blank('/a') request.registry = registry return request def _register_mapper(self, registry, routes): from pyramid.interfaces import IRoutesMapper mapper = dummy.DummyMapper(*routes) registry.registerUtility(mapper, IRoutesMapper) def test__find_view_no_match(self): from pyramid.registry import Registry registry = Registry() self._register_mapper(registry, []) command = self._makeOne(registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertEqual(result, None) def test__find_view_no_match_multiview_registered(self): from zope.interface import implementer from zope.interface import providedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView from pyramid.traversal import DefaultRootFactory from pyramid.registry import Registry registry = Registry() @implementer(IMultiView) class View1(object): pass request = dummy.DummyRequest({'PATH_INFO':'/a'}) root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter(View1(), (IViewClassifier, IRequest, root_iface), IMultiView) self._register_mapper(registry, []) command = self._makeOne(registry=registry) request = self._makeRequest('/x', registry) result = command._find_view(request) self.assertEqual(result, None) def test__find_view_traversal(self): from zope.interface import providedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from pyramid.traversal import DefaultRootFactory from pyramid.registry import Registry registry = Registry() def view1(): pass request = dummy.DummyRequest({'PATH_INFO':'/a'}) root = DefaultRootFactory(request) root_iface = providedBy(root) registry.registerAdapter(view1, (IViewClassifier, IRequest, root_iface), IView, name='a') self._register_mapper(registry, []) command = self._makeOne(registry=registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertEqual(result, view1) def test__find_view_traversal_multiview(self): from zope.interface import implementer from zope.interface import providedBy from pyramid.interfaces import IRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IMultiView from pyramid.traversal import DefaultRootFactory from pyramid.registry import Registry registry = Registry() @implementer(IMultiView) class View1(object): pass request = dummy.DummyRequest({'PATH_INFO':'/a'}) root = DefaultRootFactory(request) root_iface = providedBy(root) view = View1() registry.registerAdapter(view, (IViewClassifier, IRequest, root_iface), IMultiView, name='a') self._register_mapper(registry, []) command = self._makeOne(registry=registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertEqual(result, view) def test__find_view_route_no_multiview(self): from zope.interface import Interface from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from pyramid.registry import Registry registry = Registry() def view():pass class IMyRoot(Interface): pass class IMyRoute(Interface): pass registry.registerAdapter(view, (IViewClassifier, IMyRoute, IMyRoot), IView, '') registry.registerUtility(IMyRoute, IRouteRequest, name='a') @implementer(IMyRoot) class Factory(object): def __init__(self, request): pass routes = [dummy.DummyRoute('a', '/a', factory=Factory, matchdict={}), dummy.DummyRoute('b', '/b', factory=Factory)] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertEqual(result, view) def test__find_view_route_multiview_no_view_registered(self): from zope.interface import Interface from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IMultiView from pyramid.interfaces import IRootFactory from pyramid.registry import Registry registry = Registry() def view1():pass def view2():pass class IMyRoot(Interface): pass class IMyRoute1(Interface): pass class IMyRoute2(Interface): pass registry.registerUtility(IMyRoute1, IRouteRequest, name='a') registry.registerUtility(IMyRoute2, IRouteRequest, name='b') @implementer(IMyRoot) class Factory(object): def __init__(self, request): pass registry.registerUtility(Factory, IRootFactory) routes = [dummy.DummyRoute('a', '/a', matchdict={}), dummy.DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertTrue(IMultiView.providedBy(result)) def test__find_view_route_multiview(self): from zope.interface import Interface from zope.interface import implementer from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from pyramid.interfaces import IMultiView from pyramid.interfaces import IRootFactory from pyramid.registry import Registry registry = Registry() def view1():pass def view2():pass class IMyRoot(Interface): pass class IMyRoute1(Interface): pass class IMyRoute2(Interface): pass registry.registerAdapter(view1, (IViewClassifier, IMyRoute1, IMyRoot), IView, '') registry.registerAdapter(view2, (IViewClassifier, IMyRoute2, IMyRoot), IView, '') registry.registerUtility(IMyRoute1, IRouteRequest, name='a') registry.registerUtility(IMyRoute2, IRouteRequest, name='b') @implementer(IMyRoot) class Factory(object): def __init__(self, request): pass registry.registerUtility(Factory, IRootFactory) routes = [dummy.DummyRoute('a', '/a', matchdict={}), dummy.DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) request = self._makeRequest('/a', registry) result = command._find_view(request) self.assertTrue(IMultiView.providedBy(result)) self.assertEqual(len(result.views), 2) self.assertTrue((None, view1, None) in result.views) self.assertTrue((None, view2, None) in result.views) def test__find_multi_routes_all_match(self): command = self._makeOne() def factory(request): pass routes = [dummy.DummyRoute('a', '/a', factory=factory, matchdict={}), dummy.DummyRoute('b', '/a', factory=factory, matchdict={})] mapper = dummy.DummyMapper(*routes) request = dummy.DummyRequest({'PATH_INFO':'/a'}) result = command._find_multi_routes(mapper, request) self.assertEqual(result, [{'match':{}, 'route':routes[0]}, {'match':{}, 'route':routes[1]}]) def test__find_multi_routes_some_match(self): command = self._makeOne() def factory(request): pass routes = [dummy.DummyRoute('a', '/a', factory=factory), dummy.DummyRoute('b', '/a', factory=factory, matchdict={})] mapper = dummy.DummyMapper(*routes) request = dummy.DummyRequest({'PATH_INFO':'/a'}) result = command._find_multi_routes(mapper, request) self.assertEqual(result, [{'match':{}, 'route':routes[1]}]) def test__find_multi_routes_none_match(self): command = self._makeOne() def factory(request): pass routes = [dummy.DummyRoute('a', '/a', factory=factory), dummy.DummyRoute('b', '/a', factory=factory)] mapper = dummy.DummyMapper(*routes) request = dummy.DummyRequest({'PATH_INFO':'/a'}) result = command._find_multi_routes(mapper, request) self.assertEqual(result, []) def test_views_command_not_found(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append command._find_view = lambda arg1: None command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' Not found.') def test_views_command_not_found_url_starts_without_slash(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append command._find_view = lambda arg1: None command.args = ('/foo/bar/myapp.ini#myapp', 'a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' Not found.') def test_views_command_single_view_traversal(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append view = dummy.DummyView(context='context', view_name='a') command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.DummyView') def test_views_command_single_view_function_traversal(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append def view(): pass view.__request_attrs__ = {'context': 'context', 'view_name': 'a'} command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.test_pviews.view') def test_views_command_single_view_traversal_with_permission(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append view = dummy.DummyView(context='context', view_name='a') view.__permission__ = 'test' command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.DummyView') self.assertEqual(L[9], ' required permission = test') def test_views_command_single_view_traversal_with_predicates(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass predicate.text = lambda *arg: "predicate = x" view = dummy.DummyView(context='context', view_name='a') view.__predicates__ = [predicate] command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.DummyView') self.assertEqual(L[9], ' view predicates (predicate = x)') def test_views_command_single_view_route(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append route = dummy.DummyRoute('a', '/a', matchdict={}) view = dummy.DummyView(context='context', view_name='a', matched_route=route, subpath='') command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[6], ' Route:') self.assertEqual(L[8], ' route name: a') self.assertEqual(L[9], ' route pattern: /a') self.assertEqual(L[10], ' route path: /a') self.assertEqual(L[11], ' subpath: ') self.assertEqual(L[15], ' pyramid.tests.test_scripts.dummy.DummyView') def test_views_command_multi_view_nested(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append view1 = dummy.DummyView(context='context', view_name='a1') view1.__name__ = 'view1' view1.__view_attr__ = 'call' multiview1 = dummy.DummyMultiView(view1, context='context', view_name='a1') multiview2 = dummy.DummyMultiView(multiview1, context='context', view_name='a') command._find_view = lambda arg1: multiview2 command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.DummyMultiView') self.assertEqual(L[12], ' pyramid.tests.test_scripts.dummy.view1.call') def test_views_command_single_view_route_with_route_predicates(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass predicate.text = lambda *arg: "predicate = x" route = dummy.DummyRoute('a', '/a', matchdict={}, predicate=predicate) view = dummy.DummyView(context='context', view_name='a', matched_route=route, subpath='') command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[6], ' Route:') self.assertEqual(L[8], ' route name: a') self.assertEqual(L[9], ' route pattern: /a') self.assertEqual(L[10], ' route path: /a') self.assertEqual(L[11], ' subpath: ') self.assertEqual(L[12], ' route predicates (predicate = x)') self.assertEqual(L[16], ' pyramid.tests.test_scripts.dummy.DummyView') def test_views_command_multiview(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append view = dummy.DummyView(context='context') view.__name__ = 'view' view.__view_attr__ = 'call' multiview = dummy.DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.view.call') def test_views_command_multiview_with_permission(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append view = dummy.DummyView(context='context') view.__name__ = 'view' view.__view_attr__ = 'call' view.__permission__ = 'test' multiview = dummy.DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.view.call') self.assertEqual(L[9], ' required permission = test') def test_views_command_multiview_with_predicates(self): from pyramid.registry import Registry registry = Registry() command = self._makeOne(registry=registry) L = [] command.out = L.append def predicate(): pass predicate.text = lambda *arg: "predicate = x" view = dummy.DummyView(context='context') view.__name__ = 'view' view.__view_attr__ = 'call' view.__predicates__ = [predicate] multiview = dummy.DummyMultiView(view, context='context', view_name='a') command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) self.assertEqual(L[1], 'URL = /a') self.assertEqual(L[3], ' context: context') self.assertEqual(L[4], ' view name: a') self.assertEqual(L[8], ' pyramid.tests.test_scripts.dummy.view.call') self.assertEqual(L[9], ' view predicates (predicate = x)') class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pviews import main return main(argv, quiet=True) def test_it(self): result = self._callFUT(['pviews']) self.assertEqual(result, 2) pyramid-1.6/pyramid/tests/test_security.py0000644000076500000240000004367012524266531021661 0ustar michaelstaff00000000000000import unittest from pyramid import testing class TestAllPermissionsList(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _getTargetClass(self): from pyramid.security import AllPermissionsList return AllPermissionsList def _makeOne(self): return self._getTargetClass()() def test_it(self): thing = self._makeOne() self.assertTrue(thing.__eq__(thing)) self.assertEqual(thing.__iter__(), ()) self.assertTrue('anything' in thing) def test_singleton(self): from pyramid.security import ALL_PERMISSIONS self.assertEqual(ALL_PERMISSIONS.__class__, self._getTargetClass()) class TestAllowed(unittest.TestCase): def _getTargetClass(self): from pyramid.security import Allowed return Allowed def _makeOne(self, *arg, **kw): klass = self._getTargetClass() return klass(*arg, **kw) def test_it(self): allowed = self._makeOne('hello') self.assertEqual(allowed.msg, 'hello') self.assertEqual(allowed, True) self.assertTrue(allowed) self.assertEqual(str(allowed), 'hello') self.assertTrue('" in repr(allowed)) class TestDenied(unittest.TestCase): def _getTargetClass(self): from pyramid.security import Denied return Denied def _makeOne(self, *arg, **kw): klass = self._getTargetClass() return klass(*arg, **kw) def test_it(self): denied = self._makeOne('hello') self.assertEqual(denied.msg, 'hello') self.assertEqual(denied, False) self.assertFalse(denied) self.assertEqual(str(denied), 'hello') self.assertTrue('" in repr(denied)) class TestACLAllowed(unittest.TestCase): def _getTargetClass(self): from pyramid.security import ACLAllowed return ACLAllowed def _makeOne(self, *arg, **kw): klass = self._getTargetClass() return klass(*arg, **kw) def test_it(self): msg = ("ACLAllowed permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") allowed = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') self.assertTrue(msg in allowed.msg) self.assertEqual(allowed, True) self.assertTrue(allowed) self.assertEqual(str(allowed), msg) self.assertTrue('" % msg in repr(allowed)) class TestACLDenied(unittest.TestCase): def _getTargetClass(self): from pyramid.security import ACLDenied return ACLDenied def _makeOne(self, *arg, **kw): klass = self._getTargetClass() return klass(*arg, **kw) def test_it(self): msg = ("ACLDenied permission 'permission' via ACE 'ace' in ACL 'acl' " "on context 'ctx' for principals 'principals'") denied = self._makeOne('ace', 'acl', 'permission', 'principals', 'ctx') self.assertTrue(msg in denied.msg) self.assertEqual(denied, False) self.assertFalse(denied) self.assertEqual(str(denied), msg) self.assertTrue('" % msg in repr(denied)) class TestPrincipalsAllowedByPermission(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, *arg): from pyramid.security import principals_allowed_by_permission return principals_allowed_by_permission(*arg) def test_no_authorization_policy(self): from pyramid.security import Everyone context = DummyContext() result = self._callFUT(context, 'view') self.assertEqual(result, [Everyone]) def test_with_authorization_policy(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() _registerAuthorizationPolicy(registry, 'yo') context = DummyContext() result = self._callFUT(context, 'view') self.assertEqual(result, 'yo') class TestRemember(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, *arg, **kwarg): from pyramid.security import remember return remember(*arg, **kwarg) def test_no_authentication_policy(self): request = _makeRequest() result = self._callFUT(request, 'me') self.assertEqual(result, []) def test_with_authentication_policy(self): request = _makeRequest() registry = request.registry _registerAuthenticationPolicy(registry, 'yo') result = self._callFUT(request, 'me') self.assertEqual(result, [('X-Pyramid-Test', 'me')]) def test_with_authentication_policy_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = _makeRequest() del request.registry _registerAuthenticationPolicy(registry, 'yo') 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() def tearDown(self): testing.tearDown() def _callFUT(self, *arg): from pyramid.security import forget return forget(*arg) def test_no_authentication_policy(self): request = _makeRequest() result = self._callFUT(request) self.assertEqual(result, []) def test_with_authentication_policy(self): request = _makeRequest() _registerAuthenticationPolicy(request.registry, 'yo') result = self._callFUT(request) self.assertEqual(result, [('X-Pyramid-Test', 'logout')]) def test_with_authentication_policy_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = _makeRequest() del request.registry _registerAuthenticationPolicy(registry, 'yo') result = self._callFUT(request) self.assertEqual(result, [('X-Pyramid-Test', 'logout')]) class TestViewExecutionPermitted(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self, *arg, **kw): from pyramid.security import view_execution_permitted return view_execution_permitted(*arg, **kw) def _registerSecuredView(self, view_name, allow=True): from pyramid.threadlocal import get_current_registry from zope.interface import Interface from pyramid.interfaces import ISecuredView from pyramid.interfaces import IViewClassifier class Checker(object): def __permitted__(self, context, request): self.context = context self.request = request return allow checker = Checker() reg = get_current_registry() reg.registerAdapter(checker, (IViewClassifier, Interface, Interface), ISecuredView, view_name) return checker def test_no_permission(self): from zope.interface import Interface from pyramid.threadlocal import get_current_registry from pyramid.interfaces import ISettings from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier settings = dict(debug_authorization=True) reg = get_current_registry() reg.registerUtility(settings, ISettings) context = DummyContext() request = testing.DummyRequest({}) class DummyView(object): pass view = DummyView() reg.registerAdapter(view, (IViewClassifier, Interface, Interface), IView, '') result = self._callFUT(context, request, '') msg = result.msg self.assertTrue("Allowed: view name '' in context" in msg) self.assertTrue('(no permission defined)' in msg) self.assertEqual(result, True) def test_no_view_registered(self): from pyramid.threadlocal import get_current_registry from pyramid.interfaces import ISettings settings = dict(debug_authorization=True) reg = get_current_registry() reg.registerUtility(settings, ISettings) context = DummyContext() request = testing.DummyRequest({}) self.assertRaises(TypeError, self._callFUT, context, request, '') def test_with_permission(self): from zope.interface import Interface from zope.interface import directlyProvides from pyramid.interfaces import IRequest class IContext(Interface): pass context = DummyContext() directlyProvides(context, IContext) self._registerSecuredView('', True) request = testing.DummyRequest({}) directlyProvides(request, IRequest) result = self._callFUT(context, request, '') self.assertTrue(result) class TestAuthenticatedUserId(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def test_backward_compat_delegates_to_mixin(self): from zope.deprecation import __show__ try: __show__.off() request = _makeFakeRequest() from pyramid.security import authenticated_userid self.assertEqual( authenticated_userid(request), 'authenticated_userid' ) finally: __show__.on() def test_no_authentication_policy(self): request = _makeRequest() self.assertEqual(request.authenticated_userid, None) def test_with_authentication_policy(self): request = _makeRequest() _registerAuthenticationPolicy(request.registry, 'yo') self.assertEqual(request.authenticated_userid, 'yo') def test_with_authentication_policy_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = _makeRequest() del request.registry _registerAuthenticationPolicy(registry, 'yo') self.assertEqual(request.authenticated_userid, 'yo') class TestUnAuthenticatedUserId(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def test_backward_compat_delegates_to_mixin(self): from zope.deprecation import __show__ try: __show__.off() request = _makeFakeRequest() from pyramid.security import unauthenticated_userid self.assertEqual( unauthenticated_userid(request), 'unauthenticated_userid', ) finally: __show__.on() def test_no_authentication_policy(self): request = _makeRequest() self.assertEqual(request.unauthenticated_userid, None) def test_with_authentication_policy(self): request = _makeRequest() _registerAuthenticationPolicy(request.registry, 'yo') self.assertEqual(request.unauthenticated_userid, 'yo') def test_with_authentication_policy_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = _makeRequest() del request.registry _registerAuthenticationPolicy(registry, 'yo') self.assertEqual(request.unauthenticated_userid, 'yo') class TestEffectivePrincipals(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def test_backward_compat_delegates_to_mixin(self): request = _makeFakeRequest() from zope.deprecation import __show__ try: __show__.off() from pyramid.security import effective_principals self.assertEqual( effective_principals(request), 'effective_principals' ) finally: __show__.on() def test_no_authentication_policy(self): from pyramid.security import Everyone request = _makeRequest() self.assertEqual(request.effective_principals, [Everyone]) def test_with_authentication_policy(self): request = _makeRequest() _registerAuthenticationPolicy(request.registry, 'yo') self.assertEqual(request.effective_principals, 'yo') def test_with_authentication_policy_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = _makeRequest() del request.registry _registerAuthenticationPolicy(registry, 'yo') self.assertEqual(request.effective_principals, 'yo') class TestHasPermission(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self): from pyramid.security import AuthorizationAPIMixin from pyramid.registry import Registry mixin = AuthorizationAPIMixin() mixin.registry = Registry() mixin.context = object() return mixin def test_delegates_to_mixin(self): from zope.deprecation import __show__ try: __show__.off() mixin = self._makeOne() from pyramid.security import has_permission self.called_has_permission = False def mocked_has_permission(*args, **kw): self.called_has_permission = True mixin.has_permission = mocked_has_permission has_permission('view', object(), mixin) self.assertTrue(self.called_has_permission) finally: __show__.on() def test_no_authentication_policy(self): request = self._makeOne() result = request.has_permission('view') self.assertTrue(result) self.assertEqual(result.msg, 'No authentication policy in use.') def test_with_no_authorization_policy(self): request = self._makeOne() _registerAuthenticationPolicy(request.registry, None) self.assertRaises(ValueError, request.has_permission, 'view', context=None) def test_with_authn_and_authz_policies_registered(self): request = self._makeOne() _registerAuthenticationPolicy(request.registry, None) _registerAuthorizationPolicy(request.registry, 'yo') self.assertEqual(request.has_permission('view', context=None), 'yo') def test_with_no_reg_on_request(self): from pyramid.threadlocal import get_current_registry registry = get_current_registry() request = self._makeOne() del request.registry _registerAuthenticationPolicy(registry, None) _registerAuthorizationPolicy(registry, 'yo') self.assertEqual(request.has_permission('view'), 'yo') def test_with_no_context_passed(self): request = self._makeOne() self.assertTrue(request.has_permission('view')) def test_with_no_context_passed_or_on_request(self): request = self._makeOne() del request.context self.assertRaises(AttributeError, request.has_permission, 'view') _TEST_HEADER = 'X-Pyramid-Test' class DummyContext: def __init__(self, *arg, **kw): self.__dict__.update(kw) class DummyAuthenticationPolicy: def __init__(self, result): self.result = result def effective_principals(self, request): return self.result def unauthenticated_userid(self, request): return self.result def authenticated_userid(self, request): return self.result def remember(self, request, userid, **kw): headers = [(_TEST_HEADER, userid)] self._header_remembered = headers[0] return headers def forget(self, request): headers = [(_TEST_HEADER, 'logout')] self._header_forgotten = headers[0] return headers class DummyAuthorizationPolicy: def __init__(self, result): self.result = result def permits(self, context, principals, permission): return self.result def principals_allowed_by_permission(self, context, permission): return self.result def _registerAuthenticationPolicy(reg, result): from pyramid.interfaces import IAuthenticationPolicy policy = DummyAuthenticationPolicy(result) reg.registerUtility(policy, IAuthenticationPolicy) return policy def _registerAuthorizationPolicy(reg, result): from pyramid.interfaces import IAuthorizationPolicy policy = DummyAuthorizationPolicy(result) reg.registerUtility(policy, IAuthorizationPolicy) return policy def _makeRequest(): from pyramid.registry import Registry request = testing.DummyRequest(environ={}) request.registry = Registry() request.context = object() return request def _makeFakeRequest(): class FakeRequest(testing.DummyRequest): @property def authenticated_userid(req): return 'authenticated_userid' @property def unauthenticated_userid(req): return 'unauthenticated_userid' @property def effective_principals(req): return 'effective_principals' return FakeRequest({}) pyramid-1.6/pyramid/tests/test_session.py0000644000076500000240000006656012622227523021475 0ustar michaelstaff00000000000000import base64 import json import unittest from pyramid import testing class SharedCookieSessionTests(object): def test_ctor_no_cookie(self): request = testing.DummyRequest() session = self._makeOne(request) self.assertEqual(dict(session), {}) def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import ISession request = testing.DummyRequest() session = self._makeOne(request) verifyObject(ISession, session) def test_ctor_with_cookie_still_valid(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request) self.assertEqual(dict(session), {'state':1}) def test_ctor_with_cookie_expired(self): request = testing.DummyRequest() cookieval = self._serialize((0, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request) self.assertEqual(dict(session), {}) def test_ctor_with_bad_cookie_cannot_deserialize(self): request = testing.DummyRequest() request.cookies['session'] = 'abc' session = self._makeOne(request) self.assertEqual(dict(session), {}) def test_ctor_with_bad_cookie_not_tuple(self): request = testing.DummyRequest() cookieval = self._serialize('abc') request.cookies['session'] = cookieval session = self._makeOne(request) self.assertEqual(dict(session), {}) def test_timeout(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time() - 5, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, timeout=1) self.assertEqual(dict(session), {}) def test_timeout_never(self): import time request = testing.DummyRequest() LONG_TIME = 31536000 cookieval = self._serialize((time.time() + LONG_TIME, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, timeout=None) self.assertEqual(dict(session), {'state': 1}) def test_timeout_str(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time() - 5, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, timeout='1') self.assertEqual(dict(session), {}) def test_timeout_invalid(self): request = testing.DummyRequest() self.assertRaises(ValueError, self._makeOne, request, timeout='Invalid value') def test_changed(self): request = testing.DummyRequest() session = self._makeOne(request) self.assertEqual(session.changed(), None) self.assertTrue(session._dirty) def test_invalidate(self): request = testing.DummyRequest() session = self._makeOne(request) session['a'] = 1 self.assertEqual(session.invalidate(), None) self.assertFalse('a' in session) def test_reissue_triggered(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time() - 2, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request) self.assertEqual(session['state'], 1) self.assertTrue(session._dirty) def test__set_cookie_on_exception(self): request = testing.DummyRequest() request.exception = True session = self._makeOne(request) session._cookie_on_exception = False response = DummyResponse() self.assertEqual(session._set_cookie(response), False) def test__set_cookie_on_exception_no_request_exception(self): import webob request = testing.DummyRequest() request.exception = None session = self._makeOne(request) session._cookie_on_exception = False response = webob.Response() self.assertEqual(session._set_cookie(response), True) self.assertEqual(response.headerlist[-1][0], 'Set-Cookie') def test__set_cookie_cookieval_too_long(self): request = testing.DummyRequest() session = self._makeOne(request) session['abc'] = 'x'*100000 response = DummyResponse() self.assertRaises(ValueError, session._set_cookie, response) def test__set_cookie_real_webob_response(self): import webob request = testing.DummyRequest() session = self._makeOne(request) session['abc'] = 'x' response = webob.Response() self.assertEqual(session._set_cookie(response), True) self.assertEqual(response.headerlist[-1][0], 'Set-Cookie') def test__set_cookie_options(self): from pyramid.response import Response request = testing.DummyRequest() request.exception = None session = self._makeOne(request, cookie_name='abc', path='/foo', domain='localhost', secure=True, httponly=True, ) session['abc'] = 'x' response = Response() self.assertEqual(session._set_cookie(response), True) cookieval = response.headerlist[-1][1] val, domain, path, secure, httponly = [x.strip() for x in cookieval.split(';')] self.assertTrue(val.startswith('abc=')) self.assertEqual(domain, 'Domain=localhost') self.assertEqual(path, 'Path=/foo') self.assertEqual(secure, 'secure') self.assertEqual(httponly, 'HttpOnly') def test_flash_default(self): request = testing.DummyRequest() session = self._makeOne(request) session.flash('msg1') session.flash('msg2') self.assertEqual(session['_f_'], ['msg1', 'msg2']) def test_flash_allow_duplicate_false(self): request = testing.DummyRequest() session = self._makeOne(request) session.flash('msg1') session.flash('msg1', allow_duplicate=False) self.assertEqual(session['_f_'], ['msg1']) def test_flash_allow_duplicate_true_and_msg_not_in_storage(self): request = testing.DummyRequest() session = self._makeOne(request) session.flash('msg1', allow_duplicate=True) self.assertEqual(session['_f_'], ['msg1']) def test_flash_allow_duplicate_false_and_msg_not_in_storage(self): request = testing.DummyRequest() session = self._makeOne(request) session.flash('msg1', allow_duplicate=False) self.assertEqual(session['_f_'], ['msg1']) def test_flash_mixed(self): request = testing.DummyRequest() session = self._makeOne(request) session.flash('warn1', 'warn') session.flash('warn2', 'warn') session.flash('err1', 'error') session.flash('err2', 'error') self.assertEqual(session['_f_warn'], ['warn1', 'warn2']) def test_pop_flash_default_queue(self): request = testing.DummyRequest() session = self._makeOne(request) queue = ['one', 'two'] session['_f_'] = queue result = session.pop_flash() self.assertEqual(result, queue) self.assertEqual(session.get('_f_'), None) def test_pop_flash_nodefault_queue(self): request = testing.DummyRequest() session = self._makeOne(request) queue = ['one', 'two'] session['_f_error'] = queue result = session.pop_flash('error') self.assertEqual(result, queue) self.assertEqual(session.get('_f_error'), None) def test_peek_flash_default_queue(self): request = testing.DummyRequest() session = self._makeOne(request) queue = ['one', 'two'] session['_f_'] = queue result = session.peek_flash() self.assertEqual(result, queue) self.assertEqual(session.get('_f_'), queue) def test_peek_flash_nodefault_queue(self): request = testing.DummyRequest() session = self._makeOne(request) queue = ['one', 'two'] session['_f_error'] = queue result = session.peek_flash('error') self.assertEqual(result, queue) self.assertEqual(session.get('_f_error'), queue) def test_new_csrf_token(self): request = testing.DummyRequest() session = self._makeOne(request) token = session.new_csrf_token() self.assertEqual(token, session['_csrft_']) def test_get_csrf_token(self): request = testing.DummyRequest() session = self._makeOne(request) session['_csrft_'] = 'token' token = session.get_csrf_token() self.assertEqual(token, 'token') self.assertTrue('_csrft_' in session) def test_get_csrf_token_new(self): request = testing.DummyRequest() session = self._makeOne(request) token = session.get_csrf_token() self.assertTrue(token) self.assertTrue('_csrft_' in session) def test_no_set_cookie_with_exception(self): import webob request = testing.DummyRequest() request.exception = True session = self._makeOne(request, set_on_exception=False) session['a'] = 1 callbacks = request.response_callbacks self.assertEqual(len(callbacks), 1) response = webob.Response() result = callbacks[0](request, response) self.assertEqual(result, None) self.assertFalse('Set-Cookie' in dict(response.headerlist)) def test_set_cookie_with_exception(self): import webob request = testing.DummyRequest() request.exception = True session = self._makeOne(request) session['a'] = 1 callbacks = request.response_callbacks self.assertEqual(len(callbacks), 1) response = webob.Response() result = callbacks[0](request, response) self.assertEqual(result, None) self.assertTrue('Set-Cookie' in dict(response.headerlist)) def test_cookie_is_set(self): import webob request = testing.DummyRequest() session = self._makeOne(request) session['a'] = 1 callbacks = request.response_callbacks self.assertEqual(len(callbacks), 1) response = webob.Response() result = callbacks[0](request, response) self.assertEqual(result, None) self.assertTrue('Set-Cookie' in dict(response.headerlist)) class TestBaseCookieSession(SharedCookieSessionTests, unittest.TestCase): def _makeOne(self, request, **kw): from pyramid.session import BaseCookieSessionFactory serializer = DummySerializer() return BaseCookieSessionFactory(serializer, **kw)(request) def _serialize(self, value): return base64.b64encode(json.dumps(value).encode('utf-8')) def test_reissue_not_triggered(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time=1) self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) def test_reissue_never(self): request = testing.DummyRequest() cookieval = self._serialize((0, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time=None, timeout=None) self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) def test_reissue_str_triggered(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time() - 2, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time='0') self.assertEqual(session['state'], 1) self.assertTrue(session._dirty) def test_reissue_invalid(self): request = testing.DummyRequest() self.assertRaises(ValueError, self._makeOne, request, reissue_time='invalid value') def test_cookie_max_age_invalid(self): request = testing.DummyRequest() self.assertRaises(ValueError, self._makeOne, request, max_age='invalid value') class TestSignedCookieSession(SharedCookieSessionTests, unittest.TestCase): def _makeOne(self, request, **kw): from pyramid.session import SignedCookieSessionFactory kw.setdefault('secret', 'secret') return SignedCookieSessionFactory(**kw)(request) def _serialize(self, value, salt=b'pyramid.session.', hashalg='sha512'): import base64 import hashlib import hmac import pickle digestmod = lambda: hashlib.new(hashalg) cstruct = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) sig = hmac.new(salt + b'secret', cstruct, digestmod).digest() return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') def test_reissue_not_triggered(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time=1) self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) def test_reissue_never(self): request = testing.DummyRequest() cookieval = self._serialize((0, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time=None, timeout=None) self.assertEqual(session['state'], 1) self.assertFalse(session._dirty) def test_reissue_str_triggered(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time() - 2, 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, reissue_time='0') self.assertEqual(session['state'], 1) self.assertTrue(session._dirty) def test_reissue_invalid(self): request = testing.DummyRequest() self.assertRaises(ValueError, self._makeOne, request, reissue_time='invalid value') def test_cookie_max_age_invalid(self): request = testing.DummyRequest() self.assertRaises(ValueError, self._makeOne, request, max_age='invalid value') def test_custom_salt(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1}), salt=b'f.') request.cookies['session'] = cookieval session = self._makeOne(request, salt=b'f.') self.assertEqual(session['state'], 1) def test_salt_mismatch(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1}), salt=b'f.') request.cookies['session'] = cookieval session = self._makeOne(request, salt=b'g.') self.assertEqual(session, {}) def test_custom_hashalg(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1}), hashalg='sha1') request.cookies['session'] = cookieval session = self._makeOne(request, hashalg='sha1') self.assertEqual(session['state'], 1) def test_hashalg_mismatch(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1}), hashalg='sha1') request.cookies['session'] = cookieval session = self._makeOne(request, hashalg='sha256') self.assertEqual(session, {}) def test_secret_mismatch(self): import time request = testing.DummyRequest() cookieval = self._serialize((time.time(), 0, {'state': 1})) request.cookies['session'] = cookieval session = self._makeOne(request, secret='evilsecret') self.assertEqual(session, {}) def test_custom_serializer(self): import base64 from hashlib import sha512 import hmac import time request = testing.DummyRequest() serializer = DummySerializer() cstruct = serializer.dumps((time.time(), 0, {'state': 1})) sig = hmac.new(b'pyramid.session.secret', cstruct, sha512).digest() cookieval = base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') request.cookies['session'] = cookieval session = self._makeOne(request, serializer=serializer) self.assertEqual(session['state'], 1) def test_invalid_data_size(self): from hashlib import sha512 import base64 request = testing.DummyRequest() num_bytes = sha512().digest_size - 1 cookieval = base64.b64encode(b' ' * num_bytes) request.cookies['session'] = cookieval session = self._makeOne(request) self.assertEqual(session, {}) def test_very_long_key(self): verylongkey = b'a' * 1024 import webob request = testing.DummyRequest() session = self._makeOne(request, secret=verylongkey) session['a'] = 1 callbacks = request.response_callbacks self.assertEqual(len(callbacks), 1) response = webob.Response() try: result = callbacks[0](request, response) except TypeError: # pragma: no cover self.fail('HMAC failed to initialize due to key length.') self.assertEqual(result, None) self.assertTrue('Set-Cookie' in dict(response.headerlist)) class TestUnencryptedCookieSession(SharedCookieSessionTests, unittest.TestCase): def setUp(self): super(TestUnencryptedCookieSession, self).setUp() from zope.deprecation import __show__ __show__.off() def tearDown(self): super(TestUnencryptedCookieSession, self).tearDown() from zope.deprecation import __show__ __show__.on() def _makeOne(self, request, **kw): from pyramid.session import UnencryptedCookieSessionFactoryConfig self._rename_cookie_var(kw, 'path', 'cookie_path') self._rename_cookie_var(kw, 'domain', 'cookie_domain') self._rename_cookie_var(kw, 'secure', 'cookie_secure') self._rename_cookie_var(kw, 'httponly', 'cookie_httponly') self._rename_cookie_var(kw, 'set_on_exception', 'cookie_on_exception') return UnencryptedCookieSessionFactoryConfig('secret', **kw)(request) def _rename_cookie_var(self, kw, src, dest): if src in kw: kw.setdefault(dest, kw.pop(src)) def _serialize(self, value): from pyramid.compat import bytes_ from pyramid.session import signed_serialize return bytes_(signed_serialize(value, 'secret')) def test_serialize_option(self): from pyramid.response import Response secret = 'secret' request = testing.DummyRequest() session = self._makeOne(request, signed_serialize=dummy_signed_serialize) session['key'] = 'value' response = Response() self.assertEqual(session._set_cookie(response), True) cookie = response.headerlist[-1][1] expected_cookieval = dummy_signed_serialize( (session.accessed, session.created, {'key': 'value'}), secret) response = Response() response.set_cookie('session', expected_cookieval) expected_cookie = response.headerlist[-1][1] self.assertEqual(cookie, expected_cookie) def test_deserialize_option(self): import time secret = 'secret' request = testing.DummyRequest() accessed = time.time() state = {'key': 'value'} cookieval = dummy_signed_serialize((accessed, accessed, state), secret) request.cookies['session'] = cookieval session = self._makeOne(request, signed_deserialize=dummy_signed_deserialize) self.assertEqual(dict(session), state) def dummy_signed_serialize(data, secret): import base64 from pyramid.compat import pickle, bytes_ pickled = pickle.dumps(data) return base64.b64encode(bytes_(secret)) + base64.b64encode(pickled) def dummy_signed_deserialize(serialized, secret): import base64 from pyramid.compat import pickle, bytes_ serialized_data = base64.b64decode( serialized[len(base64.b64encode(bytes_(secret))):]) return pickle.loads(serialized_data) class Test_manage_accessed(unittest.TestCase): def _makeOne(self, wrapped): from pyramid.session import manage_accessed return manage_accessed(wrapped) def test_accessed_set(self): request = testing.DummyRequest() session = DummySessionFactory(request) session.renewed = 0 wrapper = self._makeOne(session.__class__.get) wrapper(session, 'a') self.assertNotEqual(session.accessed, None) self.assertTrue(session._dirty) def test_accessed_without_renew(self): import time request = testing.DummyRequest() session = DummySessionFactory(request) session._reissue_time = 5 session.renewed = time.time() wrapper = self._makeOne(session.__class__.get) wrapper(session, 'a') self.assertNotEqual(session.accessed, None) self.assertFalse(session._dirty) def test_already_dirty(self): request = testing.DummyRequest() session = DummySessionFactory(request) session.renewed = 0 session._dirty = True session['a'] = 1 wrapper = self._makeOne(session.__class__.get) self.assertEqual(wrapper.__doc__, session.get.__doc__) result = wrapper(session, 'a') self.assertEqual(result, 1) callbacks = request.response_callbacks if callbacks is not None: self.assertEqual(len(callbacks), 0) class Test_manage_changed(unittest.TestCase): def _makeOne(self, wrapped): from pyramid.session import manage_changed return manage_changed(wrapped) def test_it(self): request = testing.DummyRequest() session = DummySessionFactory(request) wrapper = self._makeOne(session.__class__.__setitem__) wrapper(session, 'a', 1) self.assertNotEqual(session.accessed, None) self.assertTrue(session._dirty) def serialize(data, secret): import hmac import base64 from hashlib import sha1 from pyramid.compat import bytes_ from pyramid.compat import native_ from pyramid.compat import pickle pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL) sig = hmac.new(bytes_(secret, 'utf-8'), pickled, sha1).hexdigest() return sig + native_(base64.b64encode(pickled)) class Test_signed_serialize(unittest.TestCase): def _callFUT(self, data, secret): from pyramid.session import signed_serialize return signed_serialize(data, secret) def test_it(self): expected = serialize('123', 'secret') result = self._callFUT('123', 'secret') self.assertEqual(result, expected) def test_it_with_highorder_secret(self): secret = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8') expected = serialize('123', secret) result = self._callFUT('123', secret) self.assertEqual(result, expected) def test_it_with_latin1_secret(self): secret = b'La Pe\xc3\xb1a' expected = serialize('123', secret) result = self._callFUT('123', secret.decode('latin-1')) self.assertEqual(result, expected) class Test_signed_deserialize(unittest.TestCase): def _callFUT(self, serialized, secret, hmac=None): if hmac is None: import hmac from pyramid.session import signed_deserialize return signed_deserialize(serialized, secret, hmac=hmac) def test_it(self): serialized = serialize('123', 'secret') result = self._callFUT(serialized, 'secret') self.assertEqual(result, '123') def test_invalid_bits(self): serialized = serialize('123', 'secret') self.assertRaises(ValueError, self._callFUT, serialized, 'seekrit') def test_invalid_len(self): class hmac(object): def new(self, *arg): return self def hexdigest(self): return '1234' serialized = serialize('123', 'secret123') self.assertRaises(ValueError, self._callFUT, serialized, 'secret', hmac=hmac()) def test_it_bad_encoding(self): serialized = 'bad' + serialize('123', 'secret') self.assertRaises(ValueError, self._callFUT, serialized, 'secret') def test_it_with_highorder_secret(self): secret = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8') serialized = serialize('123', secret) result = self._callFUT(serialized, secret) self.assertEqual(result, '123') # bwcompat with pyramid <= 1.5b1 where latin1 is the default def test_it_with_latin1_secret(self): secret = b'La Pe\xc3\xb1a' serialized = serialize('123', secret) result = self._callFUT(serialized, secret.decode('latin-1')) self.assertEqual(result, '123') class Test_check_csrf_token(unittest.TestCase): def _callFUT(self, *args, **kwargs): from ..session import check_csrf_token return check_csrf_token(*args, **kwargs) def test_success_token(self): request = testing.DummyRequest() request.params['csrf_token'] = request.session.get_csrf_token() self.assertEqual(self._callFUT(request, token='csrf_token'), True) def test_success_header(self): request = testing.DummyRequest() request.headers['X-CSRF-Token'] = request.session.get_csrf_token() self.assertEqual(self._callFUT(request, header='X-CSRF-Token'), True) def test_success_default_token(self): request = testing.DummyRequest() request.params['csrf_token'] = request.session.get_csrf_token() self.assertEqual(self._callFUT(request), True) def test_success_default_header(self): request = testing.DummyRequest() request.headers['X-CSRF-Token'] = request.session.get_csrf_token() self.assertEqual(self._callFUT(request), True) def test_failure_raises(self): from pyramid.exceptions import BadCSRFToken request = testing.DummyRequest() self.assertRaises(BadCSRFToken, self._callFUT, request, 'csrf_token') def test_failure_no_raises(self): request = testing.DummyRequest() result = self._callFUT(request, 'csrf_token', raises=False) self.assertEqual(result, False) class DummySerializer(object): def dumps(self, value): return base64.b64encode(json.dumps(value).encode('utf-8')) def loads(self, value): try: return json.loads(base64.b64decode(value).decode('utf-8')) # base64.b64decode raises a TypeError on py2 instead of a ValueError # and a ValueError is required for the session to handle it properly except TypeError: raise ValueError class DummySessionFactory(dict): _dirty = False _cookie_name = 'session' _cookie_max_age = None _cookie_path = '/' _cookie_domain = None _cookie_secure = False _cookie_httponly = False _timeout = 1200 _reissue_time = 0 def __init__(self, request): self.request = request dict.__init__(self, {}) def changed(self): self._dirty = True class DummyResponse(object): def __init__(self): self.headerlist = [] pyramid-1.6/pyramid/tests/test_settings.py0000644000076500000240000000462612517346416021653 0ustar michaelstaff00000000000000import unittest class Test_asbool(unittest.TestCase): def _callFUT(self, s): from pyramid.settings import asbool return asbool(s) def test_s_is_None(self): result = self._callFUT(None) self.assertEqual(result, False) def test_s_is_True(self): result = self._callFUT(True) self.assertEqual(result, True) def test_s_is_False(self): result = self._callFUT(False) self.assertEqual(result, False) def test_s_is_true(self): result = self._callFUT('True') self.assertEqual(result, True) def test_s_is_false(self): result = self._callFUT('False') self.assertEqual(result, False) def test_s_is_yes(self): result = self._callFUT('yes') self.assertEqual(result, True) def test_s_is_on(self): result = self._callFUT('on') self.assertEqual(result, True) def test_s_is_1(self): result = self._callFUT(1) self.assertEqual(result, True) class Test_aslist_cronly(unittest.TestCase): def _callFUT(self, val): from pyramid.settings import aslist_cronly return aslist_cronly(val) def test_with_list(self): result = self._callFUT(['abc', 'def']) self.assertEqual(result, ['abc', 'def']) def test_with_string(self): result = self._callFUT('abc def') self.assertEqual(result, ['abc def']) def test_with_string_crsep(self): result = self._callFUT(' abc\n def') self.assertEqual(result, ['abc', 'def']) class Test_aslist(unittest.TestCase): def _callFUT(self, val, **kw): from pyramid.settings import aslist return aslist(val, **kw) def test_with_list(self): result = self._callFUT(['abc', 'def']) self.assertEqual(list(result), ['abc', 'def']) def test_with_string(self): result = self._callFUT('abc def') self.assertEqual(result, ['abc', 'def']) def test_with_string_crsep(self): result = self._callFUT(' abc\n def') self.assertEqual(result, ['abc', 'def']) def test_with_string_crsep_spacesep(self): result = self._callFUT(' abc\n def ghi') self.assertEqual(result, ['abc', 'def', 'ghi']) def test_with_string_crsep_spacesep_no_flatten(self): result = self._callFUT(' abc\n def ghi ', flatten=False) self.assertEqual(result, ['abc', 'def ghi']) pyramid-1.6/pyramid/tests/test_static.py0000644000076500000240000004557712634703652021313 0ustar michaelstaff00000000000000import datetime import os.path import unittest here = os.path.dirname(__file__) # 5 years from now (more or less) fiveyrsfuture = datetime.datetime.utcnow() + datetime.timedelta(5*365) class Test_static_view_use_subpath_False(unittest.TestCase): def _getTargetClass(self): from pyramid.static import static_view return static_view def _makeOne(self, *arg, **kw): return self._getTargetClass()(*arg, **kw) def _makeRequest(self, kw=None): from pyramid.request import Request environ = { 'wsgi.url_scheme':'http', 'wsgi.version':(1,0), 'SERVER_NAME':'example.com', 'SERVER_PORT':'6543', 'PATH_INFO':'/', 'SCRIPT_NAME':'', 'REQUEST_METHOD':'GET', } 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') self.assertEqual(inst.docroot, 'resource_name') self.assertEqual(inst.cache_max_age, 3600) self.assertEqual(inst.index, 'index.html') def test_call_adds_slash_path_info_empty(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':''}) context = DummyContext() from pyramid.httpexceptions import HTTPMovedPermanently self.assertRaises(HTTPMovedPermanently, inst, context, request) def test_path_info_slash_means_index_html(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/./index.html'}) context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') self.assertTrue(b'static' in response.body) def test_oob_emptyelement(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'//index.html'}) context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') self.assertTrue(b'static' in response.body) def test_oob_dotdotslash(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/subdir/../../minimal.pt'}) context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_oob_dotdotslash_encoded(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest( {'PATH_INFO':'/subdir/%2E%2E%2F%2E%2E/minimal.pt'}) 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') dds = '..' + os.sep request = self._makeRequest({'PATH_INFO':'/subdir/%s%sminimal.pt' % (dds, dds)}) context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_resource_doesnt_exist(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/notthere'}) context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_resource_isdir(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/subdir/'}) context = DummyContext() response = inst(context, request) self.assertTrue(b'subdir' in response.body) def test_resource_is_file(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' 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'static' 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') request = self._makeRequest({'PATH_INFO':'/index.html'}) class _Wrapper(object): def __init__(self, file, block_size=None): self.file = file self.block_size = block_size request.environ['wsgi.file_wrapper'] = _Wrapper context = DummyContext() response = inst(context, request) app_iter = response.app_iter self.assertTrue(isinstance(app_iter, _Wrapper)) self.assertTrue(b'static' in app_iter.file.read()) self.assertEqual(app_iter.block_size, _BLOCK_SIZE) app_iter.file.close() def test_resource_is_file_with_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600) request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 5) header_names = [ x[0] for x in response.headerlist ] header_names.sort() self.assertEqual(header_names, ['Cache-Control', 'Content-Length', 'Content-Type', 'Expires', 'Last-Modified']) def test_resource_is_file_with_no_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=None) request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() self.assertEqual( header_names, ['Content-Length', 'Content-Type', 'Last-Modified']) def test_resource_notmodified(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/index.html'}) request.if_modified_since = fiveyrsfuture context = DummyContext() response = inst(context, request) start_response = DummyStartResponse() app_iter = response(request.environ, start_response) try: self.assertEqual(start_response.status, '304 Not Modified') self.assertEqual(list(app_iter), []) finally: app_iter.close() def test_not_found(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/notthere.html'}) context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_resource_with_content_encoding(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/arcs.svg.tgz'}) context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/x-tar') self.assertEqual(response.content_encoding, 'gzip') response.app_iter.close() def test_resource_no_content_encoding(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':'/index.html'}) context = DummyContext() response = inst(context, request) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'text/html') self.assertEqual(response.content_encoding, None) response.app_iter.close() class Test_static_view_use_subpath_True(unittest.TestCase): def _getTargetClass(self): from pyramid.static import static_view return static_view def _makeOne(self, *arg, **kw): kw['use_subpath'] = True return self._getTargetClass()(*arg, **kw) def _makeRequest(self, kw=None): from pyramid.request import Request environ = { 'wsgi.url_scheme':'http', 'wsgi.version':(1,0), 'SERVER_NAME':'example.com', 'SERVER_PORT':'6543', 'PATH_INFO':'/', 'SCRIPT_NAME':'', 'REQUEST_METHOD':'GET', } 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') self.assertEqual(inst.docroot, 'resource_name') self.assertEqual(inst.cache_max_age, 3600) self.assertEqual(inst.index, 'index.html') def test_call_adds_slash_path_info_empty(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest({'PATH_INFO':''}) request.subpath = () context = DummyContext() from pyramid.httpexceptions import HTTPMovedPermanently self.assertRaises(HTTPMovedPermanently, inst, context, request) def test_path_info_slash_means_index_html(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = () context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) def test_oob_singledot(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('.', 'index.html') context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_oob_emptyelement(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('', 'index.html') context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_oob_dotdotslash(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('subdir', '..', '..', 'minimal.pt') context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_oob_dotdotslash_encoded(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('subdir', '%2E%2E', '%2E%2E', 'minimal.pt') 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') dds = '..' + os.sep request = self._makeRequest() request.subpath = ('subdir', dds, dds, 'minimal.pt') context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_resource_doesnt_exist(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('notthere,') context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) def test_resource_isdir(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('subdir',) context = DummyContext() response = inst(context, request) self.assertTrue(b'subdir' in response.body) def test_resource_is_file(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) def test_resource_is_file_with_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600) request = self._makeRequest() request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 5) header_names = [ x[0] for x in response.headerlist ] header_names.sort() self.assertEqual(header_names, ['Cache-Control', 'Content-Length', 'Content-Type', 'Expires', 'Last-Modified']) def test_resource_is_file_with_no_cache_max_age(self): inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=None) request = self._makeRequest() request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) self.assertTrue(b'static' in response.body) self.assertEqual(len(response.headerlist), 3) header_names = [ x[0] for x in response.headerlist ] header_names.sort() self.assertEqual( header_names, ['Content-Length', 'Content-Type', 'Last-Modified']) def test_resource_notmodified(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.if_modified_since = fiveyrsfuture request.subpath = ('index.html',) context = DummyContext() response = inst(context, request) start_response = DummyStartResponse() app_iter = response(request.environ, start_response) try: self.assertEqual(start_response.status, '304 Not Modified') self.assertEqual(list(app_iter), []) finally: app_iter.close() def test_not_found(self): inst = self._makeOne('pyramid.tests:fixtures/static') request = self._makeRequest() request.subpath = ('notthere.html',) context = DummyContext() from pyramid.httpexceptions import HTTPNotFound self.assertRaises(HTTPNotFound, inst, context, request) class TestQueryStringConstantCacheBuster(unittest.TestCase): 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().tokenize self.assertEqual(fut(None, 'whatever', None), 'foo') def test_it(self): fut = self._makeOne() self.assertEqual( fut('foo', 'bar', {}), ('bar', {'_query': {'x': 'foo'}})) def test_change_param(self): fut = self._makeOne('y') self.assertEqual( fut('foo', 'bar', {}), ('bar', {'_query': {'y': 'foo'}})) def test_query_is_already_tuples(self): fut = self._makeOne() self.assertEqual( fut('foo', 'bar', {'_query': [('a', 'b')]}), ('bar', {'_query': (('a', 'b'), ('x', 'foo'))})) def test_query_is_tuple_of_tuples(self): fut = self._makeOne() self.assertEqual( fut('foo', 'bar', {'_query': (('a', 'b'),)}), ('bar', {'_query': (('a', 'b'), ('x', 'foo'))})) class TestManifestCacheBuster(unittest.TestCase): def _makeOne(self, path, **kw): from pyramid.static import ManifestCacheBuster as cls return cls(path, **kw) def test_it(self): manifest_path = os.path.join(here, 'fixtures', 'manifest.json') fut = self._makeOne(manifest_path) self.assertEqual(fut('foo', 'bar', {}), ('bar', {})) self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-test.css', {})) def test_it_with_relspec(self): fut = self._makeOne('fixtures/manifest.json') self.assertEqual(fut('foo', 'bar', {}), ('bar', {})) self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-test.css', {})) def test_it_with_absspec(self): fut = self._makeOne('pyramid.tests:fixtures/manifest.json') self.assertEqual(fut('foo', 'bar', {}), ('bar', {})) self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-test.css', {})) def test_reload(self): manifest_path = os.path.join(here, 'fixtures', 'manifest.json') new_manifest_path = os.path.join(here, 'fixtures', 'manifest2.json') inst = self._makeOne('foo', reload=True) inst.getmtime = lambda *args, **kwargs: 0 fut = inst # test without a valid manifest self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main.css', {})) # swap to a real manifest, setting mtime to 0 inst.manifest_path = manifest_path self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-test.css', {})) # ensure switching the path doesn't change the result inst.manifest_path = new_manifest_path self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-test.css', {})) # update mtime, should cause a reload inst.getmtime = lambda *args, **kwargs: 1 self.assertEqual( fut('foo', 'css/main.css', {}), ('css/main-678b7c80.css', {})) def test_invalid_manifest(self): self.assertRaises(IOError, lambda: self._makeOne('foo')) def test_invalid_manifest_with_reload(self): inst = self._makeOne('foo', reload=True) self.assertEqual(inst.manifest, {}) class DummyContext: pass class DummyStartResponse: status = () headers = () def __call__(self, status, headers): self.status = status self.headers = headers pyramid-1.6/pyramid/tests/test_testing.py0000644000076500000240000006150212524266531021461 0ustar michaelstaff00000000000000import unittest class TestDummyRootFactory(unittest.TestCase): def _makeOne(self, environ): from pyramid.testing import DummyRootFactory return DummyRootFactory(environ) def test_it(self): environ = {'bfg.routes.matchdict':{'a':1}} factory = self._makeOne(environ) self.assertEqual(factory.a, 1) class TestDummySecurityPolicy(unittest.TestCase): def _getTargetClass(self): from pyramid.testing import DummySecurityPolicy return DummySecurityPolicy def _makeOne(self, userid=None, groupids=(), permissive=True): klass = self._getTargetClass() return klass(userid, groupids, permissive) def test_authenticated_userid(self): policy = self._makeOne('user') self.assertEqual(policy.authenticated_userid(None), 'user') def test_unauthenticated_userid(self): policy = self._makeOne('user') self.assertEqual(policy.unauthenticated_userid(None), 'user') def test_effective_principals_userid(self): policy = self._makeOne('user', ('group1',)) from pyramid.security import Everyone from pyramid.security import Authenticated self.assertEqual(policy.effective_principals(None), [Everyone, Authenticated, 'user', 'group1']) def test_effective_principals_nouserid(self): policy = self._makeOne() from pyramid.security import Everyone self.assertEqual(policy.effective_principals(None), [Everyone]) def test_permits(self): policy = self._makeOne() self.assertEqual(policy.permits(None, None, None), True) def test_principals_allowed_by_permission(self): policy = self._makeOne('user', ('group1',)) from pyramid.security import Everyone from pyramid.security import Authenticated result = policy.principals_allowed_by_permission(None, None) self.assertEqual(result, [Everyone, Authenticated, 'user', 'group1']) def test_forget(self): policy = self._makeOne() self.assertEqual(policy.forget(None), []) def test_remember(self): policy = self._makeOne() self.assertEqual(policy.remember(None, None), []) class TestDummyResource(unittest.TestCase): def _getTargetClass(self): from pyramid.testing import DummyResource return DummyResource def _makeOne(self, name=None, parent=None, **kw): klass = self._getTargetClass() return klass(name, parent, **kw) def test__setitem__and__getitem__and__delitem__and__contains__and_get(self): class Dummy: pass dummy = Dummy() resource = self._makeOne() resource['abc'] = dummy self.assertEqual(dummy.__name__, 'abc') self.assertEqual(dummy.__parent__, resource) self.assertEqual(resource['abc'], dummy) self.assertEqual(resource.get('abc'), dummy) self.assertRaises(KeyError, resource.__getitem__, 'none') self.assertTrue('abc' in resource) del resource['abc'] self.assertFalse('abc' in resource) self.assertEqual(resource.get('abc', 'foo'), 'foo') self.assertEqual(resource.get('abc'), None) def test_extra_params(self): resource = self._makeOne(foo=1) self.assertEqual(resource.foo, 1) def test_clone(self): resource = self._makeOne('name', 'parent', foo=1, bar=2) clone = resource.clone('name2', 'parent2', bar=1) self.assertEqual(clone.bar, 1) self.assertEqual(clone.__name__, 'name2') self.assertEqual(clone.__parent__, 'parent2') self.assertEqual(clone.foo, 1) def test_keys_items_values_len(self): class Dummy: pass resource = self._makeOne() resource['abc'] = Dummy() resource['def'] = Dummy() L = list self.assertEqual(L(resource.values()), L(resource.subs.values())) self.assertEqual(L(resource.items()), L(resource.subs.items())) self.assertEqual(L(resource.keys()), L(resource.subs.keys())) self.assertEqual(len(resource), 2) def test_nonzero(self): resource = self._makeOne() self.assertEqual(resource.__nonzero__(), True) def test_bool(self): resource = self._makeOne() self.assertEqual(resource.__bool__(), True) def test_ctor_with__provides__(self): resource = self._makeOne(__provides__=IDummy) self.assertTrue(IDummy.providedBy(resource)) class TestDummyRequest(unittest.TestCase): def _getTargetClass(self): from pyramid.testing import DummyRequest return DummyRequest def _makeOne(self, *arg, **kw): return self._getTargetClass()(*arg, **kw) def test_params(self): request = self._makeOne(params = {'say':'Hello'}, environ = {'PATH_INFO':'/foo'}, headers = {'X-Foo':'YUP'}, ) self.assertEqual(request.params['say'], 'Hello') self.assertEqual(request.GET['say'], 'Hello') self.assertEqual(request.POST['say'], 'Hello') self.assertEqual(request.headers['X-Foo'], 'YUP') self.assertEqual(request.environ['PATH_INFO'], '/foo') def test_defaults(self): from pyramid.threadlocal import get_current_registry from pyramid.testing import DummySession request = self._makeOne() self.assertEqual(request.method, 'GET') self.assertEqual(request.application_url, 'http://example.com') self.assertEqual(request.host_url, 'http://example.com') self.assertEqual(request.path_url, 'http://example.com') self.assertEqual(request.url, 'http://example.com') self.assertEqual(request.host, 'example.com:80') self.assertEqual(request.content_length, 0) self.assertEqual(request.environ.get('PATH_INFO'), None) self.assertEqual(request.headers.get('X-Foo'), None) self.assertEqual(request.params.get('foo'), None) self.assertEqual(request.GET.get('foo'), None) self.assertEqual(request.POST.get('foo'), None) self.assertEqual(request.cookies.get('type'), None) self.assertEqual(request.path, '/') self.assertEqual(request.path_info, '/') self.assertEqual(request.script_name, '') self.assertEqual(request.path_qs, '') self.assertEqual(request.view_name, '') self.assertEqual(request.subpath, ()) self.assertEqual(request.context, None) self.assertEqual(request.root, None) self.assertEqual(request.virtual_root, None) self.assertEqual(request.virtual_root_path, ()) self.assertEqual(request.registry, get_current_registry()) self.assertEqual(request.session.__class__, DummySession) def test_params_explicit(self): request = self._makeOne(params = {'foo':'bar'}) self.assertEqual(request.params['foo'], 'bar') self.assertEqual(request.GET['foo'], 'bar') self.assertEqual(request.POST['foo'], 'bar') def test_environ_explicit(self): request = self._makeOne(environ = {'PATH_INFO':'/foo'}) self.assertEqual(request.environ['PATH_INFO'], '/foo') def test_headers_explicit(self): request = self._makeOne(headers = {'X-Foo':'YUP'}) self.assertEqual(request.headers['X-Foo'], 'YUP') def test_path_explicit(self): request = self._makeOne(path = '/abc') self.assertEqual(request.path, '/abc') def test_cookies_explicit(self): request = self._makeOne(cookies = {'type': 'gingersnap'}) self.assertEqual(request.cookies['type'], 'gingersnap') def test_post_explicit(self): POST = {'foo': 'bar', 'baz': 'qux'} request = self._makeOne(post=POST) self.assertEqual(request.method, 'POST') self.assertEqual(request.POST, POST) # N.B.: Unlike a normal request, passing 'post' should *not* put # explict POST data into params: doing so masks a possible # XSS bug in the app. Tests for apps which don't care about # the distinction should just use 'params'. self.assertEqual(request.params, {}) def test_post_empty_shadows_params(self): request = self._makeOne(params={'foo': 'bar'}, post={}) self.assertEqual(request.method, 'POST') self.assertEqual(request.params.get('foo'), 'bar') self.assertEqual(request.POST.get('foo'), None) def test_kwargs(self): request = self._makeOne(water = 1) self.assertEqual(request.water, 1) def test_add_response_callback(self): request = self._makeOne() request.add_response_callback(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 from pyramid.registry import Registry from pyramid.config import Configurator request = self._makeOne() try: registry = Registry('this_test') config = Configurator(registry=registry) config.begin() self.assertTrue(request.registry is registry) finally: config.end() def test_set_registry(self): request = self._makeOne() request.registry = 'abc' self.assertEqual(request.registry, 'abc') def test_del_registry(self): # see https://github.com/Pylons/pyramid/issues/165 from pyramid.registry import Registry from pyramid.config import Configurator request = self._makeOne() request.registry = 'abc' self.assertEqual(request.registry, 'abc') del request.registry try: registry = Registry('this_test') config = Configurator(registry=registry) config.begin() self.assertTrue(request.registry is registry) finally: config.end() def test_response_with_responsefactory(self): from pyramid.registry import Registry from pyramid.interfaces import IResponseFactory registry = Registry('this_test') class ResponseFactory(object): pass registry.registerUtility( lambda r: ResponseFactory(), IResponseFactory ) request = self._makeOne() request.registry = registry resp = request.response self.assertEqual(resp.__class__, ResponseFactory) self.assertTrue(request.response is resp) # reified def test_response_without_responsefactory(self): from pyramid.registry import Registry from pyramid.response import Response registry = Registry('this_test') request = self._makeOne() request.registry = registry resp = request.response self.assertEqual(resp.__class__, Response) self.assertTrue(request.response is resp) # reified class TestDummyTemplateRenderer(unittest.TestCase): def _getTargetClass(self, ): from pyramid.testing import DummyTemplateRenderer return DummyTemplateRenderer def _makeOne(self, string_response=''): return self._getTargetClass()(string_response=string_response) def test_implementation(self): renderer = self._makeOne() impl = renderer.implementation() impl(a=1, b=2) self.assertEqual(renderer._implementation._received['a'], 1) self.assertEqual(renderer._implementation._received['b'], 2) def test_getattr(self): renderer = self._makeOne() renderer({'a':1}) self.assertEqual(renderer.a, 1) self.assertRaises(AttributeError, renderer.__getattr__, 'b') def test_assert_(self): renderer = self._makeOne() renderer({'a':1, 'b':2}) self.assertRaises(AssertionError, renderer.assert_, c=1) self.assertRaises(AssertionError, renderer.assert_, b=3) self.assertTrue(renderer.assert_(a=1, b=2)) def test_nondefault_string_response(self): renderer = self._makeOne('abc') result = renderer({'a':1, 'b':2}) self.assertEqual(result, 'abc') class Test_setUp(unittest.TestCase): def _callFUT(self, **kw): from pyramid.testing import setUp return setUp(**kw) def tearDown(self): from pyramid.threadlocal import manager manager.clear() getSiteManager = self._getSM() if getSiteManager is not None: getSiteManager.reset() def _getSM(self): try: from zope.component import getSiteManager except ImportError: # pragma: no cover getSiteManager = None return getSiteManager def _assertSMHook(self, hook): getSiteManager = self._getSM() if getSiteManager is not None: result = getSiteManager.sethook(None) self.assertEqual(result, hook) def test_it_defaults(self): from pyramid.threadlocal import manager from pyramid.threadlocal import get_current_registry from pyramid.registry import Registry old = True manager.push(old) config = self._callFUT() current = manager.get() self.assertFalse(current is old) 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): from pyramid.registry import Registry from pyramid.threadlocal import manager registry = Registry() self._callFUT(registry=registry) current = manager.get() self.assertEqual(current['registry'], registry) def test_it_with_request(self): from pyramid.threadlocal import manager request = object() self._callFUT(request=request) 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() self._callFUT(registry=registry, hook_zca=False) getSiteManager = self._getSM() if getSiteManager is not None: sm = getSiteManager() self.assertFalse(sm is registry) def test_it_with_settings_passed_explicit_registry(self): from pyramid.registry import Registry registry = Registry() self._callFUT(registry=registry, hook_zca=False, settings=dict(a=1)) self.assertEqual(registry.settings['a'], 1) def test_it_with_settings_passed_implicit_registry(self): config = self._callFUT(hook_zca=False, settings=dict(a=1)) self.assertEqual(config.registry.settings['a'], 1) class Test_cleanUp(Test_setUp): def _callFUT(self, *arg, **kw): from pyramid.testing import cleanUp return cleanUp(*arg, **kw) class Test_tearDown(unittest.TestCase): def _callFUT(self, **kw): from pyramid.testing import tearDown return tearDown(**kw) def tearDown(self): from pyramid.threadlocal import manager manager.clear() getSiteManager = self._getSM() if getSiteManager is not None: getSiteManager.reset() def _getSM(self): try: from zope.component import getSiteManager except ImportError: # pragma: no cover getSiteManager = None return getSiteManager def _assertSMHook(self, hook): getSiteManager = self._getSM() if getSiteManager is not None: result = getSiteManager.sethook(None) self.assertEqual(result, hook) def _setSMHook(self, hook): getSiteManager = self._getSM() if getSiteManager is not None: getSiteManager.sethook(hook) def test_defaults(self): from pyramid.threadlocal import manager registry = DummyRegistry() old = {'registry':registry} hook = lambda *arg: None try: self._setSMHook(hook) manager.push(old) self._callFUT() current = manager.get() self.assertNotEqual(current, old) self.assertEqual(registry.inited, 2) finally: getSiteManager = self._getSM() if getSiteManager is not None: result = getSiteManager.sethook(None) self.assertNotEqual(result, hook) def test_registry_cannot_be_inited(self): from pyramid.threadlocal import manager registry = DummyRegistry() def raiseit(name): raise TypeError registry.__init__ = raiseit old = {'registry':registry} try: manager.push(old) self._callFUT() # doesn't blow up current = manager.get() self.assertNotEqual(current, old) self.assertEqual(registry.inited, 1) finally: manager.clear() def test_unhook_zc_false(self): hook = lambda *arg: None try: self._setSMHook(hook) self._callFUT(unhook_zca=False) finally: self._assertSMHook(hook) class TestDummyRendererFactory(unittest.TestCase): def _makeOne(self, name, factory): from pyramid.testing import DummyRendererFactory return DummyRendererFactory(name, factory) def test_add_no_colon(self): f = self._makeOne('name', None) f.add('spec', 'renderer') self.assertEqual(f.renderers['spec'], 'renderer') def test_add_with_colon(self): f = self._makeOne('name', None) f.add('spec:spec2', 'renderer') self.assertEqual(f.renderers['spec:spec2'], 'renderer') self.assertEqual(f.renderers['spec2'], 'renderer') def test_call(self): f = self._makeOne('name', None) f.renderers['spec'] = 'renderer' info = DummyRendererInfo({'name':'spec'}) self.assertEqual(f(info), 'renderer') def test_call2(self): f = self._makeOne('name', None) f.renderers['spec'] = 'renderer' info = DummyRendererInfo({'name':'spec:spec'}) self.assertEqual(f(info), 'renderer') def test_call3(self): def factory(spec): return 'renderer' f = self._makeOne('name', factory) info = DummyRendererInfo({'name':'spec'}) self.assertEqual(f(info), 'renderer') def test_call_miss(self): f = self._makeOne('name', None) info = DummyRendererInfo({'name':'spec'}) self.assertRaises(KeyError, f, info) class TestMockTemplate(unittest.TestCase): def _makeOne(self, response): from pyramid.testing import MockTemplate return MockTemplate(response) def test_getattr(self): template = self._makeOne(None) self.assertEqual(template.foo, template) def test_getitem(self): template = self._makeOne(None) self.assertEqual(template['foo'], template) def test_call(self): template = self._makeOne('123') self.assertEqual(template(), '123') class Test_skip_on(unittest.TestCase): def setUp(self): from pyramid.testing import skip_on self.os_name = skip_on.os_name skip_on.os_name = 'wrong' def tearDown(self): from pyramid.testing import skip_on skip_on.os_name = self.os_name def _callFUT(self, *platforms): from pyramid.testing import skip_on return skip_on(*platforms) def test_wrong_platform(self): def foo(): return True decorated = self._callFUT('wrong')(foo) self.assertEqual(decorated(), None) def test_ok_platform(self): def foo(): return True decorated = self._callFUT('ok')(foo) self.assertEqual(decorated(), True) class TestDummySession(unittest.TestCase): def _makeOne(self): from pyramid.testing import DummySession return DummySession() def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import ISession session = self._makeOne() verifyObject(ISession, session) def test_changed(self): session = self._makeOne() self.assertEqual(session.changed(), None) def test_invalidate(self): session = self._makeOne() session['a'] = 1 self.assertEqual(session.invalidate(), None) self.assertFalse('a' in session) def test_flash_default(self): session = self._makeOne() session.flash('msg1') session.flash('msg2') self.assertEqual(session['_f_'], ['msg1', 'msg2']) def test_flash_mixed(self): session = self._makeOne() session.flash('warn1', 'warn') session.flash('warn2', 'warn') session.flash('err1', 'error') session.flash('err2', 'error') self.assertEqual(session['_f_warn'], ['warn1', 'warn2']) def test_pop_flash_default_queue(self): session = self._makeOne() queue = ['one', 'two'] session['_f_'] = queue result = session.pop_flash() self.assertEqual(result, queue) self.assertEqual(session.get('_f_'), None) def test_pop_flash_nodefault_queue(self): session = self._makeOne() queue = ['one', 'two'] session['_f_error'] = queue result = session.pop_flash('error') self.assertEqual(result, queue) self.assertEqual(session.get('_f_error'), None) def test_peek_flash_default_queue(self): session = self._makeOne() queue = ['one', 'two'] session['_f_'] = queue result = session.peek_flash() self.assertEqual(result, queue) self.assertEqual(session.get('_f_'), queue) def test_peek_flash_nodefault_queue(self): session = self._makeOne() queue = ['one', 'two'] session['_f_error'] = queue result = session.peek_flash('error') self.assertEqual(result, queue) self.assertEqual(session.get('_f_error'), queue) def test_new_csrf_token(self): session = self._makeOne() token = session.new_csrf_token() self.assertEqual(token, session['_csrft_']) def test_get_csrf_token(self): session = self._makeOne() session['_csrft_'] = 'token' token = session.get_csrf_token() self.assertEqual(token, 'token') self.assertTrue('_csrft_' in session) def test_get_csrf_token_generates_token(self): session = self._makeOne() token = session.get_csrf_token() self.assertNotEqual(token, None) self.assertTrue(len(token) >= 1) from zope.interface import Interface from zope.interface import implementer class IDummy(Interface): pass @implementer(IDummy) class DummyEvent: pass class DummyFactory: def __init__(self, environ): """ """ class DummyRegistry(object): inited = 0 __name__ = 'name' def __init__(self, name=''): self.inited = self.inited + 1 class DummyRendererInfo(object): def __init__(self, kw): self.__dict__.update(kw) class Test_testConfig(unittest.TestCase): def _setUp(self, **kw): self._log.append(('setUp', kw)) return 'fake config' def _tearDown(self, **kw): self._log.append(('tearDown', kw)) def setUp(self): from pyramid import testing self._log = [] self._orig_setUp = testing.setUp testing.setUp = self._setUp self._orig_tearDown = testing.tearDown testing.tearDown = self._tearDown def tearDown(self): from pyramid import testing testing.setUp = self._orig_setUp testing.tearDown = self._orig_tearDown def _callFUT(self, inner, **kw): from pyramid.testing import testConfig with testConfig(**kw) as config: inner(config) def test_ok_calls(self): self.assertEqual(self._log, []) def inner(config): self.assertEqual(self._log, [('setUp', {'autocommit': True, 'hook_zca': True, 'registry': None, 'request': None, 'settings': None})]) self._log.pop() self._callFUT(inner) self.assertEqual(self._log, [('tearDown', {'unhook_zca': True})]) def test_teardown_called_on_exception(self): class TestException(Exception): pass def inner(config): self._log = [] raise TestException('oops') self.assertRaises(TestException, self._callFUT, inner) self.assertEqual(self._log[0][0], 'tearDown') def test_ok_get_config(self): def inner(config): self.assertEqual(config, 'fake config') self._callFUT(inner) pyramid-1.6/pyramid/tests/test_threadlocal.py0000644000076500000240000000527312234375161022267 0ustar michaelstaff00000000000000from pyramid import testing import unittest class TestThreadLocalManager(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _getTargetClass(self): from pyramid.threadlocal import ThreadLocalManager return ThreadLocalManager def _makeOne(self, default=lambda *x: 1): return self._getTargetClass()(default) def test_init(self): local = self._makeOne() self.assertEqual(local.stack, []) self.assertEqual(local.get(), 1) def test_default(self): def thedefault(): return '123' local = self._makeOne(thedefault) self.assertEqual(local.stack, []) self.assertEqual(local.get(), '123') def test_push_and_pop(self): local = self._makeOne() local.push(True) self.assertEqual(local.get(), True) self.assertEqual(local.pop(), True) self.assertEqual(local.pop(), None) self.assertEqual(local.get(), 1) def test_set_get_and_clear(self): local = self._makeOne() local.set(None) self.assertEqual(local.stack, [None]) self.assertEqual(local.get(), None) local.clear() self.assertEqual(local.get(), 1) local.clear() self.assertEqual(local.get(), 1) class TestGetCurrentRequest(unittest.TestCase): def _callFUT(self): from pyramid.threadlocal import get_current_request return get_current_request() def test_it_None(self): request = self._callFUT() self.assertEqual(request, None) def test_it(self): from pyramid.threadlocal import manager request = object() try: manager.push({'request':request}) self.assertEqual(self._callFUT(), request) finally: manager.pop() self.assertEqual(self._callFUT(), None) class GetCurrentRegistryTests(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _callFUT(self): from pyramid.threadlocal import get_current_registry return get_current_registry() def test_it(self): from pyramid.threadlocal import manager try: manager.push({'registry':123}) self.assertEqual(self._callFUT(), 123) finally: manager.pop() class GetCurrentRegistryWithoutTestingRegistry(unittest.TestCase): def _callFUT(self): from pyramid.threadlocal import get_current_registry return get_current_registry() def test_it(self): from pyramid.registry import global_registry self.assertEqual(self._callFUT(), global_registry) pyramid-1.6/pyramid/tests/test_traversal.py0000644000076500000240000014477212524266531022022 0ustar michaelstaff00000000000000import unittest import warnings from pyramid.testing import cleanUp from pyramid.compat import ( text_, native_, text_type, url_quote, PY3, ) with warnings.catch_warnings(record=True) as w: warnings.filterwarnings('always') from pyramid.interfaces import IContextURL assert(len(w) == 1) class TraversalPathTests(unittest.TestCase): def _callFUT(self, path): from pyramid.traversal import traversal_path return traversal_path(path) def test_utf8(self): la = b'La Pe\xc3\xb1a' encoded = url_quote(la) decoded = text_(la, 'utf-8') path = '/'.join([encoded, encoded]) result = self._callFUT(path) self.assertEqual(result, (decoded, decoded)) def test_utf16(self): from pyramid.exceptions import URLDecodeError la = text_(b'La Pe\xc3\xb1a', 'utf-8').encode('utf-16') encoded = url_quote(la) path = '/'.join([encoded, encoded]) self.assertRaises(URLDecodeError, self._callFUT, path) def test_unicode_highorder_chars(self): path = text_('/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') self.assertEqual(self._callFUT(path), (text_('\u6d41\u884c\u8d8b\u52bf', 'unicode_escape'),)) def test_element_urllquoted(self): self.assertEqual(self._callFUT('/foo/space%20thing/bar'), (text_('foo'), text_('space thing'), text_('bar'))) def test_unicode_undecodeable_to_ascii(self): path = text_(b'/La Pe\xc3\xb1a', 'utf-8') self.assertRaises(UnicodeEncodeError, self._callFUT, path) class TraversalPathInfoTests(unittest.TestCase): def _callFUT(self, path): from pyramid.traversal import traversal_path_info return traversal_path_info(path) def test_path_startswith_endswith(self): self.assertEqual(self._callFUT('/foo/'), (text_('foo'),)) def test_empty_elements(self): self.assertEqual(self._callFUT('foo///'), (text_('foo'),)) def test_onedot(self): self.assertEqual(self._callFUT('foo/./bar'), (text_('foo'), text_('bar'))) def test_twodots(self): self.assertEqual(self._callFUT('foo/../bar'), (text_('bar'),)) def test_twodots_at_start(self): self.assertEqual(self._callFUT('../../bar'), (text_('bar'),)) def test_segments_are_unicode(self): result = self._callFUT('/foo/bar') self.assertEqual(type(result[0]), text_type) self.assertEqual(type(result[1]), text_type) def test_same_value_returned_if_cached(self): result1 = self._callFUT('/foo/bar') result2 = self._callFUT('/foo/bar') self.assertEqual(result1, (text_('foo'), text_('bar'))) self.assertEqual(result2, (text_('foo'), text_('bar'))) def test_unicode_simple(self): path = text_('/abc') self.assertEqual(self._callFUT(path), (text_('abc'),)) def test_highorder(self): la = b'La Pe\xc3\xb1a' latin1 = native_(la) result = self._callFUT(latin1) self.assertEqual(result, (text_(la, 'utf-8'),)) def test_highorder_undecodeable(self): from pyramid.exceptions import URLDecodeError la = text_(b'La Pe\xc3\xb1a', 'utf-8') notlatin1 = native_(la) self.assertRaises(URLDecodeError, self._callFUT, notlatin1) class ResourceTreeTraverserTests(unittest.TestCase): def setUp(self): cleanUp() def tearDown(self): cleanUp() def _getTargetClass(self): from pyramid.traversal import ResourceTreeTraverser return ResourceTreeTraverser def _makeOne(self, *arg, **kw): klass = self._getTargetClass() return klass(*arg, **kw) def _getEnviron(self, **kw): environ = {} environ.update(kw) return environ def test_class_conforms_to_ITraverser(self): from zope.interface.verify import verifyClass from pyramid.interfaces import ITraverser verifyClass(ITraverser, self._getTargetClass()) def test_instance_conforms_to_ITraverser(self): from zope.interface.verify import verifyObject from pyramid.interfaces import ITraverser context = DummyContext() verifyObject(ITraverser, self._makeOne(context)) def test_call_with_empty_pathinfo(self): policy = self._makeOne(None) environ = self._getEnviron() request = DummyRequest(environ, path_info='') result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_pathinfo_KeyError(self): policy = self._makeOne(None) environ = self._getEnviron() request = DummyRequest(environ, toraise=KeyError) result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_pathinfo_highorder(self): path = text_(b'/Qu\xc3\xa9bec', 'utf-8') foo = DummyContext(None, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) environ = self._getEnviron() request = DummyRequest(environ, path_info=path) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (path[1:],)) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_pathel_with_no_getitem(self): policy = self._makeOne(None) environ = self._getEnviron() request = DummyRequest(environ, path_info=text_('/foo/bar')) result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], 'foo') self.assertEqual(result['subpath'], ('bar',)) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_withconn_getitem_emptypath_nosubpath(self): root = DummyContext() policy = self._makeOne(root) environ = self._getEnviron() request = DummyRequest(environ, path_info=text_('')) result = policy(request) self.assertEqual(result['context'], root) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) def test_call_withconn_getitem_withpath_nosubpath(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron() request = DummyRequest(environ, path_info=text_('/foo/bar')) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('foo'),)) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) def test_call_withconn_getitem_withpath_withsubpath(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron() request = DummyRequest(environ, path_info=text_('/foo/bar/baz/buz')) result = policy(request) self.assertEqual(result['context'], foo) self.assertEqual(result['view_name'], 'bar') self.assertEqual(result['subpath'], ('baz', 'buz')) self.assertEqual(result['traversed'], (text_('foo'),)) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_explicit_viewname(self): foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron() request = DummyRequest(environ, path_info=text_('/@@foo')) result = policy(request) self.assertEqual(result['context'], root) self.assertEqual(result['view_name'], 'foo') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root(self): environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) request = DummyRequest(environ, path_info=text_('/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], bar) self.assertEqual(result['virtual_root_path'], (text_('foo'), text_('bar'))) def test_call_with_vh_root2(self): environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) request = DummyRequest(environ, path_info=text_('/bar/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], foo) self.assertEqual(result['virtual_root_path'], (text_('foo'),)) def test_call_with_vh_root3(self): environ = self._getEnviron(HTTP_X_VHM_ROOT='/') baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) policy = self._makeOne(root) request = DummyRequest(environ, path_info=text_('/foo/bar/baz')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root4(self): environ = self._getEnviron(HTTP_X_VHM_ROOT='/foo/bar/baz') baz = DummyContext(None, 'baz') bar = DummyContext(baz, 'bar') foo = DummyContext(bar, 'foo') root = DummyContext(foo, 'root') policy = self._makeOne(root) request = DummyRequest(environ, path_info=text_('/')) result = policy(request) self.assertEqual(result['context'], baz) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], (text_('foo'), text_('bar'), text_('baz'))) self.assertEqual(result['root'], root) self.assertEqual(result['virtual_root'], baz) self.assertEqual(result['virtual_root_path'], (text_('foo'), text_('bar'), text_('baz'))) def test_call_with_vh_root_path_root(self): policy = self._makeOne(None) environ = self._getEnviron(HTTP_X_VHM_ROOT='/') request = DummyRequest(environ, path_info=text_('/')) result = policy(request) self.assertEqual(result['context'], None) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], policy.root) self.assertEqual(result['virtual_root_path'], ()) def test_call_with_vh_root_highorder(self): path = text_(b'Qu\xc3\xa9bec', 'utf-8') bar = DummyContext(None, 'bar') foo = DummyContext(bar, path) root = DummyContext(foo, 'root') policy = self._makeOne(root) if PY3: vhm_root = b'/Qu\xc3\xa9bec'.decode('latin-1') else: vhm_root = b'/Qu\xc3\xa9bec' environ = self._getEnviron(HTTP_X_VHM_ROOT=vhm_root) request = DummyRequest(environ, path_info=text_('/bar')) result = policy(request) self.assertEqual(result['context'], bar) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual( result['traversed'], (path, text_('bar')) ) self.assertEqual(result['root'], policy.root) self.assertEqual(result['virtual_root'], foo) self.assertEqual( result['virtual_root_path'], (path,) ) def test_path_info_raises_unicodedecodeerror(self): from pyramid.exceptions import URLDecodeError foo = DummyContext() root = DummyContext(foo) policy = self._makeOne(root) environ = self._getEnviron() toraise = UnicodeDecodeError('ascii', b'a', 2, 3, '5') request = DummyRequest(environ, toraise=toraise) request.matchdict = None self.assertRaises(URLDecodeError, policy, request) def test_withroute_nothingfancy(self): resource = DummyContext() traverser = self._makeOne(resource) request = DummyRequest({}) request.matchdict = {} result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_with_subpath_string(self): resource = DummyContext() traverser = self._makeOne(resource) matchdict = {'subpath':'/a/b/c'} request = DummyRequest({}) request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ('a', 'b','c')) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_with_subpath_tuple(self): resource = DummyContext() traverser = self._makeOne(resource) matchdict = {'subpath':('a', 'b', 'c')} request = DummyRequest({}) request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ('a', 'b','c')) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_and_traverse_string(self): resource = DummyContext() traverser = self._makeOne(resource) matchdict = {'traverse':text_('foo/bar')} request = DummyRequest({}) request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], 'foo') self.assertEqual(result['subpath'], ('bar',)) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_and_traverse_tuple(self): resource = DummyContext() traverser = self._makeOne(resource) matchdict = {'traverse':('foo', 'bar')} request = DummyRequest({}) request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], 'foo') self.assertEqual(result['subpath'], ('bar',)) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_and_traverse_empty(self): resource = DummyContext() traverser = self._makeOne(resource) matchdict = {'traverse':''} request = DummyRequest({}) request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], resource) self.assertEqual(result['view_name'], '') self.assertEqual(result['subpath'], ()) self.assertEqual(result['traversed'], ()) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], resource) self.assertEqual(result['virtual_root_path'], ()) def test_withroute_and_traverse_and_vroot(self): abc = DummyContext() resource = DummyContext(next=abc) environ = self._getEnviron(HTTP_X_VHM_ROOT='/abc') request = DummyRequest(environ) traverser = self._makeOne(resource) matchdict = {'traverse':text_('/foo/bar')} request.matchdict = matchdict result = traverser(request) self.assertEqual(result['context'], abc) self.assertEqual(result['view_name'], 'foo') self.assertEqual(result['subpath'], ('bar',)) self.assertEqual(result['traversed'], ('abc', 'foo')) self.assertEqual(result['root'], resource) self.assertEqual(result['virtual_root'], abc) self.assertEqual(result['virtual_root_path'], ('abc',)) class FindInterfaceTests(unittest.TestCase): def _callFUT(self, context, iface): from pyramid.traversal import find_interface return find_interface(context, iface) def test_it_interface(self): baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) root.__parent__ = None root.__name__ = 'root' foo.__parent__ = root foo.__name__ = 'foo' bar.__parent__ = foo bar.__name__ = 'bar' baz.__parent__ = bar baz.__name__ = 'baz' from zope.interface import directlyProvides from zope.interface import Interface class IFoo(Interface): pass directlyProvides(root, IFoo) result = self._callFUT(baz, IFoo) self.assertEqual(result.__name__, 'root') def test_it_class(self): class DummyRoot(object): def __init__(self, child): self.child = child baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyRoot(foo) root.__parent__ = None root.__name__ = 'root' foo.__parent__ = root foo.__name__ = 'foo' bar.__parent__ = foo bar.__name__ = 'bar' baz.__parent__ = bar baz.__name__ = 'baz' result = self._callFUT(baz, DummyRoot) self.assertEqual(result.__name__, 'root') class FindRootTests(unittest.TestCase): def _callFUT(self, context): from pyramid.traversal import find_root return find_root(context) def test_it(self): dummy = DummyContext() baz = DummyContext() baz.__parent__ = dummy baz.__name__ = 'baz' dummy.__parent__ = None dummy.__name__ = None result = self._callFUT(baz) self.assertEqual(result, dummy) class FindResourceTests(unittest.TestCase): def _callFUT(self, context, name): from pyramid.traversal import find_resource return find_resource(context, name) def _registerTraverser(self, traverser): from pyramid.threadlocal import get_current_registry reg = get_current_registry() from pyramid.interfaces import ITraverser from zope.interface import Interface reg.registerAdapter(traverser, (Interface,), ITraverser) def test_list(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, ['']) self.assertEqual(result, resource) self.assertEqual(resource.request.environ['PATH_INFO'], '/') def test_generator(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) def foo(): yield '' result = self._callFUT(resource, foo()) self.assertEqual(result, resource) self.assertEqual(resource.request.environ['PATH_INFO'], '/') def test_self_string_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, '') self.assertEqual(result, resource) self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_self_tuple_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, ()) self.assertEqual(result, resource) self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_relative_string_found(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, 'baz') self.assertEqual(result, baz) self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_relative_tuple_found(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, ('baz',)) self.assertEqual(result, baz) self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_relative_string_notfound(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':'bar'}) self._registerTraverser(traverser) self.assertRaises(KeyError, self._callFUT, resource, 'baz') self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_relative_tuple_notfound(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':'bar'}) self._registerTraverser(traverser) self.assertRaises(KeyError, self._callFUT, resource, ('baz',)) self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_absolute_string_found(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, '/') self.assertEqual(result, root) self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_absolute_tuple_found(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverser(traverser) result = self._callFUT(resource, ('',)) self.assertEqual(result, root) self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_absolute_string_notfound(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':'fuz'}) self._registerTraverser(traverser) self.assertRaises(KeyError, self._callFUT, resource, '/') self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_absolute_tuple_notfound(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':'fuz'}) self._registerTraverser(traverser) self.assertRaises(KeyError, self._callFUT, resource, ('',)) self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_absolute_unicode_found(self): # test for bug wiggy found in wild, traceback stack: # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' # wiggy's code: section=find_resource(page, root) # find_resource L76: D = traverse(resource, path) # traverse L291: return traverser(request) # __call__ line 568: vpath_tuple = traversal_path(vpath) # lru_cached line 91: f(*arg) # traversal_path line 443: path.encode('ascii') # UnicodeEncodeError: 'ascii' codec can't encode characters in # position 1-12: ordinal not in range(128) # # solution: encode string to ascii in pyramid.traversal.traverse # before passing it along to webob as path_info from pyramid.traversal import ResourceTreeTraverser unprintable = DummyContext() root = DummyContext(unprintable) unprintable.__parent__ = root unprintable.__name__ = text_( b'/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8') root.__parent__ = None root.__name__ = None traverser = ResourceTreeTraverser self._registerTraverser(traverser) result = self._callFUT( root, text_(b'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') ) self.assertEqual(result, unprintable) class ResourcePathTests(unittest.TestCase): def _callFUT(self, resource, *elements): from pyramid.traversal import resource_path return resource_path(resource, *elements) def test_it(self): baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) root.__parent__ = None root.__name__ = None foo.__parent__ = root foo.__name__ = 'foo ' bar.__parent__ = foo bar.__name__ = 'bar' baz.__parent__ = bar baz.__name__ = 'baz' result = self._callFUT(baz, 'this/theotherthing', 'that') self.assertEqual(result, '/foo%20/bar/baz/this%2Ftheotherthing/that') def test_root_default(self): root = DummyContext() root.__parent__ = None root.__name__ = None result = self._callFUT(root) self.assertEqual(result, '/') def test_root_default_emptystring(self): root = DummyContext() root.__parent__ = None root.__name__ = '' result = self._callFUT(root) self.assertEqual(result, '/') def test_root_object_nonnull_name_direct(self): root = DummyContext() root.__parent__ = None root.__name__ = 'flubadub' result = self._callFUT(root) self.assertEqual(result, 'flubadub') # insane case def test_root_object_nonnull_name_indirect(self): root = DummyContext() root.__parent__ = None root.__name__ = 'flubadub' other = DummyContext() other.__parent__ = root other.__name__ = 'barker' result = self._callFUT(other) self.assertEqual(result, 'flubadub/barker') # insane case def test_nonroot_default(self): root = DummyContext() root.__parent__ = None root.__name__ = None other = DummyContext() other.__parent__ = root other.__name__ = 'other' result = self._callFUT(other) self.assertEqual(result, '/other') def test_path_with_None_itermediate_names(self): root = DummyContext() root.__parent__ = None root.__name__ = None other = DummyContext() other.__parent__ = root other.__name__ = None other2 = DummyContext() other2.__parent__ = other other2.__name__ = 'other2' result = self._callFUT(other2) self.assertEqual(result, '//other2') class ResourcePathTupleTests(unittest.TestCase): def _callFUT(self, resource, *elements): from pyramid.traversal import resource_path_tuple return resource_path_tuple(resource, *elements) def test_it(self): baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) root.__parent__ = None root.__name__ = None foo.__parent__ = root foo.__name__ = 'foo ' bar.__parent__ = foo bar.__name__ = 'bar' baz.__parent__ = bar baz.__name__ = 'baz' result = self._callFUT(baz, 'this/theotherthing', 'that') self.assertEqual(result, ('','foo ', 'bar', 'baz', 'this/theotherthing', 'that')) def test_root_default(self): root = DummyContext() root.__parent__ = None root.__name__ = None result = self._callFUT(root) self.assertEqual(result, ('',)) def test_root_default_emptystring_name(self): root = DummyContext() root.__parent__ = None root.__name__ = '' other = DummyContext() other.__parent__ = root other.__name__ = 'other' result = self._callFUT(other) self.assertEqual(result, ('', 'other',)) def test_nonroot_default(self): root = DummyContext() root.__parent__ = None root.__name__ = None other = DummyContext() other.__parent__ = root other.__name__ = 'other' result = self._callFUT(other) self.assertEqual(result, ('', 'other')) def test_path_with_None_itermediate_names(self): root = DummyContext() root.__parent__ = None root.__name__ = None other = DummyContext() other.__parent__ = root other.__name__ = None other2 = DummyContext() other2.__parent__ = other other2.__name__ = 'other2' result = self._callFUT(other2) self.assertEqual(result, ('', '', 'other2')) class QuotePathSegmentTests(unittest.TestCase): def _callFUT(self, s): from pyramid.traversal import quote_path_segment return quote_path_segment(s) def test_unicode(self): la = text_(b'/La Pe\xc3\xb1a', 'utf-8') result = self._callFUT(la) self.assertEqual(result, '%2FLa%20Pe%C3%B1a') def test_string(self): s = '/ hello!' result = self._callFUT(s) self.assertEqual(result, '%2F%20hello%21') def test_int(self): s = 12345 result = self._callFUT(s) self.assertEqual(result, '12345') def test_long(self): from pyramid.compat import long import sys s = long(sys.maxsize + 1) result = self._callFUT(s) expected = str(s) self.assertEqual(result, expected) def test_other(self): class Foo(object): def __str__(self): return 'abc' s = Foo() result = self._callFUT(s) self.assertEqual(result, 'abc') class ResourceURLTests(unittest.TestCase): def _makeOne(self, context, url): return self._getTargetClass()(context, url) def _getTargetClass(self): from pyramid.traversal import ResourceURL return ResourceURL def _registerTraverser(self, traverser): from pyramid.threadlocal import get_current_registry reg = get_current_registry() from pyramid.interfaces import ITraverser from zope.interface import Interface reg.registerAdapter(traverser, (Interface,), ITraverser) def test_class_conforms_to_IContextURL(self): # bw compat from zope.interface.verify import verifyClass verifyClass(IContextURL, self._getTargetClass()) def test_instance_conforms_to_IContextURL(self): from zope.interface.verify import verifyObject context = DummyContext() request = DummyRequest() verifyObject(IContextURL, self._makeOne(context, request)) def test_instance_conforms_to_IResourceURL(self): from pyramid.interfaces import IResourceURL from zope.interface.verify import verifyObject context = DummyContext() request = DummyRequest() verifyObject(IResourceURL, self._makeOne(context, request)) def test_call_withlineage(self): baz = DummyContext() bar = DummyContext(baz) foo = DummyContext(bar) root = DummyContext(foo) root.__parent__ = None root.__name__ = None foo.__parent__ = root foo.__name__ = 'foo ' bar.__parent__ = foo bar.__name__ = 'bar' baz.__parent__ = bar baz.__name__ = 'baz' request = DummyRequest() context_url = self._makeOne(baz, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/foo%20/bar/baz/') def test_call_nolineage(self): context = DummyContext() context.__name__ = '' context.__parent__ = None request = DummyRequest() context_url = self._makeOne(context, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/') def test_call_unicode_mixed_with_bytes_in_resource_names(self): root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = text_(b'La Pe\xc3\xb1a', 'utf-8') two = DummyContext() two.__parent__ = one two.__name__ = b'La Pe\xc3\xb1a' request = DummyRequest() context_url = self._makeOne(two, request) result = context_url() self.assertEqual( result, 'http://example.com:5432/La%20Pe%C3%B1a/La%20Pe%C3%B1a/') def test_call_with_virtual_root_path(self): from pyramid.interfaces import VH_ROOT_KEY root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = 'one' two = DummyContext() two.__parent__ = one two.__name__ = 'two' request = DummyRequest({VH_ROOT_KEY:'/one'}) context_url = self._makeOne(two, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/two/') request = DummyRequest({VH_ROOT_KEY:'/one/two'}) context_url = self._makeOne(two, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/') def test_call_with_virtual_root_path_physical_not_startwith_vroot(self): from pyramid.interfaces import VH_ROOT_KEY root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = 'one' two = DummyContext() two.__parent__ = one two.__name__ = 'two' request = DummyRequest({VH_ROOT_KEY:'/wrong'}) context_url = self._makeOne(two, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/one/two/') def test_call_empty_names_not_ignored(self): bar = DummyContext() empty = DummyContext(bar) root = DummyContext(empty) root.__parent__ = None root.__name__ = None empty.__parent__ = root empty.__name__ = '' bar.__parent__ = empty bar.__name__ = 'bar' request = DummyRequest() context_url = self._makeOne(bar, request) result = context_url() self.assertEqual(result, 'http://example.com:5432//bar/') def test_call_local_url_returns_None(self): resource = DummyContext() def resource_url(request, info): self.assertEqual(info['virtual_path'], '/') self.assertEqual(info['physical_path'], '/') return None resource.__resource_url__ = resource_url request = DummyRequest() context_url = self._makeOne(resource, request) result = context_url() self.assertEqual(result, 'http://example.com:5432/') def test_call_local_url_returns_url(self): resource = DummyContext() def resource_url(request, info): self.assertEqual(info['virtual_path'], '/') self.assertEqual(info['physical_path'], '/') return 'abc' resource.__resource_url__ = resource_url request = DummyRequest() context_url = self._makeOne(resource, request) result = context_url() self.assertEqual(result, 'abc') def test_virtual_root_no_virtual_root_path(self): root = DummyContext() root.__name__ = None root.__parent__ = None one = DummyContext() one.__name__ = 'one' one.__parent__ = root request = DummyRequest() context_url = self._makeOne(one, request) self.assertEqual(context_url.virtual_root(), root) def test_virtual_root_no_virtual_root_path_with_root_on_request(self): context = DummyContext() context.__parent__ = None request = DummyRequest() request.root = DummyContext() context_url = self._makeOne(context, request) self.assertEqual(context_url.virtual_root(), request.root) def test_virtual_root_with_virtual_root_path(self): from pyramid.interfaces import VH_ROOT_KEY context = DummyContext() context.__parent__ = None traversed_to = DummyContext() environ = {VH_ROOT_KEY:'/one'} request = DummyRequest(environ) traverser = make_traverser({'context':traversed_to, 'view_name':''}) self._registerTraverser(traverser) context_url = self._makeOne(context, request) self.assertEqual(context_url.virtual_root(), traversed_to) self.assertEqual(context.request.environ['PATH_INFO'], '/one') def test_IResourceURL_attributes_with_vroot(self): from pyramid.interfaces import VH_ROOT_KEY root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = 'one' two = DummyContext() two.__parent__ = one two.__name__ = 'two' environ = {VH_ROOT_KEY:'/one'} request = DummyRequest(environ) context_url = self._makeOne(two, request) self.assertEqual(context_url.physical_path, '/one/two/') self.assertEqual(context_url.virtual_path, '/two/') self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) self.assertEqual(context_url.virtual_path_tuple, ('', 'two', '')) def test_IResourceURL_attributes_vroot_ends_with_slash(self): from pyramid.interfaces import VH_ROOT_KEY root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = 'one' two = DummyContext() two.__parent__ = one two.__name__ = 'two' environ = {VH_ROOT_KEY:'/one/'} request = DummyRequest(environ) context_url = self._makeOne(two, request) self.assertEqual(context_url.physical_path, '/one/two/') self.assertEqual(context_url.virtual_path, '/two/') self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) self.assertEqual(context_url.virtual_path_tuple, ('', 'two', '')) def test_IResourceURL_attributes_no_vroot(self): root = DummyContext() root.__parent__ = None root.__name__ = None one = DummyContext() one.__parent__ = root one.__name__ = 'one' two = DummyContext() two.__parent__ = one two.__name__ = 'two' environ = {} request = DummyRequest(environ) context_url = self._makeOne(two, request) self.assertEqual(context_url.physical_path, '/one/two/') self.assertEqual(context_url.virtual_path, '/one/two/') self.assertEqual(context_url.physical_path_tuple, ('', 'one', 'two','')) self.assertEqual(context_url.virtual_path_tuple, ('', 'one', 'two', '')) class TestVirtualRoot(unittest.TestCase): def setUp(self): cleanUp() def tearDown(self): cleanUp() def _callFUT(self, resource, request): from pyramid.traversal import virtual_root return virtual_root(resource, request) def test_registered(self): from zope.interface import Interface request = _makeRequest() request.registry.registerAdapter(DummyContextURL, (Interface,Interface), IContextURL) context = DummyContext() result = self._callFUT(context, request) self.assertEqual(result, '123') def test_default(self): context = DummyContext() request = _makeRequest() request.environ['PATH_INFO'] = '/' result = self._callFUT(context, request) self.assertEqual(result, context) def test_default_no_registry_on_request(self): context = DummyContext() request = _makeRequest() del request.registry request.environ['PATH_INFO'] = '/' result = self._callFUT(context, request) self.assertEqual(result, context) class TraverseTests(unittest.TestCase): def setUp(self): cleanUp() def tearDown(self): cleanUp() def _callFUT(self, context, name): from pyramid.traversal import traverse return traverse(context, name) def _registerTraverser(self, traverser): from pyramid.threadlocal import get_current_registry reg = get_current_registry() from pyramid.interfaces import ITraverser from zope.interface import Interface reg.registerAdapter(traverser, (Interface,), ITraverser) def test_request_has_registry(self): from pyramid.threadlocal import get_current_registry resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ['']) self.assertEqual(resource.request.registry, get_current_registry()) def test_list(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ['']) self.assertEqual(resource.request.environ['PATH_INFO'], '/') def test_generator(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) def foo(): yield '' self._callFUT(resource, foo()) self.assertEqual(resource.request.environ['PATH_INFO'], '/') def test_self_string_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, '') self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_self_unicode_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, text_('')) self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_self_tuple_found(self): resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ()) self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_relative_string_found(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, 'baz') self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_relative_tuple_found(self): resource = DummyContext() baz = DummyContext() traverser = make_traverser({'context':baz, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ('baz',)) self.assertEqual(resource.request.environ['PATH_INFO'], 'baz') def test_absolute_string_found(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, '/') self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_absolute_tuple_found(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ('',)) self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') def test_empty_sequence(self): root = DummyContext() resource = DummyContext() resource.__parent__ = root resource.__name__ = 'baz' traverser = make_traverser({'context':root, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, []) self.assertEqual(resource.wascontext, True) self.assertEqual(resource.request.environ['PATH_INFO'], '') def test_default_traverser(self): resource = DummyContext() result = self._callFUT(resource, '') self.assertEqual(result['view_name'], '') self.assertEqual(result['context'], resource) def test_requestfactory_overridden(self): from pyramid.interfaces import IRequestFactory from pyramid.request import Request from pyramid.threadlocal import get_current_registry reg = get_current_registry() class MyRequest(Request): pass reg.registerUtility(MyRequest, IRequestFactory) resource = DummyContext() traverser = make_traverser({'context':resource, 'view_name':''}) self._registerTraverser(traverser) self._callFUT(resource, ['']) self.assertEqual(resource.request.__class__, MyRequest) class TestDefaultRootFactory(unittest.TestCase): def _getTargetClass(self): from pyramid.traversal import DefaultRootFactory return DefaultRootFactory def _makeOne(self, environ): return self._getTargetClass()(environ) def test_it(self): class DummyRequest(object): pass root = self._makeOne(DummyRequest()) self.assertEqual(root.__parent__, None) self.assertEqual(root.__name__, None) class Test__join_path_tuple(unittest.TestCase): def _callFUT(self, tup): from pyramid.traversal import _join_path_tuple return _join_path_tuple(tup) def test_empty_tuple(self): # tests "or '/'" case result = self._callFUT(()) self.assertEqual(result, '/') def test_nonempty_tuple(self): result = self._callFUT(('x',)) self.assertEqual(result, 'x') def make_traverser(result): class DummyTraverser(object): def __init__(self, context): self.context = context context.wascontext = True def __call__(self, request): self.context.request = request return result return DummyTraverser class DummyContext(object): __parent__ = None def __init__(self, next=None, name=None): self.next = next self.__name__ = name def __getitem__(self, name): if self.next is None: raise KeyError(name) return self.next def __repr__(self): return ''%(self.__name__, id(self)) class DummyRequest: application_url = 'http://example.com:5432' # app_url never ends with slash matchdict = None matched_route = None def __init__(self, environ=None, path_info=text_('/'), toraise=None): if environ is None: environ = {} self.environ = environ self._set_path_info(path_info) self.toraise = toraise def _get_path_info(self): if self.toraise: raise self.toraise return self._path_info def _set_path_info(self, v): self._path_info = v path_info = property(_get_path_info, _set_path_info) class DummyContextURL: def __init__(self, context, request): pass def virtual_root(self): return '123' def _makeRequest(environ=None): from pyramid.registry import Registry request = DummyRequest() request.registry = Registry() return request pyramid-1.6/pyramid/tests/test_url.py0000644000076500000240000014671612520062551020611 0ustar michaelstaff00000000000000import os import unittest import warnings from pyramid import testing from pyramid.compat import ( text_, WIN, ) class TestURLMethodsMixin(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeOne(self, environ=None): from pyramid.url import URLMethodsMixin if environ is None: environ = {} class Request(URLMethodsMixin): application_url = 'http://example.com:5432' script_name = '' def __init__(self, environ): self.environ = environ request = Request(environ) request.registry = self.config.registry return request def _registerContextURL(self, reg): with warnings.catch_warnings(record=True): from pyramid.interfaces import IContextURL from zope.interface import Interface class DummyContextURL(object): def __init__(self, context, request): pass def __call__(self): return 'http://example.com/context/' reg.registerAdapter(DummyContextURL, (Interface, Interface), IContextURL) def _registerResourceURL(self, reg): from pyramid.interfaces import IResourceURL from zope.interface import Interface class DummyResourceURL(object): physical_path = '/context/' virtual_path = '/context/' def __init__(self, context, request): pass reg.registerAdapter(DummyResourceURL, (Interface, Interface), IResourceURL) return DummyResourceURL def test_resource_url_root_default(self): request = self._makeOne() self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_url(root) self.assertEqual(result, 'http://example.com:5432/context/') def test_resource_url_extra_args(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'this/theotherthing', 'that') self.assertEqual( result, 'http://example.com:5432/context/this%2Ftheotherthing/that') def test_resource_url_unicode_in_element_names(self): request = self._makeOne() self._registerResourceURL(request.registry) uc = text_(b'La Pe\xc3\xb1a', 'utf-8') context = DummyContext() result = request.resource_url(context, uc) self.assertEqual(result, 'http://example.com:5432/context/La%20Pe%C3%B1a') def test_resource_url_at_sign_in_element_names(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, '@@myview') self.assertEqual(result, 'http://example.com:5432/context/@@myview') def test_resource_url_element_names_url_quoted(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'a b c') self.assertEqual(result, 'http://example.com:5432/context/a%20b%20c') def test_resource_url_with_query_str(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'a', query='(openlayers)') self.assertEqual(result, 'http://example.com:5432/context/a?(openlayers)') def test_resource_url_with_query_dict(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, 'a', query={'a':uc}) self.assertEqual(result, 'http://example.com:5432/context/a?a=La+Pe%C3%B1a') def test_resource_url_with_query_seq(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, 'a', query=[('a', 'hi there'), ('b', uc)]) self.assertEqual(result, 'http://example.com:5432/context/a?a=hi+there&b=La+Pe%C3%B1a') def test_resource_url_with_query_empty(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'a', query=[]) self.assertEqual(result, 'http://example.com:5432/context/a') def test_resource_url_anchor_is_after_root_when_no_elements(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, anchor='a') self.assertEqual(result, 'http://example.com:5432/context/#a') def test_resource_url_anchor_is_after_elements_when_no_qs(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'a', anchor='b') self.assertEqual(result, 'http://example.com:5432/context/a#b') def test_resource_url_anchor_is_after_qs_when_qs_is_present(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, 'a', query={'b':'c'}, anchor='d') self.assertEqual(result, 'http://example.com:5432/context/a?b=c#d') def test_resource_url_anchor_is_encoded_utf8_if_unicode(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() uc = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.resource_url(context, anchor=uc) self.assertEqual(result, 'http://example.com:5432/context/#La%20Pe%C3%B1a') def test_resource_url_anchor_is_urlencoded_safe(self): request = self._makeOne() self._registerResourceURL(request.registry) context = DummyContext() result = request.resource_url(context, anchor=' /#?&+') self.assertEqual(result, 'http://example.com:5432/context/#%20/%23?&+') def test_resource_url_no_IResourceURL_registered(self): # falls back to ResourceURL root = DummyContext() root.__name__ = '' root.__parent__ = None request = self._makeOne() request.environ = {} result = request.resource_url(root) self.assertEqual(result, 'http://example.com:5432/') def test_resource_url_no_registry_on_request(self): request = self._makeOne() self._registerResourceURL(request.registry) del request.registry root = DummyContext() result = request.resource_url(root) self.assertEqual(result, 'http://example.com:5432/context/') def test_resource_url_finds_IContextURL(self): request = self._makeOne() self._registerContextURL(request.registry) root = DummyContext() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') result = request.resource_url(root) self.assertEqual(len(w), 1) self.assertEqual(result, 'http://example.com/context/') def test_resource_url_with_app_url(self): request = self._makeOne() self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_url(root, app_url='http://somewhere.com') self.assertEqual(result, 'http://somewhere.com/context/') def test_resource_url_with_scheme(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_url(root, scheme='https') self.assertEqual(result, 'https://example.com/context/') def test_resource_url_with_host(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_url(root, host='someotherhost.com') self.assertEqual(result, 'http://someotherhost.com:8080/context/') def test_resource_url_with_port(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_url(root, port='8181') self.assertEqual(result, 'http://example.com:8181/context/') def test_resource_url_with_local_url(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) self._registerResourceURL(request.registry) root = DummyContext() def resource_url(req, info): self.assertEqual(req, request) self.assertEqual(info['virtual_path'], '/context/') self.assertEqual(info['physical_path'], '/context/') self.assertEqual(info['app_url'], 'http://example.com:5432') return 'http://example.com/contextabc/' root.__resource_url__ = resource_url result = request.resource_url(root) self.assertEqual(result, 'http://example.com/contextabc/') def test_resource_url_with_route_name_no_remainder_on_adapter(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # no virtual_path_tuple on adapter adapter.virtual_path = '/a/b/c/' route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, route_name='foo') self.assertEqual(result, 'http://example.com:5432/1/2/3') self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) def test_resource_url_with_route_name_remainder_on_adapter(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, route_name='foo') self.assertEqual(result, 'http://example.com:5432/1/2/3') self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) def test_resource_url_with_route_name_and_app_url(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, route_name='foo', app_url='app_url') self.assertEqual(result, 'app_url/1/2/3') self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) def test_resource_url_with_route_name_and_scheme_host_port_etc(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, route_name='foo', scheme='scheme', host='host', port='port', query={'a':'1'}, anchor='anchor') self.assertEqual(result, 'scheme://host:port/1/2/3?a=1#anchor') self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) def test_resource_url_with_route_name_and_route_kwargs(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url( root, route_name='foo', route_kw={'a':'1', 'b':'2'}) self.assertEqual(result, 'http://example.com:5432/1/2/3') self.assertEqual( route.kw, {'traverse': ('', 'a', 'b', 'c', ''), 'a':'1', 'b':'2'} ) def test_resource_url_with_route_name_and_elements(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, 'e1', 'e2', route_name='foo') self.assertEqual(result, 'http://example.com:5432/1/2/3/e1/e2') self.assertEqual(route.kw, {'traverse': ('', 'a', 'b', 'c', '')}) def test_resource_url_with_route_name_and_remainder_name(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'8080', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) adapter = self._registerResourceURL(request.registry) # virtual_path_tuple on adapter adapter.virtual_path_tuple = ('', 'a', 'b', 'c', '') route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route) request.registry.registerUtility(mapper, IRoutesMapper) root = DummyContext() result = request.resource_url(root, route_name='foo', route_remainder_name='fred') self.assertEqual(result, 'http://example.com:5432/1/2/3') self.assertEqual(route.kw, {'fred': ('', 'a', 'b', 'c', '')}) def test_resource_path(self): request = self._makeOne() self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_path(root) self.assertEqual(result, '/context/') def test_resource_path_kwarg(self): request = self._makeOne() self._registerResourceURL(request.registry) root = DummyContext() result = request.resource_path(root, anchor='abc') self.assertEqual(result, '/context/#abc') def test_route_url_with_elements(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', 'extra1', 'extra2') self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2') def test_route_url_with_elements_path_endswith_slash(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3/')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', 'extra1', 'extra2') self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2') def test_route_url_no_elements(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', a=1, b=2, c=3, _query={'a':1}, _anchor=text_(b"foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3?a=1#foo') def test_route_url_with_anchor_binary(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _anchor=b"La Pe\xc3\xb1a") self.assertEqual(result, 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a') def test_route_url_with_anchor_unicode(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) anchor = text_(b'La Pe\xc3\xb1a', 'utf-8') result = request.route_url('flub', _anchor=anchor) self.assertEqual(result, 'http://example.com:5432/1/2/3#La%20Pe%C3%B1a') def test_route_url_with_query(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _query={'q':'1'}) self.assertEqual(result, 'http://example.com:5432/1/2/3?q=1') def test_route_url_with_query_str(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _query='(openlayers)') self.assertEqual(result, 'http://example.com:5432/1/2/3?(openlayers)') def test_route_url_with_empty_query(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _query={}) self.assertEqual(result, 'http://example.com:5432/1/2/3') def test_route_url_with_app_url(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _app_url='http://example2.com') self.assertEqual(result, 'http://example2.com/1/2/3') def test_route_url_with_host(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'5432', } request = self._makeOne(environ) mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _host='someotherhost.com') self.assertEqual(result, 'http://someotherhost.com:5432/1/2/3') def test_route_url_with_port(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'5432', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _port='8080') self.assertEqual(result, 'http://example.com:8080/1/2/3') def test_route_url_with_scheme(self): from pyramid.interfaces import IRoutesMapper environ = { 'wsgi.url_scheme':'http', 'SERVER_PORT':'5432', 'SERVER_NAME':'example.com', } request = self._makeOne(environ) mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _scheme='https') self.assertEqual(result, 'https://example.com/1/2/3') def test_route_url_generation_error(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(raise_exc=KeyError) request.registry.registerUtility(mapper, IRoutesMapper) mapper.raise_exc = KeyError self.assertRaises(KeyError, request.route_url, 'flub', request, a=1) def test_route_url_generate_doesnt_receive_query_or_anchor(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() route = DummyRoute(result='') mapper = DummyRoutesMapper(route=route) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', _query=dict(name='some_name')) self.assertEqual(route.kw, {}) # shouldnt have anchor/query self.assertEqual(result, 'http://example.com:5432?name=some_name') def test_route_url_with_pregenerator(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() route = DummyRoute(result='/1/2/3') def pregenerator(request, elements, kw): return ('a',), {'_app_url':'http://example2.com'} route.pregenerator = pregenerator mapper = DummyRoutesMapper(route=route) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub') self.assertEqual(result, 'http://example2.com/1/2/3/a') self.assertEqual(route.kw, {}) # shouldnt have anchor/query def test_route_url_with_anchor_app_url_elements_and_query(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute(result='/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', 'element1', _app_url='http://example2.com', _anchor='anchor', _query={'q':'1'}) self.assertEqual(result, 'http://example2.com/1/2/3/element1?q=1#anchor') def test_route_url_integration_with_real_request(self): # to try to replicate https://github.com/Pylons/pyramid/issues/213 from pyramid.interfaces import IRoutesMapper from pyramid.request import Request request = Request.blank('/') request.registry = self.config.registry mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_url('flub', 'extra1', 'extra2') self.assertEqual(result, 'http://localhost/1/2/3/extra1/extra2') def test_current_route_url_current_request_has_no_route(self): request = self._makeOne() self.assertRaises(ValueError, request.current_route_url) def test_current_route_url_with_elements_query_and_anchor(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url('extra1', 'extra2', _query={'a':1}, _anchor=text_(b"foo")) self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') def test_current_route_url_with_route_name(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url('extra1', 'extra2', _query={'a':1}, _anchor=text_(b"foo"), _route_name='bar') self.assertEqual(result, 'http://example.com:5432/1/2/3/extra1/extra2?a=1#foo') def test_current_route_url_with_request_query(self): from pyramid.interfaces import IRoutesMapper from webob.multidict import GetDict request = self._makeOne() request.GET = GetDict([('q', '123')], {}) route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url() self.assertEqual(result, 'http://example.com:5432/1/2/3?q=123') def test_current_route_url_with_request_query_duplicate_entries(self): from pyramid.interfaces import IRoutesMapper from webob.multidict import GetDict request = self._makeOne() request.GET = GetDict( [('q', '123'), ('b', '2'), ('b', '2'), ('q', '456')], {}) route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url() self.assertEqual(result, 'http://example.com:5432/1/2/3?q=123&b=2&b=2&q=456') def test_current_route_url_with_query_override(self): from pyramid.interfaces import IRoutesMapper from webob.multidict import GetDict request = self._makeOne() request.GET = GetDict([('q', '123')], {}) route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_url(_query={'a':1}) self.assertEqual(result, 'http://example.com:5432/1/2/3?a=1') def test_current_route_path(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() route = DummyRoute('/1/2/3') mapper = DummyRoutesMapper(route=route) request.matched_route = route request.matchdict = {} request.script_name = '/script_name' request.registry.registerUtility(mapper, IRoutesMapper) result = request.current_route_path('extra1', 'extra2', _query={'a':1}, _anchor=text_(b"foo")) self.assertEqual(result, '/script_name/1/2/3/extra1/extra2?a=1#foo') def test_route_path_with_elements(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) request.script_name = '' result = request.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, _anchor=text_(b"foo")) self.assertEqual(result, '/1/2/3/extra1/extra2?a=1#foo') def test_route_path_with_script_name(self): from pyramid.interfaces import IRoutesMapper request = self._makeOne() request.script_name = '/foo' mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3')) request.registry.registerUtility(mapper, IRoutesMapper) result = request.route_path('flub', 'extra1', 'extra2', a=1, b=2, c=3, _query={'a':1}, _anchor=text_(b"foo")) self.assertEqual(result, '/foo/1/2/3/extra1/extra2?a=1#foo') def test_static_url_staticurlinfo_notfound(self): request = self._makeOne() self.assertRaises(ValueError, request.static_url, 'static/foo.css') def test_static_url_abspath(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() info = DummyStaticURLInfo('abc') registry = request.registry registry.registerUtility(info, IStaticURLInfo) abspath = makeabs('static', 'foo.css') result = request.static_url(abspath) self.assertEqual(result, 'abc') self.assertEqual(info.args, (makeabs('static', 'foo.css'), request, {})) request = self._makeOne() def test_static_url_found_rel(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() info = DummyStaticURLInfo('abc') request.registry.registerUtility(info, IStaticURLInfo) result = request.static_url('static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {}) ) def test_static_url_abs(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() info = DummyStaticURLInfo('abc') request.registry.registerUtility(info, IStaticURLInfo) result = request.static_url('pyramid.tests:static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {}) ) def test_static_url_found_abs_no_registry_on_request(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() registry = request.registry info = DummyStaticURLInfo('abc') registry.registerUtility(info, IStaticURLInfo) del request.registry result = request.static_url('pyramid.tests:static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {}) ) def test_static_url_abspath_integration_with_staticurlinfo(self): from pyramid.interfaces import IStaticURLInfo from pyramid.config.views import StaticURLInfo info = StaticURLInfo() here = os.path.abspath(os.path.dirname(__file__)) info.add(self.config, 'absstatic', here) request = self._makeOne() registry = request.registry registry.registerUtility(info, IStaticURLInfo) abspath = os.path.join(here, 'test_url.py') result = request.static_url(abspath) self.assertEqual(result, 'http://example.com:5432/absstatic/test_url.py') def test_static_url_noscheme_uses_scheme_from_request(self): from pyramid.interfaces import IStaticURLInfo from pyramid.config.views import StaticURLInfo info = StaticURLInfo() here = os.path.abspath(os.path.dirname(__file__)) info.add(self.config, '//subdomain.example.com/static', here) request = self._makeOne({'wsgi.url_scheme': 'https'}) registry = request.registry registry.registerUtility(info, IStaticURLInfo) abspath = os.path.join(here, 'test_url.py') result = request.static_url(abspath) self.assertEqual(result, 'https://subdomain.example.com/static/test_url.py') def test_static_path_abspath(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() request.script_name = '/foo' info = DummyStaticURLInfo('abc') registry = request.registry registry.registerUtility(info, IStaticURLInfo) abspath = makeabs('static', 'foo.css') result = request.static_path(abspath) self.assertEqual(result, 'abc') self.assertEqual(info.args, (makeabs('static', 'foo.css'), request, {'_app_url':'/foo'}) ) def test_static_path_found_rel(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() request.script_name = '/foo' info = DummyStaticURLInfo('abc') request.registry.registerUtility(info, IStaticURLInfo) result = request.static_path('static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {'_app_url':'/foo'}) ) def test_static_path_abs(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() request.script_name = '/foo' info = DummyStaticURLInfo('abc') request.registry.registerUtility(info, IStaticURLInfo) result = request.static_path('pyramid.tests:static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {'_app_url':'/foo'}) ) def test_static_path(self): from pyramid.interfaces import IStaticURLInfo request = self._makeOne() request.script_name = '/foo' info = DummyStaticURLInfo('abc') request.registry.registerUtility(info, IStaticURLInfo) result = request.static_path('static/foo.css') self.assertEqual(result, 'abc') self.assertEqual(info.args, ('pyramid.tests:static/foo.css', request, {'_app_url':'/foo'}) ) def test_partial_application_url_with_http_host_default_port_http(self): environ = { 'wsgi.url_scheme':'http', 'HTTP_HOST':'example.com:80', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'http://example.com') def test_partial_application_url_with_http_host_default_port_https(self): environ = { 'wsgi.url_scheme':'https', 'HTTP_HOST':'example.com:443', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'https://example.com') def test_partial_application_url_with_http_host_nondefault_port_http(self): environ = { 'wsgi.url_scheme':'http', 'HTTP_HOST':'example.com:8080', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'http://example.com:8080') def test_partial_application_url_with_http_host_nondefault_port_https(self): environ = { 'wsgi.url_scheme':'https', 'HTTP_HOST':'example.com:4443', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'https://example.com:4443') def test_partial_application_url_with_http_host_no_colon(self): environ = { 'wsgi.url_scheme':'http', 'HTTP_HOST':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'http://example.com') def test_partial_application_url_no_http_host(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url() self.assertEqual(result, 'http://example.com') def test_partial_application_replace_port(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url(port=8080) self.assertEqual(result, 'http://example.com:8080') def test_partial_application_replace_scheme_https_special_case(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url(scheme='https') self.assertEqual(result, 'https://example.com') def test_partial_application_replace_scheme_https_special_case_avoid(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url(scheme='https', port='8080') self.assertEqual(result, 'https://example.com:8080') def test_partial_application_replace_scheme_http_special_case(self): environ = { 'wsgi.url_scheme':'https', 'SERVER_NAME':'example.com', 'SERVER_PORT':'8080', } request = self._makeOne(environ) result = request._partial_application_url(scheme='http') self.assertEqual(result, 'http://example.com') def test_partial_application_replace_scheme_http_special_case_avoid(self): environ = { 'wsgi.url_scheme':'https', 'SERVER_NAME':'example.com', 'SERVER_PORT':'8000', } request = self._makeOne(environ) result = request._partial_application_url(scheme='http', port='8080') self.assertEqual(result, 'http://example.com:8080') def test_partial_application_replace_host_no_port(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url(host='someotherhost.com') self.assertEqual(result, 'http://someotherhost.com') def test_partial_application_replace_host_with_port(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'8000', } request = self._makeOne(environ) result = request._partial_application_url(host='someotherhost.com:8080') self.assertEqual(result, 'http://someotherhost.com:8080') def test_partial_application_replace_host_and_port(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url(host='someotherhost.com:8080', port='8000') self.assertEqual(result, 'http://someotherhost.com:8000') def test_partial_application_replace_host_port_and_scheme(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'80', } request = self._makeOne(environ) result = request._partial_application_url( host='someotherhost.com:8080', port='8000', scheme='https', ) self.assertEqual(result, 'https://someotherhost.com:8000') def test_partial_application_url_with_custom_script_name(self): environ = { 'wsgi.url_scheme':'http', 'SERVER_NAME':'example.com', 'SERVER_PORT':'8000', } request = self._makeOne(environ) request.script_name = '/abc' result = request._partial_application_url() self.assertEqual(result, 'http://example.com:8000/abc') class Test_route_url(unittest.TestCase): def _callFUT(self, route_name, request, *elements, **kw): from pyramid.url import route_url return route_url(route_name, request, *elements, **kw) def _makeRequest(self): class Request(object): def route_url(self, route_name, *elements, **kw): self.route_name = route_name self.elements = elements self.kw = kw return 'route url' return Request() def test_it(self): request = self._makeRequest() result = self._callFUT('abc', request, 'a', _app_url='') self.assertEqual(result, 'route url') self.assertEqual(request.route_name, 'abc') self.assertEqual(request.elements, ('a',)) self.assertEqual(request.kw, {'_app_url':''}) class Test_route_path(unittest.TestCase): def _callFUT(self, route_name, request, *elements, **kw): from pyramid.url import route_path return route_path(route_name, request, *elements, **kw) def _makeRequest(self): class Request(object): def route_path(self, route_name, *elements, **kw): self.route_name = route_name self.elements = elements self.kw = kw return 'route path' return Request() def test_it(self): request = self._makeRequest() result = self._callFUT('abc', request, 'a', _app_url='') self.assertEqual(result, 'route path') self.assertEqual(request.route_name, 'abc') self.assertEqual(request.elements, ('a',)) self.assertEqual(request.kw, {'_app_url':''}) class Test_resource_url(unittest.TestCase): def _callFUT(self, resource, request, *elements, **kw): from pyramid.url import resource_url return resource_url(resource, request, *elements, **kw) def _makeRequest(self): class Request(object): def resource_url(self, resource, *elements, **kw): self.resource = resource self.elements = elements self.kw = kw return 'resource url' return Request() def test_it(self): request = self._makeRequest() result = self._callFUT('abc', request, 'a', _app_url='') self.assertEqual(result, 'resource url') self.assertEqual(request.resource, 'abc') self.assertEqual(request.elements, ('a',)) self.assertEqual(request.kw, {'_app_url':''}) class Test_static_url(unittest.TestCase): def _callFUT(self, path, request, **kw): from pyramid.url import static_url return static_url(path, request, **kw) def _makeRequest(self): class Request(object): def static_url(self, path, **kw): self.path = path self.kw = kw return 'static url' return Request() def test_it_abs(self): request = self._makeRequest() result = self._callFUT('/foo/bar/abc', request, _app_url='') self.assertEqual(result, 'static url') self.assertEqual(request.path, '/foo/bar/abc') self.assertEqual(request.kw, {'_app_url':''}) def test_it_absspec(self): request = self._makeRequest() result = self._callFUT('foo:abc', request, _anchor='anchor') self.assertEqual(result, 'static url') self.assertEqual(request.path, 'foo:abc') self.assertEqual(request.kw, {'_anchor':'anchor'}) def test_it_rel(self): request = self._makeRequest() result = self._callFUT('abc', request, _app_url='') self.assertEqual(result, 'static url') self.assertEqual(request.path, 'pyramid.tests:abc') self.assertEqual(request.kw, {'_app_url':''}) class Test_static_path(unittest.TestCase): def _callFUT(self, path, request, **kw): from pyramid.url import static_path return static_path(path, request, **kw) def _makeRequest(self): class Request(object): def static_path(self, path, **kw): self.path = path self.kw = kw return 'static path' return Request() def test_it_abs(self): request = self._makeRequest() result = self._callFUT('/foo/bar/abc', request, _anchor='anchor') self.assertEqual(result, 'static path') self.assertEqual(request.path, '/foo/bar/abc') self.assertEqual(request.kw, {'_anchor':'anchor'}) def test_it_absspec(self): request = self._makeRequest() result = self._callFUT('foo:abc', request, _anchor='anchor') self.assertEqual(result, 'static path') self.assertEqual(request.path, 'foo:abc') self.assertEqual(request.kw, {'_anchor':'anchor'}) def test_it_rel(self): request = self._makeRequest() result = self._callFUT('abc', request, _app_url='') self.assertEqual(result, 'static path') self.assertEqual(request.path, 'pyramid.tests:abc') self.assertEqual(request.kw, {'_app_url':''}) class Test_current_route_url(unittest.TestCase): def _callFUT(self, request, *elements, **kw): from pyramid.url import current_route_url return current_route_url(request, *elements, **kw) def _makeRequest(self): class Request(object): def current_route_url(self, *elements, **kw): self.elements = elements self.kw = kw return 'current route url' return Request() def test_it(self): request = self._makeRequest() result = self._callFUT(request, 'abc', _app_url='') self.assertEqual(result, 'current route url') self.assertEqual(request.elements, ('abc',)) self.assertEqual(request.kw, {'_app_url':''}) class Test_current_route_path(unittest.TestCase): def _callFUT(self, request, *elements, **kw): from pyramid.url import current_route_path return current_route_path(request, *elements, **kw) def _makeRequest(self): class Request(object): def current_route_path(self, *elements, **kw): self.elements = elements self.kw = kw return 'current route path' return Request() def test_it(self): request = self._makeRequest() result = self._callFUT(request, 'abc', _anchor='abc') self.assertEqual(result, 'current route path') self.assertEqual(request.elements, ('abc',)) self.assertEqual(request.kw, {'_anchor':'abc'}) class Test_external_static_url_integration(unittest.TestCase): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _makeRequest(self): from pyramid.request import Request return Request.blank('/') def test_generate_external_url(self): self.config.add_route('acme', 'https://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'https://acme.org/path/bar') def test_generate_external_url_without_scheme(self): self.config.add_route('acme', '//acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'http://acme.org/path/bar') def test_generate_external_url_with_explicit_scheme(self): self.config.add_route('acme', '//acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar', _scheme='https'), 'https://acme.org/path/bar') def test_generate_external_url_with_explicit_app_url(self): self.config.add_route('acme', 'http://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertRaises(ValueError, request.route_url, 'acme', foo='bar', _app_url='http://fakeme.com') def test_generate_external_url_route_path(self): self.config.add_route('acme', 'https://acme.org/path/{foo}') request = self._makeRequest() request.registry = self.config.registry self.assertRaises(ValueError, request.route_path, 'acme', foo='bar') def test_generate_external_url_with_pregenerator(self): def pregenerator(request, elements, kw): kw['_query'] = {'q': 'foo'} return elements, kw self.config.add_route('acme', 'https://acme.org/path/{foo}', pregenerator=pregenerator) request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'https://acme.org/path/bar?q=foo') def test_external_url_with_route_prefix(self): def includeme(config): config.add_route('acme', '//acme.org/{foo}') self.config.include(includeme, route_prefix='some_prefix') request = self._makeRequest() request.registry = self.config.registry self.assertEqual( request.route_url('acme', foo='bar'), 'http://acme.org/bar') class DummyContext(object): def __init__(self, next=None): self.next = next class DummyRoutesMapper: raise_exc = None def __init__(self, route=None, raise_exc=False): self.route = route def get_route(self, route_name): return self.route class DummyRoute: pregenerator = None name = 'route' def __init__(self, result='/1/2/3'): self.result = result def generate(self, kw): self.kw = kw return self.result class DummyStaticURLInfo: def __init__(self, result): self.result = result def generate(self, path, request, **kw): self.args = path, request, kw return self.result def makeabs(*elements): if WIN: # pragma: no cover return r'c:\\' + os.path.sep.join(elements) else: return os.path.sep + os.path.sep.join(elements) pyramid-1.6/pyramid/tests/test_urldispatch.py0000644000076500000240000005631312524266531022332 0ustar michaelstaff00000000000000import unittest from pyramid import testing from pyramid.compat import ( text_, PY3, ) class TestRoute(unittest.TestCase): def _getTargetClass(self): from pyramid.urldispatch import Route return Route def _makeOne(self, *arg): return self._getTargetClass()(*arg) def test_provides_IRoute(self): from pyramid.interfaces import IRoute from zope.interface.verify import verifyObject verifyObject(IRoute, self._makeOne('name', 'pattern')) def test_ctor(self): import types route = self._makeOne('name', ':path', 'factory') self.assertEqual(route.pattern, ':path') self.assertEqual(route.path, ':path') self.assertEqual(route.name, 'name') self.assertEqual(route.factory, 'factory') self.assertTrue(route.generate.__class__ is types.FunctionType) self.assertTrue(route.match.__class__ is types.FunctionType) def test_ctor_defaults(self): import types route = self._makeOne('name', ':path') self.assertEqual(route.pattern, ':path') self.assertEqual(route.path, ':path') self.assertEqual(route.name, 'name') self.assertEqual(route.factory, None) self.assertTrue(route.generate.__class__ is types.FunctionType) self.assertTrue(route.match.__class__ is types.FunctionType) def test_match(self): route = self._makeOne('name', ':path') self.assertEqual(route.match('/whatever'), {'path':'whatever'}) def test_generate(self): route = self._makeOne('name', ':path') self.assertEqual(route.generate({'path':'abc'}), '/abc') class RoutesMapperTests(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _getRequest(self, **kw): from pyramid.threadlocal import get_current_registry environ = {'SERVER_NAME':'localhost', 'wsgi.url_scheme':'http'} environ.update(kw) request = DummyRequest(environ) reg = get_current_registry() request.registry = reg return request def _getTargetClass(self): from pyramid.urldispatch import RoutesMapper return RoutesMapper def _makeOne(self): klass = self._getTargetClass() return klass() def test_provides_IRoutesMapper(self): from pyramid.interfaces import IRoutesMapper from zope.interface.verify import verifyObject verifyObject(IRoutesMapper, self._makeOne()) def test_no_route_matches(self): mapper = self._makeOne() request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['match'], None) self.assertEqual(result['route'], None) def test_connect_name_exists_removes_old(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article') mapper.connect('foo', 'archives/:action/:article2') self.assertEqual(len(mapper.routelist), 1) self.assertEqual(len(mapper.routes), 1) self.assertEqual(mapper.routes['foo'].pattern, 'archives/:action/:article2') self.assertEqual(mapper.routelist[0].pattern, 'archives/:action/:article2') def test_connect_static(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article', static=True) self.assertEqual(len(mapper.routelist), 0) self.assertEqual(len(mapper.routes), 1) self.assertEqual(mapper.routes['foo'].pattern, 'archives/:action/:article') def test_connect_static_overridden(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article', static=True) self.assertEqual(len(mapper.routelist), 0) self.assertEqual(len(mapper.routes), 1) self.assertEqual(mapper.routes['foo'].pattern, 'archives/:action/:article') mapper.connect('foo', 'archives/:action/:article2') self.assertEqual(len(mapper.routelist), 1) self.assertEqual(len(mapper.routes), 1) self.assertEqual(mapper.routes['foo'].pattern, 'archives/:action/:article2') self.assertEqual(mapper.routelist[0].pattern, 'archives/:action/:article2') def test___call__pathinfo_cant_be_decoded(self): from pyramid.exceptions import URLDecodeError mapper = self._makeOne() if PY3: path_info = b'\xff\xfe\xe6\x00'.decode('latin-1') else: path_info = b'\xff\xfe\xe6\x00' request = self._getRequest(PATH_INFO=path_info) self.assertRaises(URLDecodeError, mapper, request) def test___call__route_matches(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article') request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') self.assertEqual(result['match']['article'], 'article1') def test___call__route_matches_with_predicates(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/:article', predicates=[lambda *arg: True]) request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['foo']) self.assertEqual(result['match']['action'], 'action1') self.assertEqual(result['match']['article'], 'article1') def test___call__route_fails_to_match_with_predicates(self): mapper = self._makeOne() mapper.connect('foo', 'archives/:action/article1', predicates=[lambda *arg: True, lambda *arg: False]) mapper.connect('bar', 'archives/:action/:article') request = self._getRequest(PATH_INFO='/archives/action1/article1') result = mapper(request) self.assertEqual(result['route'], mapper.routes['bar']) self.assertEqual(result['match']['action'], 'action1') self.assertEqual(result['match']['article'], 'article1') def test___call__custom_predicate_gets_info(self): mapper = self._makeOne() def pred(info, request): self.assertEqual(info['match'], {'action':'action1'}) self.assertEqual(info['route'], mapper.routes['foo']) return True mapper.connect('foo', 'archives/:action/article1', predicates=[pred]) request = self._getRequest(PATH_INFO='/archives/action1/article1') mapper(request) def test_cc_bug(self): # "unordered" as reported in IRC by author of # http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/ mapper = self._makeOne() mapper.connect('rdf', 'licenses/:license_code/:license_version/rdf') mapper.connect('juri', 'licenses/:license_code/:license_version/:jurisdiction') request = self._getRequest(PATH_INFO='/licenses/1/v2/rdf') result = mapper(request) self.assertEqual(result['route'], mapper.routes['rdf']) self.assertEqual(result['match']['license_code'], '1') self.assertEqual(result['match']['license_version'], 'v2') request = self._getRequest(PATH_INFO='/licenses/1/v2/usa') result = mapper(request) self.assertEqual(result['route'], mapper.routes['juri']) self.assertEqual(result['match']['license_code'], '1') self.assertEqual(result['match']['license_version'], 'v2') self.assertEqual(result['match']['jurisdiction'], 'usa') def test___call__root_route_matches(self): mapper = self._makeOne() mapper.connect('root', '') request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) def test___call__root_route_matches2(self): mapper = self._makeOne() mapper.connect('root', '/') request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) def test___call__root_route_when_path_info_empty(self): mapper = self._makeOne() mapper.connect('root', '/') request = self._getRequest(PATH_INFO='') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) def test___call__root_route_when_path_info_notempty(self): mapper = self._makeOne() mapper.connect('root', '/') request = self._getRequest(PATH_INFO='/') result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) def test___call__no_path_info(self): mapper = self._makeOne() mapper.connect('root', '/') request = self._getRequest() result = mapper(request) self.assertEqual(result['route'], mapper.routes['root']) self.assertEqual(result['match'], {}) def test_has_routes(self): mapper = self._makeOne() self.assertEqual(mapper.has_routes(), False) mapper.connect('whatever', 'archives/:action/:article') self.assertEqual(mapper.has_routes(), True) def test_get_routes(self): from pyramid.urldispatch import Route mapper = self._makeOne() self.assertEqual(mapper.get_routes(), []) mapper.connect('whatever', 'archives/:action/:article') routes = mapper.get_routes() self.assertEqual(len(routes), 1) self.assertEqual(routes[0].__class__, Route) def test_get_route_matches(self): mapper = self._makeOne() mapper.connect('whatever', 'archives/:action/:article') result = mapper.get_route('whatever') self.assertEqual(result.pattern, 'archives/:action/:article') def test_get_route_misses(self): mapper = self._makeOne() result = mapper.get_route('whatever') self.assertEqual(result, None) def test_generate(self): mapper = self._makeOne() def generator(kw): return 123 route = DummyRoute(generator) mapper.routes['abc'] = route self.assertEqual(mapper.generate('abc', {}), 123) class TestCompileRoute(unittest.TestCase): def _callFUT(self, pattern): from pyramid.urldispatch import _compile_route return _compile_route(pattern) def test_no_star(self): matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar') self.assertEqual(matcher('/foo/baz/biz/buz/bar'), {'baz':'baz', 'buz':'buz'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') def test_with_star(self): matcher, generator = self._callFUT('/foo/:baz/biz/:buz/bar*traverse') self.assertEqual(matcher('/foo/baz/biz/buz/bar'), {'baz':'baz', 'buz':'buz', 'traverse':()}) self.assertEqual(matcher('/foo/baz/biz/buz/bar/everything/else/here'), {'baz':'baz', 'buz':'buz', 'traverse':('everything', 'else', 'here')}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator( {'baz':1, 'buz':2, 'traverse':'/a/b'}), '/foo/1/biz/2/bar/a/b') def test_with_bracket_star(self): matcher, generator = self._callFUT( '/foo/{baz}/biz/{buz}/bar{remainder:.*}') self.assertEqual(matcher('/foo/baz/biz/buz/bar'), {'baz':'baz', 'buz':'buz', 'remainder':''}) self.assertEqual(matcher('/foo/baz/biz/buz/bar/everything/else/here'), {'baz':'baz', 'buz':'buz', 'remainder':'/everything/else/here'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator( {'baz':1, 'buz':2, 'remainder':'/a/b'}), '/foo/1/biz/2/bar/a/b') def test_no_beginning_slash(self): matcher, generator = self._callFUT('foo/:baz/biz/:buz/bar') self.assertEqual(matcher('/foo/baz/biz/buz/bar'), {'baz':'baz', 'buz':'buz'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') def test_custom_regex(self): matcher, generator = self._callFUT('foo/{baz}/biz/{buz:[^/\.]+}.{bar}') self.assertEqual(matcher('/foo/baz/biz/buz.bar'), {'baz':'baz', 'buz':'buz', 'bar':'bar'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}), '/foo/1/biz/2.html') def test_custom_regex_with_colons(self): matcher, generator = self._callFUT('foo/{baz}/biz/{buz:(?:[^/\.]+)}.{bar}') self.assertEqual(matcher('/foo/baz/biz/buz.bar'), {'baz':'baz', 'buz':'buz', 'bar':'bar'}) self.assertEqual(matcher('foo/baz/biz/buz/bar'), None) self.assertEqual(generator({'baz':1, 'buz':2, 'bar': 'html'}), '/foo/1/biz/2.html') def test_mixed_newstyle_oldstyle_pattern_defaults_to_newstyle(self): # pattern: '\\/foo\\/(?Pabc)\\/biz\\/(?P[^/]+)\\/bar$' # note presence of :abc in pattern (oldstyle match) matcher, generator = self._callFUT('foo/{baz:abc}/biz/{buz}/bar') self.assertEqual(matcher('/foo/abc/biz/buz/bar'), {'baz':'abc', 'buz':'buz'}) self.assertEqual(generator({'baz':1, 'buz':2}), '/foo/1/biz/2/bar') def test_custom_regex_with_embedded_squigglies(self): matcher, generator = self._callFUT('/{buz:\d{4}}') self.assertEqual(matcher('/2001'), {'buz':'2001'}) self.assertEqual(matcher('/200'), None) self.assertEqual(generator({'buz':2001}), '/2001') def test_custom_regex_with_embedded_squigglies2(self): matcher, generator = self._callFUT('/{buz:\d{3,4}}') self.assertEqual(matcher('/2001'), {'buz':'2001'}) self.assertEqual(matcher('/200'), {'buz':'200'}) self.assertEqual(matcher('/20'), None) self.assertEqual(generator({'buz':2001}), '/2001') def test_custom_regex_with_embedded_squigglies3(self): matcher, generator = self._callFUT( '/{buz:(\d{2}|\d{4})-[a-zA-Z]{3,4}-\d{2}}') self.assertEqual(matcher('/2001-Nov-15'), {'buz':'2001-Nov-15'}) self.assertEqual(matcher('/99-June-10'), {'buz':'99-June-10'}) self.assertEqual(matcher('/2-Nov-15'), None) self.assertEqual(matcher('/200-Nov-15'), None) self.assertEqual(matcher('/2001-No-15'), None) self.assertEqual(generator({'buz':'2001-Nov-15'}), '/2001-Nov-15') self.assertEqual(generator({'buz':'99-June-10'}), '/99-June-10') def test_pattern_with_high_order_literal(self): pattern = text_(b'/La Pe\xc3\xb1a/{x}', 'utf-8') matcher, generator = self._callFUT(pattern) self.assertEqual(matcher(text_(b'/La Pe\xc3\xb1a/x', 'utf-8')), {'x':'x'}) self.assertEqual(generator({'x':'1'}), '/La%20Pe%C3%B1a/1') def test_pattern_generate_with_high_order_dynamic(self): pattern = '/{x}' _, generator = self._callFUT(pattern) self.assertEqual( generator({'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}), '/La%20Pe%C3%B1a') def test_docs_sample_generate(self): # sample from urldispatch.rst pattern = text_(b'/La Pe\xc3\xb1a/{city}', 'utf-8') _, generator = self._callFUT(pattern) self.assertEqual( generator({'city':text_(b'Qu\xc3\xa9bec', 'utf-8')}), '/La%20Pe%C3%B1a/Qu%C3%A9bec') def test_generate_with_mixedtype_values(self): pattern = '/{city}/{state}' _, generator = self._callFUT(pattern) result = generator( {'city': text_(b'Qu\xc3\xa9bec', 'utf-8'), 'state': b'La Pe\xc3\xb1a'} ) self.assertEqual(result, '/Qu%C3%A9bec/La%20Pe%C3%B1a') # should be a native string self.assertEqual(type(result), str) def test_highorder_pattern_utf8(self): pattern = b'/La Pe\xc3\xb1a/{city}' self.assertRaises(ValueError, self._callFUT, pattern) def test_generate_with_string_remainder_and_unicode_replacement(self): pattern = text_(b'/abc*remainder', 'utf-8') _, generator = self._callFUT(pattern) result = generator( {'remainder': text_(b'/Qu\xc3\xa9bec/La Pe\xc3\xb1a', 'utf-8')} ) self.assertEqual(result, '/abc/Qu%C3%A9bec/La%20Pe%C3%B1a') # should be a native string self.assertEqual(type(result), str) def test_generate_with_string_remainder_and_nonstring_replacement(self): pattern = text_(b'/abc/*remainder', 'utf-8') _, generator = self._callFUT(pattern) result = generator( {'remainder': None} ) self.assertEqual(result, '/abc/None') # should be a native string self.assertEqual(type(result), str) class TestCompileRouteFunctional(unittest.TestCase): def matches(self, pattern, path, expected): from pyramid.urldispatch import _compile_route matcher = _compile_route(pattern)[0] result = matcher(path) self.assertEqual(result, expected) def generates(self, pattern, dict, result): from pyramid.urldispatch import _compile_route self.assertEqual(_compile_route(pattern)[1](dict), result) def test_matcher_functional_notdynamic(self): self.matches('/', '', None) self.matches('', '', None) self.matches('/', '/foo', None) self.matches('/foo/', '/foo', None) self.matches('', '/', {}) self.matches('/', '/', {}) def test_matcher_functional_newstyle(self): self.matches('/{x}', '', None) self.matches('/{x}', '/', None) self.matches('/abc/{def}', '/abc/', None) self.matches('/{x}', '/a', {'x':'a'}) self.matches('zzz/{x}', '/zzz/abc', {'x':'abc'}) self.matches('zzz/{x}*traverse', '/zzz/abc', {'x':'abc', 'traverse':()}) self.matches('zzz/{x}*traverse', '/zzz/abc/def/g', {'x':'abc', 'traverse':('def', 'g')}) self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) #'/La%20Pe%C3%B1a' self.matches('{x}', text_(b'/La Pe\xc3\xb1a', 'utf-8'), {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}) # '/La%20Pe%C3%B1a/x' self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x'), {'traverse':(text_(b'La Pe\xc3\xb1a'), 'x')}) self.matches('/foo/{id}.html', '/foo/bar.html', {'id':'bar'}) self.matches('/{num:[0-9]+}/*traverse', '/555/abc/def', {'num':'555', 'traverse':('abc', 'def')}) self.matches('/{num:[0-9]*}/*traverse', '/555/abc/def', {'num':'555', 'traverse':('abc', 'def')}) self.matches('zzz/{_}', '/zzz/abc', {'_':'abc'}) self.matches('zzz/{_abc}', '/zzz/abc', {'_abc':'abc'}) self.matches('zzz/{abc_def}', '/zzz/abc', {'abc_def':'abc'}) def test_matcher_functional_oldstyle(self): self.matches('/:x', '', None) self.matches('/:x', '/', None) self.matches('/abc/:def', '/abc/', None) self.matches('/:x', '/a', {'x':'a'}) self.matches('zzz/:x', '/zzz/abc', {'x':'abc'}) self.matches('zzz/:x*traverse', '/zzz/abc', {'x':'abc', 'traverse':()}) self.matches('zzz/:x*traverse', '/zzz/abc/def/g', {'x':'abc', 'traverse':('def', 'g')}) self.matches('*traverse', '/zzz/abc', {'traverse':('zzz', 'abc')}) self.matches('*traverse', '/zzz/ abc', {'traverse':('zzz', ' abc')}) #'/La%20Pe%C3%B1a' # pattern, path, expected self.matches(':x', text_(b'/La Pe\xc3\xb1a', 'utf-8'), {'x':text_(b'La Pe\xc3\xb1a', 'utf-8')}) # '/La%20Pe%C3%B1a/x' self.matches('*traverse', text_(b'/La Pe\xc3\xb1a/x', 'utf-8'), {'traverse':(text_(b'La Pe\xc3\xb1a', 'utf-8'), 'x')}) self.matches('/foo/:id.html', '/foo/bar.html', {'id':'bar'}) self.matches('/foo/:id_html', '/foo/bar_html', {'id_html':'bar_html'}) self.matches('zzz/:_', '/zzz/abc', {'_':'abc'}) self.matches('zzz/:_abc', '/zzz/abc', {'_abc':'abc'}) self.matches('zzz/:abc_def', '/zzz/abc', {'abc_def':'abc'}) def test_generator_functional_notdynamic(self): self.generates('', {}, '/') self.generates('/', {}, '/') def test_generator_functional_newstyle(self): self.generates('/{x}', {'x':''}, '/') self.generates('/{x}', {'x':'a'}, '/a') self.generates('zzz/{x}', {'x':'abc'}, '/zzz/abc') self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':''}, '/zzz/abc') self.generates('zzz/{x}*traverse', {'x':'abc', 'traverse':'/def/g'}, '/zzz/abc/def/g') self.generates('/{x}', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')}, '//La%20Pe%C3%B1a') self.generates('/{x}*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y':'/rest/of/path'}, '//La%20Pe%C3%B1a/rest/of/path') self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))}, '/a/La%20Pe%C3%B1a') self.generates('/foo/{id}.html', {'id':'bar'}, '/foo/bar.html') self.generates('/foo/{_}', {'_':'20'}, '/foo/20') self.generates('/foo/{_abc}', {'_abc':'20'}, '/foo/20') self.generates('/foo/{abc_def}', {'abc_def':'20'}, '/foo/20') def test_generator_functional_oldstyle(self): self.generates('/:x', {'x':''}, '/') self.generates('/:x', {'x':'a'}, '/a') self.generates('zzz/:x', {'x':'abc'}, '/zzz/abc') self.generates('zzz/:x*traverse', {'x':'abc', 'traverse':''}, '/zzz/abc') self.generates('zzz/:x*traverse', {'x':'abc', 'traverse':'/def/g'}, '/zzz/abc/def/g') self.generates('/:x', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8')}, '//La%20Pe%C3%B1a') self.generates('/:x*y', {'x':text_(b'/La Pe\xc3\xb1a', 'utf-8'), 'y':'/rest/of/path'}, '//La%20Pe%C3%B1a/rest/of/path') self.generates('*traverse', {'traverse':('a', text_(b'La Pe\xf1a'))}, '/a/La%20Pe%C3%B1a') self.generates('/foo/:id.html', {'id':'bar'}, '/foo/bar.html') self.generates('/foo/:_', {'_':'20'}, '/foo/20') self.generates('/foo/:_abc', {'_abc':'20'}, '/foo/20') self.generates('/foo/:abc_def', {'abc_def':'20'}, '/foo/20') class DummyContext(object): """ """ class DummyRequest(object): def __init__(self, environ): self.environ = environ class DummyRoute(object): def __init__(self, generator): self.generate = generator pyramid-1.6/pyramid/tests/test_util.py0000644000076500000240000006757612524266531021002 0ustar michaelstaff00000000000000import unittest from pyramid.compat import PY3 class Test_InstancePropertyHelper(unittest.TestCase): def _makeOne(self): cls = self._getTargetClass() return cls() def _getTargetClass(self): from pyramid.util import InstancePropertyHelper return InstancePropertyHelper def test_callable(self): def worker(obj): return obj.bar foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker) foo.bar = 1 self.assertEqual(1, foo.worker) foo.bar = 2 self.assertEqual(2, foo.worker) def test_callable_with_name(self): def worker(obj): return obj.bar foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x') foo.bar = 1 self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) def test_callable_with_reify(self): def worker(obj): return obj.bar foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, reify=True) foo.bar = 1 self.assertEqual(1, foo.worker) foo.bar = 2 self.assertEqual(1, foo.worker) def test_callable_with_name_reify(self): def worker(obj): return obj.bar foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x') helper.set_property(foo, worker, name='y', reify=True) foo.bar = 1 self.assertEqual(1, foo.y) self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) self.assertEqual(1, foo.y) def test_property_without_name(self): def worker(obj): pass foo = Dummy() helper = self._getTargetClass() self.assertRaises(ValueError, helper.set_property, foo, property(worker)) def test_property_with_name(self): def worker(obj): return obj.bar foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, property(worker), name='x') foo.bar = 1 self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) def test_property_with_reify(self): def worker(obj): pass foo = Dummy() helper = self._getTargetClass() self.assertRaises(ValueError, helper.set_property, foo, property(worker), name='x', reify=True) def test_override_property(self): def worker(obj): pass foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x') def doit(): foo.x = 1 self.assertRaises(AttributeError, doit) def test_override_reify(self): def worker(obj): pass foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, worker, name='x', reify=True) foo.x = 1 self.assertEqual(1, foo.x) foo.x = 2 self.assertEqual(2, foo.x) def test_reset_property(self): foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, lambda _: 1, name='x') self.assertEqual(1, foo.x) helper.set_property(foo, lambda _: 2, name='x') self.assertEqual(2, foo.x) def test_reset_reify(self): """ This is questionable behavior, but may as well get notified if it changes.""" foo = Dummy() helper = self._getTargetClass() helper.set_property(foo, lambda _: 1, name='x', reify=True) self.assertEqual(1, foo.x) helper.set_property(foo, lambda _: 2, name='x', reify=True) self.assertEqual(1, foo.x) def test_make_property(self): from pyramid.decorator import reify helper = self._getTargetClass() name, fn = helper.make_property(lambda x: 1, name='x', reify=True) self.assertEqual(name, 'x') self.assertTrue(isinstance(fn, reify)) def test_apply_properties_with_iterable(self): foo = Dummy() helper = self._getTargetClass() x = helper.make_property(lambda _: 1, name='x', reify=True) y = helper.make_property(lambda _: 2, name='y') helper.apply_properties(foo, [x, y]) self.assertEqual(1, foo.x) self.assertEqual(2, foo.y) def test_apply_properties_with_dict(self): foo = Dummy() helper = self._getTargetClass() x_name, x_fn = helper.make_property(lambda _: 1, name='x', reify=True) y_name, y_fn = helper.make_property(lambda _: 2, name='y') helper.apply_properties(foo, {x_name: x_fn, y_name: y_fn}) self.assertEqual(1, foo.x) self.assertEqual(2, foo.y) def test_make_property_unicode(self): from pyramid.compat import text_ from pyramid.exceptions import ConfigurationError cls = self._getTargetClass() if PY3: # pragma: nocover name = b'La Pe\xc3\xb1a' else: # pragma: nocover name = text_(b'La Pe\xc3\xb1a', 'utf-8') def make_bad_name(): cls.make_property(lambda x: 1, name=name, reify=True) self.assertRaises(ConfigurationError, make_bad_name) def test_add_property(self): helper = self._makeOne() helper.add_property(lambda obj: obj.bar, name='x', reify=True) helper.add_property(lambda obj: obj.bar, name='y') self.assertEqual(len(helper.properties), 2) foo = Dummy() helper.apply(foo) foo.bar = 1 self.assertEqual(foo.x, 1) self.assertEqual(foo.y, 1) foo.bar = 2 self.assertEqual(foo.x, 1) self.assertEqual(foo.y, 2) def test_apply_multiple_times(self): helper = self._makeOne() helper.add_property(lambda obj: 1, name='x') foo, bar = Dummy(), Dummy() helper.apply(foo) self.assertEqual(foo.x, 1) helper.add_property(lambda obj: 2, name='x') helper.apply(bar) self.assertEqual(foo.x, 1) self.assertEqual(bar.x, 2) class Test_InstancePropertyMixin(unittest.TestCase): def _makeOne(self): cls = self._getTargetClass() class Foo(cls): pass return Foo() def _getTargetClass(self): from pyramid.util import InstancePropertyMixin return InstancePropertyMixin def test_callable(self): def worker(obj): return obj.bar foo = self._makeOne() foo.set_property(worker) foo.bar = 1 self.assertEqual(1, foo.worker) foo.bar = 2 self.assertEqual(2, foo.worker) def test_callable_with_name(self): def worker(obj): return obj.bar foo = self._makeOne() foo.set_property(worker, name='x') foo.bar = 1 self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) def test_callable_with_reify(self): def worker(obj): return obj.bar foo = self._makeOne() foo.set_property(worker, reify=True) foo.bar = 1 self.assertEqual(1, foo.worker) foo.bar = 2 self.assertEqual(1, foo.worker) def test_callable_with_name_reify(self): def worker(obj): return obj.bar foo = self._makeOne() foo.set_property(worker, name='x') foo.set_property(worker, name='y', reify=True) foo.bar = 1 self.assertEqual(1, foo.y) self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) self.assertEqual(1, foo.y) def test_property_without_name(self): def worker(obj): pass foo = self._makeOne() self.assertRaises(ValueError, foo.set_property, property(worker)) def test_property_with_name(self): def worker(obj): return obj.bar foo = self._makeOne() foo.set_property(property(worker), name='x') foo.bar = 1 self.assertEqual(1, foo.x) foo.bar = 2 self.assertEqual(2, foo.x) def test_property_with_reify(self): def worker(obj): pass foo = self._makeOne() self.assertRaises(ValueError, foo.set_property, property(worker), name='x', reify=True) def test_override_property(self): def worker(obj): pass foo = self._makeOne() foo.set_property(worker, name='x') def doit(): foo.x = 1 self.assertRaises(AttributeError, doit) def test_override_reify(self): def worker(obj): pass foo = self._makeOne() foo.set_property(worker, name='x', reify=True) foo.x = 1 self.assertEqual(1, foo.x) foo.x = 2 self.assertEqual(2, foo.x) def test_reset_property(self): foo = self._makeOne() foo.set_property(lambda _: 1, name='x') self.assertEqual(1, foo.x) foo.set_property(lambda _: 2, name='x') self.assertEqual(2, foo.x) def test_reset_reify(self): """ This is questionable behavior, but may as well get notified if it changes.""" foo = self._makeOne() foo.set_property(lambda _: 1, name='x', reify=True) self.assertEqual(1, foo.x) foo.set_property(lambda _: 2, name='x', reify=True) self.assertEqual(1, foo.x) class Test_WeakOrderedSet(unittest.TestCase): def _makeOne(self): from pyramid.config import WeakOrderedSet return WeakOrderedSet() def test_ctor(self): wos = self._makeOne() self.assertEqual(len(wos), 0) self.assertEqual(wos.last, None) def test_add_item(self): wos = self._makeOne() reg = Dummy() wos.add(reg) self.assertEqual(list(wos), [reg]) self.assertTrue(reg in wos) self.assertEqual(wos.last, reg) def test_add_multiple_items(self): wos = self._makeOne() reg1 = Dummy() reg2 = Dummy() wos.add(reg1) wos.add(reg2) self.assertEqual(len(wos), 2) self.assertEqual(list(wos), [reg1, reg2]) self.assertTrue(reg1 in wos) self.assertTrue(reg2 in wos) self.assertEqual(wos.last, reg2) def test_add_duplicate_items(self): wos = self._makeOne() reg = Dummy() wos.add(reg) wos.add(reg) self.assertEqual(len(wos), 1) self.assertEqual(list(wos), [reg]) self.assertTrue(reg in wos) self.assertEqual(wos.last, reg) def test_weakref_removal(self): wos = self._makeOne() reg = Dummy() wos.add(reg) wos.remove(reg) self.assertEqual(len(wos), 0) self.assertEqual(list(wos), []) self.assertEqual(wos.last, None) def test_last_updated(self): wos = self._makeOne() reg = Dummy() reg2 = Dummy() wos.add(reg) wos.add(reg2) wos.remove(reg2) self.assertEqual(len(wos), 1) self.assertEqual(list(wos), [reg]) self.assertEqual(wos.last, reg) def test_empty(self): wos = self._makeOne() reg = Dummy() reg2 = Dummy() wos.add(reg) wos.add(reg2) wos.empty() self.assertEqual(len(wos), 0) 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 return object_description(object) def test_string(self): self.assertEqual(self._callFUT('abc'), 'abc') def test_int(self): self.assertEqual(self._callFUT(1), '1') def test_bool(self): self.assertEqual(self._callFUT(True), 'True') def test_None(self): self.assertEqual(self._callFUT(None), 'None') def test_float(self): self.assertEqual(self._callFUT(1.2), '1.2') def test_tuple(self): self.assertEqual(self._callFUT(('a', 'b')), "('a', 'b')") def test_set(self): if PY3: self.assertEqual(self._callFUT(set(['a'])), "{'a'}") else: self.assertEqual(self._callFUT(set(['a'])), "set(['a'])") def test_list(self): self.assertEqual(self._callFUT(['a']), "['a']") def test_dict(self): self.assertEqual(self._callFUT({'a':1}), "{'a': 1}") def test_nomodule(self): o = object() self.assertEqual(self._callFUT(o), 'object %s' % str(o)) def test_module(self): import pyramid self.assertEqual(self._callFUT(pyramid), 'module pyramid') def test_method(self): self.assertEqual( self._callFUT(self.test_method), 'method test_method of class pyramid.tests.test_util.' 'Test_object_description') def test_class(self): self.assertEqual( self._callFUT(self.__class__), 'class pyramid.tests.test_util.Test_object_description') def test_function(self): self.assertEqual( self._callFUT(dummyfunc), 'function pyramid.tests.test_util.dummyfunc') def test_instance(self): inst = Dummy() self.assertEqual( self._callFUT(inst), "object %s" % str(inst)) def test_shortened_repr(self): inst = ['1'] * 1000 self.assertEqual( self._callFUT(inst), str(inst)[:100] + ' ... ]') class TestTopologicalSorter(unittest.TestCase): def _makeOne(self, *arg, **kw): from pyramid.util import TopologicalSorter return TopologicalSorter(*arg, **kw) def test_remove(self): inst = self._makeOne() inst.names.append('name') inst.name2val['name'] = 1 inst.req_after.add('name') inst.req_before.add('name') inst.name2after['name'] = ('bob',) inst.name2before['name'] = ('fred',) inst.order.append(('bob', 'name')) inst.order.append(('name', 'fred')) inst.remove('name') self.assertFalse(inst.names) self.assertFalse(inst.req_before) self.assertFalse(inst.req_after) self.assertFalse(inst.name2before) self.assertFalse(inst.name2after) self.assertFalse(inst.name2val) self.assertFalse(inst.order) def test_add(self): from pyramid.util import LAST sorter = self._makeOne() sorter.add('name', 'factory') self.assertEqual(sorter.names, ['name']) self.assertEqual(sorter.name2val, {'name':'factory'}) self.assertEqual(sorter.order, [('name', LAST)]) sorter.add('name2', 'factory2') self.assertEqual(sorter.names, ['name', 'name2']) self.assertEqual(sorter.name2val, {'name':'factory', 'name2':'factory2'}) self.assertEqual(sorter.order, [('name', LAST), ('name2', LAST)]) sorter.add('name3', 'factory3', before='name2') self.assertEqual(sorter.names, ['name', 'name2', 'name3']) self.assertEqual(sorter.name2val, {'name':'factory', 'name2':'factory2', 'name3':'factory3'}) self.assertEqual(sorter.order, [('name', LAST), ('name2', LAST), ('name3', 'name2')]) def test_sorted_ordering_1(self): sorter = self._makeOne() sorter.add('name1', 'factory1') sorter.add('name2', 'factory2') self.assertEqual(sorter.sorted(), [ ('name1', 'factory1'), ('name2', 'factory2'), ]) def test_sorted_ordering_2(self): from pyramid.util import FIRST sorter = self._makeOne() sorter.add('name1', 'factory1') sorter.add('name2', 'factory2', after=FIRST) self.assertEqual(sorter.sorted(), [ ('name2', 'factory2'), ('name1', 'factory1'), ]) def test_sorted_ordering_3(self): from pyramid.util import FIRST sorter = self._makeOne() add = sorter.add add('auth', 'auth_factory', after='browserid') add('dbt', 'dbt_factory') add('retry', 'retry_factory', before='txnmgr', after='exceptionview') add('browserid', 'browserid_factory') add('txnmgr', 'txnmgr_factory', after='exceptionview') add('exceptionview', 'excview_factory', after=FIRST) self.assertEqual(sorter.sorted(), [ ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ('dbt', 'dbt_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ]) def test_sorted_ordering_4(self): from pyramid.util import FIRST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', after=FIRST) add('auth', 'auth_factory', after='browserid') add('retry', 'retry_factory', before='txnmgr', after='exceptionview') add('browserid', 'browserid_factory') add('txnmgr', 'txnmgr_factory', after='exceptionview') add('dbt', 'dbt_factory') self.assertEqual(sorter.sorted(), [ ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('dbt', 'dbt_factory'), ]) def test_sorted_ordering_5(self): from pyramid.util import LAST, FIRST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory') add('auth', 'auth_factory', after=FIRST) add('retry', 'retry_factory', before='txnmgr', after='exceptionview') add('browserid', 'browserid_factory', after=FIRST) add('txnmgr', 'txnmgr_factory', after='exceptionview', before=LAST) add('dbt', 'dbt_factory') self.assertEqual(sorter.sorted(), [ ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('txnmgr', 'txnmgr_factory'), ('dbt', 'dbt_factory'), ]) def test_sorted_ordering_missing_before_partial(self): from pyramid.exceptions import ConfigurationError sorter = self._makeOne() add = sorter.add add('dbt', 'dbt_factory') add('auth', 'auth_factory', after='browserid') add('retry', 'retry_factory', before='txnmgr', after='exceptionview') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, sorter.sorted) def test_sorted_ordering_missing_after_partial(self): from pyramid.exceptions import ConfigurationError sorter = self._makeOne() add = sorter.add add('dbt', 'dbt_factory') add('auth', 'auth_factory', after='txnmgr') add('retry', 'retry_factory', before='dbt', after='exceptionview') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, sorter.sorted) def test_sorted_ordering_missing_before_and_after_partials(self): from pyramid.exceptions import ConfigurationError sorter = self._makeOne() add = sorter.add add('dbt', 'dbt_factory') add('auth', 'auth_factory', after='browserid') add('retry', 'retry_factory', before='foo', after='txnmgr') add('browserid', 'browserid_factory') self.assertRaises(ConfigurationError, sorter.sorted) def test_sorted_ordering_missing_before_partial_with_fallback(self): from pyramid.util import LAST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', before=LAST) add('auth', 'auth_factory', after='browserid') add('retry', 'retry_factory', before=('txnmgr', LAST), after='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(sorter.sorted(), [ ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('dbt', 'dbt_factory'), ]) def test_sorted_ordering_missing_after_partial_with_fallback(self): from pyramid.util import FIRST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', after=FIRST) add('auth', 'auth_factory', after=('txnmgr','browserid')) add('retry', 'retry_factory', after='exceptionview') add('browserid', 'browserid_factory') add('dbt', 'dbt_factory') self.assertEqual(sorter.sorted(), [ ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ('browserid', 'browserid_factory'), ('auth', 'auth_factory'), ('dbt', 'dbt_factory'), ]) def test_sorted_ordering_with_partial_fallbacks(self): from pyramid.util import LAST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', before=('wontbethere', LAST)) add('retry', 'retry_factory', after='exceptionview') add('browserid', 'browserid_factory', before=('wont2', 'exceptionview')) self.assertEqual(sorter.sorted(), [ ('browserid', 'browserid_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_sorted_ordering_with_multiple_matching_fallbacks(self): from pyramid.util import LAST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', before=LAST) add('retry', 'retry_factory', after='exceptionview') add('browserid', 'browserid_factory', before=('retry', 'exceptionview')) self.assertEqual(sorter.sorted(), [ ('browserid', 'browserid_factory'), ('exceptionview', 'excview_factory'), ('retry', 'retry_factory'), ]) def test_sorted_ordering_with_missing_fallbacks(self): from pyramid.exceptions import ConfigurationError from pyramid.util import LAST sorter = self._makeOne() add = sorter.add add('exceptionview', 'excview_factory', before=LAST) add('retry', 'retry_factory', after='exceptionview') add('browserid', 'browserid_factory', before=('txnmgr', 'auth')) self.assertRaises(ConfigurationError, sorter.sorted) def test_sorted_ordering_conflict_direct(self): from pyramid.exceptions import CyclicDependencyError sorter = self._makeOne() add = sorter.add add('browserid', 'browserid_factory') add('auth', 'auth_factory', before='browserid', after='browserid') self.assertRaises(CyclicDependencyError, sorter.sorted) def test_sorted_ordering_conflict_indirect(self): from pyramid.exceptions import CyclicDependencyError sorter = self._makeOne() add = sorter.add add('browserid', 'browserid_factory') add('auth', 'auth_factory', before='browserid') add('dbt', 'dbt_factory', after='browserid', before='auth') self.assertRaises(CyclicDependencyError, sorter.sorted) class TestSentinel(unittest.TestCase): def test_repr(self): from pyramid.util import Sentinel r = repr(Sentinel('ABC')) self.assertEqual(r, 'ABC') class TestActionInfo(unittest.TestCase): def _getTargetClass(self): from pyramid.util import ActionInfo return ActionInfo def _makeOne(self, filename, lineno, function, linerepr): return self._getTargetClass()(filename, lineno, function, linerepr) def test_class_conforms(self): from zope.interface.verify import verifyClass from pyramid.interfaces import IActionInfo verifyClass(IActionInfo, self._getTargetClass()) def test_instance_conforms(self): from zope.interface.verify import verifyObject from pyramid.interfaces import IActionInfo verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) def test_ctor(self): inst = self._makeOne('filename', 10, 'function', 'src') self.assertEqual(inst.file, 'filename') self.assertEqual(inst.line, 10) self.assertEqual(inst.function, 'function') self.assertEqual(inst.src, 'src') def test___str__(self): inst = self._makeOne('filename', 0, 'function', ' linerepr ') self.assertEqual(str(inst), "Line 0 of file filename:\n linerepr ") class TestCallableName(unittest.TestCase): def test_valid_ascii(self): from pyramid.util import get_callable_name from pyramid.compat import text_, PY3 if PY3: # pragma: nocover name = b'hello world' else: # pragma: nocover name = text_(b'hello world', 'utf-8') self.assertEqual(get_callable_name(name), 'hello world') def test_invalid_ascii(self): from pyramid.util import get_callable_name from pyramid.compat import text_, PY3 from pyramid.exceptions import ConfigurationError def get_bad_name(): if PY3: # pragma: nocover name = b'La Pe\xc3\xb1a' else: # pragma: nocover name = text_(b'La Pe\xc3\xb1a', 'utf-8') get_callable_name(name) self.assertRaises(ConfigurationError, get_bad_name) def dummyfunc(): pass class Dummy(object): pass pyramid-1.6/pyramid/tests/test_view.py0000644000076500000240000006764012575217552020774 0ustar michaelstaff00000000000000import unittest import sys from zope.interface import implementer from pyramid import testing from pyramid.interfaces import IRequest class BaseTest(object): def setUp(self): self.config = testing.setUp() def tearDown(self): testing.tearDown() def _registerView(self, reg, app, name): from pyramid.interfaces import IViewClassifier for_ = (IViewClassifier, IRequest, IContext) from pyramid.interfaces import IView reg.registerAdapter(app, for_, IView, name) def _makeEnviron(self, **extras): environ = { 'wsgi.url_scheme':'http', 'wsgi.version':(1,0), 'SERVER_NAME':'localhost', 'SERVER_PORT':'8080', 'REQUEST_METHOD':'GET', 'PATH_INFO':'/', } environ.update(extras) return environ def _makeRequest(self, **environ): from pyramid.request import Request from pyramid.registry import Registry environ = self._makeEnviron(**environ) request = Request(environ) request.registry = Registry() return request def _makeContext(self): from zope.interface import directlyProvides context = DummyContext() directlyProvides(context, IContext) return context class Test_notfound_view_config(BaseTest, unittest.TestCase): def _makeOne(self, **kw): from pyramid.view import notfound_view_config return notfound_view_config(**kw) def test_ctor(self): inst = self._makeOne(attr='attr', path_info='path_info', append_slash=True) self.assertEqual(inst.__dict__, {'attr':'attr', 'path_info':'path_info', 'append_slash':True}) def test_it_function(self): def view(request): pass decorator = self._makeOne(attr='attr', renderer='renderer', append_slash=True) venusian = DummyVenusian() decorator.venusian = venusian wrapped = decorator(view) self.assertTrue(wrapped is view) config = call_venusian(venusian) settings = config.settings self.assertEqual( settings, [{'attr': 'attr', 'venusian': venusian, 'append_slash': True, 'renderer': 'renderer', '_info': 'codeinfo', 'view': None}] ) def test_it_class(self): decorator = self._makeOne() venusian = DummyVenusian() decorator.venusian = venusian decorator.venusian.info.scope = 'class' class view(object): pass wrapped = decorator(view) self.assertTrue(wrapped is view) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(len(settings[0]), 4) self.assertEqual(settings[0]['venusian'], venusian) self.assertEqual(settings[0]['view'], None) # comes from call_venusian self.assertEqual(settings[0]['attr'], 'view') self.assertEqual(settings[0]['_info'], 'codeinfo') class Test_forbidden_view_config(BaseTest, unittest.TestCase): def _makeOne(self, **kw): from pyramid.view import forbidden_view_config return forbidden_view_config(**kw) def test_ctor(self): inst = self._makeOne(attr='attr', path_info='path_info') self.assertEqual(inst.__dict__, {'attr':'attr', 'path_info':'path_info'}) def test_it_function(self): def view(request): pass decorator = self._makeOne(attr='attr', renderer='renderer') venusian = DummyVenusian() decorator.venusian = venusian wrapped = decorator(view) self.assertTrue(wrapped is view) config = call_venusian(venusian) settings = config.settings self.assertEqual( settings, [{'attr': 'attr', 'venusian': venusian, 'renderer': 'renderer', '_info': 'codeinfo', 'view': None}] ) def test_it_class(self): decorator = self._makeOne() venusian = DummyVenusian() decorator.venusian = venusian decorator.venusian.info.scope = 'class' class view(object): pass wrapped = decorator(view) self.assertTrue(wrapped is view) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(len(settings[0]), 4) self.assertEqual(settings[0]['venusian'], venusian) self.assertEqual(settings[0]['view'], None) # comes from call_venusian self.assertEqual(settings[0]['attr'], 'view') self.assertEqual(settings[0]['_info'], 'codeinfo') class RenderViewToResponseTests(BaseTest, unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.view import render_view_to_response return render_view_to_response(*arg, **kw) def test_call_no_view_registered(self): request = self._makeRequest() context = self._makeContext() result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) def test_call_no_registry_on_request(self): request = self._makeRequest() del request.registry context = self._makeContext() result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) def test_call_view_registered_secure(self): request = self._makeRequest() context = self._makeContext() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') response = self._callFUT(context, request, name='registered', secure=True) self.assertEqual(response.status, '200 OK') def test_call_view_registered_insecure_no_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') response = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(response.status, '200 OK') def test_call_view_registered_insecure_with_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) def anotherview(context, request): return DummyResponse('anotherview') view.__call_permissive__ = anotherview self._registerView(request.registry, view, 'registered') response = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(response.status, '200 OK') self.assertEqual(response.app_iter, ['anotherview']) def test_call_view_with_request_iface_on_request(self): # See https://github.com/Pylons/pyramid/issues/1643 from zope.interface import Interface class IWontBeFound(Interface): pass context = self._makeContext() request = self._makeRequest() request.request_iface = IWontBeFound response = DummyResponse('aview') view = make_view(response) self._registerView(request.registry, view, 'aview') response = self._callFUT(context, request, name='aview') self.assertEqual(response.status, '200 OK') self.assertEqual(response.app_iter, ['aview']) class RenderViewToIterableTests(BaseTest, unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.view import render_view_to_iterable return render_view_to_iterable(*arg, **kw) def test_call_no_view_registered(self): request = self._makeRequest() context = self._makeContext() result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) def test_call_view_registered_secure(self): request = self._makeRequest() context = self._makeContext() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') iterable = self._callFUT(context, request, name='registered', secure=True) self.assertEqual(iterable, ()) def test_call_view_registered_insecure_no_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') iterable = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(iterable, ()) def test_call_view_registered_insecure_with_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) def anotherview(context, request): return DummyResponse(b'anotherview') view.__call_permissive__ = anotherview self._registerView(request.registry, view, 'registered') iterable = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(iterable, [b'anotherview']) def test_verify_output_bytestring(self): from pyramid.request import Request from pyramid.config import Configurator from pyramid.view import render_view from webob.compat import text_type config = Configurator(settings={}) def view(request): request.response.text = text_type('') return request.response config.add_view(name='test', view=view) config.commit() r = Request({}) r.registry = config.registry self.assertEqual(render_view(object(), r, 'test'), b'') def test_call_request_has_no_registry(self): request = self._makeRequest() del request.registry registry = self.config.registry context = self._makeContext() response = DummyResponse() view = make_view(response) self._registerView(registry, view, 'registered') iterable = self._callFUT(context, request, name='registered', secure=True) self.assertEqual(iterable, ()) class RenderViewTests(BaseTest, unittest.TestCase): def _callFUT(self, *arg, **kw): from pyramid.view import render_view return render_view(*arg, **kw) def test_call_no_view_registered(self): request = self._makeRequest() context = self._makeContext() result = self._callFUT(context, request, name='notregistered') self.assertEqual(result, None) def test_call_view_registered_secure(self): request = self._makeRequest() context = self._makeContext() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') s = self._callFUT(context, request, name='registered', secure=True) self.assertEqual(s, b'') def test_call_view_registered_insecure_no_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) self._registerView(request.registry, view, 'registered') s = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(s, b'') def test_call_view_registered_insecure_with_call_permissive(self): context = self._makeContext() request = self._makeRequest() response = DummyResponse() view = make_view(response) def anotherview(context, request): return DummyResponse(b'anotherview') view.__call_permissive__ = anotherview self._registerView(request.registry, view, 'registered') s = self._callFUT(context, request, name='registered', secure=False) self.assertEqual(s, b'anotherview') class TestViewConfigDecorator(unittest.TestCase): def setUp(self): testing.setUp() def tearDown(self): testing.tearDown() def _getTargetClass(self): from pyramid.view import view_config return view_config def _makeOne(self, *arg, **kw): return self._getTargetClass()(*arg, **kw) def test_create_defaults(self): decorator = self._makeOne() self.assertEqual(decorator.__dict__, {}) def test_create_context_trumps_for(self): decorator = self._makeOne(context='123', for_='456') self.assertEqual(decorator.context, '123') def test_create_for_trumps_context_None(self): decorator = self._makeOne(context=None, for_='456') self.assertEqual(decorator.context, '456') def test_create_nondefaults(self): decorator = self._makeOne( name=None, request_type=None, for_=None, permission='foo', mapper='mapper', decorator='decorator', match_param='match_param' ) self.assertEqual(decorator.name, None) self.assertEqual(decorator.request_type, None) self.assertEqual(decorator.context, None) self.assertEqual(decorator.permission, 'foo') self.assertEqual(decorator.mapper, 'mapper') self.assertEqual(decorator.decorator, 'decorator') self.assertEqual(decorator.match_param, 'match_param') def test_create_with_other_predicates(self): decorator = self._makeOne(foo=1) self.assertEqual(decorator.foo, 1) def test_create_decorator_tuple(self): decorator = self._makeOne(decorator=('decorator1', 'decorator2')) self.assertEqual(decorator.decorator, ('decorator1', 'decorator2')) def test_call_function(self): decorator = self._makeOne() venusian = DummyVenusian() decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(len(settings), 1) self.assertEqual(len(settings[0]), 3) self.assertEqual(settings[0]['venusian'], venusian) self.assertEqual(settings[0]['view'], None) # comes from call_venusian self.assertEqual(settings[0]['_info'], 'codeinfo') def test_call_class(self): decorator = self._makeOne() venusian = DummyVenusian() decorator.venusian = venusian decorator.venusian.info.scope = 'class' class foo(object): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(len(settings[0]), 4) self.assertEqual(settings[0]['venusian'], venusian) self.assertEqual(settings[0]['view'], None) # comes from call_venusian self.assertEqual(settings[0]['attr'], 'foo') self.assertEqual(settings[0]['_info'], 'codeinfo') def test_call_class_attr_already_set(self): decorator = self._makeOne(attr='abc') venusian = DummyVenusian() decorator.venusian = venusian decorator.venusian.info.scope = 'class' class foo(object): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(len(settings[0]), 4) self.assertEqual(settings[0]['venusian'], venusian) self.assertEqual(settings[0]['view'], None) # comes from call_venusian self.assertEqual(settings[0]['attr'], 'abc') self.assertEqual(settings[0]['_info'], 'codeinfo') def test_stacking(self): decorator1 = self._makeOne(name='1') venusian1 = DummyVenusian() decorator1.venusian = venusian1 venusian2 = DummyVenusian() decorator2 = self._makeOne(name='2') decorator2.venusian = venusian2 def foo(): pass wrapped1 = decorator1(foo) wrapped2 = decorator2(wrapped1) self.assertTrue(wrapped1 is foo) self.assertTrue(wrapped2 is foo) config1 = call_venusian(venusian1) self.assertEqual(len(config1.settings), 1) self.assertEqual(config1.settings[0]['name'], '1') config2 = call_venusian(venusian2) self.assertEqual(len(config2.settings), 1) self.assertEqual(config2.settings[0]['name'], '2') def test_call_as_method(self): decorator = self._makeOne() venusian = DummyVenusian() decorator.venusian = venusian decorator.venusian.info.scope = 'class' def foo(self): pass def bar(self): pass class foo(object): foomethod = decorator(foo) barmethod = decorator(bar) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 2) self.assertEqual(settings[0]['attr'], 'foo') self.assertEqual(settings[1]['attr'], 'bar') def test_with_custom_predicates(self): decorator = self._makeOne(custom_predicates=(1,)) venusian = DummyVenusian() decorator.venusian = venusian def foo(context, request): pass decorated = decorator(foo) self.assertTrue(decorated is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(settings[0]['custom_predicates'], (1,)) def test_call_with_renderer_string(self): import pyramid.tests decorator = self._makeOne(renderer='fixtures/minimal.pt') venusian = DummyVenusian() decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) renderer = settings[0]['renderer'] self.assertEqual(renderer, 'fixtures/minimal.pt') self.assertEqual(config.pkg, pyramid.tests) def test_call_with_renderer_dict(self): import pyramid.tests decorator = self._makeOne(renderer={'a':1}) venusian = DummyVenusian() decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) config = call_venusian(venusian) settings = config.settings self.assertEqual(len(settings), 1) self.assertEqual(settings[0]['renderer'], {'a':1}) self.assertEqual(config.pkg, pyramid.tests) def test_call_with_renderer_IRendererInfo(self): import pyramid.tests from pyramid.interfaces import IRendererInfo @implementer(IRendererInfo) class DummyRendererHelper(object): pass renderer_helper = DummyRendererHelper() decorator = self._makeOne(renderer=renderer_helper) venusian = DummyVenusian() decorator.venusian = venusian def foo(): pass wrapped = decorator(foo) self.assertTrue(wrapped is foo) context = DummyVenusianContext() config = call_venusian(venusian, context) settings = config.settings self.assertEqual(len(settings), 1) renderer = settings[0]['renderer'] self.assertTrue(renderer is renderer_helper) self.assertEqual(config.pkg, pyramid.tests) def test_call_withdepth(self): decorator = self._makeOne(_depth=1) venusian = DummyVenusian() decorator.venusian = venusian def foo(): pass decorator(foo) self.assertEqual(venusian.depth, 2) class Test_append_slash_notfound_view(BaseTest, unittest.TestCase): def _callFUT(self, context, request): from pyramid.view import append_slash_notfound_view return append_slash_notfound_view(context, request) def _registerMapper(self, reg, match=True): from pyramid.interfaces import IRoutesMapper class DummyRoute(object): def __init__(self, val): self.val = val def match(self, path): return self.val class DummyMapper(object): def __init__(self): self.routelist = [ DummyRoute(match) ] def get_routes(self): return self.routelist mapper = DummyMapper() reg.registerUtility(mapper, IRoutesMapper) return mapper def test_context_is_not_exception(self): request = self._makeRequest(PATH_INFO='/abc') request.exception = ExceptionResponse() context = DummyContext() response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') self.assertEqual(response.app_iter, ['Not Found']) def test_no_mapper(self): request = self._makeRequest(PATH_INFO='/abc') context = ExceptionResponse() response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_no_path(self): request = self._makeRequest() context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_mapper_path_already_slash_ending(self): request = self._makeRequest(PATH_INFO='/abc/') context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_no_route_matches(self): request = self._makeRequest(PATH_INFO='/abc') context = ExceptionResponse() mapper = self._registerMapper(request.registry, True) mapper.routelist[0].val = None response = self._callFUT(context, request) self.assertEqual(response.status, '404 Not Found') def test_matches(self): request = self._makeRequest(PATH_INFO='/abc') context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '302 Found') self.assertEqual(response.location, '/abc/') def test_matches_with_script_name(self): request = self._makeRequest(PATH_INFO='/abc', SCRIPT_NAME='/foo') context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '302 Found') self.assertEqual(response.location, '/foo/abc/') def test_with_query_string(self): request = self._makeRequest(PATH_INFO='/abc', QUERY_STRING='a=1&b=2') context = ExceptionResponse() self._registerMapper(request.registry, True) response = self._callFUT(context, request) self.assertEqual(response.status, '302 Found') self.assertEqual(response.location, '/abc/?a=1&b=2') class TestAppendSlashNotFoundViewFactory(BaseTest, unittest.TestCase): def _makeOne(self, notfound_view): from pyramid.view import AppendSlashNotFoundViewFactory return AppendSlashNotFoundViewFactory(notfound_view) def test_custom_notfound_view(self): request = self._makeRequest(PATH_INFO='/abc') context = ExceptionResponse() def custom_notfound(context, request): return 'OK' view = self._makeOne(custom_notfound) response = view(context, request) self.assertEqual(response, 'OK') class Test_default_exceptionresponse_view(unittest.TestCase): def _callFUT(self, context, request): from pyramid.view import default_exceptionresponse_view return default_exceptionresponse_view(context, request) def test_is_exception(self): context = Exception() result = self._callFUT(context, None) self.assertTrue(result is context) def test_is_not_exception_context_is_false_still_chose(self): request = DummyRequest() request.exception = 0 result = self._callFUT(None, request) self.assertTrue(result is None) def test_is_not_exception_no_request_exception(self): context = object() request = DummyRequest() request.exception = None result = self._callFUT(context, request) self.assertTrue(result is context) def test_is_not_exception_request_exception(self): context = object() request = DummyRequest() request.exception = 'abc' result = self._callFUT(context, request) self.assertEqual(result, 'abc') class Test_view_defaults(unittest.TestCase): def test_it(self): from pyramid.view import view_defaults @view_defaults(route_name='abc', renderer='def') class Foo(object): pass self.assertEqual(Foo.__view_defaults__['route_name'],'abc') self.assertEqual(Foo.__view_defaults__['renderer'],'def') def test_it_inheritance_not_overridden(self): from pyramid.view import view_defaults @view_defaults(route_name='abc', renderer='def') class Foo(object): pass class Bar(Foo): pass self.assertEqual(Bar.__view_defaults__['route_name'],'abc') self.assertEqual(Bar.__view_defaults__['renderer'],'def') def test_it_inheritance_overriden(self): from pyramid.view import view_defaults @view_defaults(route_name='abc', renderer='def') class Foo(object): pass @view_defaults(route_name='ghi') class Bar(Foo): pass self.assertEqual(Bar.__view_defaults__['route_name'],'ghi') self.assertFalse('renderer' in Bar.__view_defaults__) def test_it_inheritance_overriden_empty(self): from pyramid.view import view_defaults @view_defaults(route_name='abc', renderer='def') class Foo(object): pass @view_defaults() class Bar(Foo): pass self.assertEqual(Bar.__view_defaults__, {}) class ExceptionResponse(Exception): status = '404 Not Found' app_iter = ['Not Found'] headerlist = [] class DummyContext: pass def make_view(response): def view(context, request): return response return view class DummyRequest: exception = None request_iface = IRequest def __init__(self, environ=None): if environ is None: environ = {} self.environ = environ from pyramid.interfaces import IResponse @implementer(IResponse) class DummyResponse(object): headerlist = () app_iter = () status = '200 OK' environ = None def __init__(self, body=None): if body is None: self.app_iter = () else: self.app_iter = [body] from zope.interface import Interface class IContext(Interface): pass class DummyVenusianInfo(object): scope = 'notaclass' module = sys.modules['pyramid.tests'] codeinfo = 'codeinfo' class DummyVenusian(object): def __init__(self, info=None): if info is None: info = DummyVenusianInfo() self.info = info self.attachments = [] def attach(self, wrapped, callback, category=None, depth=1): self.attachments.append((wrapped, callback, category)) self.depth = depth return self.info class DummyRegistry(object): pass class DummyConfig(object): def __init__(self): self.settings = [] self.registry = DummyRegistry() def add_view(self, **kw): self.settings.append(kw) add_notfound_view = add_forbidden_view = add_view def with_package(self, pkg): self.pkg = pkg return self class DummyVenusianContext(object): def __init__(self): self.config = DummyConfig() def call_venusian(venusian, context=None): if context is None: context = DummyVenusianContext() for wrapped, callback, category in venusian.attachments: callback(context, None, None) return context.config pyramid-1.6/pyramid/tests/test_wsgi.py0000644000076500000240000001135312520062551020744 0ustar michaelstaff00000000000000import unittest class WSGIAppTests(unittest.TestCase): def _callFUT(self, app): 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() decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) def test_decorator_object_instance(self): context = DummyContext() request = DummyRequest() app = DummyApp() decorator = self._callFUT(app) response = decorator(context, request) self.assertEqual(response, app) class WSGIApp2Tests(unittest.TestCase): def _callFUT(self, app): 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() request.subpath = ('subpath',) request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/view_name/subpath'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/subpath') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo/b/view_name') def test_decorator_with_subpath_no_view_name(self): context = DummyContext() request = DummyRequest() request.subpath = ('subpath',) request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/subpath'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/subpath') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo/b') def test_decorator_no_subpath_with_view_name(self): context = DummyContext() request = DummyRequest() request.subpath = () request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/b/view_name'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo/b/view_name') def test_decorator_traversed_empty_with_view_name(self): context = DummyContext() request = DummyRequest() request.subpath = () request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/view_name'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo/view_name') def test_decorator_traversed_empty_no_view_name(self): context = DummyContext() request = DummyRequest() request.subpath = () request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo') def test_decorator_traversed_empty_no_view_name_no_script_name(self): context = DummyContext() request = DummyRequest() request.subpath = () request.environ = {'SCRIPT_NAME':'', 'PATH_INFO':'/'} decorator = self._callFUT(dummyapp) response = decorator(context, request) self.assertEqual(response, dummyapp) self.assertEqual(request.environ['PATH_INFO'], '/') self.assertEqual(request.environ['SCRIPT_NAME'], '') def test_decorator_on_callable_object_instance(self): context = DummyContext() request = DummyRequest() request.subpath = () request.environ = {'SCRIPT_NAME':'/foo', 'PATH_INFO':'/'} app = DummyApp() decorator = self._callFUT(app) response = decorator(context, request) self.assertEqual(response, app) self.assertEqual(request.environ['PATH_INFO'], '/') self.assertEqual(request.environ['SCRIPT_NAME'], '/foo') def dummyapp(environ, start_response): """ """ class DummyApp(object): def __call__(self, environ, start_response): """ """ class DummyContext: pass class DummyRequest: def get_response(self, application): return application def copy(self): self.copied = True return self pyramid-1.6/pyramid/threadlocal.py0000644000076500000240000000375212520062551020060 0ustar michaelstaff00000000000000import threading from pyramid.registry import global_registry class ThreadLocalManager(threading.local): def __init__(self, default=None): # http://code.google.com/p/google-app-engine-django/issues/detail?id=119 # we *must* use a keyword argument for ``default`` here instead # of a positional argument to work around a bug in the # implementation of _threading_local.local in Python, which is # used by GAE instead of _thread.local self.stack = [] self.default = default def push(self, info): self.stack.append(info) set = push # b/c def pop(self): if self.stack: return self.stack.pop() def get(self): try: return self.stack[-1] except IndexError: return self.default() def clear(self): self.stack[:] = [] def defaults(): return {'request':None, 'registry':global_registry} manager = ThreadLocalManager(default=defaults) def get_current_request(): """Return the currently active request or ``None`` if no request is currently active. This function should be used *extremely sparingly*, usually only in unit testing code. It's almost always usually a mistake to use ``get_current_request`` outside a testing context because its usage makes it possible to write code that can be neither easily tested nor scripted. """ return manager.get()['request'] def get_current_registry(context=None): # context required by getSiteManager API """Return the currently active :term:`application registry` or the global application registry if no request is currently active. This function should be used *extremely sparingly*, usually only in unit testing code. It's almost always usually a mistake to use ``get_current_registry`` outside a testing context because its usage makes it possible to write code that can be neither easily tested nor scripted. """ return manager.get()['registry'] pyramid-1.6/pyramid/traversal.py0000644000076500000240000010616612642137120017604 0ustar michaelstaff00000000000000import warnings from zope.deprecation import deprecated from zope.interface import implementer from zope.interface.interfaces import IInterface from repoze.lru import lru_cache from pyramid.interfaces import ( IResourceURL, IRequestFactory, ITraverser, VH_ROOT_KEY, ) with warnings.catch_warnings(): warnings.filterwarnings('ignore') from pyramid.interfaces import IContextURL from pyramid.compat import ( PY3, native_, text_, ascii_native_, text_type, binary_type, is_nonstr_iter, decode_path_info, unquote_bytes_to_wsgi, ) from pyramid.encode import url_quote from pyramid.exceptions import URLDecodeError from pyramid.location import lineage from pyramid.threadlocal import get_current_registry empty = text_('') def find_root(resource): """ Find the root node in the resource tree to which ``resource`` belongs. Note that ``resource`` should be :term:`location`-aware. Note that the root resource is available in the request object by accessing the ``request.root`` attribute. """ for location in lineage(resource): if location.__parent__ is None: resource = location break return resource def find_resource(resource, path): """ Given a resource object and a string or tuple representing a path (such as the return value of :func:`pyramid.traversal.resource_path` or :func:`pyramid.traversal.resource_path_tuple`), return a resource in this application's resource tree at the specified path. The resource passed in *must* be :term:`location`-aware. If the path cannot be resolved (if the respective node in the resource tree does not exist), a :exc:`KeyError` will be raised. This function is the logical inverse of :func:`pyramid.traversal.resource_path` and :func:`pyramid.traversal.resource_path_tuple`; it can resolve any path string or tuple generated by either of those functions. Rules for passing a *string* as the ``path`` argument: if the first character in the path string is the ``/`` character, the path is considered absolute and the resource tree traversal will start at the root resource. If the first character of the path string is *not* the ``/`` character, the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following manner: each Unicode path segment must be encoded as UTF-8 and as each path segment must escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). Rules for passing *text* (Unicode) as the ``path`` argument are the same as those for a string. In particular, the text may not have any nonascii characters in it. Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', 'a', 'b', 'c')``, the path is considered absolute and the resource tree traversal will start at the resource tree root object. If the first element in the path tuple is not the empty string (for example ``('a', 'b', 'c')``), the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No URL-quoting or UTF-8-encoding of individual path segments within the tuple is required (each segment may be any string or unicode object representing a resource name). Resource path tuples generated by :func:`pyramid.traversal.resource_path_tuple` can always be resolved by ``find_resource``. .. note:: For backwards compatibility purposes, this function can also be imported as :func:`pyramid.traversal.find_model`, although doing so will emit a deprecation warning. """ if isinstance(path, text_type): path = ascii_native_(path) D = traverse(resource, path) view_name = D['view_name'] context = D['context'] if view_name: raise KeyError('%r has no subelement %s' % (context, view_name)) return context find_model = find_resource # b/w compat (forever) def find_interface(resource, class_or_interface): """ Return the first resource found in the :term:`lineage` of ``resource`` which, a) if ``class_or_interface`` is a Python class object, is an instance of the class or any subclass of that class or b) if ``class_or_interface`` is a :term:`interface`, provides the specified interface. Return ``None`` if no resource providing ``interface_or_class`` can be found in the lineage. The ``resource`` passed in *must* be :term:`location`-aware. """ if IInterface.providedBy(class_or_interface): test = class_or_interface.providedBy else: test = lambda arg: isinstance(arg, class_or_interface) for location in lineage(resource): if test(location): return location def resource_path(resource, *elements): """ Return a string object representing the absolute physical path of the resource object based on its position in the resource tree, e.g ``/foo/bar``. Any positional arguments passed in as ``elements`` will be appended as path segments to the end of the resource path. For instance, if the resource's path is ``/foo/bar`` and ``elements`` equals ``('a', 'b')``, the returned string will be ``/foo/bar/a/b``. The first character in the string will always be the ``/`` character (a leading ``/`` character in a path string represents that the path is absolute). Resource path strings returned will be escaped in the following manner: each unicode path segment will be encoded as UTF-8 and each path segment will be escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a``. This function is a logical inverse of :mod:`pyramid.traversal.find_resource`: it can be used to generate path references that can later be resolved via that function. The ``resource`` passed in *must* be :term:`location`-aware. .. note:: Each segment in the path string returned will use the ``__name__`` attribute of the resource it represents within the resource tree. Each of these segments *should* be a unicode or string object (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a dictionary, the :func:`pyramid.traversal.resource_path` function will attempt to append it to a string and it will cause a :exc:`pyramid.exceptions.URLDecodeError`. .. note:: The :term:`root` resource *must* have a ``__name__`` attribute with a value of either ``None`` or the empty string for paths to be generated properly. If the root resource has a non-null ``__name__`` attribute, its name will be prepended to the generated path rather than a single leading '/' character. .. note:: For backwards compatibility purposes, this function can also be imported as ``model_path``, although doing so will cause a deprecation warning to be emitted. """ # joining strings is a bit expensive so we delegate to a function # which caches the joined result for us return _join_path_tuple(resource_path_tuple(resource, *elements)) model_path = resource_path # b/w compat (forever) def traverse(resource, path): """Given a resource object as ``resource`` and a string or tuple representing a path as ``path`` (such as the return value of :func:`pyramid.traversal.resource_path` or :func:`pyramid.traversal.resource_path_tuple` or the value of ``request.environ['PATH_INFO']``), return a dictionary with the keys ``context``, ``root``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. A definition of each value in the returned dictionary: - ``context``: The :term:`context` (a :term:`resource` object) found via traversal or url dispatch. If the ``path`` passed in is the empty string, the value of the ``resource`` argument passed to this function is returned. - ``root``: The resource object at which :term:`traversal` begins. If the ``resource`` passed in was found via url dispatch or if the ``path`` passed in was relative (non-absolute), the value of the ``resource`` argument passed to this function is returned. - ``view_name``: The :term:`view name` found during :term:`traversal` or :term:`url dispatch`; if the ``resource`` was found via traversal, this is usually a representation of the path segment which directly follows the path to the ``context`` in the ``path``. The ``view_name`` will be a Unicode object or the empty string. The ``view_name`` will be the empty string if there is no element which follows the ``context`` path. An example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view name' will be ``u'bar'``. If the ``resource`` was found via urldispatch, the view_name will be the name the route found was registered with. - ``subpath``: For a ``resource`` found via :term:`traversal`, this is a sequence of path segments found in the ``path`` that follow the ``view_name`` (if any). Each of these items is a Unicode object. If no path segments follow the ``view_name``, the subpath will be the empty sequence. An example: if the path passed is ``/foo/bar/baz/buz``, and a resource object is found at ``/foo`` (but not ``/foo/bar``), the 'view name' will be ``u'bar'`` and the :term:`subpath` will be ``[u'baz', u'buz']``. For a ``resource`` found via url dispatch, the subpath will be a sequence of values discerned from ``*subpath`` in the route pattern matched or the empty sequence. - ``traversed``: The sequence of path elements traversed from the root to find the ``context`` object during :term:`traversal`. Each of these items is a Unicode object. If no path segments were traversed to find the ``context`` object (e.g. if the ``path`` provided is the empty string), the ``traversed`` value will be the empty sequence. If the ``resource`` is a resource found via :term:`url dispatch`, traversed will be None. - ``virtual_root``: A resource object representing the 'virtual' root of the resource tree being traversed during :term:`traversal`. See :ref:`vhosting_chapter` for a definition of the virtual root object. If no virtual hosting is in effect, and the ``path`` passed in was absolute, the ``virtual_root`` will be the *physical* root resource object (the object at which :term:`traversal` begins). If the ``resource`` passed in was found via :term:`URL dispatch` or if the ``path`` passed in was relative, the ``virtual_root`` will always equal the ``root`` object (the resource passed in). - ``virtual_root_path`` -- If :term:`traversal` was used to find the ``resource``, this will be the sequence of path elements traversed to find the ``virtual_root`` resource. Each of these items is a Unicode object. If no path segments were traversed to find the ``virtual_root`` resource (e.g. if virtual hosting is not in effect), the ``traversed`` value will be the empty list. If url dispatch was used to find the ``resource``, this will be ``None``. If the path cannot be resolved, a :exc:`KeyError` will be raised. Rules for passing a *string* as the ``path`` argument: if the first character in the path string is the with the ``/`` character, the path will considered absolute and the resource tree traversal will start at the root resource. If the first character of the path string is *not* the ``/`` character, the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following manner: each Unicode path segment must be encoded as UTF-8 and each path segment must escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', 'a', 'b', 'c')``, the path is considered absolute and the resource tree traversal will start at the resource tree root object. If the first element in the path tuple is not the empty string (for example ``('a', 'b', 'c')``), the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No URL-quoting or UTF-8-encoding of individual path segments within the tuple is required (each segment may be any string or unicode object representing a resource name). Explanation of the conversion of ``path`` segment values to Unicode during traversal: Each segment is URL-unquoted, and decoded into Unicode. Each segment is assumed to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment cannot be decoded. If a segment name is empty or if it is ``.``, it is ignored. If a segment name is ``..``, the previous segment is deleted, and the ``..`` is ignored. As a result of this process, the return values ``view_name``, each element in the ``subpath``, each element in ``traversed``, and each element in the ``virtual_root_path`` will be Unicode as opposed to a string, and will be URL-decoded. """ if is_nonstr_iter(path): # the traverser factory expects PATH_INFO to be a string, not # unicode and it expects path segments to be utf-8 and # urlencoded (it's the same traverser which accepts PATH_INFO # from user agents; user agents always send strings). if path: path = _join_path_tuple(tuple(path)) else: path = '' # The user is supposed to pass us a string object, never Unicode. In # practice, however, users indeed pass Unicode to this API. If they do # pass a Unicode object, its data *must* be entirely encodeable to ASCII, # so we encode it here as a convenience to the user and to prevent # second-order failures from cropping up (all failures will occur at this # step rather than later down the line as the result of calling # ``traversal_path``). path = ascii_native_(path) if path and path[0] == '/': resource = find_root(resource) reg = get_current_registry() request_factory = reg.queryUtility(IRequestFactory) if request_factory is None: from pyramid.request import Request # avoid circdep request_factory = Request request = request_factory.blank(path) request.registry = reg traverser = reg.queryAdapter(resource, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(resource) return traverser(request) def resource_path_tuple(resource, *elements): """ Return a tuple representing the absolute physical path of the ``resource`` object based on its position in a resource tree, e.g ``('', 'foo', 'bar')``. Any positional arguments passed in as ``elements`` will be appended as elements in the tuple representing the resource path. For instance, if the resource's path is ``('', 'foo', 'bar')`` and elements equals ``('a', 'b')``, the returned tuple will be ``('', 'foo', 'bar', 'a', 'b')``. The first element of this tuple will always be the empty string (a leading empty string element in a path tuple represents that the path is absolute). This function is a logical inverse of :func:`pyramid.traversal.find_resource`: it can be used to generate path references that can later be resolved by that function. The ``resource`` passed in *must* be :term:`location`-aware. .. note:: Each segment in the path tuple returned will equal the ``__name__`` attribute of the resource it represents within the resource tree. Each of these segments *should* be a unicode or string object (as per the contract of :term:`location`-awareness). However, no conversion or safety checking of resource names is performed. For instance, if one of the resources in your tree has a ``__name__`` which (by error) is a dictionary, that dictionary will be placed in the path tuple; no warning or error will be given. .. note:: The :term:`root` resource *must* have a ``__name__`` attribute with a value of either ``None`` or the empty string for path tuples to be generated properly. If the root resource has a non-null ``__name__`` attribute, its name will be the first element in the generated path tuple rather than the empty string. .. note:: For backwards compatibility purposes, this function can also be imported as ``model_path_tuple``, although doing so will cause a deprecation warning to be emitted. """ return tuple(_resource_path_list(resource, *elements)) model_path_tuple = resource_path_tuple # b/w compat (forever) def _resource_path_list(resource, *elements): """ Implementation detail shared by resource_path and resource_path_tuple""" path = [loc.__name__ or '' for loc in lineage(resource)] path.reverse() path.extend(elements) return path _model_path_list = _resource_path_list # b/w compat, not an API def virtual_root(resource, request): """ Provided any :term:`resource` and a :term:`request` object, return the resource object representing the :term:`virtual root` of the current :term:`request`. Using a virtual root in a :term:`traversal` -based :app:`Pyramid` application permits rooting, for example, the resource at the traversal path ``/cms`` at ``http://example.com/`` instead of rooting it at ``http://example.com/cms/``. If the ``resource`` passed in is a context obtained via :term:`traversal`, and if the ``HTTP_X_VHM_ROOT`` key is in the WSGI environment, the value of this key will be treated as a 'virtual root path': the :func:`pyramid.traversal.find_resource` API will be used to find the virtual root resource using this path; if the resource is found, it will be returned. If the ``HTTP_X_VHM_ROOT`` key is not present in the WSGI environment, the physical :term:`root` of the resource tree will be returned instead. Virtual roots are not useful at all in applications that use :term:`URL dispatch`. Contexts obtained via URL dispatch don't really support being virtually rooted (each URL dispatch context is both its own physical and virtual root). However if this API is called with a ``resource`` argument which is a context obtained via URL dispatch, the resource passed in will be returned unconditionally.""" try: reg = request.registry except AttributeError: reg = get_current_registry() # b/c urlgenerator = reg.queryMultiAdapter((resource, request), IContextURL) if urlgenerator is None: urlgenerator = TraversalContextURL(resource, request) return urlgenerator.virtual_root() def traversal_path(path): """ Variant of :func:`pyramid.traversal.traversal_path_info` suitable for decoding paths that are URL-encoded. If this function is passed a Unicode object instead of a sequence of bytes as ``path``, that Unicode object *must* directly encodeable to ASCII. For example, u'/foo' will work but u'/' (a Unicode object with characters that cannot be encoded to ascii) will not. A :exc:`UnicodeEncodeError` will be raised if the Unicode cannot be encoded directly to ASCII. """ if isinstance(path, text_type): # must not possess characters outside ascii path = path.encode('ascii') # we unquote this path exactly like a PEP 3333 server would path = unquote_bytes_to_wsgi(path) # result will be a native string return traversal_path_info(path) # result will be a tuple of unicode @lru_cache(1000) def traversal_path_info(path): """ Given``path``, return a tuple representing that path which can be used to traverse a resource tree. ``path`` is assumed to be an already-URL-decoded ``str`` type as if it had come to us from an upstream WSGI server as the ``PATH_INFO`` environ variable. The ``path`` is first decoded to from its WSGI representation to Unicode; it is decoded differently depending on platform: - On Python 2, ``path`` is decoded to Unicode from bytes using the UTF-8 decoding directly; a :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be decoded. - On Python 3, as per the PEP 3333 spec, ``path`` is first encoded to bytes using the Latin-1 encoding; the resulting set of bytes is subsequently decoded to text using the UTF-8 encoding; a :exc:`pyramid.exc.URLDecodeError` is raised if a the URL cannot be decoded. The ``path`` is split on slashes, creating a list of segments. If a segment name is empty or if it is ``.``, it is ignored. If a segment name is ``..``, the previous segment is deleted, and the ``..`` is ignored. Examples: ``/`` () ``/foo/bar/baz`` (u'foo', u'bar', u'baz') ``foo/bar/baz`` (u'foo', u'bar', u'baz') ``/foo/bar/baz/`` (u'foo', u'bar', u'baz') ``/foo//bar//baz/`` (u'foo', u'bar', u'baz') ``/foo/bar/baz/..`` (u'foo', u'bar') ``/my%20archives/hello`` (u'my archives', u'hello') ``/archives/La%20Pe%C3%B1a`` (u'archives', u'') .. note:: This function does not generate the same type of tuples that :func:`pyramid.traversal.resource_path_tuple` does. In particular, the leading empty string is not present in the tuple it returns, unlike tuples returned by :func:`pyramid.traversal.resource_path_tuple`. As a result, tuples generated by ``traversal_path`` are not resolveable by the :func:`pyramid.traversal.find_resource` API. ``traversal_path`` is a function mostly used by the internals of :app:`Pyramid` and by people writing their own traversal machinery, as opposed to users writing applications in :app:`Pyramid`. """ try: path = decode_path_info(path) # result will be Unicode except UnicodeDecodeError as e: raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) return split_path_info(path) # result will be tuple of Unicode @lru_cache(1000) def split_path_info(path): # suitable for splitting an already-unquoted-already-decoded (unicode) # path value path = path.strip('/') clean = [] for segment in path.split('/'): if not segment or segment == '.': continue elif segment == '..': if clean: del clean[-1] else: clean.append(segment) return tuple(clean) _segment_cache = {} quote_path_segment_doc = """ \ Return a quoted representation of a 'path segment' (such as the string ``__name__`` attribute of a resource) as a string. If the ``segment`` passed in is a unicode object, it is converted to a UTF-8 string, then it is URL-quoted using Python's ``urllib.quote``. If the ``segment`` passed in is a string, it is URL-quoted using Python's :mod:`urllib.quote`. If the segment passed in is not a string or unicode object, an error will be raised. The return value of ``quote_path_segment`` is always a string, never Unicode. You may pass a string of characters that need not be encoded as the ``safe`` argument to this function. This corresponds to the ``safe`` argument to :mod:`urllib.quote`. .. note:: The return value for each segment passed to this function is cached in a module-scope dictionary for speed: the cached version is returned when possible rather than recomputing the quoted version. No cache emptying is ever done for the lifetime of an application, however. If you pass arbitrary user-supplied strings to this function (as opposed to some bounded set of values from a 'working set' known to your application), it may become a memory leak. """ if PY3: # special-case on Python 2 for speed? unchecked def quote_path_segment(segment, safe=''): """ %s """ % quote_path_segment_doc # The bit of this code that deals with ``_segment_cache`` is an # optimization: we cache all the computation of URL path segments # in this module-scope dictionary with the original string (or # unicode value) as the key, so we can look it up later without # needing to reencode or re-url-quote it try: return _segment_cache[(segment, safe)] except KeyError: if segment.__class__ not in (text_type, binary_type): segment = str(segment) result = url_quote(native_(segment, 'utf-8'), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result return result else: def quote_path_segment(segment, safe=''): """ %s """ % quote_path_segment_doc # The bit of this code that deals with ``_segment_cache`` is an # optimization: we cache all the computation of URL path segments # in this module-scope dictionary with the original string (or # unicode value) as the key, so we can look it up later without # needing to reencode or re-url-quote it try: return _segment_cache[(segment, safe)] except KeyError: if segment.__class__ is text_type: #isinstance slighly slower (~15%) result = url_quote(segment.encode('utf-8'), safe) else: result = url_quote(str(segment), safe) # we don't need a lock to mutate _segment_cache, as the below # will generate exactly one Python bytecode (STORE_SUBSCR) _segment_cache[(segment, safe)] = result return result slash = text_('/') @implementer(ITraverser) class ResourceTreeTraverser(object): """ A resource tree traverser that should be used (for speed) when every resource in the tree supplies a ``__name__`` and ``__parent__`` attribute (ie. every resource in the tree is :term:`location` aware) .""" VIEW_SELECTOR = '@@' def __init__(self, root): self.root = root def __call__(self, request): environ = request.environ matchdict = request.matchdict if matchdict is not None: path = matchdict.get('traverse', slash) or slash if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) # routing has already decoded these elements, so we just # need to join them path = '/' + slash.join(path) or slash subpath = matchdict.get('subpath', ()) if not is_nonstr_iter(subpath): # this is not a *subpath stararg (just a {subpath}) # routing has already decoded this string, so we just need # to split it subpath = split_path_info(subpath) else: # this request did not match a route subpath = () try: # empty if mounted under a path in mod_wsgi, for example path = request.path_info or slash except KeyError: # if environ['PATH_INFO'] is just not there path = slash except UnicodeDecodeError as e: raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) if VH_ROOT_KEY in environ: # HTTP_X_VHM_ROOT vroot_path = decode_path_info(environ[VH_ROOT_KEY]) vroot_tuple = split_path_info(vroot_path) vpath = vroot_path + path # both will (must) be unicode or asciistr vroot_idx = len(vroot_tuple) -1 else: vroot_tuple = () vpath = path vroot_idx = -1 root = self.root ob = vroot = root if vpath == slash: # invariant: vpath must not be empty # prevent a call to traversal_path if we know it's going # to return the empty tuple vpath_tuple = () else: # we do dead reckoning here via tuple slicing instead of # pushing and popping temporary lists for speed purposes # and this hurts readability; apologies i = 0 view_selector = self.VIEW_SELECTOR vpath_tuple = split_path_info(vpath) for segment in vpath_tuple: if segment[:2] == view_selector: return {'context':ob, 'view_name':segment[2:], 'subpath':vpath_tuple[i+1:], 'traversed':vpath_tuple[:vroot_idx+i+1], 'virtual_root':vroot, 'virtual_root_path':vroot_tuple, 'root':root} try: getitem = ob.__getitem__ except AttributeError: return {'context':ob, 'view_name':segment, 'subpath':vpath_tuple[i+1:], 'traversed':vpath_tuple[:vroot_idx+i+1], 'virtual_root':vroot, 'virtual_root_path':vroot_tuple, 'root':root} try: next = getitem(segment) except KeyError: return {'context':ob, 'view_name':segment, 'subpath':vpath_tuple[i+1:], 'traversed':vpath_tuple[:vroot_idx+i+1], 'virtual_root':vroot, 'virtual_root_path':vroot_tuple, 'root':root} if i == vroot_idx: vroot = next ob = next i += 1 return {'context':ob, 'view_name':empty, 'subpath':subpath, 'traversed':vpath_tuple, 'virtual_root':vroot, 'virtual_root_path':vroot_tuple, 'root':root} ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild @implementer(IResourceURL, IContextURL) class ResourceURL(object): vroot_varname = VH_ROOT_KEY def __init__(self, resource, request): physical_path_tuple = resource_path_tuple(resource) physical_path = _join_path_tuple(physical_path_tuple) if physical_path_tuple != ('',): physical_path_tuple = physical_path_tuple + ('',) physical_path = physical_path + '/' virtual_path = physical_path virtual_path_tuple = physical_path_tuple environ = request.environ vroot_path = environ.get(self.vroot_varname) # if the physical path starts with the virtual root path, trim it out # of the virtual path if vroot_path is not None: vroot_path = vroot_path.rstrip('/') if vroot_path and physical_path.startswith(vroot_path): vroot_path_tuple = tuple(vroot_path.split('/')) numels = len(vroot_path_tuple) virtual_path_tuple = ('',) + physical_path_tuple[numels:] virtual_path = physical_path[len(vroot_path):] self.virtual_path = virtual_path # IResourceURL attr self.physical_path = physical_path # IResourceURL attr self.virtual_path_tuple = virtual_path_tuple # IResourceURL attr (1.5) self.physical_path_tuple = physical_path_tuple # IResourceURL attr (1.5) # bw compat for IContextURL methods self.resource = resource self.context = resource self.request = request # IContextURL method (deprecated in 1.3) def virtual_root(self): environ = self.request.environ vroot_varname = self.vroot_varname if vroot_varname in environ: return find_resource(self.context, environ[vroot_varname]) # shortcut instead of using find_root; we probably already # have it on the request try: return self.request.root except AttributeError: return find_root(self.context) # IContextURL method (deprecated in 1.3) def __call__(self): """ Generate a URL based on the :term:`lineage` of a :term:`resource` object that is ``self.context``. If any resource in the context lineage has a Unicode name, it will be converted to a UTF-8 string before being attached to the URL. If a ``HTTP_X_VHM_ROOT`` key is present in the WSGI environment, its value will be treated as a 'virtual root path': the path of the URL generated by this will be left-stripped of this virtual root path value. """ local_url = getattr(self.context, '__resource_url__', None) if local_url is not None: result = local_url( self.request, {'virtual_path':self.virtual_path, 'physical_path':self.physical_path}, ) if result is not None: # allow it to punt by returning ``None`` return result app_url = self.request.application_url # never ends in a slash return app_url + self.virtual_path TraversalContextURL = ResourceURL # deprecated as of 1.3 deprecated( 'TraversalContextURL', 'As of Pyramid 1.3 the, "pyramid.traversal.TraversalContextURL" class is ' 'scheduled to be removed. Use the ' '"pyramid.config.Configurator.add_resource_url_adapter" method to register ' 'a class that implements "pyramid.interfaces.IResourceURL" instead. ' 'See the "What\'s new In Pyramid 1.3" document for a further description.' ) @lru_cache(1000) def _join_path_tuple(tuple): return tuple and '/'.join([quote_path_segment(x) for x in tuple]) or '/' class DefaultRootFactory: __parent__ = None __name__ = None def __init__(self, request): pass pyramid-1.6/pyramid/tweens.py0000644000076500000240000000424412524266531017110 0ustar michaelstaff00000000000000import sys from pyramid.interfaces import ( IExceptionViewClassifier, IRequest, ) from zope.interface import providedBy from pyramid.view import _call_view def excview_tween_factory(handler, registry): """ A :term:`tween` factory which produces a tween that catches an exception raised by downstream tweens (or the main Pyramid request handler) and, if possible, converts it into a Response using an :term:`exception view`.""" def excview_tween(request): attrs = request.__dict__ try: response = handler(request) except Exception as exc: # WARNING: do not assign the result of sys.exc_info() to a local # var here, doing so will cause a leak. We used to actually # explicitly delete both "exception" and "exc_info" from ``attrs`` # in a ``finally:`` clause below, but now we do not because these # attributes are useful to upstream tweens. This actually still # apparently causes a reference cycle, but it is broken # successfully by the garbage collector (see # https://github.com/Pylons/pyramid/issues/1223). attrs['exc_info'] = sys.exc_info() attrs['exception'] = exc # clear old generated request.response, if any; it may # have been mutated by the view, and its state is not # sane (e.g. caching headers) if 'response' in attrs: del attrs['response'] # we use .get instead of .__getitem__ below due to # https://github.com/Pylons/pyramid/issues/700 request_iface = attrs.get('request_iface', IRequest) provides = providedBy(exc) response = _call_view( registry, request, exc, provides, '', view_classifier=IExceptionViewClassifier, request_iface=request_iface.combined ) if response is None: raise return response return excview_tween MAIN = 'MAIN' INGRESS = 'INGRESS' EXCVIEW = 'pyramid.tweens.excview_tween_factory' pyramid-1.6/pyramid/url.py0000644000076500000240000011373012642137120016376 0ustar michaelstaff00000000000000""" Utility functions for dealing with URLs in pyramid """ import os import warnings from repoze.lru import lru_cache from pyramid.interfaces import ( IResourceURL, IRoutesMapper, IStaticURLInfo, ) from pyramid.compat import ( bytes_, string_types, ) from pyramid.encode import ( url_quote, urlencode, ) from pyramid.path import caller_package from pyramid.threadlocal import get_current_registry from pyramid.traversal import ( ResourceURL, quote_path_segment, ) PATH_SAFE = '/:@&+$,' # from webob QUERY_SAFE = '/?:@!$&\'()*+,;=' # RFC 3986 ANCHOR_SAFE = QUERY_SAFE def parse_url_overrides(kw): """Parse special arguments passed when generating urls. The supplied dictionary is mutated, popping arguments as necessary. Returns a 6-tuple of the format ``(app_url, scheme, host, port, qs, anchor)``. """ anchor = '' qs = '' app_url = None host = None scheme = None port = None if '_query' in kw: query = kw.pop('_query') if isinstance(query, string_types): qs = '?' + url_quote(query, QUERY_SAFE) elif query: qs = '?' + urlencode(query, doseq=True) if '_anchor' in kw: anchor = kw.pop('_anchor') anchor = url_quote(anchor, ANCHOR_SAFE) anchor = '#' + anchor if '_app_url' in kw: app_url = kw.pop('_app_url') if '_host' in kw: host = kw.pop('_host') if '_scheme' in kw: scheme = kw.pop('_scheme') if '_port' in kw: port = kw.pop('_port') return app_url, scheme, host, port, qs, anchor class URLMethodsMixin(object): """ Request methods mixin for BaseRequest having to do with URL generation """ def _partial_application_url(self, scheme=None, host=None, port=None): """ Construct the URL defined by request.application_url, replacing any of the default scheme, host, or port portions with user-supplied variants. If ``scheme`` is passed as ``https``, and the ``port`` is *not* passed, the ``port`` value is assumed to ``443``. Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not passed, the ``port`` value is assumed to be ``80``. """ e = self.environ if scheme is None: scheme = e['wsgi.url_scheme'] else: if scheme == 'https': if port is None: port = '443' if scheme == 'http': if port is None: port = '80' url = scheme + '://' if port is not None: port = str(port) if host is None: host = e.get('HTTP_HOST') if host is None: host = e['SERVER_NAME'] if port is None: if ':' in host: host, port = host.split(':', 1) else: port = e['SERVER_PORT'] else: if ':' in host: host, _ = host.split(':', 1) if scheme == 'https': if port == '443': port = None elif scheme == 'http': if port == '80': port = None url += host if port: url += ':%s' % port url_encoding = getattr(self, 'url_encoding', 'utf-8') # webob 1.2b3+ bscript_name = bytes_(self.script_name, url_encoding) return url + url_quote(bscript_name, PATH_SAFE) def route_url(self, route_name, *elements, **kw): """Generates a fully qualified URL for a named :app:`Pyramid` :term:`route configuration`. Use the route's ``name`` as the first positional argument. Additional positional arguments (``*elements``) are appended to the URL as path segments after it is generated. Use keyword arguments to supply values which match any dynamic path elements in the route definition. Raises a :exc:`KeyError` exception if the URL cannot be generated for any reason (not enough arguments, for example). For example, if you've defined a route named "foobar" with the path ``{foo}/{bar}/*traverse``:: request.route_url('foobar', foo='1') => request.route_url('foobar', foo='1', bar='2') => request.route_url('foobar', foo='1', bar='2', traverse=('a','b')) => http://e.com/1/2/a/b request.route_url('foobar', foo='1', bar='2', traverse='/a/b') => http://e.com/1/2/a/b Values replacing ``:segment`` arguments can be passed as strings or Unicode objects. They will be encoded to UTF-8 and URL-quoted before being placed into the generated URL. Values replacing ``*remainder`` arguments can be passed as strings *or* tuples of Unicode/string values. If a tuple is passed as a ``*remainder`` replacement value, its values are URL-quoted and encoded to UTF-8. The resulting strings are joined with slashes and rendered into the URL. If a string is passed as a ``*remainder`` replacement value, it is tacked on to the URL after being URL-quoted-except-for-embedded-slashes. If no ``_query`` keyword argument is provided, the request query string will be returned in the URL. If it is present, it will be used to compose a query string that will be tacked on to the end of the URL, replacing any request query string. The value of ``_query`` may be a sequence of two-tuples *or* a data structure with an ``.items()`` method that returns a sequence of two-tuples (presumably a dictionary). This data structure will be turned into a query string per the documentation of :func:`pyramid.url.urlencode` function. This will produce a query string in the ``x-www-form-urlencoded`` format. A non-``x-www-form-urlencoded`` query string may be used by passing a *string* value as ``_query`` in which case it will be URL-quoted (e.g. query="foo bar" will become "foo%20bar"). However, the result will not need to be in ``k=v`` form as required by ``x-www-form-urlencoded``. After the query data is turned into a query string, a leading ``?`` is prepended, and the resulting string is appended to the generated URL. .. note:: Python data structures that are passed as ``_query`` which are sequences or dictionaries are turned into a string under the same rules as when run through :func:`urllib.urlencode` with the ``doseq`` argument equal to ``True``. This means that sequences can be passed as values, and a k=v pair will be placed into the query string for each value. .. versionchanged:: 1.5 Allow the ``_query`` option to be a string to enable alternative encodings. If a keyword argument ``_anchor`` is present, its string representation will be quoted per :rfc:`3986#section-3.5` and used as a named anchor in the generated URL (e.g. if ``_anchor`` is passed as ``foo`` and the route URL is ``http://example.com/route/url``, the resulting generated URL will be ``http://example.com/route/url#foo``). .. note:: If ``_anchor`` is passed as a string, it should be UTF-8 encoded. If ``_anchor`` is passed as a Unicode object, it will be converted to UTF-8 before being appended to the URL. .. versionchanged:: 1.5 The ``_anchor`` option will be escaped instead of using its raw string representation. If both ``_anchor`` and ``_query`` are specified, the anchor element will always follow the query element, e.g. ``http://example.com?foo=1#bar``. If any of the keyword arguments ``_scheme``, ``_host``, or ``_port`` is passed and is non-``None``, the provided value will replace the 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 ``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 ``443``. Likewise, if ``_scheme`` is passed as ``http`` and ``_port`` is not passed, the ``_port`` value is assumed to have been passed as ``80``. To avoid this behavior, always explicitly pass ``_port`` whenever you pass ``_scheme``. If a keyword ``_app_url`` is present, it will be used as the protocol/hostname/port/leading path prefix of the generated URL. For example, using an ``_app_url`` of ``http://example.com:8080/foo`` would cause the URL ``http://example.com:8080/foo/fleeb/flub`` to be returned from this function if the expansion of the route pattern associated with the ``route_name`` expanded to ``/fleeb/flub``. If ``_app_url`` is not specified, the result of ``request.application_url`` will be used as the prefix (the default). If both ``_app_url`` and any of ``_scheme``, ``_host``, or ``_port`` are passed, ``_app_url`` takes precedence and any values passed for ``_scheme``, ``_host``, and ``_port`` will be ignored. This function raises a :exc:`KeyError` if the URL cannot be generated due to missing replacement names. Extra replacement names are ignored. If the route object which matches the ``route_name`` argument has a :term:`pregenerator`, the ``*elements`` and ``**kw`` arguments passed to this function might be augmented or changed. """ try: reg = self.registry except AttributeError: reg = get_current_registry() # b/c mapper = reg.getUtility(IRoutesMapper) route = mapper.get_route(route_name) if route is None: raise KeyError('No such route named %s' % route_name) if route.pregenerator is not None: elements, kw = route.pregenerator(self, elements, kw) app_url, scheme, host, port, qs, anchor = parse_url_overrides(kw) if app_url is None: if (scheme is not None or host is not None or port is not None): app_url = self._partial_application_url(scheme, host, port) else: app_url = self.application_url path = route.generate(kw) # raises KeyError if generate fails if elements: suffix = _join_elements(elements) if not path.endswith('/'): suffix = '/' + suffix else: suffix = '' return app_url + path + suffix + qs + anchor def route_path(self, route_name, *elements, **kw): """ Generates a path (aka a 'relative URL', a URL minus the host, scheme, and port) for a named :app:`Pyramid` :term:`route configuration`. This function accepts the same argument as :meth:`pyramid.request.Request.route_url` and performs the same duty. It just omits the host, port, and scheme information in the return value; only the script_name, path, query parameters, and anchor data are present in the returned string. For example, if you've defined a route named 'foobar' with the path ``/{foo}/{bar}``, this call to ``route_path``:: request.route_path('foobar', foo='1', bar='2') Will return the string ``/1/2``. .. note:: Calling ``request.route_path('route')`` is the same as calling ``request.route_url('route', _app_url=request.script_name)``. :meth:`pyramid.request.Request.route_path` is, in fact, implemented in terms of :meth:`pyramid.request.Request.route_url` in just this way. As a result, any ``_app_url`` passed within the ``**kw`` values to ``route_path`` will be ignored. """ kw['_app_url'] = self.script_name return self.route_url(route_name, *elements, **kw) def resource_url(self, resource, *elements, **kw): """ Generate a string representing the absolute URL of the :term:`resource` object based on the ``wsgi.url_scheme``, ``HTTP_HOST`` or ``SERVER_NAME`` in the request, plus any ``SCRIPT_NAME``. The overall result of this method is always a UTF-8 encoded string. Examples:: request.resource_url(resource) => http://example.com/ request.resource_url(resource, 'a.html') => http://example.com/a.html request.resource_url(resource, 'a.html', query={'q':'1'}) => http://example.com/a.html?q=1 request.resource_url(resource, 'a.html', anchor='abc') => http://example.com/a.html#abc request.resource_url(resource, app_url='') => / Any positional arguments passed in as ``elements`` must be strings Unicode objects, or integer objects. These will be joined by slashes and appended to the generated resource URL. Each of the elements passed in is URL-quoted before being appended; if any element is Unicode, it will converted to a UTF-8 bytestring before being URL-quoted. If any element is an integer, it will be converted to its string representation before being URL-quoted. .. warning:: if no ``elements`` arguments are specified, the resource URL will end with a trailing slash. If any ``elements`` are used, the generated URL will *not* end in a trailing slash. If a keyword argument ``query`` is present, it will be used to compose a query string that will be tacked on to the end of the URL. The value of ``query`` may be a sequence of two-tuples *or* a data structure with an ``.items()`` method that returns a sequence of two-tuples (presumably a dictionary). This data structure will be turned into a query string per the documentation of :func:``pyramid.url.urlencode`` function. This will produce a query string in the ``x-www-form-urlencoded`` encoding. A non-``x-www-form-urlencoded`` query string may be used by passing a *string* value as ``query`` in which case it will be URL-quoted (e.g. query="foo bar" will become "foo%20bar"). However, the result will not need to be in ``k=v`` form as required by ``x-www-form-urlencoded``. After the query data is turned into a query string, a leading ``?`` is prepended, and the resulting string is appended to the generated URL. .. note:: Python data structures that are passed as ``query`` which are sequences or dictionaries are turned into a string under the same rules as when run through :func:`urllib.urlencode` with the ``doseq`` argument equal to ``True``. This means that sequences can be passed as values, and a k=v pair will be placed into the query string for each value. .. versionchanged:: 1.5 Allow the ``query`` option to be a string to enable alternative encodings. If a keyword argument ``anchor`` is present, its string representation will be used as a named anchor in the generated URL (e.g. if ``anchor`` is passed as ``foo`` and the resource URL is ``http://example.com/resource/url``, the resulting generated URL will be ``http://example.com/resource/url#foo``). .. note:: If ``anchor`` is passed as a string, it should be UTF-8 encoded. If ``anchor`` is passed as a Unicode object, it will be converted to UTF-8 before being appended to the URL. .. versionchanged:: 1.5 The ``anchor`` option will be escaped instead of using its raw string representation. If both ``anchor`` and ``query`` are specified, the anchor element will always follow the query element, e.g. ``http://example.com?foo=1#bar``. If any of the keyword arguments ``scheme``, ``host``, or ``port`` is passed and is non-``None``, the provided value will replace the 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 ``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``. Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not passed, the ``port`` value is assumed to have been passed as ``80``. To avoid this behavior, always explicitly pass ``port`` whenever you pass ``scheme``. If a keyword argument ``app_url`` is passed and is not ``None``, it should be a string that will be used as the port/hostname/initial path portion of the generated URL instead of the default request application URL. For example, if ``app_url='http://foo'``, then the resulting url of a resource that has a path of ``/baz/bar`` will be ``http://foo/baz/bar``. If you want to generate completely relative URLs with no leading scheme, host, port, or initial path, you can pass ``app_url=''``. Passing ``app_url=''`` when the resource path is ``/baz/bar`` will return ``/baz/bar``. .. versionadded:: 1.3 ``app_url`` If ``app_url`` is passed and any of ``scheme``, ``port``, or ``host`` are also passed, ``app_url`` will take precedence and the values passed for ``scheme``, ``host``, and/or ``port`` will be ignored. If the ``resource`` passed in has a ``__resource_url__`` method, it will be used to generate the URL (scheme, host, port, path) for the base resource which is operated upon by this function. .. seealso:: See also :ref:`overriding_resource_url_generation`. .. versionadded:: 1.5 ``route_name``, ``route_kw``, and ``route_remainder_name`` If ``route_name`` is passed, this function will delegate its URL production to the ``route_url`` function. Calling ``resource_url(someresource, 'element1', 'element2', query={'a':1}, route_name='blogentry')`` is roughly equivalent to doing:: remainder_path = request.resource_path(someobject) url = request.route_url( 'blogentry', 'element1', 'element2', _query={'a':'1'}, traverse=traversal_path, ) It is only sensible to pass ``route_name`` if the route being named has a ``*remainder`` stararg value such as ``*traverse``. The remainder value will be ignored in the output otherwise. By default, the resource path value will be passed as the name ``traverse`` when ``route_url`` is called. You can influence this by passing a different ``route_remainder_name`` value if the route has a different ``*stararg`` value at its end. For example if the route pattern you want to replace has a ``*subpath`` stararg ala ``/foo*subpath``:: request.resource_url( resource, route_name='myroute', route_remainder_name='subpath' ) If ``route_name`` is passed, it is also permissible to pass ``route_kw``, which will passed as additional keyword arguments to ``route_url``. Saying ``resource_url(someresource, 'element1', 'element2', route_name='blogentry', route_kw={'id':'4'}, _query={'a':'1'})`` is roughly equivalent to:: remainder_path = request.resource_path_tuple(someobject) kw = {'id':'4', '_query':{'a':'1'}, 'traverse':traversal_path} url = request.route_url( 'blogentry', 'element1', 'element2', **kw, ) If ``route_kw`` or ``route_remainder_name`` is passed, but ``route_name`` is not passed, both ``route_kw`` and ``route_remainder_name`` will be ignored. If ``route_name`` is passed, the ``__resource_url__`` method of the resource passed is ignored unconditionally. This feature is incompatible with resources which generate their own URLs. .. note:: If the :term:`resource` used is the result of a :term:`traversal`, it must be :term:`location`-aware. The resource can also be the context of a :term:`URL dispatch`; contexts found this way do not need to be location-aware. .. note:: If a 'virtual root path' is present in the request environment (the value of the WSGI environ key ``HTTP_X_VHM_ROOT``), and the resource was obtained via :term:`traversal`, the URL path will not include the virtual root prefix (it will be stripped off the left hand side of the generated URL). .. note:: For backwards compatibility purposes, this method is also aliased as the ``model_url`` method of request. """ try: reg = self.registry except AttributeError: reg = get_current_registry() # b/c url_adapter = reg.queryMultiAdapter((resource, self), IResourceURL) if url_adapter is None: url_adapter = ResourceURL(resource, self) virtual_path = getattr(url_adapter, 'virtual_path', None) if virtual_path is None: # old-style IContextURL adapter (Pyramid 1.2 and previous) warnings.warn( 'Pyramid is using an IContextURL adapter to generate a ' 'resource URL; any "app_url", "host", "port", or "scheme" ' 'arguments passed to resource_url are being ignored. To ' 'avoid this behavior, as of Pyramid 1.3, register an ' 'IResourceURL adapter instead of an IContextURL ' 'adapter for the resource type(s). IContextURL adapters ' 'will be ignored in a later major release of Pyramid.', DeprecationWarning, 2) resource_url = url_adapter() else: # IResourceURL adapter (Pyramid 1.3 and after) app_url = None scheme = None host = None port = None if 'route_name' in kw: newkw = {} route_name = kw['route_name'] remainder = getattr(url_adapter, 'virtual_path_tuple', None) if remainder is None: # older user-supplied IResourceURL adapter without 1.5 # virtual_path_tuple remainder = tuple(url_adapter.virtual_path.split('/')) remainder_name = kw.get('route_remainder_name', 'traverse') newkw[remainder_name] = remainder for name in ( 'app_url', 'scheme', 'host', 'port', 'query', 'anchor' ): val = kw.get(name, None) if val is not None: newkw['_' + name] = val if 'route_kw' in kw: route_kw = kw.get('route_kw') if route_kw is not None: newkw.update(route_kw) return self.route_url(route_name, *elements, **newkw) if 'app_url' in kw: app_url = kw['app_url'] if 'scheme' in kw: scheme = kw['scheme'] if 'host' in kw: host = kw['host'] if 'port' in kw: port = kw['port'] if app_url is None: if scheme or host or port: app_url = self._partial_application_url(scheme, host, port) else: app_url = self.application_url resource_url = None local_url = getattr(resource, '__resource_url__', None) if local_url is not None: # the resource handles its own url generation d = dict( virtual_path = virtual_path, physical_path = url_adapter.physical_path, app_url = app_url, ) # allow __resource_url__ to punt by returning None resource_url = local_url(self, d) if resource_url is None: # the resource did not handle its own url generation or the # __resource_url__ function returned None resource_url = app_url + virtual_path qs = '' anchor = '' if 'query' in kw: query = kw['query'] if isinstance(query, string_types): qs = '?' + url_quote(query, QUERY_SAFE) elif query: qs = '?' + urlencode(query, doseq=True) if 'anchor' in kw: anchor = kw['anchor'] anchor = url_quote(anchor, ANCHOR_SAFE) anchor = '#' + anchor if elements: suffix = _join_elements(elements) else: suffix = '' return resource_url + suffix + qs + anchor model_url = resource_url # b/w compat forever def resource_path(self, resource, *elements, **kw): """ Generates a path (aka a 'relative URL', a URL minus the host, scheme, and port) for a :term:`resource`. This function accepts the same argument as :meth:`pyramid.request.Request.resource_url` and performs the same duty. It just omits the host, port, and scheme information in the return value; only the script_name, path, query parameters, and anchor data are present in the returned string. .. note:: Calling ``request.resource_path(resource)`` is the same as calling ``request.resource_path(resource, app_url=request.script_name)``. :meth:`pyramid.request.Request.resource_path` is, in fact, implemented in terms of :meth:`pyramid.request.Request.resource_url` in just this way. As a result, any ``app_url`` passed within the ``**kw`` values to ``route_path`` will be ignored. ``scheme``, ``host``, and ``port`` are also ignored. """ kw['app_url'] = self.script_name return self.resource_url(resource, *elements, **kw) def static_url(self, path, **kw): """ Generates a fully qualified URL for a static :term:`asset`. The asset must live within a location defined via the :meth:`pyramid.config.Configurator.add_static_view` :term:`configuration declaration` (see :ref:`static_assets_section`). Example:: request.static_url('mypackage:static/foo.css') => http://example.com/static/foo.css The ``path`` argument points at a file or directory on disk which a URL should be generated for. The ``path`` may be either a relative path (e.g. ``static/foo.css``) or an absolute path (e.g. ``/abspath/to/static/foo.css``) or a :term:`asset specification` (e.g. ``mypackage:static/foo.css``). The purpose of the ``**kw`` argument is the same as the purpose of the :meth:`pyramid.request.Request.route_url` ``**kw`` argument. See the documentation for that function to understand the arguments which you can provide to it. However, typically, you don't need to pass anything as ``*kw`` when generating a static asset URL. This function raises a :exc:`ValueError` if a static view definition cannot be found which matches the path specification. """ if not os.path.isabs(path): if not ':' in path: # if it's not a package:relative/name and it's not an # /absolute/path it's a relative/path; this means its relative # to the package in which the caller's module is defined. package = caller_package() path = '%s:%s' % (package.__name__, path) try: reg = self.registry except AttributeError: reg = get_current_registry() # b/c info = reg.queryUtility(IStaticURLInfo) if info is None: raise ValueError('No static URL definition matching %s' % path) return info.generate(path, self, **kw) def static_path(self, path, **kw): """ Generates a path (aka a 'relative URL', a URL minus the host, scheme, and port) for a static resource. This function accepts the same argument as :meth:`pyramid.request.Request.static_url` and performs the same duty. It just omits the host, port, and scheme information in the return value; only the script_name, path, query parameters, and anchor data are present in the returned string. Example:: request.static_path('mypackage:static/foo.css') => /static/foo.css .. note:: Calling ``request.static_path(apath)`` is the same as calling ``request.static_url(apath, _app_url=request.script_name)``. :meth:`pyramid.request.Request.static_path` is, in fact, implemented in terms of `:meth:`pyramid.request.Request.static_url` in just this way. As a result, any ``_app_url`` passed within the ``**kw`` values to ``static_path`` will be ignored. """ if not os.path.isabs(path): if not ':' in path: # if it's not a package:relative/name and it's not an # /absolute/path it's a relative/path; this means its relative # to the package in which the caller's module is defined. package = caller_package() path = '%s:%s' % (package.__name__, path) kw['_app_url'] = self.script_name return self.static_url(path, **kw) def current_route_url(self, *elements, **kw): """ Generates a fully qualified URL for a named :app:`Pyramid` :term:`route configuration` based on the 'current route'. This function supplements :meth:`pyramid.request.Request.route_url`. It presents an easy way to generate a URL for the 'current route' (defined as the route which matched when the request was generated). The arguments to this method have the same meaning as those with the same names passed to :meth:`pyramid.request.Request.route_url`. It also understands an extra argument which ``route_url`` does not named ``_route_name``. The route name used to generate a URL is taken from either the ``_route_name`` keyword argument or the name of the route which is currently associated with the request if ``_route_name`` was not passed. Keys and values from the current request :term:`matchdict` are combined with the ``kw`` arguments to form a set of defaults named ``newkw``. Then ``request.route_url(route_name, *elements, **newkw)`` is called, returning a URL. Examples follow. If the 'current route' has the route pattern ``/foo/{page}`` and the current url path is ``/foo/1`` , the matchdict will be ``{'page':'1'}``. The result of ``request.current_route_url()`` in this situation will be ``/foo/1``. If the 'current route' has the route pattern ``/foo/{page}`` and the current url path is ``/foo/1``, the matchdict will be ``{'page':'1'}``. The result of ``request.current_route_url(page='2')`` in this situation will be ``/foo/2``. Usage of the ``_route_name`` keyword argument: if our routing table defines routes ``/foo/{action}`` named 'foo' and ``/foo/{action}/{page}`` named ``fooaction``, and the current url pattern is ``/foo/view`` (which has matched the ``/foo/{action}`` route), we may want to use the matchdict args to generate a URL to the ``fooaction`` route. In this scenario, ``request.current_route_url(_route_name='fooaction', page='5')`` Will return string like: ``/foo/view/5``. """ if '_route_name' in kw: route_name = kw.pop('_route_name') else: route = getattr(self, 'matched_route', None) route_name = getattr(route, 'name', None) if route_name is None: raise ValueError('Current request matches no route') if '_query' not in kw: kw['_query'] = self.GET newkw = {} newkw.update(self.matchdict) newkw.update(kw) return self.route_url(route_name, *elements, **newkw) def current_route_path(self, *elements, **kw): """ Generates a path (aka a 'relative URL', a URL minus the host, scheme, and port) for the :app:`Pyramid` :term:`route configuration` matched by the current request. This function accepts the same argument as :meth:`pyramid.request.Request.current_route_url` and performs the same duty. It just omits the host, port, and scheme information in the return value; only the script_name, path, query parameters, and anchor data are present in the returned string. For example, if the route matched by the current request has the pattern ``/{foo}/{bar}``, this call to ``current_route_path``:: request.current_route_path(foo='1', bar='2') Will return the string ``/1/2``. .. note:: Calling ``request.current_route_path('route')`` is the same as calling ``request.current_route_url('route', _app_url=request.script_name)``. :meth:`pyramid.request.Request.current_route_path` is, in fact, implemented in terms of :meth:`pyramid.request.Request.current_route_url` in just this way. As a result, any ``_app_url`` passed within the ``**kw`` values to ``current_route_path`` will be ignored. """ kw['_app_url'] = self.script_name return self.current_route_url(*elements, **kw) def route_url(route_name, request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.route_url(route_name, *elements, **kw) See :meth:`pyramid.request.Request.route_url` for more information. """ return request.route_url(route_name, *elements, **kw) def route_path(route_name, request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.route_path(route_name, *elements, **kw) See :meth:`pyramid.request.Request.route_path` for more information. """ return request.route_path(route_name, *elements, **kw) def resource_url(resource, request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.resource_url(resource, *elements, **kw) See :meth:`pyramid.request.Request.resource_url` for more information. """ return request.resource_url(resource, *elements, **kw) model_url = resource_url # b/w compat (forever) def static_url(path, request, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.static_url(path, **kw) See :meth:`pyramid.request.Request.static_url` for more information. """ if not os.path.isabs(path): if not ':' in path: # if it's not a package:relative/name and it's not an # /absolute/path it's a relative/path; this means its relative # to the package in which the caller's module is defined. package = caller_package() path = '%s:%s' % (package.__name__, path) return request.static_url(path, **kw) def static_path(path, request, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.static_path(path, **kw) See :meth:`pyramid.request.Request.static_path` for more information. """ if not os.path.isabs(path): if not ':' in path: # if it's not a package:relative/name and it's not an # /absolute/path it's a relative/path; this means its relative # to the package in which the caller's module is defined. package = caller_package() path = '%s:%s' % (package.__name__, path) return request.static_path(path, **kw) def current_route_url(request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.current_route_url(*elements, **kw) See :meth:`pyramid.request.Request.current_route_url` for more information. """ return request.current_route_url(*elements, **kw) def current_route_path(request, *elements, **kw): """ This is a backwards compatibility function. Its result is the same as calling:: request.current_route_path(*elements, **kw) See :meth:`pyramid.request.Request.current_route_path` for more information. """ return request.current_route_path(*elements, **kw) @lru_cache(1000) def _join_elements(elements): return '/'.join([quote_path_segment(s, safe=':@&+$,') for s in elements]) pyramid-1.6/pyramid/urldispatch.py0000644000076500000240000002045412524266531020126 0ustar michaelstaff00000000000000import re from zope.interface import implementer from pyramid.interfaces import ( IRoutesMapper, IRoute, ) from pyramid.compat import ( PY3, native_, text_, text_type, string_types, binary_type, is_nonstr_iter, decode_path_info, ) from pyramid.exceptions import URLDecodeError from pyramid.traversal import ( quote_path_segment, split_path_info, ) _marker = object() @implementer(IRoute) class Route(object): def __init__(self, name, pattern, factory=None, predicates=(), pregenerator=None): self.pattern = pattern self.path = pattern # indefinite b/w compat, not in interface self.match, self.generate = _compile_route(pattern) self.name = name self.factory = factory self.predicates = predicates self.pregenerator = pregenerator @implementer(IRoutesMapper) class RoutesMapper(object): def __init__(self): self.routelist = [] self.static_routes = [] self.routes = {} def has_routes(self): return bool(self.routelist) def get_routes(self, include_static=False): if include_static is True: return self.routelist + self.static_routes return self.routelist def get_route(self, name): return self.routes.get(name) def connect(self, name, pattern, factory=None, predicates=(), pregenerator=None, static=False): if name in self.routes: oldroute = self.routes[name] if oldroute in self.routelist: self.routelist.remove(oldroute) route = Route(name, pattern, factory, predicates, pregenerator) if not static: self.routelist.append(route) else: self.static_routes.append(route) self.routes[name] = route return route def generate(self, name, kw): return self.routes[name].generate(kw) def __call__(self, request): environ = request.environ try: # empty if mounted under a path in mod_wsgi, for example path = decode_path_info(environ['PATH_INFO'] or '/') except KeyError: path = '/' except UnicodeDecodeError as e: raise URLDecodeError(e.encoding, e.object, e.start, e.end, e.reason) for route in self.routelist: match = route.match(path) if match is not None: preds = route.predicates info = {'match':match, 'route':route} if preds and not all((p(info, request) for p in preds)): continue return info return {'route':None, 'match':None} # stolen from bobo and modified old_route_re = re.compile(r'(\:[_a-zA-Z]\w*)') star_at_end = re.compile(r'\*(\w*)$') # The tortuous nature of the regex named ``route_re`` below is due to the # fact that we need to support at least one level of "inner" squigglies # inside the expr of a {name:expr} pattern. This regex used to be just # (\{[a-zA-Z][^\}]*\}) but that choked when supplied with e.g. {foo:\d{4}}. route_re = re.compile(r'(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})') def update_pattern(matchobj): name = matchobj.group(0) return '{%s}' % name[1:] def _compile_route(route): # This function really wants to consume Unicode patterns natively, but if # someone passes us a bytestring, we allow it by converting it to Unicode # using the ASCII decoding. We decode it using ASCII because we don't # want to accept bytestrings with high-order characters in them here as # we have no idea what the encoding represents. if route.__class__ is not text_type: try: route = text_(route, 'ascii') except UnicodeDecodeError: raise ValueError( 'The pattern value passed to add_route must be ' 'either a Unicode string or a plain string without ' 'any non-ASCII characters (you provided %r).' % route) if old_route_re.search(route) and not route_re.search(route): route = old_route_re.sub(update_pattern, route) if not route.startswith('/'): route = '/' + route remainder = None if star_at_end.search(route): route, remainder = route.rsplit('*', 1) pat = route_re.split(route) # every element in "pat" will be Unicode (regardless of whether the # route_re regex pattern is itself Unicode or str) pat.reverse() rpat = [] gen = [] prefix = pat.pop() # invar: always at least one element (route='/'+route) # We want to generate URL-encoded URLs, so we url-quote the prefix, being # careful not to quote any embedded slashes. We have to replace '%' with # '%%' afterwards, as the strings that go into "gen" are used as string # replacement targets. gen.append(quote_path_segment(prefix, safe='/').replace('%', '%%')) # native rpat.append(re.escape(prefix)) # unicode while pat: name = pat.pop() # unicode name = name[1:-1] if ':' in name: # reg may contain colons as well, # so we must strictly split name into two parts name, reg = name.split(':', 1) else: reg = '[^/]+' gen.append('%%(%s)s' % native_(name)) # native name = '(?P<%s>%s)' % (name, reg) # unicode rpat.append(name) s = pat.pop() # unicode if s: rpat.append(re.escape(s)) # unicode # We want to generate URL-encoded URLs, so we url-quote this # literal in the pattern, being careful not to quote the embedded # slashes. We have to replace '%' with '%%' afterwards, as the # strings that go into "gen" are used as string replacement # targets. What is appended to gen is a native string. gen.append(quote_path_segment(s, safe='/').replace('%', '%%')) if remainder: rpat.append('(?P<%s>.*?)' % remainder) # unicode gen.append('%%(%s)s' % native_(remainder)) # native pattern = ''.join(rpat) + '$' # unicode match = re.compile(pattern).match def matcher(path): # This function really wants to consume Unicode patterns natively, # but if someone passes us a bytestring, we allow it by converting it # to Unicode using the ASCII decoding. We decode it using ASCII # because we don't want to accept bytestrings with high-order # characters in them here as we have no idea what the encoding # represents. if path.__class__ is not text_type: path = text_(path, 'ascii') m = match(path) if m is None: return None d = {} for k, v in m.groupdict().items(): # k and v will be Unicode 2.6.4 and lower doesnt accept unicode # kwargs as **kw, so we explicitly cast the keys to native # strings in case someone wants to pass the result as **kw nk = native_(k, 'ascii') if k == remainder: d[nk] = split_path_info(v) else: d[nk] = v return d gen = ''.join(gen) def generator(dict): newdict = {} for k, v in dict.items(): if PY3: if v.__class__ is binary_type: # url_quote below needs a native string, not bytes on Py3 v = v.decode('utf-8') else: if v.__class__ is text_type: # url_quote below needs bytes, not unicode on Py2 v = v.encode('utf-8') if k == remainder: # a stararg argument if is_nonstr_iter(v): v = '/'.join( [quote_path_segment(x, safe='/') for x in v] ) # native else: if v.__class__ not in string_types: v = str(v) v = quote_path_segment(v, safe='/') else: if v.__class__ not in string_types: v = str(v) # v may be bytes (py2) or native string (py3) v = quote_path_segment(v, safe='/') # at this point, the value will be a native string newdict[k] = v result = gen % newdict # native string result return result return matcher, generator pyramid-1.6/pyramid/util.py0000644000076500000240000004737512642137120016564 0ustar michaelstaff00000000000000import 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 from zope.interface import implementer from pyramid.exceptions import ( ConfigurationError, CyclicDependencyError, ) from pyramid.compat import ( iteritems_, is_nonstr_iter, integer_types, string_types, text_, PY3, native_ ) from pyramid.interfaces import IActionInfo from pyramid.path import DottedNameResolver as _DottedNameResolver class DottedNameResolver(_DottedNameResolver): def __init__(self, package=None): # default to package = None for bw compat return _DottedNameResolver.__init__(self, package) _marker = object() class InstancePropertyHelper(object): """A helper object for assigning properties and descriptors to instances. It is not normally possible to do this because descriptors must be defined on the class itself. This class is optimized for adding multiple properties at once to an instance. This is done by calling :meth:`.add_property` once per-property and then invoking :meth:`.apply` on target objects. """ def __init__(self): self.properties = {} @classmethod def make_property(cls, callable, name=None, reify=False): """ Convert a callable into one suitable for adding to the instance. This will return a 2-tuple containing the computed (name, property) pair. """ is_property = isinstance(callable, property) if is_property: fn = callable if name is None: raise ValueError('must specify "name" for a property') if reify: raise ValueError('cannot reify a property') elif name is not None: fn = lambda this: callable(this) fn.__name__ = get_callable_name(name) fn.__doc__ = callable.__doc__ else: name = callable.__name__ fn = callable if reify: import pyramid.decorator # avoid circular import fn = pyramid.decorator.reify(fn) elif not is_property: fn = property(fn) return name, fn @classmethod def apply_properties(cls, target, properties): """Accept a list or dict of ``properties`` generated from :meth:`.make_property` and apply them to a ``target`` object. """ attrs = dict(properties) if attrs: parent = target.__class__ newcls = type(parent.__name__, (parent, object), attrs) # We assign __provides__ and __implemented__ below to prevent a # memory leak that results from from the usage of this instance's # eventual use in an adapter lookup. Adapter lookup results in # ``zope.interface.implementedBy`` being called with the # newly-created class as an argument. Because the newly-created # class has no interface specification data of its own, lookup # causes new ClassProvides and Implements instances related to our # just-generated class to be created and set into the newly-created # class' __dict__. We don't want these instances to be created; we # want this new class to behave exactly like it is the parent class # instead. See GitHub issues #1212, #1529 and #1568 for more # information. for name in ('__implemented__', '__provides__'): # we assign these attributes conditionally to make it possible # to test this class in isolation without having any interfaces # attached to it val = getattr(parent, name, _marker) if val is not _marker: setattr(newcls, name, val) target.__class__ = newcls @classmethod def set_property(cls, target, callable, name=None, reify=False): """A helper method to apply a single property to an instance.""" prop = cls.make_property(callable, name=name, reify=reify) cls.apply_properties(target, [prop]) def add_property(self, callable, name=None, reify=False): """Add a new property configuration. This should be used in combination with :meth:`.apply` as a more efficient version of :meth:`.set_property`. """ name, fn = self.make_property(callable, name=name, reify=reify) self.properties[name] = fn def apply(self, target): """ Apply all configured properties to the ``target`` instance.""" if self.properties: self.apply_properties(target, self.properties) class InstancePropertyMixin(object): """ Mixin that will allow an instance to add properties at run-time as if they had been defined via @property or @reify on the class itself. """ def set_property(self, callable, name=None, reify=False): """ Add a callable or a property descriptor to the instance. Properties, unlike attributes, are lazily evaluated by executing an underlying callable when accessed. They can be useful for adding features to an object without any cost if those features go unused. A property may also be reified via the :class:`pyramid.decorator.reify` decorator by setting ``reify=True``, allowing the result of the evaluation to be cached. Using this method, the value of the property is only computed once for the lifetime of the object. ``callable`` can either be a callable that accepts the instance as its single positional parameter, or it can be a property descriptor. If the ``callable`` is a property descriptor, the ``name`` parameter must be supplied or a ``ValueError`` will be raised. Also note that a property descriptor cannot be reified, so ``reify`` must be ``False``. If ``name`` is None, the name of the property will be computed from the name of the ``callable``. .. code-block:: python :linenos: class Foo(InstancePropertyMixin): _x = 1 def _get_x(self): return _x def _set_x(self, value): self._x = value foo = Foo() foo.set_property(property(_get_x, _set_x), name='x') foo.set_property(_get_x, name='y', reify=True) >>> foo.x 1 >>> foo.y 1 >>> foo.x = 5 >>> foo.x 5 >>> foo.y # notice y keeps the original value 1 """ InstancePropertyHelper.set_property( self, callable, name=name, reify=reify) class WeakOrderedSet(object): """ Maintain a set of items. Each item is stored as a weakref to avoid extending their lifetime. The values may be iterated over or the last item added may be accessed via the ``last`` property. If items are added more than once, the most recent addition will be remembered in the order: order = WeakOrderedSet() order.add('1') order.add('2') order.add('1') list(order) == ['2', '1'] order.last == '1' """ def __init__(self): self._items = {} self._order = [] def add(self, item): """ Add an item to the set.""" oid = id(item) if oid in self._items: self._order.remove(oid) self._order.append(oid) return ref = weakref.ref(item, lambda x: self.remove(item)) self._items[oid] = ref self._order.append(oid) def remove(self, item): """ Remove an item from the set.""" oid = id(item) if oid in self._items: del self._items[oid] self._order.remove(oid) def empty(self): """ Clear all objects from the set.""" self._items = {} self._order = [] def __len__(self): return len(self._order) def __contains__(self, item): oid = id(item) return oid in self._items def __iter__(self): return (self._items[oid]() for oid in self._order) @property def last(self): if self._order: oid = self._order[-1] return self._items[oid]() 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 if they are equal. It's careful not to leak information about *where* they differ as a result of its running time, which can be very important to avoid certain timing-related crypto attacks: http://seb.dbzteam.org/crypto/python-oauth-timing-hmac.pdf .. 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): """ Produce a human-consumable text description of ``object``, usually involving a Python dotted name. For example: >>> object_description(None) u'None' >>> from xml.dom import minidom >>> object_description(minidom) u'module xml.dom.minidom' >>> object_description(minidom.Attr) u'class xml.dom.minidom.Attr' >>> object_description(minidom.Attr.appendChild) u'method appendChild of class xml.dom.minidom.Attr' If this method cannot identify the type of the object, a generic description ala ``object `` will be returned. If the object passed is already a string, it is simply returned. If it is a boolean, an integer, a list, a tuple, a set, or ``None``, a (possibly shortened) string representation is returned. """ if isinstance(object, string_types): return text_(object) if isinstance(object, integer_types): return text_(str(object)) if isinstance(object, (bool, float, type(None))): return text_(str(object)) if isinstance(object, set): if PY3: return shortrepr(object, '}') else: return shortrepr(object, ')') if isinstance(object, tuple): return shortrepr(object, ')') if isinstance(object, list): return shortrepr(object, ']') if isinstance(object, dict): return shortrepr(object, '}') module = inspect.getmodule(object) if module is None: return text_('object %s' % str(object)) modulename = module.__name__ if inspect.ismodule(object): return text_('module %s' % modulename) if inspect.ismethod(object): oself = getattr(object, '__self__', None) if oself is None: # pragma: no cover oself = getattr(object, 'im_self', None) return text_('method %s of class %s.%s' % (object.__name__, modulename, oself.__class__.__name__)) if inspect.isclass(object): dottedname = '%s.%s' % (modulename, object.__name__) return text_('class %s' % dottedname) if inspect.isfunction(object): dottedname = '%s.%s' % (modulename, object.__name__) return text_('function %s' % dottedname) return text_('object %s' % str(object)) def shortrepr(object, closer): r = str(object) if len(r) > 100: r = r[:100] + ' ... %s' % closer return r class Sentinel(object): def __init__(self, repr): self.repr = repr def __repr__(self): return self.repr FIRST = Sentinel('FIRST') LAST = Sentinel('LAST') class TopologicalSorter(object): """ A utility class which can be used to perform topological sorts against tuple-like data.""" def __init__( self, default_before=LAST, default_after=None, first=FIRST, last=LAST, ): self.names = [] self.req_before = set() self.req_after = set() self.name2before = {} self.name2after = {} self.name2val = {} self.order = [] self.default_before = default_before self.default_after = default_after self.first = first self.last = last def remove(self, name): """ Remove a node from the sort input """ self.names.remove(name) del self.name2val[name] after = self.name2after.pop(name, []) if after: self.req_after.remove(name) for u in after: self.order.remove((u, name)) before = self.name2before.pop(name, []) if before: self.req_before.remove(name) for u in before: self.order.remove((name, u)) def add(self, name, val, after=None, before=None): """ Add a node to the sort input. The ``name`` should be a string or any other hashable object, the ``val`` should be the sortable (doesn't need to be hashable). ``after`` and ``before`` represents the name of one of the other sortables (or a sequence of such named) or one of the special sentinel values :attr:`pyramid.util.FIRST`` or :attr:`pyramid.util.LAST` representing the first or last positions respectively. ``FIRST`` and ``LAST`` can also be part of a sequence passed as ``before`` or ``after``. A sortable should not be added after LAST or before FIRST. An example:: sorter = TopologicalSorter() sorter.add('a', {'a':1}, before=LAST, after='b') sorter.add('b', {'b':2}, before=LAST, after='c') sorter.add('c', {'c':3}) sorter.sorted() # will be {'c':3}, {'b':2}, {'a':1} """ if name in self.names: self.remove(name) self.names.append(name) self.name2val[name] = val if after is None and before is None: before = self.default_before after = self.default_after if after is not None: if not is_nonstr_iter(after): after = (after,) self.name2after[name] = after self.order += [(u, name) for u in after] self.req_after.add(name) if before is not None: if not is_nonstr_iter(before): before = (before,) self.name2before[name] = before self.order += [(name, o) for o in before] self.req_before.add(name) def sorted(self): """ Returns the sort input values in topologically sorted order""" order = [(self.first, self.last)] roots = [] graph = {} names = [self.first, self.last] names.extend(self.names) for a, b in self.order: order.append((a, b)) def add_node(node): if not node in graph: roots.append(node) graph[node] = [0] # 0 = number of arcs coming into this node def add_arc(fromnode, tonode): graph[fromnode].append(tonode) graph[tonode][0] += 1 if tonode in roots: roots.remove(tonode) for name in names: add_node(name) has_before, has_after = set(), set() for a, b in order: if a in names and b in names: # deal with missing dependencies add_arc(a, b) has_before.add(a) has_after.add(b) if not self.req_before.issubset(has_before): raise ConfigurationError( 'Unsatisfied before dependencies: %s' % (', '.join(sorted(self.req_before - has_before))) ) if not self.req_after.issubset(has_after): raise ConfigurationError( 'Unsatisfied after dependencies: %s' % (', '.join(sorted(self.req_after - has_after))) ) sorted_names = [] while roots: root = roots.pop(0) sorted_names.append(root) children = graph[root][1:] for child in children: arcs = graph[child][0] arcs -= 1 graph[child][0] = arcs if arcs == 0: roots.insert(0, child) del graph[root] if graph: # loop in input cycledeps = {} for k, v in graph.items(): cycledeps[k] = v[1:] raise CyclicDependencyError(cycledeps) result = [] for name in sorted_names: if name in self.names: result.append((name, self.name2val[name])) return result def viewdefaults(wrapped): """ Decorator for add_view-like methods which takes into account __view_defaults__ attached to view it is passed. Not a documented API but used by some external systems.""" def wrapper(self, *arg, **kw): defaults = {} if arg: view = arg[0] else: view = kw.get('view') view = self.maybe_dotted(view) if inspect.isclass(view): defaults = getattr(view, '__view_defaults__', {}).copy() if not '_backframes' in kw: kw['_backframes'] = 1 # for action_method defaults.update(kw) return wrapped(self, *arg, **defaults) return functools.wraps(wrapped)(wrapper) @implementer(IActionInfo) class ActionInfo(object): def __init__(self, file, line, function, src): self.file = file self.line = line self.function = function self.src = src def __str__(self): srclines = self.src.split('\n') src = '\n'.join(' %s' % x for x in srclines) return 'Line %s of file %s:\n%s' % (self.line, self.file, src) def action_method(wrapped): """ Wrapper to provide the right conflict info report data when a method that calls Configurator.action calls another that does the same. Not a documented API but used by some external systems.""" def wrapper(self, *arg, **kw): if self._ainfo is None: self._ainfo = [] info = kw.pop('_info', None) # backframes for outer decorators to actionmethods backframes = kw.pop('_backframes', 0) + 2 if is_nonstr_iter(info) and len(info) == 4: # _info permitted as extract_stack tuple info = ActionInfo(*info) if info is None: try: f = traceback.extract_stack(limit=4) # Work around a Python 3.5 issue whereby it would insert an # extra stack frame. This should no longer be necessary in # Python 3.5.1 last_frame = ActionInfo(*f[-1]) if last_frame.function == 'extract_stack': # pragma: no cover f.pop() info = ActionInfo(*f[-backframes]) except: # pragma: no cover info = ActionInfo(None, 0, '', '') self._ainfo.append(info) try: result = wrapped(self, *arg, **kw) finally: self._ainfo.pop() return result if hasattr(wrapped, '__name__'): functools.update_wrapper(wrapper, wrapped) wrapper.__docobj__ = wrapped return wrapper def get_callable_name(name): """ Verifies that the ``name`` is ascii and will raise a ``ConfigurationError`` if it is not. """ try: return native_(name, 'ascii') except (UnicodeEncodeError, UnicodeDecodeError): msg = ( '`name="%s"` is invalid. `name` must be ascii because it is ' 'used on __name__ of the method' ) raise ConfigurationError(msg % name) pyramid-1.6/pyramid/view.py0000644000076500000240000005217212642137120016550 0ustar michaelstaff00000000000000import itertools import venusian from zope.interface import providedBy from pyramid.interfaces import ( IRoutesMapper, IMultiView, ISecuredView, IView, IViewClassifier, IRequest, ) from pyramid.compat import decode_path_info from pyramid.exceptions import PredicateMismatch from pyramid.httpexceptions import ( HTTPFound, default_exceptionresponse_view, ) from pyramid.threadlocal import get_current_registry _marker = object() def render_view_to_response(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` registered against the specified ``context`` and ``request`` and return a :term:`response` object. This function will return ``None`` if a corresponding :term:`view callable` cannot be found (when no :term:`view configuration` matches the combination of ``name`` / ``context`` / and ``request``). If `secure`` is ``True``, and the :term:`view callable` found is protected by a permission, the permission will be checked before calling the view function. If the permission check disallows view execution (based on the current :term:`authorization policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised. The exception's ``args`` attribute explains why the view access was disallowed. If ``secure`` is ``False``, no permission checking is done.""" registry = getattr(request, 'registry', None) if registry is None: registry = get_current_registry() context_iface = providedBy(context) # We explicitly pass in the interfaces provided by the request as # request_iface to _call_view; we don't want _call_view to use # request.request_iface, because render_view_to_response and friends are # pretty much limited to finding views that are not views associated with # routes, and the only thing request.request_iface is used for is to find # route-based views. The render_view_to_response API is (and always has # been) a stepchild API reserved for use of those who actually use # traversal. Doing this fixes an infinite recursion bug introduced in # Pyramid 1.6a1, and causes the render_view* APIs to behave as they did in # 1.5 and previous. We should probably provide some sort of different API # that would allow people to find views for routes. See # https://github.com/Pylons/pyramid/issues/1643 for more info. request_iface = providedBy(request) response = _call_view( registry, request, context, context_iface, name, secure=secure, request_iface=request_iface, ) return response # NB: might be None def render_view_to_iterable(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` registered against the specified ``context`` and ``request`` and return an iterable object which represents the body of a response. This function will return ``None`` if a corresponding :term:`view callable` cannot be found (when no :term:`view configuration` matches the combination of ``name`` / ``context`` / and ``request``). Additionally, this function will raise a :exc:`ValueError` if a view function is found and called but the view function's result does not have an ``app_iter`` attribute. You can usually get the bytestring representation of the return value of this function by calling ``b''.join(iterable)``, or just use :func:`pyramid.view.render_view` instead. If ``secure`` is ``True``, and the view is protected by a permission, the permission will be checked before the view function is invoked. If the permission check disallows view execution (based on the current :term:`authentication policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its ``args`` attribute explains why the view access was disallowed. If ``secure`` is ``False``, no permission checking is done.""" response = render_view_to_response(context, request, name, secure) if response is None: return None return response.app_iter def render_view(context, request, name='', secure=True): """ Call the :term:`view callable` configured with a :term:`view configuration` that matches the :term:`view name` ``name`` registered against the specified ``context`` and ``request`` and unwind the view response's ``app_iter`` (see :ref:`the_response`) into a single bytestring. This function will return ``None`` if a corresponding :term:`view callable` cannot be found (when no :term:`view configuration` matches the combination of ``name`` / ``context`` / and ``request``). Additionally, this function will raise a :exc:`ValueError` if a view function is found and called but the view function's result does not have an ``app_iter`` attribute. This function will return ``None`` if a corresponding view cannot be found. If ``secure`` is ``True``, and the view is protected by a permission, the permission will be checked before the view is invoked. If the permission check disallows view execution (based on the current :term:`authorization policy`), a :exc:`pyramid.httpexceptions.HTTPForbidden` exception will be raised; its ``args`` attribute explains why the view access was disallowed. If ``secure`` is ``False``, no permission checking is done.""" iterable = render_view_to_iterable(context, request, name, secure) if iterable is None: return None return b''.join(iterable) class view_config(object): """ A function, class or method :term:`decorator` which allows a developer to create view registrations nearer to a :term:`view callable` definition than use :term:`imperative configuration` to do the same. For example, this code in a module ``views.py``:: from resources import MyResource @view_config(name='my_view', context=MyResource, permission='read', route_name='site1') def my_view(context, request): return 'OK' Might replace the following call to the :meth:`pyramid.config.Configurator.add_view` method:: import views from resources import MyResource config.add_view(views.my_view, context=MyResource, name='my_view', permission='read', route_name='site1') .. note: :class:`pyramid.view.view_config` is also importable, for backwards compatibility purposes, as the name :class:`pyramid.view.bfg_view`. :class:`pyramid.view.view_config` supports the following keyword arguments: ``context``, ``permission``, ``name``, ``request_type``, ``route_name``, ``request_method``, ``request_param``, ``containment``, ``xhr``, ``accept``, ``header``, ``path_info``, ``custom_predicates``, ``decorator``, ``mapper``, ``http_cache``, ``match_param``, ``csrf_token``, ``physical_path``, and ``predicates``. The meanings of these arguments are the same as the arguments passed to :meth:`pyramid.config.Configurator.add_view`. If any argument is left out, its default will be the equivalent ``add_view`` default. An additional keyword argument named ``_depth`` is provided for people who wish to reuse this class from another decorator. The default value is ``0`` and should be specified relative to the ``view_config`` invocation. It will be passed in to the :term:`venusian` ``attach`` function as the depth of the callstack when Venusian checks if the decorator is being used in a class or module context. It's not often used, but it can be useful in this circumstance. See the ``attach`` function in Venusian for more information. .. seealso:: See also :ref:`mapping_views_using_a_decorator_section` for details about using :class:`pyramid.view.view_config`. .. warning:: ``view_config`` will work ONLY on module top level members because of the limitation of ``venusian.Scanner.scan``. """ venusian = venusian # for testing injection def __init__(self, **settings): if 'for_' in settings: if settings.get('context') is None: settings['context'] = settings['for_'] self.__dict__.update(settings) def __call__(self, wrapped): settings = self.__dict__.copy() depth = settings.pop('_depth', 0) def callback(context, name, ob): config = context.config.with_package(info.module) config.add_view(view=ob, **settings) info = self.venusian.attach(wrapped, callback, category='pyramid', depth=depth + 1) if info.scope == 'class': # if the decorator was attached to a method in a class, or # otherwise executed at class scope, we need to set an # 'attr' into the settings if one isn't already in there if settings.get('attr') is None: settings['attr'] = wrapped.__name__ settings['_info'] = info.codeinfo # fbo "action_method" return wrapped bfg_view = view_config # bw compat (forever) class view_defaults(view_config): """ A class :term:`decorator` which, when applied to a class, will provide defaults for all view configurations that use the class. This decorator accepts all the arguments accepted by :meth:`pyramid.view.view_config`, and each has the same meaning. See :ref:`view_defaults` for more information. """ def __call__(self, wrapped): wrapped.__view_defaults__ = self.__dict__.copy() return wrapped class AppendSlashNotFoundViewFactory(object): """ There can only be one :term:`Not Found view` in any :app:`Pyramid` application. Even if you use :func:`pyramid.view.append_slash_notfound_view` as the Not Found view, :app:`Pyramid` still must generate a ``404 Not Found`` response when it cannot redirect to a slash-appended URL; this not found response will be visible to site users. If you don't care what this 404 response looks like, and you only need redirections to slash-appended route URLs, you may use the :func:`pyramid.view.append_slash_notfound_view` object as the Not Found view. However, if you wish to use a *custom* notfound view callable when a URL cannot be redirected to a slash-appended URL, you may wish to use an instance of this class as the Not Found view, supplying a :term:`view callable` to be used as the custom notfound view as the first argument to its constructor. For instance: .. code-block:: python from pyramid.httpexceptions import HTTPNotFound from pyramid.view import AppendSlashNotFoundViewFactory def notfound_view(context, request): return HTTPNotFound('nope') custom_append_slash = AppendSlashNotFoundViewFactory(notfound_view) config.add_view(custom_append_slash, context=HTTPNotFound) The ``notfound_view`` supplied must adhere to the two-argument view callable calling convention of ``(context, request)`` (``context`` will be the exception object). .. deprecated:: 1.3 """ def __init__(self, notfound_view=None, redirect_class=HTTPFound): if notfound_view is None: notfound_view = default_exceptionresponse_view self.notfound_view = notfound_view self.redirect_class = redirect_class def __call__(self, context, request): path = decode_path_info(request.environ['PATH_INFO'] or '/') registry = request.registry mapper = registry.queryUtility(IRoutesMapper) if mapper is not None and not path.endswith('/'): slashpath = path + '/' for route in mapper.get_routes(): if route.match(slashpath) is not None: qs = request.query_string if qs: qs = '?' + qs return self.redirect_class(location=request.path+'/'+qs) return self.notfound_view(context, request) append_slash_notfound_view = AppendSlashNotFoundViewFactory() append_slash_notfound_view.__doc__ = """\ For behavior like Django's ``APPEND_SLASH=True``, use this view as the :term:`Not Found view` in your application. When this view is the Not Found view (indicating that no view was found), and any routes have been defined in the configuration of your application, if the value of the ``PATH_INFO`` WSGI environment variable does not already end in a slash, and if the value of ``PATH_INFO`` *plus* a slash matches any route's path, do an HTTP redirect to the slash-appended PATH_INFO. Note that this will *lose* ``POST`` data information (turning it into a GET), so you shouldn't rely on this to redirect POST requests. Note also that static routes are not considered when attempting to find a matching route. Use the :meth:`pyramid.config.Configurator.add_view` method to configure this view as the Not Found view:: from pyramid.httpexceptions import HTTPNotFound from pyramid.view import append_slash_notfound_view config.add_view(append_slash_notfound_view, context=HTTPNotFound) .. deprecated:: 1.3 """ class notfound_view_config(object): """ .. versionadded:: 1.3 An analogue of :class:`pyramid.view.view_config` which registers a :term:`Not Found View`. The ``notfound_view_config`` constructor accepts most of the same arguments as the constructor of :class:`pyramid.view.view_config`. It can be used in the same places, and behaves in largely the same way, except it always registers a not found exception view instead of a 'normal' view. Example: .. code-block:: python from pyramid.view import notfound_view_config from pyramid.response import Response @notfound_view_config() def notfound(request): return Response('Not found, dude!', status='404 Not Found') All arguments except ``append_slash`` have the same meaning as :meth:`pyramid.view.view_config` and each predicate argument restricts the set of circumstances under which this notfound view will be invoked. If ``append_slash`` is ``True``, when the Not Found View is invoked, and the current path info does not end in a slash, the notfound logic will attempt to find a :term:`route` that matches the request's path info suffixed with a slash. If such a route exists, Pyramid will issue a redirect to the URL implied by the route; if it does not, Pyramid will return the result of the view callable provided as ``view``, as normal. If the argument provided as ``append_slash`` is not a boolean but instead implements :class:`~pyramid.interfaces.IResponse`, the append_slash logic will behave as if ``append_slash=True`` was passed, but the provided class will be used as the response class instead of the default :class:`~pyramid.httpexceptions.HTTPFound` response class when a redirect is performed. For example: .. code-block:: python from pyramid.httpexceptions import ( HTTPMovedPermanently, HTTPNotFound ) @notfound_view_config(append_slash=HTTPMovedPermanently) def aview(request): return HTTPNotFound('not found') The above means that a redirect to a slash-appended route will be attempted, but instead of :class:`~pyramid.httpexceptions.HTTPFound` being used, :class:`~pyramid.httpexceptions.HTTPMovedPermanently will be used` for the redirect response if a slash-appended route is found. .. versionchanged:: 1.6 See :ref:`changing_the_notfound_view` for detailed usage information. """ venusian = venusian def __init__(self, **settings): self.__dict__.update(settings) def __call__(self, wrapped): settings = self.__dict__.copy() def callback(context, name, ob): config = context.config.with_package(info.module) config.add_notfound_view(view=ob, **settings) info = self.venusian.attach(wrapped, callback, category='pyramid') if info.scope == 'class': # if the decorator was attached to a method in a class, or # otherwise executed at class scope, we need to set an # 'attr' into the settings if one isn't already in there if settings.get('attr') is None: settings['attr'] = wrapped.__name__ settings['_info'] = info.codeinfo # fbo "action_method" return wrapped class forbidden_view_config(object): """ .. versionadded:: 1.3 An analogue of :class:`pyramid.view.view_config` which registers a :term:`forbidden view`. The forbidden_view_config constructor accepts most of the same arguments as the constructor of :class:`pyramid.view.view_config`. It can be used in the same places, and behaves in largely the same way, except it always registers a forbidden exception view instead of a 'normal' view. Example: .. code-block:: python from pyramid.view import forbidden_view_config from pyramid.response import Response @forbidden_view_config() def forbidden(request): return Response('You are not allowed', status='403 Forbidden') All arguments passed to this function have the same meaning as :meth:`pyramid.view.view_config` and each predicate argument restricts the set of circumstances under which this notfound view will be invoked. See :ref:`changing_the_forbidden_view` for detailed usage information. """ venusian = venusian def __init__(self, **settings): self.__dict__.update(settings) def __call__(self, wrapped): settings = self.__dict__.copy() def callback(context, name, ob): config = context.config.with_package(info.module) config.add_forbidden_view(view=ob, **settings) info = self.venusian.attach(wrapped, callback, category='pyramid') if info.scope == 'class': # if the decorator was attached to a method in a class, or # otherwise executed at class scope, we need to set an # 'attr' into the settings if one isn't already in there if settings.get('attr') is None: settings['attr'] = wrapped.__name__ settings['_info'] = info.codeinfo # fbo "action_method" return wrapped def _find_views( registry, request_iface, context_iface, view_name, view_types=None, view_classifier=None, ): if view_types is None: view_types = (IView, ISecuredView, IMultiView) if view_classifier is None: view_classifier = IViewClassifier registered = registry.adapters.registered cache = registry._view_lookup_cache views = cache.get((request_iface, context_iface, view_name)) if views is None: views = [] for req_type, ctx_type in itertools.product( request_iface.__sro__, context_iface.__sro__ ): source_ifaces = (view_classifier, req_type, ctx_type) for view_type in view_types: view_callable = registered( source_ifaces, view_type, name=view_name, ) if view_callable is not None: views.append(view_callable) if views: # do not cache view lookup misses. rationale: dont allow cache to # grow without bound if somebody tries to hit the site with many # missing URLs. we could use an LRU cache instead, but then # purposeful misses by an attacker would just blow out the cache # anyway. downside: misses will almost always consume more CPU than # hits in steady state. with registry._lock: cache[(request_iface, context_iface, view_name)] = views return views def _call_view( registry, request, context, context_iface, view_name, view_types=None, view_classifier=None, secure=True, request_iface=None, ): if request_iface is None: request_iface = getattr(request, 'request_iface', IRequest) view_callables = _find_views( registry, request_iface, context_iface, view_name, view_types=view_types, view_classifier=view_classifier, ) pme = None response = None for view_callable in view_callables: # look for views that meet the predicate criteria try: if not secure: # the view will have a __call_permissive__ attribute if it's # secured; otherwise it won't. view_callable = getattr( view_callable, '__call_permissive__', view_callable ) # if this view is secured, it will raise a Forbidden # appropriately if the executing user does not have the proper # permission response = view_callable(context, request) return response except PredicateMismatch as _pme: pme = _pme if pme is not None: raise pme return response pyramid-1.6/pyramid/wsgi.py0000644000076500000240000000642512520062551016547 0ustar michaelstaff00000000000000from functools import wraps from pyramid.request import call_app_with_subpath_as_path_info def wsgiapp(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` :term:`view callable`. This decorator differs from the :func:`pyramid.wsgi.wsgiapp2` decorator inasmuch as fixups of ``PATH_INFO`` and ``SCRIPT_NAME`` within the WSGI environment *are not* performed before the application is invoked. E.g., the following in a ``views.py`` module:: @wsgiapp def hello_world(environ, start_response): body = 'Hello world' start_response('200 OK', [ ('Content-Type', 'text/plain'), ('Content-Length', len(body)) ] ) return [body] Allows the following call to :meth:`pyramid.config.Configurator.add_view`:: from views import hello_world config.add_view(hello_world, name='hello_world.txt') The ``wsgiapp`` decorator will convert the result of the WSGI application to a :term:`Response` and return it to :app:`Pyramid` as if the WSGI app were a :app:`Pyramid` view. """ if wrapped is None: raise ValueError('wrapped can not be None') def decorator(context, request): return request.get_response(wrapped) # Support case where wrapped is a callable object instance if getattr(wrapped, '__name__', None): return wraps(wrapped)(decorator) return wraps(wrapped, ('__module__', '__doc__'))(decorator) def wsgiapp2(wrapped): """ Decorator to turn a WSGI application into a :app:`Pyramid` view callable. This decorator differs from the :func:`pyramid.wsgi.wsgiapp` decorator inasmuch as fixups of ``PATH_INFO`` and ``SCRIPT_NAME`` within the WSGI environment *are* performed before the application is invoked. E.g. the following in a ``views.py`` module:: @wsgiapp2 def hello_world(environ, start_response): body = 'Hello world' start_response('200 OK', [ ('Content-Type', 'text/plain'), ('Content-Length', len(body)) ] ) return [body] Allows the following call to :meth:`pyramid.config.Configurator.add_view`:: from views import hello_world config.add_view(hello_world, name='hello_world.txt') The ``wsgiapp2`` decorator will convert the result of the WSGI application to a Response and return it to :app:`Pyramid` as if the WSGI app were a :app:`Pyramid` view. The ``SCRIPT_NAME`` and ``PATH_INFO`` values present in the WSGI environment are fixed up before the application is invoked. In particular, a new WSGI environment is generated, and the :term:`subpath` of the request passed to ``wsgiapp2`` is used as the new request's ``PATH_INFO`` and everything preceding the subpath is used as the ``SCRIPT_NAME``. The new environment is passed to the downstream WSGI application.""" 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) # Support case where wrapped is a callable object instance if getattr(wrapped, '__name__', None): return wraps(wrapped)(decorator) return wraps(wrapped, ('__module__', '__doc__'))(decorator) pyramid-1.6/pyramid.egg-info/0000755000076500000240000000000012642137501016712 5ustar michaelstaff00000000000000pyramid-1.6/pyramid.egg-info/dependency_links.txt0000644000076500000240000000000112642137473022770 0ustar michaelstaff00000000000000 pyramid-1.6/pyramid.egg-info/entry_points.txt0000644000076500000240000000152412642137473022222 0ustar michaelstaff00000000000000 [pyramid.scaffold] starter=pyramid.scaffolds:StarterProjectTemplate zodb=pyramid.scaffolds:ZODBProjectTemplate alchemy=pyramid.scaffolds:AlchemyProjectTemplate [pyramid.pshell_runner] python=pyramid.scripts.pshell:python_shell_runner [console_scripts] pcreate = pyramid.scripts.pcreate:main pserve = pyramid.scripts.pserve:main pshell = pyramid.scripts.pshell:main proutes = pyramid.scripts.proutes:main pviews = pyramid.scripts.pviews:main ptweens = pyramid.scripts.ptweens:main prequest = pyramid.scripts.prequest:main pdistreport = pyramid.scripts.pdistreport:main [paste.server_runner] wsgiref = pyramid.scripts.pserve:wsgiref_server_runner cherrypy = pyramid.scripts.pserve:cherrypy_server_runner pyramid-1.6/pyramid.egg-info/not-zip-safe0000644000076500000240000000000112465350752021150 0ustar michaelstaff00000000000000 pyramid-1.6/pyramid.egg-info/PKG-INFO0000644000076500000240000005722412642137473020031 0ustar michaelstaff00000000000000Metadata-Version: 1.1 Name: pyramid Version: 1.6 Summary: The Pyramid Web Framework, a Pylons project Home-page: http://docs.pylonsproject.org/en/latest/docs/pyramid.html Author: Chris McDonough, Agendaless Consulting Author-email: pylons-discuss@googlegroups.com License: BSD-derived (http://www.repoze.org/LICENSE.txt) Description: Pyramid ======= .. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.6-branch :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: Master Documentation Status .. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/ :alt: Latest Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg :target: https://webchat.freenode.net/?channels=pyramid :alt: IRC Freenode 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. Pyramid is produced by the `Pylons Project `_. Support and Documentation ------------------------- See the `Pylons Project website `_ to view documentation, report bugs, and obtain support. Developing and Contributing --------------------------- See ``HACKING.txt`` and ``contributing.md`` for guidelines for running tests, adding features, coding style, and updating documentation when developing in or contributing to Pyramid. License ------- Pyramid is offered under the BSD-derived `Repoze Public License `_. Authors ------- Pyramid is made available by `Agendaless Consulting `_ and a team of contributors. 1.6 (2016-01-03) ================ Deprecations ------------ - Continue removal of ``pserve`` daemon/process management features by deprecating ``--user`` and ``--group`` options. See https://github.com/Pylons/pyramid/pull/2190 1.6b3 (2015-12-17) ================== Backward Incompatibilities -------------------------- - Remove the ``cachebust`` option from ``config.add_static_view``. See ``config.add_cache_buster`` for the new way to attach cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 - Modify the ``pyramid.interfaces.ICacheBuster`` API to be a simple callable instead of an object with ``match`` and ``pregenerate`` methods. Cache busters are now focused solely on generation. Matching has been dropped. Note this affects usage of ``pyramid.static.QueryStringCacheBuster`` and ``pyramid.static.ManifestCacheBuster``. See https://github.com/Pylons/pyramid/pull/2186 Features -------- - Add a new ``config.add_cache_buster`` API for attaching cache busters to static assets. See https://github.com/Pylons/pyramid/pull/2186 Bug Fixes --------- - Ensure that ``IAssetDescriptor.abspath`` always returns an absolute path. There were cases depending on the process CWD that a relative path would be returned. See https://github.com/Pylons/pyramid/issues/2188 1.6b2 (2015-10-15) ================== Features -------- - Allow asset specifications to be supplied to ``pyramid.static.ManifestCacheBuster`` instead of requiring a filesystem path. 1.6b1 (2015-10-15) ================== Backward Incompatibilities -------------------------- - IPython and BPython support have been removed from pshell in the core. To continue using them on Pyramid 1.6+ you must install the binding packages explicitly:: $ pip install pyramid_ipython or $ pip install pyramid_bpython - Remove default cache busters introduced in 1.6a1 including ``PathSegmentCacheBuster``, ``PathSegmentMd5CacheBuster``, and ``QueryStringMd5CacheBuster``. See https://github.com/Pylons/pyramid/pull/2116 Features -------- - Additional shells for ``pshell`` can now be registered as entrypoints. See https://github.com/Pylons/pyramid/pull/1891 and https://github.com/Pylons/pyramid/pull/2012 - The variables injected into ``pshell`` are now displayed with their docstrings instead of the default ``str(obj)`` when possible. See https://github.com/Pylons/pyramid/pull/1929 - Add new ``pyramid.static.ManifestCacheBuster`` for use with external asset pipelines as well as examples of common usages in the narrative. See https://github.com/Pylons/pyramid/pull/2116 - Fix ``pserve --reload`` to not crash on syntax errors!!! See https://github.com/Pylons/pyramid/pull/2125 - Fix an issue when user passes unparsed strings to ``pyramid.session.CookieSession`` and ``pyramid.authentication.AuthTktCookieHelper`` for time related parameters ``timeout``, ``reissue_time``, ``max_age`` that expect an integer value. See https://github.com/Pylons/pyramid/pull/2050 Bug Fixes --------- - ``pyramid.httpexceptions.HTTPException`` now defaults to ``520 Unknown Error`` instead of ``None None`` to conform with changes in WebOb 1.5. See https://github.com/Pylons/pyramid/pull/1865 - ``pshell`` will now preserve the capitalization of variables in the ``[pshell]`` section of the INI file. This makes exposing classes to the shell a little more straightfoward. See https://github.com/Pylons/pyramid/pull/1883 - Fixed usage of ``pserve --monitor-restart --daemon`` which would fail in horrible ways. See https://github.com/Pylons/pyramid/pull/2118 - Explicitly prevent ``pserve --reload --daemon`` from being used. It's never been supported but would work and fail in weird ways. See https://github.com/Pylons/pyramid/pull/2119 - Fix an issue on Windows when running ``pserve --reload`` in which the process failed to fork because it could not find the pserve script to run. See https://github.com/Pylons/pyramid/pull/2138 Deprecations ------------ - Deprecate ``pserve --monitor-restart`` in favor of user's using a real process manager such as Systemd or Upstart as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/2120 1.6a2 (2015-06-30) ================== Bug Fixes --------- - Ensure that ``pyramid.httpexceptions.exception_response`` returns the appropriate "concrete" class for ``400`` and ``500`` status codes. See https://github.com/Pylons/pyramid/issues/1832 - Fix an infinite recursion bug introduced in 1.6a1 when ``pyramid.view.render_view_to_response`` was called directly or indirectly. See https://github.com/Pylons/pyramid/issues/1643 - Further fix the JSONP renderer by prefixing the returned content with a comment. This should mitigate attacks from Flash (See CVE-2014-4671). See https://github.com/Pylons/pyramid/pull/1649 - Allow periods and brackets (``[]``) in the JSONP callback. The original fix was overly-restrictive and broke Angular. See https://github.com/Pylons/pyramid/pull/1649 1.6a1 (2015-04-15) ================== Features -------- - pcreate will now ask for confirmation if invoked with an argument for a project name that already exists or is importable in the current environment. See https://github.com/Pylons/pyramid/issues/1357 and https://github.com/Pylons/pyramid/pull/1837 - Make it possible to subclass ``pyramid.request.Request`` and also use ``pyramid.request.Request.add_request.method``. See https://github.com/Pylons/pyramid/issues/1529 - The ``pyramid.config.Configurator`` has grown the ability to allow actions to call other actions during a commit-cycle. This enables much more logic to be placed into actions, such as the ability to invoke other actions or group them for improved conflict detection. We have also exposed and documented the config phases that Pyramid uses in order to further assist in building conforming addons. See https://github.com/Pylons/pyramid/pull/1513 - Add ``pyramid.request.apply_request_extensions`` function which can be used in testing to apply any request extensions configured via ``config.add_request_method``. Previously it was only possible to test the extensions by going through Pyramid's router. See https://github.com/Pylons/pyramid/pull/1581 - pcreate when run without a scaffold argument will now print information on the missing flag, as well as a list of available scaffolds. See https://github.com/Pylons/pyramid/pull/1566 and https://github.com/Pylons/pyramid/issues/1297 - Added support / testing for 'pypy3' under Tox and Travis. See https://github.com/Pylons/pyramid/pull/1469 - Automate code coverage metrics across py2 and py3 instead of just py2. See https://github.com/Pylons/pyramid/pull/1471 - Cache busting for static resources has been added and is available via a new argument to ``pyramid.config.Configurator.add_static_view``: ``cachebust``. Core APIs are shipped for both cache busting via query strings and path segments and may be extended to fit into custom asset pipelines. See https://github.com/Pylons/pyramid/pull/1380 and https://github.com/Pylons/pyramid/pull/1583 - 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 - Allow an iterator to be returned from a renderer. Previously it was only possible to return bytes or unicode. See https://github.com/Pylons/pyramid/pull/1417 - ``pserve`` can now take a ``-b`` or ``--browser`` option to open the server URL in a web browser. See https://github.com/Pylons/pyramid/pull/1533 - Overall improvments for the ``proutes`` command. Added ``--format`` and ``--glob`` arguments to the command, introduced the ``method`` column for displaying available request methods, and improved the ``view`` output by showing the module instead of just ``__repr__``. See https://github.com/Pylons/pyramid/pull/1488 - Support keyword-only arguments and function annotations in views in Python 3. See https://github.com/Pylons/pyramid/pull/1556 - ``request.response`` will no longer be mutated when using the ``pyramid.renderers.render_to_response()`` API. It is now necessary to pass in a ``response=`` argument to ``render_to_response`` if you wish to supply the renderer with a custom response object for it to use. If you do not pass one then a response object will be created using the application's ``IResponseFactory``. Almost all renderers mutate the ``request.response`` response object (for example, the JSON renderer sets ``request.response.content_type`` to ``application/json``). However, when invoking ``render_to_response`` it is not expected that the response object being returned would be the same one used later in the request. The response object returned from ``render_to_response`` is now explicitly different from ``request.response``. This does not change the API of a renderer. See https://github.com/Pylons/pyramid/pull/1563 - The ``append_slash`` argument of ```Configurator().add_notfound_view()`` will now accept anything that implements the ``IResponse`` interface and will use that as the response class instead of the default ``HTTPFound``. See https://github.com/Pylons/pyramid/pull/1610 Bug Fixes --------- - The JSONP renderer created JavaScript code in such a way that a callback variable could be used to arbitrarily inject javascript into the response object. https://github.com/Pylons/pyramid/pull/1627 - Work around an issue where ``pserve --reload`` would leave terminal echo disabled if it reloaded during a pdb session. See https://github.com/Pylons/pyramid/pull/1577, https://github.com/Pylons/pyramid/pull/1592 - ``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 - Allow the ``pyramid.renderers.JSONP`` renderer to work even if there is no valid request object. In this case it will not wrap the object in a callback and thus behave just like the ``pyramid.renderers.JSON`` renderer. See https://github.com/Pylons/pyramid/pull/1561 - Prevent "parameters to load are deprecated" ``DeprecationWarning`` from setuptools>=11.3. See https://github.com/Pylons/pyramid/pull/1541 - Avoiding sharing the ``IRenderer`` objects across threads when attached to a view using the `renderer=` argument. These renderers were instantiated at time of first render and shared between requests, causing potentially subtle effects like `pyramid.reload_templates = true` failing to work in `pyramid_mako`. See https://github.com/Pylons/pyramid/pull/1575 and https://github.com/Pylons/pyramid/issues/1268 - Avoiding timing attacks against CSRF tokens. See https://github.com/Pylons/pyramid/pull/1574 - ``request.finished_callbacks`` and ``request.response_callbacks`` now default to an iterable instead of ``None``. It may be checked for a length of 0. This was the behavior in 1.5. Deprecations ------------ - The ``pserve`` command's daemonization features have been deprecated. This includes the ``[start,stop,restart,status]`` subcommands as well as the ``--daemon``, ``--stop-server``, ``--pid-file``, and ``--status`` flags. Please use a real process manager in the future instead of relying on the ``pserve`` to daemonize itself. Many options exist including your Operating System's services such as Systemd or Upstart, as well as Python-based solutions like Circus and Supervisor. See https://github.com/Pylons/pyramid/pull/1641 - 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 ---- - Moved the documentation for ``accept`` on ``Configurator.add_view`` to no longer be part of the predicate list. See https://github.com/Pylons/pyramid/issues/1391 for a bug report stating ``not_`` was failing on ``accept``. Discussion with @mcdonc led to the conclusion that it should not be documented as a predicate. See https://github.com/Pylons/pyramid/pull/1487 for this PR - Removed logging configuration from Quick Tutorial ini files except for scaffolding- and logging-related chapters to avoid needing to explain it too early. - 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 - Add documentation of command line programs (``p*`` scripts). See https://github.com/Pylons/pyramid/pull/2191 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. - You can now run the scaffolding func tests via ``tox py2-scaffolds`` and ``tox py3-scaffolds``. Keywords: web wsgi pylons pyramid Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Framework :: Pyramid Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: WSGI Classifier: License :: Repoze Public License pyramid-1.6/pyramid.egg-info/requires.txt0000644000076500000240000000056012642137473021323 0ustar michaelstaff00000000000000setuptools WebOb >= 1.3.1 repoze.lru >= 0.4 zope.interface >= 3.8.0 zope.deprecation >= 3.5.0 venusian >= 1.0a3 translationstring >= 0.4 PasteDeploy >= 1.5.0 [docs] Sphinx >= 1.3.1 docutils repoze.sphinx.autointerface pylons_sphinx_latesturl pylons-sphinx-themes sphinxcontrib-programoutput [testing] WebTest >= 1.3.1 zope.component>=3.11.0 nose coverage virtualenv pyramid-1.6/pyramid.egg-info/SOURCES.txt0000644000076500000240000011335612642137501020607 0ustar michaelstaff00000000000000.gitignore .travis.yml BFG_HISTORY.txt CHANGES.txt CONTRIBUTORS.txt COPYRIGHT.txt HACKING.txt HISTORY.txt LICENSE.txt README.rst RELEASING.txt TODO.txt builddocs.sh contributing.md coverage.sh hacking-tox.ini rtd.txt scaffoldtests.sh setup.cfg setup.py tox.ini docs/.gitignore docs/Makefile docs/authorintro.rst docs/changes.rst docs/conf.py docs/conventions.rst docs/convert_images.sh docs/copyright.rst docs/coversizing.py docs/designdefense.rst docs/foreword.rst docs/glossary.rst docs/index.rst docs/latexindex.rst docs/make_book docs/make_epub docs/make_pdf docs/python-3.png docs/quick_tour.rst docs/remake docs/whatsnew-1.0.rst docs/whatsnew-1.1.rst docs/whatsnew-1.2.rst docs/whatsnew-1.3.rst docs/whatsnew-1.4.rst docs/whatsnew-1.5.rst docs/whatsnew-1.6.rst docs/_static/latex-note.png docs/_static/latex-warning.png docs/_static/pyramid_request_processing.graffle docs/_static/pyramid_request_processing.png docs/_static/pyramid_request_processing.svg docs/_static/pyramid_router.graffle docs/_static/pyramid_router.png docs/_static/pyramid_router.svg docs/api/authentication.rst docs/api/authorization.rst docs/api/compat.rst docs/api/config.rst docs/api/decorator.rst docs/api/events.rst docs/api/exceptions.rst docs/api/httpexceptions.rst docs/api/i18n.rst docs/api/index.rst docs/api/interfaces.rst docs/api/location.rst docs/api/paster.rst docs/api/path.rst docs/api/registry.rst docs/api/renderers.rst docs/api/request.rst docs/api/response.rst docs/api/scaffolds.rst docs/api/scripting.rst docs/api/security.rst docs/api/session.rst docs/api/settings.rst docs/api/static.rst docs/api/testing.rst docs/api/threadlocal.rst docs/api/traversal.rst docs/api/tweens.rst docs/api/url.rst docs/api/view.rst docs/api/wsgi.rst docs/narr/advconfig.rst docs/narr/assets.rst docs/narr/commandline.rst docs/narr/configuration.rst docs/narr/environment.rst docs/narr/events.rst docs/narr/extconfig.rst docs/narr/extending.rst docs/narr/firstapp.rst docs/narr/hellotraversal.py docs/narr/hellotraversal.rst docs/narr/helloworld.py docs/narr/hooks.rst docs/narr/hybrid.rst docs/narr/i18n.rst docs/narr/install.rst docs/narr/introduction.rst docs/narr/introspector.rst docs/narr/logging.rst docs/narr/muchadoabouttraversal.rst docs/narr/paste.rst docs/narr/project-debug.png docs/narr/project-show-toolbar.png docs/narr/project.png docs/narr/project.rst docs/narr/renderers.rst docs/narr/resources.rst docs/narr/resourcetreetraverser.png docs/narr/router.png docs/narr/router.rst docs/narr/scaffolding.rst docs/narr/security.rst docs/narr/sessions.rst docs/narr/startup.rst docs/narr/subrequest.rst docs/narr/tb_introspector.png docs/narr/templates.rst docs/narr/testing.rst docs/narr/threadlocals.rst docs/narr/traversal.rst docs/narr/upgrading.rst docs/narr/urldispatch.rst docs/narr/vhosting.rst docs/narr/viewconfig.rst docs/narr/views.rst docs/narr/webob.rst docs/narr/zca.rst docs/narr/MyProject/CHANGES.txt docs/narr/MyProject/MANIFEST.in docs/narr/MyProject/README.txt docs/narr/MyProject/development.ini docs/narr/MyProject/production.ini docs/narr/MyProject/setup.py docs/narr/MyProject/myproject/__init__.py docs/narr/MyProject/myproject/tests.py docs/narr/MyProject/myproject/views.py docs/narr/MyProject/myproject/static/pyramid-16x16.png docs/narr/MyProject/myproject/static/pyramid.png docs/narr/MyProject/myproject/static/theme.css docs/narr/MyProject/myproject/static/theme.min.css docs/narr/MyProject/myproject/templates/mytemplate.pt docs/pscripts/index.rst docs/pscripts/pcreate.rst docs/pscripts/pdistreport.rst docs/pscripts/prequest.rst docs/pscripts/proutes.rst docs/pscripts/pserve.rst docs/pscripts/pshell.rst docs/pscripts/ptweens.rst docs/pscripts/pviews.rst docs/quick_tour/awesome/CHANGES.txt docs/quick_tour/awesome/MANIFEST.in docs/quick_tour/awesome/README.txt docs/quick_tour/awesome/development.ini docs/quick_tour/awesome/message-extraction.ini docs/quick_tour/awesome/setup.py docs/quick_tour/awesome/awesome/__init__.py docs/quick_tour/awesome/awesome/models.py docs/quick_tour/awesome/awesome/tests.py docs/quick_tour/awesome/awesome/views.py docs/quick_tour/awesome/awesome/locale/awesome.pot docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.mo docs/quick_tour/awesome/awesome/locale/de/LC_MESSAGES/awesome.po docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.mo docs/quick_tour/awesome/awesome/locale/fr/LC_MESSAGES/awesome.po docs/quick_tour/awesome/awesome/static/favicon.ico docs/quick_tour/awesome/awesome/static/logo.png docs/quick_tour/awesome/awesome/static/pylons.css docs/quick_tour/awesome/awesome/templates/mytemplate.jinja2 docs/quick_tour/hello_world/app.py docs/quick_tour/jinja2/app.py docs/quick_tour/jinja2/hello_world.jinja2 docs/quick_tour/jinja2/views.py docs/quick_tour/json/app.py docs/quick_tour/json/hello_world.jinja2 docs/quick_tour/json/hello_world.pt docs/quick_tour/json/views.py docs/quick_tour/package/CHANGES.txt docs/quick_tour/package/MANIFEST.in docs/quick_tour/package/README.txt docs/quick_tour/package/development.ini docs/quick_tour/package/message-extraction.ini docs/quick_tour/package/setup.py docs/quick_tour/package/hello_world/__init__.py docs/quick_tour/package/hello_world/init.py docs/quick_tour/package/hello_world/models.py docs/quick_tour/package/hello_world/tests.py docs/quick_tour/package/hello_world/views.py docs/quick_tour/package/hello_world/locale/hello_world.pot docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.mo docs/quick_tour/package/hello_world/locale/de/LC_MESSAGES/hello_world.po docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.mo docs/quick_tour/package/hello_world/locale/fr/LC_MESSAGES/hello_world.po docs/quick_tour/package/hello_world/static/favicon.ico docs/quick_tour/package/hello_world/static/logo.png docs/quick_tour/package/hello_world/static/pylons.css docs/quick_tour/package/hello_world/templates/mytemplate.jinja2 docs/quick_tour/requests/app.py docs/quick_tour/routing/app.py docs/quick_tour/routing/views.py docs/quick_tour/sqla_demo/CHANGES.txt docs/quick_tour/sqla_demo/MANIFEST.in docs/quick_tour/sqla_demo/README.txt docs/quick_tour/sqla_demo/development.ini docs/quick_tour/sqla_demo/production.ini docs/quick_tour/sqla_demo/setup.py docs/quick_tour/sqla_demo/sqla_demo.sqlite docs/quick_tour/sqla_demo/sqla_demo/__init__.py docs/quick_tour/sqla_demo/sqla_demo/models.py docs/quick_tour/sqla_demo/sqla_demo/tests.py docs/quick_tour/sqla_demo/sqla_demo/views.py docs/quick_tour/sqla_demo/sqla_demo/scripts/__init__.py docs/quick_tour/sqla_demo/sqla_demo/scripts/initializedb.py docs/quick_tour/sqla_demo/sqla_demo/static/favicon.ico docs/quick_tour/sqla_demo/sqla_demo/static/footerbg.png docs/quick_tour/sqla_demo/sqla_demo/static/headerbg.png docs/quick_tour/sqla_demo/sqla_demo/static/ie6.css docs/quick_tour/sqla_demo/sqla_demo/static/middlebg.png docs/quick_tour/sqla_demo/sqla_demo/static/pylons.css docs/quick_tour/sqla_demo/sqla_demo/static/pyramid-small.png docs/quick_tour/sqla_demo/sqla_demo/static/pyramid.png docs/quick_tour/sqla_demo/sqla_demo/static/transparent.gif docs/quick_tour/sqla_demo/sqla_demo/templates/mytemplate.pt docs/quick_tour/static_assets/app.py docs/quick_tour/static_assets/hello_world.jinja2 docs/quick_tour/static_assets/hello_world.pt docs/quick_tour/static_assets/views.py docs/quick_tour/static_assets/static/app.css docs/quick_tour/templating/app.py docs/quick_tour/templating/hello_world.pt docs/quick_tour/templating/views.py docs/quick_tour/view_classes/app.py docs/quick_tour/view_classes/delete.jinja2 docs/quick_tour/view_classes/edit.jinja2 docs/quick_tour/view_classes/hello.jinja2 docs/quick_tour/view_classes/views.py docs/quick_tour/views/app.py docs/quick_tour/views/views.py docs/quick_tutorial/authentication.rst docs/quick_tutorial/authorization.rst docs/quick_tutorial/conf.py docs/quick_tutorial/databases.rst docs/quick_tutorial/debugtoolbar.rst docs/quick_tutorial/forms.rst docs/quick_tutorial/functional_testing.rst docs/quick_tutorial/hello_world.rst docs/quick_tutorial/index.rst docs/quick_tutorial/ini.rst docs/quick_tutorial/jinja2.rst docs/quick_tutorial/json.rst docs/quick_tutorial/logging.rst docs/quick_tutorial/more_view_classes.rst docs/quick_tutorial/package.rst docs/quick_tutorial/request_response.rst docs/quick_tutorial/requirements.rst docs/quick_tutorial/routing.rst docs/quick_tutorial/scaffolds.rst docs/quick_tutorial/sessions.rst docs/quick_tutorial/static_assets.rst docs/quick_tutorial/templating.rst docs/quick_tutorial/tutorial_approach.rst docs/quick_tutorial/unit_testing.rst docs/quick_tutorial/view_classes.rst docs/quick_tutorial/views.rst docs/quick_tutorial/authentication/development.ini docs/quick_tutorial/authentication/setup.py docs/quick_tutorial/authentication/tutorial/__init__.py docs/quick_tutorial/authentication/tutorial/home.pt docs/quick_tutorial/authentication/tutorial/login.pt docs/quick_tutorial/authentication/tutorial/security.py docs/quick_tutorial/authentication/tutorial/views.py docs/quick_tutorial/authorization/development.ini docs/quick_tutorial/authorization/setup.py docs/quick_tutorial/authorization/tutorial/__init__.py docs/quick_tutorial/authorization/tutorial/home.pt docs/quick_tutorial/authorization/tutorial/login.pt docs/quick_tutorial/authorization/tutorial/resources.py docs/quick_tutorial/authorization/tutorial/security.py docs/quick_tutorial/authorization/tutorial/views.py docs/quick_tutorial/databases/development.ini docs/quick_tutorial/databases/setup.py docs/quick_tutorial/databases/sqltutorial.sqlite docs/quick_tutorial/databases/tutorial/__init__.py docs/quick_tutorial/databases/tutorial/initialize_db.py docs/quick_tutorial/databases/tutorial/models.py docs/quick_tutorial/databases/tutorial/tests.py docs/quick_tutorial/databases/tutorial/views.py docs/quick_tutorial/databases/tutorial/wiki_view.pt docs/quick_tutorial/databases/tutorial/wikipage_addedit.pt docs/quick_tutorial/databases/tutorial/wikipage_view.pt docs/quick_tutorial/debugtoolbar/development.ini docs/quick_tutorial/debugtoolbar/setup.py docs/quick_tutorial/debugtoolbar/tutorial/__init__.py docs/quick_tutorial/forms/development.ini docs/quick_tutorial/forms/setup.py docs/quick_tutorial/forms/tutorial/__init__.py docs/quick_tutorial/forms/tutorial/tests.py docs/quick_tutorial/forms/tutorial/views.py docs/quick_tutorial/forms/tutorial/wiki_view.pt docs/quick_tutorial/forms/tutorial/wikipage_addedit.pt docs/quick_tutorial/forms/tutorial/wikipage_view.pt docs/quick_tutorial/functional_testing/development.ini docs/quick_tutorial/functional_testing/setup.py docs/quick_tutorial/functional_testing/tutorial/__init__.py docs/quick_tutorial/functional_testing/tutorial/tests.py docs/quick_tutorial/hello_world/app.py docs/quick_tutorial/ini/development.ini docs/quick_tutorial/ini/setup.py docs/quick_tutorial/ini/tutorial/__init__.py docs/quick_tutorial/jinja2/development.ini docs/quick_tutorial/jinja2/setup.py docs/quick_tutorial/jinja2/tutorial/__init__.py docs/quick_tutorial/jinja2/tutorial/home.jinja2 docs/quick_tutorial/jinja2/tutorial/tests.py docs/quick_tutorial/jinja2/tutorial/views.py docs/quick_tutorial/json/development.ini docs/quick_tutorial/json/setup.py docs/quick_tutorial/json/tutorial/__init__.py docs/quick_tutorial/json/tutorial/home.pt docs/quick_tutorial/json/tutorial/tests.py docs/quick_tutorial/json/tutorial/views.py docs/quick_tutorial/logging/development.ini docs/quick_tutorial/logging/setup.py docs/quick_tutorial/logging/tutorial/__init__.py docs/quick_tutorial/logging/tutorial/home.pt docs/quick_tutorial/logging/tutorial/tests.py docs/quick_tutorial/logging/tutorial/views.py docs/quick_tutorial/more_view_classes/development.ini docs/quick_tutorial/more_view_classes/setup.py docs/quick_tutorial/more_view_classes/tutorial/__init__.py docs/quick_tutorial/more_view_classes/tutorial/delete.pt docs/quick_tutorial/more_view_classes/tutorial/edit.pt docs/quick_tutorial/more_view_classes/tutorial/hello.pt docs/quick_tutorial/more_view_classes/tutorial/home.pt docs/quick_tutorial/more_view_classes/tutorial/tests.py docs/quick_tutorial/more_view_classes/tutorial/views.py docs/quick_tutorial/package/setup.py docs/quick_tutorial/package/tutorial/__init__.py docs/quick_tutorial/package/tutorial/app.py docs/quick_tutorial/request_response/development.ini docs/quick_tutorial/request_response/setup.py docs/quick_tutorial/request_response/tutorial/__init__.py docs/quick_tutorial/request_response/tutorial/tests.py docs/quick_tutorial/request_response/tutorial/views.py docs/quick_tutorial/retail_forms/development.ini docs/quick_tutorial/retail_forms/setup.py docs/quick_tutorial/retail_forms/tutorial/__init__.py docs/quick_tutorial/retail_forms/tutorial/tests.py docs/quick_tutorial/retail_forms/tutorial/views.py docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt docs/quick_tutorial/routing/development.ini docs/quick_tutorial/routing/setup.py docs/quick_tutorial/routing/tutorial/__init__.py docs/quick_tutorial/routing/tutorial/home.pt docs/quick_tutorial/routing/tutorial/tests.py docs/quick_tutorial/routing/tutorial/views.py docs/quick_tutorial/scaffolds/CHANGES.txt docs/quick_tutorial/scaffolds/MANIFEST.in docs/quick_tutorial/scaffolds/README.txt docs/quick_tutorial/scaffolds/development.ini docs/quick_tutorial/scaffolds/production.ini docs/quick_tutorial/scaffolds/setup.py docs/quick_tutorial/scaffolds/scaffolds/__init__.py docs/quick_tutorial/scaffolds/scaffolds/tests.py docs/quick_tutorial/scaffolds/scaffolds/views.py docs/quick_tutorial/scaffolds/scaffolds/static/favicon.ico docs/quick_tutorial/scaffolds/scaffolds/static/footerbg.png docs/quick_tutorial/scaffolds/scaffolds/static/headerbg.png docs/quick_tutorial/scaffolds/scaffolds/static/ie6.css docs/quick_tutorial/scaffolds/scaffolds/static/middlebg.png docs/quick_tutorial/scaffolds/scaffolds/static/pylons.css docs/quick_tutorial/scaffolds/scaffolds/static/pyramid-small.png docs/quick_tutorial/scaffolds/scaffolds/static/pyramid.png docs/quick_tutorial/scaffolds/scaffolds/static/transparent.gif docs/quick_tutorial/scaffolds/scaffolds/templates/mytemplate.pt docs/quick_tutorial/sessions/development.ini docs/quick_tutorial/sessions/setup.py docs/quick_tutorial/sessions/tutorial/__init__.py docs/quick_tutorial/sessions/tutorial/home.pt docs/quick_tutorial/sessions/tutorial/tests.py docs/quick_tutorial/sessions/tutorial/views.py docs/quick_tutorial/static_assets/development.ini docs/quick_tutorial/static_assets/setup.py docs/quick_tutorial/static_assets/tutorial/__init__.py docs/quick_tutorial/static_assets/tutorial/home.pt docs/quick_tutorial/static_assets/tutorial/tests.py docs/quick_tutorial/static_assets/tutorial/views.py docs/quick_tutorial/static_assets/tutorial/static/app.css docs/quick_tutorial/templating/development.ini docs/quick_tutorial/templating/setup.py docs/quick_tutorial/templating/tutorial/__init__.py docs/quick_tutorial/templating/tutorial/home.pt docs/quick_tutorial/templating/tutorial/tests.py docs/quick_tutorial/templating/tutorial/views.py docs/quick_tutorial/unit_testing/development.ini docs/quick_tutorial/unit_testing/setup.py docs/quick_tutorial/unit_testing/tutorial/__init__.py docs/quick_tutorial/unit_testing/tutorial/tests.py docs/quick_tutorial/view_classes/development.ini docs/quick_tutorial/view_classes/setup.py docs/quick_tutorial/view_classes/tutorial/__init__.py docs/quick_tutorial/view_classes/tutorial/home.pt docs/quick_tutorial/view_classes/tutorial/tests.py docs/quick_tutorial/view_classes/tutorial/views.py docs/quick_tutorial/views/development.ini docs/quick_tutorial/views/setup.py docs/quick_tutorial/views/tutorial/__init__.py docs/quick_tutorial/views/tutorial/tests.py docs/quick_tutorial/views/tutorial/views.py docs/tutorials/.gitignore docs/tutorials/modwsgi/index.rst docs/tutorials/wiki/NOTE-relocatable.txt docs/tutorials/wiki/authorization.rst docs/tutorials/wiki/background.rst docs/tutorials/wiki/basiclayout.rst docs/tutorials/wiki/definingmodels.rst docs/tutorials/wiki/definingviews.rst docs/tutorials/wiki/design.rst docs/tutorials/wiki/distributing.rst docs/tutorials/wiki/index.rst docs/tutorials/wiki/installation.rst docs/tutorials/wiki/tests.rst docs/tutorials/wiki/src/authorization/CHANGES.txt docs/tutorials/wiki/src/authorization/MANIFEST.in docs/tutorials/wiki/src/authorization/README.txt docs/tutorials/wiki/src/authorization/development.ini docs/tutorials/wiki/src/authorization/production.ini docs/tutorials/wiki/src/authorization/setup.py docs/tutorials/wiki/src/authorization/tutorial/__init__.py docs/tutorials/wiki/src/authorization/tutorial/models.py docs/tutorials/wiki/src/authorization/tutorial/security.py docs/tutorials/wiki/src/authorization/tutorial/tests.py docs/tutorials/wiki/src/authorization/tutorial/views.py docs/tutorials/wiki/src/authorization/tutorial/static/pyramid-16x16.png docs/tutorials/wiki/src/authorization/tutorial/static/pyramid.png docs/tutorials/wiki/src/authorization/tutorial/static/theme.css docs/tutorials/wiki/src/authorization/tutorial/static/theme.min.css docs/tutorials/wiki/src/authorization/tutorial/templates/edit.pt docs/tutorials/wiki/src/authorization/tutorial/templates/login.pt docs/tutorials/wiki/src/authorization/tutorial/templates/mytemplate.pt docs/tutorials/wiki/src/authorization/tutorial/templates/view.pt docs/tutorials/wiki/src/basiclayout/CHANGES.txt docs/tutorials/wiki/src/basiclayout/MANIFEST.in docs/tutorials/wiki/src/basiclayout/README.txt docs/tutorials/wiki/src/basiclayout/development.ini docs/tutorials/wiki/src/basiclayout/production.ini docs/tutorials/wiki/src/basiclayout/setup.py docs/tutorials/wiki/src/basiclayout/tutorial/__init__.py docs/tutorials/wiki/src/basiclayout/tutorial/models.py docs/tutorials/wiki/src/basiclayout/tutorial/tests.py docs/tutorials/wiki/src/basiclayout/tutorial/views.py docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid-16x16.png docs/tutorials/wiki/src/basiclayout/tutorial/static/pyramid.png docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.css docs/tutorials/wiki/src/basiclayout/tutorial/static/theme.min.css docs/tutorials/wiki/src/basiclayout/tutorial/templates/mytemplate.pt docs/tutorials/wiki/src/models/CHANGES.txt docs/tutorials/wiki/src/models/MANIFEST.in docs/tutorials/wiki/src/models/README.txt docs/tutorials/wiki/src/models/development.ini docs/tutorials/wiki/src/models/production.ini docs/tutorials/wiki/src/models/setup.py docs/tutorials/wiki/src/models/tutorial/__init__.py docs/tutorials/wiki/src/models/tutorial/models.py docs/tutorials/wiki/src/models/tutorial/tests.py docs/tutorials/wiki/src/models/tutorial/views.py docs/tutorials/wiki/src/models/tutorial/static/pyramid-16x16.png docs/tutorials/wiki/src/models/tutorial/static/pyramid.png docs/tutorials/wiki/src/models/tutorial/static/theme.css docs/tutorials/wiki/src/models/tutorial/static/theme.min.css docs/tutorials/wiki/src/models/tutorial/templates/mytemplate.pt docs/tutorials/wiki/src/tests/CHANGES.txt docs/tutorials/wiki/src/tests/MANIFEST.in docs/tutorials/wiki/src/tests/README.txt docs/tutorials/wiki/src/tests/development.ini docs/tutorials/wiki/src/tests/production.ini docs/tutorials/wiki/src/tests/setup.py docs/tutorials/wiki/src/tests/tutorial/__init__.py docs/tutorials/wiki/src/tests/tutorial/models.py docs/tutorials/wiki/src/tests/tutorial/security.py docs/tutorials/wiki/src/tests/tutorial/tests.py docs/tutorials/wiki/src/tests/tutorial/views.py docs/tutorials/wiki/src/tests/tutorial/static/pyramid-16x16.png docs/tutorials/wiki/src/tests/tutorial/static/pyramid.png docs/tutorials/wiki/src/tests/tutorial/static/theme.css docs/tutorials/wiki/src/tests/tutorial/static/theme.min.css docs/tutorials/wiki/src/tests/tutorial/templates/edit.pt docs/tutorials/wiki/src/tests/tutorial/templates/login.pt docs/tutorials/wiki/src/tests/tutorial/templates/mytemplate.pt docs/tutorials/wiki/src/tests/tutorial/templates/view.pt docs/tutorials/wiki/src/views/CHANGES.txt docs/tutorials/wiki/src/views/MANIFEST.in docs/tutorials/wiki/src/views/README.txt docs/tutorials/wiki/src/views/development.ini docs/tutorials/wiki/src/views/production.ini docs/tutorials/wiki/src/views/setup.py docs/tutorials/wiki/src/views/tutorial/__init__.py docs/tutorials/wiki/src/views/tutorial/models.py docs/tutorials/wiki/src/views/tutorial/tests.py docs/tutorials/wiki/src/views/tutorial/views.py docs/tutorials/wiki/src/views/tutorial/static/pyramid-16x16.png docs/tutorials/wiki/src/views/tutorial/static/pyramid.png docs/tutorials/wiki/src/views/tutorial/static/theme.css docs/tutorials/wiki/src/views/tutorial/static/theme.min.css docs/tutorials/wiki/src/views/tutorial/templates/edit.pt docs/tutorials/wiki/src/views/tutorial/templates/mytemplate.pt docs/tutorials/wiki/src/views/tutorial/templates/view.pt docs/tutorials/wiki2/authorization.rst docs/tutorials/wiki2/background.rst docs/tutorials/wiki2/basiclayout.rst docs/tutorials/wiki2/definingmodels.rst docs/tutorials/wiki2/definingviews.rst docs/tutorials/wiki2/design.rst docs/tutorials/wiki2/distributing.rst docs/tutorials/wiki2/index.rst docs/tutorials/wiki2/installation.rst docs/tutorials/wiki2/tests.rst docs/tutorials/wiki2/src/authorization/CHANGES.txt docs/tutorials/wiki2/src/authorization/MANIFEST.in docs/tutorials/wiki2/src/authorization/README.txt docs/tutorials/wiki2/src/authorization/development.ini docs/tutorials/wiki2/src/authorization/production.ini docs/tutorials/wiki2/src/authorization/setup.py docs/tutorials/wiki2/src/authorization/tutorial/__init__.py docs/tutorials/wiki2/src/authorization/tutorial/models.py docs/tutorials/wiki2/src/authorization/tutorial/security.py docs/tutorials/wiki2/src/authorization/tutorial/tests.py docs/tutorials/wiki2/src/authorization/tutorial/views.py docs/tutorials/wiki2/src/authorization/tutorial/scripts/__init__.py docs/tutorials/wiki2/src/authorization/tutorial/scripts/initializedb.py docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid-16x16.png docs/tutorials/wiki2/src/authorization/tutorial/static/pyramid.png docs/tutorials/wiki2/src/authorization/tutorial/static/theme.css docs/tutorials/wiki2/src/authorization/tutorial/static/theme.min.css docs/tutorials/wiki2/src/authorization/tutorial/templates/edit.pt docs/tutorials/wiki2/src/authorization/tutorial/templates/login.pt docs/tutorials/wiki2/src/authorization/tutorial/templates/mytemplate.pt docs/tutorials/wiki2/src/authorization/tutorial/templates/view.pt docs/tutorials/wiki2/src/basiclayout/CHANGES.txt docs/tutorials/wiki2/src/basiclayout/MANIFEST.in docs/tutorials/wiki2/src/basiclayout/README.txt docs/tutorials/wiki2/src/basiclayout/development.ini docs/tutorials/wiki2/src/basiclayout/production.ini docs/tutorials/wiki2/src/basiclayout/setup.py docs/tutorials/wiki2/src/basiclayout/tutorial/__init__.py docs/tutorials/wiki2/src/basiclayout/tutorial/models.py docs/tutorials/wiki2/src/basiclayout/tutorial/tests.py docs/tutorials/wiki2/src/basiclayout/tutorial/views.py docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/__init__.py docs/tutorials/wiki2/src/basiclayout/tutorial/scripts/initializedb.py docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid-16x16.png docs/tutorials/wiki2/src/basiclayout/tutorial/static/pyramid.png docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.css docs/tutorials/wiki2/src/basiclayout/tutorial/static/theme.min.css docs/tutorials/wiki2/src/basiclayout/tutorial/templates/mytemplate.pt docs/tutorials/wiki2/src/models/CHANGES.txt docs/tutorials/wiki2/src/models/MANIFEST.in docs/tutorials/wiki2/src/models/README.txt docs/tutorials/wiki2/src/models/development.ini docs/tutorials/wiki2/src/models/production.ini docs/tutorials/wiki2/src/models/setup.py docs/tutorials/wiki2/src/models/tutorial/__init__.py docs/tutorials/wiki2/src/models/tutorial/models.py docs/tutorials/wiki2/src/models/tutorial/tests.py docs/tutorials/wiki2/src/models/tutorial/views.py docs/tutorials/wiki2/src/models/tutorial/scripts/__init__.py docs/tutorials/wiki2/src/models/tutorial/scripts/initializedb.py docs/tutorials/wiki2/src/models/tutorial/static/pyramid-16x16.png docs/tutorials/wiki2/src/models/tutorial/static/pyramid.png docs/tutorials/wiki2/src/models/tutorial/static/theme.css docs/tutorials/wiki2/src/models/tutorial/static/theme.min.css docs/tutorials/wiki2/src/models/tutorial/templates/mytemplate.pt docs/tutorials/wiki2/src/tests/CHANGES.txt docs/tutorials/wiki2/src/tests/MANIFEST.in docs/tutorials/wiki2/src/tests/README.txt docs/tutorials/wiki2/src/tests/development.ini docs/tutorials/wiki2/src/tests/production.ini docs/tutorials/wiki2/src/tests/setup.py docs/tutorials/wiki2/src/tests/tutorial/__init__.py docs/tutorials/wiki2/src/tests/tutorial/models.py docs/tutorials/wiki2/src/tests/tutorial/security.py docs/tutorials/wiki2/src/tests/tutorial/tests.py docs/tutorials/wiki2/src/tests/tutorial/views.py docs/tutorials/wiki2/src/tests/tutorial/scripts/__init__.py docs/tutorials/wiki2/src/tests/tutorial/scripts/initializedb.py docs/tutorials/wiki2/src/tests/tutorial/static/pyramid-16x16.png docs/tutorials/wiki2/src/tests/tutorial/static/pyramid.png docs/tutorials/wiki2/src/tests/tutorial/static/theme.css docs/tutorials/wiki2/src/tests/tutorial/static/theme.min.css docs/tutorials/wiki2/src/tests/tutorial/templates/edit.pt docs/tutorials/wiki2/src/tests/tutorial/templates/login.pt docs/tutorials/wiki2/src/tests/tutorial/templates/mytemplate.pt docs/tutorials/wiki2/src/tests/tutorial/templates/view.pt docs/tutorials/wiki2/src/views/CHANGES.txt docs/tutorials/wiki2/src/views/MANIFEST.in docs/tutorials/wiki2/src/views/README.txt docs/tutorials/wiki2/src/views/development.ini docs/tutorials/wiki2/src/views/production.ini docs/tutorials/wiki2/src/views/setup.py docs/tutorials/wiki2/src/views/tutorial/__init__.py docs/tutorials/wiki2/src/views/tutorial/models.py docs/tutorials/wiki2/src/views/tutorial/tests.py docs/tutorials/wiki2/src/views/tutorial/views.py docs/tutorials/wiki2/src/views/tutorial/scripts/__init__.py docs/tutorials/wiki2/src/views/tutorial/scripts/initializedb.py docs/tutorials/wiki2/src/views/tutorial/static/pyramid-16x16.png docs/tutorials/wiki2/src/views/tutorial/static/pyramid.png docs/tutorials/wiki2/src/views/tutorial/static/theme.css docs/tutorials/wiki2/src/views/tutorial/static/theme.min.css docs/tutorials/wiki2/src/views/tutorial/templates/edit.pt docs/tutorials/wiki2/src/views/tutorial/templates/mytemplate.pt docs/tutorials/wiki2/src/views/tutorial/templates/view.pt pyramid/__init__.py pyramid/asset.py pyramid/authentication.py pyramid/authorization.py pyramid/compat.py pyramid/decorator.py pyramid/encode.py pyramid/events.py pyramid/exceptions.py pyramid/httpexceptions.py pyramid/i18n.py pyramid/interfaces.py pyramid/location.py pyramid/paster.py pyramid/path.py pyramid/registry.py pyramid/renderers.py pyramid/request.py pyramid/resource.py pyramid/response.py pyramid/router.py pyramid/scripting.py pyramid/security.py pyramid/session.py pyramid/settings.py pyramid/static.py pyramid/testing.py pyramid/threadlocal.py pyramid/traversal.py pyramid/tweens.py pyramid/url.py pyramid/urldispatch.py pyramid/util.py pyramid/view.py pyramid/wsgi.py pyramid.egg-info/PKG-INFO pyramid.egg-info/SOURCES.txt pyramid.egg-info/dependency_links.txt pyramid.egg-info/entry_points.txt pyramid.egg-info/not-zip-safe pyramid.egg-info/requires.txt pyramid.egg-info/top_level.txt pyramid/config/__init__.py pyramid/config/adapters.py pyramid/config/assets.py pyramid/config/factories.py pyramid/config/i18n.py pyramid/config/predicates.py pyramid/config/rendering.py pyramid/config/routes.py pyramid/config/security.py pyramid/config/settings.py pyramid/config/testing.py pyramid/config/tweens.py pyramid/config/util.py pyramid/config/views.py pyramid/config/zca.py pyramid/scaffolds/__init__.py pyramid/scaffolds/copydir.py pyramid/scaffolds/template.py pyramid/scaffolds/tests.py pyramid/scaffolds/alchemy/CHANGES.txt_tmpl pyramid/scaffolds/alchemy/MANIFEST.in_tmpl pyramid/scaffolds/alchemy/README.txt_tmpl pyramid/scaffolds/alchemy/development.ini_tmpl pyramid/scaffolds/alchemy/production.ini_tmpl pyramid/scaffolds/alchemy/setup.py_tmpl pyramid/scaffolds/alchemy/+package+/__init__.py pyramid/scaffolds/alchemy/+package+/models.py pyramid/scaffolds/alchemy/+package+/tests.py_tmpl pyramid/scaffolds/alchemy/+package+/views.py_tmpl pyramid/scaffolds/alchemy/+package+/scripts/__init__.py pyramid/scaffolds/alchemy/+package+/scripts/initializedb.py pyramid/scaffolds/alchemy/+package+/static/pyramid-16x16.png pyramid/scaffolds/alchemy/+package+/static/pyramid.png pyramid/scaffolds/alchemy/+package+/static/theme.css pyramid/scaffolds/alchemy/+package+/static/theme.min.css pyramid/scaffolds/alchemy/+package+/templates/mytemplate.pt_tmpl pyramid/scaffolds/starter/CHANGES.txt_tmpl pyramid/scaffolds/starter/MANIFEST.in_tmpl pyramid/scaffolds/starter/README.txt_tmpl pyramid/scaffolds/starter/development.ini_tmpl pyramid/scaffolds/starter/production.ini_tmpl pyramid/scaffolds/starter/setup.py_tmpl pyramid/scaffolds/starter/+package+/__init__.py pyramid/scaffolds/starter/+package+/tests.py_tmpl pyramid/scaffolds/starter/+package+/views.py_tmpl pyramid/scaffolds/starter/+package+/static/pyramid-16x16.png pyramid/scaffolds/starter/+package+/static/pyramid.png pyramid/scaffolds/starter/+package+/static/theme.css pyramid/scaffolds/starter/+package+/static/theme.min.css pyramid/scaffolds/starter/+package+/templates/mytemplate.pt_tmpl pyramid/scaffolds/zodb/CHANGES.txt_tmpl pyramid/scaffolds/zodb/MANIFEST.in_tmpl pyramid/scaffolds/zodb/README.txt_tmpl pyramid/scaffolds/zodb/development.ini_tmpl pyramid/scaffolds/zodb/production.ini_tmpl pyramid/scaffolds/zodb/setup.py_tmpl pyramid/scaffolds/zodb/+package+/__init__.py pyramid/scaffolds/zodb/+package+/models.py pyramid/scaffolds/zodb/+package+/tests.py_tmpl pyramid/scaffolds/zodb/+package+/views.py_tmpl pyramid/scaffolds/zodb/+package+/static/pyramid-16x16.png pyramid/scaffolds/zodb/+package+/static/pyramid.png pyramid/scaffolds/zodb/+package+/static/theme.css pyramid/scaffolds/zodb/+package+/static/theme.min.css pyramid/scaffolds/zodb/+package+/templates/mytemplate.pt_tmpl pyramid/scripts/__init__.py pyramid/scripts/common.py pyramid/scripts/pcreate.py pyramid/scripts/pdistreport.py pyramid/scripts/prequest.py pyramid/scripts/proutes.py pyramid/scripts/pserve.py pyramid/scripts/pshell.py pyramid/scripts/ptweens.py pyramid/scripts/pviews.py pyramid/tests/__init__.py pyramid/tests/test_asset.py pyramid/tests/test_authentication.py pyramid/tests/test_authorization.py pyramid/tests/test_compat.py pyramid/tests/test_decorator.py pyramid/tests/test_docs.py pyramid/tests/test_encode.py pyramid/tests/test_events.py pyramid/tests/test_exceptions.py pyramid/tests/test_httpexceptions.py pyramid/tests/test_i18n.py pyramid/tests/test_integration.py pyramid/tests/test_location.py pyramid/tests/test_paster.py pyramid/tests/test_path.py pyramid/tests/test_registry.py pyramid/tests/test_renderers.py pyramid/tests/test_request.py pyramid/tests/test_response.py pyramid/tests/test_router.py pyramid/tests/test_scripting.py pyramid/tests/test_security.py pyramid/tests/test_session.py pyramid/tests/test_settings.py pyramid/tests/test_static.py pyramid/tests/test_testing.py pyramid/tests/test_threadlocal.py pyramid/tests/test_traversal.py pyramid/tests/test_url.py pyramid/tests/test_urldispatch.py pyramid/tests/test_util.py pyramid/tests/test_view.py pyramid/tests/test_wsgi.py pyramid/tests/fixtures/dummy.ini pyramid/tests/fixtures/manifest.json pyramid/tests/fixtures/manifest2.json pyramid/tests/fixtures/minimal.jpg pyramid/tests/fixtures/minimal.pdf pyramid/tests/fixtures/minimal.txt pyramid/tests/fixtures/minimal.xml pyramid/tests/fixtures/nonminimal.txt pyramid/tests/fixtures/static/.hiddenfile pyramid/tests/fixtures/static/arcs.svg.tgz pyramid/tests/fixtures/static/index.html pyramid/tests/fixtures/static/subdir/index.html pyramid/tests/pkgs/__init__.py pyramid/tests/pkgs/ccbugapp/__init__.py pyramid/tests/pkgs/conflictapp/__init__.py pyramid/tests/pkgs/conflictapp/included.py pyramid/tests/pkgs/defpermbugapp/__init__.py pyramid/tests/pkgs/eventonly/__init__.py pyramid/tests/pkgs/exceptionviewapp/__init__.py pyramid/tests/pkgs/exceptionviewapp/models.py pyramid/tests/pkgs/exceptionviewapp/views.py pyramid/tests/pkgs/fixtureapp/__init__.py pyramid/tests/pkgs/fixtureapp/models.py pyramid/tests/pkgs/fixtureapp/views.py pyramid/tests/pkgs/fixtureapp/subpackage/__init__.py pyramid/tests/pkgs/forbiddenapp/__init__.py pyramid/tests/pkgs/forbiddenview/__init__.py pyramid/tests/pkgs/hybridapp/__init__.py pyramid/tests/pkgs/hybridapp/views.py pyramid/tests/pkgs/includeapp1/__init__.py pyramid/tests/pkgs/includeapp1/root.py pyramid/tests/pkgs/includeapp1/three.py pyramid/tests/pkgs/includeapp1/two.py pyramid/tests/pkgs/localeapp/__init__.py pyramid/tests/pkgs/localeapp/locale/GARBAGE pyramid/tests/pkgs/localeapp/locale/be/LC_MESSAGES pyramid/tests/pkgs/localeapp/locale/de/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale/de/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale/de_DE/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale/de_DE/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale/en/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale/en/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale2/GARBAGE pyramid/tests/pkgs/localeapp/locale2/be/LC_MESSAGES pyramid/tests/pkgs/localeapp/locale2/de/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale2/de/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale2/en/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale2/en/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale3/GARBAGE pyramid/tests/pkgs/localeapp/locale3/be/LC_MESSAGES pyramid/tests/pkgs/localeapp/locale3/de/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale3/de/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/localeapp/locale3/en/LC_MESSAGES/deformsite.mo pyramid/tests/pkgs/localeapp/locale3/en/LC_MESSAGES/deformsite.po pyramid/tests/pkgs/notfoundview/__init__.py pyramid/tests/pkgs/permbugapp/__init__.py pyramid/tests/pkgs/rendererscanapp/__init__.py pyramid/tests/pkgs/rendererscanapp/two/__init__.py pyramid/tests/pkgs/restbugapp/__init__.py pyramid/tests/pkgs/restbugapp/views.py pyramid/tests/pkgs/static_abspath/__init__.py pyramid/tests/pkgs/static_assetspec/__init__.py pyramid/tests/pkgs/static_routeprefix/__init__.py pyramid/tests/pkgs/staticpermapp/__init__.py pyramid/tests/pkgs/subrequestapp/__init__.py pyramid/tests/pkgs/viewdecoratorapp/__init__.py pyramid/tests/pkgs/viewdecoratorapp/views/__init__.py pyramid/tests/pkgs/viewdecoratorapp/views/views.py pyramid/tests/pkgs/wsgiapp2app/__init__.py pyramid/tests/test_config/__init__.py pyramid/tests/test_config/test_adapters.py pyramid/tests/test_config/test_assets.py pyramid/tests/test_config/test_factories.py pyramid/tests/test_config/test_i18n.py pyramid/tests/test_config/test_init.py pyramid/tests/test_config/test_predicates.py pyramid/tests/test_config/test_rendering.py pyramid/tests/test_config/test_routes.py pyramid/tests/test_config/test_security.py pyramid/tests/test_config/test_settings.py pyramid/tests/test_config/test_testing.py pyramid/tests/test_config/test_tweens.py pyramid/tests/test_config/test_util.py pyramid/tests/test_config/test_views.py pyramid/tests/test_config/files/minimal.txt pyramid/tests/test_config/files/assets/dummy.txt pyramid/tests/test_config/path/scanerror/__init__.py pyramid/tests/test_config/path/scanerror/will_raise_error.py pyramid/tests/test_config/pkgs/__init__.py pyramid/tests/test_config/pkgs/asset/__init__.py pyramid/tests/test_config/pkgs/asset/subpackage/__init__.py pyramid/tests/test_config/pkgs/asset/subpackage/templates/bar.pt pyramid/tests/test_config/pkgs/scanextrakw/__init__.py pyramid/tests/test_config/pkgs/scannable/__init__.py pyramid/tests/test_config/pkgs/scannable/another.py pyramid/tests/test_config/pkgs/scannable/pod/notinit.py pyramid/tests/test_config/pkgs/scannable/subpackage/__init__.py pyramid/tests/test_config/pkgs/scannable/subpackage/notinit.py pyramid/tests/test_config/pkgs/scannable/subpackage/subsubpackage/__init__.py pyramid/tests/test_config/pkgs/selfscan/__init__.py pyramid/tests/test_config/pkgs/selfscan/another.py pyramid/tests/test_scaffolds/__init__.py pyramid/tests/test_scaffolds/test_copydir.py pyramid/tests/test_scaffolds/test_init.py pyramid/tests/test_scaffolds/test_template.py pyramid/tests/test_scaffolds/fixture_scaffold/CHANGES.txt_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/MANIFEST.in_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/README.txt_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/development.ini_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/production.ini_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/setup.py_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/+package+/.badfile pyramid/tests/test_scaffolds/fixture_scaffold/+package+/__init__.py_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/+package+/resources.py pyramid/tests/test_scaffolds/fixture_scaffold/+package+/test_no_content.py_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/+package+/tests.py_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/+package+/views.py_tmpl pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/favicon.ico pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/footerbg.png pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/headerbg.png pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/ie6.css pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/middlebg.png pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/pylons.css pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/pyramid-small.png pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/pyramid.png pyramid/tests/test_scaffolds/fixture_scaffold/+package+/static/transparent.gif pyramid/tests/test_scaffolds/fixture_scaffold/+package+/templates/mytemplate.pt_tmpl pyramid/tests/test_scripts/__init__.py pyramid/tests/test_scripts/dummy.py pyramid/tests/test_scripts/pystartup.txt pyramid/tests/test_scripts/test_common.py pyramid/tests/test_scripts/test_pcreate.py pyramid/tests/test_scripts/test_pdistreport.py pyramid/tests/test_scripts/test_prequest.py pyramid/tests/test_scripts/test_proutes.py pyramid/tests/test_scripts/test_pserve.py pyramid/tests/test_scripts/test_pshell.py pyramid/tests/test_scripts/test_ptweens.py pyramid/tests/test_scripts/test_pviews.pypyramid-1.6/pyramid.egg-info/top_level.txt0000644000076500000240000000001012642137473021443 0ustar michaelstaff00000000000000pyramid pyramid-1.6/README.rst0000644000076500000240000000307612642137120015245 0ustar michaelstaff00000000000000Pyramid ======= .. image:: https://travis-ci.org/Pylons/pyramid.png?branch=1.6-branch :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: Master Documentation Status .. image:: https://readthedocs.org/projects/pyramid/badge/?version=latest :target: http://docs.pylonsproject.org/projects/pyramid/en/latest/ :alt: Latest Documentation Status .. image:: https://img.shields.io/badge/irc-freenode-blue.svg :target: https://webchat.freenode.net/?channels=pyramid :alt: IRC Freenode 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. Pyramid is produced by the `Pylons Project `_. Support and Documentation ------------------------- See the `Pylons Project website `_ to view documentation, report bugs, and obtain support. Developing and Contributing --------------------------- See ``HACKING.txt`` and ``contributing.md`` for guidelines for running tests, adding features, coding style, and updating documentation when developing in or contributing to Pyramid. License ------- Pyramid is offered under the BSD-derived `Repoze Public License `_. Authors ------- Pyramid is made available by `Agendaless Consulting `_ and a team of contributors. pyramid-1.6/RELEASING.txt0000644000076500000240000000422212642137120015622 0ustar michaelstaff00000000000000Releasing Pyramid ================= - Do any necessary branch merges (e.g. master to branch, branch to master). - On release branch: $ git pull - Do platform test via tox: $ tox -r Make sure statement coverage is at 100% (the test run will fail if not). - Run tests on Windows if feasible. - Make sure all scaffold tests pass (Py 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, pypy, and pypy3 on UNIX; this doesn't work on Windows): $ ./scaffoldtests.sh - Ensure all features of the release are documented (audit CHANGES.txt or communicate with contributors). - 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". - Update whatsnew-X.X.rst in docs to point at change log entries for individual releases if applicable. - Change setup.py version to the new version number. - Change CHANGES.txt heading to reflect the new version number. - Make sure PyPI long description renders (requires ``collective.dist`` installed into your Python):: $ python setup.py check -r - Create a release tag. - Make sure your Python has ``setuptools-git``, ``twine`` and ``wheel`` installed and release to PyPI:: $ python setup.py sdist bdist_wheel $ twine upload dist/pyramid-X.X-* - Edit `http://wiki.python.org/moin/WebFrameworks `_. - Publish new version of docs. - Announce to maillist. - Announce to Twitter. Announcement template ---------------------- Pyramid 1.X.X has been released. Here are the changes: <> A "What's New In Pyramid 1.X" document exists at http://docs.pylonsproject.org/projects/pyramid/1.X/whatsnew-1.X.html . You will be able to see the 1.X release documentation (across all alphas and betas, as well as when it eventually gets to final release) at http://docs.pylonsproject.org/projects/pyramid/1.X/ . You can install it via PyPI: easy_install Pyramid==1.X Enjoy, and please report any issues you find to the issue tracker at https://github.com/Pylons/pyramid/issues Thanks! - C pyramid-1.6/rtd.txt0000644000076500000240000000001312575217547015115 0ustar michaelstaff00000000000000-e .[docs] pyramid-1.6/scaffoldtests.sh0000755000076500000240000000010412524266531016756 0ustar michaelstaff00000000000000#!/bin/bash tox -e{py26,py27,py32,py33,py34,pypy,pypy3}-scaffolds, pyramid-1.6/setup.cfg0000644000076500000240000000042112642137501015371 0ustar michaelstaff00000000000000[easy_install] zip_ok = false [nosetests] match = ^test where = pyramid nocapture = 1 [aliases] dev = develop easy_install pyramid[testing] docs = develop easy_install pyramid[docs] [bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pyramid-1.6/setup.py0000644000076500000240000001067012642137254015276 0ustar michaelstaff00000000000000############################################################################## # # Copyright (c) 2008-2013 Agendaless Consulting and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the BSD-like license at # http://www.repoze.org/LICENSE.txt. A copy of the license should accompany # this distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL # EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, # THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND # FITNESS FOR A PARTICULAR PURPOSE # ############################################################################## import os import sys from setuptools import setup, find_packages py_version = sys.version_info[:2] PY3 = py_version[0] == 3 if PY3: if py_version < (3, 2): raise RuntimeError('On Python 3, Pyramid requires Python 3.2 or better') else: if py_version < (2, 6): raise RuntimeError('On Python 2, Pyramid requires Python 2.6 or better') here = os.path.abspath(os.path.dirname(__file__)) try: 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() except IOError: README = CHANGES = '' install_requires=[ 'setuptools', 'WebOb >= 1.3.1', # request.domain and CookieProfile 'repoze.lru >= 0.4', # py3 compat 'zope.interface >= 3.8.0', # has zope.interface.registry 'zope.deprecation >= 3.5.0', # py3 compat 'venusian >= 1.0a3', # ``ignore`` 'translationstring >= 0.4', # py3 compat 'PasteDeploy >= 1.5.0', # py3 compat ] tests_require = [ 'WebTest >= 1.3.1', # py3 compat ] if not PY3: tests_require.append('zope.component>=3.11.0') docs_extras = [ 'Sphinx >= 1.3.1', 'docutils', 'repoze.sphinx.autointerface', 'pylons_sphinx_latesturl', 'pylons-sphinx-themes', 'sphinxcontrib-programoutput', ] testing_extras = tests_require + [ 'nose', 'coverage', 'virtualenv', # for scaffolding tests ] setup(name='pyramid', version='1.6', 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", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI", "License :: Repoze Public License", ], keywords='web wsgi pylons pyramid', author="Chris McDonough, Agendaless Consulting", author_email="pylons-discuss@googlegroups.com", 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, zip_safe=False, install_requires = install_requires, extras_require = { 'testing':testing_extras, 'docs':docs_extras, }, tests_require = tests_require, test_suite="pyramid.tests", entry_points = """\ [pyramid.scaffold] starter=pyramid.scaffolds:StarterProjectTemplate zodb=pyramid.scaffolds:ZODBProjectTemplate alchemy=pyramid.scaffolds:AlchemyProjectTemplate [pyramid.pshell_runner] python=pyramid.scripts.pshell:python_shell_runner [console_scripts] pcreate = pyramid.scripts.pcreate:main pserve = pyramid.scripts.pserve:main pshell = pyramid.scripts.pshell:main proutes = pyramid.scripts.proutes:main pviews = pyramid.scripts.pviews:main ptweens = pyramid.scripts.ptweens:main prequest = pyramid.scripts.prequest:main pdistreport = pyramid.scripts.pdistreport:main [paste.server_runner] wsgiref = pyramid.scripts.pserve:wsgiref_server_runner cherrypy = pyramid.scripts.pserve:cherrypy_server_runner """ ) pyramid-1.6/TODO.txt0000644000076500000240000001244012642137120015057 0ustar michaelstaff00000000000000Pyramid TODOs ============= Nice-to-Have ------------ - config.set_registry_attr with conflict detection... make sure the attr is added before a commit, but register an action so a conflict can be detected. - Provide the presumed renderer name to the called view as an attribute of the request. - Have action methods return their discriminators. - Modify view mapper narrative docs to not use pyramid_handlers. - Modify the urldispatch chapter examples to assume a scan rather than ``add_view``. - Introspection: * ``default root factory`` category (prevent folks from needing to searh "root factories" category)? * ``default view mapper`` category (prevent folks from needing to search "view mappers" category)? * get rid of "tweens" category (can't sort properly?) - Fix deployment recipes in cookbook (discourage proxying without changing server). - Try "with transaction.manager" in an exception view with SQLA (preempt homina homina response about how to write "to the database" from within in an exception view). Note: tried this and couldn't formulate the right situation where the database could not be written to within an exception view (but didn't try exhaustively). - Add narrative docs for wsgiapp and wsgiapp2. - Basic WSGI documentation (pipeline / app / server). - Change docs about creating a venusian decorator to not use ZCA (use configurator methods instead). - Try to better explain the relationship between a renderer and a template in the templates chapter and elsewhere. Scan the documentation for reference to a renderer as *only* view configuration (it's a larger concept now). - Add better docs about what-to-do-when-behind-a-proxy: paste.urlmap ("/foo = app1" and "domain app1.localhost = app1"), ProxyPreserveHost and the nginx equivalent, preserving HTTPS URLs. - Alias the stupid long default session factory name. - Debug option to print view matching decision (e.g. debug_viewlookup or so). - Non-bwcompat use of threadlocals that need to be documented or ameliorated: security.principals_allowed_by_permission resource.OverrideProvider._get_overrides: can't credibly be removed, because it stores an overrideprovider as a module-scope global. traversal.traverse: this API is a stepchild, and needs to be changed. Configurator.add_translation_dirs: not passed any context but a message, can't credibly be removed. - Deprecate pyramid.security.view_execution_permitted (it only works for traversal). - Provide a ``has_view`` function. - Update App engine chapter with less creaky directions. - Idea from Zart: diff --git a/pyramid/paster.py b/pyramid/paster.py index b0e4d79..b3bd82a 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -8,6 +8,7 @@ from paste.deploy import ( from pyramid.compat import configparser from logging.config import fileConfig from pyramid.scripting import prepare +from pyramid.config import Configurator def get_app(config_uri, name=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy @@ -111,3 +112,10 @@ def bootstrap(config_uri, request=None): env['app'] = app return env +def make_pyramid_app(global_conf, app=None, **settings): + """Return pyramid application configured with provided settings""" + config = Configurator(package='pyramid', settings=settings) + if app: + config.include(app) + app = config.make_wsgi_app() + return app diff --git a/setup.py b/setup.py index 03ebb42..91e0e21 100644 --- a/setup.py +++ b/setup.py @@ -118,6 +118,8 @@ setup(name='pyramid', [paste.server_runner] wsgiref = pyramid.scripts.pserve:wsgiref_server_runner cherrypy = pyramid.scripts.pserve:cherrypy_server_runner + [paster.app_factory] + main = pyramid.paster:make_pyramid_app """ ) Future ------ - 1.6: turn ``pyramid.settings.Settings`` into a function that returns the original dict (after ``__getattr__`` deprecation period, it was deprecated in 1.2). - 1.6: Remove IContextURL and TraversalContextURL. - 1.7: Change ``pyramid.authentication.AuthTktAuthenticationPolicy`` default ``hashalg`` to ``sha512``. - 1.8: Remove set_request_property. - 1.9: Remove extra code enabling ``pyramid.security.remember(principal=...)`` and force use of ``userid``. Probably Bad Ideas ------------------ - Add functionality that mocks the behavior of ``repoze.browserid``. - Consider implementing the API outlined in http://plope.com/pyramid_auth_design_api_postmortem, phasing out the current auth-n-auth abstractions in a backwards compatible way. - Maybe add ``add_renderer_globals`` method to Configurator. - Supply ``X-Vhm-Host`` support (probably better to do what paste#prefix middleware does). - Have ``remember`` and ``forget`` actually set headers on the response using a response callback (and return the empty list)? - http://pythonguy.wordpress.com/2011/06/22/dynamic-variables-revisited/ instead of thread locals - Context manager for creating a new configurator (replacing ``with_package``). E.g.:: with config.partial(package='bar') as c: c.add_view(...) or:: with config.partial(introspection=False) as c: c.add_view(..) - _fix_registry should dictify the registry being fixed. pyramid-1.6/tox.ini0000644000076500000240000000444112642137120015066 0ustar michaelstaff00000000000000[tox] envlist = py26,py27,py32,py33,py34,py35,pypy,pypy3,docs, {py2,py3}-cover,coverage, [testenv] # Most of these are defaults but if you specify any you can't fall back # to defaults for others. basepython = py26: python2.6 py27: python2.7 py32: python3.2 py33: python3.3 py34: python3.4 py35: python3.5 pypy: pypy pypy3: pypy3 py2: python2.7 py3: python3.5 commands = pip install pyramid[testing] nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} [testenv:py26-scaffolds] basepython = python2.6 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:py27-scaffolds] basepython = python2.6 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:py32-scaffolds] basepython = python3.2 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:py33-scaffolds] basepython = python3.4 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:py34-scaffolds] basepython = python3.4 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:pypy-scaffolds] basepython = pypy commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:pypy3-scaffolds] basepython = pypy3 commands = python pyramid/scaffolds/tests.py deps = virtualenv [testenv:docs] basepython = python3.5 whitelist_externals = make commands = pip install pyramid[docs] make -C docs html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E" # we separate coverage into its own testenv because a) "last run wins" wrt # cobertura jenkins reporting and b) pypy and jython can't handle any # combination of versions of coverage and nosexcover that i can find. [testenv:py2-cover] commands = pip install pyramid[testing] coverage run --source=pyramid {envbindir}/nosetests coverage xml -o coverage-py2.xml setenv = COVERAGE_FILE=.coverage.py2 [testenv:py3-cover] commands = pip install pyramid[testing] coverage run --source=pyramid {envbindir}/nosetests coverage xml -o coverage-py3.xml setenv = COVERAGE_FILE=.coverage.py3 [testenv:coverage] basepython = python3.5 commands = coverage erase coverage combine coverage xml coverage report --show-missing --fail-under=100 deps = coverage setenv = COVERAGE_FILE=.coverage