Commits

agentultra committed 2b85681 Merge

Merge upstream with pylons

Comments (0)

Files changed (76)

 Pylons Changelog
 ================
 
+bfg_branch
+* Add dependency on repoze.bfg 1.3a4+.
+
 1.1 (**tip**)
 * Remove Pylons nose plugin. Increase globals that default tests setup.
 

pylons/__init__.py

 from paste.registry import StackedObjectProxy
 
 from pylons.configuration import config
+from pylons.controllers.util import Request
+from pylons.controllers.util import Response
 
 __all__ = ['app_globals', 'cache', 'config', 'request', 'response',
-           'session', 'tmpl_context', 'url']
+           'session', 'tmpl_context', 'url', 'Request', 'Response']
 
 def __figure_version():
     try:

pylons/configuration.py

 
 """
 import copy
+import functools
+import inspect
 import logging
 import os
-import sys
+import re
 
 from paste.config import DispatchingConfig
 from paste.deploy.converters import asbool
-from routes import Mapper
 from webhelpers.mimehelper import MIMETypes
 
+from repoze.bfg.configuration import Configurator as BFGConfigurator
+from repoze.bfg.exceptions import ConfigurationError
+from repoze.bfg.threadlocal import get_current_registry
+from repoze.bfg.threadlocal import get_current_request
+
+from repoze.bfg.mako import renderer_factory as mako_renderer_factory
+
+from pylons.controllers.util import Request
+from pylons.events import TemplateGlobals
+from pylons.url import route_url
+
+
 
 request_defaults = dict(charset='utf-8', errors='replace',
                         decode_param_names=False, language='en-us')
 # built in the paste.app_factory entry point.
 pylons_config.update(copy.deepcopy(PylonsConfig.defaults))
 config.push_process_config(pylons_config)
+
+def globals_factory(system):
+    req = system['request']
+    if req is None:
+        registry = get_current_registry()
+        req = get_current_request()
+        system['request'] = req
+    else:
+        registry = req.registry
+    d = {
+        'url': route_url,
+        'h': registry.helpers,
+    }
+    if req:
+        d['c'] = req.tmpl_context
+        d['tmpl_context'] = req.tmpl_context
+        if 'session' in req.__dict__:
+            d['session'] = req.session
+    
+    has_listeners = registry.has_listeners
+    has_listeners and registry.notify(TemplateGlobals(d))
+    return d
+
+
+class Configurator(BFGConfigurator):
+
+    pylons_route_re = re.compile(r'(/{[a-zA-Z]\w*})')
+
+    def __init__(self, *arg, **kw):
+        result = BFGConfigurator.__init__(self, *arg, **kw)
+        for extension in ('.mak', '.mako'):
+            self.add_renderer(extension, mako_renderer_factory)
+        self.set_renderer_globals_factory(globals_factory)
+        self.registry.helpers = None
+        self.registry.session_options = None
+        self.registry.session_exception = True
+        self.set_request_factory(Request)
+        return result
+    
+    def add_helpers(self, module_ref):
+        """ Add a reference to the helpers module
+                
+        The ``module_ref`` argument should be either an actual
+        reference to the module object that should be used, or a
+        :term:`dotted name string` which will be imported and
+        assigned as the helpers module.
+        
+        The helpers module is generally used with templates to provide
+        a common place to put additional functions needed within all
+        templates. Names present in the helpers module will be
+        available under the 'h' namespace in templates.
+        
+        """
+        module_ref = self.maybe_dotted(module_ref)
+        self.registry.helpers = module_ref
+    
+    def add_sessions(self, settings=None, exception_abort=True, **fallback):
+        """Adds session support to the Pylons application.
+        
+        The ``settings`` argument should be a dict, usually the same
+        settings dict that the :class:`Configurator` was instantiated
+        with. Values will be pulled out of the dict that have a key
+        that begin with either 'beaker.session.' or 'session.'. Options
+        from this dict replace any options of the same name from the
+        fallback keyword args.
+        
+        The ``exception_abort`` argument determines whether the session
+        will be persisted to the database and a cookie sent to the
+        browser in the event that an exception occurs during the request
+        processing. By default, this is set to ``True`` to prevent the 
+        session from being saved if an exception in the framework
+        occurs. Exceptions raised from view code that are not handled
+        by the framework will always cause the session to be aborted.
+        
+        ``fallback`` keyword arguments should be provided to ensure a
+        working application in the event no INI settings are found
+        since many features in a website will depend on sessions. These
+        options 
+        
+        """
+        session_settings = fallback
+        settings = settings or {}
+        for key in settings.keys():
+            for prefix in ['beaker.session.', 'session.']:
+                if key.startswith(prefix):
+                    name = key.split(prefix)[1]
+                    session_settings[name] = settings[key]
+        self.registry.session_exception = exception_abort
+        self.registry.session_options = session_settings
+
+    def add_route(self, name, pattern, **kw):
+        """ Support the syntax supported by
+        :meth:`repoze.bfg.configuration.Configurator.add_route` but
+        also support the ``/{squiggly}`` segment syntax by
+        transforming it into ``/:colon``-style syntax. """
+        parts = self.pylons_route_re.split(pattern)
+        npattern = []
+
+        for part in parts:
+            match = self.pylons_route_re.match(part)
+            if match:
+                npattern.append('/:%s' % match.group()[2:-1])
+            else:
+                npattern.append(part)
+
+        npattern = ''.join(npattern)
+
+        return BFGConfigurator.add_route(self, name, npattern, **kw)
+
+    def add_handler(self, route_name, pattern, handler, action=None, **kw):
+        """ Add a Pylons handler.  This function adds a route and some
+        number of views based on a handler object (usually a class).
+        
+        ``route_name`` is the name of the route (to be used later in
+        URL generation).
+
+        ``pattern`` is the matching pattern,
+        e.g. ``'/blog/{action}'``.  ``pattern`` may be ``None``, in
+        which case the pattern of an existing route named the same as
+        ``route_name`` is used.  If ``pattern`` is ``None`` and no
+        route named ``route_name`` exists, a ``ConfigurationError`` is
+        raised.
+        
+        ``handler`` is a dotted name of (or direct reference to) a
+        Python handler class,
+        e.g. ``'my.package.handlers.MyHandler'``.
+
+        If ``{action}`` or ``:action`` is in
+        the pattern, the exposed methods of the handler will be used
+        as views.
+
+        If ``action`` is passed, it will be considered the method name
+        of the handler to use as a view.
+
+        Passing both ``action`` and having an ``{action}`` in the
+        route pattern is disallowed.
+
+        Any extra keyword arguments are passed along to ``add_route``.
+
+        This method returns the result of add_route."""
+        handler = self.maybe_dotted(handler)
+
+        if pattern is not None:
+            route = self.add_route(route_name, pattern, **kw)
+        else:
+            mapper = self.get_routes_mapper()
+            route = mapper.get_route(route_name)
+            if route is None:
+                raise ConfigurationError(
+                    'The "pattern" parameter may only be "None" when a route '
+                    'with the route_name argument was previously registered. '
+                    'No such route named %r exists'  % route_name)
+                                         
+            pattern = route.pattern
+
+        path_has_action = ':action' in pattern or '{action}' in pattern
+
+        if action and path_has_action:
+            raise ConfigurationError(
+                'action= (%r) disallowed when an action is in the route '
+                'path %r' % (action, pattern))
+
+        if path_has_action:
+            autoexpose = getattr(handler, '__autoexpose__', r'[A-Za-z]+')
+            if autoexpose:
+                try:
+                    autoexpose = re.compile(autoexpose).match
+                except (re.error, TypeError), why:
+                    raise ConfigurationError(why[0])
+            for method_name, method in inspect.getmembers(
+                handler, inspect.ismethod):
+                configs = getattr(method, '__exposed__', [])
+                if autoexpose and not configs:
+                    if autoexpose(method_name):
+                        configs = [{}]
+                for expose_config in configs:
+                    # we don't want to mutate any dict in __exposed__,
+                    # so we copy each
+                    view_args = expose_config.copy()
+                    action = view_args.pop('name', method_name)
+                    preds = list(view_args.pop('custom_predicates',[]))
+                    preds.append(ActionPredicate(action))
+                    view_args['custom_predicates'] = preds
+                    self.add_view(view=handler, attr=method_name,
+                                  route_name=route_name, **view_args)
+        else:
+            method_name = action
+            if method_name is None:
+                method_name = '__call__'
+            
+            # Scan the controller for any other methods with this action name
+            for meth_name, method in inspect.getmembers(
+                handler, inspect.ismethod):
+                configs = getattr(method, '__exposed__', [{}])
+                for expose_config in configs:
+                    # Don't re-register the same view if this method name is
+                    # the action name
+                    if meth_name == action:
+                        continue
+                    # We only reg a view if the name matches the action
+                    if expose_config.get('name') != method_name:
+                        continue
+                    # we don't want to mutate any dict in __exposed__,
+                    # so we copy each
+                    view_args = expose_config.copy()
+                    del view_args['name']
+                    self.add_view(view=handler, attr=meth_name,
+                                  route_name=route_name, **view_args)
+            
+            # Now register the method itself
+            method = getattr(handler, method_name, None)
+            configs = getattr(method, '__exposed__', [{}])
+            for expose_config in configs:
+                self.add_view(view=handler, attr=action, route_name=route_name,
+                              **expose_config)
+
+        return route
+
+    def add_rest_handler(self, collection_name, member_name, handler):
+        """ Add a special kind of handler, a 'REST handler`.
+
+        A 'REST handler' is a class that has a particular interface.
+        This is the interface::
+
+          class MyRestHandler(object):
+              def __init__(self, request):
+                  self.request = request
+                  
+              def index(self):
+                  ''' Return an index of links to members '''
+
+              def create(self):
+                  ''' Create a new member '''
+
+              def new(self):
+                  ''' Returns a new member prototoype '''
+
+              def update(self, id):
+                  ''' Update a member '''
+
+              def delete(self, id):
+                  ''' Delete a member '''
+
+              def show(self, id):
+                  ''' Show a member '''
+
+              def edit(self, id):
+                  ''' Edit a member '''
+
+        All methods should return a Python object, which will be
+        serialized to JSON (or a webob.Response, which will not be).
+
+        The configuration call for
+        ``config.add_rest_handler('message', 'messages', Messages)``
+        is shortcut for a set of ``add_route`` calls which makes the
+        following result table true where ``messages`` below is an
+        instance of the ``Messages`` class::
+
+          GET    /messages        => messages.index()    => url("messages")
+          POST   /messages        => messages.create()   => url("messages")
+          GET    /messages/new    => messages.new()      => url("new_message")
+          PUT    /messages/1      => messages.update(id) => url("message", id=1)
+          DELETE /messages/1      => messages.delete(id) => url("message", id=1)
+          GET    /messages/1      => messages.show(id)   => url("message", id=1)
+          GET    /messages/1/edit => messages.edit(id)   => url("edit_message", id=1)
+
+        XXX need a lot more docs
+
+        """
+        handler = self.maybe_dotted(handler)
+
+        handler = self._make_rest_handler(handler)
+        add = functools.partial(self.add_route, view_renderer='json',
+                                view=handler)
+        add(collection_name, collection_name,
+            request_method='GET', view_attr='index')
+        add(collection_name + '_create', collection_name,
+            request_method='POST', view_attr='create')
+        add('new_%s' % member_name, '%s/new' % collection_name,
+            request_method='GET', view_attr='new')
+        add(member_name, '%s/:id' % collection_name,
+            request_method='GET', view_attr='show')
+        add(member_name + '_update', '%s/:id' % collection_name,
+            request_method='PUT', view_attr='update')
+        add(member_name + '_delete', '%s/:id' % collection_name,
+            request_method='DELETE', view_attr='delete')
+        add('edit_%s' % member_name, '%s/:id/edit' % collection_name,
+            request_method='GET', view_attr='edit')
+        return handler
+
+    def _make_rest_handler(self, handler_factory):
+        class RestHandlerWrapper(object):
+            factory = handler_factory
+            def __init__(self, request):
+                self.request = request
+                
+            def _get_handler(self):
+                handler =  handler_factory(self.request)
+                return handler
+
+            def index(self):
+                handler = self._get_handler()
+                return handler.index()
+
+            def create(self):
+                handler = self._get_handler()
+                return handler.create()
+
+            def new(self):
+                handler = self._get_handler()
+                return handler.new()
+
+            def update(self):
+                handler = self._get_handler()
+                id = self.request.matchdict['id']
+                return handler.update(id)
+
+            def delete(self):
+                handler = self._get_handler()
+                id = self.request.matchdict['id']
+                return handler.delete(id)
+
+            def show(self):
+                handler = self._get_handler()
+                id = self.request.matchdict['id']
+                return handler.show(id)
+
+            def edit(self):
+                handler = self._get_handler()
+                id = self.request.matchdict['id']
+                return handler.edit(id)
+
+        return RestHandlerWrapper
+
+class ActionPredicate(object):
+    action_name = 'action'
+    def __init__(self, action):
+        self.action = action
+        try:
+            self.action_re = re.compile(action + '$')
+        except (re.error, TypeError), why:
+            raise ConfigurationError(why[0])
+
+    def __call__(self, context, request):
+        matchdict = getattr(request, 'matchdict', None)
+        if matchdict is None:
+            return False
+        action = matchdict.get(self.action_name)
+        if action is None:
+            return False
+        return bool(self.action_re.match(action))
+
+    def __hash__(self):
+        # allow this predicate's phash to be compared as equal to
+        # others that share the same action name
+        return hash(self.action)
+        

pylons/controllers/util.py

 import hmac
 import logging
 import re
-import sys
 try:
     import cPickle as pickle
-except ImportError:
+except ImportError: # pragma: no cover
     import pickle
 try:
     from hashlib import sha1
-except ImportError:
+except ImportError: # pragma: no cover
     import sha as sha1
 
-import pkg_resources
-from webob import Request as WebObRequest
+from beaker.session import SessionObject
+from repoze.bfg.decorator import reify
+from repoze.bfg.interfaces import ISettings
+from repoze.bfg.request import Request as RepozeBFGRequest
 from webob import Response as WebObResponse
 from webob.exc import status_map
 
 import pylons
+from pylons.util import PylonsContext
 
-__all__ = ['abort', 'etag_cache', 'lookup_controller', 'redirect', 
-           'redirect_to', 'Request', 'Response']
+__all__ = ['abort', 'etag_cache', 'redirect', 'Request', 'Response']
 
 log = logging.getLogger(__name__)
 
 IF_NONE_MATCH = re.compile('(?:W/)?(?:"([^"]*)",?\s*)')
 
 
-class Request(WebObRequest):
+class Request(RepozeBFGRequest):
     """WebOb Request subclass
     
     The WebOb :class:`webob.Request` has no charset, or other defaults. This subclass
     adds defaults, along with several methods for backwards 
     compatibility with paste.wsgiwrappers.WSGIRequest.
     
-    """    
+    """
+    def __init__(self, *args, **kw):
+        RepozeBFGRequest.__init__(self, *args, **kw)
+        attrs = self.__dict__
+        attrs['tmpl_context'] = PylonsContext()
+    
+    @reify
+    def settings(self):
+        return self.registry.queryUtility(ISettings)
+    
+    @reify
+    def session(self):
+        """Create and return the session object
+        
+        This also adds a response callback, which ensures that the
+        session is written out, and the appropriate cookie's are
+        set if necessary on the response object.
+        
+        """
+        attrs = self.__dict__
+        exception_abort = self.registry.session_exception
+        sess_opts = self.registry.session_options
+        if not sess_opts:
+            raise Exception("Can't use the session without configuring sessions")
+        session = SessionObject(self.environ, **sess_opts)
+        def session_callback(request, response):
+            if 'exception' in attrs and exception_abort:
+                return None
+            if session.accessed():
+                session.persist()
+                if session.__dict__['_headers']['set_cookie']:
+                    cookie = session.__dict__['_headers']['cookie_out']
+                    if cookie:
+                        response.headerlist.append(('Set-cookie', cookie))
+        self._sess_callback = session_callback
+        self.add_response_callback(session_callback)
+        return session
+    
+    def abort_session(self):
+        """Aborts a session
+        
+        This causes a session that was used to be removed from the
+        request, and any saves that were pending will not be persisted.
+        Nor will any cookie be written out indicating the session was
+        accessed.
+        
+        Once a session is aborted, any further use of the `request.session`
+        object will not result in changes being persisted, or update the
+        accessed time for an existing session.
+        
+        """
+        try:
+            sess_callback = self._sess_callback
+        except AttributeError:
+            raise Exception("You cannot cancel a session if there was no"
+                            " session in use.")
+        callbacks = []
+        for cb in self.response_callbacks:
+            if cb != sess_callback:
+                callbacks.append(cb)
+        self.response_callbacks = callbacks
+    
     def determine_browser_charset(self):
         """Legacy method to return the
         :attr:`webob.Request.accept_charset`"""

pylons/docs/en/execution.rst

+Pylons Execution Analysis
+%%%%%%%%%%%%%%%%%%%%%%%%%
+
+*By Mike Orr and Alfredo Deza*
+
+This chapter shows how Pylons calls your application, and how Pylons interacts
+with Paste, Routes, Mako, and its other dependencies.  We'll create
+a simple application and then analyze the Python code executed starting from
+the moment we run the "paster serve" command.
+
+**Abbreviations:** **$APP** is your top-level application directory. 
+**$SP** is the site-packages directory where Pylons is installed.
+**$BIN** is the location of ``paster`` and other executables. $SP paths are
+shown in pip style ($SP/pylons) rather than easy_install style
+($SP/Pylons-VERSION.egg/pylons).
+
+The sample application
+========================
+
+1. Create an application called "Analysis" with a controller called "main"::
+
+    $ paster create -t pylons Analysis
+    $ cd Analysis
+    $ paster controller main
+
+   Press Enter at all question prompts.
+
+2. Edit **analysis/controllers/main.py** to look like this::
+
+        from analysis.lib.base import BaseController
+
+        class MainController(BaseController):
+
+            def index(self):
+                return '<h1>Welcome to the Analysis Demo</h1>Here is a <a href="/page2">link</a>.'
+
+            def page2(self):
+                return 'Thank you for using the Analysis Demo. <a href="/">Home</a>'
+                
+   There are two shortcuts here which you would not use in a normal
+   application. One, we're returning incomplete HTML documents. Two, we've
+   hardcoded the URLs to make the analysis easier to follow, rather than using
+   the ``url`` object.
+
+3. Now edit **analysis/config/routing.py**.  Add these lines after "CUSTOM
+   ROUTES HERE" (line 21)::
+
+    map.connect("home", "/", controller="main", action="index")
+    map.connect("page2", "/page2", controller="main", action="page2")
+
+4. Delete the file **analysis/public/index.html**.
+
+5. Now run the server.  (Press ctrl-C to quit it.) ::
+
+    $ paster serve development.ini
+    Starting server in PID 7341.
+    serving on http://127.0.0.1:5000
+
+
+Pylons' dependencies
+====================
+
+Pylons 1.0 has the following direct and indirect dependencies, which will be
+found in your site-packages directory ($SP):
+
+* Beaker 1.5.4
+* decorator 3.2.0
+* FormEncode 1.2.2
+* Mako 0.3.4
+* MarkupSafe 0.9.3
+* Nose 0.11.4
+* Paste 1.7.3.1
+* PasteDeploy 1.3.3
+* PasteScript 1.7.3
+* Routes 1.12.3
+* simplejson 2.0.9 (if Python < 2.6)
+* Tempita 0.4
+* WebError 0.10.2
+* WebHelpers 1.2
+* WebOb 0.9.8
+* Webtest 1.2.1
+
+These are the current versions as of August 29, 2010. Your installation may have
+slightly newer or older versions.
+
+The analysis
+============
+
+Startup (PasteScript)
+---------------------
+
+When you run ``paster serve development.ini``, it runs the "$BIN/paster" program.
+This is a platform-specific stub created by ``pip`` or ``easy_install``.  It
+does this::
+
+    __requires__ = 'PasteScript==1.7.3'
+    import sys
+    from pkg_resources import load_entry_point
+
+    sys.exit(
+       load_entry_point('PasteScript==1.7.3', 'console_scripts', 'paster')()
+    )
+
+This says to load a Python object "paster" located in an egg "PasteScript",
+version 1.7.3, under the entry point group ``[console_scripts]``.  
+
+To explain what this means we have to get into Setuptools. Setuptools is
+Python's de facto package manager, and was installed as part of your virtualenv
+or Pylons installation. (If you're using Distribute 0.6, an alternative
+package manager, it works the same way.) ``load_entry_point`` is a function
+that looks up a Python object via entry point and returns it.
+
+So what's an entry point? It's an alias for a Python object. Here's the entry
+point itself::
+
+        [console_scripts]
+        paster=paste.script.command:run
+
+This is from $SP/PasteScript-VERSION.egg-info/entry_points.txt.
+(If you used easy_install rather than pip, the path would be slightly
+different: $APP/PasteScript-VERSION.egg/EGG-INFO/entry_points.txt.)
+
+"console_scripts" is the entry point group. "paster" is the
+entry point. The right side of the value tells which module to import
+(``paste.script.command``) and which object in it to return (the ``run``
+function). (To create an entry point, define it in your package's setup.py. Pip
+or easy_install will create the egg_info metadata from that. If you modify a
+package's entry points, you must reinstall the package to update the egg_info.)
+
+The most common use case for entry points is for plugins. So Nose for instance
+defines an entry point group by which it will look for plugins. Any other
+package can provide plugins for Nose by defining entry points in that group.
+Paster uses plugins extensively, as we'll soon see.
+
+So to make a long story short, "paster serve" calls this ``run`` function. I
+inserted print statements into ``paste.script.command`` to figure out what it
+does. Here's a simplified description:
+
+1. The ``run()`` function parses the command-line options into a subcommand 
+   ``"serve"`` with arguments ``["development.ini"]``.
+
+2. It calls ``get_commands()``, which loads Paster commands from plugins
+   located at various entry points.  (You can add custom commands with the
+   "--plugin" command-line argument.)  Paste's standard commands are listed in
+   the same entry_points.txt file we saw above::
+
+        [paste.global_paster_command]
+        serve=paste.script.serve:ServeCommand [Config]
+        #... other commands like "make-config", "setup-app", etc ...
+
+3. It calls ``invoke()``, which essentially does
+   ``paste.script.serve.ServeCommand(["development.ini"]).run()``. This in turn
+   calls ``ServeCommand.command()``, which handles daemonizing and other
+   top-level stuff.  Since our command line is short, there's no top-level
+   stuff to do. It creates 'server' and 'app' objects based on the
+   configuration file, and calls ``server(app)``.
+
+Loading the server and the application (PasteDeploy)
+----------------------------------------------------
+
+This all happens during step 3 of the application startup. We need to find and
+instantiate the WSGI application and server based on the configuration file.
+The application is our Analysis application.  The server is Paste's built-in
+multithreaded HTTP server.  A simplified version of the code is::
+
+    # Inside paste.script.serve module, ServeCommand.command() method.
+    from paste.deploy.loadwsgi import loadapp, loadserver
+    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)
+
+``loadserver()`` and ``loadapp()`` are defined in module
+``paste.deploy.loadwsgi``. The code here is complex, so we'll just look at its
+general behavior. Both functions see the "config:" URI and read our config
+file. Since there is no server name or app name they both default to "main".
+Therefore loadserver() looks for a "\[server:main]" section in the config file,
+and loadapp()` looks for "\[app:main]". Here's what they find in
+"development.ini"::
+
+    [server:main]
+    use = egg:Paste#http
+    host = 127.0.0.1
+    port = 5000
+
+    [app:main]
+    use = egg:Analysis
+    full_stack = true
+    static_files = true
+    ...
+
+The "use =" line in each section tells which object to load. The other lines
+are configuration parameters for that object, or for plugins that object is
+expected to load.  We can also put custom parameters in \[app:main] for our
+application to read directly.
+
+
+Server loading
+++++++++++++++
+
+1. ``loadserver()``'s args are ``uri="config:development.ini", name=None,
+   relative_to="$APP"``.
+
+2. A "config:" URI means to read a config file.
+
+3. A server name was not specified so it defaults to "main". So loadserver()
+   looks for a section "\[server:main]". The "server" part comes from the
+   loadwsgi._Server.config_prefixes class attribute in
+   $SP/paste/deploy/loadwsgi.py).
+
+4. "use = egg:Paste#http" says to load an egg called "Paste".
+
+5. loadwsgi._Server.egg_protocols lists two protocols it supports:
+   "server_factory" and "server_runner".
+
+6. "paste.server_runner" is an entry point group in the "Paste" egg, and it has
+   an entry point "http". The relevant lines in
+   $SP/Paste\*.egg_info/entry_points.txt are::
+
+        [paste.server_runner]
+        http = paste.httpserver:server_runner
+
+7. There's a server_runner() function in the paste.httpserver module
+   ($SP/paste/httpserver.py).
+
+We'll stop here for a moment and look at how the application is loaded.
+
+Application loading
++++++++++++++++++++
+
+1. loadapp() looks for a section "\[app:main]" in the config file. The "app"
+   part comes from the loadwsgi._App.config_prefixes class attribute (in
+   $SP/paste/deploy/loadwsgi.py).
+
+2. "use = egg:Analysis" says to find an egg called "Analysis".
+
+3. loadwsgi._App.egg_protocols lists "paste.app_factory" as one of the
+   protocols it supports.
+
+4. "paste.app_factory" is also an entry point group in the egg, as seen in
+   $APP/Analysis.egg-info/entry_points.txt::
+
+        [paste.app_factory]
+        main = analysis.config.middleware:make_app
+
+5. The line "main = analysis.config.middleware:make_app" means to 
+   look for a ``make_app()`` object in the ``analysis`` package. 
+   This is a function imported from ``analysis.config.middleware`` 
+   ($APP/analysis/config/middleware.py).
+
+
+Instantiating the application (Analysis)
+----------------------------------------
+
+Here's a closer look at our application's ``make_app`` function::
+
+    # In $APP/analysis/config/middleware.py
+    def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
+        config = load_environment(global_conf, app_conf)
+        app = PylonsApp(config=config)
+        app = SomeMiddleware(app, ...)   # Repeated for several middlewares.
+        app.config = config
+        return app
+
+This sets up the Pylons environment (next subsection), creates the application
+object (following subsection), wraps it in several layers of middleware (listed
+in "Anatomy of a Request" below), and returns the complete application object.
+
+The \[DEFAULT] section of the config file is passed as dict ``global_conf``.
+The \[app:main] section is passed as keyword arguments into dict ``app_conf``.
+
+``full_stack`` defaults to True because we're running the application
+standalone.  If we were embedding this application as a WSGI component of some
+larger application, we'd set ``full_stack`` to False to disable some of the
+middleware.  
+
+``static_files=True`` means to serve static files from our public
+directory ($APP/analysis/public). Advanced users can arrange for Apache to
+serve the static files itself, and put "static_files = false"
+in their configuration file to gain a bit of efficiency.
+
+load_environment & pylons.config
+++++++++++++++++++++++++++++++++
+
+Before we begin, remember that ``pylons.config``, ``pylons.app_globals``,
+``pylons.request``, ``pylons.response``, ``pylons.session``, ``pylons.url``,
+and ``pylons.cache`` are special globals that change value depending on the
+current request. The objects are proxies which maintain a thread-local stack of
+real values.  Pylons pushes the actual values onto them at the beginning of a
+request, and pops them off at the end. (Some of them it also pushes at other
+times so they can be used outside of requests.) The proxies delegate attribute
+access and key access to the topmost actual object on the stack. (You can also
+call ``myproxy._current_obj()`` to get the actual object itself.)  The proxy
+code is in ``paste.registry.StackedObjectProxy``, so these are called
+"StackedObjectProxies", or "SOPs" for short.
+
+The first thing ``analysis.config.middleware.make_app()`` does is call
+``analysis.config.environment.load_environment()``::
+
+    def load_environment(global_conf, app_conf):
+        config = PylonsConfig()
+        root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+        paths = dict(root=root,
+                     controllers=os.path.join(root, 'controllers'),
+                     static_files=os.path.join(root, 'public'),
+                     templates=[os.path.join(root, 'templates')])
+
+        # Initialize config with the basic options
+        config.init_app(global_conf, app_conf, package='analysis',
+                        paths=paths)
+        config['routes.map'] = make_map(config)
+        config['pylons.app_globals'] = app_globals.Globals(config)
+        config['pylons.h'] = analysis.lib.helpers
+
+        # Setup cache object as early as possible
+        import pylons
+        pylons.cache._push_object(config['pylons.app_globals'].cache)
+
+        # Create the Mako TemplateLookup, with the default auto-escaping
+        config['pylons.app_globals'].mako_lookup = TemplateLookup(
+            directories=paths['templates'],
+            error_handler=handle_mako_error,
+            module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
+            input_encoding='utf-8', default_filters=['escape'],
+            imports=['from webhelpers.html import escape'])
+
+        # CONFIGURATION OPTIONS HERE (note: all config options will override
+        # any Pylons config options)
+
+        return config
+
+``config`` is the Pylons configuration object, which will later be pushed onto
+``pylons.config``. It's an instance of ``pylons.configuration.PylonsConfig``, a
+dict subclass. ``config.init_app()`` initializes the dict's keys.  It sets the
+keys to a merger of app_conf and global_conf (with app_conf overriding). It
+also adds "app_conf" and "global_conf" keys so you can access the original
+app_conf and global_conf if desired. It also adds several Pylons-specific keys.
+
+``config["routes.map"]`` is the Routes map defined in
+``analysis.config.routing.make_map()``. 
+
+``config["pylons.app_globals"]`` is the application's globals object, which
+will later be pushed onto ``pylons.app_globals``.  It's an instance of
+``analysis.lib.app_globals.Globals``.
+
+``config["pylons.h"]`` is the helpers module, ``analysis.lib.helpers``. Pylons
+will assign it to ``h`` in the templates' namespace.
+
+The "cache" lines push ``pylons.app_globals.cache`` onto ``pylons.cache`` for
+backward compatibility.  This gives a preview of how StackedObjectProxies work.
+
+The Mako stanza creates a TemplateLookup, which ``render()`` will use to find
+templates. The object is put on ``app_globals``.
+
+If you've used older versions of Pylons, you'll notice a couple differences in
+1.0.  The ``config`` object is created as a local variable and returned, and
+it's passed explicitly to the route map factory and globals factory. Previous
+versions pushed it onto ``pylons.config`` immediately and used it from there.
+This was changed to make it easier to nest Pylons applications inside other
+Pylons applications. 
+
+The other difference is that Buffet is gone, and along with it the
+``template_engine`` argument and template config options. Pylons 1.0 gets out
+of the business of initializing template engines.  You use one of the standard
+render functions such as ``render_mako`` or write your own, and define any
+attributes in ``app_globals`` that your render function depends on.
+
+
+PylonsApp
++++++++++
+
+The second line of ``make_app()`` creates a Pylons application object
+based on your configuration.  Again the ``config`` object is passed around
+explicitly, unlike older versions of Pylons. A Pylons application is an
+instance of ``pylons.wsgiapp.PylonsApp`` instance. (Older versions of Pylons
+had a ``PylonsBaseWSGIApp`` superclass, but that has been merged into
+``PylonsApp``.)
+
+Middleware
+++++++++++
+
+``make_app()`` then wraps the application (the ``app`` variable) in several
+layers of middleware. Each middleware provides an optional add-on service. 
+
+================== ============================ ===============================
+Middleware         Service                      Effect if disabled
+================== ============================ ===============================
+RoutesMiddleware   Use Routes to manage URLs.   Routes and ``pylons.url`` won't
+                                                work.
+SessionMiddleware  HTTP sessions using Beaker,  ``pylons.session`` won't work.
+                   with flexible persistence
+                   backends (disk, memached,
+                   database).
+ErrorHandler       Display interactive          Paste will catch exceptions and 
+                   traceback if an exception    convert them to Internal Server
+                   occurs. In production mode,  Error.
+                   email the traceback to the
+                   site admin.
+StatusCodeRedirect If an HTTP error occurs,     If an HTTP error occurs, 
+                   make a subrequest to display display a plain white HTML page
+                   a fancy styled HTML error    with the error message.
+                   page.
+RegistryManager    Handles the special globals  The special globals won't work. 
+                   (``pylons.request``, etc).   There are other ways to access
+                                                the objects without going
+                                                through the special globals.
+StaticURLParser    Serve the static files       The static files won't be 
+                   in the application's         found. Presumably you've
+                   public directory.            configured Apache to serve them
+                                                directly.
+Cascade            Call several sub-middlewares No cascading through
+                   in order, and use the first  alternative apps.
+                   one that doesn't return
+                   "404 Not Found". Used in
+                   conjunction with 
+                   StaticURLParser.
+================== ============================ ===============================
+
+At the end of the function, ``app.config`` is set to the ``config`` object, so
+that any part of the application can access the config without going through
+the special global.
+
+Anatomy of a request
+--------------------
+
+Let's say you're running the demo and click the "link" link on the home page.
+The browser sends a request for "http://localhost:5000/page2".  In my Firefox
+the HTTP request headers are::
+
+    GET /page2 
+    Host: 127.0.0.1:5000
+    User-Agent: Mozilla/5.0 ...
+    Accept: text/html,...
+    Accept-Language: en-us,en;q=0.5
+    Accept-Encoding: gzip,deflate
+    Accept-Charset: ISO-8859-1,utf-8;q=0.7*;q=0.7
+    Keep-Alive: 300
+    Connection: keep-alive
+    Referer: http://127.0.0.1/5000/
+    Cache-Control   max-age=0
+
+The response is::
+
+    HTTP/1.x 200 OK
+    Server: PasteWSGIServer/0.5 Python/2.6.4
+    Date: Sun, 06 Dec 2009 14:06:05 GMT
+    Content-Type: text/html; charset=utf-8
+    Pragma:  no-cache
+    Cache-Control:   no-cache
+    Content-Length:  59
+
+    Thank you for using the Analysis Demo.  <a href="/">Home</a>
+
+Here's the processing sequence:
+
+1. ``server(app)`` is still running, called by ``ServeCommand.command()`` in
+   $SP/paste/script/serve.py.
+
+2. ``server`` is actually ``paste.httpserver.server_runner()`` in
+   $SP/paste/httpserver. The only keyword args are 'host' and
+   'port' extracted from the config file.  ``server_runner`` de-stringifies
+   the arguments and calls ``serve(wsgi_app, **kwargs)`` (same module).  
+
+3. ``serve()``'s 'use_threadpool' arg defaults to True, so it creates a
+   ``WSGIThreadPoolServer`` instance called (``server``) with the following
+   inheritance::
+
+        SocketServer.BaseServer     # In SocketServer.py in Python stdlib.
+        BaseHTTPServer.HTTPServer  # In BaseHTTPServer.py in Python stdlib.
+        paste.httpserver.SecureHTTPServer  # Adds SSL (HTTPS).
+        paste.httpserver.WSGIServerBase    # Adds WSGI.
+        paste.httpserver.WSGIThreadPoolServer
+            multiple inheritance: ThreadPoolMixIn <= ThreadPool
+
+    Note that SecureHTTPServer overrides the implementation of Python's
+    SocketServer.TCPServer
+
+4. It calls ``server.serve_forever()``, implemented by the ``ThreadPoolMixIn``
+   superclass.  This calls ``self.handle_request()`` in a loop until
+   ``self.running`` becomes false.  That initiates this call stack::
+
+        # In paste.httpserver.serve(), calling 'server.serve_forever()'
+        ThreadPoolMixIn.serve_forever()  # Defined in paste.httpserver.
+        -> TCPServer.handle_request()    # Called for every request.
+        -> WSGIServerBase.get_request()
+        -> SecureHTTPServer.get_request()
+        -> self.socket.accept()          # Defined in stdlib socket module.
+
+   ``self.socket.accept()`` blocks, waiting for the next request.
+
+5. The request arrives and ``self.socket.accept()`` returns a new socket for
+   the connection. ``TCPServer.handle_request()`` continues. It calls
+   ``ThreadPoolMixIn.process_request()``, which puts the request in a thread
+   queue::
+
+        self.thread_pooladd.add_task(
+            lambda: self.process_request_in_thread(request, client_address))
+            # 'request' is the connection socket.
+
+   The thread pool is defined in the ``ThreadPool`` class. It spawns a number
+   of threads which each wait on the queue for a callable to run. In this case
+   the callable will be a complete Web transaction including sending the HTML
+   page to the client. Each thread will repeatedly process transactions from
+   the queue until they receive a sentinel value ordering them to die.
+
+   The main thread goes back to listening for other requests, so we're no
+   longer interested in it.
+
+6. **Thread #2** pulls the lambda out of the queue and calls it::
+
+        lambda
+        -> ThreadPoolMixIn.process_request_in_thread()
+        -> BaseServer.finish_request()
+        -> self.RequestHandlerClass(request, client_address, self)  # Instantiates this.
+           The class instantiated is paste.httpserver.WSGIHandler; i.e., the 'handler' variable in serve().
+
+7. The newly-created request handler takes over::
+
+        SocketServer.BaseRequestHandler.__init__(request, client_address, server)
+        -> WSGIHandler.handle()
+        -> BaseHTTPRequestHandler.handle()  # In stdlib BaseHTTPServer.py
+           Handles requests in a loop until self.close_connection is true.  (For HTTP keepalive?)
+        -> WSGIHandler.handle_one_request()
+           Reads the command from the socket.  The command is
+           "GET /page2 HTTP/1.1" plus the HTTP headers above.
+           BaseHTTPRequestHandler.parse_request() parses this into attributes
+           .command, .path, .request_version, and .headers.
+        -> WSGIHandlerMixin.wsgi_execute().
+        -> WSGIHandlerMixin.wsgi_setup()
+           Creates the .wsgi_environ dict.
+
+   The WSGI environment dict is described in PEP 333, the WSGI specification.
+   It contains various keys specifying the URL to fetch, query parameters,
+   server info, etc. All keys required by the CGI specification are present,
+   as are other keys specific to WSGI or to paricular middleware. The
+   application will calculate a response based on the dict. The application is
+   wrapped in layers of middleware -- nested function calls -- which modify
+   the dict on the way in and modify the response on the way out.
+
+8. The request handler, still in ``WSGIHandlerMixin.wsgi_execute()``, calls the
+   application thus::
+
+        result = self.server.wsgi_application(self.wsgi_environ,
+                                            self.wsgi_start_response)
+
+   ``wsgi_start_response`` is a callable mandated by the WSGI spec. The
+   application will call it to specify the HTTP headers. The return value is
+   an iteration of strings, which when concatenated form the HTML document to
+   send to the browser. Other MIME types are handled analagously.
+
+9. The application, as we remember, was returned by
+   ``analysis.config.middleware.make_app()``. It's wrapped in several layers
+   of middleware, so calling it will execute the middleware in reverse order
+   of how they're listed in $APP/analysis/config/middleware.py and
+   $SP/pylons/wsgiapp.py:
+
+        * ``Cascade`` (defined in $SP/paste/cascade.py) lists a 
+          series of applications which will be tried in order (Skipped if static_files is set to False):
+
+                1. ``StaticURLParser`` (defined in 
+                   $SP/paste/urlparser) looks for a file URL
+                   under $APP/analysis/public that matches the URL.  The demo
+                   has no static files.
+
+                2. If that fails the cascader tries your application.  
+                   But first there are other middleware to go through...
+
+        * ``RegistryManager`` (defined in $SP/paste/registry.py) 
+          makes Pylons special globals both thread-local and middleware-local.
+          This includes **app_globals**, **cache**, **request**, **response**,
+          **session**, **tmpl_context**, **url**, and any other
+          ``StackedObjectProxy`` listed in $SP/pylons/__init__.py.  (**h** is
+          a module so it doesn't need a proxy.)
+
+        * ``StatusCodeRedirect`` (defined in $SP/pylons/middleware.py)
+          intercepts any HTTP error status returned by the application (e.g.,
+          "Page Not Found", "Internal Server Error") and sends another request
+          to the application to get the appropriate error page to display
+          instead. (Skipped if ``full_stack`` argument was false.)
+
+        * ``ErrorHandler`` (defined in $SP/pylons/middleware.py)
+          sends an interactive traceback to the browser if the app raises an
+          exception, if "debug" is true in the config file.  Otherwise it
+          attempts to email the traceback to the site administrator, and
+          substitutes a generic Internal Server Error for the response.
+          (Skipped if ``full_stack`` argument was false.
+
+        * User-defined middleware goes here.
+
+        * ``SessionMiddleware`` (wsgiapp.py) adds `Beaker`_
+          session support (the ``pylons.session`` object).  (Skipped if the
+          WSGI environment has a key 'session' -- it doesn't in this demo.)
+
+        * ``RoutesMiddleware`` (wsgiapp.py) compares the request URI against the
+          routing rules in $APP/analysis/config/routing.py and sets
+          'wsgi.routing_args' to the routing match dict (useful) and
+          'routes.route' to the Route (probably not useful).  Pylons 1.0 apps
+          have a ``singleton=False`` argument that suppresses initializing the
+          deprecated ``url_for()`` function. Routes now puts a URL
+          generator in the WSGI environment, which Pylons aliases to
+          ``pylons.url``.
+
+        * The innermost middleware calls the PylonsApp instance it was 
+          initialized with.
+
+    Note: CacheMiddleware is no longer used in Pylons 1.0. Instead,
+    ``app_globals`` creates the cache as an attribute, and a line in
+    environment.py aliases ``pylons.cache`` to it.
+
+10. Surprise! PylonsApp is itself middleware. Its .\_\_call\_\_() method does::
+
+        self.setup_app_env(environ, start_response)
+        controller = self.resolve(environ, start_response)
+        response = self.dispatch(controller, environ, start_response)
+        return response
+
+    ``.setup_app_env()`` registers all those special globals.
+
+    ``.resolve()`` calculates the controller class based on the route chosen by
+    the RoutesMiddleware, and returns the controller class.
+
+    ``.dispatch`` instantiates the controller class and calls in the WSGI
+    manner.  If the controller does not exist (``.resolve()`` returned None),
+    raise an Exception that tells you what controller did not have any 
+    content.
+
+    This method also handles the special URL "/_test_vars", which is enabled
+    if the application is running under a Nose test. This URL initializes
+    Pylons' special globals, for tests that have to access them before making
+    a regular request.
+
+11. ``analysis.controllers.main.MainController`` does not have a
+    ``.\_\_call\_\_()`` method, so control falls to its parent,
+    ``analysis.lib.base.BaseController``.  This trivially calls the
+    grandparent, ``pylons.controllers.WSGIController``. It calls the action
+    method ``MainController.page2()``. The action method may have any number of
+    positional arguments as long as they correspond to variables in the routing
+    match dict.  (GET/POST variables are in the **request.params** dict.)  If
+    the method has a ``\*\*kwargs`` argument, all other match variables are put
+    there.  Any variables passed to the action method are also put on the
+    **tmpl_context** object as attributes. If an action method name
+    starts with "\_", it's private and HTTPNotFound is raised.
+
+12. If the controller has .\_\_before\_\_() and/or .\_\_after\_\_() methods,
+    they are called before and after the action, respectively. These can
+    perform authorization, lock OS resources, etc. These methods can have
+    arguments in the same manner as the action method.  However, if the code is
+    used by all controllers, most Pylons programmers prefer to it in the base
+    controller's ``.\_\_call\_\_`` method instead.
+
+13. The action method returns a string, unicode, Response object, or is a
+    generator of strings. In this trivial case it returns a string. A typical
+    Pylons action would set some *tmpl_context* attributes and 'return
+    render('/some/template.html")' . In either case the global *response*
+    object's body would be set to the string.
+
+14. ``WSGIController.\_\_call\_\_()`` continues, converting the Response object
+    to an appropriate WSGI return value. (First it calls the start_response
+    callback to specify the HTTP headers, then it returns an iteration of
+    strings.  The Response object converts unicode to utf-8 encoded strings, or
+    whatever encoding you've specified in the config file.)
+
+15. The stack of middleware calls unwinds, each modifying the return value and
+    headers if it desires.  
+
+16. The server receives the final return value. (We're way back in
+    ``paste.httpserver.WSGIHandlerMixin.wsgi_execute()`` now.) The outermost
+    middleware has called back to ``server.start_response()``, which has saved
+    the status and HTTP headers in ``.wsgi_curr_headers``. ``.wsgi_execute()``
+    then iterates the application's return value, calling
+    ``.wsgi_write_chunk(chunk)`` for each encoded string yielded.
+    ``.wsgi_write_chunk('')`` formats the status and HTTP headers and sends them
+    on the socket if they haven't been sent yet, then sends the chunk. The
+    convoluted header behavior here is mandated by the WSGI spec.
+
+17. Control returns to ``BaseHTTPRequestHandler.handle()``.
+    ``.close_connection`` is true so this method returns. The call stack
+    continues unwinding all the way to
+    ``paste.httpserver.ThreadPoolMixIn.process_request_in_thread()``. This
+    tries to finish the request first and then close it unless it finds errors in it to end raising an Exception.
+
+18. The request lambda finishes and control returns to
+    ``ThreadPool.worker_thread_callback()``. It waits for another request in
+    the thread queue. If the next item in the queue is the shutdown sentinel
+    value, thread #2 dies.
+
+Thus endeth our request's long journey, and this analysis is finished too.
+
+.. _beaker:  http://beaker.groovie.org/

pylons/docs/en/glossary.rst

             Pylons :ref:`deployment`
 
         .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
-
+    
+    dotted name string
+        A reference to a Python module by name using a string to identify it,
+        e.g. ``pylons.controllers.util``. These strings are evaluated to
+        import the module being referenced without having to import it in
+        the code used. This is generally used to avoid import-time 
+        side-effects.
+    
     egg
         Python egg's are bundled Python packages, generally installed by
         a package called :term:`setuptools`. Unlike normal Python package

pylons/docs/en/index.rst

     security_policy_for_bugs
     wsgi_support
     advanced_pylons/index
+    execution
 
 
 Module Listing
+from zope.interface import implements
+
+from pylons.interfaces import ITemplateGlobals
+
+class TemplateGlobals(object):
+    """ An instance of this class is emitted as an :term:`event`
+    whenever :mod:`pylons` has finished setting up the template
+    globals. The instance has an attribute, ``system``, which is a
+    dict of all the globals that will be added to the template's
+    global namespace during rendering.
+    
+    """
+    implements(ITemplateGlobals)
+    def __init__(self, system):
+        self.system = system

pylons/interfaces.py

+from repoze.bfg.interfaces import IAfterTraversal
+from repoze.bfg.interfaces import INewRequest
+from repoze.bfg.interfaces import INewResponse
+from repoze.bfg.interfaces import IRoutesMapper
+from repoze.bfg.interfaces import IWSGIApplicationCreatedEvent
+
+from zope.interface import Interface
+
+# internal interfaces
+
+class ITemplateGlobals(Interface):
+    """ Event issued when Pylons is done loading globals """

pylons/middleware.py

 
 report_libs = ['pylons', 'genshi', 'sqlalchemy']
 
+def DebugHandler(app, global_conf, **kwargs):
+    footer = footer_html % (kwargs.get('traceback_host', 
+                                       'pylonshq.com'),
+                            pylons.__version__)
+    py_media = dict(pylons=media_path)
+    app = EvalException(app, global_conf, 
+                        templating_formatters=template_error_formatters,
+                        media_paths=py_media, head_html=head_html, 
+                        footer_html=footer,
+                        libraries=report_libs)
+    return app
+
+
 def ErrorHandler(app, global_conf, **errorware):
     """ErrorHandler Toggle
     
 </body>
 </html>
 """)
+
+def debugger_filter_factory(global_conf, **kwargs):
+    def filter(app):
+        return DebugHandler(app, global_conf, **kwargs)
+    return filter
+
+
+def debugger_filter_app_factory(app, global_conf, **kwargs):
+    return DebugHandler(app, global_conf, **kwargs)

pylons/templates/legacy_project/+package+/__init__.py_tmpl

Empty file added.

pylons/templates/legacy_project/+package+/config/deployment.ini_tmpl_tmpl

+#
+# {{project}} - Pylons configuration
+#
+# The %(here)s variable will be replaced with the parent directory of this file
+#
+[DEFAULT]
+debug = true
+email_to = you@yourdomain.com
+smtp_server = localhost
+error_email_from = paste@localhost
+
+[server:main]
+use = egg:Paste#http
+host = 0.0.0.0
+port = 5000
+
+[app:main]
+use = egg:{{project}}
+full_stack = true
+static_files = true
+
+cache_dir = %(here)s/data
+beaker.session.key = {{package}}
+beaker.session.secret = ${app_instance_secret}
+app_instance_uuid = ${app_instance_uuid}
+
+# If you'd like to fine-tune the individual locations of the cache data dirs
+# for the Cache data, or the Session saves, un-comment the desired settings
+# here:
+#beaker.cache.data_dir = %(here)s/data/cache
+#beaker.session.data_dir = %(here)s/data/sessions
+
+# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
+# Debug mode will enable the interactive debugging tool, allowing ANYONE to
+# execute malicious code after an exception is raised.
+set debug = false
+
+
+# Logging configuration
+[loggers]
+keys = root
+
+[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

pylons/templates/legacy_project/+package+/controllers/__init__.py_tmpl

+"""The base Controller API
+
+Provides the BaseController class for subclassing.
+"""
+from pylons.controllers import WSGIController
+{{if template_engine in ('genshi', 'jinja2', 'mako')}}
+from pylons.templating import render_{{template_engine}} as render
+{{endif}}
+
+class BaseController(WSGIController):
+
+    def __call__(self, environ, start_response):
+        """Invoke the Controller"""
+        # WSGIController.__call__ dispatches to the Controller method
+        # the request is routed to. This routing information is
+        # available in environ['pylons.routes_dict']
+        return WSGIController.__call__(self, environ, start_response)

pylons/templates/legacy_project/+package+/helpers.py_tmpl

+"""Helper functions
+
+Consists of functions to typically be used within templates, but also
+available to Controllers. This module is available to templates as 'h'.
+"""
+# Import helpers as desired, or define your own, ie:
+#from webhelpers.html.tags import checkbox, password

pylons/templates/legacy_project/+package+/public/bg.png

Added
New image

pylons/templates/legacy_project/+package+/public/favicon.ico

Added
New image

pylons/templates/legacy_project/+package+/public/index.html_tmpl

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html 
+     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+    <head>
+        <title>Welcome to Pylons!</title>
+        <style type="text/css">
+            body {
+                font-family: arial, helvetica, sans-serif;
+                background-color: #ffc900;
+                background-image: url(bg.png);
+                background-repeat: repeat-x;
+                width:100%;
+                height:100%;
+                margin:0;
+                max-height: 100%;
+                padding:0;
+                border:none;
+                line-height:1.4;
+            }
+            #container {
+                color:white;
+                background-color:#111;
+                position: absolute;
+                left: 50%;
+                width: 500px;
+                margin-left: -300px;
+                padding:50px;
+                height:100%;
+            }
+            #footer {
+                margin: 120px 0 0 0;
+                padding: 3px;
+                text-align:center;
+                font-size:small;
+                background-color:#222;
+                letter-spacing: 1px;
+            }
+            h1 {
+                text-align:center;
+                font-size:xx-large;
+                font-weight:normal;
+                margin: 0 0 20px 0;
+                border:none;
+                padding:0;
+                letter-spacing: 5px;
+            }
+            h2 {
+                font-size:xx-large;
+                font-weight:normal;
+                margin: 0 0 20px 0;
+                border:none;
+                padding:0;
+            }
+            hr {
+                margin-bottom:30px;
+                border: 1px solid #222;
+                background-color: #222;
+                padding: 2px;
+            }
+            #logo {
+                background-image: url(signum8b_spk.png);
+                background-repeat: no-repeat;
+                height: 0;
+                overflow: hidden;
+                padding-top: 99px;
+                width: 239px;
+            }
+            #left {
+                float:left;
+                width:250px;
+                margin:0 50px 0 0;
+                border:none;
+                padding:0 0 0 10px;
+            }
+            #right {
+                margin:0 0 0 330px;
+                border:none;
+                padding:0;
+            }
+            ul {
+                list-style:none;
+                margin:0;
+                border:none;
+                padding:0;
+            }
+            a:visited {
+                color:white;
+                text-decoration:none;
+            }
+            a:link {
+                color:white;
+                text-decoration:none;
+            }</style>
+    </head>
+    <body>
+        <div id="container">
+            <h1>Welcome to <img src="pylons-logo.gif" alt="Logo displaying the word Pylons"
+                    style="vertical-align:-15px; width: 250px;"/>
+            </h1>
+            <hr/>
+            <div id="left">
+                <h2>Let's begin!</h2>
+                <p>If you haven't used Pylons before, start with the <a href="http://pylonshq.com/docs/en/1.0/gettingstarted/"
+                        style="text-decoration:underline;">beginners' tutorial</a>.</p>
+            </div>
+            <div id="right">
+                <h2>Help</h2>
+                <ul>
+                    <li>
+                        <a href="http://pylonshq.com/docs/en/1.0/">Official documentation</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonsfaq/Home">FAQ</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/dashboard.action">Wiki</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonscommunity/Home#Home-JointheMailingLists">Mailing list</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonscommunity/Home#Home-IRC">IRC</a>
+                    </li>
+                    <li>
+                        <a href="http://pylonshq.com/project/pylonshq/roadmap">Bug tracker</a>
+                    </li>
+                </ul>
+            </div>
+            <div id="footer">
+                <a href="http://www.pylonshq.com" style="color: #ccc; text-decoration:none;"
+                    >www.pylonshq.com</a>
+            </div>
+        </div>
+    </body>
+</html>

pylons/templates/legacy_project/+package+/public/pylons-logo.gif

Added
New image

pylons/templates/legacy_project/+package+/routing.py_tmpl

+"""Routes configuration
+
+The more specific and detailed routes should be defined first so they
+may take precedent over the more generic routes. For more information
+refer to the routes manual at http://routes.groovie.org/docs/
+"""
+from routes import Mapper
+
+def make_map(config):
+    """Create, configure and return the routes Mapper"""
+    map = Mapper(directory=config['pylons.paths']['controllers'],
+                 always_scan=config['debug'])
+    map.minimization = False
+    map.explicit = False
+
+    # The ErrorController route (handles 404/500 error pages); it should
+    # likely stay at the top, ensuring it can always be resolved
+    map.connect('/error/{action}', controller='error')
+    map.connect('/error/{action}/{id}', controller='error')
+
+    # CUSTOM ROUTES HERE
+
+    map.connect('/{controller}/{action}')
+    map.connect('/{controller}/{action}/{id}')
+
+    return map

pylons/templates/legacy_project/+package+/templates/.distutils_placeholder

Empty file added.

pylons/templates/legacy_project/+package+/templates/__init__.py_tmpl

+{{if template_engine not in ['genshi', 'kid']}}
+{{skip_template()}}
+{{endif}}

pylons/templates/legacy_project/+package+/templates/index.pt

+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html 
+     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+    <head>
+        <title>Welcome to (New-Style) Pylons!</title>
+        <style type="text/css">
+            body {
+                font-family: arial, helvetica, sans-serif;
+                background-color: #ffc900;
+                background-image: url(bg.png);
+                background-repeat: repeat-x;
+                width:100%;
+                height:100%;
+                margin:0;
+                max-height: 100%;
+                padding:0;
+                border:none;
+                line-height:1.4;
+            }
+            #container {
+                color:white;
+                background-color:#111;
+                position: absolute;
+                left: 50%;
+                width: 500px;
+                margin-left: -300px;
+                padding:50px;
+                height:100%;
+            }
+            #footer {
+                margin: 120px 0 0 0;
+                padding: 3px;
+                text-align:center;
+                font-size:small;
+                background-color:#222;
+                letter-spacing: 1px;
+            }
+            h1 {
+                text-align:center;
+                font-size:xx-large;
+                font-weight:normal;
+                margin: 0 0 20px 0;
+                border:none;
+                padding:0;
+                letter-spacing: 5px;
+            }
+            h2 {
+                font-size:xx-large;
+                font-weight:normal;
+                margin: 0 0 20px 0;
+                border:none;
+                padding:0;
+            }
+            hr {
+                margin-bottom:30px;
+                border: 1px solid #222;
+                background-color: #222;
+                padding: 2px;
+            }
+            #logo {
+                background-image: url(signum8b_spk.png);
+                background-repeat: no-repeat;
+                height: 0;
+                overflow: hidden;
+                padding-top: 99px;
+                width: 239px;
+            }
+            #left {
+                float:left;
+                width:250px;
+                margin:0 50px 0 0;
+                border:none;
+                padding:0 0 0 10px;
+            }
+            #right {
+                margin:0 0 0 330px;
+                border:none;
+                padding:0;
+            }
+            ul {
+                list-style:none;
+                margin:0;
+                border:none;
+                padding:0;
+            }
+            a:visited {
+                color:white;
+                text-decoration:none;
+            }
+            a:link {
+                color:white;
+                text-decoration:none;
+            }</style>
+    </head>
+    <body>
+        <div id="container">
+            <h1>Welcome to (New-Style) <img src="pylons-logo.gif" alt="Logo displaying the word Pylons"
+                    style="vertical-align:-15px; width: 250px;"/>
+            </h1>
+            <hr/>
+            <div id="left">
+                <h2>Let's begin!</h2>
+                <p>If you haven't used Pylons before, start with the <a href="http://pylonshq.com/docs/en/1.0/gettingstarted/"
+                        style="text-decoration:underline;">beginners' tutorial</a>.</p>
+            </div>
+            <div id="right">
+                <h2>Help</h2>
+                <ul>
+                    <li>
+                        <a href="http://pylonshq.com/docs/en/1.0/">Official documentation</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonsfaq/Home">FAQ</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/dashboard.action">Wiki</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonscommunity/Home#Home-JointheMailingLists">Mailing list</a>
+                    </li>
+                    <li>
+                        <a href="http://wiki.pylonshq.com/display/pylonscommunity/Home#Home-IRC">IRC</a>
+                    </li>
+                    <li>
+                        <a href="http://pylonshq.com/project/pylonshq/roadmap">Bug tracker</a>
+                    </li>
+                </ul>
+            </div>
+            <div id="footer">
+                <a href="http://www.pylonshq.com" style="color: #ccc; text-decoration:none;"
+                    >www.pylonshq.com</a>
+            </div>
+        </div>
+    </body>
+</html>

pylons/templates/legacy_project/+package+/tests/__init__.py_tmpl

+"""Pylons application test package
+
+This package assumes the Pylons environment is already loaded, such as
+when this script is imported from the `nosetests --with-pylons=test.ini`
+command.
+
+This module initializes the application via ``websetup`` (`paster
+setup-app`) and provides the base testing objects.
+"""
+from unittest import TestCase
+
+from paste.script.appinstall import SetupCommand
+from pylons import url
+from routes.util import URLGenerator
+from webtest import TestApp
+
+import pylons.test
+
+__all__ = ['environ', 'url', 'TestController']
+
+# Invoke websetup with the current config file
+#SetupCommand('setup-app').run([pylons.test.pylonsapp.config['__file__']])
+# XXX: the above line is commented out because a scan invokes it and fails
+
+environ = {}
+
+class TestController(TestCase):
+
+    def __init__(self, *args, **kwargs):
+        wsgiapp = pylons.test.pylonsapp
+        config = wsgiapp.config
+        self.app = TestApp(wsgiapp)
+        url._push_object(URLGenerator(config['routes.map'], environ))
+        TestCase.__init__(self, *args, **kwargs)

pylons/templates/legacy_project/+package+/views.py

+from repoze.bfg.view import bfg_view as pylons_view # ;-)
+
+@pylons_view(route_name='new', renderer='templates/index.pt')
+def index_view(request):
+    return {}
+
+
+    
+    

pylons/templates/legacy_project/+package+/wsgiapp.py_tmpl

+"""The {{project}} WSGI application"""
+import os
+
+from beaker.cache import CacheManager
+from beaker.middleware import SessionMiddleware
+from beaker.util import parse_cache_config_options
+{{if template_engine == 'mako'}}
+from mako.lookup import TemplateLookup
+{{elif template_engine == 'genshi'}}
+from genshi.template import TemplateLoader
+{{elif template_engine == 'jinja2'}}
+from jinja2 import ChoiceLoader, Environment, FileSystemLoader
+{{endif}}
+from paste.cascade import Cascade
+from paste.registry import RegistryManager
+from paste.urlparser import StaticURLParser
+from paste.deploy.converters import asbool
+from pylons.configuration import PylonsConfig
+{{if template_engine == 'mako'}}
+from pylons.error import handle_mako_error
+{{endif}}
+from pylons.middleware import ErrorHandler, StatusCodeRedirect
+from pylons import url
+from pylons.wsgiapp import PylonsApp
+from pylons.views import LegacyView
+from routes.middleware import RoutesMiddleware
+from repoze.bfg.configuration import Configurator
+from repoze.bfg.exceptions import NotFound
+
+import {{package}}.helpers
+from {{package}}.routing import make_map
+
+def load_environment(global_conf, app_conf):
+    """Configure the Pylons environment via the ``pylons.config``
+    object
+    """
+    # Pylons paths
+    config = PylonsConfig()
+    root = os.path.dirname(os.path.abspath(__file__))
+    paths = dict(root=root,
+                 controllers=os.path.join(root, 'controllers'),
+                 static_files=os.path.join(root, 'public'),
+                 templates=[os.path.join(root, 'templates')])
+
+    # Initialize config with the basic options
+    config.init_app(global_conf, app_conf, package='{{package}}', paths=paths)
+
+    config['routes.map'] = make_map(config)
+    config['pylons.app_globals'] = Globals(config)
+    config['pylons.h'] = {{package}}.helpers
+    {{if template_engine == 'mako'}}
+
+    # Create the Mako TemplateLookup, with the default auto-escaping
+    config['pylons.app_globals'].mako_lookup = TemplateLookup(
+        directories=paths['templates'],
+        error_handler=handle_mako_error,
+        module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
+        input_encoding='utf-8', default_filters=['escape'],
+        imports=['from markupsafe import escape'])
+    {{elif template_engine == 'genshi'}}
+
+    # Create the Genshi TemplateLoader
+    config['pylons.app_globals'].genshi_loader = TemplateLoader(
+        paths['templates'], auto_reload=True)
+    {{elif template_engine == 'jinja2'}}
+
+    # Create the Jinja2 Environment
+    config['pylons.app_globals'].jinja2_env = Environment(loader=ChoiceLoader(
+            [FileSystemLoader(path) for path in paths['templates']]))
+    # Jinja2's unable to request c's attributes without strict_c
+    config['pylons.strict_c'] = True
+    {{endif}}
+
+    # CONFIGURATION OPTIONS HERE (note: all config options will override
+    # any Pylons config options)
+    
+    return config
+
+def make_app(global_conf, **settings):
+    url._push_object(None) # avoid problems during a scan
+    legacy_app = make_legacy_app(global_conf, **settings)
+    config = Configurator(settings=settings)
+    config.begin()
+    legacy_view = LegacyView(legacy_app)
+    config.add_view(context=NotFound, view=legacy_view)
+    config.add_route('new', '/new')
+    config.scan()
+    config.end()
+    url._pop_object()
+    return config.make_wsgi_app()
+
+def make_legacy_app(global_conf, full_stack=True, static_files=True, 
+                    **app_conf):
+    """Create a legacy Pylons WSGI application and return it
+
+    ``global_conf``
+        The inherited configuration for this application. Normally from
+        the [DEFAULT] section of the Paste ini file.
+
+    ``full_stack``
+        Whether or not this application provides a full WSGI stack (by
+        default, meaning it handles its own exceptions and errors).
+        Disable full_stack when this application is "managed" by another
+        WSGI middleware.
+
+    ``static_files``
+        Whether this application serves its own static files; disable
+        when another web server is responsible for serving them.
+
+    ``app_conf``
+        The application's local configuration. Normally specified in the
+        [app:<name>] section of the Paste ini file (where <name>
+        defaults to main).
+    """
+    # Configure the Pylons environment
+    config = load_environment(global_conf, app_conf)
+
+    # The Pylons WSGI app
+    app = PylonsApp(config=config)
+