ct 0.10.8.114__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. cantools/__init__.py +24 -0
  2. cantools/_db.py +142 -0
  3. cantools/_memcache.py +76 -0
  4. cantools/_pay.py +46 -0
  5. cantools/admin.py +31 -0
  6. cantools/cfg.py +347 -0
  7. cantools/config.py +131 -0
  8. cantools/db/__init__.py +18 -0
  9. cantools/db/admin.py +27 -0
  10. cantools/db/gae/__init__.py +0 -0
  11. cantools/db/gae/model.py +127 -0
  12. cantools/db/gae/properties.py +35 -0
  13. cantools/db/wp.py +99 -0
  14. cantools/geo.py +188 -0
  15. cantools/hooks.py +13 -0
  16. cantools/scripts/__init__.py +0 -0
  17. cantools/scripts/bench.py +167 -0
  18. cantools/scripts/builder.py +272 -0
  19. cantools/scripts/deploy.py +154 -0
  20. cantools/scripts/doc.py +239 -0
  21. cantools/scripts/index.py +226 -0
  22. cantools/scripts/init.py +345 -0
  23. cantools/scripts/migrate.py +593 -0
  24. cantools/scripts/pubsub/__init__.py +28 -0
  25. cantools/scripts/pubsub/actor.py +13 -0
  26. cantools/scripts/pubsub/bots.py +143 -0
  27. cantools/scripts/pubsub/channel.py +85 -0
  28. cantools/scripts/pubsub/ps.py +145 -0
  29. cantools/scripts/pubsub/user.py +51 -0
  30. cantools/scripts/start.py +53 -0
  31. cantools/scripts/util.py +24 -0
  32. cantools/util/__init__.py +78 -0
  33. cantools/util/admin.py +620 -0
  34. cantools/util/data.py +109 -0
  35. cantools/util/media.py +303 -0
  36. cantools/util/package.py +125 -0
  37. cantools/util/system.py +73 -0
  38. cantools/web/__init__.py +9 -0
  39. cantools/web/dez_server/__init__.py +1 -0
  40. cantools/web/dez_server/controller.py +129 -0
  41. cantools/web/dez_server/cron.py +115 -0
  42. cantools/web/dez_server/daemons.py +64 -0
  43. cantools/web/dez_server/mail.py +24 -0
  44. cantools/web/dez_server/response.py +63 -0
  45. cantools/web/dez_server/routes.py +21 -0
  46. cantools/web/dez_server/server.py +229 -0
  47. cantools/web/dez_server/sms.py +12 -0
  48. cantools/web/gae_server.py +68 -0
  49. cantools/web/util.py +552 -0
  50. ct-0.10.8.114.dist-info/LICENSE +9 -0
  51. ct-0.10.8.114.dist-info/METADATA +25 -0
  52. ct-0.10.8.114.dist-info/RECORD +55 -0
  53. ct-0.10.8.114.dist-info/WHEEL +5 -0
  54. ct-0.10.8.114.dist-info/entry_points.txt +10 -0
  55. ct-0.10.8.114.dist-info/top_level.txt +1 -0
@@ -0,0 +1,68 @@
1
+ from google.appengine.runtime.apiproxy_errors import RequestTooLargeError
2
+ from google.appengine.api import mail
3
+ from .util import *
4
+
5
+ envelope = {
6
+ 'plain': "\n%s",
7
+ 'html': "\n\n<html><head></head><body>%s</body></html>"
8
+ }
9
+
10
+ respond = do_respond
11
+ set_env(lambda html : envelope[html and 'html' or 'plain'])
12
+
13
+ # requests
14
+ def fetch(host, path="/", port=80, asjson=False):
15
+ from google.appengine.api.urlfetch import fetch
16
+ raw = fetch("http://%s:%s%s"%(host, port, path)).content
17
+ if asjson:
18
+ return json.loads(raw)
19
+ return raw
20
+
21
+ def post(host, path="/", port=80, data=None, protocol="http", asjson=False):
22
+ from google.appengine.api.urlfetch import fetch
23
+ raw = fetch("%s://%s:%s%s"%(protocol, host, port, path),
24
+ payload=json.dumps(data), method="POST").content
25
+ if asjson:
26
+ return json.loads(raw)
27
+ return raw
28
+
29
+ # file uploads
30
+ def read_file(data_field):
31
+ try:
32
+ return data_field.file.read()
33
+ except RequestTooLargeError:
34
+ fail("The file you are trying to upload is too large. Please submit something under 1MB. Thank you!", html=True, noenc=True)
35
+
36
+ # emails
37
+ def send_mail(to=None, subject=None, body=None, html=None):
38
+ m = config.mailer
39
+ if not m:
40
+ fail("failed to send email -- no MAILER specified in ct.cfg!")
41
+ if config.mailername:
42
+ m = "%s <%s>"%(config.mailername, m)
43
+ mail.send_mail(to=to, sender=m, subject=subject, body=body, html=html)
44
+
45
+ # memcache stuff
46
+ def getmem(key, tojson=True):
47
+ from google.appengine.api import memcache
48
+ result = memcache.get(key)
49
+ if result is None: return None
50
+ return tojson and json.loads(result) or result
51
+
52
+ def setmem(key, val, fromjson=True):
53
+ from google.appengine.api import memcache
54
+ memcache.set(key, fromjson and json.dumps(val) or val)
55
+
56
+ def delmem(key):
57
+ from google.appengine.api import memcache
58
+ if memcache.get(key) is not None:
59
+ memcache.delete(key)
60
+
61
+ def clearmem():
62
+ from google.appengine.api import memcache
63
+ memcache.flush_all()
64
+
65
+ set_getmem(getmem)
66
+ set_setmem(setmem)
67
+ set_delmem(delmem)
68
+ set_clearmem(clearmem)
cantools/web/util.py ADDED
@@ -0,0 +1,552 @@
1
+ import re, os, sys, ast, json, time, threading
2
+ try:
3
+ from urllib.parse import quote, unquote, urlencode # py3
4
+ from urllib.request import urlopen, Request
5
+ except:
6
+ from urllib import quote, unquote, urlencode # py2.7
7
+ from urllib2 import urlopen, Request
8
+ from base64 import b64encode, b64decode
9
+ from dez.http.static import StaticStore
10
+ from cantools import config
11
+ from six import string_types
12
+
13
+ DEBUG = True
14
+
15
+ # memcache stuff -- overwrite with setters
16
+ def getmem(key, tojson=True):
17
+ log("memcache getting: %s"%(key,))
18
+
19
+ def setmem(key, val, fromjson=True):
20
+ log("memcache setting: %s -> %s"%(key, val))
21
+
22
+ def delmem(key):
23
+ log("memcache deleting: %s"%(key,))
24
+
25
+ def clearmem():
26
+ log("memcache clearing")
27
+
28
+ def set_getmem(f):
29
+ global getmem
30
+ getmem = f
31
+
32
+ def set_setmem(f):
33
+ global setmem
34
+ setmem = f
35
+
36
+ def set_delmem(f):
37
+ global delmem
38
+ delmem = f
39
+
40
+ def set_clearmem(f):
41
+ global clearmem
42
+ clearmem = f
43
+
44
+ # logging -- overwrite with setlog if ya want
45
+ def log(*args, **kwargs):
46
+ print(args, kwargs)
47
+
48
+ # encoding, decoding -- may overwrite with setenc/setdec, but not _that_ necessary
49
+ _c = config.scrambler
50
+ _cl = len(_c)
51
+ _chl = int(_cl / 2)
52
+
53
+ def flip(c):
54
+ i = _c.find(c)
55
+ if i == -1:
56
+ return c
57
+ return _c[(i + _chl) % _cl]
58
+
59
+ def scramble(s):
60
+ return "".join([flip(c) for c in s])
61
+
62
+ def enc(data):
63
+ return scramble(b64encode(hasattr(data, "encode") and data.encode() or data).decode())
64
+
65
+ def dec(data):
66
+ return data.startswith("{") and data or b64decode(scramble(data)).decode()
67
+
68
+ # setters (see above)
69
+ def setlog(f):
70
+ global log
71
+ log = f
72
+
73
+ def setenc(f):
74
+ global enc
75
+ enc = f
76
+
77
+ def setdec(f):
78
+ global dec
79
+ dec = f
80
+
81
+ # threading
82
+ localvars = threading.local()
83
+ def local(key, fallback=None):
84
+ return getattr(localvars, key, fallback)
85
+
86
+ # request functions
87
+ def deUnicodeDict(d):
88
+ if not isinstance(d, dict):
89
+ return d
90
+ n = {}
91
+ for v in d:
92
+ n[str(v)] = deUnicodeDict(d[v])
93
+ return n
94
+
95
+ def cgi_dump():
96
+ return local("request_string")
97
+
98
+ def cgi_read():
99
+ return local("read", sys.stdin.read)()
100
+
101
+ def set_read(f):
102
+ localvars.read = f
103
+
104
+ def set_redir(f):
105
+ localvars.redir = f
106
+
107
+ def rdec(data):
108
+ bdata = b64decode(data.encode())
109
+ try: # py2
110
+ return unquote(bdata).decode()
111
+ except: #py3
112
+ return unquote(bdata.decode())
113
+
114
+ def renc(data):
115
+ try: # py2
116
+ return b64encode(quote(data).encode()).decode()
117
+ except: #py3
118
+ return b64encode(quote(data.encode())).decode()
119
+
120
+ def rb64(data, de=False): # depped
121
+ log("[DEPRECATION WARNING] Something just called rb64(), which is depped -- use rec_conv()")
122
+ return rec_conv(data, de)
123
+
124
+ def rec_conv(data, de=False):
125
+ if isinstance(data, bytes):
126
+ try:
127
+ data = data.decode()
128
+ except:
129
+ pass
130
+ if isinstance(data, string_types):
131
+ return (de and rdec or renc)(data)
132
+ elif isinstance(data, dict):
133
+ for k, v in list(data.items()):
134
+ data[k] = rec_conv(v, de)
135
+ elif isinstance(data, list):
136
+ return [rec_conv(d, de) for d in data]
137
+ return data
138
+
139
+ def qs_get(x, y):
140
+ val = localvars.request.getvalue(x, y)
141
+ if val:
142
+ val = unquote(val)
143
+ return val
144
+
145
+ def cgi_load(force=False):
146
+ localvars.request_string = cgi_read()
147
+ data = config.encode and dec(localvars.request_string) or localvars.request_string
148
+ try:
149
+ try:
150
+ jdata = json.loads(data)
151
+ except:
152
+ jdata = ast.literal_eval(data)
153
+ try:
154
+ localvars.request = rec_conv(jdata, True)
155
+ except:
156
+ localvars.request = jdata
157
+ except:
158
+ import cgi
159
+ localvars.request = cgi.FieldStorage()
160
+ setattr(localvars.request, "get", qs_get)
161
+ if not localvars.request:
162
+ if force or config.web.server == "dez":
163
+ localvars.request = {}
164
+ else:
165
+ fail('no request data!')
166
+
167
+ def cgi_get(key, choices=None, required=True, default=None, shield=False, decode=False, base64=False):
168
+ request = local("request")
169
+ val = request.get(key, default)
170
+ if val is None:
171
+ required and fail('no value submitted for required field: "%s" [%s]'%(key, request))
172
+ elif shield:
173
+ ip = local("ip")
174
+ shield = config.web.shield
175
+ if shield(val, ip, fspath=True, count=False):
176
+ log('cgi_get() shield bounced "%s" for "%s"'%(ip, shield.ip(ip)["message"]))
177
+ fail()
178
+ if choices and val not in choices:
179
+ fail('invalid value for "%s": "%s"'%(key, val))
180
+ if base64 and val:
181
+ val = b64decode(unquote(val))
182
+ if decode and val:
183
+ val = unquote(val)
184
+ return val
185
+
186
+ # response functions
187
+ def _send(data):
188
+ send = local("send")
189
+ if send:
190
+ send(data)
191
+ else:
192
+ print(data)
193
+
194
+ def set_send(f):
195
+ localvars.send = f
196
+
197
+ def _close():
198
+ local("close", sys.exit)()
199
+
200
+ def set_close(f):
201
+ localvars.close = f
202
+
203
+ def _pre_close():
204
+ pass
205
+
206
+ def set_pre_close(f):
207
+ global _pre_close
208
+ _pre_close = f
209
+
210
+ def _header(hkey, hval):
211
+ header = local("header")
212
+ if header:
213
+ header(hkey, hval)
214
+ else:
215
+ _send("%s: %s"%(hkey, hval))
216
+
217
+ def set_header(f):
218
+ localvars.header = f
219
+
220
+ def _write(data, exit=True, savename=None):
221
+ if savename:
222
+ setmem(savename, data, False)
223
+ # try:
224
+ # data = data.decode('ascii', 'replace').encode('utf-8')
225
+ # except Exception as e:
226
+ # data = data.encode('utf-8')
227
+ _send(data)
228
+ if exit:
229
+ _pre_close()
230
+ _close()
231
+
232
+ def trysavedresponse(key=None):
233
+ key = key or local("request_string")
234
+ response = getmem(key, False)
235
+ response and _write(response, exit=True)
236
+
237
+ def dez_wrap(resp, failure):
238
+ from cantools.db import seshman
239
+ from rel.errors import AbortBranch
240
+ def f():
241
+ try:
242
+ resp()
243
+ except AbortBranch as e:
244
+ seshman.close()
245
+ raise AbortBranch() # handled in rel
246
+ except SystemExit:
247
+ pass
248
+ except Exception as e:
249
+ failure(e)
250
+ return f
251
+
252
+ def gae_wrap(resp, failure):
253
+ def f():
254
+ try:
255
+ resp()
256
+ except SystemExit:
257
+ pass
258
+ except Exception as e:
259
+ failure(e)
260
+ return f
261
+
262
+ resp_wrap = { "dez": dez_wrap, "gae": gae_wrap }
263
+
264
+ def do_respond(responseFunc, failMsg="failed", failHtml=False, failNoEnc=False, noLoad=False, threaded=False, response=None, autowin=True):
265
+ def resp():
266
+ response and response.set_cbs()
267
+ noLoad or cgi_load()
268
+ responseFunc()
269
+ autowin and succeed()
270
+
271
+ def failure(e):
272
+ fail(data=failMsg, html=failHtml, err=e, noenc=failNoEnc)
273
+
274
+ wrapped_response = resp_wrap[config.web.server](resp, failure)
275
+ if threaded: # dez only!!!
276
+ from rel import thread
277
+ thread(wrapped_response)
278
+ else:
279
+ wrapped_response()
280
+
281
+ def redirect(addr, msg="", noscript=False, exit=True, metas=None):
282
+ a = "<script>"
283
+ if msg:
284
+ a += 'alert("%s"); '%(msg,)
285
+ a += "document.location = '%s';</script>"%(addr,)
286
+ if noscript:
287
+ a += '<noscript>This site requires Javascript to function properly. To enable Javascript in your browser, please follow <a href="http://www.google.com/support/bin/answer.py?answer=23852">these instructions</a>. Thank you, and have a nice day.</noscript>'
288
+ if metas:
289
+ a = "<html><head>%s%s</head><body></body></html>"%(metas, a)
290
+ _header("Content-Type", "text/html")
291
+ _write(_env(True)%(a,), exit)
292
+
293
+ def setcachedefault(shouldCache=True):
294
+ # deprecated -- should set via config.memcache.update("requst", [bool])
295
+ config.memcache.update("request", shouldCache)
296
+
297
+ def _env(html):
298
+ return "%s"
299
+
300
+ def set_env(f):
301
+ global _env
302
+ _env = f
303
+
304
+ def processResponse(data, code):
305
+ if code == "1":
306
+ try:
307
+ data = json.dumps(data)
308
+ except:
309
+ data = json.dumps(rec_conv(data))
310
+ code = "3"
311
+ elif code == "0":
312
+ try:
313
+ json.dumps(data)
314
+ except:
315
+ data = rec_conv(data)
316
+ code = "2"
317
+ return "%s%s"%(code, data)
318
+
319
+ def succeed_sync(func, cb):
320
+ d = {}
321
+ def handle(*a, **k):
322
+ d["a"] = a
323
+ d["k"] = k
324
+ func(handle)
325
+ while True:
326
+ time.sleep(0.01)
327
+ if d["a"] or d["k"]:
328
+ succeed(cb(*d["a"], **d["k"]))
329
+
330
+ def succeed(data="", html=False, noenc=False, savename=None, cache=False):
331
+ if cache or config.memcache.request:
332
+ savename = local("request_string")
333
+ _header("Content-Type", "text/%s"%(html and "html" or "plain"))
334
+ draw = processResponse(data, "1")
335
+ dstring = (config.encode and not noenc) and enc(draw) or draw
336
+ _write(_env(html)%(dstring,), savename=savename)
337
+
338
+ def fail(data="failed", html=False, err=None, noenc=False, exit=True):
339
+ if err:
340
+ # log it
341
+ import traceback
342
+ logdata = "%s --- %s --> %s"%(data, repr(err), traceback.format_exc())
343
+ log(logdata, "error")
344
+ if DEBUG:
345
+ # write it
346
+ data = logdata
347
+ resp = local("response")
348
+ reqstring = local("request_string")
349
+ path = resp and resp.request.url or "can't find path!"
350
+ ip = local("ip") or (resp and resp.ip or "can't find ip!")
351
+ edump = "%s\n\n%s\n\n%s\n\n%s"%(path, ip, reqstring, logdata)
352
+ shield = config.web.shield
353
+ if reqstring and shield(reqstring, ip):
354
+ data = "nabra"
355
+ reason = shield.ip(ip)["message"]
356
+ logline = "%s - IP (%s) banned!"%(reason, ip)
357
+ edump = "%s\n\n%s"%(logline, edump)
358
+ log(logline)
359
+ elif config.web.eflags:
360
+ samples = {
361
+ "traceback": logdata
362
+ }
363
+ if reqstring:
364
+ samples["request"] = reqstring
365
+ for sample in samples:
366
+ for ef in config.web.eflags:
367
+ if ef in samples[sample]:
368
+ reason = '"%s" in %s'%(ef, sample)
369
+ logline = "%s - IP (%s) banned!"%(reason, ip)
370
+ edump = "%s\n\n%s"%(logline, edump)
371
+ shield.suss(ip, reason)
372
+ log(logline)
373
+ if config.web.report:
374
+ from cantools.web import email_admins
375
+ email_admins("error encountered", edump)
376
+ _header("Content-Type", "text/%s"%(html and "html" or "plain"))
377
+ draw = processResponse(data, "0")
378
+ dstring = (config.encode and not noenc) and enc(draw) or draw
379
+ _write(_env(html)%(dstring,), exit)
380
+
381
+ def _headers(headers):
382
+ for k, v in list(headers.items()):
383
+ _header(k, v)
384
+ if config.web.server == "gae":
385
+ _send("")
386
+
387
+ def send_pdf(data, title=None):
388
+ if title:
389
+ _headers({
390
+ "Content-Type": 'application/pdf; name="%s.pdf"'%(title,),
391
+ "Content-Disposition": 'attachment; filename="%s.pdf"'%(title,)
392
+ })
393
+ else:
394
+ _headers({"Content-Type": "application/pdf"})
395
+ _send(data)
396
+ _close()
397
+
398
+ def send_image(data):
399
+ _headers({"Content-Type": "image/png"})
400
+ _send(data)
401
+ _close()
402
+
403
+ FILETYPES = {"pdf": "application/pdf", "img": "image/png", "ico": "image/ico", "html": "text/html"}
404
+
405
+ def send_file(data, file_type=None, detect=False, headers={}):
406
+ if detect:
407
+ import magic
408
+ file_type = data and magic.from_buffer(data, True)
409
+ if file_type:
410
+ headers["Content-Type"] = FILETYPES.get(file_type, file_type)
411
+ _headers(headers)
412
+ _send(data)
413
+ _close()
414
+
415
+ def send_text(data, dtype="html", fname=None, exit=True, headers={}):
416
+ headers["Content-Type"] = "text/%s"%(dtype,)
417
+ if fname:
418
+ headers['Content-Disposition'] = 'attachment; filename="%s.%s"'%(fname, dtype)
419
+ _headers(headers)
420
+ _write(data, exit)
421
+
422
+ def send_xml(data):
423
+ send_text(data, "xml")
424
+
425
+ # misc
426
+ def verify_recaptcha(cresponse, pkey):
427
+ verification_result = urlopen(Request(
428
+ url = "https://www.google.com/recaptcha/api/siteverify",
429
+ data = urlencode({
430
+ 'secret': pkey,
431
+ 'remoteip': os.environ.get('REMOTE_ADDR', os.environ.get('REMOTE_HOST')),
432
+ 'response': cresponse
433
+ }).encode(),
434
+ headers = {
435
+ "Content-type": "application/x-www-form-urlencoded"
436
+ }))
437
+ vdata = verification_result.read().decode()
438
+ verification_result.close()
439
+ if "true" not in vdata:
440
+ fail(vdata)
441
+
442
+ def strip_punctuation(s):
443
+ return "".join([c for c in s if c.isalnum() or c.isspace()])
444
+
445
+ def strip_html(s):
446
+ p = re.compile(r'<.*?>')
447
+ return p.sub("", s)
448
+
449
+ def strip_html_carefully(s, besides=[]):
450
+ i = s.find('<');
451
+ while i != -1:
452
+ j = s.find('>', i)
453
+ if s[i+1:j] in besides or s[i+2:j] in besides:
454
+ i = s.find('<', i+1)
455
+ else:
456
+ s = s[:i] + s[j+1:]
457
+ i = s.find('<')
458
+ s = s.replace("&nbsp;", " ")
459
+ while " " in s:
460
+ s = s.replace(" ", " ")
461
+ return s
462
+
463
+ # media extraction
464
+ ITYPES = [ # from CT.parse.imgTypes[]
465
+ ".png", ".PNG",
466
+ ".jpg", ".JPG",
467
+ ".gif", ".GIF",
468
+ ".img", ".IMG",
469
+ ".bmp", ".BMP",
470
+ "jpeg", "JPEG",
471
+ "webp", "WEBP",
472
+ "avif", "AVIF"
473
+ ]
474
+
475
+ def vid2thumb(url):
476
+ if "youtube.com" in url and "img.youtube.com" not in url:
477
+ if "shorts" in url:
478
+ token = url.split("/").pop()
479
+ else:
480
+ token = url.split("?v=")[1]
481
+ url = "https://img.youtube.com/vi/%s/0.jpg"%(token,)
482
+ elif "tl.fzn.party" in url:
483
+ url = url.replace("/v/", "/img/v/").replace(".mp4", ".jpg")
484
+ if url[-4:] in ITYPES: # fast
485
+ return url
486
+ for itype in ITYPES: # thorough
487
+ if itype in url:
488
+ return url
489
+
490
+ def text2image(parts, full=False):
491
+ name = []
492
+ while parts:
493
+ part = parts.pop(0)
494
+ if part.startswith("https://"):
495
+ part = vid2thumb(part)
496
+ if not part:
497
+ continue
498
+ if part[-4:] in ITYPES: # fast
499
+ break
500
+ for itype in ITYPES: # thorough
501
+ if itype in part:
502
+ break
503
+ name.append(part)
504
+ if full:
505
+ return " ".join(name), part, " ".join(parts)
506
+ return part
507
+
508
+ def text2parts(text):
509
+ if " http" in text:
510
+ name, rest = text.split(" http", 1)
511
+ if " " in rest:
512
+ image, blurb = rest.split(" ", 1)
513
+ image = vid2thumb("http%s"%(image,))
514
+ if name and image and blurb:
515
+ return name, image, blurb
516
+ return text2image(text.split(" "), True)
517
+
518
+ # metaization
519
+ metastore = StaticStore()
520
+ qcache = {}
521
+ METS = """
522
+ <meta property="og:title" content="%s">
523
+ <meta property="twitter:title" content="%s">
524
+ <meta property="og:image" content="%s">
525
+ <meta property="twitter:image" content="%s">
526
+ <meta property="og:description" content="%s">
527
+ <meta property="twitter:description" content="%s">
528
+ <meta property="description" content="%s">
529
+ """
530
+
531
+ def metized(m, markup=None):
532
+ n = m["name"]
533
+ i = m["image"]
534
+ b = m["blurb"]
535
+ mets = METS%(n, n, i, i, b, b, b)
536
+ return markup and markup.replace("<head>",
537
+ "<head>%s"%(mets,)) or mets
538
+
539
+ def metize(mextractor):
540
+ qs = local("request_string") # better key?
541
+ p = local("response").request.url
542
+ mdir = config.mode == "dynamic" and "html" or "html-%s"%(config.mode,)
543
+ fp = "%s%s"%(mdir, p)
544
+ if os.path.isdir(fp):
545
+ fp += "/index.html"
546
+ if p not in qcache:
547
+ qcache[p] = {}
548
+ if qs not in qcache[p]:
549
+ markup = metastore.read(fp)[0].decode()
550
+ metas = mextractor(p, markup)
551
+ qcache[p][qs] = metas and metized(metas, markup) or markup
552
+ send_file(qcache[p][qs])
@@ -0,0 +1,9 @@
1
+ MIT License:
2
+
3
+ Copyright (c) 2011 Civil Action Network
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ Metadata-Version: 2.1
2
+ Name: ct
3
+ Version: 0.10.8.114
4
+ Summary: Modern minimal web framework
5
+ Author: Mario Balibrera
6
+ Author-email: mario.balibrera@gmail.com
7
+ License: MIT License
8
+ Classifier: Development Status :: 5 - Production/Stable
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ License-File: LICENSE
16
+ Requires-Dist: catmail >=0.1.9.1
17
+ Requires-Dist: databae >=0.1.4.6
18
+ Requires-Dist: dez >=0.10.10.37
19
+ Requires-Dist: fyg >=0.1.7.2
20
+ Requires-Dist: rel >=0.4.9.20
21
+ Requires-Dist: requests >=2.3.0
22
+ Requires-Dist: six >=1.12.0
23
+ Requires-Dist: venvr >=0.1.5.3
24
+
25
+ This portable modern web framework is the application-neutral backbone of Civil Action Network. It includes: a pubsub WebSocket server and bot platform; swappable web backends capable of targeting high-concurrency standalone or cloud platforms; a variable-mode application compiler; a broad-spectrum ORM and database migration tools; a built in administrative interface; and a rich modular JavaScript library.