summaryrefslogtreecommitdiff
path: root/repoze/bfg/request.py
blob: e5482848bfb87637e66b0820a6bedba654791c8f (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
from zope.interface import implements
from webob import Request as WebobRequest

from zope.interface.interface import InterfaceClass

from repoze.bfg.interfaces import IRequest
from repoze.bfg.interfaces import IGETRequest
from repoze.bfg.interfaces import IPOSTRequest
from repoze.bfg.interfaces import IPUTRequest
from repoze.bfg.interfaces import IDELETERequest
from repoze.bfg.interfaces import IHEADRequest

from repoze.bfg.threadlocal import manager

def current_request():
    """Return the currently active request or ``None`` if no request
    is currently active.  This is *not* an official API, but it's
    going to live here 'forever' and so can be relied on to exist.

    **This function should be used extremely sparingly** (read: almost
    never), because its usage makes it possible to write code that can
    be neither easily tested nor scripted.  The author of this
    function reserves the right to point and laugh at code which uses
    it inappropriately.  Inappropriate usage is defined as follows:

    - This function should never be called within :term:`view` code.
      View code already has access to the request (it's passed in).

    - This function should never be called in :term:`model` code.
      Model code should never require any access to the request; if
      your model code requires access to a request object, you've
      almost certainly factored something wrong, and you should change
      your code rather than using this function.

    - This function should never be called within application-specific
      forks of third-party library code.  The library you've forked
      almost certainly has nothing to do with repoze.bfg, and making
      it dependent on repoze.bfg (rather than making your repoze.bfg
      application depend upon it) means you're forming a dependency in
      the wrong direction.

    - This function should never be called because it's 'easier' or
      'more elegant' to think about calling it than to pass a request
      through a series of function calls when creating some API
      design.  Your application should instead almost certainly pass
      data derived from the request around rather than relying on
      being able to call this function to obtain the request in places
      that actually have no business knowing about it.  Parameters are
      meant to be passed around as function arguments, not obtained
      from some pseudo-global.  Don't try to 'save typing' or create
      'nicer APIs' by using this function in the place where a request
      is required; this will only lead to sadness later.

    However, this function *is* still useful in very limited
    circumstances.  As a rule of thumb, usage of ``current_request``
    is useful **within code which is meant to eventually be removed**.
    For instance, you may find yourself wanting to deprecate some API
    that expects to be passed a request object in favor of one that
    does not expect to be passed a request object.  But you need to
    keep implementations of the old API working for some period of
    time while you deprecate the older API.  So you write a 'facade'
    implementation of the new API which calls into the code which
    implements the older API.  Since the new API does not require the
    request, your facade implementation doesn't have local access to
    the request when it needs to pass it into the older API
    implementaton.  After some period of time, the older
    implementation code is disused and the hack that uses
    ``current_request`` is removed.  This would be an appropriate
    place to use the ``current_request`` function.

    ``current_request`` retrieves a request object from a thread-local
    stack that is managed by a :term:`Router` object.  Therefore the
    very definition of 'current request' is defined entirely by the
    behavior of a repoze.bfg Router.  Scripts which use
    :mod:`repoze.bfg` machinery but never actually start a WSGI server
    or receive requests via HTTP (such as scripts which use the
    :mod:`repoze.bfg.scripting`` API) will never cause any Router code
    to be executed.  Such scripts should expect this function to
    always return ``None``.
    """
    return manager.get()['request']

def request_factory(environ):
    try:
        method = environ['REQUEST_METHOD']
    except KeyError:
        method = None

    if 'bfg.routes.route' in environ:
        route = environ['bfg.routes.route']
        request_factories = route.request_factories
    else:
        request_factories = DEFAULT_REQUEST_FACTORIES

    try:
        request_factory = request_factories[method]['factory']
    except KeyError:
        request_factory = request_factories[None]['factory']

    return request_factory(environ)

def make_request_ascii(event):
    """ An event handler that causes the request charset to be ASCII;
    used as an INewRequest subscriber so code written before 0.7.0 can
    continue to work without a change"""
    request = event.request
    request.charset = None

def named_request_factories(name=None):
    # We use 'precooked' Request subclasses that correspond to HTTP
    # request methods when returning a request object from
    # ``request_factory`` rather than using ``alsoProvides`` to attach
    # the proper interface to an unsubclassed webob.Request.  This
    # pattern is purely an optimization (e.g. preventing calls to
    # ``alsoProvides`` means the difference between 590 r/s and 690
    # r/s on a MacBook 2GHz).  This method should be never imported
    # directly by user code; it is *not* an API.
    if name is None:
        default_iface = IRequest
        get_iface = IGETRequest
        post_iface = IPOSTRequest
        put_iface = IPUTRequest
        delete_iface = IDELETERequest
        head_iface = IHEADRequest
    else:
        default_iface = InterfaceClass('%s_IRequest' % name)
        get_iface = InterfaceClass('%s_IGETRequest' % name, (default_iface,))
        post_iface = InterfaceClass('%s_IPOSTRequest' % name, (default_iface,))
        put_iface = InterfaceClass('%s_IPUTRequest' % name, (default_iface,))
        delete_iface = InterfaceClass('%s_IDELETERequest' % name,
                                      (default_iface,))
        head_iface = InterfaceClass('%s_IHEADRequest' % name, (default_iface))
        
    class Request(WebobRequest):
        implements(default_iface)
        charset = 'utf-8'

    class GETRequest(WebobRequest):
        implements(get_iface)
        charset = 'utf-8'

    class POSTRequest(WebobRequest):
        implements(post_iface)
        charset = 'utf-8'
    
    class PUTRequest(WebobRequest):
        implements(put_iface)
        charset = 'utf-8'

    class DELETERequest(WebobRequest):
        implements(delete_iface)
        charset = 'utf-8'

    class HEADRequest(WebobRequest):
        implements(head_iface)
        charset = 'utf-8'

    factories = {
        IRequest:{'interface':default_iface, 'factory':Request},
        IGETRequest:{'interface':get_iface, 'factory':GETRequest},
        IPOSTRequest:{'interface':post_iface, 'factory':POSTRequest},
        IPUTRequest:{'interface':put_iface, 'factory':PUTRequest},
        IDELETERequest:{'interface':delete_iface, 'factory':DELETERequest},
        IHEADRequest:{'interface':head_iface, 'factory':HEADRequest},
        None:{'interface':default_iface, 'factory':Request},
        'GET':{'interface':get_iface, 'factory':GETRequest},
        'POST':{'interface':post_iface, 'factory':POSTRequest},
        'PUT':{'interface':put_iface, 'factory':PUTRequest},
        'DELETE':{'interface':delete_iface, 'factory':DELETERequest},
        'HEAD':{'interface':head_iface, 'factory':HEADRequest},
        }

    return factories

DEFAULT_REQUEST_FACTORIES = named_request_factories()