Skip to content

Commit

Permalink
Merge branch 'master' into feature/connect-to-more-elns
Browse files Browse the repository at this point in the history
  • Loading branch information
yakutovicha committed Dec 4, 2024
2 parents 59471bb + 2677368 commit b35cad9
Show file tree
Hide file tree
Showing 34 changed files with 590 additions and 271 deletions.
25 changes: 25 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# Configuration for automatically generated release notes:
# https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
# We exclude PRs from bots and categorize PRs based on labels
changelog:
exclude:
authors:
- dependabot
- pre-commit-ci
categories:
- title: Breaking Changes 🛠
labels:
- breaking
- title: New Features 🎉
labels:
- enhancement
- title: Bug fixes 🐛
labels:
- bug
- title: Documentation 📝
labels:
- docs
- title: Other Changes
labels:
- "*"
30 changes: 19 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ name: CI
on:
push:
branches:
- main
- master
pull_request:

env:
FORCE_COLOR: "1"
UV_VERSION: "0.4.6"

# https://docs.github.com/en/actions/using-jobs/using-concurrency
concurrency:
Expand All @@ -23,7 +26,8 @@ jobs:
strategy:
matrix:
browser: [Chrome, Firefox]
aiida-core-version: [2.1.2, 2.4.3] # test on the latest and the oldest supported version
# test on the latest and the oldest supported version
aiida-core-version: [2.1.2, 2.6.2]
fail-fast: false

runs-on: ubuntu-latest
Expand All @@ -39,8 +43,10 @@ jobs:
with:
python-version: '3.10'

- name: Install uv
run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.1.44/uv-installer.sh | sh
- name: Setup uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}

- name: Install package test dependencies
# Notebook tests happen in the container, here we only need to install
Expand Down Expand Up @@ -82,9 +88,9 @@ jobs:

strategy:
matrix:
python-version: ['3.9', '3.10']
python-version: ['3.9', '3.11']
# Test on the latest and oldest supported version
aiida-core-version: [2.2.2, 2.4.3]
aiida-core-version: [2.2.2, 2.6.2]
fail-fast: false

runs-on: ubuntu-latest
Expand All @@ -107,8 +113,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/uv/releases/download/0.1.27/uv-installer.sh | sh
- name: Setup uv
uses: astral-sh/setup-uv@v4
with:
version: ${{ env.UV_VERSION }}

- name: Install package
# NOTE: uv (unlike pip) does not compile python to bytecode after install.
Expand All @@ -117,13 +125,13 @@ jobs:
# Ideally, these would be fixed, but vapory is largely unmaintained,
# so here we simply keep the pip behaviour with the --compile flag.
# See https://github.com/astral-sh/uv/issues/1928#issuecomment-1968857514
run: uv pip install --compile --system .[dev,smiles,optimade] aiida-core==${{ matrix.aiida-core-version }}
run: uv pip install --compile --system -e .[dev,smiles,optimade,eln] aiida-core==${{ matrix.aiida-core-version }}

- name: Run pytest
run: pytest -v tests --cov
run: pytest -v tests --cov=aiidalab_widgets_base

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
flags: python-${{ matrix.python-version }}
token: ${{ secrets.CODECOV_TOKEN }}
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ ci:

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v5.0.0
hooks:
- id: check-json
- id: check-yaml
Expand All @@ -13,7 +13,7 @@ repos:
exclude: miscellaneous/structures

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.6.9
hooks:
- id: ruff-format
exclude: ^docs/.*
Expand All @@ -27,7 +27,7 @@ repos:
args: [--preserve-quotes]

- repo: https://github.com/sirosen/check-jsonschema
rev: 0.28.1
rev: 0.29.3
hooks:
- id: check-github-workflows

Expand Down
42 changes: 27 additions & 15 deletions aiidalab_widgets_base/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""Reusable widgets for AiiDAlab applications."""

from aiida.manage import get_profile

_WARNING_TEMPLATE = """
<div style="background-color: #f7f7f7; border: 2px solid #e0e0e0; padding: 20px; border-radius: 5px;">
<p style="font-size: 16px; font-weight: bold; color: #ff5733;">Warning:</p>
Expand All @@ -13,6 +11,20 @@
"""


def load_default_profile():
"""Loads the default profile if none loaded and warn of deprecation."""
from aiida import load_profile

load_profile()

profile = get_profile()
assert profile is not None, "Failed to load the default profile"

# raise a deprecation warning
warning = HTML(_WARNING_TEMPLATE.format(profile=profile.name, version="v3.0.0"))
display(warning)


# We only detect profile and throw a warning if it is on the notebook
# It is not necessary to do this in the unit tests
def is_running_in_jupyter():
Expand All @@ -27,22 +39,22 @@ def is_running_in_jupyter():
return False


# load the default profile if no profile is loaded, and raise a deprecation warning
# this is a temporary solution to avoid breaking existing notebooks
# this will be removed in the next major release
if is_running_in_jupyter() and get_profile() is None:
# if no profile is loaded, load the default profile and raise a deprecation warning
from aiida import load_profile
if is_running_in_jupyter():
from pathlib import Path

from aiida.manage import get_profile
from IPython.display import HTML, display

load_profile()
# load the default profile if no profile is loaded, and raise a deprecation warning
# this is a temporary solution to avoid breaking existing notebooks
# this will be removed in the next major release
if get_profile() is None:
load_default_profile()

profile = get_profile()
assert profile is not None, "Failed to load the default profile"
from .utils.loaders import load_css

load_css(css_path=Path(__file__).parent / "static/styles")

# raise a deprecation warning
warning = HTML(_WARNING_TEMPLATE.format(profile=profile.name, version="v3.0.0"))
display(warning)

from .computational_resources import (
ComputationalResourcesWidget,
Expand Down Expand Up @@ -117,4 +129,4 @@ def is_running_in_jupyter():
"viewer",
]

__version__ = "2.2.2"
__version__ = "2.3.0a2"
17 changes: 15 additions & 2 deletions aiidalab_widgets_base/computational_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,16 +1196,26 @@ def on_setup_code(self, _=None):
"append_text",
]

containerized_code_additional_items = [
"image_name",
"engine_command",
]

kwargs = {key: getattr(self, key).value for key in items_to_configure}

# Check for additional keys needed for orm.ContainerizedCode
for container_key in containerized_code_additional_items:
if container_key in self.code_setup.keys():
kwargs[container_key] = self.code_setup[container_key]

# set computer from its widget value the UUID of the computer.
computer = orm.load_computer(self.computer.value)

# Checking if the code with this name already exists
qb = orm.QueryBuilder()
qb.append(orm.Computer, filters={"uuid": computer.uuid}, tag="computer")
qb.append(
orm.InstalledCode,
orm.AbstractCode,
with_computer="computer",
filters={"label": kwargs["label"]},
)
Expand All @@ -1223,7 +1233,10 @@ def on_setup_code(self, _=None):
return False

try:
code = orm.InstalledCode(computer=computer, **kwargs)
if "image_name" in kwargs.keys():
code = orm.ContainerizedCode(computer=computer, **kwargs)
else:
code = orm.InstalledCode(computer=computer, **kwargs)
except (common.exceptions.InputValidationError, KeyError) as exception:
self.message = wrap_message(
f"Invalid input for code creation: <code>{exception}</code>",
Expand Down
33 changes: 26 additions & 7 deletions aiidalab_widgets_base/elns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
from pathlib import Path

import ipywidgets as ipw
import requests_cache
import traitlets as tl
from aiida import orm
from aiidalab_eln import get_eln_connector
from IPython.display import clear_output, display

ELN_CONFIG = Path.home() / ".aiidalab" / "aiidalab-eln-config.json"
Expand All @@ -15,6 +13,13 @@


def connect_to_eln(eln_instance=None, **kwargs):
try:
from aiidalab_eln import get_eln_connector
except ImportError:
return (
None,
"AiiDAlab-ELN connector not installed. Install with `pip install aiidalab-eln`",
)
# assuming that the connection can only be established to the ELNs
# with the stored configuration.
try:
Expand Down Expand Up @@ -62,6 +67,8 @@ class ElnImportWidget(ipw.VBox):
node = tl.Instance(orm.Node, allow_none=True)

def __init__(self, path_to_root="../", **kwargs):
import requests_cache

# Used to output additional settings.
self._output = ipw.Output()

Expand All @@ -77,8 +84,9 @@ def __init__(self, path_to_root="../", **kwargs):
return

tl.dlink((eln, "node"), (self, "node"))
# Since the requests cache is enabled globally in aiidalab package, we disable it here to get correct results.
# This can be removed once https://github.com/aiidalab/aiidalab/issues/196 is fixed.
with requests_cache.disabled():
# Since the cache is enabled in AiiDAlab, we disable it here to get correct results.
eln.import_data()


Expand Down Expand Up @@ -124,9 +132,8 @@ def _observe_node(self, _=None):
if self.node is None or self.eln is None:
return

if "eln" in self.node.extras:
info = self.node.extras["eln"]
else:
info = self.node.base.extras.get("eln", {})
if not info:
try:
q = orm.QueryBuilder().append(
orm.Node,
Expand Down Expand Up @@ -175,10 +182,13 @@ def get_all_structures_and_geoopts(self, node):
return all_structures, all_geoopts

def send_to_eln(self, _=None):
import requests_cache

if self.eln and self.eln.is_connected:
self.message.value = f"\u29d7 Sending data to {self.eln.eln_instance}..."
# Since the requests cache is enabled globally in aiidalab package, we disable it here to get correct results.
# This can be removed once https://github.com/aiidalab/aiidalab/issues/196 is fixed.
with requests_cache.disabled():
# Since the cache is enabled in AiiDAlab, we disable it here to get correct results.
self.eln.export_data()
self.message.value = (
f"\u2705 The data were successfully sent to {self.eln.eln_instance}."
Expand Down Expand Up @@ -306,6 +316,15 @@ def check_connection(self, _=None):

def display_eln_config(self, value=None):
"""Display ELN configuration specific to the selected type of ELN."""
try:
from aiidalab_eln import get_eln_connector
except ImportError:
with self._output:
clear_output()
msg = "AiiDAlab-ELN connector not installed. Install with `pip install aiidalab-eln`"
display(ipw.HTML(f"&#10060; {msg}"))
return

try:
eln_class = get_eln_connector(self.eln_types.value)
except NotImplementedError as err:
Expand Down
6 changes: 3 additions & 3 deletions aiidalab_widgets_base/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ExportButtonWidget(ipw.Button):
def __init__(self, process, **kwargs):
self.process = process
if "description" not in kwargs:
kwargs["description"] = f"Export workflow ({self.process.id})"
kwargs["description"] = f"Export workflow ({self.process.pk})"
if "layout" not in kwargs:
kwargs["layout"] = {}
kwargs["layout"]["width"] = "initial"
Expand All @@ -28,7 +28,7 @@ def export_aiida_subgraph(self, change=None): # pylint: disable=unused-argument

fname = os.path.join(tempfile.mkdtemp(), "export.aiida")
subprocess.call(
["verdi", "archive", "create", fname, "-N", str(self.process.id)]
["verdi", "archive", "create", fname, "-N", str(self.process.pk)]
)
with open(fname, "rb") as fobj:
b64 = base64.b64encode(fobj.read())
Expand All @@ -41,6 +41,6 @@ def export_aiida_subgraph(self, change=None): # pylint: disable=unused-argument
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
""".format(payload=payload, filename=f"export_{self.process.id}.aiida")
""".format(payload=payload, filename=f"export_{self.process.pk}.aiida")
)
display(javas)
3 changes: 2 additions & 1 deletion aiidalab_widgets_base/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import traitlets as tl
from aiida import common, engine, orm
from aiida.cmdline.utils.ascii_vis import calc_info
from aiidalab.app import _AiidaLabApp
from IPython.display import clear_output, display

CALCULATION_TYPES = [
Expand Down Expand Up @@ -326,6 +325,8 @@ def find_node(self, pk, namespaces=None):

class _AppIcon:
def __init__(self, app, path_to_root, node):
from aiidalab.app import _AiidaLabApp

name = app["name"]
app_object = _AiidaLabApp.from_id(name)
self.logo = app_object.metadata["logo"]
Expand Down
6 changes: 3 additions & 3 deletions aiidalab_widgets_base/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def show_selected_output(self, change=None):
clear_output()
if change["new"]:
selected_output = self.process.outputs[change["new"]]
self.info.value = f"PK: {selected_output.id}"
self.info.value = f"PK: {selected_output.pk}"
display(viewer(selected_output))


Expand Down Expand Up @@ -537,7 +537,7 @@ def __init__(self, title="Running Job Output", **kwargs):
self.title = title
self.selection = ipw.Dropdown(
description="Select calculation:",
options=tuple((p.id, p) for p in get_running_calcs(self.process)),
options=tuple((p.pk, p) for p in get_running_calcs(self.process)),
style={"description_width": "initial"},
)
self.output = CalcJobOutputWidget()
Expand All @@ -551,7 +551,7 @@ def update(self):
with self.hold_trait_notifications():
old_label = self.selection.label
self.selection.options = tuple(
(str(p.id), p) for p in get_running_calcs(self.process)
(str(p.pk), p) for p in get_running_calcs(self.process)
)
# If the selection remains the same.
if old_label in self.selection.options:
Expand Down
Empty file.
Loading

0 comments on commit b35cad9

Please sign in to comment.