diff --git a/aiida_datatypes.ipynb b/aiida_datatypes.ipynb new file mode 100644 index 000000000..2f5a0dcd5 --- /dev/null +++ b/aiida_datatypes.ipynb @@ -0,0 +1,206 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from os import path\n", + "from aiida import load_dbenv, is_dbenv_loaded\n", + "from aiida.backends import settings\n", + "if not is_dbenv_loaded():\n", + " load_dbenv(profile=settings.AIIDADB_PROFILE)\n", + "from aiida.orm import DataFactory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from aiidalab_widgets_base import aiidalab_display" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ParameterData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# visualize ParameterData\n", + "ParameterData = DataFactory('parameter')\n", + "p = ParameterData(dict={\n", + " 'Parameter' :'super long string '*4,\n", + " 'parameter 2' :'value 2',\n", + " 'parameter 3' : 1,\n", + " 'parameter 4' : 2,\n", + "})\n", + "aiidalab_display(p.store(), downloadable=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create molecule\n", + "from ase.build import molecule\n", + "m = molecule('H2O')\n", + "m.center(vacuum=2.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## CifData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# visualize CifData\n", + "CifData = DataFactory('cif')\n", + "s = CifData(ase=m)\n", + "aiidalab_display(s.store(), downloadable=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## StructureData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# visualize StructureData\n", + "StructureData = DataFactory('structure')\n", + "s = StructureData(ase=m)\n", + "aiidalab_display(s.store(), downloadable=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## BandsData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "BandsData = DataFactory('array.bands')\n", + "bs = BandsData()\n", + "kpoints = np.array([[0. , 0. , 0. ], # array shape is 12 * 3\n", + " [0.1 , 0. , 0.1 ],\n", + " [0.2 , 0. , 0.2 ],\n", + " [0.3 , 0. , 0.3 ],\n", + " [0.4 , 0. , 0.4 ],\n", + " [0.5 , 0. , 0.5 ],\n", + " [0.5 , 0. , 0.5 ],\n", + " [0.525 , 0.05 , 0.525 ],\n", + " [0.55 , 0.1 , 0.55 ],\n", + " [0.575 , 0.15 , 0.575 ],\n", + " [0.6 , 0.2 , 0.6 ],\n", + " [0.625 , 0.25 , 0.625 ]])\n", + "\n", + "bands = np.array([\n", + " [-5.64024889, 6.66929678, 6.66929678, 6.66929678, 8.91047649], # array shape is 12 * 5, where 12 is the size of the kpoints mesh\n", + " [-5.46976726, 5.76113772, 5.97844699, 5.97844699, 8.48186734], # and 5 is the number of states\n", + " [-4.93870761, 4.06179965, 4.97235487, 4.97235488, 7.68276008],\n", + " [-4.05318686, 2.21579935, 4.18048674, 4.18048675, 7.04145185],\n", + " [-2.83974972, 0.37738276, 3.69024464, 3.69024465, 6.75053465],\n", + " [-1.34041116, -1.34041115, 3.52500177, 3.52500178, 6.92381041],\n", + " [-1.34041116, -1.34041115, 3.52500177, 3.52500178, 6.92381041],\n", + " [-1.34599146, -1.31663872, 3.34867603, 3.54390139, 6.93928289],\n", + " [-1.36769345, -1.24523403, 2.94149041, 3.6004033 , 6.98809593],\n", + " [-1.42050683, -1.12604118, 2.48497007, 3.69389815, 7.07537154],\n", + " [-1.52788845, -0.95900776, 2.09104321, 3.82330632, 7.20537566],\n", + " [-1.71354964, -0.74425095, 1.82242466, 3.98697455, 7.37979746]])\n", + "bs.set_kpoints(kpoints)\n", + "bs.set_bands(bands)\n", + "labels = [(0, u'GAMMA'),\n", + " (5, u'X'),\n", + " (6, u'Z'),\n", + " (11, u'U')]\n", + "bs.labels = labels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "aiidalab_display(bs.store()) # to visualize the bands" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## FolderData" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "FolderData = DataFactory('folder')\n", + "fd = FolderData()\n", + "with fd.folder.open(path.join('path','test1.txt'), 'w') as fobj:\n", + " fobj.write('content of test1 file')\n", + "with fd.folder.open(path.join('path','test2.txt'), 'w') as fobj:\n", + " fobj.write('content of test2\\nfile')\n", + "with fd.folder.open(path.join('path','test_long.txt'), 'w') as fobj:\n", + " fobj.write('content of test_long file'*1000)\n", + "aiidalab_display(fd.store(), downloadable=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/aiidalab_widgets_base/__init__.py b/aiidalab_widgets_base/__init__.py index e1dd90bcb..b8d09f6c3 100644 --- a/aiidalab_widgets_base/__init__.py +++ b/aiidalab_widgets_base/__init__.py @@ -1,6 +1,14 @@ # pylint: disable=unused-import +from aiida import load_dbenv, is_dbenv_loaded +from aiida.backends import settings +if not is_dbenv_loaded(): + load_dbenv(profile=settings.AIIDADB_PROFILE) + from .structures import StructureUploadWidget # noqa from .structures_multi import MultiStructureUploadWidget # noqa -from .codes import CodeDropdown # noqa +from .codes import CodeDropdown, AiiDACodeSetup, extract_aiidacodesetup_arguments # noqa +from .computers import SshComputerSetup, extract_sshcomputersetup_arguments # noqa +from .computers import AiidaComputerSetup, extract_aiidacomputer_arguments # noqa +from .display import aiidalab_display # noqa -__version__ = "0.2.0a1" +__version__ = "0.3.0b1" diff --git a/aiidalab_widgets_base/aiida_visualizers.py b/aiidalab_widgets_base/aiida_visualizers.py new file mode 100644 index 000000000..8283e52c7 --- /dev/null +++ b/aiidalab_widgets_base/aiida_visualizers.py @@ -0,0 +1,145 @@ +from __future__ import print_function +import os + +import ipywidgets as ipw + +class ParameterDataVisualizer(ipw.HTML): + """Visualizer class for ParameterData object""" + def __init__(self, parameter, downloadable=True, **kwargs): + super(ParameterDataVisualizer, self).__init__(**kwargs) + import pandas as pd + # Here we are defining properties of 'df' class (specified while exporting pandas table into html). + # Since the exported object is nothing more than HTML table, all 'standard' HTML table settings + # can be applied to it as well. + # For more information on how to controle the table appearance please visit: + # https://css-tricks.com/complete-guide-table-element/ + self.value = ''' + + ''' + pd.set_option('max_colwidth', 40) + df = pd.DataFrame([(key, value) for key, value + in sorted(parameter.get_dict().items()) + ], columns=['Key', 'Value']) + self.value += df.to_html(classes='df', index=False) # specify that exported table belongs to 'df' class + # this is used to setup table's appearance using CSS + if downloadable: + import base64 + payload = base64.b64encode(df.to_csv(index=False).encode()).decode() + fname = '{}.csv'.format(parameter.pk) + to_add = """Download table in csv format: {title}""" + self.value += to_add.format(filename=fname, payload=payload,title=fname) + +class StructureDataVisualizer(ipw.VBox): + """Visualizer class for StructureData object""" + def __init__(self, structure, downloadable=True, **kwargs): + import nglview + self._structure = structure + viewer = nglview.NGLWidget() + viewer.add_component(nglview.ASEStructure(self._structure.get_ase())) # adds ball+stick + viewer.add_unitcell() + children = [viewer] + if downloadable: + self.file_format = ipw.Dropdown( + options=['xyz', 'cif'], + description="File format:", + ) + self.download_btn = ipw.Button(description="Download") + self.download_btn.on_click(self.download) + children.append(ipw.HBox([self.file_format, self.download_btn])) + super(StructureDataVisualizer, self).__init__(children, **kwargs) + + def download(self, b=None): + import base64 + from tempfile import TemporaryFile + from IPython.display import Javascript + with TemporaryFile() as fobj: + self._structure.get_ase().write(fobj, format=self.file_format.value) + fobj.seek(0) + b64 = base64.b64encode(fobj.read()) + payload = b64.decode() + js = Javascript( + """ + var link = document.createElement('a'); + link.href = "data:;base64,{payload}" + link.download = "{filename}" + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + """.format(payload=payload,filename=str(self._structure.id)+'.'+self.file_format.value) + ) + display(js) + +class FolderDataVisualizer(ipw.VBox): + """Visualizer class for FolderData object""" + def __init__(self, folder, downloadable=True, **kwargs): + self._folder = folder + self.files = ipw.Dropdown( + options=self._folder.get_folder_list(), + description="Select file:", + ) + self.text = ipw.Textarea( + value="", + description='File content:', + layout={'width':"900px", 'height':'300px'}, + disabled=False + ) + self.change_file_view() + self.files.observe(self.change_file_view, names='value') + children = [self.files, self.text] + if downloadable: + self.download_btn = ipw.Button(description="Download") + self.download_btn.on_click(self.download) + children.append(self.download_btn) + super(FolderDataVisualizer, self).__init__(children, **kwargs) + + def change_file_view(self, b=None): + with open(self._folder.get_abs_path(self.files.value), "rb") as fobj: + self.text.value = fobj.read() + + def download(self, b=None): + import base64 + from IPython.display import Javascript + with open(self._folder.get_abs_path(self.files.value), "rb") as fobj: + b64 = base64.b64encode(fobj.read()) + payload = b64.decode() + js = Javascript( + """ + var link = document.createElement('a'); + link.href = "data:;base64,{payload}" + link.download = "{filename}" + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + """.format(payload=payload,filename=self.files.value) + ) + display(js) + +class BandsDataVisualizer(ipw.VBox): + """Visualizer class for BandsData object""" + def __init__(self, bands, **kwargs): + from bokeh.plotting import figure + from bokeh.io import show, output_notebook + output_notebook(hide_banner=True) + out = ipw.Output() + with out: + plot_info = bands._get_bandplot_data(cartesian=True, join_symbol="|") + y = plot_info['y'].transpose().tolist() + x = [plot_info['x'] for i in range(len(y))] + labels = plot_info['labels'] + p = figure(y_axis_label='Dispersion ({})'.format(bands.units)) + p.multi_line(x, y, line_width=2) + p.xaxis.ticker = [l[0] for l in labels] + p.xaxis.major_label_overrides = {int(l[0]) if l[0].is_integer() else l[0]:l[1] for l in labels} + # int(l[0]) if l[0].is_integer() else l[0] + # This trick was suggested here: https://github.com/bokeh/bokeh/issues/8166#issuecomment-426124290 + show(p) + children = [out] + super(BandsDataVisualizer, self).__init__(children, **kwargs) \ No newline at end of file diff --git a/aiidalab_widgets_base/codes.py b/aiidalab_widgets_base/codes.py index 115f95d15..46d7eef1f 100644 --- a/aiidalab_widgets_base/codes.py +++ b/aiidalab_widgets_base/codes.py @@ -2,8 +2,28 @@ from __future__ import absolute_import import ipywidgets as ipw + +from subprocess import check_output from IPython.display import clear_output +from aiida.orm import Code + +VALID_AIIDA_CODE_SETUP_ARGUMETNS = {'label', 'selected_computer', 'plugin', 'description', + 'exec_path', 'prepend_text', 'append_text'} + +def valid_arguments(arguments, valid_arguments): + result = {} + for key, value in arguments.items(): + if key in valid_arguments: + if type(value) is tuple or type(value) is list: + result[key] = '\n'.join(value) + else: + result[key] = value + return result + +def extract_aiidacodesetup_arguments(arguments): + return valid_arguments(arguments, VALID_AIIDA_CODE_SETUP_ARGUMETNS) + class CodeDropdown(ipw.VBox): def __init__(self, input_plugin, text='Select code:', **kwargs): @@ -18,25 +38,23 @@ def __init__(self, input_plugin, text='Select code:', **kwargs): self.input_plugin = input_plugin self.codes = {} - self.label = ipw.Label(value=text) - self.dropdown = ipw.Dropdown(options=[], disabled=True) + self.dropdown = ipw.Dropdown(description=text, disabled=True) + self._btn_refresh = ipw.Button(description="Refresh", layout=ipw.Layout(width="70px")) + self._btn_refresh.on_click(self.refresh) + self._setup_another = ipw.HTML(value="""Setup new code""") self.output = ipw.Output() - children = [ipw.HBox([self.label, self.dropdown, self.output])] + children = [ipw.HBox([self.dropdown, self._btn_refresh, self._setup_another]), + self.output] super(CodeDropdown, self).__init__(children=children, **kwargs) - from aiida import load_dbenv, is_dbenv_loaded - from aiida.backends import settings - if not is_dbenv_loaded(): - load_dbenv(profile=settings.AIIDADB_PROFILE) self.refresh() def _get_codes(self, input_plugin): from aiida.orm.querybuilder import QueryBuilder - from aiida.orm import Code, Computer from aiida.backends.utils import get_automatic_user - + from aiida.orm import Computer current_user = get_automatic_user() qb = QueryBuilder() @@ -62,7 +80,7 @@ def _get_codes(self, input_plugin): codes = {"{}@{}".format(r[1].label, r[0].name): r[1] for r in results} return codes - def refresh(self): + def refresh(self, b=None): with self.output: clear_output() self.codes = self._get_codes(self.input_plugin) @@ -83,3 +101,158 @@ def selected_code(self): return self.codes[self.dropdown.value] except KeyError: return None + +class AiiDACodeSetup(ipw.VBox): + """Class that allows to setup AiiDA code""" + def __init__(self, **kwargs): + from aiida.common.pluginloader import all_plugins + from aiidalab_widgets_base.computers import ComputerDropdown + + style = {"description_width":"200px"} + + # list of widgets to be displayed + + self._inp_code_label = ipw.Text(description="AiiDA code label:", + layout=ipw.Layout(width="500px"), + style=style) + + self._computer = ComputerDropdown(layout={'margin': '0px 0px 0px 125px'}) + + self._inp_code_description = ipw.Text(placeholder='No description (yet)', + description="Code description:", + layout=ipw.Layout(width="500px"), + style=style) + + self._inp_code_plugin = ipw.Dropdown(options=sorted(all_plugins('calculations')), + description="Code plugin:", + layout=ipw.Layout(width="500px"), + style=style) + + self._exec_path = ipw.Text(placeholder='/path/to/executable', + description="Absolute path to executable:", + layout=ipw.Layout(width="500px"), + style=style) + + self._prepend_text = ipw.Textarea(placeholder='Text to prepend to each command execution', + description='Prepend text:', + layout=ipw.Layout(width="400px")) + + self._append_text = ipw.Textarea(placeholder='Text to append to each command execution', + description='Append text:', + layout=ipw.Layout(width="400px")) + + self._btn_setup_code = ipw.Button(description="Setup code") + self._btn_setup_code.on_click(self._setup_code) + self._setup_code_out = ipw.Output() + children = [ipw.HBox([ipw.VBox([self._inp_code_label, + self._computer, + self._inp_code_plugin, + self._inp_code_description, + self._exec_path]), ipw.VBox([self._prepend_text, + self._append_text])]), + self._btn_setup_code, + self._setup_code_out, + ] + # Check if some settings were already provided + self._predefine_settings(**kwargs) + super(AiiDACodeSetup, self).__init__(children, **kwargs) + + def _predefine_settings(self, **kwargs): + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + else: + raise AttributeError("'{}' object has no attribute '{}'".format(self, key)) + + def _setup_code(self, b=None): + with self._setup_code_out: + clear_output() + if self.label is None: + print("You did not specify code label") + return + if not self.exec_path: + print("You did not specify absolute path to the executable") + return + if self.exists(): + print ("Code {}@{} already exists".format(self.label, self.selected_computer.name)) + return + code = Code(remote_computer_exec=(self.selected_computer, self.exec_path)) + code.label = self.label + code.description = self.description + code.set_input_plugin_name(self.plugin) + code.set_prepend_text(self.prepend_text) + code.set_append_text(self.append_text) + code.store() + code._reveal() + full_string = "{}@{}".format(self.label, self.selected_computer.name) + print(check_output(['verdi', 'code', 'show', full_string])) + + def exists(self): + from aiida.common.exceptions import NotExistent, MultipleObjectsError + try: + Code.get_from_string("{}@{}".format(self.label, self.selected_computer.name)) + return True + except MultipleObjectsError: + return True + except NotExistent: + return False + + @property + def label(self): + if len(self._inp_code_label.value.strip()) == 0: + return None + else: + return self._inp_code_label.value + + @label.setter + def label(self, label): + self._inp_code_label.value = label + + @property + def description(self): + return self._inp_code_description.value + + @description.setter + def description(self, description): + self._inp_code_description.value = description + + @property + def plugin(self): + return self._inp_code_plugin.value + + @plugin.setter + def plugin(self, plugin): + if plugin in self._inp_code_plugin.options: + self._inp_code_plugin.value = plugin + + @property + def exec_path(self): + return self._exec_path.value + + @exec_path.setter + def exec_path(self, exec_path): + self._exec_path.value = exec_path + + @property + def prepend_text(self): + return self._prepend_text.value + + @prepend_text.setter + def prepend_text(self, prepend_text): + self._prepend_text.value = prepend_text + + @property + def append_text(self): + return self._append_text.value + + @append_text.setter + def append_text(self, append_text): + self._append_text.value = append_text + + @property + def selected_computer(self): + return self._computer.selected_computer + + @selected_computer.setter + def selected_computer(self, selected_computer): + self._computer.selected_computer = selected_computer diff --git a/aiidalab_widgets_base/computers.py b/aiidalab_widgets_base/computers.py new file mode 100644 index 000000000..96cb55eb8 --- /dev/null +++ b/aiidalab_widgets_base/computers.py @@ -0,0 +1,717 @@ +from __future__ import print_function + +import pexpect +import ipywidgets as ipw + +from os import path +from copy import copy +from IPython.display import clear_output +from subprocess import check_output, call +from traitlets import Int + +from aiida.orm import Computer +from aiida.backends.utils import get_automatic_user, get_backend_type +from aiida.common.exceptions import NotExistent +from aiida.transport.plugins.ssh import parse_sshconfig + +if get_backend_type() == 'sqlalchemy': + from aiida.backends.sqlalchemy.models.authinfo import DbAuthInfo +else: + from aiida.backends.djsite.db.models import DbAuthInfo + +VALID_SSH_COMPUTER_SETUP_ARGUMETNS = {'hostname', 'username', 'proxy_hostname', 'proxy_username'} +VALID_AIIDA_COMPUTER_SETUP_ARGUMETNS = {'name', 'hostname', 'description', 'workdir', 'mpirun_cmd', + 'ncpus', 'transport_type', 'scheduler', 'prepend_text', 'append_text'} + +def valid_arguments(arguments, valid_arguments): + result = {} + for key, value in arguments.items(): + if key in valid_arguments: + if type(value) is tuple or type(value) is list: + result[key] = '\n'.join(value) + else: + result[key] = value + return result + +def extract_sshcomputersetup_arguments(arguments): + return valid_arguments(arguments, VALID_SSH_COMPUTER_SETUP_ARGUMETNS) + +def extract_aiidacomputer_arguments(arguments): + return valid_arguments(arguments, VALID_AIIDA_COMPUTER_SETUP_ARGUMETNS) + +class SshComputerSetup(ipw.VBox): + setup_counter = Int(0) # Traitlet to inform other widgets about changes + def __init__(self, **kwargs): + style = {"description_width":"200px"} + computer_image = ipw.HTML('') + self._inp_username = ipw.Text(description="SSH username:", + layout=ipw.Layout(width="350px"), + style=style) + self._inp_password = ipw.Password(description="SSH password:", + layout=ipw.Layout(width="130px"), + style=style) + # Computer ssh settings + self._inp_computer_hostname = ipw.Text(description="Computer name:", + layout=ipw.Layout(width="350px"), + style=style) + self._use_proxy = ipw.Checkbox(value=False, description='Use proxy') + self._use_proxy.observe(self.on_use_proxy_change, names='value') + + + # Proxy ssh settings + self._inp_proxy_address = ipw.Text(description="Proxy server address:", + layout=ipw.Layout(width="350px"), + style=style) + self._use_diff_proxy_username = ipw.Checkbox(value=False, + description='Use different username and password', + layout={'width': 'initial'}) + self._use_diff_proxy_username.observe(self.on_use_diff_proxy_username_change, names='value') + + self._inp_proxy_username = ipw.Text(value='', + description="Proxy server username:", + layout=ipw.Layout(width="350px"), style=style) + self._inp_proxy_password = ipw.Password(value='', + description="Proxy server password:", + layout=ipw.Layout(width="138px"), + style=style) + self._btn_setup_ssh = ipw.Button(description="Setup ssh") + self._btn_setup_ssh.on_click(self.on_setup_ssh) + self._setup_ssh_out = ipw.Output() + + # Check if some settings were already provided + self._predefine_settings(**kwargs) + + # Defining widgets positions + computer_ssh_box = ipw.VBox([self._inp_computer_hostname, + self._inp_username, + self._inp_password, + self._use_proxy], + layout=ipw.Layout(width="400px")) + + self._proxy_user_password_box = ipw.VBox([self._inp_proxy_username, + self._inp_proxy_password], + layout={'visibility':'hidden'}) + + self._proxy_ssh_box = ipw.VBox([self._inp_proxy_address, + self._use_diff_proxy_username, + self._proxy_user_password_box], + layout = {'visibility':'hidden','width':'400px'}) + + children = [ipw.HBox([computer_image, computer_ssh_box, self._proxy_ssh_box]), + self._btn_setup_ssh, + self._setup_ssh_out] + + super(SshComputerSetup, self).__init__(children, **kwargs) + + def _predefine_settings(self, **kwargs): + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + else: + raise AttributeError("'{}' object has no attirubte '{}'".format(self, key)) + + def _ssh_keygen(self): + fn = path.expanduser("~/.ssh/id_rsa") + if not path.exists(fn): + print("Creating ssh key pair") + # returns non-0 if the key pair already exists + call(["ssh-keygen", "-f", fn, "-t", "rsa", "-N", ""]) + + def is_host_known(self, hostname=None): + if hostname is None: + hostname = self.hostname + fn = path.expanduser("~/.ssh/known_hosts") + if not path.exists(fn): + return False + return call(["ssh-keygen", "-F", hostname]) == 0 + + def _make_host_known(self, hostname, proxycmd=[]): + fn = path.expanduser("~/.ssh/known_hosts") + print("Adding keys from %s to %s"%(hostname, fn)) + hashes = check_output(proxycmd+["ssh-keyscan", "-H", hostname]) + with open(fn, "a") as f: + f.write(hashes) + + def can_login(self, silent=False): + if self.username is None: # if I can't find the username - I must fail + return False + userhost = self.username+"@"+self.hostname + if not silent: + print("Trying ssh "+userhost+"... ", end='') + # With BatchMode on, no password prompt or other interaction is attempted, + # so a connect that requires a password will fail. + ret = call(["ssh", userhost, "-o", "BatchMode=yes", "-o", "ConnectTimeout=5", "true"]) + if not silent: + print("Ok" if ret==0 else "Failed") + return ret==0 + + def is_in_config(self): + fn = path.expanduser("~/.ssh/config") + if not path.exists(fn): + return False + cfglines = open(fn).read().split("\n") + return "Host "+self.hostname in cfglines + + def _write_ssh_config(self, proxycmd=''): + fn = path.expanduser("~/.ssh/config") + print("Adding section to "+fn) + with open(fn, "a") as f: + f.write("Host "+self.hostname+"\n") + f.write("User "+self.username+"\n") + if proxycmd: + f.write("ProxyCommand ssh -q -Y "+proxycmd+" netcat %h %p\n") + f.write("ServerAliveInterval 5\n") + + def _send_pubkey(self, hostname, username, password, proxycmd=''): + from pexpect import TIMEOUT + timeout = 10 + print("Sending public key to {}... ".format(hostname),end='') + str_ssh = 'ssh-copy-id {}@{}'.format(username, hostname) + if proxycmd: + str_ssh += ' -o "ProxyCommand ssh -q -Y '+proxycmd+' netcat %h %p\n"' + child = pexpect.spawn(str_ssh) + try: + index = child.expect(['s password:', # 0 + 'ERROR: No identities found', # 1 + 'All keys were skipped because they already exist on the remote system', # 2 + 'Could not resolve hostname', # 3 + pexpect.EOF],timeout=timeout) # final + except TIMEOUT: + print ("Exceeded {} s timeout".format(timeout)) + return False + + if index == 0: + child.sendline(password) + try: + child.expect("Now try logging into",timeout=5) + except: + print("Failed") + print("Please check your username and/or password") + return False + print("Ok") + child.close() + return True + elif index == 1: + print("Failed") + print("Looks like the key pair is not present in ~/.ssh folder") + return False + elif index == 2: + print("Keys are already there") + return True + elif index == 3: + print("Failed") + print("Unknown hostname") + return False + else: + print ("Failed") + print ("Unknown problem") + print (child.before, child.after) + child.close() + return False + + def _configure_proxy(self, password, proxy_password): + # if proxy IS required + if self._use_proxy.value: + # again some standard checks if proxy server parameters are provided + if self.proxy_hostname is None: # hostname + print("Please specify the proxy server hostname") + return False, '' + # if proxy username and password must be different from the main computer - they should be provided + if self._use_diff_proxy_username.value: + # check username + if not self.proxy_username is None: + proxy_username = self.proxy_username + else: + print("Please specify the proxy server username") + return False, '' + # check password + if len(proxy_password.strip()) == 0: + print("Please specify the proxy server password") + return False, '' + else: # if username and password are the same as for the main computer + proxy_username = self.username + proxy_password = password + # make proxy server known + if not self.is_host_known(self.proxy_hostname): + self._make_host_known(self.proxy_hostname) + # Finally trying to connect + if self._send_pubkey(self.proxy_hostname, proxy_username, proxy_password): + return True, proxy_username + '@' + self.proxy_hostname + else: + print ("Could not send public key to {} (proxy server).".format(self.proxy_hostname)) + return False, '' + # if proxy is NOT required + else: + return True, '' + + def on_setup_ssh(self, b): + """ATTENTION: modifying the order of operations in this function can lead to unexpected problems""" + with self._setup_ssh_out: + clear_output() + self._ssh_keygen() + + #temporary passwords + password = self.__password + proxy_password = self.__proxy_password + + # step 1: if hostname is not provided - do not do anything + if self.hostname is None: # check hostname + print("Please specify the computer hostname") + return + + # step 2: check if password-free access was enabled earlier + if self.can_login(): + print ("Password-free access is already enabled") + # it can still happen that password-free access is enabled + # but host is not present in the config file - fixing this + if not self.is_in_config(): + self._write_ssh_config() # we do not use proxy here, because if computer + # can be accessed without any info in the config - proxy is not needed. + self.setup_counter += 1 # only if config file has changed - increase setup_counter + return + + # step 3: if can't login already, chek whether all required information is provided + if self.username is None: # check username + print("Please enter your ssh username") + return + if len(password.strip()) == 0: # check password + print("Please enter your ssh password") + return + + # step 4: get the right commands to access the proxy server (if provided) + success, proxycmd = self._configure_proxy(password, proxy_password) + if not success: + return + + # step 5: make host known by ssh on the proxy server + if not self.is_host_known(): + self._make_host_known(self.hostname,['ssh']+[proxycmd] if proxycmd else []) + + # step 6: sending public key to the main host + if not self._send_pubkey(self.hostname, self.username, password, proxycmd): + print ("Could not send public key to {}".format(self.hostname)) + return + + # step 7: modify the ssh config file if necessary + if not self.is_in_config(): + self._write_ssh_config(proxycmd=proxycmd) + # TODO: add a check if new config is different from the current one. If so + # infrom the user about it. + + # step 8: final check + if self.can_login(): + self.setup_counter += 1 + print("Automatic ssh setup successful :-)") + return + else: + print("Automatic ssh setup failed, sorry :-(") + return + + def on_use_proxy_change(self, b): + if self._use_proxy.value: + self._proxy_ssh_box.layout.visibility = 'visible' + else: + self._proxy_ssh_box.layout.visibility = 'hidden' + self._use_diff_proxy_username.value = False + + def on_use_diff_proxy_username_change(self, b): + if self._use_diff_proxy_username.value: + self._proxy_user_password_box.layout.visibility = 'visible' + else: + self._proxy_user_password_box.layout.visibility = 'hidden' + +# Keep this function only because it might be used later. +# What it does: looks inside .ssh/config file and loads computer setup from +# there (if present) +# def _get_from_config(self, b): +# config = parse_sshconfig(self.hostname) +# if 'user' in config: +# self._inp_username.value = config['user'] +# else: +# self._inp_username.value = '' +# if 'proxycommand' in config: +# self._use_proxy.value = True +# proxy = ''.join([ s for s in config['proxycommand'].split() if '@' in s]) +# username, hostname = proxy.split('@') +# self._inp_proxy_address.value = hostname +# if username != self.username: +# self._use_diff_proxy_username.value = True +# self.proxy_username = username +# else: +# self._use_proxy.value = False + + @property + def __password(self): + """Returning the password and immediately destroying it""" + passwd = copy(self._inp_password.value) + self._inp_password.value = '' + return passwd + + @property + def __proxy_password(self): + """Returning the password and immediately destroying it""" + passwd = copy(self._inp_proxy_password.value) + self._inp_proxy_password.value = '' + return passwd + + @property + def hostname(self): + if len(self._inp_computer_hostname.value.strip()) == 0: # check hostname + return None + else: + return self._inp_computer_hostname.value + + @hostname.setter + def hostname(self, hostname): + self._inp_computer_hostname.value = hostname + + @property + def username(self): + """Loking for username in user's input and config file""" + if len(self._inp_username.value.strip()) == 0: # if username provided by user + if not self.hostname is None: + config = parse_sshconfig(self.hostname) + if 'user' in config: # if username is present in the config file + return config['user'] + else: + return None + else: + return self._inp_username.value + + @username.setter + def username(self, username): + self._inp_username.value = username + + @property + def proxy_hostname(self): + if len(self._inp_proxy_address.value.strip()) == 0: + return None + else: + return self._inp_proxy_address.value + + @proxy_hostname.setter + def proxy_hostname(self, proxy_hostname): + self._use_proxy.value = True + self._inp_proxy_address.value = proxy_hostname + + @property + def proxy_username(self): + if len(self._inp_proxy_username.value.strip()) == 0: + return None + else: + return self._inp_proxy_username.value + + @proxy_username.setter + def proxy_username(self, proxy_username): + self._use_proxy.value = True + self._use_diff_proxy_username.value = True + self._inp_proxy_username.value = proxy_username + +class AiidaComputerSetup(ipw.VBox): + def __init__(self, **kwargs): + from aiida.transport import Transport + from aiida.scheduler import Scheduler + style = {"description_width":"200px"} + + # list of widgets to be displayed + self._btn_setup_comp = ipw.Button(description="Setup computer") + self._btn_setup_comp.on_click(self._on_setup_computer) + self._inp_computer_name = ipw.Text(value='', + placeholder='Will only be used within AiiDA', + description="AiiDA computer name:", + layout=ipw.Layout(width="500px"), + style=style) + self._computer_hostname = ipw.Dropdown(description="Select among configured hosts:", + layout=ipw.Layout(width="500px"), + style=style) + self._inp_computer_description = ipw.Text(value='', + placeholder='No description (yet)', + description="Computer description:", + layout=ipw.Layout(width="500px"), + style=style) + self._computer_workdir = ipw.Text(value='/scratch/{username}/aiida_run', + description="Workdir:", + layout=ipw.Layout(width="500px"), + style=style) + self._computer_mpirun_cmd = ipw.Text(value='mpirun -n {tot_num_mpiprocs}', + description="Mpirun command:", + layout=ipw.Layout(width="500px"), + style=style) + self._computer_ncpus = ipw.IntText(value=12, + step=1, + description='Number of CPU(s) per node:', + layout=ipw.Layout(width="270px"), + style=style) + self._transport_type = ipw.Dropdown(value='ssh', + options=Transport.get_valid_transports(), + description="Transport type:", + style=style) + self._scheduler = ipw.Dropdown(value='slurm', + options=Scheduler.get_valid_schedulers(), + description="Scheduler:", + style=style) + self._prepend_text = ipw.Textarea(placeholder='Text to prepend to each command execution', + description='Prepend text:', + layout=ipw.Layout(width="400px") + ) + self._append_text = ipw.Textarea(placeholder='Text to append to each command execution', + description='Append text:', + layout=ipw.Layout(width="400px") + ) + self._btn_test = ipw.Button(description="Test computer") + self._btn_test.on_click(self.test) + + self._setup_comp_out = ipw.Output(layout=ipw.Layout(width="500px")) + self._test_out = ipw.Output(layout=ipw.Layout(width="500px")) + + # getting the list of available computers + self.get_available_computers() + + # Check if some settings were already provided + self._predefine_settings(**kwargs) + children =[ipw.HBox([ipw.VBox([self._inp_computer_name, + self._computer_hostname, + self._inp_computer_description, + self._computer_workdir, + self._computer_mpirun_cmd, + self._computer_ncpus, + self._transport_type, + self._scheduler]), + ipw.VBox([self._prepend_text, + self._append_text])]), + ipw.HBox([self._btn_setup_comp, self._btn_test]), + ipw.HBox([self._setup_comp_out, self._test_out]), + ] + super(AiidaComputerSetup, self).__init__(children, **kwargs) + + def _predefine_settings(self, **kwargs): + for key, value in kwargs.items(): + if hasattr(self, key): + setattr(self, key, value) + else: + raise AttributeError("'{}' object has no attribute '{}'".format(self, key)) + + def get_available_computers(self, b=None): + fn = path.expanduser("~/.ssh/config") + if not path.exists(fn): + return [] + cfglines = open(fn).readlines() + self._computer_hostname.options = [line.split()[1] for line in cfglines if 'Host' in line] + + def _configure_computer(self): + """create DbAuthInfo""" + print("Configuring '{}'".format(self.name)) + sshcfg = parse_sshconfig(self.hostname) + authparams = { + 'compress': True, + 'gss_auth': False, + 'gss_deleg_creds': False, + 'gss_host': self.hostname, + 'gss_kex': False, + 'key_policy': 'WarningPolicy', + 'load_system_host_keys': True, + 'port': 22, + 'timeout': 60, + } + if 'user' in sshcfg: + authparams['username'] = sshcfg['user'] + else: + print ("SSH username is not provided, please run `verdi computer configure {}` " + "from the command line".format(self.name)) + return + if 'proxycommand' in sshcfg: + authparams['proxy_command'] = sshcfg['proxycommand'] + aiidauser = get_automatic_user() + authinfo = DbAuthInfo(dbcomputer=Computer.get(self.name).dbcomputer, aiidauser=aiidauser) + authinfo.set_auth_params(authparams) + authinfo.save() + print(check_output(['verdi', 'computer', 'show', self.name])) + + def _on_setup_computer(self, b): + with self._setup_comp_out: + clear_output() + if self.name is None: # check hostname + print("Please specify the computer name (for AiiDA)") + return + try: + computer = Computer.get(self.name) + print("A computer called {} already exists.".format(self.name)) + return + except NotExistent: + pass + + print("Creating new computer with name '{}'".format(self.name)) + computer = Computer(name=self.name) + computer.set_hostname(self.hostname) + computer.set_description(self.description) + computer.set_enabled_state(True) + computer.set_transport_type(self.transport_type) + computer.set_scheduler_type(self.scheduler) + computer.set_workdir(self.workdir) + computer.set_mpirun_command(self.mpirun_cmd.split()) + computer.set_default_mpiprocs_per_machine(self.ncpus) + if self._prepend_text.value: + computer.set_prepend_text(self.prepend_text) + if self._append_text.value: + computer.set_append_text(self.append_text) + computer.store() + self._configure_computer() + + def test(self, b=None): + with self._test_out: + clear_output() + print(check_output(['verdi', 'computer', 'test', '--traceback', self.name])) + + @property + def name(self): + if len(self._inp_computer_name.value.strip()) == 0: # check hostname + return None + else: + return self._inp_computer_name.value + + @name.setter + def name(self, name): + self._inp_computer_name.value = name + + @property + def hostname(self): + if self._computer_hostname.value is None or len(self._computer_hostname.value.strip()) == 0: # check hostname + return None + else: + return self._computer_hostname.value + + @hostname.setter + def hostname(self, hostname): + if hostname in self._computer_hostname.options: + self._computer_hostname.value = hostname + + @property + def description(self): + return self._inp_computer_description.value + + @description.setter + def description(self, description): + self._inp_computer_description.value = description + + @property + def workdir(self): + return self._computer_workdir.value + + @workdir.setter + def workdir(self, workdir): + self._computer_workdir.value = workdir + + @property + def mpirun_cmd(self): + return self._computer_mpirun_cmd.value + + @mpirun_cmd.setter + def mpirun_cmd(self, mpirun_cmd): + self._computer_mpirun_cmd.value = mpirun_cmd + + @property + def ncpus(self): + return self._computer_ncpus.value + + @ncpus.setter + def ncpus(self, ncpus): + self._computer_ncpus.value = int(ncpus) + + @property + def transport_type(self): + return self._transport_type.value + + @transport_type.setter + def transport_type(self, transport_type): + if transport_type in self._transport_type.options: + self._transport_type.value = transport_type + @property + def scheduler(self): + return self._scheduler.value + + @scheduler.setter + def scheduler(self, scheduler): + if scheduler in self._scheduler.options: + self._scheduler.value = scheduler + + @property + def prepend_text(self): + return self._prepend_text.value + + @prepend_text.setter + def prepend_text(self, prepend_text): + self._prepend_text.value = prepend_text + + @property + def append_text(self): + return self._append_text.value + + @append_text.setter + def append_text(self, append_text): + self._append_text.value = append_text + +class ComputerDropdown(ipw.VBox): + def __init__(self, text='Select computer:', **kwargs): + """ Dropdown for Codes for one input plugin. + + :param text: Text to display before dropdown + :type text: str + """ + + self._dropdown = ipw.Dropdown(options=[], description=text, style={'description_width': 'initial'}, disabled=True) + self._btn_refresh = ipw.Button(description="Refresh", layout=ipw.Layout(width="70px")) + + self._setup_another = ipw.HTML(value="""Setup new computer""", + layout = {'margin': '0px 0px 0px 250px'}) + self._btn_refresh.on_click(self._refresh) + self.output = ipw.Output() + + children = [ipw.HBox([self._dropdown, self._btn_refresh]), + self._setup_another, + self.output] + + super(ComputerDropdown, self).__init__(children=children, **kwargs) + + self._refresh() + + def _get_computers(self): + from aiida.orm.querybuilder import QueryBuilder + current_user = get_automatic_user() + + qb = QueryBuilder() + qb.append( + Computer, filters={'enabled': True}, project=['*'], tag='computer') + + results = qb.all() + + # only computers configured for the current user + results = [r for r in results if r[0].is_user_configured(current_user)] + + self._dropdown.options = {r[0].name:r[0] for r in results} + + def _refresh(self, b=None): + with self.output: + clear_output() + self._get_computers() + if not self.computers: + print("No computers found.") + self._dropdown.disabled = True + else: + self._dropdown.disabled = False + + @property + def computers(self): + return self._dropdown.options + + @property + def selected_computer(self): + try: + return self._dropdown.value + except KeyError: + return None + + @selected_computer.setter + def selected_computer(self, selected_computer): + if selected_computer in self.computers: + self._dropdown.label = selected_computer + diff --git a/aiidalab_widgets_base/display.py b/aiidalab_widgets_base/display.py new file mode 100644 index 000000000..ce4f0489d --- /dev/null +++ b/aiidalab_widgets_base/display.py @@ -0,0 +1,26 @@ +from __future__ import print_function + +import importlib +from IPython.display import display + +AIIDA_VISUALIZER_MAPPING = { + 'data.parameter.ParameterData.' : 'ParameterDataVisualizer', + 'data.structure.StructureData.' : 'StructureDataVisualizer', + 'data.cif.CifData.' : 'StructureDataVisualizer', + 'data.folder.FolderData.' : 'FolderDataVisualizer', + 'data.array.bands.BandsData.' : 'BandsDataVisualizer', +} + +def aiidalab_display(obj, downloadable=True, **kwargs): + """Display AiiDA data types in Jupyter notebooks. + + :param downloadable: If True, add link/button to download content of displayed AiiDA object. + + Defers to IPython.display.display for any objects it does not recognize. + """ + from aiidalab_widgets_base import aiida_visualizers + try: + visualizer = getattr(aiida_visualizers, AIIDA_VISUALIZER_MAPPING[obj.type]) + display(visualizer(obj, downloadable=downloadable), **kwargs) + except KeyError: + display(obj, **kwargs) \ No newline at end of file diff --git a/aiidalab_widgets_base/search_qb.py b/aiidalab_widgets_base/search_qb.py index 09a208f1e..94c70b57b 100644 --- a/aiidalab_widgets_base/search_qb.py +++ b/aiidalab_widgets_base/search_qb.py @@ -1,8 +1,3 @@ -from aiida import load_dbenv, is_dbenv_loaded -from aiida.backends import settings -if not is_dbenv_loaded(): - load_dbenv(profile=settings.AIIDADB_PROFILE) - from aiida.orm.querybuilder import QueryBuilder from aiida.orm.data.structure import StructureData diff --git a/aiidalab_widgets_base/structures.py b/aiidalab_widgets_base/structures.py index 2dd905687..f327b6a91 100644 --- a/aiidalab_widgets_base/structures.py +++ b/aiidalab_widgets_base/structures.py @@ -1,6 +1,6 @@ from __future__ import print_function - from __future__ import absolute_import + import ase.io import ipywidgets as ipw from fileupload import FileUploadWidget @@ -54,11 +54,6 @@ def __init__(self, text="Upload Structure", node_class=None, **kwargs): self.file_upload.observe(self._on_file_upload, names='data') self.btn_store.on_click(self._on_click_store) - from aiida import load_dbenv, is_dbenv_loaded - from aiida.backends import settings - if not is_dbenv_loaded(): - load_dbenv(profile=settings.AIIDADB_PROFILE) - # pylint: disable=unused-argument def _on_file_upload(self, change): self.tmp_folder = tempfile.mkdtemp() diff --git a/aiidalab_widgets_base/structures_multi.py b/aiidalab_widgets_base/structures_multi.py index 699bd5aa8..e88de5a9e 100644 --- a/aiidalab_widgets_base/structures_multi.py +++ b/aiidalab_widgets_base/structures_multi.py @@ -68,12 +68,6 @@ def __init__(self, text="Upload Zip or Tar archive", node_class=None, **kwargs): self.btn_store_all.on_click(self._on_click_store_all) self.btn_store_selected.on_click(self._on_click_store_selected) - # create aiida-related things - from aiida import load_dbenv, is_dbenv_loaded - from aiida.backends import settings - if not is_dbenv_loaded(): - load_dbenv(profile=settings.AIIDADB_PROFILE) - # function to be called when selection_slider changes def change_structure(self): if self.selection_slider.value is None: diff --git a/cod.ipynb b/cod.ipynb index cf8b2b23e..ae8ec38d1 100644 --- a/cod.ipynb +++ b/cod.ipynb @@ -266,7 +266,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.13" + "version": "2.7.15rc1" } }, "nbformat": 4, diff --git a/codes.ipynb b/codes.ipynb index d009889a0..a9fb7ea0f 100644 --- a/codes.ipynb +++ b/codes.ipynb @@ -40,7 +40,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", - "version": "2.7.15" + "version": "2.7.15rc1" } }, "nbformat": 4, diff --git a/metadata.json b/metadata.json index be3b3e17d..e7544ce63 100644 --- a/metadata.json +++ b/metadata.json @@ -2,7 +2,7 @@ "title": "AiiDA Lab Widgets", "description": "Reusable widgets for applications in the AiiDA Lab.", "authors": "AiiDA Team", - "version": "0.2.0a2", + "version": "0.3.0b1", "logo": "miscellaneous/logos/aiidalab.png", "state": "stable" } diff --git a/miscellaneous/images/computer.png b/miscellaneous/images/computer.png new file mode 100644 index 000000000..9e0465273 Binary files /dev/null and b/miscellaneous/images/computer.png differ diff --git a/setup_code.ipynb b/setup_code.ipynb new file mode 100644 index 000000000..284641a8d --- /dev/null +++ b/setup_code.ipynb @@ -0,0 +1,62 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import urlparse\n", + "from aiidalab_widgets_base import AiiDACodeSetup, extract_aiidacodesetup_arguments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "parsed_url = urlparse.parse_qs(urlparse.urlsplit(jupyter_notebook_url).query)\n", + "args = extract_aiidacodesetup_arguments(parsed_url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "code = AiiDACodeSetup(**args)\n", + "display(code)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/setup_computer.ipynb b/setup_computer.ipynb new file mode 100644 index 000000000..5ec69f1e6 --- /dev/null +++ b/setup_computer.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup a computer with AiiDA " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import urlparse\n", + "\n", + "from aiidalab_widgets_base import SshComputerSetup, extract_sshcomputersetup_arguments\n", + "from aiidalab_widgets_base import AiidaComputerSetup, extract_aiidacomputer_arguments" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "parsed_url = urlparse.parse_qs(urlparse.urlsplit(jupyter_notebook_url).query)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1: Setup ssh\n", + "Note: The password is used only to set up the ssh connection and is never stored." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "args = extract_sshcomputersetup_arguments(parsed_url)\n", + "sshcomputer = SshComputerSetup(**args)\n", + "display(sshcomputer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 2: Setup & Test AiiDA Computer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "args = extract_aiidacomputer_arguments(parsed_url)\n", + "aiidacomputer = AiidaComputerSetup(**args)\n", + "sshcomputer.observe(aiidacomputer.get_available_computers, names=['setup_counter'])\n", + "display(aiidacomputer)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 2", + "language": "python", + "name": "python2" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.15rc1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/start.py b/start.py index ce84ab1a0..007a5b389 100644 --- a/start.py +++ b/start.py @@ -4,13 +4,20 @@ + + +
AiiDA Lab widgets
"""