pylons.wsgiapp
Covered: 216 lines
Missed: 30 lines
Skipped 84 lines
Percent: 87 %

      1

    """WSGI App Creator

      2

      3

    This module is responsible for creating the basic Pylons WSGI

      4

    application (PylonsApp). It's generally assumed that it will be called

      5

    by Paste, though any WSGI server could create and call the WSGI app as

      6

    well.

      7

      8

    """

      9

    import logging

     10

    import sys

     11

     12

    import paste.registry

     13

    import pkg_resources

     14

    from webob.exc import HTTPFound, HTTPNotFound

     15

     16

    import pylons

     17

    import pylons.templating

     18

    from pylons.controllers.util import Request, Response

     19

    from pylons.i18n.translation import _get_translator

     20

    from pylons.util import (AttribSafeContextObj, ContextObj, PylonsContext,

     21

                             class_name_from_module_name)

     22

     23

    __all__ = ['PylonsApp']

     24

     25

    log = logging.getLogger(__name__)

     26

     27

    class PylonsApp(object):

     28

        """Pylons WSGI Application

     29

     30

        This basic WSGI app is provided should a web developer want to

     31

        get access to the most basic Pylons web application environment

     32

        available. By itself, this Pylons web application does little more

     33

        than dispatch to a controller and setup the context object, the

     34

        request object, and the globals object.

     35

     36

        Additional functionality like sessions, and caching can be setup by

     37

        altering the ``environ['pylons.environ_config']`` setting to

     38

        indicate what key the ``session`` and ``cache`` functionality

     39

        should come from.

     40

     41

        Resolving the URL and dispatching can be customized by sub-classing

     42

        or "monkey-patching" this class. Subclassing is the preferred

     43

        approach.

     44

     45

        """

     46

        def __init__(self, config=None, **kwargs):

     47

            """Initialize a base Pylons WSGI application

     48

     49

            The base Pylons WSGI application requires several keywords, the

     50

            package name, and the globals object. If no helpers object is

     51

            provided then h will be None.

     52

     53

            """

     54

            self.config = config = config or pylons.config._current_obj()

     55

            package_name = config['pylons.package']

     56

            self.helpers = config['pylons.h']

     57

            self.globals = config.get('pylons.app_globals')

     58

            self.environ_config = config['pylons.environ_config']

     59

            self.package_name = package_name

     60

            self.request_options = config['pylons.request_options']

     61

            self.response_options = config['pylons.response_options']

     62

            self.controller_classes = {}

     63

            self.log_debug = False

     64

            self.config.setdefault('lang', None)

     65

     66

            # Create the redirect function we'll use and save it

     67

            def redirect_to(url):

     68

                log.debug("Raising redirect to %s", url)

     69

                raise HTTPFound(location=url)

     70

            self.redirect_to = redirect_to

     71

     72

            # Cache some options for use during requests

     73

            self._session_key = self.environ_config.get('session', 'beaker.session')

     74

            self._cache_key = self.environ_config.get('cache', 'beaker.cache')

     75

     76

        def __call__(self, environ, start_response):

     77

            """Setup and handle a web request

     78

     79

            PylonsApp splits its functionality into several methods to

     80

            make it easier to subclass and customize core functionality.

     81

     82

            The methods are called in the following order:

     83

     84

            1. :meth:`~PylonsApp.setup_app_env`

     85

            2. :meth:`~PylonsApp.load_test_env` (Only if operating in

     86

               testing mode)

     87

            3. :meth:`~PylonsApp.resolve`

     88

            4. :meth:`~PylonsApp.dispatch`

     89

     90

            The response from :meth:`~PylonsApp.dispatch` is expected to be

     91

            an iterable (valid :pep:`333` WSGI response), which is then

     92

            sent back as the response.

     93

     94

            """

     95

            # Cache the logging level for the request

     96

            log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()

     97

     98

            self.setup_app_env(environ, start_response)

     99

            if 'paste.testing_variables' in environ:

    100

                self.load_test_env(environ)

    101

                if environ['PATH_INFO'] == '/_test_vars':

    102

                    paste.registry.restorer.save_registry_state(environ)

    103

                    start_response('200 OK', [('Content-type', 'text/plain')])

    104

                    return ['%s' % paste.registry.restorer.get_request_id(environ)]

    105

    106

            controller = self.resolve(environ, start_response)

    107

            response = self.dispatch(controller, environ, start_response)

    108

    109

            if 'paste.testing_variables' in environ and hasattr(response,

    110

                                                                'wsgi_response'):

    111

                environ['paste.testing_variables']['response'] = response

    112

    113

            try:

    114

                if hasattr(response, 'wsgi_response'):

    115

                    # Transform Response objects from legacy Controller

    116

                    if log_debug:

    117

                        log.debug("Transforming legacy Response object into WSGI "

    118

                                  "response")

    119

                    return response(environ, start_response)

    120

                elif response is not None:

    121

                    return response

    122

    123

                raise Exception("No content returned by controller (Did you "

    124

                                "remember to 'return' it?) in: %r" %

    125

                                controller.__name__)

    126

            finally:

    127

                # Help Python collect ram a bit faster by removing the reference

    128

                # cycle that the pylons object causes

    129

                if 'pylons.pylons' in environ:

    130

                    del environ['pylons.pylons']

    131

    132

        def register_globals(self, environ):

    133

            """Registers globals in the environment, called from

    134

            :meth:`~PylonsApp.setup_app_env`

    135

    136

            Override this to control how the Pylons API is setup. Note that

    137

            a custom render function will need to be used if the

    138

            ``pylons.app_globals`` global is not available.

    139

    140

            """

    141

            pylons_obj = environ['pylons.pylons']

    142

    143

            registry = environ['paste.registry']

    144

            registry.register(pylons.response, pylons_obj.response)

    145

            registry.register(pylons.request, pylons_obj.request)

    146

    147

            registry.register(pylons.app_globals, self.globals)

    148

            registry.register(pylons.config, self.config)

    149

            registry.register(pylons.tmpl_context, pylons_obj.tmpl_context)

    150

            registry.register(pylons.translator, pylons_obj.translator)

    151

    152

            if 'session' in pylons_obj.__dict__:

    153

                registry.register(pylons.session, pylons_obj.session)

    154

            if 'cache' in pylons_obj.__dict__:

    155

                registry.register(pylons.cache, pylons_obj.cache)

    156

            elif 'cache' in pylons_obj.app_globals.__dict__:

    157

                registry.register(pylons.cache, pylons_obj.app_globals.cache)

    158

    159

            if 'routes.url' in environ:

    160

                registry.register(pylons.url, environ['routes.url'])

    161

    162

        def setup_app_env(self, environ, start_response):

    163

            """Setup and register all the Pylons objects with the registry

    164

    165

            After creating all the global objects for use in the request,

    166

            :meth:`~PylonsApp.register_globals` is called to register them

    167

            in the environment.

    168

    169

            """

    170

            if self.log_debug:

    171

                log.debug("Setting up Pylons stacked object globals")

    172

    173

    174

            # Setup the basic pylons global objects

    175

            req_options = self.request_options

    176

            req = Request(environ, charset=req_options['charset'],

    177

                          unicode_errors=req_options['errors'],

    178

                          decode_param_names=req_options['decode_param_names'])

    179

            req.language = req_options['language']

    180

    181

            response = Response(

    182

                content_type=self.response_options['content_type'],

    183

                charset=self.response_options['charset'])

    184

            response.headers.update(self.response_options['headers'])

    185

    186

            # Store a copy of the request/response in environ for faster access

    187

            pylons_obj = PylonsContext()

    188

            pylons_obj.config = self.config

    189

            pylons_obj.request = req

    190

            pylons_obj.response = response

    191

            pylons_obj.app_globals = self.globals

    192

            pylons_obj.h = self.helpers

    193

    194

            if 'routes.url' in environ:

    195

                pylons_obj.url = environ['routes.url']

    196

    197

            environ['pylons.pylons'] = pylons_obj

    198

    199

            environ['pylons.environ_config'] = self.environ_config

    200

    201

            # Setup the translator object

    202

            lang = self.config['lang']

    203

            pylons_obj.translator = _get_translator(lang, pylons_config=self.config)

    204

    205

            if self.config['pylons.strict_tmpl_context']:

    206

                tmpl_context = ContextObj()

    207

            else:

    208

                tmpl_context = AttribSafeContextObj()

    209

            pylons_obj.tmpl_context = tmpl_context

    210

    211

            econf = self.config['pylons.environ_config']

    212

            if self._session_key in environ:

    213

                pylons_obj.session = environ[self._session_key]

    214

            if self._cache_key in environ:

    215

                pylons_obj.cache = environ[self._cache_key]

    216

    217

            # Load the globals with the registry if around

    218

            if 'paste.registry' in environ:

    219

                self.register_globals(environ)

    220

    221

        def resolve(self, environ, start_response):

    222

            """Uses dispatching information found in

    223

            ``environ['wsgiorg.routing_args']`` to retrieve a controller

    224

            name and return the controller instance from the appropriate

    225

            controller module.

    226

    227

            Override this to change how the controller name is found and

    228

            returned.

    229

    230

            """

    231

            match = environ['wsgiorg.routing_args'][1]

    232

            environ['pylons.routes_dict'] = match

    233

            controller = match.get('controller')

    234

            if not controller:

    235

                return

    236

    237

            if self.log_debug:

    238

                log.debug("Resolved URL to controller: %r", controller)

    239

            return self.find_controller(controller)

    240

    241

        def find_controller(self, controller):

    242

            """Locates a controller by attempting to import it then grab

    243

            the SomeController instance from the imported module.

    244

    245

            Controller name is assumed to be a module in the controllers

    246

            directory unless it contains a '.' or ':' which is then assumed

    247

            to be a dotted path to the module and name of the controller

    248

            object.

    249

    250

            Override this to change how the controller object is found once

    251

            the URL has been resolved.

    252

    253

            """

    254

            # Check to see if we've cached the class instance for this name

    255

            if controller in self.controller_classes:

    256

                return self.controller_classes[controller]

    257

    258

            # Check to see if its a dotted name

    259

            if '.' in controller or ':' in controller:

    260

                mycontroller = pkg_resources.EntryPoint.parse(

    261

                    'x=%s' % controller).load(False)

    262

                self.controller_classes[controller] = mycontroller

    263

                return mycontroller

    264

    265

            # Pull the controllers class name, import controller

    266

            full_module_name = self.package_name + '.controllers.' \

    267

                + controller.replace('/', '.')

    268

    269

            # Hide the traceback here if the import fails (bad syntax and such)

    270

            __traceback_hide__ = 'before_and_this'

    271

    272

            __import__(full_module_name)

    273

            if hasattr(sys.modules[full_module_name], '__controller__'):

    274

                mycontroller = getattr(sys.modules[full_module_name],

    275

                    sys.modules[full_module_name].__controller__)

    276

            else:

    277

                module_name = controller.split('/')[-1]

    278

                class_name = class_name_from_module_name(module_name) + 'Controller'

    279

                if self.log_debug:

    280

                    log.debug("Found controller, module: '%s', class: '%s'",

    281

                              full_module_name, class_name)

    282

                mycontroller = getattr(sys.modules[full_module_name], class_name)

    283

            self.controller_classes[controller] = mycontroller

    284

            return mycontroller

    285

    286

        def dispatch(self, controller, environ, start_response):

    287

            """Dispatches to a controller, will instantiate the controller

    288

            if necessary.

    289

    290

            Override this to change how the controller dispatch is handled.

    291

    292

            """

    293

            log_debug = self.log_debug

    294

            if not controller:

    295

                if log_debug:

    296

                    log.debug("No controller found, returning 404 HTTP Not Found")

    297

                return HTTPNotFound()(environ, start_response)

    298

    299

            # If it's a class, instantiate it

    300

            if hasattr(controller, '__bases__'):

    301

                if log_debug:

    302

                    log.debug("Controller appears to be a class, instantiating")

    303

                controller = controller()

    304

                controller._pylons_log_debug = log_debug

    305

    306

            # Add a reference to the controller app located

    307

            environ['pylons.controller'] = controller

    308

    309

            # Controller is assumed to handle a WSGI call

    310

            if log_debug:

    311

                log.debug("Calling controller class with WSGI interface")

    312

            return controller(environ, start_response)

    313

    314

        def load_test_env(self, environ):

    315

            """Sets up our Paste testing environment"""

    316

            if self.log_debug:

    317

                log.debug("Setting up paste testing environment variables")

    318

            testenv = environ['paste.testing_variables']

    319

            pylons_obj = environ['pylons.pylons']

    320

            testenv['req'] = pylons_obj.request

    321

            testenv['response'] = pylons_obj.response

    322

            testenv['tmpl_context'] = pylons_obj.tmpl_context

    323

            testenv['app_globals'] = testenv['g'] = pylons_obj.app_globals

    324

            testenv['h'] = self.config['pylons.h']

    325

            testenv['config'] = self.config

    326

            if hasattr(pylons_obj, 'session'):

    327

                testenv['session'] = pylons_obj.session

    328

            if hasattr(pylons_obj, 'cache'):

    329

                testenv['cache'] = pylons_obj.cache

    330