changeset 1253:6d49fae517ba

pages: browser metadata + root `_browser`: - the `_browser` directory can now be put in root of a site `pages` directory, it will then include modules for the whole website - in `_browser` directories (notably the root one), a `browser_meta.json` file can be put to specify settings for a browser engine - pathlib.Path is now used LiberviaRootResource.site_path - introduced some type hints - task_brython copy modules in root `_browser` to build_path root. - minimal python version is now 3.7 due to type hints
author Goffi <goffi@goffi.org>
date Wed, 29 Apr 2020 17:34:53 +0200
parents 80a92eb82b7f
children 780dbc2f4853
files libervia/server/constants.py libervia/server/pages.py libervia/server/server.py libervia/server/tasks/implicit/task_brython.py setup.py
diffstat 5 files changed, 97 insertions(+), 49 deletions(-) [+]
line wrap: on
line diff
--- a/libervia/server/constants.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/libervia/server/constants.py	Wed Apr 29 17:34:53 2020 +0200
@@ -60,6 +60,7 @@
     ## Libervia pages ##
     PAGES_META_FILE = "page_meta.py"
     PAGES_BROWSER_DIR = "_browser"
+    PAGES_BROWSER_META_FILE = "browser_meta.json"
     PAGES_ACCESS_NONE = (
         "none"
     )  #  no access to this page (using its path will return a 404 error)
--- a/libervia/server/pages.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/libervia/server/pages.py	Wed Apr 29 17:34:53 2020 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-
 # Libervia: a Salut à Toi frontend
 # Copyright (C) 2011-2020 Jérôme Poisson <goffi@goffi.org>
 
@@ -17,14 +16,18 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 import uuid
 import os.path
 import urllib.request, urllib.parse, urllib.error
 import time
 import hashlib
 import copy
+import json
+from pathlib import Path
 from functools import reduce
-from pathlib import Path
+from typing import Optional, List
 
 from twisted.web import server
 from twisted.web import resource as web_resource
@@ -36,6 +39,7 @@
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.tools.common import date_utils
+from sat.tools.common import utils
 from sat.core.log import getLogger
 from sat_frontends.bridge.bridge_frontend import BridgeException
 
@@ -123,7 +127,7 @@
         @param host(Libervia): the running instance of Libervia
         @param vhost_root(web_resource.Resource): root resource of the virtual host which
             handle this page.
-        @param root_dir(unicode): aboslute file path of the page
+        @param root_dir(Path): aboslute file path of the page
         @param url(unicode): relative URL to the page
             this URL may not be valid, as pages may require path arguments
         @param name(unicode, None): if not None, a unique name to identify the page
@@ -260,7 +264,7 @@
     def createPage(host, meta_path, vhost_root, url_elts, replace_on_conflict=False):
         """Create a LiberviaPage instance
 
-        @param meta_path(unicode): path to the page_meta.py file
+        @param meta_path(Path): path to the page_meta.py file
         @param vhost_root(resource.Resource): root resource of the virtual host
         @param url_elts(list[unicode]): list of path element from root site to this page
         @param replace_on_conflict(bool): same as for [LiberviaPage]
@@ -268,7 +272,7 @@
             - page_data: dict containing data of the page
             - libervia_page: created resource
         """
-        dir_path = os.path.dirname(meta_path)
+        dir_path = meta_path.parent
         page_data = {"__name__": ".".join(["page"] + url_elts)}
         # we don't want to force the presence of __init__.py
         # so we use execfile instead of import.
@@ -295,6 +299,47 @@
             replace_on_conflict=replace_on_conflict
         )
 
+    @staticmethod
+    def createBrowserData(
+        vhost_root,
+        resource: Optional(LiberviaPage),
+        browser_path: Path,
+        path_elts: Optional(List[str]),
+        engine: str = "brython"
+    ) -> None:
+        """create and store data for browser dynamic code"""
+        dyn_data = {
+            "path": browser_path,
+            "url_hash": (
+                hashlib.sha256('/'.join(path_elts).encode()).hexdigest()
+                if path_elts is not None else None
+            ),
+        }
+        browser_meta_path = browser_path / C.PAGES_BROWSER_META_FILE
+        if browser_meta_path.is_file():
+            with browser_meta_path.open() as f:
+                browser_meta = json.load(f)
+            utils.recursive_update(vhost_root.browser_modules, browser_meta)
+            if resource is not None:
+                utils.recursive_update(resource.dyn_data, browser_meta)
+
+        init_path = browser_path / '__init__.py'
+        if init_path.is_file():
+            vhost_root.browser_modules.setdefault(
+                engine, []).append(dyn_data)
+            if resource is not None:
+                resource.dyn_data[engine] = dyn_data
+        elif path_elts is None:
+            try:
+                next(browser_path.glob('*.py'))
+            except StopIteration:
+                # no python file, nothing for Brython
+                pass
+            else:
+                vhost_root.browser_modules.setdefault(
+                    engine, []).append(dyn_data)
+
+
     @classmethod
     def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None,
         _extra_pages=False):
@@ -302,7 +347,7 @@
 
         @param host(Libervia): Libervia instance
         @param vhost_root(LiberviaRootResource): root of this VirtualHost
-        @param root_path(unicode, None): use this root path instead of vhost_root's one
+        @param root_path(Path, None): use this root path instead of vhost_root's one
             Used to add default site pages to external sites
         @param _parent(Resource, None): _parent page. Do not set yourself, this is for
             internal use only
@@ -315,23 +360,27 @@
             _path = []
         if _parent is None:
             if root_path is None:
-                root_dir = os.path.join(vhost_root.site_path, C.PAGES_DIR)
+                root_dir = vhost_root.site_path / C.PAGES_DIR
             else:
-                root_dir = os.path.join(root_path, C.PAGES_DIR)
+                root_dir = root_path / C.PAGES_DIR
                 _extra_pages = True
             _parent = vhost_root
+            root_browser_path = root_dir / C.PAGES_BROWSER_DIR
+            if root_browser_path.is_dir():
+                cls.createBrowserData(vhost_root, None, root_browser_path, None)
         else:
             root_dir = _parent.root_dir
+
         for d in os.listdir(root_dir):
-            dir_path = os.path.join(root_dir, d)
-            if not os.path.isdir(dir_path):
+            dir_path = root_dir / d
+            if not dir_path.is_dir():
                 continue
             if _extra_pages and d in _parent.children:
                 log.debug(_("[{host_name}] {path} is already present, ignoring it")
                     .format(host_name=vhost_root.host_name, path='/'.join(_path+[d])))
                 continue
-            meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
-            if os.path.isfile(meta_path):
+            meta_path = dir_path / C.PAGES_META_FILE
+            if meta_path.is_file():
                 new_path = _path + [d]
                 try:
                     page_data, resource = cls.createPage(
@@ -342,7 +391,7 @@
                         continue
                     else:
                         raise e
-                _parent.putChild(d.encode('utf-8'), resource)
+                _parent.putChild(str(d).encode(), resource)
                 log_msg = ("[{host_name}] Added /{path} page".format(
                     host_name=vhost_root.host_name,
                     path="[…]/".join(new_path)))
@@ -374,17 +423,9 @@
                     host, vhost_root, _parent=resource, _path=new_path,
                     _extra_pages=_extra_pages)
                 # now we check if there is some code for browser
-                browser_path = Path(dir_path) / C.PAGES_BROWSER_DIR
+                browser_path = dir_path / C.PAGES_BROWSER_DIR
                 if browser_path.is_dir():
-                    # for now we only handle Brython
-                    dyn_data = {
-                        "path": browser_path,
-                        "url_hash": hashlib.sha256(
-                            '/'.join(new_path).encode()).hexdigest(),
-                    }
-                    vhost_root.browser_modules.setdefault(
-                        "brython", []).append(dyn_data)
-                    resource.dyn_data['brython'] = dyn_data
+                    cls.createBrowserData(vhost_root, resource, browser_path, new_path)
 
     @classmethod
     def onFileChange(cls, host, file_path, flags, site_root, site_path):
--- a/libervia/server/server.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/libervia/server/server.py	Wed Apr 29 17:34:53 2020 +0200
@@ -183,7 +183,7 @@
         self.host = host
         self.host_name = host_name
         self.site_name = site_name
-        self.site_path = site_path
+        self.site_path = Path(site_path)
         self.named_pages = {}
         self.browser_modules = {}
         self.uri_callbacks = {}
@@ -786,9 +786,9 @@
 
         # we create virtual hosts and import Libervia pages into them
         self.vhost_root = vhost.NameVirtualHost()
-        default_site_path = os.path.abspath(os.path.dirname(libervia.__file__))
+        default_site_path = Path(libervia.__file__).parent.resolve()
         # self.sat_root is official Libervia site
-        root_path = os.path.join(default_site_path, C.TEMPLATE_STATIC_DIR)
+        root_path = default_site_path / C.TEMPLATE_STATIC_DIR
         self.sat_root = default_root = LiberviaRootResource(
             host=self, host_name='', site_name='',
             site_path=default_site_path, path=root_path)
--- a/libervia/server/tasks/implicit/task_brython.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/libervia/server/tasks/implicit/task_brython.py	Wed Apr 29 17:34:53 2020 +0200
@@ -7,7 +7,6 @@
 from sat.core.i18n import _
 from sat.core.log import getLogger
 from sat.core import exceptions
-from sat.tools.common.template import safe
 from libervia.server.constants import Const as C
 from libervia.server.tasks import task
 
@@ -16,7 +15,6 @@
 
 
 class Task(task.Task):
-    DOC_DIRS_DEFAULT = ('doc', 'docs')
 
     def prepare(self):
         if "brython" not in self.resource.browser_modules:
@@ -64,7 +62,8 @@
             import_url = f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}"
             on_load_opts = {
                 "debug": 1,
-                "pythonpath": [import_url],
+                "cache": True,
+                "pythonpath": [f"/{C.BUILD_DIR}", import_url],
             }
             dyn_data['template'] = {
                 "scripts": [{"src": f"/{C.BUILD_DIR}/brython.js"}],
@@ -72,28 +71,35 @@
             }
             self.WATCH_DIRS.append(dyn_data['path'].resolve())
 
+    def copyFiles(self, files_paths, dest):
+        for p in files_paths:
+            log.debug(f"copying {p}")
+            if p.is_dir():
+                shutil.copytree(p, dest)
+            else:
+                shutil.copy(p, dest)
+
     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']
-            page_dyn_path = dyn_path / url_hash
-            if page_dyn_path.exists():
-                log.debug("cleaning existing path")
-                shutil.rmtree(page_dyn_path)
+            if url_hash is None:
+                # root modules
+                self.copyFiles(dyn_data['path'].glob('*py'), self.build_path)
+            else:
+                page_dyn_path = dyn_path / url_hash
+                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")
-            for p in dyn_data['path'].iterdir():
-                log.debug(f"copying {p}")
-                if p.is_dir():
-                    shutil.copytree(p, page_dyn_path)
-                else:
-                    shutil.copy(p, page_dyn_path)
+                page_dyn_path.mkdir(parents=True, exist_ok=True)
+                log.debug("copying browser python files")
+                self.copyFiles(dyn_data['path'].iterdir(), page_dyn_path)
 
-            script = {
-                'type': 'text/python',
-                'src': f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}/__init__.py"
-            }
-            scripts = dyn_data['template']['scripts']
-            if script not in scripts:
-                scripts.append(script)
+                script = {
+                    'type': 'text/python',
+                    'src': f"/{C.BUILD_DIR}/{C.BUILD_DIR_DYN}/{url_hash}/__init__.py"
+                }
+                scripts = dyn_data['template']['scripts']
+                if script not in scripts:
+                    scripts.append(script)
--- a/setup.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/setup.py	Wed Apr 29 17:34:53 2020 +0200
@@ -90,5 +90,5 @@
     use_scm_version=libervia_dev_version if is_dev_version else False,
     install_requires=install_requires,
     package_data={"libervia": ["VERSION"]},
-    python_requires=">=3.6",
+    python_requires=">=3.7",
 )