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 +19 -0
- Weeaboo-10/README.md +10 -0
- Weeaboo-10/Weeaboo.egg-info/PKG-INFO +19 -0
- Weeaboo-10/Weeaboo.egg-info/SOURCES.txt +16 -0
- Weeaboo-10/Weeaboo.egg-info/dependency_links.txt +1 -0
- Weeaboo-10/Weeaboo.egg-info/entry_points.txt +3 -0
- Weeaboo-10/Weeaboo.egg-info/requires.txt +6 -0
- Weeaboo-10/Weeaboo.egg-info/top_level.txt +3 -0
- Weeaboo-10/rmexcept.py +48 -0
- Weeaboo-10/runsite.py +18 -0
- Weeaboo-10/setup.cfg +7 -0
- Weeaboo-10/setup.py +21 -0
- Weeaboo-10/weeaboo/__init__.py +54 -0
- Weeaboo-10/weeaboo/app.py +71 -0
- Weeaboo-10/weeaboo/root.arid +1 -0
- Weeaboo-10/weeaboo/util.py +28 -0
- Weeaboo-10/weeaboo/wsgi.py +17 -0
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,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 @@
|
|
|
1
|
+
|
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
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)
|