Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ on:
description: 'SFINCS_BASE_TAG'
required: false
type: string
EWTS_ORG:
description: 'EWTS_ORG'
required: false
type: string
EWTS_REF:
description: 'EWTS_REF'
required: false
type: string

permissions:
contents: read
Expand Down Expand Up @@ -262,6 +270,8 @@ jobs:
BMI_BASE_TAG=${{ needs.setup.outputs.bmi_base_tag }}
BMI_BASE_DIGEST=${{ needs.setup.outputs.bmi_base_digest }}
BMI_BASE_REVISION=${{ needs.setup.outputs.bmi_base_revision }}
EWTS_ORG=${{ inputs.EWTS_ORG || 'NGWPC' }}
EWTS_REF=${{ inputs.EWTS_REF || 'development' }}
IMAGE_SOURCE=https://github.com/${{ github.repository }}
IMAGE_VENDOR=${{ github.repository_owner }}
IMAGE_VERSION=${{ needs.setup.outputs.clean_ref }}
Expand Down
55 changes: 54 additions & 1 deletion Dockerfile.bmi-forcings
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,53 @@ ENV OMPI_MCA_btl_base_warn_component_unused=0
# Reset SHELL so we're not locked into the gcc-10 build environment
SHELL ["/bin/bash", "-c"]

# ── EWTS (Error, Warning and Trapping System)
# ngen-bmi-forcing only needs the EWTS Python package (not the C/C++/Fortran
# libraries or ngen bridge that the full ngen image requires).
#
# Build args – override at build time to pin a branch, tag, or full commit SHA:
# docker build --build-arg EWTS_REF=v1.2.3 ...
# docker build --build-arg EWTS_REF=abc123def456 ...
ARG EWTS_ORG=NGWPC
ARG EWTS_REF=development

# Clone nwm-ewts, install the Python package, capture git metadata for
# provenance, then remove the source tree.
# Try shallow clone by branch/tag name first; fall back to full clone + checkout
# for bare commit SHAs (which git clone -b doesn't support).
#
# NOTE: Unlike the ngen Dockerfile, clone + pip install + cleanup are kept in a
# single RUN so the source tree never persists in a layer. In ngen the split is
# safe because cmake installs the wheel to /opt/ewts before the source is removed;
# here there is no cmake step, so the source must remain until pip finishes.
RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache \
set -eux && \
(git clone --depth 1 -b "${EWTS_REF}" \
"https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts \
|| (git clone "https://github.com/${EWTS_ORG}/nwm-ewts.git" /tmp/nwm-ewts && \
cd /tmp/nwm-ewts && git checkout "${EWTS_REF}")) && \
cd /tmp/nwm-ewts && \
# ── Capture EWTS git provenance ──
# Saved as a JSON sidecar so the git-info step below can merge EWTS
# metadata alongside ngen-bmi-forcing.
jq -n \
--arg commit_hash "$(git rev-parse HEAD)" \
--arg branch "$(git branch -r --contains HEAD 2>/dev/null | grep -v '\->' | sed 's|origin/||' | head -n1 | xargs || echo "${EWTS_REF}")" \
--arg tags "$(git tag --points-at HEAD 2>/dev/null | tr '\n' ' ')" \
--arg author "$(git log -1 --pretty=format:'%an')" \
--arg commit_date "$(date -u -d @$(git log -1 --pretty=format:'%ct') +'%Y-%m-%d %H:%M:%S UTC')" \
--arg message "$(git log -1 --pretty=format:'%s' | tr '\n' ';')" \
--arg build_date "$(date -u +'%Y-%m-%d %H:%M:%S UTC')" \
'{"nwm-ewts": {commit_hash: $commit_hash, branch: $branch, tags: $tags, author: $author, commit_date: $commit_date, message: $message, build_date: $build_date}}' \
> /ngen-app/nwm-ewts_git_info.json && \
# ── Install the EWTS Python package into the venv ──
# This is what makes "import ewts" work for ngen-bmi-forcing.
# For example, bmi_model.py does: import ewts; LOG = ewts.get_logger(ewts.FORCING_ID)
pip install /tmp/nwm-ewts/runtime/python/ewts && \
# ── Cleanup ──
cd / && \
rm -rf /tmp/nwm-ewts

COPY . /ngen-app/ngen-forcing/

# Install ngen-forcing as a Python package
Expand Down Expand Up @@ -201,7 +248,13 @@ RUN set -eux; \
--arg message "$(git log -1 --pretty=format:'%s' | tr '\n' ';')" \
--arg build_date "$(date -u +'%Y-%m-%d %H:%M:%S UTC')" \
"{\"ngen-bmi-forcing\": {commit_hash: \$commit_hash, branch: \$branch, tags: \$tags, author: \$author, commit_date: \$commit_date, message: \$message, build_date: \$build_date}}" \
> $GIT_INFO_PATH
> $GIT_INFO_PATH; \
# Merge EWTS git provenance if it exists
if [ -f /ngen-app/nwm-ewts_git_info.json ]; then \
jq -s 'add' $GIT_INFO_PATH /ngen-app/nwm-ewts_git_info.json > ${GIT_INFO_PATH}.tmp; \
mv ${GIT_INFO_PATH}.tmp $GIT_INFO_PATH; \
rm -f /ngen-app/nwm-ewts_git_info.json; \
fi

WORKDIR /

Expand Down
21 changes: 13 additions & 8 deletions Forcing_Extraction_Scripts/forecast_download_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,9 @@
import requests
from bs4 import BeautifulSoup

from nextgen_forcings_ewts import MODULE_NAME

LOG = logging.getLogger(MODULE_NAME)
if not LOG.handlers:
# No handlers attached — fallback to default root logger
logging.basicConfig()
LOG = logging.getLogger()

# Use the Error, Warning, and Trapping System Package for logging
import ewts
LOG = ewts.get_logger(ewts.FORCING_ID)

class ForecastDownloader(ABC):
"""
Expand Down Expand Up @@ -50,6 +45,16 @@ def __init__(self, out_dir, start_time, lookback_hours, cleanback_hours, lagback
:param cleanback_hours: How far back to clean old files
:param lagback_hours: How many hours to lag before starting to fetch
"""

global LOG
if hasattr(LOG, "bind"):
# This is required prior to the first log message for the ewts package
LOG.bind()
else:
# Fallback to default root logger
logging.basicConfig()
LOG = logging.getLogger()

if lookback_hours <= lagback_hours:
raise ValueError(
f"Invalid configuration: lookback_hours ({lookback_hours}) must be greater than "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@

if TYPE_CHECKING:
from numpy.typing import NDArray
import logging

from nextgen_forcings_ewts import MODULE_NAME

LOG = logging.getLogger(MODULE_NAME)
# Use the Error, Warning, and Trapping System Package for logging
import ewts
LOG = ewts.get_logger(ewts.FORCING_ID)

_error_on_grid_type: bool = False

Expand Down
17 changes: 7 additions & 10 deletions NextGen_Forcings_Engine_BMI/NextGen_Forcings_Engine/bmi_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,14 @@

from numpy.typing import NDArray

# Use the Error, Warning, and Trapping System Package for logging
import ewts
LOG = ewts.get_logger(ewts.FORCING_ID)

# If less than 0, then ESMF.__version__ is greater than 8.7.0
if ESMF.version_compare("8.7.0", ESMF.__version__) < 0:
manager = ESMF.api.esmpymanager.Manager(endFlag=ESMF.constants.EndAction.KEEP_MPI)

import logging

from nextgen_forcings_ewts import MODULE_NAME, configure_logging

configure_logging()


LOG = logging.getLogger(MODULE_NAME)


class UnknownBMIVariable(RuntimeError):
"""Custom exception raised when an unknown BMI variable is encountered."""

Expand Down Expand Up @@ -182,6 +176,9 @@ def initialize(self, config_file: str, output_path: str | None = None) -> None:
:param config_file: The path to the configuration file for model initialization.
:raises RuntimeError: If the configuration file is invalid or missing.
"""
# This is required prior to the first log message.
LOG.bind()

LOG.info("---------------------------")
LOG.info(f"BMI Forcing Engine initialized with {config_file}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
from NextGen_Forcings_Engine_BMI.NextGen_Forcings_Engine.core.time_handling import (
calculate_lookback_window,
)
from nextgen_forcings_ewts import MODULE_NAME


from . import mpi_utils

LOG = logging.getLogger(MODULE_NAME)
# Use the Error, Warning, and Trapping System Package for logging
import ewts
LOG = ewts.get_logger(ewts.FORCING_ID)

FORCE_COUNT = 27

Expand Down
Loading
Loading