Skip to content

Commit

Permalink
Merge pull request #184 from CasperWA/close_183_create-voila-extra
Browse files Browse the repository at this point in the history
Clean up dependencies.
Create a server extra that installs Voilà and ASE.
Remove unused dependencies: App Mode, JupyterLab, NumPy.

Update README accordingly and ensure the CLI returns a non-zero code and a helpful message on how to make it work if Voilà is not installed.

The CLI is now fully tested.
  • Loading branch information
CasperWA authored Oct 14, 2020
2 parents 9679072 + a6fdbfb commit 327112d
Show file tree
Hide file tree
Showing 14 changed files with 294 additions and 51 deletions.
1 change: 1 addition & 0 deletions .github/static/update_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ echo "\n### Commit updated files ###"
git add setup.py
git add optimade_client/__init__.py
git add optimade_client/informational.py
git add optimade_client/cli/run.py
git commit -m "Release ${GITHUB_REF#refs/tags/}"
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,20 @@ jobs:
with:
python-version: ${{ matrix.python-version}}

- name: Install dependencies
- name: Install initial dependencies
run: |
python -m pip install -U pip
pip install -U setuptools
pip install -e .[testing]
- name: PyTest
run: pytest -vvv --cov=optimade_client --cov-report=xml tests/
run: pytest -vvv --cov=optimade_client --cov-report=xml --cov-append tests/

- name: Install server dependencies
run: pip install -e .[server]

- name: PyTest (with 'server' extra)
run: pytest --cov=optimade_client --cov-report=xml --cov-append tests/cli/

- name: Upload coverage to Codecov
if: matrix.python-version == 3.8 && github.repository == 'CasperWA/voila-optimade-client'
Expand Down
66 changes: 53 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ For AiiDAlab, use the App Store in the [Home App](https://github.com/aiidalab/ai

## Usage

### Default
### AiiDAlab

To use the OPTIMADE structure importer in your own AiiDAlab application write the following:

```python
from optimade_client import OptimadeQueryWidget
from aiidalab_widget_base import OptimadeQueryWidget
from aiidalab_widgets_base.viewers import StructureDataViewer
from ipywidgets import dlink

structure_query = OptimadeQueryWidget()
structure_viewer = StructureDataViewer()

# Save to `_` in order to suppress output in App Mode
# Save to `_` in order to suppress output
_ = dlink((structure_query, 'structure'), (structure_viewer, 'structure'))

display(structure_query)
Expand All @@ -47,52 +47,92 @@ See the [OPTIMADE API specification](https://github.com/Materials-Consortia/OPTi
In order to delve deeper into the details of a particular structure, you can also import and display `OptimadeResultsWidget`.
See the notebook [`OPTIMADE Client.ipynb`](OPTIMADE%20Client.ipynb) for an example of how to set up a general purpose OPTIMADE importer.

### Embedded
#### Embedded

The query widget may also be embedded into another app.
For this a more "minimalistic" version of the widget can be used by passing `embedded=True` upon initiating the widget, i.e., `structure_query = OptimadeQueryWidget(embedded=True)`.

Everything else works the same - so you would still have to link up the query widget to the rest of your app.

### General Jupyter notebook

The package's widgets can be used in any general Jupyter notebook as well as AiiDAlab.
Example:

```python
from optimade_client import
OptimadeQueryProviderWidget,
OptimadeQueryFilterWidget,
OptimadeSummaryWidget
from ipywidgets import dlink

database_selector = OptimadeQueryProviderWidget()
structure_query = OptimadeQueryFilterWidget()
structure_viewer = OptimadeSummaryWidget()

# Save to `_` in order to suppress output
_ = dlink((database_selector, 'database'), (structure_query, 'database'))
_ = dlink((structure_query, 'structure'), (structure_viewer, 'entity'))

display(database_selector, structure_query, structure_viewer)
```

This will use the package's own structure viewer and summary widget.

Note, the `OptimadeQueryWidget` mentioned above is a special wrapper widget in AiiDAlab for the `OptimadeQueryProviderWidget` and `OptimadeQueryFilterWidget` widgets.

### Running application locally

First, you will need to install the package either from [PyPI](https://pypi.org/project/optimade-client) or by retrieving the git repository hosted on [GitHub](https://github.com/CasperWA/voila-optimade-client).

#### PyPI

```shell
$ pip install optimade-client
$ pip install optimade-client[server]
```

#### GitHub

```shell
$ git clone https://github.com/CasperWA/voila-optimade-client.git
$ cd voila-optimade-client
voila-optimade-client$ pip install .
voila-optimade-client$ pip install .[server]
```

If you wish to contribute to the application, you can install it in "editable" mode by using the `-e` flag: `pip install -e .`
Note, it is important to install the `server` extra in order to also install the `voila` package (and the `ase` package for a wider variety of download formats).

To now run the application (notebook) [`OPTIMADE Client.ipynb`](OPTIMADE%20Client.ipynb) you can simply run the command `optimade-client` in a terminal and go to the printed URL (usually <http://localhost:8866>) or pass the `--open-browser` option to let the program try to automatically open your default browser at the URL.
To now run the application (notebook) [`OPTIMADE Client.ipynb`](OPTIMADE%20Client.ipynb) you can simply run the command `optimade-client` in a terminal and go to the printed URL (usually <http://localhost:8866>) or pass the `--open-browser` option to let the program try to automatically open your default browser.

The application will be run in Voilà using Voilà's own `tornado`-based server.
The configuration will automatically be copied to Jupyter's configuration directory before starting the server.

```shell
$ optimade-client
...
[Voila] Voila is running at:
[Voila] Voilà is running at:
http://localhost:8866/
...
```

For a list of all options that can be passed to `optimade-client` use the `-h/--help` option.

## Contribute

If you wish to contribute to the application, you can install it in "editable" mode by using the `-e` flag: `pip install -e .[dev]`.
It is recommended that you use the GitHub-route mentioned above.

You should also install `pre-commit` in the cloned git repository by running:

```shell
voila-optimade-client$ pre-commit install
```

To start making contributions, fork the repository and create PRs.

## Configuration (Voilà)

For running the application (in Voilà) on Binder, the configuration file [`jupyter_config.json`](optimade_client/cli/static/jupyter_config.json) can be used.
If you wish to start the Voilà server locally with the same configuration, either copy the [`jupyter_config.json`](optimade_client/cli/static/jupyter_config.json) file to your Jupyter config directory, renaming it to `voila.json` or pass the configurations when you start the server using the CLI.
For running the application (in Voilà) on Binder, the configuration file [`jupyter_config.json`](optimade_client/jupyter_config.json) can be used.
If you wish to start the Voilà server locally with the same configuration, either copy the [`jupyter_config.json`](optimade_client/jupyter_config.json) file to your Jupyter config directory, renaming it to `voila.json` or pass the configurations when you start the server using the CLI.

> **Note**: `jupyter_config.json` is automatically copied over as `voila.json` when running the application using the `optimade-client` command.
Expand All @@ -108,7 +148,7 @@ Example of passing configurations when you start the Voilà server using the CLI
```shell
$ voila --enable_nbextensions=True --VoilaExecutePreprocessor.timeout=180 "OPTIMADE Client.ipynb"
...
[Voila] Voila is running at:
[Voila] Voilà is running at:
http://localhost:8866/
...
```
Expand All @@ -117,7 +157,7 @@ To see the full list of configurations you can call `voila` and pass `--help-all

## License

MIT. The terms of the license can be found in the LICENSE file.
MIT. The terms of the license can be found in the [LICENSE](LICENSE) file.

## Contact

Expand Down
4 changes: 0 additions & 4 deletions optimade_client/cli/options.py

This file was deleted.

41 changes: 28 additions & 13 deletions optimade_client/cli/run.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import argparse
import logging
import os
from pathlib import Path
from shutil import copyfile
import subprocess
import sys

from voila.app import main as voila
try:
from voila.app import main as voila
except ImportError:
voila = None

from optimade_client import __version__
from optimade_client.cli.options import LOGGING_LEVELS

LOGGING_LEVELS = [logging.getLevelName(level).lower() for level in range(0, 51, 10)]
VERSION = "2020.10.2" # Avoid importing optimade-client package

def main():

def main(args: list = None):
"""Run the OPTIMADE Client."""
parser = argparse.ArgumentParser(
description=main.__doc__,
Expand All @@ -20,7 +26,7 @@ def main():
"--version",
action="version",
help="Show the version and exit.",
version=f"OPTIMADE Client version {__version__}",
version=f"OPTIMADE Client version {VERSION}",
)
parser.add_argument(
"--log-level",
Expand All @@ -32,22 +38,30 @@ def main():
parser.add_argument(
"--debug",
action="store_true",
help="Will set the log-level to DEBUG. Note, parameter log-level takes precedence "
"if not 'info'!",
help="Will overrule log-level option and set the log-level to 'debug'.",
)
parser.add_argument(
"--open-browser",
action="store_true",
help="Attempt to open a browser upon starting the Voilà tornado server.",
)

args = parser.parse_args()
args = parser.parse_args(args)
log_level = args.log_level
debug = args.debug
open_browser = args.open_browser

# Make sure Voilà is installed
if voila is None:
sys.exit(
"Voilà is not installed.\nPlease run:\n\n pip install optimade-client[server]\n\n"
"Or the equivalent, matching the installation in your environment, to install Voilà "
"(and ASE for a larger download format selection)."
)

# Rename jupyter_config.json to voila.json and copy it Jupyter's config dir
jupyter_config_dir = subprocess.getoutput("jupyter --config-dir")
Path(jupyter_config_dir).mkdir(parents=True, exist_ok=True)
copyfile(
Path(__file__).parent.parent.parent.joinpath("jupyter_config.json").resolve(),
f"{jupyter_config_dir}/voila.json",
Expand All @@ -60,13 +74,11 @@ def main():
check=False,
)

log_level = log_level.lower()
if debug and log_level == "info":
log_level = "debug"

argv = ["OPTIMADE Client.ipynb"]

if log_level == "debug":
if debug:
if log_level not in ("debug", "info"):
print("[OPTIMADE-Client] Overwriting requested log-level to: 'debug'")
os.environ["OPTIMADE_CLIENT_DEBUG"] = "True"
argv.append("--debug")
else:
Expand All @@ -75,4 +87,7 @@ def main():
if not open_browser:
argv.append("--no-browser")

if "--debug" not in argv:
argv.append(f"--Voila.log_level={getattr(logging, log_level.upper())}")

voila(argv)
25 changes: 21 additions & 4 deletions optimade_client/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

import nglview

from ase import Atoms as aseAtoms
try:
from ase import Atoms as aseAtoms
except ImportError:
aseAtoms = None

try:
from pymatgen import Molecule as pymatgenMolecule, Structure as pymatgenStructure
Expand Down Expand Up @@ -197,6 +200,7 @@ def __init__(self, button_style: Union[ButtonStyle, str] = None, **kwargs):
else:
self._button_style = ButtonStyle.DEFAULT

self._initialize_options()
self.dropdown = ipw.Dropdown(
options=("Select a format", {}), layout={"width": "auto"}
)
Expand Down Expand Up @@ -226,7 +230,21 @@ def _on_change_structure(self, change: dict):
self._update_options()
self.unfreeze()

def _update_options(self):
def _initialize_options(self) -> None:
"""Initialize options according to installed packages"""
for imported_object, adapter_format in [
(aseAtoms, "ase"),
(pymatgenStructure, "pymatgen"),
]:
if imported_object is None:
LOGGER.debug("%s not recognized to be installed.", adapter_format)
self._formats = [
option
for option in self._formats
if option[1].get("adapter_format", "") != adapter_format
]

def _update_options(self) -> None:
"""Update options according to chosen structure"""
# Disordered structures not usable with ASE
if "disorder" in self.structure.structure_features:
Expand All @@ -241,10 +259,9 @@ def _update_options(self):
if option[1].get("adapter_format", "") != "ase"
]
)
options.insert(0, ("Select a format", {}))
else:
options = sorted(self._formats)
options.insert(0, ("Select a format", {}))
options.insert(0, ("Select a format", {}))
LOGGER.debug("Will set the dropdown options to: %s", options)
self.dropdown.options = options

Expand Down
5 changes: 0 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
appdirs~=1.4.4
appmode~=0.8.0
ase~=3.20
cachecontrol[filecache]~=0.12.6
ipywidgets~=7.5
jupyterlab~=2.2
nglview~=2.7
numpy~=1.19
optimade~=0.12.1
pandas~=1.1
requests~=2.24
voila~=0.2.3
widget_periodictable~=2.1
2 changes: 2 additions & 0 deletions requirements_server.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ase~=3.20
voila~=0.2.3
9 changes: 6 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@
with open(MODULE_DIR.joinpath("requirements.txt")) as handle:
REQUIREMENTS = [f"{_.strip()}" for _ in handle.readlines() if " " not in _]

with open(MODULE_DIR.joinpath("requirements_server.txt")) as handle:
SERVER = [f"{_.strip()}" for _ in handle.readlines()]

with open(MODULE_DIR.joinpath("requirements_testing.txt")) as handle:
TESTING = [f"{_.strip()}" for _ in handle.readlines()]

with open(MODULE_DIR.joinpath("requirements_dev.txt")) as handle:
DEV = [f"{_.strip()}" for _ in handle.readlines()] + TESTING
DEV = [f"{_.strip()}" for _ in handle.readlines()] + TESTING + SERVER

setup(
name="optimade-client",
Expand All @@ -28,9 +31,9 @@
python_requires=">=3.6",
packages=find_packages(),
include_package_data=True,
package_data={"optimade_client": ["img/*.png", "cli/static/*.json"]},
package_data={"optimade_client": ["img/*.png", "*.json"]},
install_requires=REQUIREMENTS,
extras_require={"dev": DEV, "testing": TESTING},
extras_require={"dev": DEV, "testing": TESTING, "server": SERVER},
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: AiiDA",
Expand Down
4 changes: 4 additions & 0 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,9 @@ def update_version(_, version=""):
TOP_DIR.joinpath("optimade_client/informational.py"),
(r"Client version.*</code>", f"Client version</b>: <code>{version}</code>"),
)
update_file(
TOP_DIR.joinpath("optimade_client/cli/run.py"),
(r'VERSION = ".+"', f'VERSION = "{version}"'),
)

print(f"Bumped version to {version} !")
Loading

0 comments on commit 327112d

Please sign in to comment.