Added a mod_python-style (but better ^^) function multiplexer.
[wrw.git] / wrw / util.py
1 import inspect
2 import req, dispatch, session, form, resp
3
4 def wsgiwrap(callable):
5     def wrapper(env, startreq):
6         return dispatch.handleenv(env, startreq, callable)
7     return wrapper
8
9 def formparams(callable):
10     def wrapper(req):
11         data = form.formdata(req)
12         spec = inspect.getargspec(callable)
13         args = dict(data.items())
14         args["req"] = req
15         if not spec.keywords:
16             for arg in list(args):
17                 if arg not in spec.args:
18                     del args[arg]
19         for i in xrange(len(spec.args) - len(spec.defaults)):
20             if spec.args[i] not in args:
21                 raise resp.httperror(400, "Missing parameter", ("The query parameter `", resp.h.code(spec.args[i]), "' is required but not supplied."))
22         return callable(**args)
23     return wrapper
24
25 def funplex(*funs, **nfuns):
26     dir = {}
27     dir.update(((fun.__name__, fun) for fun in funs))
28     dir.update(nfuns)
29     def handler(req):
30         if req.pathinfo == "":
31             raise resp.redirect(req.uriname + "/")
32         if req.pathinfo[:1] != "/":
33             raise resp.notfound()
34         p = req.pathinfo[1:]
35         if p == "":
36             p = "__index__"
37             bi = 1
38         else:
39             p = p.partition("/")[0]
40             bi = len(p) + 1
41         if p in dir:
42             return dir[p](req.shift(bi))
43         raise resp.notfound()
44     return handler
45
46 def persession(data = None):
47     def dec(callable):
48         def wrapper(req):
49             sess = session.get(req)
50             if callable not in sess:
51                 if data is None:
52                     sess[callable] = callable()
53                 else:
54                     if data not in sess:
55                         sess[data] = data()
56                     sess[callable] = callable(data)
57             return sess[callable].handle(req)
58         return wrapper
59     return dec
60
61 class preiter(object):
62     __slots__ = ["bk", "bki", "_next"]
63     end = object()
64     def __init__(self, real):
65         self.bk = real
66         self.bki = iter(real)
67         self._next = None
68         self.next()
69
70     def __iter__(self):
71         return self
72
73     def next(self):
74         if self._next is self.end:
75             raise StopIteration()
76         ret = self._next
77         try:
78             self._next = next(self.bki)
79         except StopIteration:
80             self._next = self.end
81         return ret
82
83     def close(self):
84         if hasattr(self.bk, "close"):
85             self.bk.close()
86
87 def pregen(callable):
88     def wrapper(*args, **kwargs):
89         return preiter(callable(*args, **kwargs))
90     return wrapper
91
92 class sessiondata(object):
93     @classmethod
94     def get(cls, req, create = True):
95         sess = cls.sessdb().get(req)
96         with sess.lock:
97             try:
98                 return sess[cls]
99             except KeyError:
100                 if not create:
101                     return None
102                 ret = cls(req, sess)
103                 sess[cls] = ret
104                 return ret
105
106     @classmethod
107     def sessdb(cls):
108         return session.default.val
109
110 class autodirty(sessiondata):
111     @classmethod
112     def get(cls, req):
113         ret = super(autodirty, cls).get(req)
114         if "_is_dirty" not in ret.__dict__:
115             ret.__dict__["_is_dirty"] = False
116         return ret
117
118     def sessfrozen(self):
119         self.__dict__["_is_dirty"] = False
120
121     def sessdirty(self):
122         return self._is_dirty
123
124     def __setattr__(self, name, value):
125         super(autodirty, self).__setattr__(name, value)
126         if "_is_dirty" in self.__dict__:
127             self.__dict__["_is_dirty"] = True
128
129     def __delattr__(self, name):
130         super(autodirty, self).__delattr__(name, value)
131         if "_is_dirty" in self.__dict__:
132             self.__dict__["_is_dirty"] = True
133
134 class manudirty(object):
135     def __init__(self, *args, **kwargs):
136         super(manudirty, self).__init__(*args, **kwargs)
137         self.__dirty = False
138
139     def sessfrozen(self):
140         self.__dirty = False
141
142     def sessdirty(self):
143         return self.__dirty
144
145     def dirty(self):
146         self.__dirty = True
147
148 class specslot(object):
149     __slots__ = ["nm", "idx", "dirty"]
150     unbound = object()
151     
152     def __init__(self, nm, idx, dirty):
153         self.nm = nm
154         self.idx = idx
155         self.dirty = dirty
156
157     @staticmethod
158     def slist(ins):
159         # Avoid calling __getattribute__
160         return specdirty.__sslots__.__get__(ins, type(ins))
161
162     def __get__(self, ins, cls):
163         val = self.slist(ins)[self.idx]
164         if val is specslot.unbound:
165             raise AttributeError("specslot %r is unbound" % self.nm)
166         return val
167
168     def __set__(self, ins, val):
169         self.slist(ins)[self.idx] = val
170         if self.dirty:
171             ins.dirty()
172
173     def __delete__(self, ins):
174         self.slist(ins)[self.idx] = specslot.unbound
175         ins.dirty()
176
177 class specclass(type):
178     def __init__(self, name, bases, tdict):
179         super(specclass, self).__init__(name, bases, tdict)
180         sslots = set()
181         dslots = set()
182         for cls in self.__mro__:
183             css = cls.__dict__.get("__saveslots__", ())
184             sslots.update(css)
185             dslots.update(cls.__dict__.get("__dirtyslots__", css))
186         self.__sslots_l__ = list(sslots)
187         self.__sslots_a__ = list(sslots | dslots)
188         for i, slot in enumerate(self.__sslots_a__):
189             setattr(self, slot, specslot(slot, i, slot in dslots))
190
191 class specdirty(sessiondata):
192     __metaclass__ = specclass
193     __slots__ = ["session", "__sslots__", "_is_dirty"]
194     
195     def __specinit__(self):
196         pass
197
198     @staticmethod
199     def __new__(cls, req, sess):
200         self = super(specdirty, cls).__new__(cls)
201         self.session = sess
202         self.__sslots__ = [specslot.unbound] * len(cls.__sslots_a__)
203         self.__specinit__()
204         self._is_dirty = False
205         return self
206
207     def __getnewargs__(self):
208         return (None, self.session)
209
210     def dirty(self):
211         self._is_dirty = True
212
213     def sessfrozen(self):
214         self._is_dirty = False
215
216     def sessdirty(self):
217         return self._is_dirty
218
219     def __getstate__(self):
220         ret = {}
221         for nm, val in zip(type(self).__sslots_a__, specslot.slist(self)):
222             if val is specslot.unbound:
223                 ret[nm] = False, None
224             else:
225                 ret[nm] = True, val
226         return ret
227
228     def __setstate__(self, st):
229         ss = specslot.slist(self)
230         for i, nm in enumerate(type(self).__sslots_a__):
231             bound, val = st.pop(nm, (False, None))
232             if not bound:
233                 ss[i] = specslot.unbound
234             else:
235                 ss[i] = val