diff --git a/README.md b/README.md index d9b006e..fe348db 100644 --- a/README.md +++ b/README.md @@ -2,46 +2,92 @@ notebook-molecular-visualization =============================== [![PyPI version](https://badge.fury.io/py/nbmolviz.svg)](https://badge.fury.io/py/nbmolviz) -A Python widgets library for 2D and 3D molecular visualization in Jupyter notebooks +Jupyter notebook add-ons for the [Molecular Design Toolkit](https://github.com/Autodesk/molecular-design-toolkit). NBMolViz provides visualization and interactivity for 3D Molecular Structures in Jupyter notebooks. + +After installing it, you'll never need to use the NBMolViz package directly. It's instead called through MDT to provide enhanced functionality in notebooks. ## Installation +When you install `nbmolviz`, you'll need to both install the python library _and_ enable the notebook extensions. +1. **Install the python library:** +```bash $ pip install nbmolviz - $ jupyter nbextension enable --python --system nbmolviz - $ jupyter nbextension enable --python --system widgetsnbextension +``` + +2. **Activate notebook extensions:** +To enable for your user account: +```bash + $ python -m nbmolviz activate --user +``` + +To enable within your current virtual environment: +```bash + $ python -m nbmolviz activate --sys-prefix +``` +To globally enable for all users (use with caution! This may require `sudo`): +```bash + $ python -m nbmolviz activate --global +``` + +## Upgrading from older versions + +1. **Upgrade the library to the newest version** + $ pip install --upgrade nbmolviz + +2. **Remove old notebook extensions (you will be notified if it's necessary to run with `sudo`)**: +```bash + $ python -m nbmolviz clean-all +``` ## Examples -To draw an OpenBabel molecule: +Draw a small molecule: +```python +import moldesign as mdt +mol = mdt.from_name('ethylene') +mol.draw() +``` + + +Draw a protein: ```python -import nbmolviz -import pybel -benzene = pybel.read_string('smi','c1cccc1').next() -nbmolviz.visualize(benzene) +import moldesign as mdt +mol = mdt.from_pdb('3aid') +mol.draw() ``` + +Interactively select atoms (the currently selected atoms will be available as `selector.selected_atoms`) +```python +import moldesign as mdt +mol = mdt.from_pdb('3aid') +selector = mdt.widgets.ResidueSelector(mol) +selector +``` + + ## Dev install Requires npm. - $ git clone https://github.com/autodesk/notebook-molecular-visualization.git + $ git clone https://github.com/autodesk/notebook-molecular-visualization $ cd notebook-molecular-visualization + # ./set_filters.sh # tells git to clean up notebooks before committing $ python setup.py jsdeps $ pip install -e . $ jupyter nbextension install --py --symlink --user nbmolviz $ jupyter nbextension enable --py --user nbmolviz + $ cd tests/galileo && npm install This will build your widgets into a folder at `notebook-molecular-visualization/nbmolviz/static` During development, to see the effects of changes to any javascript files (in notebook-molecular/visualization/js/src), run `python setup.py jsdeps` and reload any notebook browser windows. -## Tests -Run tests with: - - pytest nbmolviz/_tests +## To run visual tests +`cd tests/nb && ../galileo/bin/galileo --launchnb` ## Releasing a new version Travis automatically releases commits that are tagged, so to trigger a new release, just do: diff --git a/img/protein.png b/img/protein.png new file mode 100644 index 0000000..6a78f9f Binary files /dev/null and b/img/protein.png differ diff --git a/img/selector.png b/img/selector.png new file mode 100644 index 0000000..305b57a Binary files /dev/null and b/img/selector.png differ diff --git a/img/smallmol.png b/img/smallmol.png new file mode 100644 index 0000000..abdc336 Binary files /dev/null and b/img/smallmol.png differ diff --git a/nbmolviz/__main__.py b/nbmolviz/__main__.py new file mode 100644 index 0000000..4ab9971 --- /dev/null +++ b/nbmolviz/__main__.py @@ -0,0 +1,107 @@ +from __future__ import print_function, absolute_import, division +from future.builtins import * +from future import standard_library +standard_library.install_aliases() + +# Copyright 2017 Autodesk Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import argparse + +class SmartFormatter(argparse.HelpFormatter): + """ + From https://stackoverflow.com/a/22157136/1958900 + """ + def _split_lines(self, text, width): + if text.startswith('R|'): + return text[2:].splitlines() + # this is the RawTextHelpFormatter._split_lines + return argparse.HelpFormatter._split_lines(self, text, width) + +def main(): + from . import install, _version + parser = argparse.ArgumentParser('python -m nbmolviz', formatter_class=SmartFormatter) + + parser.add_argument('command', choices=['activate', 'uninstall', 'check'], + help='R|activate - install and enable nbmolviz\n' + 'uninstall - remove old nbmolviz installations\n' + 'check - check installed versions\n') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--sys-prefix', '--env', '--venv', action='store_true', + help="Apply command to your virtual environment") + group.add_argument('--user', action='store_true', + help="Apply command to all of this user's environments") + group.add_argument('--sys', '--global', '--system', action='store_true', + help="Apply command to the global system configuration") + + args = parser.parse_args() + + if args.command == 'check': + print('Expected version:', _version.get_versions()['version']) + versions = install.get_installed_versions('nbmolviz-js', True) + foundone = False + for key, vers in versions.items(): + if vers.installed: + if foundone: + print('--') + else: + foundone = True + print('Installed notebook extension locations:\n') + + if key == 'user': + print('Environment:', 'current user') + elif key == 'environment': + print('Environment:', 'current virtual environment') + else: + assert key == 'system' + print('Environment:', 'global / system-wide') + + print('Version: ', vers.version) + print('Location: ', vers.path) + + if not foundone: + print("NBMolViz Jupyter extensions are not installed in any current environments") + + elif args.command == 'activate': + if not (args.sys_prefix or args.user or args.sys): + print('Please indicate which environment to activate nbmolviz in.') + parser.print_help() + sys.exit(10) + + if args.sys_prefix: + install.activate('--sys-prefix') + if args.user: + install.activate('--user') + if args.sys: + install.activate('--system') + + elif args.command == 'uninstall': + if not (args.sys_prefix or args.user or args.sys): + args.sys_prefix = args.user = args.sys = True + + if args.sys_prefix: + install.uninstall('--sys-prefix') + if args.user: + install.uninstall('--user') + if args.sys: + install.uninstall('--system') + + else: + assert False, "parser failure" + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/nbmolviz/install.py b/nbmolviz/install.py index 1d25c1c..ac7b75c 100644 --- a/nbmolviz/install.py +++ b/nbmolviz/install.py @@ -11,9 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import sys import os +import sys import collections +import subprocess import nbmolviz @@ -28,24 +29,13 @@ NbExtVersion = collections.namedtuple('NbExtVersion', 'name installed path version'.split()) -def nbextension_check(extname, getversion): +def get_installed_versions(extname, getversion): """ Check if the required NBExtensions are installed. If not, prompt user for action. """ import jupyter_core.paths as jupypaths from notebook import nbextensions - # TODO: implement the following: - # 0. Resolve jupyter nbextension search path, find installed - # jupyter-js-widgets and nbmolviz-js extensions and their versions - # 1. If extension with correct version is installed and enabled, do nothing, we're done - # 2. If correct extensions are installed but not enabled, prompt user to enable - # 3. If there are multiple copies, and the wrong version(s) are enabled, prompt user to - # enable the right ones - # 4. If not installed, prompt user to install/enable in the first writeable instance of - # the following: sys-prefix, user-dir, systemwide - # see https://github.com/ipython-contrib/jupyter_contrib_nbextensions/blob/master/src/jupyter_contrib_nbextensions/install.py - - installed = {k: nbextensions.check_nbextension('nbmolviz-js', **kwargs) for k,kwargs in EXTENSION_KWARGS.items()} + installed = {k: nbextensions.check_nbextension(extname, **kwargs) for k,kwargs in EXTENSION_KWARGS.items()} jupyter_dir = {'user': jupypaths.jupyter_data_dir(), 'environment': jupypaths.ENV_JUPYTER_PATH[0], 'system': jupypaths.SYSTEM_JUPYTER_PATH[0]} @@ -63,12 +53,43 @@ def nbextension_check(extname, getversion): with open(versionfile, 'r') as pfile: versions[k] = pfile.read().strip() else: - versions[k] = 'pre-0.8' + versions[k] = 'pre-0.7' return {k: NbExtVersion(extname, installed[k], paths.get(k, None), versions.get(k, None)) for k in installed} +def activate(flags): + try: + _jnbextrun('install', 'widgetsnbextension', flags) + _jnbextrun('enable', 'widgetsnbextension', flags) + except subprocess.CalledProcessError as exc: + if exc.returncode == 2: + print(('ERROR - failed to enable the widget extensions with %s.' % flags) + + ' Try rerunning the command with \"sudo\"!') + sys.exit(2) + + _jnbextrun('install', 'nbmolviz', flags) + _jnbextrun('enable', 'nbmolviz', flags) + + +def uninstall(flags): + try: + _jnbextrun('disable', 'nbmolviz', flags) + _jnbextrun('uninstall', 'nbmolviz', flags) + except subprocess.CalledProcessError as exc: + if exc.returncode == 2: + print(('ERROR - failed to uninstall the widget extensions with %s.' % flags) + + ' Try rerunning the command with \"sudo\"!') + sys.exit(2) + + +def _jnbextrun(cmd, lib, flags): + shellcmd = ['jupyter', 'nbextension', cmd, '--py', flags, lib] + print('> %s' % ' '.join(shellcmd)) + subprocess.check_call(shellcmd) + + def find_nbmolviz_extension(extname): import jupyter_core.paths as jupypaths for extpath in jupypaths.jupyter_path('nbextensions'): @@ -77,4 +98,3 @@ def find_nbmolviz_extension(extname): return extpath else: return None - diff --git a/tests/nb/test_compute_widgets.ipynb b/tests/nb/test_compute_widgets.ipynb new file mode 100644 index 0000000..3986c90 --- /dev/null +++ b/tests/nb/test_compute_widgets.ipynb @@ -0,0 +1,98 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#! test_about\n", + "import moldesign as mdt\n", + "ui = mdt.about()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#! test_pyccc_status_widget\n", + "import pyccc\n", + "engine = pyccc.engines.Docker()\n", + "infiles = {'a.txt':\"the only thing in this file is 'a'\"}\n", + "job = engine.launchp(image='alpine', command=\"cp a.txt b.txt\", inputs=infiles)\n", + "job.get_display_object()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#! test_logging_status_widgets\n", + "import moldesign as mdt\n", + "mdt.from_name('benzene')\n", + "mdt.from_name('hexane')\n", + "mdt.from_name('error_has_error')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as ipy" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ipy.Textarea(fontfamily='monospace', font_size=24, value='abc', font_family='monospace',\n", + " layout=ipy.Layout(font_size=24))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "_14.style" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/tests/py/test_nbmolviz.py b/tests/py/test_nbmolviz.py index 720688d..01914b0 100644 --- a/tests/py/test_nbmolviz.py +++ b/tests/py/test_nbmolviz.py @@ -2,6 +2,7 @@ the supporting functionality """ from past.builtins import unicode +import subprocess import pytest @@ -31,3 +32,7 @@ def test_generating_cubefile_works(wfn_viewer): grid, values = wfn_viewer._calc_orb_grid(wfn_viewer.mol.wfn.orbitals.canonical[1]) cb = wfn_viewer._grid_to_cube(grid, values) assert isinstance(cb, unicode) + + +def test_install_checks_doesnt_crash(): + subprocess.check_call('python -m nbmolviz check'.split())