Weeaboo 10__tar.gz

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.
Weeaboo-10/PKG-INFO ADDED
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: Weeaboo
3
+ Version: 10
4
+ Summary: Shared code for websites
5
+ Home-page: https://pypi.org/project/Weeaboo/
6
+ Author: Andrzej Cichocki
7
+ Author-email: 3613868+combatopera@users.noreply.github.com
8
+ Description-Content-Type: text/markdown
9
+
10
+ # Weeaboo
11
+ Shared code for websites
12
+
13
+ ## Commands
14
+
15
+ ### rmexcept
16
+ Delete paths except those provided as args.
17
+
18
+ ### runsite
19
+ Website entrypoint.
Weeaboo-10/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # Weeaboo
2
+ Shared code for websites
3
+
4
+ ## Commands
5
+
6
+ ### rmexcept
7
+ Delete paths except those provided as args.
8
+
9
+ ### runsite
10
+ Website entrypoint.
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: Weeaboo
3
+ Version: 10
4
+ Summary: Shared code for websites
5
+ Home-page: https://pypi.org/project/Weeaboo/
6
+ Author: Andrzej Cichocki
7
+ Author-email: 3613868+combatopera@users.noreply.github.com
8
+ Description-Content-Type: text/markdown
9
+
10
+ # Weeaboo
11
+ Shared code for websites
12
+
13
+ ## Commands
14
+
15
+ ### rmexcept
16
+ Delete paths except those provided as args.
17
+
18
+ ### runsite
19
+ Website entrypoint.
@@ -0,0 +1,16 @@
1
+ README.md
2
+ rmexcept.py
3
+ runsite.py
4
+ setup.cfg
5
+ setup.py
6
+ Weeaboo.egg-info/PKG-INFO
7
+ Weeaboo.egg-info/SOURCES.txt
8
+ Weeaboo.egg-info/dependency_links.txt
9
+ Weeaboo.egg-info/entry_points.txt
10
+ Weeaboo.egg-info/requires.txt
11
+ Weeaboo.egg-info/top_level.txt
12
+ weeaboo/__init__.py
13
+ weeaboo/app.py
14
+ weeaboo/root.arid
15
+ weeaboo/util.py
16
+ weeaboo/wsgi.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ rmexcept = rmexcept:main
3
+ runsite = runsite:main
@@ -0,0 +1,6 @@
1
+ aridity>=50
2
+ diapyr>=22
3
+ Flask>=2.0.3
4
+ lagoon>=22
5
+ mod-wsgi>=4.7.1
6
+ Werkzeug>=2.0.1
@@ -0,0 +1,3 @@
1
+ rmexcept
2
+ runsite
3
+ weeaboo
Weeaboo-10/rmexcept.py ADDED
@@ -0,0 +1,48 @@
1
+ 'Delete paths except those provided as args.'
2
+ from argparse import ArgumentParser
3
+ from pathlib import Path
4
+ from shutil import rmtree
5
+ import os, sys
6
+
7
+ def _log(p):
8
+ print('Delete:', p, file = sys.stderr)
9
+
10
+ def _rmexcept(leaves):
11
+ def filterdirnames():
12
+ for name in dirnames:
13
+ p = Path(dirpath, name)
14
+ if p in ancestors:
15
+ yield name
16
+ elif p not in leaves:
17
+ _log(p)
18
+ rmtree(p)
19
+ if not leaves:
20
+ _log(os.curdir)
21
+ rmtree(Path.cwd())
22
+ return
23
+ ancestors = set()
24
+ for p in leaves:
25
+ while True:
26
+ p = p.parent
27
+ if not p.parts:
28
+ break
29
+ ancestors.add(p)
30
+ leaves = set(leaves)
31
+ for dirpath, dirnames, filenames in os.walk(os.curdir):
32
+ dirnames[:] = filterdirnames()
33
+ for name in filenames:
34
+ p = Path(dirpath, name)
35
+ if p not in leaves:
36
+ _log(p)
37
+ p.unlink()
38
+
39
+ def main():
40
+ parser = ArgumentParser()
41
+ parser.add_argument('--open-molly-guard', action = 'store_true')
42
+ parser.add_argument('leaf', type = Path, nargs = '*')
43
+ args = parser.parse_args()
44
+ assert args.open_molly_guard
45
+ _rmexcept(args.leaf)
46
+
47
+ if '__main__' == __name__:
48
+ main()
Weeaboo-10/runsite.py ADDED
@@ -0,0 +1,18 @@
1
+ 'Website entrypoint.'
2
+ from aridity.config import ConfigCtrl
3
+ from lagoon import mod_wsgi_express
4
+ from pathlib import Path
5
+ import os
6
+
7
+ def main():
8
+ cc = ConfigCtrl()
9
+ cc.load('/site/deploy/LOADME.arid')
10
+ config = cc.node
11
+ stat = Path(config.img.root).stat()
12
+ os.setgid(stat.st_gid)
13
+ os.setuid(stat.st_uid)
14
+ del os.environ['HOME']
15
+ mod_wsgi_express.start_server.__log_to_terminal.__application_type.module[exec]('--include-file', '/site/deploy/httpd.conf', '--document-root', config.htdocs.root, 'weeaboo.wsgi')
16
+
17
+ if '__main__' == __name__:
18
+ main()
Weeaboo-10/setup.cfg ADDED
@@ -0,0 +1,7 @@
1
+ [bdist_wheel]
2
+ universal = 0
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+
Weeaboo-10/setup.py ADDED
@@ -0,0 +1,21 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ def long_description():
4
+ with open('README.md') as f:
5
+ return f.read()
6
+
7
+ setup(
8
+ name = 'Weeaboo',
9
+ version = '10',
10
+ description = 'Shared code for websites',
11
+ long_description = long_description(),
12
+ long_description_content_type = 'text/markdown',
13
+ url = 'https://pypi.org/project/Weeaboo/',
14
+ author = 'Andrzej Cichocki',
15
+ author_email = '3613868+combatopera@users.noreply.github.com',
16
+ packages = find_packages(),
17
+ py_modules = ['runsite', 'rmexcept'],
18
+ install_requires = ['aridity>=50', 'diapyr>=22', 'Flask>=2.0.3', 'lagoon>=22', 'mod-wsgi>=4.7.1', 'Werkzeug>=2.0.1'],
19
+ package_data = {'': ['*.pxd', '*.pyx', '*.pyxbld', '*.arid', '*.aridt']},
20
+ entry_points = {'console_scripts': ['rmexcept=rmexcept:main', 'runsite=runsite:main']},
21
+ )
@@ -0,0 +1,54 @@
1
+ from .util import canon, patch
2
+ from email.message import EmailMessage
3
+ from io import BytesIO
4
+ from werkzeug.datastructures import Headers, MultiDict
5
+ from werkzeug.http import _hop_by_hop_headers
6
+ from werkzeug.wrappers import Request
7
+
8
+ class Application: pass
9
+
10
+ @patch(Headers, classmethod)
11
+ def fromresponse(cls, response):
12
+ headers = cls()
13
+ for k, x in response.getheaders():
14
+ if canon(k) not in _hop_by_hop_headers:
15
+ headers.add(k, x)
16
+ return headers
17
+
18
+ @patch(Headers)
19
+ def contentcharset(headers):
20
+ msg = EmailMessage()
21
+ msg['Content-Type'], = headers.getlist('content-type')
22
+ return msg.get_content_charset()
23
+
24
+ @patch(Headers)
25
+ def safedict(headers):
26
+ canonkeys = set()
27
+ d = {}
28
+ for realk, v in headers:
29
+ canonk = canon(realk)
30
+ assert canonk not in canonkeys
31
+ canonkeys.add(canonk)
32
+ d[realk] = v
33
+ return d
34
+
35
+ @patch(MultiDict)
36
+ def uniqueornone(md, key):
37
+ vals = md.getlist(key)
38
+ if vals:
39
+ val, = vals
40
+ return val
41
+
42
+ @patch(Request)
43
+ def formpeek(request):
44
+ request.get_data() # Cache the stream.
45
+ return request.form
46
+
47
+ @patch(Request)
48
+ def streamorcached(request):
49
+ try:
50
+ cached = request._cached_data
51
+ except AttributeError:
52
+ assert 'form' not in request.__dict__, 'Stream consumed without being cached.'
53
+ return request.stream
54
+ return BytesIO(cached)
@@ -0,0 +1,71 @@
1
+ from . import Application
2
+ from .util import Pipe
3
+ from contextlib import ExitStack
4
+ from flask import Flask, g, request
5
+ from http import HTTPStatus
6
+ from mimetypes import guess_type
7
+ from os.path import normpath, pardir
8
+ from pathlib import Path
9
+ from werkzeug.http import parse_accept_header
10
+ import gzip, logging
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+ def _before():
15
+ path = request.full_path
16
+ if path.find('?') == len(path) - 1:
17
+ path = path[:-1]
18
+ log.debug("%s %s for: %s", request.method, path, request.headers.get('User-Agent'))
19
+ g.exitstack = ExitStack().__enter__()
20
+
21
+ def _teardown(exc):
22
+ try:
23
+ if exc is not None: # Otherwise do it on response close.
24
+ assert not g.exitstack.__exit__(type(exc), exc, exc.__traceback__), 'Cannot suppress exception.'
25
+ except:
26
+ log.exception('Cleanup failed:')
27
+
28
+ class FlaskApplication(Application):
29
+
30
+ def __init__(self, *args, **kwargs):
31
+ self.flask = flask = Flask(*args, **kwargs)
32
+ flask.before_request(_before)
33
+ flask.teardown_request(_teardown)
34
+ class Response(flask.response_class):
35
+ def __init__(self, *args, **kwargs):
36
+ super().__init__(*args, **kwargs)
37
+ self.call_on_close(g.exitstack.close)
38
+ flask.response_class = Response
39
+
40
+ def __call__(self, environ, start_response):
41
+ return self.flask(environ, start_response)
42
+
43
+ def route(self, rule, view_func, **options):
44
+ self.flask.add_url_rule(rule, view_func = view_func, **options)
45
+
46
+ def stream(self, basepath, uri): # XXX: Validate hash in query?
47
+ def response(isgz, ungz):
48
+ def close():
49
+ log.debug('Close file.')
50
+ f.close()
51
+ path = gzpath if isgz else ogpath
52
+ t, enc = guess_type(path)
53
+ assert ('gzip' if isgz else None) == enc
54
+ f = gzip.open(path) if ungz else path.open('rb')
55
+ g.exitstack.callback(close)
56
+ r = self.toresponse(HTTPStatus.OK, None, f, mimetype = t)
57
+ if isgz and not ungz:
58
+ r.headers['Content-Encoding'] = enc
59
+ return r # XXX: Add Cache-Control?
60
+ relpath = Path(normpath(uri))
61
+ assert pardir not in relpath.parts
62
+ ogpath = basepath / relpath
63
+ gzpath = ogpath.with_name(f"{ogpath.name}.gz")
64
+ if parse_accept_header(request.headers.get('Accept-Encoding')).quality('gzip'):
65
+ return response(True, False) if gzpath.exists() else response(False, False)
66
+ return response(False, False) if ogpath.exists() else response(True, True)
67
+
68
+ def toresponse(self, status, headersornone, payload, **kwargs):
69
+ statusline = f"{status.value} {status.phrase}"
70
+ log.debug("Send: %s", statusline)
71
+ return self.flask.response_class(Pipe(payload) if hasattr(payload, 'read') else payload, statusline, headersornone, **kwargs)
@@ -0,0 +1 @@
1
+ applicationcls := $list()
@@ -0,0 +1,28 @@
1
+ from functools import lru_cache
2
+ import logging
3
+
4
+ log = logging.getLogger(__name__)
5
+
6
+ @lru_cache(maxsize = 128)
7
+ def canon(k):
8
+ return k.lower()
9
+
10
+ def patch(cls, deco = lambda x: x):
11
+ return lambda f: setattr(cls, f.__name__, deco(f))
12
+
13
+ class Pipe:
14
+
15
+ bufsize = 16 * 1024
16
+
17
+ def __init__(self, f):
18
+ self.f = f
19
+
20
+ def __iter__(self):
21
+ n = 0
22
+ while True:
23
+ v = self.f.read(self.bufsize)
24
+ if not v:
25
+ break
26
+ yield v
27
+ n += len(v)
28
+ log.debug("Piped: %s", n)
@@ -0,0 +1,17 @@
1
+ from . import Application
2
+ from aridity.config import ConfigCtrl
3
+ from diapyr import DI
4
+ from diapyr.util import singleton
5
+ import logging
6
+
7
+ @singleton
8
+ def application():
9
+ logging.basicConfig(format = "%(levelname)s %(message)s", level = logging.DEBUG)
10
+ cc = ConfigCtrl()
11
+ cc.load('/site/etc/LOADME.arid')
12
+ config = cc.node
13
+ di = DI()
14
+ di.add(config)
15
+ for cls in config.applicationcls:
16
+ di.add(cls)
17
+ return di(Application)