From f0db1e0700231447905ac77801eae3fb7ab4f9ff Mon Sep 17 00:00:00 2001 From: Paul Madden <136389411+maddenp-noaa@users.noreply.github.com> Date: Thu, 30 May 2024 14:04:39 -0600 Subject: [PATCH] UW-557 DRY out MPAS drivers (#500) --- src/uwtools/drivers/mpas.py | 111 +++-------------------- src/uwtools/drivers/mpas_base.py | 146 +++++++++++++++++++++++++++++++ src/uwtools/drivers/mpas_init.py | 109 +++-------------------- 3 files changed, 167 insertions(+), 199 deletions(-) create mode 100644 src/uwtools/drivers/mpas_base.py diff --git a/src/uwtools/drivers/mpas.py b/src/uwtools/drivers/mpas.py index 796c81f70..94efdec38 100644 --- a/src/uwtools/drivers/mpas.py +++ b/src/uwtools/drivers/mpas.py @@ -1,48 +1,24 @@ """ -A driver for the MPAS component. +A driver for the MPAS Atmosphere component. """ -from datetime import datetime, timedelta +from datetime import timedelta from pathlib import Path -from typing import List, Optional -from iotaa import asset, task, tasks +from iotaa import asset, task -from uwtools.api.template import render from uwtools.config.formats.nml import NMLConfig -from uwtools.drivers.driver import Driver +from uwtools.drivers.mpas_base import MPASBase from uwtools.exceptions import UWConfigError from uwtools.strings import STR -from uwtools.utils.tasks import file, filecopy, symlink +from uwtools.utils.tasks import symlink -class MPAS(Driver): +class MPAS(MPASBase): """ - A driver for MPAS. + A driver for MPAS Atmosphere. """ - def __init__( - self, - cycle: datetime, - config: Optional[Path] = None, - dry_run: bool = False, - batch: bool = False, - key_path: Optional[List[str]] = None, - ): - """ - The driver. - - :param cycle: The cycle. - :param config: Path to config file (read stdin if missing or None). - :param dry_run: Run in dry-run mode? - :param batch: Run component via the batch system? - :param key_path: Keys leading through the config to the driver's configuration block. - """ - super().__init__( - config=config, dry_run=dry_run, batch=batch, cycle=cycle, key_path=key_path - ) - self._cycle = cycle - # Workflow tasks @task @@ -62,28 +38,6 @@ def boundary_files(self): symlinks[linkname] = Path(lbcs["path"]) / fn yield [symlink(target=t, linkname=l) for l, t in symlinks.items()] - @tasks - def files_copied(self): - """ - Files copied for run. - """ - yield self._taskname("files copied") - yield [ - filecopy(src=Path(src), dst=self._rundir / dst) - for dst, src in self._driver_config.get("files_to_copy", {}).items() - ] - - @tasks - def files_linked(self): - """ - Files linked for run. - """ - yield self._taskname("files linked") - yield [ - symlink(target=Path(target), linkname=self._rundir / linkname) - for linkname, target in self._driver_config.get("files_to_link", {}).items() - ] - @task def namelist_file(self): """ @@ -115,48 +69,6 @@ def namelist_file(self): path=path, ) - @tasks - def provisioned_run_directory(self): - """ - Run directory provisioned with all required content. - """ - yield self._taskname("provisioned run directory") - yield [ - self.boundary_files(), - self.files_copied(), - self.files_linked(), - self.namelist_file(), - self.runscript(), - self.streams_file(), - ] - - @task - def runscript(self): - """ - The runscript. - """ - path = self._runscript_path - yield self._taskname(path.name) - yield asset(path, path.is_file) - yield None - self._write_runscript(path=path, envvars={}) - - @task - def streams_file(self): - """ - The streams file. - """ - fn = "streams.atmosphere" - yield self._taskname(fn) - path = self._rundir / fn - yield asset(path, path.is_file) - yield file(path=Path(self._driver_config["streams"]["path"])) - render( - input_file=Path(self._driver_config["streams"]["path"]), - output_file=path, - values_src=self._driver_config["streams"]["values"], - ) - # Private helper methods @property @@ -166,10 +78,9 @@ def _driver_name(self) -> str: """ return STR.mpas - def _taskname(self, suffix: str) -> str: + @property + def _streams_fn(self) -> str: """ - Returns a common tag for graph-task log messages. - - :param suffix: Log-string suffix. + The streams filename. """ - return self._taskname_with_cycle(self._cycle, suffix) + return "streams.atmosphere" diff --git a/src/uwtools/drivers/mpas_base.py b/src/uwtools/drivers/mpas_base.py new file mode 100644 index 000000000..898f88d23 --- /dev/null +++ b/src/uwtools/drivers/mpas_base.py @@ -0,0 +1,146 @@ +""" +A base class for MPAS drivers. +""" + +from abc import abstractmethod +from datetime import datetime +from pathlib import Path +from typing import List, Optional + +from iotaa import asset, task, tasks + +from uwtools.api.template import render +from uwtools.drivers.driver import Driver +from uwtools.utils.tasks import file, filecopy, symlink + + +class MPASBase(Driver): + """ + A base class for MPAS drivers. + """ + + def __init__( + self, + cycle: datetime, + config: Optional[Path] = None, + dry_run: bool = False, + batch: bool = False, + key_path: Optional[List[str]] = None, + ): + """ + The driver. + + :param config_file: Path to config file (read stdin if missing or None). + :param cycle: The cycle. + :param dry_run: Run in dry-run mode? + :param batch: Run component via the batch system? + :param key_path: Keys leading through the config to the driver's configuration block. + """ + super().__init__( + config=config, cycle=cycle, dry_run=dry_run, batch=batch, key_path=key_path + ) + self._cycle = cycle + + # Workflow tasks + + @tasks + @abstractmethod + def boundary_files(self): + """ + Boundary files. + """ + + @tasks + def files_copied(self): + """ + Files copied for run. + """ + yield self._taskname("files copied") + yield [ + filecopy(src=Path(src), dst=self._rundir / dst) + for dst, src in self._driver_config.get("files_to_copy", {}).items() + ] + + @tasks + def files_linked(self): + """ + Files linked for run. + """ + yield self._taskname("files linked") + yield [ + symlink(target=Path(target), linkname=self._rundir / linkname) + for linkname, target in self._driver_config.get("files_to_link", {}).items() + ] + + @task + @abstractmethod + def namelist_file(self): + """ + The namelist file. + """ + + @tasks + def provisioned_run_directory(self): + """ + Run directory provisioned with all required content. + """ + yield self._taskname("provisioned run directory") + yield [ + self.boundary_files(), + self.files_copied(), + self.files_linked(), + self.namelist_file(), + self.runscript(), + self.streams_file(), + ] + + @task + def runscript(self): + """ + The runscript. + """ + path = self._runscript_path + yield self._taskname(path.name) + yield asset(path, path.is_file) + yield None + self._write_runscript(path=path, envvars={}) + + @task + def streams_file(self): + """ + The streams file. + """ + fn = self._streams_fn + yield self._taskname(fn) + path = self._rundir / fn + yield asset(path, path.is_file) + yield file(path=Path(self._driver_config["streams"]["path"])) + render( + input_file=Path(self._driver_config["streams"]["path"]), + output_file=path, + values_src=self._driver_config["streams"]["values"], + ) + + # Private helper methods + + @property + @abstractmethod + def _driver_name(self) -> str: + """ + Returns the name of this driver. + """ + + @property + @abstractmethod + def _streams_fn(self) -> str: + """ + The streams filename. + """ + + def _taskname(self, suffix: str) -> str: + """ + Returns a common tag for graph-task log messages. + + :param suffix: Log-string suffix. + """ + return self._taskname_with_cycle(self._cycle, suffix) diff --git a/src/uwtools/drivers/mpas_init.py b/src/uwtools/drivers/mpas_init.py index 9371eadf1..7ec487eeb 100644 --- a/src/uwtools/drivers/mpas_init.py +++ b/src/uwtools/drivers/mpas_init.py @@ -1,48 +1,24 @@ """ -A driver for the mpas-init component. +A driver for the MPAS Init component. """ -from datetime import datetime, timedelta +from datetime import timedelta from pathlib import Path -from typing import List, Optional from iotaa import asset, task, tasks -from uwtools.api.template import render from uwtools.config.formats.nml import NMLConfig -from uwtools.drivers.driver import Driver +from uwtools.drivers.mpas_base import MPASBase from uwtools.exceptions import UWConfigError from uwtools.strings import STR -from uwtools.utils.tasks import file, filecopy, symlink +from uwtools.utils.tasks import symlink -class MPASInit(Driver): +class MPASInit(MPASBase): """ - A driver for mpas-init. + A driver for MPAS Init. """ - def __init__( - self, - cycle: datetime, - config: Optional[Path] = None, - dry_run: bool = False, - batch: bool = False, - key_path: Optional[List[str]] = None, - ): - """ - The driver. - - :param config_file: Path to config file (read stdin if missing or None). - :param cycle: The cycle. - :param dry_run: Run in dry-run mode? - :param batch: Run component via the batch system? - :param key_path: Keys leading through the config to the driver's configuration block. - """ - super().__init__( - config=config, cycle=cycle, dry_run=dry_run, batch=batch, key_path=key_path - ) - self._cycle = cycle - # Workflow tasks @tasks @@ -64,28 +40,6 @@ def boundary_files(self): symlinks[target] = linkname yield [symlink(target=t, linkname=l) for t, l in symlinks.items()] - @tasks - def files_copied(self): - """ - Files copied for run. - """ - yield self._taskname("files copied") - yield [ - filecopy(src=Path(src), dst=self._rundir / dst) - for dst, src in self._driver_config.get("files_to_copy", {}).items() - ] - - @tasks - def files_linked(self): - """ - Files linked for run. - """ - yield self._taskname("files linked") - yield [ - symlink(target=Path(target), linkname=self._rundir / linkname) - for linkname, target in self._driver_config.get("files_to_link", {}).items() - ] - @task def namelist_file(self): """ @@ -119,48 +73,6 @@ def namelist_file(self): path=path, ) - @tasks - def provisioned_run_directory(self): - """ - Run directory provisioned with all required content. - """ - yield self._taskname("provisioned run directory") - yield [ - self.boundary_files(), - self.files_copied(), - self.files_linked(), - self.namelist_file(), - self.runscript(), - self.streams_file(), - ] - - @task - def runscript(self): - """ - The runscript. - """ - path = self._runscript_path - yield self._taskname(path.name) - yield asset(path, path.is_file) - yield None - self._write_runscript(path=path, envvars={}) - - @task - def streams_file(self): - """ - The streams file. - """ - fn = "streams.init_atmosphere" - yield self._taskname(fn) - path = self._rundir / fn - yield asset(path, path.is_file) - yield file(path=Path(self._driver_config["streams"]["path"])) - render( - input_file=Path(self._driver_config["streams"]["path"]), - output_file=path, - values_src=self._driver_config["streams"]["values"], - ) - # Private helper methods @property @@ -170,10 +82,9 @@ def _driver_name(self) -> str: """ return STR.mpasinit - def _taskname(self, suffix: str) -> str: + @property + def _streams_fn(self) -> str: """ - Returns a common tag for graph-task log messages. - - :param suffix: Log-string suffix. + The streams filename. """ - return self._taskname_with_cycle(self._cycle, suffix) + return "streams.init_atmosphere"