alc-aiidalab-widgets 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, UKRI Science and Technology Facilities Council
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.3
2
+ Name: alc-aiidalab-widgets
3
+ Version: 0.0.1
4
+ Summary: Commonly used components for ALC managed AiiDAlab plugins
5
+ Keywords: aiida,workflows,ALC
6
+ Author: Dr. Benjamin T. Speake
7
+ Author-email: Dr. Benjamin T. Speake <benjamin.speake@stfc.ac.uk>
8
+ License: BSD 3-Clause License
9
+
10
+ Copyright (c) 2026, UKRI Science and Technology Facilities Council
11
+
12
+ Redistribution and use in source and binary forms, with or without
13
+ modification, are permitted provided that the following conditions are met:
14
+
15
+ 1. Redistributions of source code must retain the above copyright notice, this
16
+ list of conditions and the following disclaimer.
17
+
18
+ 2. Redistributions in binary form must reproduce the above copyright notice,
19
+ this list of conditions and the following disclaimer in the documentation
20
+ and/or other materials provided with the distribution.
21
+
22
+ 3. Neither the name of the copyright holder nor the names of its
23
+ contributors may be used to endorse or promote products derived from
24
+ this software without specific prior written permission.
25
+
26
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
27
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
30
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36
+ Classifier: Intended Audience :: Science/Research
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Topic :: Scientific/Engineering
39
+ Classifier: Framework :: AiiDA
40
+ Classifier: Development Status :: 1 - Planning
41
+ Requires-Dist: aiidalab-widgets-base
42
+ Requires-Dist: ipywidgets
43
+ Requires-Dist: traitlets
44
+ Requires-Dist: aiida-core>=2.6
45
+ Requires-Dist: weas-widget
46
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
47
+ Requires-Dist: pytest-cov>=6.0 ; extra == 'dev'
48
+ Requires-Dist: ruff>=0.11.0 ; extra == 'dev'
49
+ Requires-Dist: pre-commit ; extra == 'dev'
50
+ Requires-Dist: sphinx>=8.0.2 ; extra == 'docs'
51
+ Requires-Python: >=3.10
52
+ Project-URL: Source, https://github.com/stfc/alc-aiidalab-widgets
53
+ Provides-Extra: dev
54
+ Provides-Extra: docs
55
+ Description-Content-Type: text/markdown
56
+
57
+ # alc-aiidalab-widgets
58
+
59
+ [![Release](https://img.shields.io/github/v/release/stfc/alc-aiidalab-widgets)](https://github.com/stfc/alc-aiidalab-widgets/releases)
60
+ [![PyPI](https://img.shields.io/pypi/v/alc-aiidalab-widgets)](https://pypi.org/project/alc-aiidalab-widgets/)
61
+
62
+ [![Pipeline Status](https://github.com/stfc/alc-aiidalab-widgets/actions/workflows/testing.yml/badge.svg?branch=main)](https://github.com/stfc/alc-aiidalab-widgets/actions)
63
+ [![Docs status](https://github.com/stfc/alc-aiidalab-widgets/actions/workflows/docs.yml/badge.svg?branch=main)](https://stfc.github.io/alc-aiidalab-widgets/)
64
+ <!-- [![Coverage Status]( https://coveralls.io/repos/github/stfc/alc-aiidalab-widgets/badge.svg?branch=main)](https://coveralls.io/github/stfc/alc-aiidalab-widgets?branch=main) -->
65
+
66
+ <!-- [![DOI](https://zenodo.org/badge/1094975386.svg)](https://doi.org/10.5281/zenodo.19188755) -->
67
+
68
+ This is a collection of commonly used widgets used across AiiDAlab plugins developed by
69
+ the Ada Lovelace Centre (STFC). It provides re-usable components and consistent styling
70
+ which can be deployed to any new or developing AiiDAlab plugin. It can be installed via
71
+ pip,
72
+
73
+ ``` sh
74
+ pip install alc-aiidalab-widgets
75
+ ```
76
+
77
+ and then imported within any python project as,
78
+
79
+ ``` python
80
+ import alc_aiidalab_widgets
81
+ ```
82
+
83
+ ## For Developers
84
+
85
+ ### Style Checking
86
+
87
+ This package uses pre-commit hooks to check for style consistency, to use these the ``pre-commit`` tool is required.
88
+ This can be installed alongside the base package by running,
89
+
90
+ ``` sh
91
+ pip install .[dev]
92
+ ```
93
+
94
+ or separately via,
95
+
96
+ ``` sh
97
+ pip install pre-commit
98
+ ```
99
+
100
+ Once installed run,
101
+
102
+ ``` sh
103
+ pre-commit install
104
+ ```
105
+
106
+ in the base repository to enable the pre-commit hooks.
107
+ This will now run style and formatting checks on every commit.
108
+
109
+ ### Testing
110
+
111
+ This package uses [pytest](https://docs.pytest.org/en/stable/)
112
+ to run all unit tests which is included in the ```[dev]``` optional
113
+ package dependencies. Once installed it can be run from the project root directory.
114
+ The CI workflows are configured to ensure all tests pass
115
+ before a pull request can be accepted into the main repository.
116
+ It is important that any new additions to the code base are accompanied
117
+ by appropriate testing, maintaining a high code coverage. The coverage
118
+ can be checked via,
119
+
120
+ ``` sh
121
+ pytest --cov=aiidalab_alc
122
+ ```
123
+
124
+ ### Documentation
125
+
126
+ The documentation, including a User Guide, Developer Guide and an API reference,
127
+ is built using [sphinx](https://www.sphinx-doc.org/). The source
128
+ for which is contained in the ```docs/``` directory. At present
129
+ only the html generator has been fully tested. All required packages can
130
+ be installed alongside the core package via,
131
+
132
+ ``` sh
133
+ pip install .[docs]
134
+ ```
135
+
136
+ and then the documentation can be built using sphinx-build,
137
+
138
+ ``` sh
139
+ sphinx-build -b html docs/src/ docs/build/html
140
+ ```
141
+
142
+ from the root directory.
143
+
144
+ ## License
145
+
146
+ [BSD 3-Clause License](LICENSE)
147
+
148
+ ## Funding
149
+
150
+ Contributors to this project were funded by
151
+
152
+ <div align="center">
153
+ <a href="https://adalovelacecentre.ac.uk/">
154
+ <img src="images/alc.svg" alt="ALC Logo" style="width: 30%">
155
+ </a>
156
+ </div>
@@ -0,0 +1,100 @@
1
+ # alc-aiidalab-widgets
2
+
3
+ [![Release](https://img.shields.io/github/v/release/stfc/alc-aiidalab-widgets)](https://github.com/stfc/alc-aiidalab-widgets/releases)
4
+ [![PyPI](https://img.shields.io/pypi/v/alc-aiidalab-widgets)](https://pypi.org/project/alc-aiidalab-widgets/)
5
+
6
+ [![Pipeline Status](https://github.com/stfc/alc-aiidalab-widgets/actions/workflows/testing.yml/badge.svg?branch=main)](https://github.com/stfc/alc-aiidalab-widgets/actions)
7
+ [![Docs status](https://github.com/stfc/alc-aiidalab-widgets/actions/workflows/docs.yml/badge.svg?branch=main)](https://stfc.github.io/alc-aiidalab-widgets/)
8
+ <!-- [![Coverage Status]( https://coveralls.io/repos/github/stfc/alc-aiidalab-widgets/badge.svg?branch=main)](https://coveralls.io/github/stfc/alc-aiidalab-widgets?branch=main) -->
9
+
10
+ <!-- [![DOI](https://zenodo.org/badge/1094975386.svg)](https://doi.org/10.5281/zenodo.19188755) -->
11
+
12
+ This is a collection of commonly used widgets used across AiiDAlab plugins developed by
13
+ the Ada Lovelace Centre (STFC). It provides re-usable components and consistent styling
14
+ which can be deployed to any new or developing AiiDAlab plugin. It can be installed via
15
+ pip,
16
+
17
+ ``` sh
18
+ pip install alc-aiidalab-widgets
19
+ ```
20
+
21
+ and then imported within any python project as,
22
+
23
+ ``` python
24
+ import alc_aiidalab_widgets
25
+ ```
26
+
27
+ ## For Developers
28
+
29
+ ### Style Checking
30
+
31
+ This package uses pre-commit hooks to check for style consistency, to use these the ``pre-commit`` tool is required.
32
+ This can be installed alongside the base package by running,
33
+
34
+ ``` sh
35
+ pip install .[dev]
36
+ ```
37
+
38
+ or separately via,
39
+
40
+ ``` sh
41
+ pip install pre-commit
42
+ ```
43
+
44
+ Once installed run,
45
+
46
+ ``` sh
47
+ pre-commit install
48
+ ```
49
+
50
+ in the base repository to enable the pre-commit hooks.
51
+ This will now run style and formatting checks on every commit.
52
+
53
+ ### Testing
54
+
55
+ This package uses [pytest](https://docs.pytest.org/en/stable/)
56
+ to run all unit tests which is included in the ```[dev]``` optional
57
+ package dependencies. Once installed it can be run from the project root directory.
58
+ The CI workflows are configured to ensure all tests pass
59
+ before a pull request can be accepted into the main repository.
60
+ It is important that any new additions to the code base are accompanied
61
+ by appropriate testing, maintaining a high code coverage. The coverage
62
+ can be checked via,
63
+
64
+ ``` sh
65
+ pytest --cov=aiidalab_alc
66
+ ```
67
+
68
+ ### Documentation
69
+
70
+ The documentation, including a User Guide, Developer Guide and an API reference,
71
+ is built using [sphinx](https://www.sphinx-doc.org/). The source
72
+ for which is contained in the ```docs/``` directory. At present
73
+ only the html generator has been fully tested. All required packages can
74
+ be installed alongside the core package via,
75
+
76
+ ``` sh
77
+ pip install .[docs]
78
+ ```
79
+
80
+ and then the documentation can be built using sphinx-build,
81
+
82
+ ``` sh
83
+ sphinx-build -b html docs/src/ docs/build/html
84
+ ```
85
+
86
+ from the root directory.
87
+
88
+ ## License
89
+
90
+ [BSD 3-Clause License](LICENSE)
91
+
92
+ ## Funding
93
+
94
+ Contributors to this project were funded by
95
+
96
+ <div align="center">
97
+ <a href="https://adalovelacecentre.ac.uk/">
98
+ <img src="images/alc.svg" alt="ALC Logo" style="width: 30%">
99
+ </a>
100
+ </div>
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["uv-build"]
3
+ build-backend = "uv_build"
4
+
5
+
6
+ [project]
7
+ version = "0.0.1"
8
+ name = "alc-aiidalab-widgets"
9
+ description = "Commonly used components for ALC managed AiiDAlab plugins"
10
+ authors = [
11
+ { name = "Dr. Benjamin T. Speake", email = "benjamin.speake@stfc.ac.uk" },
12
+ ]
13
+
14
+ readme = "README.md"
15
+ license = { file = "LICENSE" }
16
+ keywords = ["aiida", "workflows", "ALC"]
17
+ classifiers = [
18
+ "Intended Audience :: Science/Research",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: Scientific/Engineering",
21
+ "Framework :: AiiDA",
22
+ "Development Status :: 1 - Planning",
23
+ ]
24
+ requires-python = ">=3.10"
25
+ dependencies = [
26
+ "aiidalab-widgets-base",
27
+ "ipywidgets",
28
+ "traitlets",
29
+ "aiida-core>=2.6",
30
+ "weas-widget",
31
+ ]
32
+
33
+ [project.urls]
34
+ Source = "https://github.com/stfc/alc-aiidalab-widgets"
35
+
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest>=8.0", "pytest-cov>=6.0", "ruff>=0.11.0", "pre-commit"]
39
+ docs = ["sphinx>=8.0.2"]
40
+
41
+ [tool.ruff]
42
+ target-version = "py312"
43
+ line-length = 88
44
+ indent-width = 4
45
+ exclude = ["docs/src/conf.py"]
46
+
47
+ [tool.ruff.lint.per-file-ignores]
48
+
49
+ [tool.ruff.lint]
50
+ select = [
51
+ # flake8-bugbear
52
+ "B",
53
+ # pylint
54
+ "C",
55
+ "R",
56
+ # pydocstyle
57
+ "D",
58
+ # pycodestyle
59
+ "E",
60
+ "W",
61
+ # Pyflakes
62
+ "F",
63
+ "FA",
64
+ # isort
65
+ "I",
66
+ # pep8-naming
67
+ "N",
68
+ # pyupgrade
69
+ "UP",
70
+ ]
71
+ ignore = ["C901"]
72
+
73
+ [tool.ruff.lint.pydocstyle]
74
+ convention = "numpy"
75
+
76
+ [tool.ruff.lint.pylint]
77
+ max-args = 10
@@ -0,0 +1,5 @@
1
+ """The main ALC AiiDAlab Widgets python package."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("alc-aiidalab-widgets")
@@ -0,0 +1 @@
1
+ """Module providing reusable and extensible widget implementations."""
@@ -0,0 +1,54 @@
1
+ """Defines a resource setup widget based on foundations from aiidalab-widgets-base."""
2
+
3
+ from aiidalab_widgets_base.computational_resources import ResourceSetupBaseWidget
4
+ from aiidalab_widgets_base.utils import StatusHTML
5
+ from ipywidgets import HTML, Text, VBox, dlink
6
+ from traitlets import HasTraits, Unicode, observe
7
+
8
+
9
+ class CodeSetupWidget(VBox, HasTraits):
10
+ """Widget to setup a new code instance.
11
+
12
+ Extends the existing ResourceSetupBaseWidget from aiidalab-widgets-base with the
13
+ ability to alter the database source link. By default using this widget will
14
+ point to the provided ALC resources database contained within the
15
+ https://github.com/stfc/alc-ux repository.
16
+ """
17
+
18
+ _database_source = Unicode(
19
+ "",
20
+ allow_none=False,
21
+ )
22
+
23
+ def __init__(self, **kwargs):
24
+ """CodeSetupWidget constructor."""
25
+ self.source = Text(
26
+ value=self._database_source, description="Source: ", layout={"width": "80%"}
27
+ )
28
+ dlink((self.source, "value"), (self, "_database_source"))
29
+ self.resource_widget = ResourceSetupBaseWidget()
30
+ self.setup_message = StatusHTML(clear_after=15)
31
+ dlink(
32
+ (self.resource_widget, "message"),
33
+ (self.setup_message, "message"),
34
+ )
35
+
36
+ self.source.value = "https://raw.githubusercontent.com/stfc/alc-ux/refs/heads/main/resources/remotes.json"
37
+
38
+ children = [
39
+ HTML("<hr>"),
40
+ self.source,
41
+ HTML("<hr>"),
42
+ self.resource_widget,
43
+ self.setup_message,
44
+ ]
45
+ super().__init__(children=children, **kwargs)
46
+ return
47
+
48
+ @observe("_database_source")
49
+ def _update_database_source(self, _):
50
+ """Update the database source in the ResourceSetupBaseWidget."""
51
+ self.resource_widget.comp_resources_database.database_source = (
52
+ self._database_source
53
+ )
54
+ return
@@ -0,0 +1,185 @@
1
+ """Module for components relating to AiiDA database management."""
2
+
3
+ import datetime
4
+
5
+ import ipywidgets as ipw
6
+ from aiida.orm import (
7
+ CalcFunctionNode,
8
+ CalcJobNode,
9
+ Node,
10
+ QueryBuilder,
11
+ WorkChainNode,
12
+ )
13
+ from traitlets import HasTraits, Instance
14
+
15
+
16
+ class AiiDADatabaseQueryWidget(ipw.VBox, HasTraits):
17
+ """Widget for AiiDA database searching."""
18
+
19
+ data_object = Instance(Node, allow_none=True)
20
+
21
+ def __init__(self, title: str = "", query: list | None = None):
22
+ """AiiDADatabaseQueryWidget constructor.
23
+
24
+ Creates a widgets for searching specific nodes within the AiiDA database,
25
+ based on an input query list of node types. It also provides further options
26
+ for tailoring the search.
27
+
28
+ Parameters
29
+ ----------
30
+ title: str
31
+ The widget's title
32
+ query: List | None
33
+ A list of AiiDA node types to search for in the database, if None will
34
+ default to search for CalcJobNodes and WorkChainNodes.
35
+ """
36
+ if query is None:
37
+ query = []
38
+ self.title = title
39
+ self.query_type = tuple(query)
40
+
41
+ qbuilder = QueryBuilder().append((CalcJobNode, WorkChainNode), project="label")
42
+
43
+ self.drop_down = ipw.Dropdown(
44
+ options=sorted({"All"}.union({i[0] for i in qbuilder.iterall() if i[0]})),
45
+ value="All",
46
+ description="Process Label",
47
+ disabled=True,
48
+ style={"description_width": "120px"},
49
+ layout={"width": "50%"},
50
+ )
51
+ self.drop_down.observe(self.search, names="value")
52
+
53
+ # Disable process labels selection if we are not looking for the calculated
54
+ # structures.
55
+ def disable_drop_down(change):
56
+ self.drop_down.disabled = not change["new"] == "calculated"
57
+
58
+ # Select structures kind.
59
+ self.mode = ipw.RadioButtons(
60
+ options=["all", "uploaded", "calculated"], layout={"width": "25%"}
61
+ )
62
+ self.mode.observe(self.search, names="value")
63
+ self.mode.observe(disable_drop_down, names="value")
64
+
65
+ # Date range.
66
+ # Note: there is Date picker widget, but it currently does not work in Safari:
67
+ # https://ipywidgets.readthedocs.io/en/latest/examples/Widget%20List.html#Date-picker
68
+ date_text = ipw.HTML(value="<p>Select the date range:</p>")
69
+ self.start_date_widget = ipw.Text(
70
+ value="", description="From: ", style={"description_width": "120px"}
71
+ )
72
+ self.end_date_widget = ipw.Text(value="", description="To: ")
73
+
74
+ # Search button.
75
+ btn_search = ipw.Button(
76
+ description="Search",
77
+ button_style="info",
78
+ layout={"width": "initial", "margin": "2px 0 0 2em"},
79
+ )
80
+ btn_search.on_click(self.search)
81
+
82
+ age_selection = ipw.VBox(
83
+ [
84
+ date_text,
85
+ ipw.HBox([self.start_date_widget, self.end_date_widget, btn_search]),
86
+ ],
87
+ layout={"border": "1px solid #fafafa", "padding": "1em"},
88
+ )
89
+
90
+ h_line = ipw.HTML("<hr>")
91
+ box = ipw.VBox([age_selection, h_line, ipw.HBox([self.mode, self.drop_down])])
92
+
93
+ self.results = ipw.Dropdown(layout={"width": "900px"})
94
+ self.results.observe(self._on_select_node, names="value")
95
+ self.search()
96
+ super().__init__([box, h_line, self.results])
97
+
98
+ def search(self, _=None) -> None:
99
+ """Search structures in the AiiDA database."""
100
+ qbuild = QueryBuilder()
101
+
102
+ # If the date range is valid, use it for the search
103
+ try:
104
+ start_date = datetime.datetime.strptime(
105
+ self.start_date_widget.value, "%Y-%m-%d"
106
+ )
107
+ end_date = datetime.datetime.strptime(
108
+ self.end_date_widget.value, "%Y-%m-%d"
109
+ ) + datetime.timedelta(hours=24)
110
+
111
+ # Otherwise revert to the standard (i.e. last 7 days)
112
+ except ValueError:
113
+ start_date = datetime.datetime.now() - datetime.timedelta(days=7)
114
+ end_date = datetime.datetime.now() + datetime.timedelta(hours=24)
115
+
116
+ self.start_date_widget.value = start_date.strftime("%Y-%m-%d")
117
+ self.end_date_widget.value = end_date.strftime("%Y-%m-%d")
118
+
119
+ filters = {}
120
+ filters["ctime"] = {"and": [{">": start_date}, {"<=": end_date}]}
121
+
122
+ if self.mode.value == "uploaded":
123
+ qbuild2 = (
124
+ QueryBuilder()
125
+ .append(self.query_type, project=["id"], tag="structures")
126
+ .append(Node, with_outgoing="structures")
127
+ )
128
+ processed_nodes = [n[0] for n in qbuild2.all()]
129
+ if processed_nodes:
130
+ filters["id"] = {"!in": processed_nodes}
131
+ qbuild.append(self.query_type, filters=filters)
132
+
133
+ elif self.mode.value == "calculated":
134
+ if self.drop_down.value == "All":
135
+ qbuild.append((CalcJobNode, WorkChainNode), tag="calcjobworkchain")
136
+ else:
137
+ qbuild.append(
138
+ (CalcJobNode, WorkChainNode),
139
+ filters={"label": self.drop_down.value},
140
+ tag="calcjobworkchain",
141
+ )
142
+ qbuild.append(
143
+ self.query_type,
144
+ with_incoming="calcjobworkchain",
145
+ filters=filters,
146
+ )
147
+
148
+ elif self.mode.value == "edited":
149
+ qbuild.append(CalcFunctionNode)
150
+ qbuild.append(
151
+ self.query_type,
152
+ with_incoming=CalcFunctionNode,
153
+ filters=filters,
154
+ )
155
+
156
+ elif self.mode.value == "all":
157
+ qbuild.append(self.query_type, filters=filters)
158
+
159
+ qbuild.order_by({self.query_type: {"ctime": "desc"}})
160
+ matches = {n[0] for n in qbuild.iterall()}
161
+ matches = sorted(matches, reverse=True, key=lambda n: n.ctime)
162
+
163
+ options = [(f"Select a Node ({len(matches)} found)", False)]
164
+ for mch in matches:
165
+ label = f"PK: {mch.pk}"
166
+ label += " | " + mch.ctime.strftime("%Y-%m-%d %H:%M")
167
+ label += " | " + mch.base.extras.get("formula", "")
168
+ label += " | " + mch.node_type.split(".")[-2]
169
+ label += " | " + mch.label
170
+ label += " | " + mch.description
171
+ options.append((label, mch))
172
+
173
+ self.results.options = options
174
+ return
175
+
176
+ def _on_select_node(self, _) -> None:
177
+ """Update the selected node."""
178
+ self.data_object = self.results.value or None
179
+ return
180
+
181
+ def disable(self, val: bool) -> None:
182
+ """Disable the widget."""
183
+ self.results.disabled = True
184
+ # self.
185
+ return
@@ -0,0 +1,97 @@
1
+ """Module for providing widgets to deal with files."""
2
+
3
+ from io import BytesIO
4
+
5
+ import traitlets as tl
6
+ from aiida.orm import SinglefileData
7
+ from ipywidgets import FileUpload, HBox, Text
8
+
9
+
10
+ class FileUploadWidget(HBox, tl.HasTraits):
11
+ """A widget for uploading files."""
12
+
13
+ file = tl.Instance(SinglefileData, allow_none=True)
14
+
15
+ def __init__(self, description: str = "File: ", **kwargs):
16
+ """
17
+ FileUploadWidget constructor.
18
+
19
+ Parameters
20
+ ----------
21
+ **kwargs :
22
+ Keyword arguments passed to the parent class's constructor.
23
+ """
24
+ super().__init__(**kwargs)
25
+ self.file_dict = None
26
+
27
+ self.file_upload = FileUpload(
28
+ accept="",
29
+ multiple=False,
30
+ description="Upload",
31
+ layout={"width": "20%"},
32
+ )
33
+ self.file_handle = Text(
34
+ value="",
35
+ placeholder="",
36
+ description=description,
37
+ disabled=True,
38
+ layout={"width": "70%"},
39
+ )
40
+ self.children = [self.file_handle, self.file_upload]
41
+
42
+ self.file_upload.observe(self._on_file_upload, names="value")
43
+
44
+ return
45
+
46
+ @property
47
+ def has_file(self) -> bool:
48
+ """True if a file has been uploaded."""
49
+ return self.file is not None
50
+
51
+ def _on_file_upload(self, _):
52
+ """Handle file upload events."""
53
+ if self.file_upload.value:
54
+ try:
55
+ self.file_dict = self.file_upload.value[
56
+ list(self.file_upload.value.keys())[0]
57
+ ]
58
+ self.file_handle.value = self.file_dict["metadata"]["name"]
59
+ except AttributeError:
60
+ self.file_dict = self.file_upload.value[0]
61
+ self.file_handle.value = self.file_dict["name"]
62
+ finally:
63
+ self.file = self.get_aiida_file_object()
64
+ else:
65
+ self.file_handle.value = ""
66
+ return
67
+
68
+ def get_file_contents(self) -> BytesIO | None:
69
+ """Get the contents of the uploaded file as a BytesIO object."""
70
+ if self.file_dict is not None:
71
+ return BytesIO(self.file_dict["content"])
72
+ return None
73
+
74
+ def filename(self) -> str:
75
+ """Get the name of the uploaded file."""
76
+ if self.file_dict is not None:
77
+ try:
78
+ return self.file_dict["metadata"]["name"]
79
+ except KeyError:
80
+ return self.file_dict["name"]
81
+ return ""
82
+
83
+ def get_aiida_file_object(self):
84
+ """Get the uploaded file as an AiiDA SinglefileData object."""
85
+ if self.file_dict is not None:
86
+ return SinglefileData(
87
+ file=self.get_file_contents(),
88
+ filename=self.filename(),
89
+ label=self.filename(),
90
+ description=self.file_handle.description,
91
+ )
92
+ return None
93
+
94
+ def disable(self, val: bool) -> None:
95
+ """Disable the file upload widget."""
96
+ self.file_upload.disabled = val
97
+ return
@@ -0,0 +1,26 @@
1
+ """Temporary widgets used to fill wait times."""
2
+
3
+ from ipywidgets import HTML, HBox, Label, Layout
4
+
5
+
6
+ class LoadingWidget(HBox):
7
+ """Widget for displaying a loading spinner."""
8
+
9
+ def __init__(self, message="Loading", **kwargs):
10
+ """LoadingWidget constructor."""
11
+ super().__init__(
12
+ children=[
13
+ Label(message),
14
+ HTML(
15
+ value="<i class='fa fa-spinner fa-spin fa-2x fa-fw'/>",
16
+ layout=Layout(margin="12px 0 6px"),
17
+ ),
18
+ ],
19
+ layout=Layout(
20
+ justify_content="center",
21
+ align_items="center",
22
+ **kwargs.pop("layout", {}),
23
+ ),
24
+ **kwargs,
25
+ )
26
+ self.add_class("loading")
@@ -0,0 +1,80 @@
1
+ """Defines a widget for visualisation of chemical structures."""
2
+
3
+ from pathlib import Path
4
+ from tempfile import NamedTemporaryFile
5
+
6
+ from aiida.orm import StructureData
7
+ from ase import Atoms
8
+ from ase import io as ase_io
9
+ from ipywidgets import HTML, VBox
10
+ from weas_widget import WeasWidget
11
+
12
+
13
+ class StructureViewWidget(VBox):
14
+ """Visualise atom structure using weas_widget."""
15
+
16
+ def __init__(self, **kwargs):
17
+ """StructureViewWidget constructor."""
18
+ super().__init__(**kwargs)
19
+ self.message = HTML("<p>No Structure Currently Loaded</p>")
20
+ self.viewer = None
21
+ self.children = [
22
+ self.message,
23
+ ]
24
+
25
+ def assign_structure_from_file(self, fname: str, content: bytes) -> None:
26
+ """Visualise the given structure from a file.
27
+
28
+ Parameters
29
+ ----------
30
+ fname: str
31
+ The structure file path.
32
+ content: bytes
33
+ The content of the file.
34
+ """
35
+ suffix = "".join(Path(fname).suffixes)
36
+ with NamedTemporaryFile(suffix=suffix) as tmpf:
37
+ tmpf.write(content)
38
+ tmpf.flush()
39
+ try:
40
+ structure = ase_io.read(tmpf.name, index=":")[0]
41
+ except (KeyError, ase_io.formats.UnknownFileTypeError):
42
+ self.message = HTML("<p>Could not visualise structure...</p>")
43
+ self.children = [
44
+ self.message,
45
+ ]
46
+ except Exception as e:
47
+ raise e
48
+ else:
49
+ self.viewer = WeasWidget()
50
+ self.viewer.from_ase(structure)
51
+ self.children = [
52
+ self.viewer,
53
+ ]
54
+ return
55
+
56
+ def assign_structure_from_ase(self, structure: Atoms) -> None:
57
+ """Visualise the given ASE structure.
58
+
59
+ Parameters
60
+ ----------
61
+ structure: Atoms
62
+ The ASE atoms structure object.
63
+ """
64
+ self.viewer = WeasWidget()
65
+ self.viewer.from_ase(structure)
66
+ self.children = [
67
+ self.viewer,
68
+ ]
69
+ return
70
+
71
+ def assign_structure_from_structuredata(self, structure: StructureData) -> None:
72
+ """Visualise the given AiiDA StructureData object.
73
+
74
+ Parameters
75
+ ----------
76
+ structure: StructureData
77
+ The AiiDA StructureData object.
78
+ """
79
+ self.assign_structure_from_ase(structure._get_object_ase())
80
+ return
@@ -0,0 +1,60 @@
1
+ """Widgets used for displaying tabular information."""
2
+
3
+ from aiida.orm import ArrayData
4
+ from ipywidgets import HTML, Dropdown, VBox
5
+
6
+
7
+ class XYZArrayDataTableWidget(VBox):
8
+ """
9
+ Custom widget to display array data produced from ChemShell jobs.
10
+
11
+ Create a table based widget for displaying different arrays within
12
+ an ArrayData object assuming that all the data is XYZ based i.e
13
+ atomic positions of forces.
14
+ """
15
+
16
+ def __init__(self, array: ArrayData, **kwargs):
17
+ """AiidaArrayDataViewWidget Constructor.
18
+
19
+ Parameters
20
+ ----------
21
+ array : ArrayData
22
+ The AiiDA ArrayData object to display.
23
+ """
24
+ super().__init__(**kwargs)
25
+ self.array = array
26
+ self.array_names = array.get_arraynames()
27
+
28
+ self.array_selector = Dropdown(
29
+ options=self.array_names,
30
+ description="Array Label:",
31
+ disabled=False,
32
+ layout={"width": "30%"},
33
+ )
34
+ self._render_array({"new": self.array_selector.index, "old": -1})
35
+ self.array_selector.observe(self._render_array, "index")
36
+
37
+ return
38
+
39
+ def _render_array(self, change) -> None:
40
+ """Create a HTML table based on the currently selected array."""
41
+ index = change["new"]
42
+ if index == change["old"]:
43
+ return
44
+ values = self.array.get_array(self.array_names[index])
45
+ # Construct HTML Table
46
+ html = "<table style='width:100%; border: 1px solid #ddd; text-align: left; "
47
+ html += "border-collapse: collapse;'>"
48
+ html += "<tr style='background-color: #2196F3; color: white;'>"
49
+ html += "<th> </th><th>X</th><th>Y</th><th>Z</th></tr>"
50
+
51
+ for idx, row in enumerate(values):
52
+ bg_color = "#f9f9f9" if idx % 2 == 0 else "#ffffff"
53
+ html += f"<tr style='background-color: {bg_color};'>"
54
+ html += f"<td><b>{idx}</b></td><td>{row[0]:.6f}</td><td>{row[1]:.6f}</td>"
55
+ html += f"<td>{row[2]:.6f}</td>"
56
+ html += "</tr>"
57
+ html += "</table>"
58
+
59
+ self.children = [self.array_selector, HTML(html)]
60
+ return