summaryrefslogtreecommitdiff
path: root/repoze/bfg/urldispatch.py
blob: f1470cd251772c88de90efd90749814299593922 (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
from zope.interface import implements
from zope.interface import alsoProvides

from routes import Mapper
from routes import request_config

from repoze.bfg.interfaces import IRoutesContext
from repoze.bfg.interfaces import IContextNotFound

from zope.deferredimport import deprecated
from zope.deprecation import deprecated as deprecated2

_marker = ()

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

deprecated2('RoutesMapper',
            'Usage of the ``RoutesMapper`` class is deprecated.  As of '
            'repoze.bfg 0.6.3, you should use the ``<route.. >`` ZCML '
            'directive instead of manually creating a RoutesMapper.')

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

class RoutesMapper(object):
    """ The ``RoutesMapper`` is a wrapper for the ``get_root``
    callable passed in to the repoze.bfg ``Router`` at initialization
    time.  When it is instantiated, it wraps the get_root 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 'controller' name for the match.  It
    will be passed a context object that has attributes that match the
    Routes match arguments dictionary keys.  If no Routes route
    matches the current request, the 'fallback' get_root is called.

    .. warning:: This class is deprecated.  As of :mod:`repoze.bfg`
       0.6.3, you should use the ``<route.. >`` ZCML directive instead
       of manually creating a RoutesMapper.  See :ref:`urldispatch_chapter`
       for more information.
    """
    def __init__(self, get_root):
        self.get_root = get_root
        self.mapper = Mapper(controller_scan=None, directory=None,
                             explicit=True, always_scan=False)
        self.mapper.explicit = True
        self._regs_created = False

    def __call__(self, environ):
        if not self._regs_created:
            self.mapper.create_regs([])
            self._regs_created = True
        path = environ.get('PATH_INFO', '/')
        self.mapper.environ = environ
        args = self.mapper.match(path)
        if args:
            context_factory = args.get('context_factory', _marker)
            if context_factory is _marker:
                context_factory = DefaultRoutesContext
            else:
                args = args.copy()
                del args['context_factory']
            config = request_config()
            config.mapper = self.mapper
            config.mapper_dict = args
            config.host = environ.get('HTTP_HOST', environ['SERVER_NAME'])
            config.protocol = environ['wsgi.url_scheme']
            config.redirect = None
            context = context_factory(**args)
            alsoProvides(context, IRoutesContext)
            return context

        # fall back to original get_root
        return self.get_root(environ)

    def connect(self, *arg, **kw):
        """ Add a route to the Routes mapper associated with this
        request. This method accepts the same arguments as a Routes
        *Mapper* object.  One differences exists: if the
        ``context_factory`` is passed in with a value as a keyword
        argument, this callable will be called when a model object
        representing the ``context`` for the request needs to be
        constructed.  It will be called with the (all-keyword)
        arguments supplied by the Routes mapper's ``match`` method for
        this route, and should return an instance of a class.  If
        ``context_factory`` is not supplied in this way for a route, a
        default context factory (the ``DefaultRoutesContext`` class)
        will be used.  The interface
        ``repoze.bfg.interfaces.IRoutesContext`` will always be tacked
        on to the context instance in addition to whatever interfaces
        the context instance already supplies.
        """
        
        self.mapper.connect(*arg, **kw)

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

class RoutesRootFactory(Mapper):
    """ The ``RoutesRootFactory`` is a wrapper for the ``get_root``
    callable passed in to the repoze.bfg ``Router`` at initialization
    time.  When it is instantiated, it wraps the get_root 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 ``view_name`` name for the match and any
    of the interfaces named in ``_provides``.  It will be
    passed a context object that has attributes that match the Routes
    match arguments dictionary keys.  If no Routes route matches the
    current request, the 'fallback' get_root is called."""
    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

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

    def connect(self, *arg, **kw):
        # we need to deal with our custom attributes specially :-(
        factory = None
        provides = ()
        if '_provides' in kw:
            provides = kw.pop('_provides')
        if '_factory' in kw:
            factory = kw.pop('_factory')
        result = Mapper.connect(self, *arg, **kw)
        self.matchlist[-1]._factory = factory
        self.matchlist[-1]._provides = provides
        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 args:
            args = args.copy()
            routepath = route.routepath
            factory = route._factory
            if not factory:
                factory = DefaultRoutesContext
            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
            context = factory(**kw)
            provides = route._provides
            for iface in provides:
                alsoProvides(context, iface)
            alsoProvides(context, IRoutesContext)
            return context

        if self.get_root is None:
            # no fallback get_root
            return RoutesContextNotFound(
                'Routes context cannot be found and no fallback "get_root"')

        # fall back to original get_root
        return self.get_root(environ)