1
"""
2
Utilities for building custom models included in panel.
3
"""
4 6
from __future__ import absolute_import, division, unicode_literals
5

6 6
import glob
7 6
import inspect
8 6
import io
9 6
import os
10 6
import pathlib
11 6
import shutil
12 6
import tarfile
13

14 6
import param
15 6
import requests
16

17 6
from bokeh.model import Model
18

19
#---------------------------------------------------------------------
20
# Public API
21
#---------------------------------------------------------------------
22

23 6
def require_components():
24
    """
25
    Returns JS snippet to load the required dependencies in the classic
26
    notebook using REQUIRE JS.
27
    """
28 0
    from .config import config
29

30 0
    configs, requirements, exports = [], [], []
31 0
    js_requires = []
32

33 0
    from bokeh.model import Model
34 0
    for qual_name, model in Model.model_class_reverse_map.items():
35
        # We need to enable Models from Panel as well as Panel extensions
36
        # like awesome_panel_extensions.
37
        # The Bokeh models do not have "." in the qual_name
38 0
        if "." in qual_name:
39 0
            js_requires.append(model)
40

41 0
    for export, js in config.js_files.items():
42 0
        name = js.split('/')[-1].replace('.min', '').split('.')[-2]
43 0
        conf = {'paths': {name: js[:-3]}, 'exports': {name: export}}
44 0
        js_requires.append(conf)
45

46 0
    skip_import = {}
47 0
    for model in js_requires:
48 0
        if hasattr(model, '__js_skip__'):
49 0
            skip_import.update(model.__js_skip__)
50

51 0
        if not (hasattr(model, '__js_require__') or isinstance(model, dict)):
52 0
            continue
53

54 0
        if isinstance(model, dict):
55 0
            model_require = model
56
        else:
57 0
            model_require = model.__js_require__
58

59 0
        model_exports = model_require.pop('exports', {})
60 0
        if not any(model_require == config for config in configs):
61 0
            configs.append(model_require)
62

63 0
        for req in list(model_require.get('paths', [])):
64 0
            if isinstance(req, tuple):
65 0
                model_require['paths'][req[0]] = model_require['paths'].pop(req)
66

67 0
            export = req[0] if isinstance(req, tuple) else req
68 0
            if export not in model_exports:
69 0
                continue
70

71 0
            if isinstance(req, tuple):
72 0
                for r in req[1]:
73 0
                    if r not in requirements:
74 0
                        requirements.append(r)
75 0
                req = req[0]
76 0
            elif req not in requirements:
77 0
                requirements.append(req)
78

79 0
            export = model_exports[req]
80 0
            for e in (export if isinstance(export, list) else [export]):
81 0
                if export not in exports and export is not None:
82 0
                    exports.append(export)
83 0
    return configs, requirements, exports, skip_import
84

85

86 6
def write_bundled_files(name, files, bundle_dir):
87 0
    model_name = name.split('.')[-1].lower()
88 0
    for bundle_file in files:
89 0
        bundle_file = bundle_file.split('?')[0]
90 0
        try:
91 0
            response = requests.get(bundle_file)
92 0
        except Exception as e:
93 0
            print(f"Failed to fetch {name} dependency: {bundle_file}. Errored with {e}.")
94 0
            continue
95 0
        bundle_path = os.path.join(*os.path.join(*bundle_file.split('//')[1:]).split('/')[1:])
96 0
        filename = bundle_dir.joinpath(model_name, bundle_path)
97 0
        filename.parent.mkdir(parents=True, exist_ok=True)
98 0
        with open(filename, 'w', encoding="utf-8") as f:
99 0
            f.write(response.content.decode('utf-8'))
100

101 6
def write_bundled_tarball(name, tarball, bundle_dir):
102 0
    model_name = name.split('.')[-1].lower()
103 0
    response = requests.get(tarball['tar'])
104 0
    f = io.BytesIO()
105 0
    f.write(response.content)
106 0
    f.seek(0)
107 0
    tar_obj = tarfile.open(fileobj=f)
108 0
    exclude = tarball.get('exclude', [])
109 0
    for tarf in tar_obj:
110 0
        if not tarf.name.startswith(tarball['src']) or not tarf.isfile():
111 0
            continue
112 0
        path = tarf.name.replace(tarball['src'], '')
113 0
        if any(path.startswith(exc) for exc in exclude):
114 0
            continue
115 0
        bundle_path = os.path.join(*path.split('/'))
116 0
        dest_path = os.path.join(*tarball['dest'].split('/'))
117 0
        filename = bundle_dir.joinpath(model_name, dest_path, bundle_path)
118 0
        filename.parent.mkdir(parents=True, exist_ok=True)
119 0
        fobj = tar_obj.extractfile(tarf.name)
120 0
        content = fobj.read().decode('utf-8')
121 0
        with open(filename, 'w', encoding="utf-8") as f:
122 0
            f.write(content)
123 0
    tar_obj.close()
124

125
            
126 6
def bundle_resources():
127 0
    from .config import panel_extension
128 0
    from .template.base import BasicTemplate
129

130 0
    for imp in panel_extension._imports.values():
131 0
        if imp.startswith('panel.'):
132 0
            __import__(imp)
133

134 0
    bundle_dir = pathlib.Path(__file__).parent.joinpath('dist', 'bundled')
135

136 0
    js_files = {}
137 0
    css_files = {}
138 0
    for name, model in Model.model_class_reverse_map.items():
139 0
        if not name.startswith('panel.'):
140 0
            continue
141 0
        prev_jsfiles = getattr(model, '__javascript_raw__', None)
142 0
        prev_jsbundle = getattr(model, '__tarball__', None)
143 0
        prev_cls = model
144 0
        for cls in model.__mro__[1:]:
145 0
            jsfiles = getattr(cls, '__javascript_raw__', None)
146 0
            if ((jsfiles is None and prev_jsfiles is not None) or
147
                (jsfiles is not None and jsfiles != prev_jsfiles)):
148 0
                if prev_jsbundle:
149 0
                    js_files[prev_cls.__name__] = prev_jsbundle
150
                else:
151 0
                    js_files[prev_cls.__name__] = prev_jsfiles
152 0
                break
153 0
            prev_cls = cls
154 0
        prev_cssfiles = getattr(model, '__css_raw__', None)
155 0
        prev_cls = model
156 0
        for cls in model.__mro__[1:]:
157 0
            cssfiles = getattr(cls, '__css_raw__', None)
158 0
            if ((cssfiles is None and prev_cssfiles is not None) or
159
                (cssfiles is not None and cssfiles != prev_cssfiles)):
160 0
                css_files[prev_cls.__name__] = prev_cssfiles
161 0
                break
162 0
            prev_cls = cls
163

164 0
    for name, jsfiles in js_files.items():
165 0
        if isinstance(jsfiles, dict):
166 0
            write_bundled_tarball(name, jsfiles, bundle_dir)
167
        else:
168 0
            write_bundled_files(name, jsfiles, bundle_dir)
169

170 0
    for name, cssfiles in css_files.items():
171 0
        write_bundled_files(name, cssfiles, bundle_dir)
172

173
    # Bundle external template dependencies
174 0
    for name, template in param.concrete_descendents(BasicTemplate).items():
175 0
        write_bundled_files(name, list(template._resources['css'].values()), bundle_dir)
176 0
        write_bundled_files(name, list(template._resources['js'].values()), bundle_dir)
177 0
        template_dir = pathlib.Path(inspect.getfile(template)).parent
178 0
        dest_dir = bundle_dir / name.lower()
179 0
        dest_dir.mkdir(parents=True, exist_ok=True)
180 0
        for css in glob.glob(str(template_dir / '*.css')):
181 0
            shutil.copyfile(css, dest_dir / os.path.basename(css))

Read our documentation on viewing source code .

Loading