diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 123447ae1..658562e09 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -24,28 +24,16 @@ repos: hooks: - id: check-github-workflows - - repo: https://github.com/nbQA-dev/nbQA - rev: 1.8.5 - hooks: - - id: nbqa-pyupgrade - args: [--py38-plus] - - id: nbqa-isort - args: [--profile=black] - - id: nbqa-ruff - # suppress E402 - args: [--ignore=E402] - - repo: https://github.com/kynan/nbstripout rev: 0.7.1 hooks: - id: nbstripout - repo: https://github.com/astral-sh/ruff-pre-commit - # Ruff version. - rev: v0.5.0 + rev: v0.5.5 hooks: - # Run the linter. - id: ruff + types_or: [python, pyi, jupyter] args: [--fix] - # Run the formatter. - id: ruff-format + types_or: [python, pyi, jupyter] diff --git a/delete.ipynb b/delete.ipynb index 374c73a0f..670a6561f 100644 --- a/delete.ipynb +++ b/delete.ipynb @@ -18,18 +18,19 @@ "import urllib.parse as urlparse\n", "\n", "import ipywidgets as widgets\n", + "from IPython.display import Markdown, display\n", + "\n", "from aiida import load_profile\n", "from aiida.orm import load_node\n", "from aiida.tools import delete_nodes\n", - "from IPython.display import Markdown, display\n", "\n", "# Load AiiDA profile\n", "load_profile()\n", "\n", "# Parse the primary key from the Jupyter notebook URL\n", - "url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n", + "url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n", "query = urlparse.parse_qs(url.query)\n", - "pk = int(query['pk'][0])\n", + "pk = int(query[\"pk\"][0])\n", "\n", "\n", "def display_node_details(pk):\n", @@ -41,7 +42,7 @@ " print(f\"Description: {node.description}\")\n", " print(f\"Creation Time: {node.ctime}\")\n", " except Exception as e:\n", - " print(f\"Error loading node: {str(e)}\")\n", + " print(f\"Error loading node: {e!s}\")\n", " return False\n", " return True\n", "\n", @@ -50,31 +51,36 @@ " if dry_run:\n", " _, was_deleted = delete_nodes([pk], dry_run=True)\n", " if was_deleted:\n", - " print(f'Dry run: Node {pk} can be deleted.')\n", + " print(f\"Dry run: Node {pk} can be deleted.\")\n", " return\n", - " \n", + "\n", " _, was_deleted = delete_nodes([pk], dry_run=False)\n", " if was_deleted:\n", - " print(f'Node {pk} deleted successfully.')\n", + " print(f\"Node {pk} deleted successfully.\")\n", "\n", "\n", - "def confirm_deletion(b):\n", - " if delete_confirmation.value.lower() in ['y', 'yes']:\n", + "def confirm_deletion(_):\n", + " if delete_confirmation.value.lower() in (\"y\", \"yes\"):\n", " delete_node(pk, dry_run=False)\n", " else:\n", - " print('Deletion aborted.')\n", + " print(\"Deletion aborted.\")\n", "\n", "\n", - "def find_linked_qeapp_jobs(root_node_pk, process_label='QeAppWorkChain'):\n", + "def find_linked_qeapp_jobs(root_node_pk, process_label=\"QeAppWorkChain\"):\n", " \"\"\"Query all linked node with process_label = QeAppWorkChain.\"\"\"\n", " from aiida.orm import Node, QueryBuilder\n", " from aiida.orm.nodes.process.workflow.workchain import WorkChainNode\n", + "\n", " qb = QueryBuilder()\n", - " qb.append(WorkChainNode, filters={'id': root_node_pk}, tag='root')\n", - " qb.append(Node, with_incoming='root', tag='calcjob')\n", + " qb.append(WorkChainNode, filters={\"id\": root_node_pk}, tag=\"root\")\n", + " qb.append(Node, with_incoming=\"root\", tag=\"calcjob\")\n", " # There are seems a bug with `with_ancestors` in the QueryBuilder, so we have to use `with_incoming` instead.\n", - " # For the moment, it's safe to use `with_incoming` since we check it very time we delete a QEApp \n", - " qb.append(WorkChainNode, filters={'attributes.process_label': 'QeAppWorkChain'}, with_incoming='calcjob')\n", + " # For the moment, it's safe to use `with_incoming` since we check it very time we delete a QEApp\n", + " qb.append(\n", + " WorkChainNode,\n", + " filters={\"attributes.process_label\": process_label},\n", + " with_incoming=\"calcjob\",\n", + " )\n", " results = qb.all()\n", " if len(results) == 0:\n", " return None\n", @@ -96,12 +102,16 @@ " else:\n", " # Ask for confirmation\n", " nodes, _ = delete_nodes([pk], dry_run=True)\n", - " display(Markdown(f'**YOU ARE ABOUT TO DELETE `{len(nodes)}` NODES! THIS CANNOT BE UNDONE!**'))\n", + " display(\n", + " Markdown(\n", + " f\"**YOU ARE ABOUT TO DELETE `{len(nodes)}` NODES! THIS CANNOT BE UNDONE!**\"\n", + " )\n", + " )\n", " delete_confirmation = widgets.Text(\n", - " value='',\n", + " value=\"\",\n", " placeholder='Type \"yes\" to confirm',\n", - " description='Confirm:',\n", - " disabled=False\n", + " description=\"Confirm:\",\n", + " disabled=False,\n", " )\n", " confirm_button = widgets.Button(description=\"Delete Node\")\n", " confirm_button.on_click(confirm_deletion)\n", diff --git a/docs/source/conf.py b/docs/source/conf.py index fc0719077..8f39260d7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -9,13 +9,8 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys import time -# sys.path.insert(0, os.path.abspath('.')) - # -- Project information ----------------------------------------------------- version = "v24.10.0a1" @@ -30,9 +25,7 @@ if current_year == copyright_first_year else f"{copyright_first_year}-{current_year}" ) -copyright = "{}, {}. All rights reserved".format( - copyright_year_string, copyright_owners -) +copyright = f"{copyright_year_string}, {copyright_owners}. All rights reserved" # noqa: A001 # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. diff --git a/plugin_list.ipynb b/plugin_list.ipynb index 72741f3c5..30c17bc48 100644 --- a/plugin_list.ipynb +++ b/plugin_list.ipynb @@ -29,12 +29,12 @@ "# Get the current working directory\n", "cwd = Path.cwd()\n", "# Define a relative path\n", - "relative_path = 'plugins.yaml'\n", + "relative_path = \"plugins.yaml\"\n", "# Resolve the relative path to an absolute path\n", "yaml_file = cwd / relative_path\n", "\n", "# Load the YAML content\n", - "with yaml_file.open('r') as file:\n", + "with yaml_file.open(\"r\") as file:\n", " data = yaml.safe_load(file)" ] }, @@ -54,28 +54,34 @@ "\n", "def is_package_installed(package_name):\n", " import importlib\n", - " package_name = package_name.replace('-', '_')\n", + "\n", + " package_name = package_name.replace(\"-\", \"_\")\n", " try:\n", " importlib.import_module(package_name)\n", - " return True\n", " except ImportError:\n", " return False\n", + " else:\n", + " return True\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", + " if process.poll() is not None and output == \"\":\n", " break\n", " if output:\n", " output_widget.value += f\"\"\"
{output}
\"\"\"\n", "\n", "\n", - "def execute_command_with_output(command, output_widget, install_btn, remove_btn, action=\"install\"):\n", + "def execute_command_with_output(\n", + " command, output_widget, install_btn, remove_btn, action=\"install\"\n", + "):\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", + " process = subprocess.Popen(\n", + " command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1\n", + " )\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", @@ -94,63 +100,120 @@ " return False\n", "\n", "\n", - "def install_package(package_name, pip, github, post_install, output_container, message_container, install_btn, remove_btn, accordion, index):\n", + "def install_package(\n", + " package_name,\n", + " pip,\n", + " github,\n", + " post_install,\n", + " output_container,\n", + " message_container,\n", + " install_btn,\n", + " remove_btn,\n", + " accordion,\n", + " index,\n", + "):\n", " if pip:\n", " command = [\"pip\", \"install\", pip, \"--user\"]\n", " else:\n", " command = [\"pip\", \"install\", \"git+\" + github, \"--user\"]\n", - " message_container.value = f\"\"\"
Installing {package_name}...
\"\"\"\n", - " result = execute_command_with_output(command, output_container, install_btn, remove_btn)\n", + " message_container.value = (\n", + " f\"\"\"
Installing {package_name}...
\"\"\"\n", + " )\n", + " result = execute_command_with_output(\n", + " command, output_container, install_btn, remove_btn\n", + " )\n", " # Execute post install if defined in the plugin.yaml:\n", - " if post_install: \n", - " message_container.value += \"\"\"
Post installation step...
\"\"\"\n", - " command = [sys.executable, '-m', package_name.replace('-','_'), post_install]\n", + " if post_install:\n", + " message_container.value += (\n", + " \"\"\"
Post installation step...
\"\"\"\n", + " )\n", + " command = [sys.executable, \"-m\", package_name.replace(\"-\", \"_\"), post_install]\n", " # Execute the command\n", - " result = subprocess.run(command, capture_output=True, text=True)\n", + " result = subprocess.run(command, capture_output=True, text=True, check=False)\n", " # if the package was installed successfully\n", " if result:\n", " message_container.value += \"\"\"
Initiating test to load the plugin...
\"\"\"\n", " # Test plugin functionality\n", - " command = [sys.executable, '-m', 'aiidalab_qe', 'test-plugin', package_name]\n", + " command = [sys.executable, \"-m\", \"aiidalab_qe\", \"test-plugin\", package_name]\n", " # Execute the command\n", - " result = subprocess.run(command, capture_output=True, text=True)\n", + " result = subprocess.run(command, capture_output=True, text=True, check=False)\n", " if result.returncode == 0:\n", " # restart daemon\n", - " message_container.value = \"\"\"
Loading plugin test passed.
\"\"\"\n", - " message_container.value += \"\"\"
Plugin installed successfully.
\"\"\"\n", + " message_container.value = (\n", + " \"\"\"
Loading plugin test passed.
\"\"\"\n", + " )\n", + " message_container.value += (\n", + " \"\"\"
Plugin installed successfully.
\"\"\"\n", + " )\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", + " subprocess.run(command, capture_output=True, shell=False, check=False)\n", " else:\n", " # uninstall the package\n", " message_container.value = f\"\"\"
The plugin '{package_name}' was installed successfully but plugin functionality test failed: {result.stderr}.
\"\"\"\n", " message_container.value += \"\"\"
This may be due to compatibility issues with the current AiiDAlab QEApp version. Please contact the plugin author for further assistance.
\"\"\"\n", " message_container.value += \"\"\"
To prevent potential issues, the plugin will now be uninstalled.
\"\"\"\n", - " remove_package(package_name, output_container, message_container, install_btn, remove_btn, accordion, index)\n", + " remove_package(\n", + " package_name,\n", + " output_container,\n", + " message_container,\n", + " install_btn,\n", + " remove_btn,\n", + " accordion,\n", + " index,\n", + " )\n", "\n", "\n", - "def remove_package(package_name, output_container, message_container, install_btn, remove_btn, accordion, index):\n", - " message_container.value += f\"\"\"
Removing {package_name}...
\"\"\"\n", - " package_name = package_name.replace('-', '_')\n", + "def remove_package(\n", + " package_name,\n", + " output_container,\n", + " message_container,\n", + " install_btn,\n", + " remove_btn,\n", + " accordion,\n", + " index,\n", + "):\n", + " message_container.value += (\n", + " f\"\"\"
Removing {package_name}...
\"\"\"\n", + " )\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", + " result = execute_command_with_output(\n", + " command, output_container, install_btn, remove_btn, action=\"remove\"\n", + " )\n", " if result:\n", " message_container.value += f\"\"\"
{package_name} removed successfully.
\"\"\"\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", + " subprocess.run(command, capture_output=True, shell=False, check=False)\n", "\n", "\n", - "def run_remove_button(package_name, output_container, message_container, install_btn, remove_btn, accordion, index):\n", + "def run_remove_button(\n", + " package_name,\n", + " output_container,\n", + " message_container,\n", + " install_btn,\n", + " remove_btn,\n", + " accordion,\n", + " index,\n", + "):\n", " message_container.value = \"\"\n", - " remove_package(package_name, output_container, message_container, install_btn, remove_btn, accordion, index)\n", + " remove_package(\n", + " package_name,\n", + " output_container,\n", + " message_container,\n", + " install_btn,\n", + " remove_btn,\n", + " accordion,\n", + " index,\n", + " )\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", + "\n", " # Output container with customized styling\n", " output_container = ipw.HTML(\n", " value=\"\"\"\n", @@ -158,10 +221,8 @@ " \n", " \"\"\",\n", " layout=ipw.Layout(\n", - " max_height='250px', \n", - " overflow='auto',\n", - " border='2px solid #CCCCCC'\n", - " )\n", + " max_height=\"250px\", overflow=\"auto\", border=\"2px solid #CCCCCC\"\n", + " ),\n", " )\n", " # Output container with customized styling\n", " message_container = ipw.HTML(\n", @@ -170,35 +231,64 @@ " \n", " \"\"\",\n", " layout=ipw.Layout(\n", - " max_height='250px', \n", - " overflow='auto',\n", - " border='2px solid #CCCCCC'\n", - " )\n", + " max_height=\"250px\", overflow=\"auto\", border=\"2px solid #CCCCCC\"\n", + " ),\n", " )\n", - " \n", - " details = f\"Author: {plugin_data.get('author', 'N/A')}
\" \\\n", - " f\"Description: {plugin_data.get('description', 'No description available')}
\"\n", - " if 'documentation' in plugin_data:\n", + "\n", + " details = (\n", + " f\"Author: {plugin_data.get('author', 'N/A')}
\"\n", + " f\"Description: {plugin_data.get('description', 'No description available')}
\"\n", + " )\n", + " if \"documentation\" in plugin_data:\n", " details += f\"Documentation: Visit
\"\n", - " if 'github' in plugin_data:\n", - " details += f\"Github: Visit\"\n", + " if \"github\" in plugin_data:\n", + " details += (\n", + " f\"Github: Visit\"\n", + " )\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", + " install_btn = ipw.Button(\n", + " description=\"Install\", button_style=\"success\", disabled=installed\n", + " )\n", + " remove_btn = ipw.Button(\n", + " description=\"Remove\", button_style=\"danger\", disabled=not installed\n", + " )\n", "\n", - " install_btn.on_click(lambda btn, pn=plugin_name, pip=plugin_data.get('pip', None), github=plugin_data.get('github', ''), post = plugin_data.get('post_install', None), oc=output_container, mc=message_container, ib=install_btn, rb=remove_btn, ac=accordion, index=i: install_package(pn, pip, github, post, oc, mc, ib, rb, ac, index))\n", - " remove_btn.on_click(lambda btn, pn=plugin_name, oc=output_container, mc=message_container, ib=install_btn, rb=remove_btn, ac=accordion, index=i: run_remove_button(pn, oc, mc, ib, rb, ac, index))\n", + " install_btn.on_click(\n", + " lambda _btn,\n", + " pn=plugin_name,\n", + " pip=plugin_data.get(\"pip\", None), # noqa: B008\n", + " github=plugin_data.get(\"github\", \"\"), # noqa: B008\n", + " post=plugin_data.get(\"post_install\", None), # noqa: B008\n", + " oc=output_container,\n", + " mc=message_container,\n", + " ib=install_btn,\n", + " rb=remove_btn,\n", + " ac=accordion,\n", + " index=i: install_package(pn, pip, github, post, oc, mc, ib, rb, ac, index)\n", + " )\n", + " remove_btn.on_click(\n", + " lambda _btn,\n", + " pn=plugin_name,\n", + " oc=output_container,\n", + " mc=message_container,\n", + " ib=install_btn,\n", + " rb=remove_btn,\n", + " ac=accordion,\n", + " index=i: run_remove_button(pn, oc, mc, ib, rb, ac, index)\n", + " )\n", "\n", - " box = ipw.VBox([\n", - " ipw.HTML(details),\n", - " ipw.HBox([install_btn, remove_btn]),\n", - " message_container,\n", - " output_container,\n", - " ])\n", + " box = ipw.VBox(\n", + " [\n", + " ipw.HTML(details),\n", + " ipw.HBox([install_btn, remove_btn]),\n", + " message_container,\n", + " output_container,\n", + " ]\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", + " accordion.children = [*accordion.children, box]\n", "\n", "display(accordion)" ] diff --git a/pyproject.toml b/pyproject.toml index 1eb0ca77d..dd0525383 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,3 +4,36 @@ requires = [ "wheel" ] build-backend = "setuptools.build_meta" + +[tool.ruff] +line-length = 88 +show-fixes = true +output-format = "full" +target-version = "py39" +extend-include = ["*.ipynb"] + +[tool.ruff.lint] +ignore = ["E501", "E402", "TRY003", "RUF012", "N806"] +select = [ + "A", # flake8-builtins + "ARG", # flake8-unused-arguments + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "N", # pep8-naming + "PLE", # pylint error rules + "PLW", # pylint warning rules + "PLC", # pylint convention rules + "RUF", # ruff-specific rules + "TRY", # Tryceratops + "UP" # pyupgrade +] + +[tool.ruff.lint.isort] +known-first-party = ["aiida", "aiidalab_widgets_base", "aiida_quantumespresso"] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["ARG001"] +"tests_integration/*" = ["ARG001"] diff --git a/qe.ipynb b/qe.ipynb index 269e49674..cac6b0014 100644 --- a/qe.ipynb +++ b/qe.ipynb @@ -21,7 +21,7 @@ "source": [ "from aiida import load_profile\n", "\n", - "load_profile(); # noqa: E402" + "load_profile();" ] }, { @@ -42,15 +42,15 @@ "from datetime import datetime\n", "\n", "import ipywidgets as ipw\n", - "from aiidalab_widgets_base.bug_report import (\n", - " install_create_github_issue_exception_handler,\n", - ")\n", + "from aiidalab_qe.app import App, static\n", + "from aiidalab_qe.version import __version__\n", "from importlib_resources import files\n", "from IPython.display import display\n", "from jinja2 import Environment\n", "\n", - "from aiidalab_qe.app import App, static\n", - "from aiidalab_qe.version import __version__" + "from aiidalab_widgets_base.bug_report import (\n", + " install_create_github_issue_exception_handler,\n", + ")" ] }, { @@ -69,14 +69,14 @@ " f'

Copyright (c) {current_year} AiiDAlab team  Version: {__version__}

'\n", ")\n", "\n", - "url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n", + "url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n", "query = urlparse.parse_qs(url.query)\n", "\n", "\n", "app_with_work_chain_selector = App(qe_auto_setup=True)\n", "# if a pk is provided in the query string, set it as the value of the work_chain_selector\n", - "if 'pk' in query:\n", - " pk = int(query['pk'][0])\n", + "if \"pk\" in query:\n", + " pk = int(query[\"pk\"][0])\n", " app_with_work_chain_selector.work_chain_selector.value = pk\n", "\n", "output = ipw.Output()\n", diff --git a/src/aiidalab_qe/__main__.py b/src/aiidalab_qe/__main__.py index 7ea58f39d..667b011c2 100644 --- a/src/aiidalab_qe/__main__.py +++ b/src/aiidalab_qe/__main__.py @@ -1,10 +1,11 @@ """For running the app from the command line used for post_install script.""" -from pathlib import Path import sys +from pathlib import Path + import click -from aiida import load_profile +from aiida import load_profile from aiidalab_qe.common.setup_codes import codes_are_setup from aiidalab_qe.common.setup_codes import install as install_qe_codes @@ -28,7 +29,7 @@ def install_qe(force, profile): assert codes_are_setup() click.secho("Codes are setup!", fg="green") except Exception as error: - raise click.ClickException(f"Failed to set up QE failed: {error}") + raise click.ClickException(f"Failed to set up QE failed: {error}") from error @cli.command() @@ -53,7 +54,9 @@ def install_pseudos(profile, source): click.echo(msg) click.secho("Pseudopotentials are installed!", fg="green") except Exception as error: - raise click.ClickException(f"Failed to set up pseudo potentials: {error}") + raise click.ClickException( + f"Failed to set up pseudo potentials: {error}" + ) from error @cli.command() @@ -75,7 +78,9 @@ def download_pseudos(dest): click.secho("Pseudopotentials are downloaded!", fg="green") except Exception as error: - raise click.ClickException(f"Failed to download pseudo potentials: {error}") + raise click.ClickException( + f"Failed to download pseudo potentials: {error}" + ) from error @cli.command() diff --git a/src/aiidalab_qe/app/configuration/__init__.py b/src/aiidalab_qe/app/configuration/__init__.py index ff28a9402..adebd3898 100644 --- a/src/aiidalab_qe/app/configuration/__init__.py +++ b/src/aiidalab_qe/app/configuration/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Widgets for the submission of bands work chains. Authors: AiiDAlab team @@ -8,10 +7,10 @@ import ipywidgets as ipw import traitlets as tl -from aiida import orm -from aiidalab_widgets_base import WizardAppWidgetStep +from aiida import orm from aiidalab_qe.app.utils import get_entry_items +from aiidalab_widgets_base import WizardAppWidgetStep from .advanced import AdvancedSettings from .workflow import WorkChainSettings @@ -111,7 +110,7 @@ def __init__(self, **kwargs): ) @tl.observe("previous_step_state") - def _observe_previous_step_state(self, change): + def _observe_previous_step_state(self, _change): self._update_state() def get_configuration_parameters(self): diff --git a/src/aiidalab_qe/app/configuration/advanced.py b/src/aiidalab_qe/app/configuration/advanced.py index bc5e51faf..65a416960 100644 --- a/src/aiidalab_qe/app/configuration/advanced.py +++ b/src/aiidalab_qe/app/configuration/advanced.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Widgets for the submission of bands work chains. Authors: AiiDAlab team @@ -7,20 +6,21 @@ import os import ipywidgets as ipw +import numpy as np import traitlets as tl +from IPython.display import clear_output, display + from aiida import orm -import numpy as np from aiida_quantumespresso.calculations.functions.create_kpoints_from_distance import ( create_kpoints_from_distance, ) +from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData from aiida_quantumespresso.workflows.pw.base import PwBaseWorkChain -from IPython.display import clear_output, display - from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS from aiidalab_qe.common.panel import Panel from aiidalab_qe.common.setup_pseudos import PseudoFamily from aiidalab_qe.common.widgets import HubbardWidget -from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData + from .pseudos import PseudoFamilySelector, PseudoSetter diff --git a/src/aiidalab_qe/app/configuration/pseudos.py b/src/aiidalab_qe/app/configuration/pseudos.py index 912041c20..2a46a9bce 100644 --- a/src/aiidalab_qe/app/configuration/pseudos.py +++ b/src/aiidalab_qe/app/configuration/pseudos.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import annotations import io @@ -6,18 +5,18 @@ import ipywidgets as ipw import traitlets as tl + from aiida import orm from aiida.common import exceptions from aiida.plugins import DataFactory, GroupFactory from aiida_quantumespresso.workflows.pw.base import PwBaseWorkChain -from aiidalab_widgets_base.utils import StatusHTML - from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS from aiidalab_qe.common.setup_pseudos import ( PSEUDODOJO_VERSION, SSSP_VERSION, PseudoFamily, ) +from aiidalab_widgets_base.utils import StatusHTML UpfData = DataFactory("pseudo.upf") SsspFamily = GroupFactory("pseudo.family.sssp") @@ -128,6 +127,7 @@ def __init__(self, **kwargs): self.pseudo_family_help, ], layout=ipw.Layout(max_width="60%"), + **kwargs, ) ipw.dlink((self.show_ui, "value"), (self.library_selection, "disabled")) ipw.dlink((self.show_ui, "value"), (self.dft_functional, "disabled")) @@ -338,7 +338,7 @@ def _reset_traitlets(self): """Reset the traitlets to the initial state""" self.ecutwfc = 0 self.ecutrho = 0 - self.pseudos = dict() + self.pseudos = {} def _reset(self): """Reset the pseudo setting widgets according to the structure @@ -377,7 +377,7 @@ def _reset(self): pseudo_family = self._get_pseudos_family(self.pseudo_family) except exceptions.NotExistent as exception: self._status_message.message = ( - f"""
ERROR: {str(exception)}
""" + f"""
ERROR: {exception!s}
""" ) return diff --git a/src/aiidalab_qe/app/configuration/workflow.py b/src/aiidalab_qe/app/configuration/workflow.py index 9fc2f0159..47db49cc0 100644 --- a/src/aiidalab_qe/app/configuration/workflow.py +++ b/src/aiidalab_qe/app/configuration/workflow.py @@ -1,12 +1,11 @@ -# -*- coding: utf-8 -*- """Widgets for the submission of bands work chains. Authors: AiiDAlab team """ import ipywidgets as ipw -from aiida_quantumespresso.common.types import RelaxType +from aiida_quantumespresso.common.types import RelaxType from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS from aiidalab_qe.app.utils import get_entry_items from aiidalab_qe.common.panel import Panel diff --git a/src/aiidalab_qe/app/main.py b/src/aiidalab_qe/app/main.py index deed96509..b02d4feb3 100644 --- a/src/aiidalab_qe/app/main.py +++ b/src/aiidalab_qe/app/main.py @@ -1,18 +1,17 @@ -# -*- coding: utf-8 -*- """The main widget that shows the application in the Jupyter notebook. Authors: AiiDAlab team """ import ipywidgets as ipw -from aiida.orm import load_node -from aiidalab_widgets_base import WizardAppWidget, WizardAppWidgetStep +from aiida.orm import load_node from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep from aiidalab_qe.app.result import ViewQeAppWorkChainStatusAndResultsStep from aiidalab_qe.app.structure import StructureSelectionStep from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep from aiidalab_qe.common import QeAppWorkChainSelector +from aiidalab_widgets_base import WizardAppWidget, WizardAppWidgetStep class App(ipw.VBox): diff --git a/src/aiidalab_qe/app/result/__init__.py b/src/aiidalab_qe/app/result/__init__.py index 7a3d2640a..9451a28ac 100644 --- a/src/aiidalab_qe/app/result/__init__.py +++ b/src/aiidalab_qe/app/result/__init__.py @@ -1,5 +1,6 @@ import ipywidgets as ipw import traitlets as tl + from aiida import orm from aiida.engine import ProcessState from aiida.engine.processes import control diff --git a/src/aiidalab_qe/app/result/summary_viewer.py b/src/aiidalab_qe/app/result/summary_viewer.py index 0bc1b728a..9524ebe27 100644 --- a/src/aiidalab_qe/app/result/summary_viewer.py +++ b/src/aiidalab_qe/app/result/summary_viewer.py @@ -1,4 +1,5 @@ import ipywidgets as ipw + from aiida_quantumespresso.workflows.pw.bands import PwBandsWorkChain FUNCTIONAL_LINK_MAP = { diff --git a/src/aiidalab_qe/app/result/workchain_viewer.py b/src/aiidalab_qe/app/result/workchain_viewer.py index b90d76586..34ed75dc9 100644 --- a/src/aiidalab_qe/app/result/workchain_viewer.py +++ b/src/aiidalab_qe/app/result/workchain_viewer.py @@ -6,18 +6,18 @@ import ipywidgets as ipw import traitlets as tl -from aiida import orm -from aiida.cmdline.utils.common import get_workchain_report -from aiida.common import LinkType -from aiida.orm.utils.serialize import deserialize_unsafe -from aiidalab_widgets_base import ProcessMonitor, register_viewer_widget -from aiidalab_widgets_base.viewers import StructureDataViewer from filelock import FileLock, Timeout from IPython.display import HTML, display from jinja2 import Environment +from aiida import orm +from aiida.cmdline.utils.common import get_workchain_report +from aiida.common import LinkType +from aiida.orm.utils.serialize import deserialize_unsafe from aiidalab_qe.app import static from aiidalab_qe.app.utils import get_entry_items +from aiidalab_widgets_base import ProcessMonitor, register_viewer_widget +from aiidalab_widgets_base.viewers import StructureDataViewer from .summary_viewer import SummaryView @@ -147,7 +147,9 @@ class WorkChainOutputs(ipw.VBox): _busy = tl.Bool(read_only=True) def __init__(self, node, export_dir=None, **kwargs): - self.export_dir = Path.cwd().joinpath("exports") + if export_dir is None: + export_dir = Path.cwd().joinpath("exports") + self.export_dir = export_dir if node.process_label != "QeAppWorkChain": raise KeyError(str(node.node_type)) @@ -233,7 +235,7 @@ def _download_archive(self, _): finally: self.set_trait("_busy", False) - id = f"dl_{self.node.uuid}" + link_id = f"dl_{self.node.uuid}" display( HTML( @@ -241,7 +243,7 @@ def _download_archive(self, _): diff --git a/src/aiidalab_qe/app/structure/__init__.py b/src/aiidalab_qe/app/structure/__init__.py index 276f29c22..3a9652878 100644 --- a/src/aiidalab_qe/app/structure/__init__.py +++ b/src/aiidalab_qe/app/structure/__init__.py @@ -5,9 +5,13 @@ import pathlib -import aiida import ipywidgets as ipw import traitlets as tl + +import aiida +from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData +from aiidalab_qe.app.utils import get_entry_items +from aiidalab_qe.common import AddingTagsEditor from aiidalab_widgets_base import ( BasicCellEditor, BasicStructureEditor, @@ -19,10 +23,6 @@ WizardAppWidgetStep, ) -from aiidalab_qe.app.utils import get_entry_items -from aiidalab_qe.common import AddingTagsEditor -from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData - # The Examples list of (name, file) tuple curretly passed to # StructureExamplesWidget. file_path = pathlib.Path(__file__).parent diff --git a/src/aiidalab_qe/app/submission/__init__.py b/src/aiidalab_qe/app/submission/__init__.py index d5032e6a1..93c5e27af 100644 --- a/src/aiidalab_qe/app/submission/__init__.py +++ b/src/aiidalab_qe/app/submission/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Widgets for the submission of bands work chains. Authors: AiiDAlab team @@ -8,12 +7,11 @@ import ipywidgets as ipw import traitlets as tl +from IPython.display import display + from aiida import orm from aiida.common import NotExistent from aiida.engine import ProcessBuilderNamespace, submit -from aiidalab_widgets_base import WizardAppWidgetStep -from IPython.display import display - from aiidalab_qe.app.parameters import DEFAULT_PARAMETERS from aiidalab_qe.app.utils import get_entry_items from aiidalab_qe.common.setup_codes import QESetupWidget @@ -23,6 +21,7 @@ QEAppComputationalResourcesWidget, ) from aiidalab_qe.workflows import QeAppWorkChain +from aiidalab_widgets_base import WizardAppWidgetStep class SubmitQeAppWorkChainStep(ipw.VBox, WizardAppWidgetStep): @@ -138,7 +137,8 @@ def __init__(self, qe_auto_setup=True, **kwargs): self.process_label, self.process_description, self.submit_button, - ] + ], + **kwargs, ) # set default codes self.set_selected_codes(DEFAULT_PARAMETERS["codes"]) @@ -148,7 +148,7 @@ def _observe_submission_blockers(self, _change): """Observe the submission blockers and update the message area.""" blockers = self.internal_submission_blockers + self.external_submission_blockers if any(blockers): - fmt_list = "\n".join((f"
  • {item}
  • " for item in sorted(blockers))) + fmt_list = "\n".join(f"
  • {item}
  • " for item in sorted(blockers)) self._submission_blocker_messages.value = f"""
    The submission is blocked, due to the following reason(s): @@ -379,7 +379,7 @@ def _update_process_label(self) -> dict: else: properties_info = f", properties on {', '.join(properties)}" - label = "{} {} {}".format(formula, relax_info, properties_info) + label = f"{formula} {relax_info} {properties_info}" self.process_label.value = label def _create_builder(self) -> ProcessBuilderNamespace: diff --git a/src/aiidalab_qe/app/utils/search_jobs.py b/src/aiidalab_qe/app/utils/search_jobs.py index bf3d81f84..d13271010 100644 --- a/src/aiidalab_qe/app/utils/search_jobs.py +++ b/src/aiidalab_qe/app/utils/search_jobs.py @@ -1,8 +1,9 @@ import ipywidgets as ipw import pandas as pd +from IPython.display import display + from aiida.orm import QueryBuilder from aiidalab_qe.workflows import QeAppWorkChain -from IPython.display import display class QueryInterface: diff --git a/src/aiidalab_qe/common/bandpdoswidget.py b/src/aiidalab_qe/common/bandpdoswidget.py index 0550c93a1..d8494577e 100644 --- a/src/aiidalab_qe/common/bandpdoswidget.py +++ b/src/aiidalab_qe/common/bandpdoswidget.py @@ -1,14 +1,15 @@ import base64 import json +import re import ipywidgets as ipw import numpy as np import plotly.graph_objects as go -from aiida.orm import ProjectionData -from aiidalab_widgets_base.utils import string_range_to_list, StatusHTML from IPython.display import clear_output, display from plotly.subplots import make_subplots -import re + +from aiida.orm import ProjectionData +from aiidalab_widgets_base.utils import StatusHTML, string_range_to_list class BandPdosPlotly: @@ -110,7 +111,7 @@ def _band_yaxis(self): return None bandyaxis = go.layout.YAxis( - title=dict(text="Electronic Bands (eV)", standoff=1), + title={"text": "Electronic Bands (eV)", "standoff": 1}, side="left", showgrid=True, showline=True, @@ -185,7 +186,7 @@ def _get_bandspdos_plot(self): for label in band_labels[1]: fig.add_vline( x=label, - line=dict(color=self.SETTINGS["vertical_linecolor"], width=1), + line={"color": self.SETTINGS["vertical_linecolor"], "width": 1}, ) if self.project_bands: @@ -196,9 +197,11 @@ def _get_bandspdos_plot(self): if self.plot_type == "pdos": fig.add_vline( x=0, - line=dict( - color=self.SETTINGS["vertical_linecolor"], width=1, dash="dot" - ), + line={ + "color": self.SETTINGS["vertical_linecolor"], + "width": 1, + "dash": "dot", + }, ) if self.plot_type == "combined": @@ -274,11 +277,11 @@ def _add_band_traces(self, fig): x=x_bands_comb, y=y_bands_comb - fermi_energy, mode="lines", - line=dict( - color=colors[(spin_polarized, spin)], - shape="spline", - smoothing=1.3, - ), + line={ + "color": colors[(spin_polarized, spin)], + "shape": "spline", + "smoothing": 1.3, + }, showlegend=False, ) ) @@ -319,7 +322,11 @@ def _add_pdos_traces(self, fig): y=y_data, fill=fill, name=trace["label"], - line=dict(color=trace["borderColor"], shape="spline", smoothing=1.0), + line={ + "color": trace["borderColor"], + "shape": "spline", + "smoothing": 1.0, + }, legendgroup=trace["label"], ) @@ -350,7 +357,7 @@ def _add_projection_traces(self, fig): fill="toself", legendgroup=proj_bands["label"], mode="lines", - line=dict(width=0, color=proj_bands["color"]), + line={"width": 0, "color": proj_bands["color"]}, name=proj_bands["label"], # If PDOS is present, use those legend entries showlegend=True if self.plot_type == "bands" else False, @@ -363,7 +370,7 @@ def _customize_combined_layout(self, fig): self._customize_layout(fig, self._bands_xaxis, self._bands_yaxis) self._customize_layout(fig, self._pdos_xaxis, self._pdos_yaxis, col=2) fig.update_layout( - legend=dict(xanchor="left", x=1.06), + legend={"xanchor": "left", "x": 1.06}, height=self.SETTINGS["combined_plot_height"], width=self.SETTINGS["combined_plot_width"], plot_bgcolor="white", @@ -374,7 +381,11 @@ def _customize_layout(self, fig, xaxis, yaxis, row=1, col=1): fig.update_yaxes(patch=yaxis, row=row, col=col, showticklabels=True) fig.add_hline( y=0, - line=dict(color=self.SETTINGS["horizontal_linecolor"], width=1, dash="dot"), + line={ + "color": self.SETTINGS["horizontal_linecolor"], + "width": 1, + "dash": "dot", + }, row=row, col=col, ) @@ -543,14 +554,14 @@ def _download(payload, filename): from IPython.display import Javascript javas = Javascript( - """ + f""" var link = document.createElement('a'); link.href = 'data:text/json;charset=utf-8;base64,{payload}' link.download = "{filename}" document.body.appendChild(link); link.click(); document.body.removeChild(link); - """.format(payload=payload, filename=filename) + """ ) display(javas) @@ -903,13 +914,9 @@ def _curate_orbitals(orbital): qn_j = orbital_data["total_angular_momentum"] qn_l = orbital_data["angular_momentum"] qn_m_j = orbital_data["magnetic_number"] - orbital_name = "j {j} l {l} m_j{m_j}".format(j=qn_j, l=qn_l, m_j=qn_m_j) - orbital_name_plotly = "j={j} l={l} mj={m_j}".format( - j=HTML_TAGS.get(qn_j, qn_j), - l=qn_l, - m_j=HTML_TAGS.get(qn_m_j, qn_m_j), - ) - orbital_angular_momentum = "l {l} ".format(l=qn_l) + orbital_name = f"j {qn_j} l {qn_l} m_j{qn_m_j}" + orbital_name_plotly = f"j={HTML_TAGS.get(qn_j, qn_j)} l={qn_l} mj={HTML_TAGS.get(qn_m_j, qn_m_j)}" + orbital_angular_momentum = f"l {qn_l} " return orbital_name_plotly, orbital_angular_momentum, kind_name, atom_position @@ -982,7 +989,7 @@ def _projections_curated_options( curated_proj = [] for label, (energy, proj_pdos) in _proj_pdos.items(): - label += SPIN_LABELS[spin_type] + label += SPIN_LABELS[spin_type] # noqa: PLW2901 if projections_pdos == "pdos": orbital_proj_pdos = { "label": label, @@ -1054,4 +1061,4 @@ def cmap(label: str) -> str: ascn = sum([ord(c) for c in label]) random.seed(ascn) - return "#%06x" % random.randint(0, 0xFFFFFF) + return f"#{random.randint(0, 0xFFFFFF):06x}" diff --git a/src/aiidalab_qe/common/node_view.py b/src/aiidalab_qe/common/node_view.py index 764652ef9..3b8d49661 100644 --- a/src/aiidalab_qe/common/node_view.py +++ b/src/aiidalab_qe/common/node_view.py @@ -6,9 +6,10 @@ import ipywidgets as ipw import nglview import traitlets as tl +from ase import Atoms + from aiida import orm from aiidalab_widgets_base import register_viewer_widget -from ase import Atoms from .widgets import CalcJobOutputFollower, LogOutputWidget @@ -29,10 +30,10 @@ def __init__(self, structure, *args, **kwargs): self.structure = structure super().__init__( + *args, children=[ self._viewer, ], - *args, **kwargs, ) @@ -45,7 +46,7 @@ def _default_supercell(self): return [1, 1, 1] @tl.validate("structure") - def _valid_structure(self, change): # pylint: disable=no-self-use + def _valid_structure(self, change): """Update structure.""" structure = change["value"] @@ -74,9 +75,9 @@ def _update_displayed_structure(self, change): def _update_structure_viewer(self, change): """Update the view if displayed_structure trait was modified.""" with self.hold_trait_notifications(): - for comp_id in self._viewer._ngl_component_ids: # pylint: disable=protected-access + for comp_id in self._viewer._ngl_component_ids: self._viewer.remove_component(comp_id) - self.selection = list() + self.selection = [] if change["new"] is not None: self._viewer.add_component(nglview.ASEStructure(change["new"])) self._viewer.clear() @@ -87,7 +88,7 @@ def _update_structure_viewer(self, change): class VBoxWithCaption(ipw.VBox): def __init__(self, caption, body, *args, **kwargs): - super().__init__(children=[ipw.HTML(caption), body], *args, **kwargs) + super().__init__(*args, children=[ipw.HTML(caption), body], **kwargs) @register_viewer_widget("process.calculation.calcjob.CalcJobNode.") diff --git a/src/aiidalab_qe/common/panel.py b/src/aiidalab_qe/common/panel.py index bd6fdcfdc..40a7f98a8 100644 --- a/src/aiidalab_qe/common/panel.py +++ b/src/aiidalab_qe/common/panel.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Class to . Authors: diff --git a/src/aiidalab_qe/common/process.py b/src/aiidalab_qe/common/process.py index b1c2ea34a..799de74bc 100644 --- a/src/aiidalab_qe/common/process.py +++ b/src/aiidalab_qe/common/process.py @@ -4,6 +4,7 @@ import ipywidgets as ipw import traitlets as tl + from aiida.tools.query.calculation import CalculationQueryBuilder @@ -87,9 +88,9 @@ def __init__(self, process_label, **kwargs): self.refresh_work_chains() # the following is needed to disable the button. - def parse_extra_info(self, pk: int) -> dict: + def parse_extra_info(self, _pk: int) -> dict: """Parse extra information about the work chain.""" - return dict() + return {} def find_work_chains(self): builder = CalculationQueryBuilder() diff --git a/src/aiidalab_qe/common/setup_codes.py b/src/aiidalab_qe/common/setup_codes.py index 11ee58d1a..0da53b237 100644 --- a/src/aiidalab_qe/common/setup_codes.py +++ b/src/aiidalab_qe/common/setup_codes.py @@ -5,10 +5,10 @@ import ipywidgets as ipw import traitlets -from aiida.common.exceptions import NotExistent -from aiida.orm import load_code from filelock import FileLock, Timeout +from aiida.common.exceptions import NotExistent +from aiida.orm import load_code from aiidalab_qe.common.widgets import ProgressBar __all__ = [ @@ -99,11 +99,11 @@ def _generate_header_to_setup_code(): def _generate_string_to_setup_code(code_name, computer_name="localhost"): """Generate the Python string to setup an AiiDA code for a given computer. + Tries to load an existing code and if not existent, generates Python code to create and store a new code setup.""" try: load_code(f"{code_name}-{QE_VERSION}@{computer_name}") - return "" except NotExistent: label = f"{code_name}-{QE_VERSION}" description = f"{code_name}.x ({QE_VERSION}) setup by AiiDAlab." @@ -130,6 +130,9 @@ def _generate_string_to_setup_code(code_name, computer_name="localhost"): prepend_text, ) return python_code + else: + # the code already exists + return "" def setup_codes(): diff --git a/src/aiidalab_qe/common/setup_pseudos.py b/src/aiidalab_qe/common/setup_pseudos.py index f367dccfd..4285456e4 100644 --- a/src/aiidalab_qe/common/setup_pseudos.py +++ b/src/aiidalab_qe/common/setup_pseudos.py @@ -1,19 +1,18 @@ -# -*- coding: utf-8 -*- from __future__ import annotations import os +from collections.abc import Iterable from dataclasses import dataclass, field from pathlib import Path from subprocess import run from threading import Thread -from typing import Iterable import ipywidgets as ipw import traitlets -from aiida.orm import QueryBuilder from aiida_pseudo.groups.family import PseudoPotentialFamily from filelock import FileLock, Timeout +from aiida.orm import QueryBuilder from aiidalab_qe.common.widgets import ProgressBar SSSP_VERSION = "1.3" @@ -225,7 +224,7 @@ def install( if len(pseudos_to_install()) > 0: raise RuntimeError( "Installation process did not finish in the expected time." - ) + ) from None class PseudosInstallWidget(ProgressBar): @@ -287,7 +286,7 @@ def _default_error(self): @traitlets.observe("busy") @traitlets.observe("error") @traitlets.observe("installed") - def _update(self, change): + def _update(self, _change): with self.hold_trait_notifications(): if self.hide_by_default: self.layout.visibility = ( diff --git a/src/aiidalab_qe/common/widgets.py b/src/aiidalab_qe/common/widgets.py index b15165883..0d07650a2 100644 --- a/src/aiidalab_qe/common/widgets.py +++ b/src/aiidalab_qe/common/widgets.py @@ -15,19 +15,18 @@ import ipywidgets as ipw import numpy as np import traitlets -from aiida.orm import CalcJobNode +from IPython.display import HTML, Javascript, clear_output, display +from pymatgen.core.periodic_table import Element + +from aiida.orm import CalcJobNode, load_code, load_node from aiida.orm import Data as orm_Data -from aiida.orm import load_code, load_node +from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData from aiidalab_widgets_base import ComputationalResourcesWidget from aiidalab_widgets_base.utils import ( StatusHTML, list_to_string_range, string_range_to_list, ) -from IPython.display import HTML, Javascript, clear_output, display -from pymatgen.core.periodic_table import Element -from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData - __all__ = [ "CalcJobOutputFollower", @@ -43,7 +42,7 @@ class RollingOutput(ipw.VBox): value = traitlets.Unicode() auto_scroll = traitlets.Bool() - def __init__(self, num_min_lines=10, max_output_height="200px", **kwargs): + def __init__(self, num_min_lines=10, max_output_height="200px", **kwargs): # noqa: ARG002 self._num_min_lines = num_min_lines self._output = ipw.HTML(layout=ipw.Layout(min_width="50em")) self._refresh_output() @@ -121,14 +120,14 @@ def __on_click(self, _): digest = hashlib.md5(self.payload).hexdigest() # bypass browser cache payload = base64.b64encode(self.payload).decode() - id = f"dl_{digest}" + link_id = f"dl_{digest}" display( HTML( f""" - + - - - """ - ) + return + + contents: bytes = self.contents().encode("utf-8") + b64 = base64.b64encode(contents) + payload = b64.decode() + digest = hashlib.md5(contents).hexdigest() # bypass browser cache + link_id = f"dl_{digest}" + + display( + HTML( + f""" + + + + + + + + """ ) + ) def write_csv(dataset): @@ -276,9 +276,9 @@ def _update_view(self): Select spectrum to plot
    """ ) final_spectra, equivalent_sites_data = export_xas_data(self.outputs) - xas_wc = [ + xas_wc = next( n for n in self.node.called if n.process_label == "XspectraCrystalWorkChain" - ][0] + ) core_wcs = { n.get_metadata_inputs()["metadata"]["call_link_label"]: n for n in xas_wc.called @@ -310,7 +310,7 @@ def _update_view(self): min=0.0, max=5, step=0.1, - description="$\Gamma_{hole}$", # noqa: W605 + description="$\Gamma_{hole}$", disabled=False, continuous_update=False, orientation="horizontal", @@ -323,7 +323,7 @@ def _update_view(self): max=10, step=0.5, continuous_update=False, - description="$\Gamma_{max}$", # noqa: W605 + description="$\Gamma_{max}$", disabled=True, orientation="horizontal", readout=True, @@ -350,7 +350,7 @@ def _update_view(self): # # init figure g = go.FigureWidget( layout=go.Layout( - title=dict(text="XAS"), + title={"text": "XAS"}, barmode="overlay", ) ) @@ -394,7 +394,7 @@ def _update_download_selection(dataset, element): download_data.contents = lambda: write_csv(dataset) download_data.filename = f"{element}_XAS_Spectra.csv" - def response(change): + def response(_change): chosen_spectrum = spectrum_select.value chosen_spectrum_label = f"{chosen_spectrum}_xas" element_sites = [ diff --git a/src/aiidalab_qe/plugins/xas/setting.py b/src/aiidalab_qe/plugins/xas/setting.py index 2d9b52535..165143295 100644 --- a/src/aiidalab_qe/plugins/xas/setting.py +++ b/src/aiidalab_qe/plugins/xas/setting.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Panel for XAS plugin.""" import os @@ -10,8 +9,8 @@ import requests import traitlets as tl import yaml -from aiida import orm +from aiida import orm from aiidalab_qe.common.panel import Panel from aiidalab_qe.plugins import xas as xas_folder @@ -46,10 +45,10 @@ def _load_or_import_nodes_from_filenames(in_dict, path, core_wfc_data=False): def _download_extract_pseudo_archive(func): - dir = f"{head_path}/{dir_header}/{func}" + target_dir = f"{head_path}/{dir_header}/{func}" archive_filename = f"{func}_ch_pseudos.tgz" remote_archive_filename = f"{base_url}/{func}/{archive_filename}" - local_archive_filename = f"{dir}/{archive_filename}" + local_archive_filename = f"{target_dir}/{archive_filename}" env = os.environ.copy() env["PATH"] = f"{env['PATH']}:{Path.home() / '.local' / 'lib'}" @@ -62,16 +61,16 @@ def _download_extract_pseudo_archive(func): response.close() with tarfile.open(local_archive_filename, "r:gz") as tarfil: - tarfil.extractall(dir) + tarfil.extractall(target_dir) url = f"{base_url}" for func in functionals: - dir = f"{head_path}/{dir_header}/{func}" - os.makedirs(dir, exist_ok=True) + target_dir = f"{head_path}/{dir_header}/{func}" + os.makedirs(target_dir, exist_ok=True) archive_filename = f"{func}_ch_pseudos.tgz" archive_found = False - for entry in os.listdir(dir): + for entry in os.listdir(target_dir): if entry == archive_filename: archive_found = True if not archive_found: @@ -273,7 +272,7 @@ def _update_element_select_panel(self): ] ch_pseudos = self.core_hole_pseudos structure = self.input_structure - available_elements = [k for k in ch_pseudos] + available_elements = list(ch_pseudos) elements_to_select = sorted( [ kind.symbol diff --git a/src/aiidalab_qe/plugins/xas/workchain.py b/src/aiidalab_qe/plugins/xas/workchain.py index d5b3cf000..07f51bfda 100644 --- a/src/aiidalab_qe/plugins/xas/workchain.py +++ b/src/aiidalab_qe/plugins/xas/workchain.py @@ -1,12 +1,12 @@ from importlib import resources import yaml + from aiida import orm from aiida.plugins import WorkflowFactory from aiida_quantumespresso.common.types import ElectronicType, SpinType -from aiidalab_qe.plugins.utils import set_component_resources - from aiidalab_qe.plugins import xas as xas_folder +from aiidalab_qe.plugins.utils import set_component_resources XspectraCrystalWorkChain = WorkflowFactory("quantumespresso.xspectra.crystal") PSEUDO_TOC = yaml.safe_load(resources.read_text(xas_folder, "pseudo_toc.yaml")) diff --git a/src/aiidalab_qe/plugins/xps/result.py b/src/aiidalab_qe/plugins/xps/result.py index 3c0016c99..57789f555 100644 --- a/src/aiidalab_qe/plugins/xps/result.py +++ b/src/aiidalab_qe/plugins/xps/result.py @@ -29,7 +29,7 @@ def export_xps_data(outputs): def xps_spectra_broadening( - points, equivalent_sites_data, gamma=0.3, sigma=0.3, label="", intensity=1.0 + points, equivalent_sites_data, gamma=0.3, sigma=0.3, _label="", intensity=1.0 ): """Broadening the XPS spectra with Voigt function and return the spectra data""" @@ -169,7 +169,7 @@ def _update_view(self): # init figure self.g = go.FigureWidget( layout=go.Layout( - title=dict(text="XPS"), + title={"text": "XPS"}, barmode="overlay", ) ) @@ -189,7 +189,7 @@ def _update_view(self): x=d[0], y=d[1], fill="tozeroy", name=site.replace("_", " ") ) - def response(change): + def response(_change): data = [] if spectra_type.value == "chemical_shift": points = chemical_shifts diff --git a/src/aiidalab_qe/plugins/xps/setting.py b/src/aiidalab_qe/plugins/xps/setting.py index 0f2077244..ecb1b4c9e 100644 --- a/src/aiidalab_qe/plugins/xps/setting.py +++ b/src/aiidalab_qe/plugins/xps/setting.py @@ -1,10 +1,9 @@ -# -*- coding: utf-8 -*- """Panel for XPS plugin.""" import ipywidgets as ipw import traitlets as tl -from aiida.orm import Group, QueryBuilder, StructureData +from aiida.orm import Group, QueryBuilder, StructureData from aiidalab_qe.common.panel import Panel base_url = "https://github.com/superstar54/xps-data/raw/main/pseudo_demo/" diff --git a/src/aiidalab_qe/plugins/xps/workchain.py b/src/aiidalab_qe/plugins/xps/workchain.py index f707ebd5e..f0a63a2ac 100644 --- a/src/aiidalab_qe/plugins/xps/workchain.py +++ b/src/aiidalab_qe/plugins/xps/workchain.py @@ -37,14 +37,14 @@ def get_builder(codes, structure, parameters, **kwargs): for label in core_level_list: element = label.split("_")[0] pseudos[element] = { - "core_hole": [ + "core_hole": next( pseudo for pseudo in pseudo_group.nodes if pseudo.label == label - ][0], - "gipaw": [ + ), + "gipaw": next( pseudo for pseudo in pseudo_group.nodes if pseudo.label == f"{element}_gs" - ][0], + ), } correction_energies[element] = ( all_correction_energies[label]["core"] diff --git a/src/aiidalab_qe/version.py b/src/aiidalab_qe/version.py index 6d4128302..5ceb56539 100644 --- a/src/aiidalab_qe/version.py +++ b/src/aiidalab_qe/version.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """This module contains project version information for both the app and the workflow.""" diff --git a/src/aiidalab_qe/workflows/__init__.py b/src/aiidalab_qe/workflows/__init__.py index 04ebc0486..6fc798afc 100644 --- a/src/aiidalab_qe/workflows/__init__.py +++ b/src/aiidalab_qe/workflows/__init__.py @@ -1,10 +1,9 @@ # AiiDA imports. +# AiiDA Quantum ESPRESSO plugin inputs. from aiida import orm from aiida.common import AttributeDict from aiida.engine import ToContext, WorkChain, if_ from aiida.plugins import DataFactory - -# AiiDA Quantum ESPRESSO plugin inputs. from aiida_quantumespresso.common.types import ElectronicType, RelaxType, SpinType from aiida_quantumespresso.data.hubbard_structure import HubbardStructureData from aiida_quantumespresso.utils.mapping import prepare_process_inputs diff --git a/start.py b/start.py index a43a898df..7d25ecf3e 100644 --- a/start.py +++ b/start.py @@ -1,7 +1,7 @@ import ipywidgets as ipw -def get_start_widget(appbase, jupbase, notebase): +def get_start_widget(appbase, jupbase, notebase): # noqa: ARG001 return ipw.HTML( f""" diff --git a/tests/configuration/test_advanced.py b/tests/configuration/test_advanced.py index 4dee1caa9..800436bd7 100644 --- a/tests/configuration/test_advanced.py +++ b/tests/configuration/test_advanced.py @@ -120,10 +120,10 @@ def test_advanced_tot_charge_settings(): def test_advanced_kpoints_mesh(): """Test Mesh Grid HTML widget.""" - from aiida import orm - from aiidalab_qe.app.configuration.advanced import AdvancedSettings + from aiida import orm + w = AdvancedSettings() # Create a StructureData for AdvancedSettings (Silicon) diff --git a/tests/conftest.py b/tests/conftest.py index 100ebdcfd..b7c83a187 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,10 +5,10 @@ import tempfile import pytest -from aiida import orm - from aiidalab_qe.common.setup_pseudos import SSSP_VERSION +from aiida import orm + pytest_plugins = ["aiida.manage.tests.pytest_fixtures"] @@ -107,10 +107,6 @@ def _generate_xy_data(xvals=None, yvals=None, xlabel=None, ylabel=None): """ from aiida.orm import XyData - xvals = xvals - yvals = yvals - xlabel = xlabel - ylabel = ylabel xunits = "n/a" yunits = ["n/a"] * len(ylabel) @@ -130,6 +126,7 @@ def generate_bands_data(): def _generate_bands_data(): """Return a `BandsData` instance with some basic `kpoints` and `bands` arrays.""" import numpy as np + from aiida.plugins import DataFactory BandsData = DataFactory("core.array.bands") @@ -153,6 +150,7 @@ def generate_projection_data(generate_bands_data): def _generate_projection_data(): """Return an ``ProjectionData`` node.""" import numpy as np + from aiida.plugins import DataFactory, OrbitalFactory ProjectionData = DataFactory("core.array.projection") @@ -197,33 +195,34 @@ def sssp(aiida_profile, generate_upf_data): cutoffs = {} stringency = "standard" - with tempfile.TemporaryDirectory() as dirpath: + actinides = ( + "Ac", + "Th", + "Pa", + "U", + "Np", + "Pu", + "Am", + "Cm", + "Bk", + "Cf", + "Es", + "Fm", + "Md", + "No", + "Lr", + ) + + with tempfile.TemporaryDirectory() as d: + dirpath = pathlib.Path(d) + for values in elements.values(): element = values["symbol"] - actinides = ( - "Ac", - "Th", - "Pa", - "U", - "Np", - "Pu", - "Am", - "Cm", - "Bk", - "Cf", - "Es", - "Fm", - "Md", - "No", - "Lr", - ) - if element in actinides: continue upf = generate_upf_data(element) - dirpath = pathlib.Path(dirpath) filename = dirpath / f"{element}.upf" with open(filename, "w+b") as handle: @@ -443,6 +442,7 @@ def generate_pdos_workchain( def _generate_pdos_workchain(structure, spin_type="none"): import numpy as np + from aiida import engine from aiida.orm import Dict, FolderData, RemoteData from aiida_quantumespresso.workflows.pdos import PdosWorkChain @@ -630,12 +630,12 @@ def _generate_qeapp_workchain( ): from copy import deepcopy - from aiida.orm.utils.serialize import serialize - from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep from aiidalab_qe.workflows import QeAppWorkChain + from aiida.orm.utils.serialize import serialize + # Step 1: select structure from example s1 = app.structure_step if structure is None: diff --git a/tests/test_cli.py b/tests/test_cli.py index 53c334307..91bb0668f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,9 +1,9 @@ import time -import aiida +import aiidalab_qe.__main__ as cli from click.testing import CliRunner, Result -import aiidalab_qe.__main__ as cli +import aiida # To learn more about testing click applications, see: https://click.palletsprojects.com/en/8.1.x/testing/ diff --git a/tests/test_plugins_bands.py b/tests/test_plugins_bands.py index 27533eec0..b3c2a9dc8 100644 --- a/tests/test_plugins_bands.py +++ b/tests/test_plugins_bands.py @@ -3,8 +3,8 @@ @pytest.mark.usefixtures("sssp") def test_result(generate_qeapp_workchain): - from aiidalab_qe.common.bandpdoswidget import BandPdosWidget import plotly.graph_objects as go + from aiidalab_qe.common.bandpdoswidget import BandPdosWidget from aiidalab_qe.plugins.bands.result import Result wkchain = generate_qeapp_workchain() diff --git a/tests/test_plugins_electronic_structure.py b/tests/test_plugins_electronic_structure.py index c2c0203d3..271adfd1b 100644 --- a/tests/test_plugins_electronic_structure.py +++ b/tests/test_plugins_electronic_structure.py @@ -1,12 +1,12 @@ def test_electronic_structure(generate_qeapp_workchain): """Test the electronic structure tab.""" - from aiida import engine - + import plotly.graph_objects as go from aiidalab_qe.app.result.workchain_viewer import WorkChainViewer from aiidalab_qe.common.bandpdoswidget import BandPdosWidget - import plotly.graph_objects as go from aiidalab_qe.plugins.electronic_structure.result import Result + from aiida import engine + wkchain = generate_qeapp_workchain() wkchain.node.set_exit_status(0) wkchain.node.set_process_state(engine.ProcessState.FINISHED) @@ -14,11 +14,11 @@ def test_electronic_structure(generate_qeapp_workchain): # find the tab with the identifier "electronic_structure" # the built-in summary and structure tabs is not a plugin panel, # thus don't have identifiers - tab = [ + tab = next( tab for tab in wcv.result_tabs.children if getattr(tab, "identifier", "") == "electronic_structure" - ][0] + ) # It should have one children: the _bands_plot_view assert len(tab.children) == 1 diff --git a/tests/test_plugins_pdos.py b/tests/test_plugins_pdos.py index 6d02a140e..426393694 100644 --- a/tests/test_plugins_pdos.py +++ b/tests/test_plugins_pdos.py @@ -3,8 +3,8 @@ @pytest.mark.usefixtures("sssp") def test_result(generate_qeapp_workchain): - from aiidalab_qe.common.bandpdoswidget import BandPdosWidget import plotly.graph_objects as go + from aiidalab_qe.common.bandpdoswidget import BandPdosWidget from aiidalab_qe.plugins.pdos.result import Result wkchain = generate_qeapp_workchain() diff --git a/tests/test_plugins_xps.py b/tests/test_plugins_xps.py index 6bc0d9af3..8f15c3b80 100644 --- a/tests/test_plugins_xps.py +++ b/tests/test_plugins_xps.py @@ -7,6 +7,7 @@ def test_settings(): from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep from ase.build import molecule + from aiida.orm import StructureData configure_step = ConfigureQeAppWorkChainStep() diff --git a/tests/test_pseudo.py b/tests/test_pseudo.py index 2412b63bd..a85c34e86 100644 --- a/tests/test_pseudo.py +++ b/tests/test_pseudo.py @@ -1,6 +1,4 @@ import pytest -from aiida import orm - from aiidalab_qe.common.setup_pseudos import ( PSEUDODOJO_VERSION, SSSP_VERSION, @@ -9,6 +7,8 @@ pseudos_to_install, ) +from aiida import orm + def test_setup_pseudos_cmd(tmp_path): """Test _construct_cmd function in setup_pseudos.py.""" @@ -96,7 +96,7 @@ def test_setup_pseudos_cmd(tmp_path): "--pseudo-format", "upf", "--from-download", - f"{str(tmp_path)}/PseudoDojo_{PSEUDODOJO_VERSION}_PBEsol_SR_standard_upf.aiida_pseudo", + f"{tmp_path!s}/PseudoDojo_{PSEUDODOJO_VERSION}_PBEsol_SR_standard_upf.aiida_pseudo", ] @@ -111,7 +111,7 @@ def test_pseudos_installation(): } # Install the pseudos - [_ for _ in _install_pseudos(EXPECTED_PSEUDOS)] + list(_install_pseudos(EXPECTED_PSEUDOS)) # Two pseudos are installed assert len(pseudos_to_install()) == 10 @@ -127,13 +127,13 @@ def test_download_and_install_pseudo_from_file(tmp_path): } # Download the pseudos to the tmp_path but not install - [_ for _ in _install_pseudos(EXPECTED_PSEUDOS, download_only=True, cwd=tmp_path)] + list(_install_pseudos(EXPECTED_PSEUDOS, download_only=True, cwd=tmp_path)) assert len(pseudos_to_install()) == 12 assert len(list(tmp_path.iterdir())) == 2 # Install the pseudos from the tmp_path - [_ for _ in _install_pseudos(EXPECTED_PSEUDOS, cwd=tmp_path)] + list(_install_pseudos(EXPECTED_PSEUDOS, cwd=tmp_path)) # Two pseudos are installed assert len(pseudos_to_install()) == 10 diff --git a/tests/test_result.py b/tests/test_result.py index c6e087d17..9deb51f0a 100644 --- a/tests/test_result.py +++ b/tests/test_result.py @@ -43,9 +43,8 @@ def test_summary_report_advanced_settings(data_regression, generate_qeapp_workch def test_summary_view(generate_qeapp_workchain): """Test the report html can be properly generated.""" - from bs4 import BeautifulSoup - from aiidalab_qe.app.result.summary_viewer import SummaryView + from bs4 import BeautifulSoup wkchain = generate_qeapp_workchain() viewer = SummaryView(wkchain.node) diff --git a/tests_integration/test_app.py b/tests_integration/test_app.py index d847e7ff0..e1475282b 100755 --- a/tests_integration/test_app.py +++ b/tests_integration/test_app.py @@ -1,7 +1,7 @@ import time from pathlib import Path -import selenium.webdriver.support.expected_conditions as EC +import selenium.webdriver.support.expected_conditions as EC # noqa: N812 from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait