~j5-dev/+junk/cherrypy3-3.2.0rc1

« back to all changes in this revision

Viewing changes to cherrypy/__init__.py

  • Committer: steveh at sjsoft
  • Date: 2010-07-01 13:07:15 UTC
  • Revision ID: steveh@sjsoft.com-20100701130715-w56oim8346qzqlka
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""CherryPy is a pythonic, object-oriented HTTP framework.
 
2
 
 
3
 
 
4
CherryPy consists of not one, but four separate API layers.
 
5
 
 
6
The APPLICATION LAYER is the simplest. CherryPy applications are written as
 
7
a tree of classes and methods, where each branch in the tree corresponds to
 
8
a branch in the URL path. Each method is a 'page handler', which receives
 
9
GET and POST params as keyword arguments, and returns or yields the (HTML)
 
10
body of the response. The special method name 'index' is used for paths
 
11
that end in a slash, and the special method name 'default' is used to
 
12
handle multiple paths via a single handler. This layer also includes:
 
13
 
 
14
 * the 'exposed' attribute (and cherrypy.expose)
 
15
 * cherrypy.quickstart()
 
16
 * _cp_config attributes
 
17
 * cherrypy.tools (including cherrypy.session)
 
18
 * cherrypy.url()
 
19
 
 
20
The ENVIRONMENT LAYER is used by developers at all levels. It provides
 
21
information about the current request and response, plus the application
 
22
and server environment, via a (default) set of top-level objects:
 
23
 
 
24
 * cherrypy.request
 
25
 * cherrypy.response
 
26
 * cherrypy.engine
 
27
 * cherrypy.server
 
28
 * cherrypy.tree
 
29
 * cherrypy.config
 
30
 * cherrypy.thread_data
 
31
 * cherrypy.log
 
32
 * cherrypy.HTTPError, NotFound, and HTTPRedirect
 
33
 * cherrypy.lib
 
34
 
 
35
The EXTENSION LAYER allows advanced users to construct and share their own
 
36
plugins. It consists of:
 
37
 
 
38
 * Hook API
 
39
 * Tool API
 
40
 * Toolbox API
 
41
 * Dispatch API
 
42
 * Config Namespace API
 
43
 
 
44
Finally, there is the CORE LAYER, which uses the core API's to construct
 
45
the default components which are available at higher layers. You can think
 
46
of the default components as the 'reference implementation' for CherryPy.
 
47
Megaframeworks (and advanced users) may replace the default components
 
48
with customized or extended components. The core API's are:
 
49
 
 
50
 * Application API
 
51
 * Engine API
 
52
 * Request API
 
53
 * Server API
 
54
 * WSGI API
 
55
 
 
56
These API's are described in the CherryPy specification:
 
57
http://www.cherrypy.org/wiki/CherryPySpec
 
58
"""
 
59
 
 
60
__version__ = "3.2.0rc1"
 
61
 
 
62
from urlparse import urljoin as _urljoin
 
63
from urllib import urlencode as _urlencode
 
64
 
 
65
 
 
66
class _AttributeDocstrings(type):
 
67
    """Metaclass for declaring docstrings for class attributes."""
 
68
    # The full docstring for this type is down in the __init__ method so
 
69
    # that it doesn't show up in help() for every consumer class.
 
70
    
 
71
    def __init__(cls, name, bases, dct):
 
72
        '''Metaclass for declaring docstrings for class attributes.
 
73
        
 
74
        Base Python doesn't provide any syntax for setting docstrings on
 
75
        'data attributes' (non-callables). This metaclass allows class
 
76
        definitions to follow the declaration of a data attribute with
 
77
        a docstring for that attribute; the attribute docstring will be
 
78
        popped from the class dict and folded into the class docstring.
 
79
        
 
80
        The naming convention for attribute docstrings is:
 
81
            <attrname> + "__doc".
 
82
        For example:
 
83
        
 
84
            class Thing(object):
 
85
                """A thing and its properties."""
 
86
                
 
87
                __metaclass__ = cherrypy._AttributeDocstrings
 
88
                
 
89
                height = 50
 
90
                height__doc = """The height of the Thing in inches."""
 
91
        
 
92
        In which case, help(Thing) starts like this:
 
93
        
 
94
            >>> help(mod.Thing)
 
95
            Help on class Thing in module pkg.mod:
 
96
            
 
97
            class Thing(__builtin__.object)
 
98
             |  A thing and its properties.
 
99
             |  
 
100
             |  height [= 50]:
 
101
             |      The height of the Thing in inches.
 
102
             | 
 
103
        
 
104
        The benefits of this approach over hand-edited class docstrings:
 
105
            1. Places the docstring nearer to the attribute declaration.
 
106
            2. Makes attribute docs more uniform ("name (default): doc").
 
107
            3. Reduces mismatches of attribute _names_ between
 
108
               the declaration and the documentation.
 
109
            4. Reduces mismatches of attribute default _values_ between
 
110
               the declaration and the documentation.
 
111
        
 
112
        The benefits of a metaclass approach over other approaches:
 
113
            1. Simpler ("less magic") than interface-based solutions.
 
114
            2. __metaclass__ can be specified at the module global level
 
115
               for classic classes.
 
116
        
 
117
        For various formatting reasons, you should write multiline docs
 
118
        with a leading newline and not a trailing one:
 
119
            
 
120
            response__doc = """
 
121
            The response object for the current thread. In the main thread,
 
122
            and any threads which are not HTTP requests, this is None."""
 
123
        
 
124
        The type of the attribute is intentionally not included, because
 
125
        that's not How Python Works. Quack.
 
126
        '''
 
127
        
 
128
        newdoc = [cls.__doc__ or ""]
 
129
        
 
130
        dctkeys = dct.keys()
 
131
        dctkeys.sort()
 
132
        for name in dctkeys:
 
133
            if name.endswith("__doc"):
 
134
                # Remove the magic doc attribute.
 
135
                if hasattr(cls, name):
 
136
                    delattr(cls, name)
 
137
                
 
138
                # Make a uniformly-indented docstring from it.
 
139
                val = '\n'.join(['    ' + line.strip()
 
140
                                 for line in dct[name].split('\n')])
 
141
                
 
142
                # Get the default value.
 
143
                attrname = name[:-5]
 
144
                try:
 
145
                    attrval = getattr(cls, attrname)
 
146
                except AttributeError:
 
147
                    attrval = "missing"
 
148
                
 
149
                # Add the complete attribute docstring to our list.
 
150
                newdoc.append("%s [= %r]:\n%s" % (attrname, attrval, val))
 
151
        
 
152
        # Add our list of new docstrings to the class docstring.
 
153
        cls.__doc__ = "\n\n".join(newdoc)
 
154
 
 
155
 
 
156
from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect
 
157
from cherrypy._cperror import NotFound, CherryPyException, TimeoutError
 
158
 
 
159
from cherrypy import _cpdispatch as dispatch
 
160
 
 
161
from cherrypy import _cptools
 
162
tools = _cptools.default_toolbox
 
163
Tool = _cptools.Tool
 
164
 
 
165
from cherrypy import _cprequest
 
166
from cherrypy.lib import httputil as _httputil
 
167
 
 
168
from cherrypy import _cptree
 
169
tree = _cptree.Tree()
 
170
from cherrypy._cptree import Application
 
171
from cherrypy import _cpwsgi as wsgi
 
172
 
 
173
from cherrypy import process
 
174
try:
 
175
    from cherrypy.process import win32
 
176
    engine = win32.Win32Bus()
 
177
    engine.console_control_handler = win32.ConsoleCtrlHandler(engine)
 
178
    del win32
 
179
except ImportError:
 
180
    engine = process.bus
 
181
 
 
182
 
 
183
# Timeout monitor
 
184
class _TimeoutMonitor(process.plugins.Monitor):
 
185
    
 
186
    def __init__(self, bus):
 
187
        self.servings = []
 
188
        process.plugins.Monitor.__init__(self, bus, self.run)
 
189
    
 
190
    def acquire(self):
 
191
        self.servings.append((serving.request, serving.response))
 
192
    
 
193
    def release(self):
 
194
        try:
 
195
            self.servings.remove((serving.request, serving.response))
 
196
        except ValueError:
 
197
            pass
 
198
    
 
199
    def run(self):
 
200
        """Check timeout on all responses. (Internal)"""
 
201
        for req, resp in self.servings:
 
202
            resp.check_timeout()
 
203
engine.timeout_monitor = _TimeoutMonitor(engine)
 
204
engine.timeout_monitor.subscribe()
 
205
 
 
206
engine.autoreload = process.plugins.Autoreloader(engine)
 
207
engine.autoreload.subscribe()
 
208
 
 
209
engine.thread_manager = process.plugins.ThreadManager(engine)
 
210
engine.thread_manager.subscribe()
 
211
 
 
212
engine.signal_handler = process.plugins.SignalHandler(engine)
 
213
 
 
214
 
 
215
from cherrypy import _cpserver
 
216
server = _cpserver.Server()
 
217
server.subscribe()
 
218
 
 
219
 
 
220
def quickstart(root=None, script_name="", config=None):
 
221
    """Mount the given root, start the builtin server (and engine), then block.
 
222
    
 
223
    root: an instance of a "controller class" (a collection of page handler
 
224
        methods) which represents the root of the application.
 
225
    script_name: a string containing the "mount point" of the application.
 
226
        This should start with a slash, and be the path portion of the URL
 
227
        at which to mount the given root. For example, if root.index() will
 
228
        handle requests to "http://www.example.com:8080/dept/app1/", then
 
229
        the script_name argument would be "/dept/app1".
 
230
        
 
231
        It MUST NOT end in a slash. If the script_name refers to the root
 
232
        of the URI, it MUST be an empty string (not "/").
 
233
    config: a file or dict containing application config. If this contains
 
234
        a [global] section, those entries will be used in the global
 
235
        (site-wide) config.
 
236
    """
 
237
    if config:
 
238
        _global_conf_alias.update(config)
 
239
    
 
240
    tree.mount(root, script_name, config)
 
241
    
 
242
    if hasattr(engine, "signal_handler"):
 
243
        engine.signal_handler.subscribe()
 
244
    if hasattr(engine, "console_control_handler"):
 
245
        engine.console_control_handler.subscribe()
 
246
    
 
247
    engine.start()
 
248
    engine.block()
 
249
 
 
250
 
 
251
try:
 
252
    from threading import local as _local
 
253
except ImportError:
 
254
    from cherrypy._cpthreadinglocal import local as _local
 
255
 
 
256
class _Serving(_local):
 
257
    """An interface for registering request and response objects.
 
258
    
 
259
    Rather than have a separate "thread local" object for the request and
 
260
    the response, this class works as a single threadlocal container for
 
261
    both objects (and any others which developers wish to define). In this
 
262
    way, we can easily dump those objects when we stop/start a new HTTP
 
263
    conversation, yet still refer to them as module-level globals in a
 
264
    thread-safe way.
 
265
    """
 
266
    
 
267
    __metaclass__ = _AttributeDocstrings
 
268
    
 
269
    request = _cprequest.Request(_httputil.Host("127.0.0.1", 80),
 
270
                                 _httputil.Host("127.0.0.1", 1111))
 
271
    request__doc = """
 
272
    The request object for the current thread. In the main thread,
 
273
    and any threads which are not receiving HTTP requests, this is None."""
 
274
    
 
275
    response = _cprequest.Response()
 
276
    response__doc = """
 
277
    The response object for the current thread. In the main thread,
 
278
    and any threads which are not receiving HTTP requests, this is None."""
 
279
    
 
280
    def load(self, request, response):
 
281
        self.request = request
 
282
        self.response = response
 
283
    
 
284
    def clear(self):
 
285
        """Remove all attributes of self."""
 
286
        self.__dict__.clear()
 
287
 
 
288
serving = _Serving()
 
289
 
 
290
 
 
291
class _ThreadLocalProxy(object):
 
292
    
 
293
    __slots__ = ['__attrname__', '__dict__']
 
294
    
 
295
    def __init__(self, attrname):
 
296
        self.__attrname__ = attrname
 
297
    
 
298
    def __getattr__(self, name):
 
299
        child = getattr(serving, self.__attrname__)
 
300
        return getattr(child, name)
 
301
    
 
302
    def __setattr__(self, name, value):
 
303
        if name in ("__attrname__", ):
 
304
            object.__setattr__(self, name, value)
 
305
        else:
 
306
            child = getattr(serving, self.__attrname__)
 
307
            setattr(child, name, value)
 
308
    
 
309
    def __delattr__(self, name):
 
310
        child = getattr(serving, self.__attrname__)
 
311
        delattr(child, name)
 
312
    
 
313
    def _get_dict(self):
 
314
        child = getattr(serving, self.__attrname__)
 
315
        d = child.__class__.__dict__.copy()
 
316
        d.update(child.__dict__)
 
317
        return d
 
318
    __dict__ = property(_get_dict)
 
319
    
 
320
    def __getitem__(self, key):
 
321
        child = getattr(serving, self.__attrname__)
 
322
        return child[key]
 
323
    
 
324
    def __setitem__(self, key, value):
 
325
        child = getattr(serving, self.__attrname__)
 
326
        child[key] = value
 
327
    
 
328
    def __delitem__(self, key):
 
329
        child = getattr(serving, self.__attrname__)
 
330
        del child[key]
 
331
    
 
332
    def __contains__(self, key):
 
333
        child = getattr(serving, self.__attrname__)
 
334
        return key in child
 
335
    
 
336
    def __len__(self):
 
337
        child = getattr(serving, self.__attrname__)
 
338
        return len(child)
 
339
    
 
340
    def __nonzero__(self):
 
341
        child = getattr(serving, self.__attrname__)
 
342
        return bool(child)
 
343
 
 
344
 
 
345
# Create request and response object (the same objects will be used
 
346
#   throughout the entire life of the webserver, but will redirect
 
347
#   to the "serving" object)
 
348
request = _ThreadLocalProxy('request')
 
349
response = _ThreadLocalProxy('response')
 
350
 
 
351
# Create thread_data object as a thread-specific all-purpose storage
 
352
class _ThreadData(_local):
 
353
    """A container for thread-specific data."""
 
354
thread_data = _ThreadData()
 
355
 
 
356
 
 
357
# Monkeypatch pydoc to allow help() to go through the threadlocal proxy.
 
358
# Jan 2007: no Googleable examples of anyone else replacing pydoc.resolve.
 
359
# The only other way would be to change what is returned from type(request)
 
360
# and that's not possible in pure Python (you'd have to fake ob_type).
 
361
def _cherrypy_pydoc_resolve(thing, forceload=0):
 
362
    """Given an object or a path to an object, get the object and its name."""
 
363
    if isinstance(thing, _ThreadLocalProxy):
 
364
        thing = getattr(serving, thing.__attrname__)
 
365
    return _pydoc._builtin_resolve(thing, forceload)
 
366
 
 
367
try:
 
368
    import pydoc as _pydoc
 
369
    _pydoc._builtin_resolve = _pydoc.resolve
 
370
    _pydoc.resolve = _cherrypy_pydoc_resolve
 
371
except ImportError:
 
372
    pass
 
373
 
 
374
 
 
375
from cherrypy import _cplogging
 
376
 
 
377
class _GlobalLogManager(_cplogging.LogManager):
 
378
    
 
379
    def __call__(self, *args, **kwargs):
 
380
        # Do NOT use try/except here. See http://www.cherrypy.org/ticket/945
 
381
        if hasattr(request, 'app') and hasattr(request.app, 'log'):
 
382
            log = request.app.log
 
383
        else:
 
384
            log = self
 
385
        return log.error(*args, **kwargs)
 
386
    
 
387
    def access(self):
 
388
        try:
 
389
            return request.app.log.access()
 
390
        except AttributeError:
 
391
            return _cplogging.LogManager.access(self)
 
392
 
 
393
 
 
394
log = _GlobalLogManager()
 
395
# Set a default screen handler on the global log.
 
396
log.screen = True
 
397
log.error_file = ''
 
398
# Using an access file makes CP about 10% slower. Leave off by default.
 
399
log.access_file = ''
 
400
 
 
401
def _buslog(msg, level):
 
402
    log.error(msg, 'ENGINE', severity=level)
 
403
engine.subscribe('log', _buslog)
 
404
 
 
405
#                       Helper functions for CP apps                       #
 
406
 
 
407
 
 
408
def expose(func=None, alias=None):
 
409
    """Expose the function, optionally providing an alias or set of aliases."""
 
410
    def expose_(func):
 
411
        func.exposed = True
 
412
        if alias is not None:
 
413
            if isinstance(alias, basestring):
 
414
                parents[alias.replace(".", "_")] = func
 
415
            else:
 
416
                for a in alias:
 
417
                    parents[a.replace(".", "_")] = func
 
418
        return func
 
419
    
 
420
    import sys, types
 
421
    if isinstance(func, (types.FunctionType, types.MethodType)):
 
422
        if alias is None:
 
423
            # @expose
 
424
            func.exposed = True
 
425
            return func
 
426
        else:
 
427
            # func = expose(func, alias)
 
428
            parents = sys._getframe(1).f_locals
 
429
            return expose_(func)
 
430
    elif func is None:
 
431
        if alias is None:
 
432
            # @expose()
 
433
            parents = sys._getframe(1).f_locals
 
434
            return expose_
 
435
        else:
 
436
            # @expose(alias="alias") or
 
437
            # @expose(alias=["alias1", "alias2"])
 
438
            parents = sys._getframe(1).f_locals
 
439
            return expose_
 
440
    else:
 
441
        # @expose("alias") or
 
442
        # @expose(["alias1", "alias2"])
 
443
        parents = sys._getframe(1).f_locals
 
444
        alias = func
 
445
        return expose_
 
446
 
 
447
 
 
448
def url(path="", qs="", script_name=None, base=None, relative=None):
 
449
    """Create an absolute URL for the given path.
 
450
    
 
451
    If 'path' starts with a slash ('/'), this will return
 
452
        (base + script_name + path + qs).
 
453
    If it does not start with a slash, this returns
 
454
        (base + script_name [+ request.path_info] + path + qs).
 
455
    
 
456
    If script_name is None, cherrypy.request will be used
 
457
    to find a script_name, if available.
 
458
    
 
459
    If base is None, cherrypy.request.base will be used (if available).
 
460
    Note that you can use cherrypy.tools.proxy to change this.
 
461
    
 
462
    Finally, note that this function can be used to obtain an absolute URL
 
463
    for the current request path (minus the querystring) by passing no args.
 
464
    If you call url(qs=cherrypy.request.query_string), you should get the
 
465
    original browser URL (assuming no internal redirections).
 
466
    
 
467
    If relative is None or not provided, request.app.relative_urls will
 
468
    be used (if available, else False). If False, the output will be an
 
469
    absolute URL (including the scheme, host, vhost, and script_name).
 
470
    If True, the output will instead be a URL that is relative to the
 
471
    current request path, perhaps including '..' atoms. If relative is
 
472
    the string 'server', the output will instead be a URL that is
 
473
    relative to the server root; i.e., it will start with a slash.
 
474
    """
 
475
    if isinstance(qs, (tuple, list, dict)):
 
476
        qs = _urlencode(qs)
 
477
    if qs:
 
478
        qs = '?' + qs
 
479
    
 
480
    if request.app:
 
481
        if not path.startswith("/"):
 
482
            # Append/remove trailing slash from path_info as needed
 
483
            # (this is to support mistyped URL's without redirecting;
 
484
            # if you want to redirect, use tools.trailing_slash).
 
485
            pi = request.path_info
 
486
            if request.is_index is True:
 
487
                if not pi.endswith('/'):
 
488
                    pi = pi + '/'
 
489
            elif request.is_index is False:
 
490
                if pi.endswith('/') and pi != '/':
 
491
                    pi = pi[:-1]
 
492
            
 
493
            if path == "":
 
494
                path = pi
 
495
            else:
 
496
                path = _urljoin(pi, path)
 
497
        
 
498
        if script_name is None:
 
499
            script_name = request.script_name
 
500
        if base is None:
 
501
            base = request.base
 
502
        
 
503
        newurl = base + script_name + path + qs
 
504
    else:
 
505
        # No request.app (we're being called outside a request).
 
506
        # We'll have to guess the base from server.* attributes.
 
507
        # This will produce very different results from the above
 
508
        # if you're using vhosts or tools.proxy.
 
509
        if base is None:
 
510
            base = server.base()
 
511
        
 
512
        path = (script_name or "") + path
 
513
        newurl = base + path + qs
 
514
    
 
515
    if './' in newurl:
 
516
        # Normalize the URL by removing ./ and ../
 
517
        atoms = []
 
518
        for atom in newurl.split('/'):
 
519
            if atom == '.':
 
520
                pass
 
521
            elif atom == '..':
 
522
                atoms.pop()
 
523
            else:
 
524
                atoms.append(atom)
 
525
        newurl = '/'.join(atoms)
 
526
    
 
527
    # At this point, we should have a fully-qualified absolute URL.
 
528
    
 
529
    if relative is None:
 
530
        relative = getattr(request.app, "relative_urls", False)
 
531
    
 
532
    # See http://www.ietf.org/rfc/rfc2396.txt
 
533
    if relative == 'server':
 
534
        # "A relative reference beginning with a single slash character is
 
535
        # termed an absolute-path reference, as defined by <abs_path>..."
 
536
        # This is also sometimes called "server-relative".
 
537
        newurl = '/' + '/'.join(newurl.split('/', 3)[3:])
 
538
    elif relative:
 
539
        # "A relative reference that does not begin with a scheme name
 
540
        # or a slash character is termed a relative-path reference."
 
541
        old = url().split('/')[:-1]
 
542
        new = newurl.split('/')
 
543
        while old and new:
 
544
            a, b = old[0], new[0]
 
545
            if a != b:
 
546
                break
 
547
            old.pop(0)
 
548
            new.pop(0)
 
549
        new = (['..'] * len(old)) + new
 
550
        newurl = '/'.join(new)
 
551
    
 
552
    return newurl
 
553
 
 
554
 
 
555
# import _cpconfig last so it can reference other top-level objects
 
556
from cherrypy import _cpconfig
 
557
# Use _global_conf_alias so quickstart can use 'config' as an arg
 
558
# without shadowing cherrypy.config.
 
559
config = _global_conf_alias = _cpconfig.Config()
 
560
config.defaults = {
 
561
    'tools.log_tracebacks.on': True,
 
562
    'tools.log_headers.on': True,
 
563
    'tools.trailing_slash.on': True,
 
564
    'tools.encode.on': True
 
565
    }
 
566
config.namespaces["log"] = lambda k, v: setattr(log, k, v)
 
567
config.namespaces["checker"] = lambda k, v: setattr(checker, k, v)
 
568
# Must reset to get our defaults applied.
 
569
config.reset()
 
570
 
 
571
from cherrypy import _cpchecker
 
572
checker = _cpchecker.Checker()
 
573
engine.subscribe('start', checker)