From 16c0b1e580f2c92baf6882ec4dfa655c267d23b9 Mon Sep 17 00:00:00 2001 From: mar77i Date: Mon, 4 Mar 2024 00:53:01 +0100 Subject: [PATCH] polish structure, update requirements --- hub/app.py | 15 ++++++++++++--- hub/hubapp.py | 40 ++++++++++++++++++++++++---------------- hub/utils.py | 14 +------------- hub/websocket.py | 10 +++++----- requirements.txt | 11 ++++++----- 5 files changed, 48 insertions(+), 42 deletions(-) diff --git a/hub/app.py b/hub/app.py index 2a0a712..22a7cd4 100644 --- a/hub/app.py +++ b/hub/app.py @@ -1,6 +1,8 @@ import socket import sys from argparse import ArgumentParser +from base64 import urlsafe_b64encode +from hashlib import pbkdf2_hmac from itertools import chain from pathlib import Path from secrets import token_urlsafe @@ -23,7 +25,7 @@ class App(FalconApp): self.base_dir = Path(__file__).parents[1] / "webroot" self.env = Environment( loader=FileSystemLoader(self.base_dir), - autoescape=select_autoescape, + autoescape=select_autoescape(), extensions=["hub.utils.StaticTag"], ) self.secret = secret or token_urlsafe(64) @@ -35,8 +37,15 @@ class App(FalconApp): HubApp(self, base_dir) self.add_error_handler(Exception, self.print_exception) - def get_hubapp_by_name(self, name): - return self.hubapps[name] + def scramble(self, value): + if isinstance(value, str): + value = value.encode() + secret = self.secret + if isinstance(secret, str): + secret = secret.encode() + return urlsafe_b64encode( + pbkdf2_hmac("sha512", value, secret, 221100) + ).rstrip(b"=").decode("ascii") async def print_exception(self, req, resp, ex, params): print_exception(*sys.exc_info()) diff --git a/hub/hubapp.py b/hub/hubapp.py index 91c9c54..f3c457c 100644 --- a/hub/hubapp.py +++ b/hub/hubapp.py @@ -2,13 +2,12 @@ from pathlib import Path from falcon.constants import MEDIA_HTML, MEDIA_JS, MEDIA_TEXT from falcon.status_codes import HTTP_OK +from jinja2 import Template from .websocket import WebSocketHub -from .utils import scramble MEDIA_CSS = "text/css" - class StaticFile: media_types_per_suffix = { ".html": MEDIA_HTML, @@ -16,6 +15,7 @@ class StaticFile: ".css": MEDIA_CSS, ".txt": MEDIA_TEXT, } + content: str | None def __init__(self, path): self.path = path @@ -41,17 +41,22 @@ class StaticFile: class StaticTemplateFile(StaticFile): TEMPLATE_SUFFIX = ".j2" + content: Template | None def __init__(self, path, hubapp): super().__init__(path) self.hubapp = hubapp self.context = {"hubapp": self.hubapp} - self.template = hubapp.app.env.get_template( - str(path.relative_to(hubapp.app.env.loader.searchpath[0])) - ) - def get(self): - return self.template.render(self.context) + def get(self) -> str: + mtime = self.path.stat().st_mtime + if mtime != self.mtime: + env = self.hubapp.app.env + self.content = env.get_template( + str(self.path.relative_to(env.loader.searchpath[0])) + ) + self.mtime = mtime + return self.content.render(self.context) @classmethod def get_media_type(cls, path): @@ -83,25 +88,28 @@ class BaseHubApp: or "master" in uri_tail[:start].split(slash) ) - def uri_from(self, path): - if isinstance(path, Path) and path.is_absolute(): - uri_tail = str(path.relative_to(self.base_dir)) + def _uri_tail(self, path, suffix): + if isinstance(path, Path): + if path.is_absolute(): + path = path.relative_to(self.base_dir) + uri_tail = str(path) else: uri_tail = str(path) - suffix = StaticTemplateFile.TEMPLATE_SUFFIX if uri_tail.endswith(suffix): uri_tail = uri_tail[:-len(suffix)] if self.is_master_uri(uri_tail): - uri_tail = scramble(self.app.secret, uri_tail) + return self.app.scramble(uri_tail) elif uri_tail == "index.html": - uri_tail = "" + return "" + return uri_tail + + def uri_from(self, path) -> str | Path: + uri_tail = self._uri_tail(path, StaticTemplateFile.TEMPLATE_SUFFIX) name = self.name if name == "root": name = "" - if name and uri_tail: - name = f"{name}/" - return f"/{name}{uri_tail}" + return f"/{name}{'/' if name and uri_tail else ''}{uri_tail}" def scan_files(self, base_dir): stack = [base_dir.iterdir()] diff --git a/hub/utils.py b/hub/utils.py index ae1fd12..ae448ed 100644 --- a/hub/utils.py +++ b/hub/utils.py @@ -1,5 +1,3 @@ -from base64 import urlsafe_b64encode -from hashlib import pbkdf2_hmac from pathlib import Path from jinja2_simple_tags import StandaloneTag @@ -12,20 +10,10 @@ class StaticTag(StandaloneTag): if not hubapp: hubapp = self.context["hubapp"] elif isinstance(hubapp, str): - hubapp = self.context["hubapp"].app.get_hubapp_by_name(hubapp) + hubapp = self.context["hubapp"].app.hubapps[hubapp] return hubapp.uri_from(Path(filename)) -def scramble(secret, value): - if isinstance(value, str): - value = value.encode() - if isinstance(secret, str): - secret = secret.encode() - return urlsafe_b64encode( - pbkdf2_hmac("sha512", value, secret, 221100) - ).rstrip(b"=").decode("ascii") - - def get_redis_pass(redis_conf): prefix = "requirepass " with open(redis_conf, "rt") as fh: diff --git a/hub/websocket.py b/hub/websocket.py index 3d51116..89f1192 100644 --- a/hub/websocket.py +++ b/hub/websocket.py @@ -13,14 +13,13 @@ from .utils import get_redis_pass class BaseWebSocketHub: - first = True client_ids_sem = asyncio.Semaphore(0) @classmethod - def __class_init(cls, redis): - if not cls.first: + def _class_init(cls, redis): + if not hasattr(cls, "_class_init"): return - cls.first = False + delattr(cls, "_class_init") asyncio.create_task(cls.initialize_client_ids(redis)) @classmethod @@ -30,7 +29,8 @@ class BaseWebSocketHub: def __init__(self): self.redis = StrictRedis(password=get_redis_pass("/etc/redis/redis.conf")) - self.__class_init(self.redis) + if hasattr(BaseWebSocketHub, "_class_init"): + BaseWebSocketHub._class_init(self.redis) def task_done(self): self.task = None diff --git a/requirements.txt b/requirements.txt index a9f2dd9..9646ad0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,23 +1,24 @@ anyio==4.2.0 -black==23.12.1 +async-timeout==4.0.3 +black==24.2.0 click==8.1.7 falcon==3.1.3 h11==0.14.0 httptools==0.6.1 idna==3.6 isort==5.13.2 -Jinja2==3.1.2 +Jinja2==3.1.3 jinja2-simple-tags==0.5.0 MarkupSafe==2.1.3 mypy-extensions==1.0.0 packaging==23.2 pathspec==0.12.1 platformdirs==4.1.0 -python-dotenv==1.0.0 +python-dotenv==1.0.1 PyYAML==6.0.1 -redis==5.0.1 +redis==5.0.2 sniffio==1.3.0 -uvicorn==0.25.0 +uvicorn==0.27.1 uvloop==0.19.0 watchfiles==0.21.0 websockets==12.0 -- 2.45.2