class JupyterDash: def __init__(self, name, gav=None, width=800, height=600, add_bootstrap_links=False, serve_locally=None): self.dd = DjangoDash(name, serve_locally=serve_locally, add_bootstrap_links=add_bootstrap_links) self.gav = gav and gav or get_global_av() self.gav.add_application(self, name) self.width = width self.height = height self.frame = False self.add_external_link = True self.session_state = dict() self.app_state = dict() self.local_uuid = str(uuid.uuid4()).replace('-', '') self.use_nbproxy = False def as_dash_instance(self, specific_identifier=None, base_pathname=None): if base_pathname is None: base_pathname = self.get_base_pathname(specific_identifier) specific_identifier = self.session_id() else: if base_pathname[0] != '/': base_pathname = "/%s" % base_pathname if base_pathname[-1] != '/': base_pathname = "%s/" % base_pathname # TODO perhaps cache this. If so, need to ensure updated if self.app_state changes return self.dd.form_dash_instance(replacements=self.app_state, ndid=specific_identifier, base_pathname=base_pathname) def get_session_state(self): return self.session_state def set_session_state(self, state): self.session_state = state def handle_current_state(self): # Do nothing, at least for the moment... pass def update_current_state(self, wid, name, value): wd = self.app_state.get(wid, None) if wd is None: wd = dict() self.app_state[wid] = wd wd[name] = value def have_current_state_entry(self, wid, name): wd = self.app_state.get(wid, {}) entry = wd.get(name, None) return entry is not None def get_base_pathname(self, specific_identifier): the_id = specific_identifier and specific_identifier or self.session_id( ) if self.use_nbproxy: # TODO this should be the_id not the uid? return '/proxy/%i/%s/' % (self.gav.port, self.dd._uid) return "/app/endpoints/%s/" % the_id def session_id(self): return self.local_uuid def get_app_root_url(self): # Local (not binder use) determined by presence of server prefix jh_serv_pref = os.environ.get('JUPYTERHUB_SERVICE_PREFIX', None) if jh_serv_pref is None: # Local use, root is given by proxy flag return self.get_base_pathname(self.session_id()) # Running on a binder or similar # TODO restrict use of use_nbproxy here return "%s%s" % (jh_serv_pref, self.get_base_pathname( self.session_id())[1:]) def __html__(self): return self._repr_html_() def _repr_html_(self): url = self.get_app_root_url() da_id = self.session_id() comm = locate_jpd_comm(da_id, self, url[1:-1]) external = self.add_external_link and '<hr/><a href="{url}" target="_new">Open in new window</a> for {url}'.format( url=url) or "" fb = 'frameborder="%i"' % (self.frame and 1 or 0) iframe = '''<div> <iframe src="%(url)s" width=%(width)s height=%(height)s %(frame)s></iframe> %(external)s </div>''' % { 'url': url, 'da_id': da_id, 'external': external, 'width': self.width, 'height': self.height, 'frame': fb, } return iframe def callback(self, *args, **kwargs): return self.dd.callback(*args, **kwargs) def expanded_callback(self, *args, **kwargs): return self.dd.expanded_callback(*args, **kwargs) def _get_layout(self): return self.dd.layout def _set_layout(self, layout): self.dd.layout = layout layout = property(_get_layout, _set_layout) def process_view(self, view_name, args, app_path): if view_name == None: view_name = '' view_name_parts = view_name.split('/') view_name = view_name_parts[0] view_name = view_name.replace('-', '_') func = getattr(self, 'rv_%s' % view_name, None) if func is not None: # TODO process app_path if needed resp, rmt = func(args, app_path, view_name_parts) return (resp, rmt) return ( "<html><body>Unable to understand view name of %s with args %s and app path %s</body></html>" % (view_name, args, app_path), "text/html") def rv_(self, args, app_path, view_name_parts): mFunc = self.as_dash_instance( base_pathname=app_path).locate_endpoint_function() response = mFunc() return (response, "text/html") def rv__dash_layout(self, args, app_path, view_name_parts): dapp = self.as_dash_instance(base_pathname=app_path) with dapp.app_context(): mFunc = dapp.locate_endpoint_function('dash-layout') resp = mFunc() body, mimetype = dapp.augment_initial_layout(resp) return (body, mimetype) def rv__dash_dependencies(self, args, app_path, view_name_parts): dapp = self.as_dash_instance(base_pathname=app_path) with dapp.app_context(): mFunc = dapp.locate_endpoint_function('dash-dependencies') resp = mFunc() return (resp.data.decode('utf-8'), resp.mimetype) def rv__dash_update_component(self, args, app_path, view_name_parts): dapp = self.as_dash_instance(base_pathname=app_path) if dapp.use_dash_dispatch(): mFunc = dapp.locate_endpoint_function('dash-update-component') import flask with dapp.test_request_context(): flask.request._cached_json = (args, flask.request._cached_json[True]) resp = mFunc() else: # Use direct dispatch with extra arguments in the argMap app_state = self.get_session_state() app_state['call_count'] = app_state.get('call_count', 0) + 1 argMap = {} argMap = { 'dash_app_id': self.local_uuid, 'dash_app': self, 'user': None, 'session_state': app_state } resp = dapp.dispatch_with_args(args, argMap) self.set_session_state(app_state) self.handle_current_state() try: rdata = resp.data rtype = resp.mimetype except: rdata = resp rtype = "application/json" return (rdata, rtype) def rv__dash_component_suites(self, args, app_path, view_name_parts): dapp = self.as_dash_instance(base_pathname=app_path) with dapp.app_context(): # Force recalc of dependencies in case the instance is a fresh one dapp.index() # Endpoint is dash-component-suites/package_name/path_in_package try: mFunc = dapp.locate_endpoint_function( 'dash-component-suites/<string:package_name>/<path:path_in_package_dist>' ) except Exception as e: return ( "<html><body>Requested %s at %s with %s and failed with %s</body></html>" % (args, app_path, view_name_parts, e), "text/html") # Need two arguments here: package_name and path_in_package_dist package_name = view_name_parts[1] path_in_package_dist = "/".join(view_name_parts[2:]) resp = mFunc(package_name=package_name, path_in_package_dist=path_in_package_dist) return (resp.data.decode('utf-8'), resp.mimetype)
class JupyterDash: def __init__(self, name, gav=None, width=800, height=600): self.dd = DjangoDash(name) self.gav = gav and gav or get_global_av() self.gav.add_app(self, name) self.width = width self.height = height self.add_external_link = True self.session_state = dict() self.app_state = dict() def as_dash_instance(self): # TODO perhaps cache this. If so, need to ensure updated if self.app_state changes return self.dd.form_dash_instance(replacements=self.app_state) def get_session_state(self): return self.session_state def set_session_state(self, state): self.session_state = state def handle_current_state(self): # Do nothing, at least for the moment... pass def update_current_state(self, wid, name, value): wd = self.app_state.get(wid,None) if wd is None: wd = dict() self.app_state[wid] = wd wd[name] = value def have_current_state_entry(self, wid, name): wd = self.app_state.get(wid,{}) entry = wd.get(name,None) return entry is not None def get_base_pathname(self, specific_identifier): return '/%s/' % specific_identifier def get_app_root_url(self): return 'http://localhost:%i%s' % (self.gav.port, self.get_base_pathname(self.dd._uid)) def _repr_html_(self): url = self.get_app_root_url() external = self.add_external_link and '<hr/><a href="{url}" target="_new">Open in new window</a>'.format(url=url) or "" iframe = '''<div> <iframe src="{url}" width={width} height={height}></iframe> {external} </div>'''.format(url = url, external = external, width = self.width, height = self.height) return iframe def callback(self, *args, **kwargs): return self.dd.callback(*args,**kwargs) def expanded_callback(self, *args, **kwargs): return self.dd.expanded_callback(*args,**kwargs) def _get_layout(self): return self.dd.layout def _set_layout(self, layout): self.dd.layout = layout layout = property(_get_layout, _set_layout)