Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugin list page #646

Merged
merged 8 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/development/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ This guide explains the architecture of the application and how to extend the fu

architecture
plugin
plugin_registry
4 changes: 4 additions & 0 deletions docs/source/development/plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,8 @@ Further Reading
QuantumESPRESSO app comes with several built-in plugins, which can be found in the ``aiidalab_qe.plugins`` folder.
You can also use them as a start point to create your own plugins.


You can register your plugin to facilitate its discovery and use by the community.
Please refer to the :doc:`Plugin registry </development/plugin_registry>` for more details.

.. _aiidalab-qe-plugin-demos: https://github.com/aiidalab/aiidalab-qe-plugin-demos
46 changes: 46 additions & 0 deletions docs/source/development/plugin_registry.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@


Plugin Registry
=========================================

If you are either in the process of creating a new plugin or already have one developed, you're encouraged to register your plugin here to become part of the official AiiDAlab Quantum ESPRESSO App plugin ecosystem.

Registering Your Plugin
-----------------------

To include your plugin in the registry, follow these steps:

1. Fork this `repository <https://github.com/aiidalab/aiidalab-qe>`_.

2. Add your plugin to the `plugins.yaml` file. Place your entry at the end of the file, following this example:

.. code-block:: yaml

aiidalab-qe-xyz:
description: "Quantum ESPRESSO plugin for XYZ by AiiDAlab."
author: "Alice Doe"
github: "https://github.com/alicedoe/aiidalab-qe-xyz"
documentation: "https://aiidalab-qe-xyz.readthedocs.io/"
pip: "aiidalab-qe-xyz"

3. Submit a Pull Request. Direct it to `this repository's Pull Requests section <https://github.com/aiidalab/aiidalab-qe/pulls>`_.

Plugin Entry Requirements
-------------------------

**Required Keys**

- **Top-level key:** The plugin's distribution name, which should be lowercase and prefixed by ``aiidalab-`` or ``aiida-``. For example, ``aiidalab-qe-coolfeature`` or ``aiidalab-neutron``.
- **description:** A brief description of your plugin.

**Optional Keys**

- **github:** If provided, this should be the URL to the plugin's GitHub homepage.

At least one of ``github`` or ``pip`` is required.

- **pip:** The PyPI package name for your plugin, useful for installation via pip. Example: ``aiida-quantum``.
- **documentation:** The URL to your plugin's online documentation, such as ReadTheDocs.
- **author:** The developer of the plugin.

By following these guidelines, you can ensure your plugin is correctly listed and accessible within the AiiDAlab Quantum ESPRESSO app, facilitating its discovery and use by the community.
196 changes: 196 additions & 0 deletions plugin_list.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## AiiDAlab Quantum ESPRESSO Plugin manager\n",
"\n",
"This page lets you manage the plugins. You can find all the plugins that available in the official AiiDAlab Quantum ESPRESSO Plugin registry. You can install and remove plugins from this page.\n",
"\n",
"### Plugin registry\n",
"\n",
"If you are starting to develop a new plugin or if you already have one, and want it discoveried and used by the community. Please refer to this [page](https://aiidalab-qe.readthedocs.io/development/plugin_registry.html) to learn how to register a plugin.\n",
"\n",
"\n",
"### Available plugins\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import requests\n",
"import yaml\n",
"\n",
"# URL of the YAML file\n",
"filepath = 'https://raw.githubusercontent.com/aiidalab/aiidalab-qe-plugin-registry/main/plugins.yaml'\n",
"\n",
"# Fetch the contents of the URL\n",
"response = requests.get(filepath)\n",
"\n",
"# Check if the request was successful\n",
"if response.status_code == 200:\n",
" # Load the YAML content\n",
" data = yaml.safe_load(response.content)\n",
" # Now 'data' contains the YAML file's contents as a Python object\n",
"else:\n",
" print(f\"Failed to fetch the YAML file: HTTP {response.status_code}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import subprocess\n",
"from threading import Thread\n",
"\n",
"import ipywidgets as ipw\n",
"from IPython.display import display\n",
"\n",
"\n",
"def is_package_installed(package_name):\n",
" import importlib\n",
" package_name = package_name.replace('-', '_')\n",
" try:\n",
" importlib.import_module(package_name)\n",
" return True\n",
" except ImportError:\n",
" return False\n",
"\n",
"\n",
"def stream_output(process, output_widget):\n",
" \"\"\"Reads output from the process and forwards it to the output widget.\"\"\"\n",
" while True:\n",
" output = process.stdout.readline()\n",
" if process.poll() is not None and output == '':\n",
" break\n",
" if output:\n",
" output_widget.value += f\"\"\"<div style=\"background-color: #3B3B3B; color: #FFFFFF;\">{output}</div>\"\"\"\n",
"\n",
"\n",
"def execute_command_with_output(command, output_widget, install_btn, remove_btn, action=\"install\"):\n",
" \"\"\"Execute a command and stream its output to the given output widget.\"\"\"\n",
" output_widget.value = \"\" # Clear the widget\n",
" process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)\n",
" # Create a thread to read the output stream and write it to the output widget\n",
" thread = Thread(target=stream_output, args=(process, output_widget))\n",
" thread.start()\n",
" thread.join() # Wait for the thread to finish\n",
"\n",
" if process.returncode == 0 and action == \"install\":\n",
" output_widget.value += \"\"\"<div style=\"background-color: #3B3B3B; color: #008000;\">Command executed successfully.</div>\"\"\"\n",
" install_btn.disabled = True\n",
" remove_btn.disabled = False\n",
" return True\n",
" elif process.returncode == 0 and action == \"remove\":\n",
" output_widget.value += \"\"\"<div style=\"background-color: #3B3B3B; color: #008000;\">Command executed successfully.</div>\"\"\"\n",
" install_btn.disabled = False\n",
" remove_btn.disabled = True\n",
" return True\n",
" else:\n",
" output_widget.value += \"\"\"<div style=\"background-color: #3B3B3B; color: #FF0000;\">Command failed.</div>\"\"\"\n",
" return False\n",
"\n",
"\n",
"def install_package(pip, github, output_container, install_btn, remove_btn, accordion, index):\n",
" if pip:\n",
" command = [\"pip\", \"install\", pip]\n",
" else:\n",
" command = [\"pip\", \"install\", \"git+\" + github]\n",
" result = execute_command_with_output(command, output_container, install_btn, remove_btn)\n",
" if result:\n",
" # restart daemon\n",
" accordion.set_title(index, f\"{accordion.get_title(index)[:-2]} ✅\")\n",
" command = [\"verdi\", \"daemon\", \"restart\"]\n",
" subprocess.run(command, capture_output=True, shell=False)\n",
"\n",
"\n",
"def remove_package(package_name, output_container, install_btn, remove_btn, accordion, index):\n",
" package_name = package_name.replace('-', '_')\n",
" command = [\"pip\", \"uninstall\", \"-y\", package_name]\n",
" result = execute_command_with_output(command, output_container, install_btn, remove_btn, action=\"remove\")\n",
" if result:\n",
" accordion.set_title(index, f\"{accordion.get_title(index)[:-2]} ☐\")\n",
" command = [\"verdi\", \"daemon\", \"restart\"]\n",
" subprocess.run(command, capture_output=True, shell=False)\n",
"\n",
"\n",
"accordion = ipw.Accordion()\n",
"\n",
"for i, (plugin_name, plugin_data) in enumerate(data.items()):\n",
" installed = is_package_installed(plugin_name)\n",
" \n",
" # Output container with customized styling\n",
" output_container = ipw.HTML(\n",
" value=\"\"\"\n",
" <div style=\"background-color: #3B3B3B; color: #FFFFFF; height: 100%; overflow: auto;\">\n",
" </div>\n",
" \"\"\",\n",
" layout=ipw.Layout(\n",
" max_height='250px', \n",
" overflow='auto',\n",
" border='2px solid #CCCCCC'\n",
" )\n",
" )\n",
" \n",
" details = f\"Author: {plugin_data.get('author', 'N/A')}<br>\" \\\n",
" f\"Description: {plugin_data.get('description', 'No description available')}<br>\"\n",
" if 'documentation' in plugin_data:\n",
" details += f\"Documentation: <a href='{plugin_data['documentation']}' target='_blank'>Visit</a><br>\"\n",
" if 'github' in plugin_data:\n",
" details += f\"Github: <a href='{plugin_data.get('github')}' target='_blank'>Visit</a>\"\n",
"\n",
" install_btn = ipw.Button(description=\"Install\", button_style='success', disabled=installed)\n",
" remove_btn = ipw.Button(description=\"Remove\", button_style='danger', disabled=not installed)\n",
"\n",
" install_btn.on_click(lambda btn, pip=plugin_data.get('pip', None), github=plugin_data.get('github', ''), oc=output_container, ib=install_btn, rb=remove_btn, ac=accordion, index=i: install_package(pip, github, oc, ib, rb, ac, index))\n",
" remove_btn.on_click(lambda btn, pn=plugin_name, oc=output_container, ib=install_btn, rb=remove_btn, ac=accordion, index=i: remove_package(pn, oc, ib, rb, ac, index))\n",
"\n",
" box = ipw.VBox([\n",
" ipw.HTML(details),\n",
" ipw.HBox([install_btn, remove_btn]),\n",
" output_container # Include the output container in the VBox\n",
" ])\n",
"\n",
" title_with_icon = f\"{plugin_name} {'✅' if installed else '☐'}\"\n",
" accordion.set_title(i, title_with_icon)\n",
" accordion.children = list(accordion.children) + [box]\n",
"\n",
"display(accordion)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
17 changes: 17 additions & 0 deletions plugins.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
aiida-bader:
description: AiiDA plugin for the Bader analysis
author: Xing Wang
github: https://github.com/superstar54/aiida-bader
documentation: https://aiida-bader.readthedocs.io/
pip: aiida-bader

aiidalab-qe-vibroscopy:
description: Plugin to compute vibrational properties of materials via the aiida-vibroscopy AiiDA plugin
author: Miki Bonacci
superstar54 marked this conversation as resolved.
Show resolved Hide resolved
github: https://github.com/mikibonacci/aiidalab-qe-vibroscopy

aiidalab-qe-muon:
description: Plugin to compute muon stopping sites and related properties via the aiida-muon and aiida-musconv AiiDA plugins
author: Miki Bonacci
github: https://github.com/mikibonacci/aiidalab-qe-muon
9 changes: 9 additions & 0 deletions start.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
def get_start_widget(appbase, jupbase, notebase):
return ipw.HTML(
f"""
<table>
<tr>
<th style="text-align:center">Preferences</th>
<tr>
<td valign="top"><ul>
<li><a href="{appbase}/plugin_list.ipynb" target="_blank">Plugins</a></li>
</ul></td>
</tr>
</table>
<div align="center">
<a href="{appbase}/qe.ipynb" target="_blank">
<img src="https://gitlab.com/QEF/q-e/raw/develop/logo.jpg" height="120px" width=243px">
Expand Down
Loading