~ubuntu-branches/ubuntu/natty/flup/natty

« back to all changes in this revision

Viewing changes to flup/resolver/objectpath.py

  • Committer: Bazaar Package Importer
  • Author(s): Kai Hendry
  • Date: 2007-09-12 20:22:04 UTC
  • mfrom: (1.2.1 upstream) (4 gutsy)
  • mto: This revision was merged to the branch mainline in revision 5.
  • Revision ID: james.westby@ubuntu.com-20070912202204-fg63etr9vzaf8hea
* New upstream release
* http://www.saddi.com/software/news/archives/58-flup-1.0-released.html
* Added a note in the description that people should probably start thinking
  of moving to modwsgi.org

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (c) 2005 Allan Saddi <allan@saddi.com>
2
 
# All rights reserved.
3
 
#
4
 
# Redistribution and use in source and binary forms, with or without
5
 
# modification, are permitted provided that the following conditions
6
 
# are met:
7
 
# 1. Redistributions of source code must retain the above copyright
8
 
#    notice, this list of conditions and the following disclaimer.
9
 
# 2. Redistributions in binary form must reproduce the above copyright
10
 
#    notice, this list of conditions and the following disclaimer in the
11
 
#    documentation and/or other materials provided with the distribution.
12
 
#
13
 
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14
 
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
 
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16
 
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17
 
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
 
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19
 
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20
 
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21
 
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22
 
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23
 
# SUCH DAMAGE.
24
 
#
25
 
# $Id: objectpath.py 1833 2005-12-17 00:58:10Z asaddi $
26
 
 
27
 
__author__ = 'Allan Saddi <allan@saddi.com>'
28
 
__version__ = '$Revision: 1833 $'
29
 
 
30
 
import re
31
 
 
32
 
from resolver import *
33
 
 
34
 
__all__ = ['ObjectPathResolver', 'expose']
35
 
 
36
 
class NoDefault(object):
37
 
    pass
38
 
 
39
 
class ObjectPathResolver(Resolver):
40
 
    """
41
 
    Inspired by CherryPy <http://www.cherrypy.org/>. :) For an explanation
42
 
    of how this works, see the excellent tutorial at
43
 
    <http://www.cherrypy.org/wiki/CherryPyTutorial>. We support the index and
44
 
    default methods, though the calling convention for the default method
45
 
    is different - we do not pass PATH_INFO as positional arguments. (It
46
 
    is passed through the request/environ as normal.)
47
 
 
48
 
    Also, we explicitly block certain function names. See below. I don't
49
 
    know if theres really any harm in letting those attributes be followed,
50
 
    but I'd rather not take the chance. And unfortunately, this solution
51
 
    is pretty half-baked as well (I'd rather only allow certain object
52
 
    types to be traversed, rather than disallow based on names.) Better
53
 
    than nothing though...
54
 
    """
55
 
    index_page = 'index'
56
 
    default_page = 'default'
57
 
 
58
 
    def __init__(self, root, index=NoDefault, default=NoDefault,
59
 
                 favorIndex=True):
60
 
        """
61
 
        root is the root object of your URL hierarchy. In CherryPy, this
62
 
        would be cpg.root.
63
 
 
64
 
        When the last component of a path has an index method and some
65
 
        object along the path has a default method, favorIndex determines
66
 
        which method is called when the URL has a trailing slash. If
67
 
        True, the index method will be called. Otherwise, the default method.
68
 
        """
69
 
        self.root = root
70
 
        if index is not NoDefault:
71
 
            self.index_page = index
72
 
        if default is not NoDefault:
73
 
            self.default_page = default
74
 
        self._favorIndex = favorIndex
75
 
 
76
 
    # Certain names should be disallowed for safety. If one of your pages
77
 
    # is showing up unexpectedly as a 404, make sure the function name doesn't
78
 
    # begin with one of these prefixes.
79
 
    _disallowed = re.compile(r'''(?:_|im_|func_|tb_|f_|co_).*''')
80
 
 
81
 
    def _exposed(self, obj, redirect):
82
 
        # If redirecting, allow non-exposed objects as well.
83
 
        return callable(obj) and (getattr(obj, 'exposed', False) or redirect)
84
 
 
85
 
    def resolve(self, request, redirect=False):
86
 
        path_info = request.pathInfo.split(';')[0]
87
 
        path_info = path_info.split('/')
88
 
 
89
 
        assert len(path_info) > 0
90
 
        assert not path_info[0]
91
 
 
92
 
        current = self.root
93
 
        current_default = None
94
 
        i = 0
95
 
        for i in range(1, len(path_info)):
96
 
            component = path_info[i]
97
 
 
98
 
            # See if we have an index page (needed for index/default
99
 
            # disambiguation, unfortunately).
100
 
            current_index = None
101
 
            if self.index_page:
102
 
                current_index = getattr(current, self.index_page, None)
103
 
                if not self._exposed(current_index, redirect):
104
 
                    current_index = None
105
 
 
106
 
            if self.default_page:
107
 
                # Remember the last default page we've seen.
108
 
                new_default = getattr(current, self.default_page, None)
109
 
                if self._exposed(new_default, redirect):
110
 
                    current_default = (i - 1, new_default)
111
 
 
112
 
            # Test for trailing slash.
113
 
            if not component and current_index is not None and \
114
 
               (self._favorIndex or current_default is None):
115
 
                # Breaking out of the loop here favors index over default.
116
 
                break
117
 
 
118
 
            # Respect __all__ attribute. (Ok to generalize to all objects?)
119
 
            all = getattr(current, '__all__', None)
120
 
 
121
 
            current = getattr(current, component, None)
122
 
            # Path doesn't exist
123
 
            if current is None or self._disallowed.match(component) or \
124
 
               (all is not None and component not in all and not redirect):
125
 
                # Use path up to latest default page.
126
 
                if current_default is not None:
127
 
                    i, current = current_default
128
 
                    break
129
 
                # No default at all, so we fail.
130
 
                return None
131
 
 
132
 
        func = None
133
 
        if self._exposed(current, redirect): # Exposed?
134
 
            func = current
135
 
        else:
136
 
            # If not, see if it as an exposed index page
137
 
            if self.index_page:
138
 
                index = getattr(current, self.index_page, None)
139
 
                if self._exposed(index, redirect): func = index
140
 
            # How about a default page?
141
 
            if func is None and self.default_page:
142
 
                default = getattr(current, self.default_page, None)
143
 
                if self._exposed(default, redirect): func = default
144
 
            # Lastly, see if we have an ancestor's default page to fall back on.
145
 
            if func is None and current_default is not None:
146
 
                i, func = current_default
147
 
 
148
 
        if func is not None:
149
 
            self._updatePath(request, i)
150
 
 
151
 
        return func
152
 
 
153
 
def expose(func):
154
 
    """Decorator to expose functions."""
155
 
    func.exposed = True
156
 
    return func