pax_global_header 0000666 0000000 0000000 00000000064 13344470245 0014520 g ustar 00root root 0000000 0000000 52 comment=38ed675aef44c876cc23a5b810087f5472550031 slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/ 0000775 0000000 0000000 00000000000 13344470245 0023055 5 ustar 00root root 0000000 0000000 slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/ 0000775 0000000 0000000 00000000000 13344470245 0024707 5 ustar 00root root 0000000 0000000 slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter/ 0000775 0000000 0000000 00000000000 13344470245 0026411 5 ustar 00root root 0000000 0000000 slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter/ERP5kernel.py 0000664 0000000 0000000 00000040247 13344470245 0030706 0 ustar 00root root 0000000 0000000 from ipykernel.kernelbase import Kernel from ipykernel.kernelapp import IPKernelApp from IPython.core.display import HTML import requests import json import sys erp5_url = None if len(sys.argv) > 1: erp5_url = "%s/erp5/Base_executeJupyter" % (sys.argv[1],) class MagicInfo: """ Magics definition structure. Initializes a new MagicInfo class with specific paramters to identify a magic. """ def __init__(self, magic_name, variable_name, send_request, request_reference, display_message): self.magic_name = magic_name self.variable_name = variable_name self.send_request = send_request self.request_reference = request_reference self.display_message = display_message # XXX: New magics to be added here in the dictionary. # In this dictionary, # key = magic_name, # value = MagicInfo Structure corresponding to the magics # Different parameters of the structures are :- # magics_name(str) = Name which would be used on jupyter frontend # variable_name(str) = Name of variable on which magic would be set in kernel # send_request(boolean) = Magics for which requests to erp5 backend need to be made # request_reference(boolean) = Request for notebook references(and titles) from erp5 # display_message(boolean) = If the magics need to display message after # making request. Useful for magics which do get some # useful content from erp5 backend and need to display MAGICS = { 'erp5_user': MagicInfo('erp5_user', 'user', True, False, True), 'erp5_password': MagicInfo('erp5_password', 'password', True, False, True), 'erp5_url': MagicInfo('erp5_url', 'url', True, False, True), 'notebook_set_reference': MagicInfo('notebook_set_reference', 'reference', True, False, True), 'notebook_set_title': MagicInfo('notebook_set_title', 'title', False, False, True), 'my_notebooks': MagicInfo('my_notebooks', '', True, True, False)} class ERP5Kernel(Kernel): """ Jupyter Kernel class to interact with erp5 backend for code from frontend. To use this kernel with erp5, user need to install 'erp5_data_notebook' bt5 Also, handlers(aka magics) starting with '%' are predefined. Each request to erp5 for code execution requires erp5_user, erp5_password and reference of the notebook. """ implementation = 'ERP5' implementation_version = '1.0' language = 'ERP5' language_version = '0.1' language_info = {'mimetype': 'text/plain', 'name':'python'} banner = "ERP5 integration with jupyter notebook" def __init__(self, user=None, password=None, url=None, status_code=None, *args, **kwargs): super(ERP5Kernel, self).__init__(*args, **kwargs) self.user = user self.password = password # By default use URL provided by buildout during initiation # It can later be overridden if url is None: self.url = erp5_url else: self.url = url self.status_code = status_code self.reference = None self.title = None # Allowed HTTP request code list for making request to erp5 from Kernel # This list should be to used check status_code before making requests to erp5 self.allowed_HTTP_request_code_list = range(500, 511) # Append request code 200 in the allowed HTTP status code list self.allowed_HTTP_request_code_list.append(200) def display_response(self, response=None): """ Dispays the stream message response to jupyter frontend. """ if response: stream_content = {'name': 'stdout', 'text': response} self.send_response(self.iopub_socket, 'stream', stream_content) def set_magic_attribute(self, magic_info=None, code=None): """ Set attribute for magic which are necessary for making requests to erp5. Catch errors and display message. Since user is in contact with jupyter frontend, so its better to catch exceptions and dispaly messages than to let them fail in backend and stuck the kernel. For a making a request to erp5, we need - erp5_url, erp5_user, erp5_password, notebook_set_reference """ # Set attributes only for magic who do have any varible to set value to if magic_info.variable_name: try: # Get the magic value recived via code from frontend magic_value = code.split()[1] # Set magic_value to the required attribute if magic_info.magic_name == 'notebook_set_reference': required_attributes = ['url', 'password', 'user'] missing_attributes = [] for attribute in required_attributes: if not getattr(self, attribute): missing_attributes.append(attribute) if missing_attributes != []: self.response = "You still haven't entered all required magics. \ Please do so before inputting your reference." else: if self.check_existing_reference(reference=magic_value): self.response = 'WARNING: You already have a notebook with \ reference %s. It might be a good idea to use different references for new \ notebooks. \n' % magic_value else: self.response = '' setattr(self, magic_info.variable_name , magic_value) self.response = self.response + 'Your %s is %s. '%(magic_info.magic_name, magic_value) elif magic_info.magic_name != 'erp5_password': setattr(self, magic_info.variable_name , magic_value) self.response = 'Your %s is %s. '%(magic_info.magic_name, magic_value) else: setattr(self, magic_info.variable_name , magic_value) self.response = "" # Catch exception while setting attribute and set message in response except AttributeError: self.response = 'Please enter %s magic value'%magic_info.variable_name # Catch IndexError while getting magic_value and set message in response object except IndexError: self.response = 'Empty value for %s magic'%magic_info.variable_name # Catch all other exceptions and set error_message in response object # XXX: Might not be best way, but its better to display error to the user # via notebook frontend than to fail in backend and stuck the Kernel without # any failure message to user. except Exception as e: self.response = str(e) # Display the message/response from this fucntion before moving forward so # as to keep track of the status if self.response != "": self.display_response(response=(self.response + '\n')) def check_required_attributes(self): """ Check if the required attributes for making a request are already set or not. Display message to frontend to provide with the values in case they aren't. This function can be called anytime to check if the attributes are set. The output result will be in Boolean form. Also, in case any of attribute is not set, call to display_response would be made to ask user to enter value. """ result_list = [] required_attributes = ['url', 'user', 'password', 'reference'] missing_attributes = [] # Set response to empty so as to flush the response set by some earlier fucntion call self.response = '' # Loop to check if the required attributes are set for attribute in required_attributes: if getattr(self, attribute): result_list.append(True) else: # Set response/message for attributes which aren't set missing_attributes.append(attribute) result_list.append(False) # Compare result_list to get True for all True results and False for any False result check_attributes = all(result_list) if check_attributes: self.response = 'You have entered all required magics. You may now use your notebook.' else: self.response = '''You have these required magics remaining: %s. \n''' % ( ', '.join(map(str, missing_attributes))) # Display response to frontend before moving forward self.display_response(response=(self.response + '\n')) return check_attributes def make_erp5_request(self, request_reference=False, display_message=True, code=None, message=None, title=None, *args, **kwargs): """ Function to make request to erp5 as per the magics. Should return the response json object. """ try: erp5_request = requests.post( self.url, verify=False, auth=(self.user, self.password), data={ 'python_expression': code, 'reference': self.reference, 'title': self.title, 'request_reference': request_reference, 'store_history': kwargs.get('store_history') }) # Set value for status_code for self object which would later be used to # dispaly response after statement check self.status_code = erp5_request.status_code # Dispaly error response in case the request give any other status # except 200 and 5xx(which is for errors on server side) if self.status_code not in self.allowed_HTTP_request_code_list: self.response = '''Error code %s on request to ERP5,\n check credentials or ERP5 family URL'''%self.status_code else: # Set value of self.response to the given value in case response from function # call. In all other case, response should be the content from request if display_message and message: self.response = message else: self.response = erp5_request.content except requests.exceptions.RequestException as e: self.response = str(e) def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): """ Validate magic and call functions to make request to erp5 backend where the code is being executed and response is sent back which is then sent to jupyter frontend. """ # By default, take the status of response as 'ok' so as show the responses # for erp5_url and erp5_user on notebook frontend as successful response. status = 'ok' if not silent: # Remove spaces and newlines from both ends of code code = code.strip() extra_data_list = [] print_result = {} displayhook_result = {} if code.startswith('%'): # No need to try-catch here as its already been taken that the code # starts-with '%', so we'll get magic_name, no matter what be after '%' magic_name = code.split()[0][1:] magics_name_list = [magic.magic_name for magic in MAGICS.values()] # Check validation of magic if magic_name and magic_name in magics_name_list: # Get MagicInfo object related to the magic magic_info = MAGICS.get(magic_name) # Function call to set the required magics self.set_magic_attribute(magic_info=magic_info, code=code) # Call to check if the required_attributes are set checked_attribute = self.check_required_attributes() if checked_attribute and magic_info.send_request: # Call the function to send request to erp5 with the arguments given self.make_erp5_request(message='Please proceed\n', request_reference=magic_info.request_reference, display_message=magic_info.display_message) # Display response from erp5 request for magic # Since this response would be either success message or failure # error message, both of which are string type, so, we can simply # display the stream response. if self.response != 'Please proceed\n': self.display_response(response=self.response) else: # Set response if there is no magic or the magic name is not in MAGICS self.response = 'Invalid Magics' self.display_response(response=self.response) else: # Check for status_code before making request to erp5 and make request in # only if the status_code is in the allowed_HTTP_request_code_list if self.status_code in self.allowed_HTTP_request_code_list: self.make_erp5_request(code=code, store_history=store_history) # For 200 status_code, Kernel will receive predefined format for data # from erp5 which is either json of result or simple result string if self.status_code == 200: mime_type = 'text/plain' try: content = json.loads(self.response) # Example format for the json result we are expecting is : # content = { # "status": "ok", # "ename": null, # "evalue": null, # "traceback": null, # "code_result": "", # "print_result": {}, # "displayhook_result": {}, # "mime_type": "text/plain", # "extra_data_list": [] # } # So, we can easily use any of the key to update values as such. # Getting code_result for succesfull execution of code code_result = content['code_result'] print_result = content['print_result'] displayhook_result = content['displayhook_result'] # Update mime_type with the mime_type from the http response result # Required in case the mime_type is anything other than 'text/plain' mime_type = content['mime_type'] extra_data_list = content.get('extra_data_list', []) # Display to frontend the error message for content status as 'error' if content['status']=='error': reply_content = { 'status': 'error', 'execution_count': self.execution_count, 'ename': content['ename'], 'evalue': content['evalue'], 'traceback': content['traceback']} self.send_response(self.iopub_socket, u'error', reply_content) return reply_content # Catch exception for content which isn't json except ValueError: content = self.response code_result = content print_result = {'data':{'text/plain':content}, 'metadata':{}} # Display basic error message to frontend in case of error on server side else: self.make_erp5_request(code=code) code_result = "Error at Server Side" print_result = {'data':{'text/plain':'Error at Server Side'}, 'metadata':{}} mime_type = 'text/plain' # For all status_code except allowed_HTTP_response_code_list show unauthorized message else: code_result = 'Unauthorized access' print_result = {'data':{'text/plain':'Unauthorized access'}, 'metadata':{}} mime_type = 'text/plain' if print_result.get('data'): self.send_response(self.iopub_socket, 'display_data', print_result) if displayhook_result.get('data'): displayhook_result['execution_count'] = self.execution_count self.send_response(self.iopub_socket, 'execute_result', displayhook_result) for extra_data in extra_data_list: self.send_response(self.iopub_socket, 'display_data', extra_data) reply_content = { 'status': status, # The base class increments the execution count 'execution_count': self.execution_count, 'payload': [], 'user_expressions': {}} return reply_content # Checks the ERP5 site if there are existing notebooks with the same reference. # Returns True if there are. def check_existing_reference(self, reference): if reference == None: return False modified_url = self.url[:self.url.rfind('/')] + '/Base_checkExistingReference' result = True try: erp5_request = requests.post( modified_url, verify=False, auth=(self.user, self.password), data={ 'reference': reference, }) result = erp5_request.content except requests.exceptions.RequestException as e: self.response = str(e) return result if __name__ == '__main__': IPKernelApp.launch_instance(kernel_class=ERP5Kernel) slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter/buildout.hash.cfg 0000664 0000000 0000000 00000002366 13344470245 0031652 0 ustar 00root root 0000000 0000000 # THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax. # The only allowed lines here are (regexes): # - "^#" comments, copied verbatim # - "^[" section beginings, copied verbatim # - lines containing an "=" sign which must fit in the following categorie. # - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file # Copied verbatim. # - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported # by the re-generation script. # Re-generated. # - other lines are copied verbatim # Substitution (${...:...}), extension ([buildout] extends = ...) and # section inheritance (< = ...) are NOT supported (but you should really # not need these here). [instance-jupyter-notebook] filename = instance.cfg.in md5sum = 78625cff193d7fdadd57670d30bc9a0d [jupyter-notebook-config] filename = jupyter_notebook_config.py.jinja md5sum = 720e90a829c63371696bc3009917a743 [jupyter-set-password] filename = jupyter_set_password.cgi.jinja md5sum = b8d31441780b524a7e52d1710dd78385 [erp5-kernel] filename = ERP5kernel.py md5sum = 7d5309fe79afbcb455c0d8181b42e56c [kernel-json] filename = kernel.json.jinja md5sum = 33547be93a67530165e079dc3ecfdac3 [custom-js] filename = custom.js md5sum = 40d938bb09261c65421a7725b40f87dc slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter/custom.js 0000664 0000000 0000000 00000012334 13344470245 0030264 0 ustar 00root root 0000000 0000000 // leave at least 2 line with only a star on it below, or doc generation fails /** * * * Placeholder for custom user javascript * mainly to be overridden in profile/static/custom/custom.js * This will always be an empty file in IPython * * User could add any javascript in the `profile/static/custom/custom.js` file. * It will be executed by the ipython notebook at load time. * * Same thing with `profile/static/custom/custom.css` to inject custom css into the notebook. * * * The object available at load time depend on the version of IPython in use. * there is no guaranties of API stability. * * The example below explain the principle, and might not be valid. * * Instances are created after the loading of this file and might need to be accessed using events: * define([ * 'base/js/namespace', * 'base/js/events' * ], function(IPython, events) { * events.on("app_initialized.NotebookApp", function () { * IPython.keyboard_manager.... * }); * }); * * __Example 1:__ * * Create a custom button in toolbar that execute `%qtconsole` in kernel * and hence open a qtconsole attached to the same kernel as the current notebook * * define([ * 'base/js/namespace', * 'base/js/events' * ], function(IPython, events) { * events.on('app_initialized.NotebookApp', function(){ * IPython.toolbar.add_buttons_group([ * { * 'label' : 'run qtconsole', * 'icon' : 'icon-terminal', // select your icon from http://fortawesome.github.io/Font-Awesome/icons * 'callback': function () { * IPython.notebook.kernel.execute('%qtconsole') * } * } * // add more button here if needed. * ]); * }); * }); * * __Example 2:__ * * At the completion of the dashboard loading, load an unofficial javascript extension * that is installed in profile/static/custom/ * * define([ * 'base/js/events' * ], function(events) { * events.on('app_initialized.DashboardApp', function(){ * require(['custom/unofficial_extension.js']) * }); * }); * * __Example 3:__ * * Use `jQuery.getScript(url [, success(script, textStatus, jqXHR)] );` * to load custom script into the notebook. * * // to load the metadata ui extension example. * $.getScript('/static/notebook/js/celltoolbarpresets/example.js'); * // or * // to load the metadata ui extension to control slideshow mode / reveal js for nbconvert * $.getScript('/static/notebook/js/celltoolbarpresets/slideshow.js'); * * * @module IPython * @namespace IPython * @class customjs * @static */ $([Jupyter.events]).on('notebook_loaded.Notebook', function(){ var kernelname = Jupyter.notebook.kernel_selector.current_selection; var display_text="
"; if (kernelname=="erp5"){ $('div#notebook-container').prepend(display_text); } }); slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter/instance.cfg.in 0000664 0000000 0000000 00000011503 13344470245 0031303 0 ustar 00root root 0000000 0000000 [buildout] parts = instance jupyter_notebook read-knowledge0 publish-connection-parameter jupyter-notebook-config erp5-kernel kernel-json custom-js monitor-base extends = {{ monitor_template_rendered }}/template-monitor.cfg eggs-directory = {{ eggs_directory }} develop-eggs-directory = {{ develop_eggs_directory }} offline = true [slapconfiguration] recipe = slapos.cookbook:slapconfiguration.serialised computer = ${slap-connection:computer-id} partition = ${slap-connection:partition-id} url = ${slap-connection:server-url} key = ${slap-connection:key-file} cert = ${slap-connection:cert-file} # ERP5 URL to use in Jupyter by default # default value is empty - which means no default ERP5 URL configuration.erp5-url = [instance-parameter] port = 8888 host = ${slapconfiguration:ipv6-random} cert_file = ${generate-certificate:cert_file} key_file = ${generate-certificate:key_file} logfile = ${directory:log}/jupyter_notebook.log notebook_dir = ${directory:notebook_dir} [dynamic-jinja2-template-base] recipe = slapos.recipe.template:jinja2 mode = 0644 [generate-certificate] ; TODO: there is a slapos recipe to generate certificates. Use it instead recipe = plone.recipe.command command = if [ ! -e ${instance-parameter:key_file} ] then {{ openssl_output }} req -x509 -nodes -days 3650 \ -subj "/C=AA/ST=X/L=X/O=Dis/CN=${instance-parameter:host}" \ -newkey rsa:1024 -keyout ${instance-parameter:key_file} \ -out ${instance-parameter:cert_file} fi update-command = ${:command} cert_file = ${directory:etc}/jupyter_cert.crt key_file = ${directory:etc}/jupyter_cert.key [instance] recipe = slapos.cookbook:wrapper command-line = {{ bin_directory }}/jupyter-lab --no-browser --ip=${instance-parameter:host} --port=${instance-parameter:port} --port-retries=0 --certfile=${instance-parameter:cert_file} --keyfile=${instance-parameter:key_file} --notebook-dir=${instance-parameter:notebook_dir} --log-level="DEBUG" wrapper-path = ${directory:service}/jupyter-lab environment = JUPYTER_PATH=${directory:jupyter_dir} JUPYTER_CONFIG_DIR=${directory:jupyter_config_dir} JUPYTER_RUNTIME_DIR=${directory:jupyter_runtime_dir} LANG=C.UTF-8 [jupyter-notebook-config] <= dynamic-jinja2-template-base template = {{ jupyter_config_location }}/{{ jupyter_config_filename }} rendered = ${directory:jupyter_config_dir}/jupyter_notebook_config.py mode = 0744 context = raw config_cfg ${buildout:directory}/knowledge0.cfg [directory] recipe = slapos.cookbook:mkdirectory home = ${buildout:directory} etc = ${:home}/etc var = ${:home}/var script = ${:etc}/run/ service = ${:etc}/service promise = ${:etc}/promise/ log = ${:var}/log notebook_dir = ${:var}/notebooks # Add folders to explicitly define jupyter directory jupyter_dir = ${:home}/jupyter jupyter_config_dir = ${:jupyter_dir}/etc jupyter_kernel_dir = ${:jupyter_dir}/kernels jupyter_runtime_dir = ${:jupyter_dir}/runtime jupyter_custom_dir = ${:jupyter_config_dir}/custom jupyter_nbextensions_dir = ${:jupyter_dir}/nbextensions erp5_kernel_dir = ${:jupyter_kernel_dir}/ERP5 [jupyter_notebook] # This part is called like this because knowledge0.write uses the part name for # the section name in the config file. recipe = slapos.cookbook:zero-knowledge.write password = filename = knowledge0.cfg [read-knowledge0] recipe = slapos.cookbook:zero-knowledge.read filename = knowledge0.cfg password = [monitor-instance-parameter] monitor-base-url = ${monitor-frontend-promise:url} # In case you're using a developer instance you should edit these in: # monitor-base-url = ${monitor-httpd-conf-parameter:url} # cors-domains = softinstXXXXX.host.vifib.net (or equivalent) # interface-url = https://softinstXXXXX.host.vifib.net/erp5/web_site_module/monitoring_rjs_unsafe instance-configuration = raw jupyter-password ${read-knowledge0:password} [publish-connection-parameter] recipe = slapos.cookbook:publish.serialised jupyter-classic-url = https://[${instance-parameter:host}]:${instance-parameter:port}/tree url = ${:jupyter-classic-url} jupyterlab-url = https://[${instance-parameter:host}]:${instance-parameter:port}/lab [erp5-kernel] recipe = slapos.cookbook:symbolic.link link-binary = {{ erp5_kernel_location }}/{{ erp5_kernel_filename }} target-directory = ${directory:erp5_kernel_dir} [kernel-json] <= dynamic-jinja2-template-base template = {{ kernel_json_location }}/{{ kernel_json_filename }} rendered = ${directory:erp5_kernel_dir}/kernel.json # Use python2.7 executable bin file for kernel config context = raw python_executable {{ python_executable }} raw kernel_dir ${erp5-kernel:target-directory}/{{ erp5_kernel_filename }} key erp5_url slapconfiguration:configuration.erp5-url raw display_name ERP5 raw language_name python [custom-js] recipe = slapos.cookbook:symbolic.link target-directory = ${directory:jupyter_custom_dir} link-binary = {{ custom_js_location }}/custom.js jupyter_notebook_config.py.jinja 0000664 0000000 0000000 00000002070 13344470245 0034724 0 ustar 00root root 0000000 0000000 slapos-38ed675aef44c876cc23a5b810087f5472550031-software-jupyter/software/jupyter ''' This script initializes Jupyter's configuration such as passwords and other things. It is run by IPython hence why it can use functions like get_config(). ''' import ConfigParser import random from notebook.auth import passwd import os def random_password(length = 10): result = "" for i in range(0, length): result = result + chr(random.randint(0, 25) + ord('a')) return result knowledge_0 = '{{ config_cfg }}' if not os.path.exists(knowledge_0): print "Your software does not embed 0-knowledge. \ This interface is useless in this case