Skip to content

Commit

Permalink
Revisit dependencies, add "server" extra
Browse files Browse the repository at this point in the history
Remove the following (unused) dependencies:
- appmode
- jupyterlab
- numpy

Moved the following dependencies to the new `server` extra:
- ase
- voila

Any non-native OPTIMADE adapter formats have been demoted to only be
included if the package is installed. This means the download format
widget can be used without having ASE installed.
For a more complete experience, ASE is kept as a dependency for the
`server` extra.

Make the CLI work if Voilà is not installed.

Update README with new dependency setup.
Create `Contribute` section in README.
  • Loading branch information
CasperWA committed Oct 14, 2020
1 parent 9679072 commit ddd7961
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 26 deletions.
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
14 changes: 13 additions & 1 deletion optimade_client/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
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
Expand Down Expand Up @@ -46,6 +50,14 @@ def main():
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")
copyfile(
Expand Down
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
33 changes: 33 additions & 0 deletions tests/cli/test_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

try:
import voila as _
except ImportError:
VOILA_PACKAGE_EXISTS = False
else:
VOILA_PACKAGE_EXISTS = True


@pytest.mark.skipif(
not VOILA_PACKAGE_EXISTS,
reason="Voilà is not installed. This test is rendered invalid.",
)
def test_default(run_cli):
"""Run `optimade-client` with default settings"""
output = run_cli()
assert "[Voila] Voilà is running at:" in output, f"output:\n{output}"


@pytest.mark.skipif(
VOILA_PACKAGE_EXISTS, reason="Voilà is installed. This test is rendered invalid."
)
def test_voila_not_installed(run_cli):
"""Ensure the CLI can handle Voilà not being installed."""
output = run_cli(raises=True)
exit_text = (
"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)."
)
assert exit_text in output
assert "[Voila]" not in output

0 comments on commit ddd7961

Please sign in to comment.