summaryrefslogtreecommitdiff
path: root/repoze/bfg/urldispatch.py
blob: 4c3bc2ed365d5b57c7f239be911466d025d2d5af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
import re

from zope.component import queryUtility

from zope.deprecation import deprecated 

from zope.interface import implements
from zope.interface import alsoProvides
from zope.interface import classProvides

from routes import Mapper
from routes import request_config
from routes import url_for

from repoze.bfg.interfaces import IContextNotFound
from repoze.bfg.interfaces import IContextURL
from repoze.bfg.interfaces import IRoutesContext
from repoze.bfg.interfaces import IRoutesContextFactory
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import ITraverserFactory

_marker = ()

class DefaultRoutesContext(object):
    implements(IRoutesContext)
    def __init__(self, **kw):
        self.__dict__.update(kw)

RoutesContext = DefaultRoutesContext
deprecated('RoutesContext',
           "('from repoze.bfg.urldispatch import RoutesContext' is now "
           "deprecated; instead use 'from repoze.bfg.urldispatch import "
           "DefaultRoutesContext')",
           )

class RoutesContextNotFound(object):
    implements(IContextNotFound)
    def __init__(self, msg):
        self.msg = msg

_notfound = RoutesContextNotFound(
    'Routes context cannot be found and no fallback "get_root"')

class RoutesRootFactory(Mapper):
    """ The ``RoutesRootFactory`` is a wrapper for the root factory
    callable passed in to the repoze.bfg ``Router`` at initialization
    time.  When it is instantiated, it wraps the root factory of an
    application in such a way that the `Routes
    <http://routes.groovie.org/index.html>`_ engine has the 'first
    crack' at resolving the current request URL to a repoze.bfg view.
    Any view that claims it is 'for' the interface
    ``repoze.bfg.interfaces.IRoutesContext`` will be called if its
    name matches the Routes route ``name`` name for the match.  It
    will be passed a context object that has attributes that are
    present as Routes match arguments dictionary keys.  If no Routes
    route matches the current request, the 'fallback' get_root is
    called."""
    decorate_context = True
    def __init__(self, get_root=None, **kw):
        self.get_root = get_root
        kw['controller_scan'] = None
        kw['always_scan'] = False
        kw['directory'] = None
        kw['explicit'] = True
        Mapper.__init__(self, **kw)
        self._regs_created = False
        context_factory = queryUtility(IRoutesContextFactory,
                                       default=DefaultRoutesContext)
        if IRoutesContext.implementedBy(context_factory):
            self.decorate_context = False
        self.default_context_factory = context_factory

    def has_routes(self):
        return bool(self.matchlist)

    def connect(self, *arg, **kw):
        # we need to deal with our custom attributes specially :-(
        factory = None
        if '_factory' in kw:
            factory = kw.pop('_factory')
        result = Mapper.connect(self, *arg, **kw)
        self.matchlist[-1]._factory = factory
        return result

    def __call__(self, environ):
        if not self._regs_created:
            self.create_regs([])
            self._regs_created = True
        path = environ.get('PATH_INFO', '/')
        self.environ = environ # sets the thread local
        match = self.routematch(path)
        if match:
            args, route = match
        else:
            args = None
        if isinstance(args, dict): # might be an empty dict
            args = args.copy()
            routepath = route.routepath
            config = request_config()
            config.mapper = self
            config.mapper_dict = args
            config.host = environ.get('HTTP_HOST', environ['SERVER_NAME'])
            config.protocol = environ['wsgi.url_scheme']
            config.redirect = None
            kw = {}
            for k, v in args.items():
                # Routes "helpfully" converts default parameter names
                # into Unicode; these can't be used as attr names
                if k.__class__ is unicode:
                    k = k.encode('utf-8')
                kw[k] = v
            factory = route._factory
            if factory is None:
                context = self.default_context_factory(**kw)
                if self.decorate_context:
                    alsoProvides(context, IRoutesContext)
            else:
                context = factory(**kw)
                alsoProvides(context, IRoutesContext)
            environ['wsgiorg.routing_args'] = ((), kw)
            environ['bfg.route'] = route
            return context

        if self.get_root is None:
            return _notfound

        return self.get_root(environ)

class RoutesModelTraverser(object):
    classProvides(ITraverserFactory)
    implements(ITraverser)
    def __init__(self, context):
        self.context = context

    def __call__(self, environ):
        route = environ['bfg.route']
        match = environ['wsgiorg.routing_args'][1]

        subpath = match.get('subpath', [])
        if subpath:
            subpath = filter(None, subpath.split('/'))

        if 'path_info' in match:
            # this is stolen from routes.middleware; if the route map
            # has a *path_info capture, use it to influence the path
            # info and script_name of the generated environment
            oldpath = environ['PATH_INFO']
            newpath = match['path_info'] or ''
            environ['PATH_INFO'] = newpath
            if not environ['PATH_INFO'].startswith('/'):
                environ['PATH_INFO'] = '/' + environ['PATH_INFO']
            pattern = r'^(.*?)/' + re.escape(newpath) + '$'
            environ['SCRIPT_NAME'] += re.sub(pattern, r'\1', oldpath)
            if environ['SCRIPT_NAME'].endswith('/'):
                environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1]

        return self.context, route.name, subpath, None, self.context, None

class RoutesContextURL(object):
    """ The IContextURL adapter used to generate URLs for a context
    object obtained via Routes URL dispatch.  This implementation
    juses the ``url_for`` Routes API to generate a URL based on
    ``environ['wsgiorg.routing_args']``.  Routes context objects,
    unlike traversal-based context objects, cannot have a virtual root
    that differs from its physical root; furthermore, the physical
    root of a Routes context is always itself, so the ``virtual_root``
    function returns the context of this adapter unconditionally."""
    implements(IContextURL)
    def __init__(self, context, request):
        self.context = context
        self.request = request

    def virtual_root(self):
        return self.context 

    def __call__(self):
        return url_for(**self.request.environ['wsgiorg.routing_args'][1])