Merge branch 'python3' of git.dolda2000.com:/srv/git/r/wrw into python3
[wrw.git] / wrw / util.py
CommitLineData
1864be32 1import inspect, math
2a5a8ce7 2from . import req, dispatch, session, form, resp, proto
b409a338
FT
3
4def wsgiwrap(callable):
5 def wrapper(env, startreq):
7450e2fc 6 return dispatch.handleenv(env, startreq, callable)
0f18b774 7 wrapper.__wrapped__ = callable
b409a338 8 return wrapper
d9979128 9
c2fc67dc
FT
10def stringwrap(charset):
11 def dec(callable):
12 def wrapper(*args, **kwargs):
13 bk = callable(*args, **kwargs)
14 for string in bk:
15 yield string.encode(charset)
16 return wrapper
17 return dec
18
bbdebbab
FT
19def formparams(callable):
20 def wrapper(req):
21 data = form.formdata(req)
22 spec = inspect.getargspec(callable)
23 args = dict(data.items())
24 args["req"] = req
25 if not spec.keywords:
26 for arg in list(args):
27 if arg not in spec.args:
28 del args[arg]
fd7af0a2 29 for i in range(len(spec.args) - len(spec.defaults)):
5a071401
FT
30 if spec.args[i] not in args:
31 raise resp.httperror(400, "Missing parameter", ("The query parameter `", resp.h.code(spec.args[i]), "' is required but not supplied."))
bbdebbab 32 return callable(**args)
0f18b774 33 wrapper.__wrapped__ = callable
bbdebbab
FT
34 return wrapper
35
612eb9f5
FT
36class funplex(object):
37 def __init__(self, *funs, **nfuns):
38 self.dir = {}
39 self.dir.update(((self.unwrap(fun).__name__, fun) for fun in funs))
40 self.dir.update(nfuns)
41
42 @staticmethod
24e514f0
FT
43 def unwrap(fun):
44 while hasattr(fun, "__wrapped__"):
45 fun = fun.__wrapped__
46 return fun
612eb9f5
FT
47
48 def __call__(self, req):
525d7938
FT
49 if req.pathinfo == "":
50 raise resp.redirect(req.uriname + "/")
51 if req.pathinfo[:1] != "/":
52 raise resp.notfound()
53 p = req.pathinfo[1:]
54 if p == "":
55 p = "__index__"
56 bi = 1
57 else:
58 p = p.partition("/")[0]
59 bi = len(p) + 1
612eb9f5
FT
60 if p in self.dir:
61 return self.dir[p](req.shift(bi))
525d7938 62 raise resp.notfound()
612eb9f5
FT
63
64 def add(self, fun):
65 self.dir[self.unwrap(fun).__name__] = fun
66 return fun
67
68 def name(self, name):
69 def dec(fun):
70 self.dir[name] = fun
71 return fun
72 return dec
525d7938 73
d9979128
FT
74def persession(data = None):
75 def dec(callable):
76 def wrapper(req):
77 sess = session.get(req)
78 if callable not in sess:
79 if data is None:
80 sess[callable] = callable()
81 else:
82 if data not in sess:
83 sess[data] = data()
84 sess[callable] = callable(data)
85 return sess[callable].handle(req)
0f18b774 86 wrapper.__wrapped__ = callable
d9979128
FT
87 return wrapper
88 return dec
d1f70c6c 89
77dd732a
FT
90class preiter(object):
91 __slots__ = ["bk", "bki", "_next"]
92 end = object()
93 def __init__(self, real):
94 self.bk = real
95 self.bki = iter(real)
96 self._next = None
a7a09080 97 self.__next__()
77dd732a
FT
98
99 def __iter__(self):
100 return self
101
a7a09080 102 def __next__(self):
77dd732a
FT
103 if self._next is self.end:
104 raise StopIteration()
105 ret = self._next
106 try:
107 self._next = next(self.bki)
108 except StopIteration:
109 self._next = self.end
110 return ret
111
112 def close(self):
113 if hasattr(self.bk, "close"):
114 self.bk.close()
115
116def pregen(callable):
117 def wrapper(*args, **kwargs):
118 return preiter(callable(*args, **kwargs))
0f18b774 119 wrapper.__wrapped__ = callable
77dd732a
FT
120 return wrapper
121
d1f70c6c
FT
122class sessiondata(object):
123 @classmethod
8f911ff6 124 def get(cls, req, create = True):
d1f70c6c
FT
125 sess = cls.sessdb().get(req)
126 with sess.lock:
127 try:
128 return sess[cls]
129 except KeyError:
8f911ff6
FT
130 if not create:
131 return None
5b35322c 132 ret = cls(req, sess)
d1f70c6c
FT
133 sess[cls] = ret
134 return ret
135
136 @classmethod
137 def sessdb(cls):
1f61bf31 138 return session.default.val
d1f70c6c 139
f13b8f5a
FT
140class autodirty(sessiondata):
141 @classmethod
142 def get(cls, req):
d13a1a57 143 ret = super().get(req)
f13b8f5a
FT
144 if "_is_dirty" not in ret.__dict__:
145 ret.__dict__["_is_dirty"] = False
617b21df 146 return ret
f13b8f5a 147
d1f70c6c 148 def sessfrozen(self):
f13b8f5a 149 self.__dict__["_is_dirty"] = False
d1f70c6c
FT
150
151 def sessdirty(self):
f13b8f5a 152 return self._is_dirty
d1f70c6c
FT
153
154 def __setattr__(self, name, value):
a4ad119b 155 super().__setattr__(name, value)
f13b8f5a
FT
156 if "_is_dirty" in self.__dict__:
157 self.__dict__["_is_dirty"] = True
d1f70c6c
FT
158
159 def __delattr__(self, name):
d13a1a57 160 super().__delattr__(name, value)
f13b8f5a
FT
161 if "_is_dirty" in self.__dict__:
162 self.__dict__["_is_dirty"] = True
3b9bc700
FT
163
164class manudirty(object):
165 def __init__(self, *args, **kwargs):
d13a1a57 166 super().__init__(*args, **kwargs)
3b9bc700
FT
167 self.__dirty = False
168
169 def sessfrozen(self):
170 self.__dirty = False
171
172 def sessdirty(self):
173 return self.__dirty
174
175 def dirty(self):
176 self.__dirty = True
df5f7868
FT
177
178class specslot(object):
179 __slots__ = ["nm", "idx", "dirty"]
180 unbound = object()
181
182 def __init__(self, nm, idx, dirty):
183 self.nm = nm
184 self.idx = idx
185 self.dirty = dirty
186
187 @staticmethod
188 def slist(ins):
189 # Avoid calling __getattribute__
190 return specdirty.__sslots__.__get__(ins, type(ins))
191
192 def __get__(self, ins, cls):
193 val = self.slist(ins)[self.idx]
194 if val is specslot.unbound:
195 raise AttributeError("specslot %r is unbound" % self.nm)
196 return val
197
198 def __set__(self, ins, val):
199 self.slist(ins)[self.idx] = val
200 if self.dirty:
201 ins.dirty()
202
203 def __delete__(self, ins):
204 self.slist(ins)[self.idx] = specslot.unbound
205 ins.dirty()
206
207class specclass(type):
208 def __init__(self, name, bases, tdict):
f605aaf2 209 super().__init__(name, bases, tdict)
df5f7868
FT
210 sslots = set()
211 dslots = set()
212 for cls in self.__mro__:
213 css = cls.__dict__.get("__saveslots__", ())
214 sslots.update(css)
215 dslots.update(cls.__dict__.get("__dirtyslots__", css))
216 self.__sslots_l__ = list(sslots)
217 self.__sslots_a__ = list(sslots | dslots)
218 for i, slot in enumerate(self.__sslots_a__):
219 setattr(self, slot, specslot(slot, i, slot in dslots))
220
f605aaf2 221class specdirty(sessiondata, metaclass=specclass):
df5f7868
FT
222 __slots__ = ["session", "__sslots__", "_is_dirty"]
223
224 def __specinit__(self):
225 pass
226
227 @staticmethod
228 def __new__(cls, req, sess):
f605aaf2 229 self = super().__new__(cls)
df5f7868
FT
230 self.session = sess
231 self.__sslots__ = [specslot.unbound] * len(cls.__sslots_a__)
232 self.__specinit__()
233 self._is_dirty = False
234 return self
235
236 def __getnewargs__(self):
237 return (None, self.session)
238
239 def dirty(self):
240 self._is_dirty = True
241
242 def sessfrozen(self):
243 self._is_dirty = False
244
245 def sessdirty(self):
246 return self._is_dirty
247
248 def __getstate__(self):
249 ret = {}
250 for nm, val in zip(type(self).__sslots_a__, specslot.slist(self)):
251 if val is specslot.unbound:
252 ret[nm] = False, None
253 else:
254 ret[nm] = True, val
255 return ret
256
257 def __setstate__(self, st):
258 ss = specslot.slist(self)
259 for i, nm in enumerate(type(self).__sslots_a__):
260 bound, val = st.pop(nm, (False, None))
df5f7868
FT
261 if not bound:
262 ss[i] = specslot.unbound
263 else:
264 ss[i] = val
1864be32
FT
265
266def datecheck(req, mtime):
267 if "If-Modified-Since" in req.ihead:
268 rtime = proto.phttpdate(req.ihead["If-Modified-Since"])
269 if rtime >= math.floor(mtime):
270 raise resp.unmodified()
271 req.ohead["Last-Modified"] = proto.httpdate(mtime)