diff libervia/web/server/tasks/implicit/task_brython.py @ 1518:eb00d593801d

refactoring: rename `libervia` to `libervia.web` + update imports following backend changes
author Goffi <goffi@goffi.org>
date Fri, 02 Jun 2023 16:49:28 +0200
parents libervia/server/tasks/implicit/task_brython.py@73c848c2f41e
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/web/server/tasks/implicit/task_brython.py	Fri Jun 02 16:49:28 2023 +0200
@@ -0,0 +1,149 @@
+#!/ur/bin/env python3
+
+from ast import literal_eval
+import json
+from pathlib import Path
+import shutil
+from typing import Any, Dict
+
+from libervia.backend.core import exceptions
+from libervia.backend.core.i18n import _
+from libervia.backend.core.log import getLogger
+from libervia.backend.tools.common import utils
+
+from libervia.web.server.classes import Script
+from libervia.web.server.constants import Const as C
+from libervia.web.server.tasks import task
+
+
+log = getLogger(__name__)
+
+
+class Task(task.Task):
+
+    def prepare(self):
+        if "brython" not in self.resource.browser_modules:
+            raise exceptions.CancelError("No brython module found")
+
+        brython_js = self.build_path / "brython.js"
+        if not brython_js.is_file():
+            installed_ver = None
+        else:
+            with brython_js.open() as f:
+                for line in f:
+                    if line.startswith('// implementation ['):
+                        installed_ver = literal_eval(line[18:])[:3]
+                        log.debug(
+                            f"brython v{'.'.join(str(v) for v in installed_ver)} already "
+                            f"installed")
+                        break
+                else:
+                    log.warning(
+                        f"brython file at {brython_js} doesn't has implementation "
+                        f"version"
+                    )
+                    installed_ver = None
+
+        try:
+            import brython
+            try:
+                from brython.__main__ import implementation
+            except ImportError:
+                from brython.version import implementation
+        except ModuleNotFoundError as e:
+            log.error('"brython" module is missing, can\'t use browser code for Brython')
+            raise e
+        ver = [int(v) for v in implementation.split('.')[:3]]
+        if ver != installed_ver:
+            log.info(_("Installing Brython v{version}").format(
+                version='.'.join(str(v) for v in ver)))
+            data_path = Path(brython.__file__).parent / 'data'
+            # shutil has blocking method, but the task is run before we start
+            # the web server, so it's not a big deal
+            shutil.copyfile(data_path / "brython.js", brython_js)
+            shutil.copy(data_path / "brython_stdlib.js", self.build_path)
+        else:
+            log.debug("Brython is already installed")
+
+        self.WATCH_DIRS = []
+        self.set_common_scripts()
+
+    def set_common_scripts(self):
+        for dyn_data in self.resource.browser_modules["brython"]:
+            url_hash = dyn_data['url_hash']
+            import_url = f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}"
+            dyn_data.setdefault('scripts', utils.OrderedSet()).update([
+                Script(src=f"/{C.BUILD_DIR}/brython.js"),
+                Script(src=f"/{C.BUILD_DIR}/brython_stdlib.js"),
+            ])
+            dyn_data.setdefault('template', {})['body_onload'] = self.get_body_onload(
+                extra_path=[import_url])
+            self.WATCH_DIRS.append(dyn_data['path'].resolve())
+
+    def get_body_onload(self, debug=True, cache=True, extra_path=None):
+        on_load_opts: Dict[str, Any]  = {"pythonpath": [f"/{C.BUILD_DIR}"]}
+        if debug:
+            on_load_opts["debug"] = 1
+        if cache:
+            on_load_opts["cache"] = True
+        if extra_path is not None:
+            on_load_opts["pythonpath"].extend(extra_path)
+
+        return f"brython({json.dumps(on_load_opts)})"
+
+    def copy_files(self, files_paths, dest):
+        for p in files_paths:
+            log.debug(f"copying {p}")
+            if p.is_dir():
+                if p.name == '__pycache__':
+                    continue
+                shutil.copytree(p, dest / p.name)
+            else:
+                shutil.copy(p, dest)
+
+    async def on_dir_event(self, host, filepath, flags):
+        self.set_common_scripts()
+        await self.manager.run_task_instance(self)
+
+    def start(self):
+        dyn_path = self.build_path / C.BUILD_DIR_DYN
+        for dyn_data in self.resource.browser_modules["brython"]:
+            url_hash = dyn_data['url_hash']
+            if url_hash is None:
+                # root modules
+                url_prefix = dyn_data.get('url_prefix')
+                if url_prefix is None:
+                    dest = self.build_path
+                    init_dest_url = f"/{C.BUILD_DIR}/__init__.py"
+                else:
+                    dest = self.build_path / url_prefix
+                    dest.mkdir(exist_ok = True)
+                    init_dest_url = f"/{C.BUILD_DIR}/{url_prefix}/__init__.py"
+
+                self.copy_files(dyn_data['path'].glob('*py'), dest)
+
+                init_file = dyn_data['path'] / '__init__.py'
+                if init_file.is_file():
+                    self.resource.dyn_data_common['scripts'].update([
+                        Script(src=f"/{C.BUILD_DIR}/brython.js"),
+                        Script(src=f"/{C.BUILD_DIR}/brython_stdlib.js"),
+                        Script(type='text/python', src=init_dest_url)
+                    ])
+                    self.resource.dyn_data_common.setdefault(
+                        "template", {})['body_onload'] = self.get_body_onload()
+            else:
+                page_dyn_path = dyn_path / url_hash
+                log.debug(f"using dynamic path at {page_dyn_path}")
+                if page_dyn_path.exists():
+                    log.debug("cleaning existing path")
+                    shutil.rmtree(page_dyn_path)
+
+                page_dyn_path.mkdir(parents=True, exist_ok=True)
+                log.debug("copying browser python files")
+                self.copy_files(dyn_data['path'].iterdir(), page_dyn_path)
+
+                script = Script(
+                    type='text/python',
+                    src=f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}/__init__.py"
+                )
+                dyn_data.setdefault('scripts', utils.OrderedSet()).add(script)