Skip to content

Commit b8a5b45

Browse files
authored
Merge pull request #42 from NREL/pp/status_upgrades
Status upgrades + Apptainer
2 parents a99acb1 + 598c184 commit b8a5b45

File tree

11 files changed

+145
-14
lines changed

11 files changed

+145
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Apptainer Build and Publish
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
build-publish-container:
10+
name: Build and Publish for ${{ matrix.os }}
11+
runs-on: ${{ matrix.os }}
12+
strategy:
13+
matrix:
14+
include:
15+
- os: ubuntu-latest
16+
artifact_name: gaps.sif
17+
asset_name: gaps-linux-amd64
18+
body: GAPs Apptainer Image (ubuntu-latest)
19+
# - os: windows-latest
20+
# artifact_name: mything.exe
21+
# asset_name: mything-windows-amd64
22+
# - os: macos-latest
23+
# artifact_name: mything
24+
# asset_name: mything-macos-amd64
25+
permissions:
26+
contents: read
27+
packages: write
28+
29+
container:
30+
image: quay.io/singularity/singularity:v3.8.1
31+
options: --privileged
32+
33+
steps:
34+
35+
- name: Check out code for the container builds
36+
uses: actions/checkout@v2
37+
38+
- name: Build Container
39+
run: |
40+
singularity build gaps.sif Apptainer
41+
42+
# - name: Login and Deploy Container
43+
# run: |
44+
# echo ${{ secrets.GITHUB_TOKEN }} | singularity remote login -u ${{ secrets.GHCR_USERNAME }} --password-stdin oras://ghcr.io
45+
# singularity push container.sif oras://ghcr.io/${GITHUB_REPOSITORY}:${tag}
46+
47+
- name: Upload container to release
48+
uses: svenstaro/upload-release-action@v2
49+
with:
50+
repo_token: ${{ secrets.GITHUB_TOKEN }}
51+
file: ${{ matrix.artifact_name }}
52+
asset_name: ${{ matrix.asset_name }}
53+
tag: ${{ github.ref }}
54+
overwrite: true
55+
body: ${{ matrix.body }}

Apptainer

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
Bootstrap: docker
2+
From: python:3.11
3+
4+
%labels
5+
Author Paul Pinchuk
6+
Maintainer [email protected]
7+
URL https://github.com/NREL/gaps
8+
9+
%post
10+
echo "Installing vim"
11+
apt-get update && apt-get -y upgrade
12+
apt-get -y --allow-unauthenticated install vim
13+
14+
echo "Installing GAPs..."
15+
pip install NREL-gaps
16+
17+
%runscript
18+
"$@"

gaps/_cli.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
GAPs CLI entry points.
4+
"""
5+
import click
6+
7+
from gaps.version import __version__
8+
from gaps.cli.status import status_command
9+
10+
11+
@click.group()
12+
@click.version_option(version=__version__)
13+
@click.pass_context
14+
def main(ctx):
15+
"""GAPs command line interface."""
16+
ctx.ensure_object(dict)
17+
18+
19+
main.add_command(status_command(), name="status")
20+
21+
22+
if __name__ == "__main__":
23+
# pylint: disable=no-value-for-parameter
24+
main(obj={})

gaps/cli/cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
"""
3-
Generation CLI entry points.
3+
Main CLI entry points.
44
"""
55
from functools import partial
66

gaps/cli/execution.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
"""
55
import logging
66
import datetime as dt
7+
from pathlib import Path
78
from warnings import warn
89
from inspect import signature
910

10-
from gaps.hpc import submit
11+
from gaps.hpc import submit, DEFAULT_STDOUT_PATH
1112
from gaps.status import (
1213
DT_FMT,
1314
Status,
@@ -153,6 +154,10 @@ def _kickoff_hpc_job(ctx, cmd, hardware_option, **kwargs):
153154
id_msg = f" (Job ID #{out})" if out else ""
154155
msg = f"Kicked off {command!r} job {name!r}{id_msg}"
155156

157+
stdout_dir = Path(kwargs.get("stdout_path", DEFAULT_STDOUT_PATH))
158+
stdout_log_file = str(stdout_dir / f"{name}_{out}.o")
159+
stdout_err_log_file = str(stdout_dir / f"{name}_{out}.e")
160+
156161
Status.mark_job_as_submitted(
157162
ctx.obj["OUT_DIR"],
158163
pipeline_step=ctx.obj["PIPELINE_STEP"],
@@ -164,6 +169,8 @@ def _kickoff_hpc_job(ctx, cmd, hardware_option, **kwargs):
164169
StatusField.QOS: kwargs.get("qos") or QOSOption.UNSPECIFIED,
165170
StatusField.JOB_STATUS: StatusOption.SUBMITTED,
166171
StatusField.TIME_SUBMITTED: dt.datetime.now().strftime(DT_FMT),
172+
StatusField.STDOUT_LOG: stdout_log_file,
173+
StatusField.STDOUT_ERR_LOG: stdout_err_log_file,
167174
},
168175
)
169176
logger.info(msg)

gaps/cli/status.py

+2
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,8 @@ def main_monitor(folder, pipe_steps, status, include, recursive):
321321
for directory in folders:
322322
if not directory.is_dir():
323323
continue
324+
if directory.name == Status.HIDDEN_SUB_DIR:
325+
continue
324326

325327
pipe_status = Status(directory)
326328
if not pipe_status:

gaps/status.py

+11
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class StatusField(CaseInsensitiveEnum):
4242
TOTAL_RUNTIME = "total_runtime"
4343
RUNTIME_SECONDS = "runtime_seconds"
4444
MONITOR_PID = "monitor_pid"
45+
STDOUT_LOG = "stdout_log"
46+
STDOUT_ERR_LOG = "stdout_err_log"
4547

4648

4749
# pylint: disable=no-member
@@ -865,6 +867,7 @@ def _add_elapsed_time(status_df):
865867
has_not_failed = status_df[StatusField.JOB_STATUS] != StatusOption.FAILED
866868
mask = has_start_time & (has_no_end_time & has_not_failed)
867869

870+
status_df = _add_time_cols_if_needed(status_df)
868871
start_times = status_df.loc[mask, StatusField.TIME_START]
869872
start_times = pd.to_datetime(start_times, format=DT_FMT)
870873
elapsed_times = dt.datetime.now() - start_times
@@ -876,6 +879,14 @@ def _add_elapsed_time(status_df):
876879
return status_df
877880

878881

882+
def _add_time_cols_if_needed(status_df):
883+
"""Adds any missing time cols to avoid pandas 2.0 warnings"""
884+
for col in [StatusField.RUNTIME_SECONDS, StatusField.TOTAL_RUNTIME]:
885+
if col not in status_df:
886+
status_df[col] = None
887+
return status_df
888+
889+
879890
def _load(fpath):
880891
"""Load status json."""
881892
if fpath.is_file():

gaps/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""GAPs Version Number. """
22

3-
__version__ = "0.6.6"
3+
__version__ = "0.6.7"

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,5 @@
5959
"dev": TEST_REQUIREMENTS + DEV_REQUIREMENTS,
6060
"docs": TEST_REQUIREMENTS + DEV_REQUIREMENTS + DOC_REQUIREMENTS,
6161
},
62+
entry_points={"console_scripts": ["gaps=gaps._cli:main"]},
6263
)

tests/cli/test_cli_execution.py

+2
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ def _test_submit(cmd):
187187
status = json.load(status_fh)
188188

189189
assert status["run"][job_name][StatusField.HARDWARE] == "eagle"
190+
assert "9999.o" in status["run"][job_name][StatusField.STDOUT_LOG]
191+
assert "9999.e" in status["run"][job_name][StatusField.STDOUT_ERR_LOG]
190192
if high_qos:
191193
assert status["run"][job_name][StatusField.QOS] == "high"
192194
else:

tests/cli/test_cli_status.py

+22-11
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import json
66
import shutil
77
from pathlib import Path
8+
from contextlib import nullcontext
89

910
import psutil
1011
import pytest
1112

1213
from gaps.status import HardwareStatusRetriever, StatusOption, Status
1314
from gaps.cli.status import status_command
15+
from gaps._cli import main
1416
from gaps.warnings import gapsWarning
1517

1618

@@ -36,7 +38,10 @@
3638
+ "-s dne".split(),
3739
],
3840
)
39-
def test_status(test_data_dir, cli_runner, extra_args, monkeypatch):
41+
@pytest.mark.parametrize("test_main_entry", [True, False])
42+
def test_status(
43+
test_data_dir, cli_runner, extra_args, test_main_entry, monkeypatch
44+
):
4045
"""Test the status command."""
4146

4247
monkeypatch.setattr(psutil, "pid_exists", lambda *__: True, raising=True)
@@ -47,18 +52,23 @@ def test_status(test_data_dir, cli_runner, extra_args, monkeypatch):
4752
raising=True,
4853
)
4954

50-
status = status_command()
55+
if test_main_entry:
56+
status = main
57+
command_args = ["status"]
58+
else:
59+
status = status_command()
60+
command_args = []
61+
62+
command_args += [(test_data_dir / "test_run").as_posix()] + extra_args
63+
5164
if "dne" in extra_args:
52-
with pytest.warns(gapsWarning):
53-
result = cli_runner.invoke(
54-
status,
55-
[(test_data_dir / "test_run").as_posix()] + extra_args,
56-
)
65+
expected_behavior = pytest.warns(gapsWarning)
5766
else:
58-
result = cli_runner.invoke(
59-
status,
60-
[(test_data_dir / "test_run").as_posix()] + extra_args,
61-
)
67+
expected_behavior = nullcontext()
68+
69+
with expected_behavior:
70+
result = cli_runner.invoke(status, command_args)
71+
6272
lines = result.stdout.split("\n")
6373
cols = [
6474
"job_status",
@@ -299,6 +309,7 @@ def test_recursive_status(tmp_path, test_data_dir, cli_runner, monkeypatch):
299309
assert any(line == "test_run:" for line in lines)
300310
assert any(line == "test_failed_run:" for line in lines)
301311
assert len(lines) > 20
312+
assert not any(Status.HIDDEN_SUB_DIR in line for line in lines)
302313

303314

304315
if __name__ == "__main__":

0 commit comments

Comments
 (0)