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

Support building and publishing to PYPI #1101

Merged
merged 4 commits into from
Jan 20, 2025
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Create QeApp class as the entry point of the app
  • Loading branch information
superstar54 committed Jan 17, 2025
commit 0d6b50079ca886448c25dc1bd8349aa3d7e2ff4f
61 changes: 9 additions & 52 deletions qe.ipynb
Original file line number Diff line number Diff line change
@@ -24,17 +24,6 @@
"load_profile();"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_widgets_base.utils.loaders import load_css\n",
"\n",
"load_css(css_path=\"src/aiidalab_qe/app/static/styles\")"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -50,43 +39,6 @@
" pass"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView\n",
"\n",
"model = AppWrapperModel()\n",
"view = AppWrapperView()\n",
"controller = AppWrapperContoller(model, view)\n",
"\n",
"display(view)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from aiidalab_qe.app.main import App\n",
"from aiidalab_widgets_base.bug_report import (\n",
" install_create_github_issue_exception_handler,\n",
")\n",
"\n",
"install_create_github_issue_exception_handler(\n",
" view.output,\n",
" url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\",\n",
" labels=(\"bug\", \"automated-report\"),\n",
")\n",
"\n",
"app = App(qe_auto_setup=True)\n",
"\n",
"view.main.children = [app]"
]
},
{
"cell_type": "code",
"execution_count": null,
@@ -95,10 +47,15 @@
"source": [
"import urllib.parse as urlparse\n",
"\n",
"from aiidalab_qe.app.main import QeApp\n",
"\n",
"url = urlparse.urlsplit(jupyter_notebook_url) # noqa F821\n",
"query = urlparse.parse_qs(url.query)\n",
"if \"pk\" in query:\n",
" app.process = query[\"pk\"][0]"
"pk = query[\"pk\"][0] if \"pk\" in query else None\n",
"\n",
"app = QeApp(\n",
" process=pk, bug_report_url=\"https://github.com/aiidalab/aiidalab-qe/issues/new\"\n",
")"
]
},
{
@@ -107,13 +64,13 @@
"metadata": {},
"outputs": [],
"source": [
"controller.enable_controls()"
"app.load()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "base",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
254 changes: 48 additions & 206 deletions src/aiidalab_qe/app/main.py
Original file line number Diff line number Diff line change
@@ -1,210 +1,52 @@
"""The main widget that shows the application in the Jupyter notebook.
"""The main app that shows the application in the Jupyter notebook.

Authors: AiiDAlab team
"""

import ipywidgets as ipw
import traitlets as tl
from IPython.display import Javascript, display

from aiida.orm import load_node
from aiida.orm.utils.serialize import deserialize_unsafe
from aiidalab_qe.app.configuration import ConfigureQeAppWorkChainStep
from aiidalab_qe.app.configuration.model import ConfigurationStepModel
from aiidalab_qe.app.result import ViewQeAppWorkChainStatusAndResultsStep
from aiidalab_qe.app.result.model import ResultsStepModel
from aiidalab_qe.app.structure import StructureSelectionStep
from aiidalab_qe.app.structure.model import StructureStepModel
from aiidalab_qe.app.submission import SubmitQeAppWorkChainStep
from aiidalab_qe.app.submission.model import SubmissionStepModel
from aiidalab_qe.common.infobox import InAppGuide
from aiidalab_qe.common.widgets import LoadingWidget, QeWizardStep
from aiidalab_widgets_base import WizardAppWidget


class App(ipw.VBox):
"""The main widget that combines all the application steps together."""

# The PK or UUID of the work chain node.
process = tl.Union([tl.Unicode(), tl.Int()], allow_none=True)

def __init__(self, qe_auto_setup=True):
# Initialize the models
self.structure_model = StructureStepModel()
self.configure_model = ConfigurationStepModel()
self.submit_model = SubmissionStepModel()
self.results_model = ResultsStepModel()

# Create the application steps
self.structure_step = StructureSelectionStep(
model=self.structure_model,
auto_advance=True,
)
self.configure_step = ConfigureQeAppWorkChainStep(
model=self.configure_model,
auto_advance=True,
)
self.submit_step = SubmitQeAppWorkChainStep(
model=self.submit_model,
auto_advance=True,
qe_auto_setup=qe_auto_setup,
)
self.results_step = ViewQeAppWorkChainStatusAndResultsStep(
model=self.results_model,
)

# Wizard step observations
ipw.dlink(
(self.structure_step, "state"),
(self.configure_step, "previous_step_state"),
)
self.structure_model.observe(
self._on_structure_confirmation_change,
"confirmed",
)
ipw.dlink(
(self.configure_step, "state"),
(self.submit_step, "previous_step_state"),
)
self.configure_model.observe(
self._on_configuration_confirmation_change,
"confirmed",
)
ipw.dlink(
(self.submit_step, "state"),
(self.results_step, "previous_step_state"),
)
self.submit_model.observe(
self._on_submission,
"confirmed",
)

# Add the application steps to the application
self._wizard_app_widget = WizardAppWidget(
steps=[
("Select structure", self.structure_step),
("Configure workflow", self.configure_step),
("Choose computational resources", self.submit_step),
("Status & results", self.results_step),
]
)
self._wizard_app_widget.observe(
self._on_step_change,
"selected_index",
)

# Hide the header
self._wizard_app_widget.children[0].layout.display = "none" # type: ignore

self._process_loading_message = LoadingWidget(
message="Loading process",
layout=ipw.Layout(display="none"),
)

super().__init__(
children=[
InAppGuide(identifier="guide-header"),
self._process_loading_message,
self._wizard_app_widget,
InAppGuide(identifier="post-guide"),
]
)

self._wizard_app_widget.selected_index = None

self.structure_step.state = QeWizardStep.State.READY

self._update_blockers()

@property
def steps(self):
return self._wizard_app_widget.steps

@tl.observe("process")
def _on_process_change(self, change):
self._update_from_process(change["new"])

def _on_new_workchain_button_click(self, _):
display(Javascript("window.open('./qe.ipynb', '_blank')"))

def _on_step_change(self, change):
if (step_index := change["new"]) is not None:
self._render_step(step_index)

def _on_structure_confirmation_change(self, _):
self._update_configuration_step()
self._update_blockers()

def _on_configuration_confirmation_change(self, _):
self._update_submission_step()
self._update_blockers()

def _on_submission(self, _):
self._update_results_step()
self._lock_app()

def _render_step(self, step_index):
step: QeWizardStep = self.steps[step_index][1]
step.render()

def _update_configuration_step(self):
if self.structure_model.confirmed:
self.configure_model.input_structure = self.structure_model.input_structure
else:
self.configure_model.input_structure = None

def _update_submission_step(self):
if self.configure_model.confirmed:
self.submit_model.input_structure = self.structure_model.input_structure
self.submit_model.input_parameters = self.configure_model.get_model_state()
else:
self.submit_model.input_structure = None
self.submit_model.input_parameters = {}

def _update_results_step(self):
ipw.dlink(
(self.submit_model, "process_node"),
(self.results_model, "process_uuid"),
lambda node: node.uuid if node is not None else None,
)

def _lock_app(self):
for model in (
self.structure_model,
self.configure_model,
self.submit_model,
):
model.unobserve_all("confirmed")

def _update_blockers(self):
self.submit_model.external_submission_blockers = [
f"Unsaved changes in the <b>{title}</b> step. Please confirm the changes before submitting."
for title, step in self.steps[:2]
if not step.is_saved()
]

def _update_from_process(self, pk):
if pk is None:
self._wizard_app_widget.reset()
self._wizard_app_widget.selected_index = 0
else:
self._show_process_loading_message()
process_node = load_node(pk)
self.structure_model.input_structure = process_node.inputs.structure
self.structure_model.confirm()
parameters = process_node.base.extras.get("ui_parameters", {})
if parameters and isinstance(parameters, str):
parameters = deserialize_unsafe(parameters)
self.configure_model.set_model_state(parameters)
self.configure_model.confirm()
self.submit_model.process_node = process_node
self.submit_model.set_model_state(parameters)
self.submit_model.confirm()
self._wizard_app_widget.selected_index = 3
self._hide_process_loading_message()

def _show_process_loading_message(self):
self._process_loading_message.layout.display = "flex"

def _hide_process_loading_message(self):
self._process_loading_message.layout.display = "none"
from pathlib import Path

from IPython.display import display

from aiidalab_qe.app.static import styles
from aiidalab_qe.app.wizard_app import WizardApp
from aiidalab_qe.app.wrapper import AppWrapperContoller, AppWrapperModel, AppWrapperView
from aiidalab_widgets_base.bug_report import (
install_create_github_issue_exception_handler,
)
from aiidalab_widgets_base.utils.loaders import load_css


class QeApp:
def __init__(self, process=None, bug_report_url=None):
"""Initialize the AiiDAlab QE application with the necessary setup."""
self._load_styles()

# Initialize MVC components
self.model = AppWrapperModel()
self.view = AppWrapperView()
display(self.view)

# Set up bug report handling (if a URL is provided)
if bug_report_url:
install_create_github_issue_exception_handler(
self.view.output,
url=bug_report_url,
labels=("bug", "automated-report"),
)

# setup UI controls
self.controller = AppWrapperContoller(self.model, self.view)
self.controller.enable_controls()
self.process = process

def _load_styles(self):
"""Load CSS styles from the static directory."""
load_css(css_path=Path(styles.__file__).parent)

def load(self):
"""Initialize the WizardApp and integrate the app into the main view."""
self.app = WizardApp(qe_auto_setup=True)
self.view.main.children = [self.app]
# load a previous calculation if it is provided
if self.process:
self.app.process = self.process
Loading
Loading