1 6
import json
2 6
import os
3 6
import pkg_resources
4 6
import tempfile
5 6
import traceback
6

7 6
from unittest.mock import MagicMock
8 6
from urllib.parse import parse_qs
9

10 6
import param
11

12 6
from runpy import run_path
13 6
from tornado import web
14 6
from tornado.wsgi import WSGIContainer
15

16 6
from .state import state
17

18

19 6
class HTTPError(web.HTTPError):
20
    """
21
    Custom HTTPError type
22
    """
23

24

25 6
class BaseHandler(web.RequestHandler):
26

27 6
    def write_error(self, status_code, **kwargs):
28 0
        self.set_header('Content-Type', 'application/json')
29 0
        if self.settings.get("serve_traceback") and "exc_info" in kwargs:
30
            # in debug mode, try to send a traceback
31 0
            lines = []
32 0
            for line in traceback.format_exception(*kwargs["exc_info"]):
33 0
                lines.append(line)
34 0
            self.finish(json.dumps({
35
                'error': {
36
                    'code': status_code,
37
                    'message': self._reason,
38
                    'traceback': lines,
39
                }
40
            }))
41
        else:
42 0
            self.finish(json.dumps({
43
                'error': {
44
                    'code': status_code,
45
                    'message': self._reason,
46
                }
47
            }))
48

49 6
class ParamHandler(BaseHandler):
50

51 6
    def __init__(self, app, request, **kwargs):
52 0
        self.root = kwargs.pop('root', None)
53 0
        super().__init__(app, request, **kwargs)
54

55 6
    @classmethod
56 1
    def serialize(cls, parameterized, parameters):
57 0
        values = {p: getattr(parameterized, p) for p in parameters}
58 0
        return parameterized.param.serialize_parameters(values)
59

60 6
    @classmethod
61 1
    def deserialize(cls, parameterized, parameters):
62 0
        for p in parameters:
63 0
            if p not in parameterized.param:
64 0
                reason = f"'{p}' query parameter not recognized."
65 0
                raise HTTPError(reason=reason, status_code=400)
66 0
        return {p: parameterized.param.deserialize_value(p, v)
67
                for p, v in parameters.items()}
68

69 6
    async def get(self):
70 0
        path = self.request.path
71 0
        endpoint = path[path.index(self.root)+len(self.root):]
72 0
        parameterized, parameters, _ = state._rest_endpoints.get(
73
            endpoint, (None, None, None)
74
        )
75 0
        if not parameterized:
76 0
            return
77 0
        args = parse_qs(self.request.query)
78 0
        params = self.deserialize(parameterized[0], args)
79 0
        parameterized[0].param.set_param(**params)
80 0
        self.set_header('Content-Type', 'application/json')
81 0
        self.write(self.serialize(parameterized[0], parameters))
82

83

84 6
def build_tranquilize_application(files):
85 0
    from tranquilizer.handler import ScriptHandler, NotebookHandler
86 0
    from tranquilizer.main import make_app, UnsupportedFileType
87

88 0
    functions = []
89 0
    for filename in files:
90 0
        extension = filename.split('.')[-1]
91 0
        if extension == 'py':
92 0
            source = ScriptHandler(filename)
93 0
        elif extension == 'ipynb':
94 0
            try:
95 0
                import nbconvert # noqa
96
            except ImportError as e: # pragma no cover
97
                raise ImportError("Please install nbconvert to serve Jupyter Notebooks.") from e
98

99 0
            source = NotebookHandler(filename)
100
        else:
101 0
            raise UnsupportedFileType('{} is not a script (.py) or notebook (.ipynb)'.format(filename))
102 0
        functions.extend(source.tranquilized_functions)
103 0
    return make_app(functions, 'Panel REST API', prefix='rest/')
104

105

106 6
def tranquilizer_rest_provider(files, endpoint):
107
    """
108
    Returns a Tranquilizer based REST API. Builds the API by evaluating
109
    the scripts and notebooks being served and finding all tranquilized
110
    functions inside them.
111

112
    Arguments
113
    ---------
114
    files: list(str)
115
      A list of paths being served
116
    endpoint: str
117
      The endpoint to serve the REST API on
118

119
    Returns
120
    -------
121
    A Tornado routing pattern containing the route and handler
122
    """
123 0
    app = build_tranquilize_application(files)
124 0
    tr = WSGIContainer(app)
125 0
    return [(r"^/%s/.*" % endpoint, web.FallbackHandler, dict(fallback=tr))]
126

127

128 6
def param_rest_provider(files, endpoint):
129
    """
130
    Returns a Param based REST API given the scripts or notebooks
131
    containing the tranquilized functions.
132

133
    Arguments
134
    ---------
135
    files: list(str)
136
      A list of paths being served
137
    endpoint: str
138
      The endpoint to serve the REST API on
139

140
    Returns
141
    -------
142
    A Tornado routing pattern containing the route and handler
143
    """
144 6
    for filename in files:
145 0
        extension = filename.split('.')[-1]
146 0
        if extension == 'py':
147 0
            try:
148 0
                run_path(filename)
149 0
            except Exception:
150 0
                param.main.warning("Could not run app script on REST server startup.")
151 0
        elif extension == 'ipynb':
152 0
            try:
153 0
                import nbconvert # noqa
154 0
            except ImportError:
155 0
                raise ImportError("Please install nbconvert to serve Jupyter Notebooks.")
156 0
            from nbconvert import ScriptExporter
157 0
            exporter = ScriptExporter()
158 0
            source, _ = exporter.from_filename(filename)
159 0
            source_dir = os.path.dirname(filename)
160 0
            with tempfile.NamedTemporaryFile(mode='w', dir=source_dir, delete=True) as tmp:
161 0
                tmp.write(source)
162 0
                tmp.flush()
163 0
                try:
164 0
                    run_path(tmp.name, init_globals={'get_ipython': MagicMock()})
165 0
                except Exception:
166 0
                    param.main.warning("Could not run app notebook on REST server startup.")
167
        else:
168 0
            raise ValueError('{} is not a script (.py) or notebook (.ipynb)'.format(filename))
169

170 6
    if endpoint and not endpoint.endswith('/'):
171 6
        endpoint += '/'
172 6
    return [((r"^/%s.*" % endpoint if endpoint else r"^.*"), ParamHandler, dict(root=endpoint))]
173

174

175 6
REST_PROVIDERS = {
176
    'tranquilizer': tranquilizer_rest_provider,
177
    'param': param_rest_provider
178
}
179

180
# Populate REST Providers from external extensions
181 6
for entry_point in pkg_resources.iter_entry_points('panel.io.rest'):
182 0
    REST_PROVIDERS[entry_point.name] = entry_point.resolve()

Read our documentation on viewing source code .

Loading