From 05b99573ae1a2a0ae6782344ce6ed476ebc56a2a Mon Sep 17 00:00:00 2001 From: Paul Madden <136389411+maddenp-noaa@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:25:13 -0600 Subject: [PATCH] omnibus-2024-06-24 (#517) --- src/uwtools/api/config.py | 4 +-- src/uwtools/api/file.py | 6 ++-- src/uwtools/api/template.py | 10 +++--- src/uwtools/cli.py | 28 ++++++++-------- src/uwtools/config/formats/base.py | 10 +++--- src/uwtools/config/jinja2.py | 20 ++++++------ src/uwtools/config/support.py | 6 ++-- src/uwtools/config/tools.py | 12 +++---- src/uwtools/config/validator.py | 4 +-- src/uwtools/drivers/chgres_cube.py | 4 +-- src/uwtools/drivers/driver.py | 20 ++++++------ src/uwtools/drivers/esg_grid.py | 4 +-- src/uwtools/drivers/filter_topo.py | 4 +-- src/uwtools/drivers/fv3.py | 4 +-- src/uwtools/drivers/global_equiv_resol.py | 4 +-- src/uwtools/drivers/jedi_base.py | 4 +-- src/uwtools/drivers/make_hgrid.py | 4 +-- src/uwtools/drivers/make_solo_mosaic.py | 4 +-- src/uwtools/drivers/mpas_base.py | 4 +-- src/uwtools/drivers/orog_gsl.py | 4 +-- src/uwtools/drivers/schism.py | 4 +-- src/uwtools/drivers/sfc_climo_gen.py | 4 +-- src/uwtools/drivers/shave.py | 4 +-- src/uwtools/drivers/support.py | 4 +-- src/uwtools/drivers/ungrib.py | 4 +-- src/uwtools/drivers/upp.py | 4 +-- src/uwtools/drivers/ww3.py | 4 +-- src/uwtools/file.py | 4 +-- src/uwtools/rocoto.py | 6 ++-- src/uwtools/scheduler.py | 24 +++++++------- src/uwtools/strings.py | 5 ++- src/uwtools/tests/api/test_config.py | 6 ++-- src/uwtools/tests/api/test_drivers.py | 8 ++--- src/uwtools/tests/config/formats/test_base.py | 7 ++-- src/uwtools/tests/config/formats/test_sh.py | 4 +-- src/uwtools/tests/config/test_jinja2.py | 15 ++++----- src/uwtools/tests/config/test_support.py | 9 ++---- src/uwtools/tests/config/test_tools.py | 7 ++-- src/uwtools/tests/config/test_validator.py | 10 +++--- src/uwtools/tests/drivers/test_chgres_cube.py | 13 +++++--- src/uwtools/tests/drivers/test_driver.py | 22 ++++++------- src/uwtools/tests/drivers/test_esg_grid.py | 13 +++++--- src/uwtools/tests/drivers/test_filter_topo.py | 13 +++++--- src/uwtools/tests/drivers/test_fv3.py | 20 ++++++------ .../tests/drivers/test_global_equiv_resol.py | 13 +++++--- src/uwtools/tests/drivers/test_ioda.py | 13 +++++--- src/uwtools/tests/drivers/test_jedi.py | 13 +++++--- src/uwtools/tests/drivers/test_make_hgrid.py | 13 +++++--- .../tests/drivers/test_make_solo_mosaic.py | 13 +++++--- src/uwtools/tests/drivers/test_mpas.py | 21 ++++++------ src/uwtools/tests/drivers/test_mpas_init.py | 16 ++++++---- src/uwtools/tests/drivers/test_orog_gsl.py | 13 +++++--- src/uwtools/tests/drivers/test_schism.py | 13 +++++--- .../tests/drivers/test_sfc_climo_gen.py | 13 +++++--- src/uwtools/tests/drivers/test_shave.py | 13 +++++--- src/uwtools/tests/drivers/test_ungrib.py | 13 +++++--- src/uwtools/tests/drivers/test_upp.py | 13 +++++--- src/uwtools/tests/drivers/test_ww3.py | 13 +++++--- src/uwtools/tests/test_cli.py | 32 +++++++++---------- src/uwtools/tests/test_file.py | 13 ++++---- src/uwtools/tests/test_rocoto.py | 16 ++++------ src/uwtools/tests/test_scheduler.py | 4 +-- src/uwtools/tests/test_schemas.py | 7 ++-- src/uwtools/tests/utils/test_api.py | 9 +++--- src/uwtools/tests/utils/test_file.py | 13 +++++--- src/uwtools/utils/api.py | 14 ++++---- src/uwtools/utils/processing.py | 6 ++-- 67 files changed, 359 insertions(+), 322 deletions(-) diff --git a/src/uwtools/api/config.py b/src/uwtools/api/config.py index 408dbd35e..b8e91faed 100644 --- a/src/uwtools/api/config.py +++ b/src/uwtools/api/config.py @@ -4,7 +4,7 @@ import os from pathlib import Path -from typing import List, Optional, Union +from typing import Optional, Union from uwtools.config.formats.fieldtable import FieldTableConfig as _FieldTableConfig from uwtools.config.formats.ini import INIConfig as _INIConfig @@ -119,7 +119,7 @@ def realize( update_format: Optional[str] = None, output_file: Optional[Union[Path, str]] = None, output_format: Optional[str] = None, - key_path: Optional[List[Union[str, int]]] = None, + key_path: Optional[list[Union[str, int]]] = None, values_needed: bool = False, total: bool = False, dry_run: bool = False, diff --git a/src/uwtools/api/file.py b/src/uwtools/api/file.py index 973b48942..48a8065f5 100644 --- a/src/uwtools/api/file.py +++ b/src/uwtools/api/file.py @@ -4,7 +4,7 @@ import datetime as dt from pathlib import Path -from typing import List, Optional, Union +from typing import Optional, Union from uwtools.file import FileCopier as _FileCopier from uwtools.file import FileLinker as _FileLinker @@ -16,7 +16,7 @@ def copy( config: Optional[Union[dict, Path, str]] = None, cycle: Optional[dt.datetime] = None, leadtime: Optional[dt.timedelta] = None, - keys: Optional[List[str]] = None, + keys: Optional[list[str]] = None, dry_run: bool = False, stdin_ok: bool = False, ) -> bool: @@ -48,7 +48,7 @@ def link( config: Optional[Union[dict, Path, str]] = None, cycle: Optional[dt.datetime] = None, leadtime: Optional[dt.timedelta] = None, - keys: Optional[List[str]] = None, + keys: Optional[list[str]] = None, dry_run: bool = False, stdin_ok: bool = False, ) -> bool: diff --git a/src/uwtools/api/template.py b/src/uwtools/api/template.py index 2b1f808c0..7f589ce08 100644 --- a/src/uwtools/api/template.py +++ b/src/uwtools/api/template.py @@ -4,7 +4,7 @@ import os from pathlib import Path -from typing import Dict, List, Optional, Union +from typing import Optional, Union from uwtools.config.atparse_to_jinja2 import convert as _convert_atparse_to_jinja2 from uwtools.config.jinja2 import render as _render @@ -18,9 +18,9 @@ def render( values_format: Optional[str] = None, input_file: Optional[Union[Path, str]] = None, output_file: Optional[Union[Path, str]] = None, - overrides: Optional[Dict[str, str]] = None, + overrides: Optional[dict[str, str]] = None, env: bool = False, - searchpath: Optional[List[str]] = None, + searchpath: Optional[list[str]] = None, values_needed: bool = False, dry_run: bool = False, stdin_ok: bool = False, @@ -69,9 +69,9 @@ def render_to_str( # pylint: disable=unused-argument values_src: Optional[Union[dict, Path, str]] = None, values_format: Optional[str] = None, input_file: Optional[Union[Path, str]] = None, - overrides: Optional[Dict[str, str]] = None, + overrides: Optional[dict[str, str]] = None, env: bool = False, - searchpath: Optional[List[str]] = None, + searchpath: Optional[list[str]] = None, values_needed: bool = False, dry_run: bool = False, ) -> str: diff --git a/src/uwtools/cli.py b/src/uwtools/cli.py index 360a07888..f30f54ccc 100644 --- a/src/uwtools/cli.py +++ b/src/uwtools/cli.py @@ -13,7 +13,7 @@ from functools import partial from importlib import import_module from pathlib import Path -from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple +from typing import Any, Callable, NoReturn, Optional import uwtools.api import uwtools.api.config @@ -31,10 +31,10 @@ LEADTIME_DESC = "hours[:minutes[:seconds]]" TITLE_REQ_ARG = "Required arguments" -Args = Dict[str, Any] -ActionChecks = List[Callable[[Args], Args]] -ModeChecks = Dict[str, ActionChecks] -Checks = Dict[str, ModeChecks] +Args = dict[str, Any] +ActionChecks = list[Callable[[Args], Args]] +ModeChecks = dict[str, ActionChecks] +Checks = dict[str, ModeChecks] def main() -> None: @@ -56,13 +56,13 @@ def main() -> None: _abort(str(e)) try: log.debug("Command: %s %s", Path(sys.argv[0]).name, " ".join(sys.argv[1:])) - tools: Dict[str, Callable[..., bool]] = { + tools: dict[str, Callable[..., bool]] = { STR.config: _dispatch_config, STR.file: _dispatch_file, STR.rocoto: _dispatch_rocoto, STR.template: _dispatch_template, } - drivers: Dict[str, Callable[..., bool]] = { + drivers: dict[str, Callable[..., bool]] = { x: partial(_dispatch_to_driver, x) for x in [ STR.chgrescube, @@ -591,7 +591,7 @@ def _add_arg_env(group: Group) -> None: def _add_arg_file_format( - group: Group, switch: str, helpmsg: str, choices: List[str], required: bool = False + group: Group, switch: str, helpmsg: str, choices: list[str], required: bool = False ) -> None: group.add_argument( switch, @@ -632,7 +632,7 @@ def _add_arg_input_file(group: Group, required: bool = False) -> None: ) -def _add_arg_input_format(group: Group, choices: List[str], required: bool = False) -> None: +def _add_arg_input_format(group: Group, choices: list[str], required: bool = False) -> None: group.add_argument( _switch(STR.infmt), choices=choices, @@ -690,7 +690,7 @@ def _add_arg_output_file(group: Group, required: bool = False) -> None: ) -def _add_arg_output_format(group: Group, choices: List[str], required: bool = False) -> None: +def _add_arg_output_format(group: Group, choices: list[str], required: bool = False) -> None: group.add_argument( _switch(STR.outfmt), choices=choices, @@ -758,7 +758,7 @@ def _add_arg_update_file(group: Group, required: bool = False) -> None: ) -def _add_arg_update_format(group: Group, choices: List[str], required: bool = False) -> None: +def _add_arg_update_format(group: Group, choices: list[str], required: bool = False) -> None: group.add_argument( _switch(STR.updatefmt), choices=choices, @@ -778,7 +778,7 @@ def _add_arg_values_file(group: Group, required: bool = False) -> None: ) -def _add_arg_values_format(group: Group, choices: List[str]) -> None: +def _add_arg_values_format(group: Group, choices: list[str]) -> None: group.add_argument( _switch(STR.valsfmt), choices=choices, @@ -970,7 +970,7 @@ def _check_verbosity(args: Args) -> Args: return args -def _dict_from_key_eq_val_strings(config_items: List[str]) -> Dict[str, str]: +def _dict_from_key_eq_val_strings(config_items: list[str]) -> dict[str, str]: """ Given a list of key=value strings, return a dictionary of key/value pairs. @@ -1012,7 +1012,7 @@ def _formatter(prog: str) -> HelpFormatter: return HelpFormatter(prog, max_help_position=6) -def _parse_args(raw_args: List[str]) -> Tuple[Args, Checks]: +def _parse_args(raw_args: list[str]) -> tuple[Args, Checks]: """ Parse command-line arguments. diff --git a/src/uwtools/config/formats/base.py b/src/uwtools/config/formats/base.py index 9982285f7..3ca86d266 100644 --- a/src/uwtools/config/formats/base.py +++ b/src/uwtools/config/formats/base.py @@ -7,7 +7,7 @@ from copy import deepcopy from io import StringIO from pathlib import Path -from typing import List, Optional, Tuple, Union +from typing import Optional, Union import yaml @@ -63,7 +63,7 @@ def _load(self, config_file: Optional[Path]) -> dict: :param config_file: Path to config file to load. """ - def _load_paths(self, config_files: List[Path]) -> dict: + def _load_paths(self, config_files: list[Path]) -> dict: """ Merge and return the contents of a collection of config files. @@ -83,7 +83,7 @@ def _load_paths(self, config_files: List[Path]) -> dict: # Public methods - def characterize_values(self, values: dict, parent: str) -> Tuple[list, list]: + def characterize_values(self, values: dict, parent: str) -> tuple[list, list]: """ Characterize values as complete or as template placeholders. @@ -91,8 +91,8 @@ def characterize_values(self, values: dict, parent: str) -> Tuple[list, list]: :param parent: Parent key. :return: Lists of of complete and template-placeholder values. """ - complete: List[str] = [] - template: List[str] = [] + complete: list[str] = [] + template: list[str] = [] for key, val in values.items(): if isinstance(val, dict): complete.append(f"{INDENT}{parent}{key}") diff --git a/src/uwtools/config/jinja2.py b/src/uwtools/config/jinja2.py index 3d6181e07..a0367a19d 100644 --- a/src/uwtools/config/jinja2.py +++ b/src/uwtools/config/jinja2.py @@ -5,7 +5,7 @@ import os from functools import cached_property from pathlib import Path -from typing import Dict, List, Optional, Set, Union +from typing import Optional, Union from jinja2 import Environment, FileSystemLoader, StrictUndefined, Undefined, meta from jinja2.exceptions import UndefinedError @@ -26,7 +26,7 @@ def __init__( self, values: dict, template_source: Optional[Union[str, Path]] = None, - searchpath: Optional[List[str]] = None, + searchpath: Optional[list[str]] = None, ) -> None: """ :param values: Values needed to render the provided template. @@ -88,7 +88,7 @@ def render(self) -> str: return self._template.render(self._values) @property - def undeclared_variables(self) -> Set[str]: + def undeclared_variables(self) -> set[str]: """ Returns the names of variables needed to render the template. @@ -102,7 +102,7 @@ def undeclared_variables(self) -> Set[str]: def dereference( - val: _ConfigVal, context: dict, local: Optional[dict] = None, keys: Optional[List[str]] = None + val: _ConfigVal, context: dict, local: Optional[dict] = None, keys: Optional[list[str]] = None ) -> _ConfigVal: """ Render Jinja2 syntax, wherever possible. @@ -152,9 +152,9 @@ def render( values_format: Optional[str] = None, input_file: Optional[Path] = None, output_file: Optional[Path] = None, - overrides: Optional[Dict[str, str]] = None, + overrides: Optional[dict[str, str]] = None, env: bool = False, - searchpath: Optional[List[str]] = None, + searchpath: Optional[list[str]] = None, values_needed: bool = False, dry_run: bool = False, ) -> Optional[str]: @@ -286,7 +286,7 @@ def _dry_run_template(rendered_template: str) -> str: return rendered_template -def _log_missing_values(missing: List[str]) -> None: +def _log_missing_values(missing: list[str]) -> None: """ Log values missing from template and raise an exception. @@ -305,7 +305,7 @@ def _register_filters(env: Environment) -> Environment: :return: The input Environment, with filters added. """ - def path_join(path_components: List[str]) -> str: + def path_join(path_components: list[str]) -> str: if any(isinstance(x, Undefined) for x in path_components): raise UndefinedError() return os.path.join(*path_components) @@ -332,7 +332,7 @@ def _report(args: dict) -> None: def _supplement_values( values_src: Optional[Union[dict, Path]] = None, values_format: Optional[str] = None, - overrides: Optional[Dict[str, str]] = None, + overrides: Optional[dict[str, str]] = None, env: bool = False, ) -> dict: """ @@ -361,7 +361,7 @@ def _supplement_values( return values -def _values_needed(undeclared_variables: Set[str]) -> None: +def _values_needed(undeclared_variables: set[str]) -> None: """ Log variables needed to render the template. diff --git a/src/uwtools/config/support.py b/src/uwtools/config/support.py index 1a6416ff9..f09f19de6 100644 --- a/src/uwtools/config/support.py +++ b/src/uwtools/config/support.py @@ -2,7 +2,7 @@ from collections import OrderedDict from importlib import import_module -from typing import Dict, Type, Union +from typing import Type, Union import yaml @@ -46,7 +46,7 @@ def format_to_config(fmt: str) -> Type: return cfgclass -def from_od(d: Union[OrderedDict, Dict]) -> dict: +def from_od(d: Union[OrderedDict, dict]) -> dict: """ Returns a (nested) dict with content equivalent to the given (nested) OrderedDict. @@ -105,7 +105,7 @@ def convert(self) -> Union[float, int]: Will raise an exception if the value cannot be represented as the specified type. """ - converters: Dict[str, Union[Type[float], Type[int]]] = dict(zip(self.TAGS, [float, int])) + converters: dict[str, Union[Type[float], Type[int]]] = dict(zip(self.TAGS, [float, int])) return converters[self.tag](self.value) diff --git a/src/uwtools/config/tools.py b/src/uwtools/config/tools.py index 942d1ba19..ab86472a7 100644 --- a/src/uwtools/config/tools.py +++ b/src/uwtools/config/tools.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import Callable, List, Optional, Tuple, Union +from typing import Callable, Optional, Union from uwtools.config.formats.base import Config from uwtools.config.jinja2 import unrendered @@ -82,7 +82,7 @@ def realize_config( update_format: Optional[str] = None, output_file: Optional[Path] = None, output_format: Optional[str] = None, - key_path: Optional[List[Union[str, int]]] = None, + key_path: Optional[list[Union[str, int]]] = None, values_needed: bool = False, total: bool = False, dry_run: bool = False, @@ -135,7 +135,7 @@ def _ensure_format( return fmt -def _print_config_section(config: dict, key_path: List[str]) -> None: +def _print_config_section(config: dict, key_path: list[str]) -> None: """ Descends into the config via the given keys, then prints the contents of the located subtree as key=value pairs, one per line. @@ -162,7 +162,7 @@ def _print_config_section(config: dict, key_path: List[str]) -> None: def _realize_config_input_setup( input_config: Union[Config, Optional[Path]] = None, input_format: Optional[str] = None -) -> Tuple[Config, str]: +) -> tuple[Config, str]: """ Set up config-realize input. @@ -185,8 +185,8 @@ def _realize_config_output_setup( input_obj: Config, output_file: Optional[Path] = None, output_format: Optional[str] = None, - key_path: Optional[List[Union[str, int]]] = None, -) -> Tuple[dict, str]: + key_path: Optional[list[Union[str, int]]] = None, +) -> tuple[dict, str]: """ Set up config-realize output. diff --git a/src/uwtools/config/validator.py b/src/uwtools/config/validator.py index 69e3b7d3a..39b85405f 100644 --- a/src/uwtools/config/validator.py +++ b/src/uwtools/config/validator.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from typing import List, Optional, Union +from typing import Optional, Union from jsonschema import Draft202012Validator from jsonschema.exceptions import ValidationError @@ -95,7 +95,7 @@ def _prep_config(config: Union[dict, YAMLConfig, Optional[Path]]) -> YAMLConfig: return cfgobj -def _validation_errors(config: Union[dict, list], schema: dict) -> List[ValidationError]: +def _validation_errors(config: Union[dict, list], schema: dict) -> list[ValidationError]: """ Identify schema-validation errors. diff --git a/src/uwtools/drivers/chgres_cube.py b/src/uwtools/drivers/chgres_cube.py index 2a5f2c2cf..a4be10b11 100644 --- a/src/uwtools/drivers/chgres_cube.py +++ b/src/uwtools/drivers/chgres_cube.py @@ -4,7 +4,7 @@ from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -25,7 +25,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/driver.py b/src/uwtools/drivers/driver.py index 7e74e8991..c3a2f1c3a 100644 --- a/src/uwtools/drivers/driver.py +++ b/src/uwtools/drivers/driver.py @@ -11,7 +11,7 @@ from functools import partial from pathlib import Path from textwrap import dedent -from typing import Any, Dict, List, Optional, Type, Union +from typing import Any, Optional, Type, Union from iotaa import asset, dryrun, external, task, tasks @@ -36,7 +36,7 @@ def __init__( batch: bool = False, cycle: Optional[datetime] = None, leadtime: Optional[timedelta] = None, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ) -> None: """ A component driver. @@ -115,13 +115,13 @@ def _create_user_updated_config( log.debug(f"Failed to validate {path}") @property - def _driver_config(self) -> Dict[str, Any]: + def _driver_config(self) -> dict[str, Any]: """ Returns the config block specific to this driver. """ name = self._driver_name try: - driver_config: Dict[str, Any] = self._config[name] + driver_config: dict[str, Any] = self._config[name] return driver_config except KeyError as e: raise UWConfigError("Required '%s' block missing in config" % name) from e @@ -134,7 +134,7 @@ def _driver_name(self) -> str: """ def _namelist_schema( - self, config_keys: Optional[List[str]] = None, schema_keys: Optional[List[str]] = None + self, config_keys: Optional[list[str]] = None, schema_keys: Optional[list[str]] = None ) -> dict: """ Returns the (sub)schema for validating the driver's namelist content. @@ -258,7 +258,7 @@ def _run_via_local_execution(self): # Private helper methods @property - def _resources(self) -> Dict[str, Any]: + def _resources(self) -> dict[str, Any]: """ Returns platform configuration data. """ @@ -290,9 +290,9 @@ def _runcmd(self) -> str: def _runscript( self, - execution: List[str], - envcmds: Optional[List[str]] = None, - envvars: Optional[Dict[str, str]] = None, + execution: list[str], + envcmds: Optional[list[str]] = None, + envvars: Optional[dict[str, str]] = None, scheduler: Optional[JobScheduler] = None, ) -> str: """ @@ -352,7 +352,7 @@ def _validate(self) -> None: for schema_name in (self._driver_name.replace("_", "-"), "platform"): validate_internal(schema_name=schema_name, config=self._config) - def _write_runscript(self, path: Path, envvars: Optional[Dict[str, str]] = None) -> None: + def _write_runscript(self, path: Path, envvars: Optional[dict[str, str]] = None) -> None: """ Write the runscript. """ diff --git a/src/uwtools/drivers/esg_grid.py b/src/uwtools/drivers/esg_grid.py index 5f36f2f59..8d97fb76d 100644 --- a/src/uwtools/drivers/esg_grid.py +++ b/src/uwtools/drivers/esg_grid.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -23,7 +23,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/filter_topo.py b/src/uwtools/drivers/filter_topo.py index 71dd5393f..62d1f56b5 100644 --- a/src/uwtools/drivers/filter_topo.py +++ b/src/uwtools/drivers/filter_topo.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -23,7 +23,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/fv3.py b/src/uwtools/drivers/fv3.py index ecd086ab4..58c58f8cd 100644 --- a/src/uwtools/drivers/fv3.py +++ b/src/uwtools/drivers/fv3.py @@ -5,7 +5,7 @@ from datetime import datetime from pathlib import Path from shutil import copy -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -28,7 +28,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/global_equiv_resol.py b/src/uwtools/drivers/global_equiv_resol.py index aaa3506a4..9b266cc4f 100644 --- a/src/uwtools/drivers/global_equiv_resol.py +++ b/src/uwtools/drivers/global_equiv_resol.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, external, tasks @@ -21,7 +21,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/jedi_base.py b/src/uwtools/drivers/jedi_base.py index 9418ec482..e8127bf88 100644 --- a/src/uwtools/drivers/jedi_base.py +++ b/src/uwtools/drivers/jedi_base.py @@ -5,7 +5,7 @@ from abc import abstractmethod from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -25,7 +25,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/make_hgrid.py b/src/uwtools/drivers/make_hgrid.py index c68a8b282..33e9a8061 100644 --- a/src/uwtools/drivers/make_hgrid.py +++ b/src/uwtools/drivers/make_hgrid.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import tasks @@ -21,7 +21,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/make_solo_mosaic.py b/src/uwtools/drivers/make_solo_mosaic.py index 753dc92a1..5f333cdcc 100644 --- a/src/uwtools/drivers/make_solo_mosaic.py +++ b/src/uwtools/drivers/make_solo_mosaic.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import tasks @@ -21,7 +21,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/mpas_base.py b/src/uwtools/drivers/mpas_base.py index a72be4dd7..b380daa70 100644 --- a/src/uwtools/drivers/mpas_base.py +++ b/src/uwtools/drivers/mpas_base.py @@ -5,7 +5,7 @@ from abc import abstractmethod from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks from lxml import etree @@ -26,7 +26,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/orog_gsl.py b/src/uwtools/drivers/orog_gsl.py index a95e82413..fe2f4975a 100644 --- a/src/uwtools/drivers/orog_gsl.py +++ b/src/uwtools/drivers/orog_gsl.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -22,7 +22,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/schism.py b/src/uwtools/drivers/schism.py index 899eb6f40..d33a6d070 100644 --- a/src/uwtools/drivers/schism.py +++ b/src/uwtools/drivers/schism.py @@ -4,7 +4,7 @@ from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -25,7 +25,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/sfc_climo_gen.py b/src/uwtools/drivers/sfc_climo_gen.py index f83676321..acd4ea8fc 100644 --- a/src/uwtools/drivers/sfc_climo_gen.py +++ b/src/uwtools/drivers/sfc_climo_gen.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -23,7 +23,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/shave.py b/src/uwtools/drivers/shave.py index e5f023887..8ea738f9c 100644 --- a/src/uwtools/drivers/shave.py +++ b/src/uwtools/drivers/shave.py @@ -3,7 +3,7 @@ """ from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import tasks @@ -21,7 +21,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/support.py b/src/uwtools/drivers/support.py index dc7d4f9d7..141e076ca 100644 --- a/src/uwtools/drivers/support.py +++ b/src/uwtools/drivers/support.py @@ -2,8 +2,6 @@ Driver support. """ -from typing import Dict - import iotaa as _iotaa from uwtools.drivers.driver import Driver @@ -16,7 +14,7 @@ def graph() -> str: return _iotaa.graph() -def tasks(driver_class: type[Driver]) -> Dict[str, str]: +def tasks(driver_class: type[Driver]) -> dict[str, str]: """ Returns a mapping from task names to their one-line descriptions. diff --git a/src/uwtools/drivers/ungrib.py b/src/uwtools/drivers/ungrib.py index c1186a83e..25a928b0b 100644 --- a/src/uwtools/drivers/ungrib.py +++ b/src/uwtools/drivers/ungrib.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -25,7 +25,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/upp.py b/src/uwtools/drivers/upp.py index 3ba6b2c6c..7487d774c 100644 --- a/src/uwtools/drivers/upp.py +++ b/src/uwtools/drivers/upp.py @@ -4,7 +4,7 @@ from datetime import datetime, timedelta from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -26,7 +26,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/drivers/ww3.py b/src/uwtools/drivers/ww3.py index edd3ecdf2..c1c73ce2d 100644 --- a/src/uwtools/drivers/ww3.py +++ b/src/uwtools/drivers/ww3.py @@ -4,7 +4,7 @@ from datetime import datetime from pathlib import Path -from typing import List, Optional +from typing import Optional from iotaa import asset, task, tasks @@ -25,7 +25,7 @@ def __init__( config: Optional[Path] = None, dry_run: bool = False, batch: bool = False, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, ): """ The driver. diff --git a/src/uwtools/file.py b/src/uwtools/file.py index b3c307405..f5c02b38e 100644 --- a/src/uwtools/file.py +++ b/src/uwtools/file.py @@ -5,7 +5,7 @@ import datetime as dt from functools import cached_property from pathlib import Path -from typing import List, Optional, Union +from typing import Optional, Union from iotaa import dryrun, tasks @@ -27,7 +27,7 @@ def __init__( config: Optional[Union[dict, Path]] = None, cycle: Optional[dt.datetime] = None, leadtime: Optional[dt.timedelta] = None, - keys: Optional[List[str]] = None, + keys: Optional[list[str]] = None, dry_run: bool = False, ) -> None: """ diff --git a/src/uwtools/rocoto.py b/src/uwtools/rocoto.py index e68df989e..8b84dddf6 100644 --- a/src/uwtools/rocoto.py +++ b/src/uwtools/rocoto.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from math import log10 from pathlib import Path -from typing import Any, List, Optional, Tuple, Union +from typing import Any, Optional, Union from lxml import etree from lxml.etree import Element, SubElement, _Element @@ -307,7 +307,7 @@ def _add_workflow(self, config: dict) -> None: self._add_workflow_tasks(e, config[STR.tasks]) self._root: _Element = e - def _add_workflow_cycledef(self, e: _Element, config: List[dict]) -> None: + def _add_workflow_cycledef(self, e: _Element, config: list[dict]) -> None: """ Add element(s) to the . @@ -399,7 +399,7 @@ def _set_attrs(self, e: _Element, config: dict) -> None: for attr, val in config.get(STR.attrs, {}).items(): e.set(attr, str(val)) - def _tag_name(self, key: str) -> Tuple[str, str]: + def _tag_name(self, key: str) -> tuple[str, str]: """ Return the tag and metadata extracted from a metadata-bearing key. diff --git a/src/uwtools/scheduler.py b/src/uwtools/scheduler.py index aac1ed376..39963d582 100644 --- a/src/uwtools/scheduler.py +++ b/src/uwtools/scheduler.py @@ -9,7 +9,7 @@ from copy import deepcopy from dataclasses import dataclass, fields from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Optional from uwtools.exceptions import UWConfigError from uwtools.logging import log @@ -21,14 +21,14 @@ class JobScheduler(ABC): An abstract class for interacting with HPC schedulers. """ - def __init__(self, props: Dict[str, Any]): + def __init__(self, props: dict[str, Any]): self._props = {k: v for k, v in props.items() if k != "scheduler"} self._validate_props() # Public methods @property - def directives(self) -> List[str]: + def directives(self) -> list[str]: """ Returns resource-request scheduler directives. """ @@ -90,7 +90,7 @@ def _directive_separator(self) -> str: @property @abstractmethod - def _managed_directives(self) -> Dict[str, Any]: + def _managed_directives(self) -> dict[str, Any]: """ Returns a mapping from canonical names to scheduler-specific CLI switches. """ @@ -103,7 +103,7 @@ def _prefix(self) -> str: """ @property - def _processed_props(self) -> Dict[str, Any]: + def _processed_props(self) -> dict[str, Any]: """ Pre-process directives before converting to runscript. """ @@ -143,7 +143,7 @@ def _directive_separator(self) -> str: return " " @property - def _managed_directives(self) -> Dict[str, Any]: + def _managed_directives(self) -> dict[str, Any]: """ Returns a mapping from canonical names to scheduler-specific CLI switches. """ @@ -168,7 +168,7 @@ def _prefix(self) -> str: return "#BSUB" @property - def _processed_props(self) -> Dict[str, Any]: + def _processed_props(self) -> dict[str, Any]: props = deepcopy(self._props) props[_DirectivesOptional.THREADS] = props.get(_DirectivesOptional.THREADS, 1) return props @@ -194,7 +194,7 @@ def _directive_separator(self) -> str: return " " @property - def _managed_directives(self) -> Dict[str, Any]: + def _managed_directives(self) -> dict[str, Any]: """ Returns a mapping from canonical names to scheduler-specific CLI switches. """ @@ -213,7 +213,7 @@ def _managed_directives(self) -> Dict[str, Any]: } @staticmethod - def _placement(items: Dict[str, Any]) -> Dict[str, Any]: + def _placement(items: dict[str, Any]) -> dict[str, Any]: """ Placement logic. """ @@ -238,7 +238,7 @@ def _prefix(self) -> str: return "#PBS" @property - def _processed_props(self) -> Dict[str, Any]: + def _processed_props(self) -> dict[str, Any]: props = self._props props.update(self._select(props)) props.update(self._placement(props)) @@ -251,7 +251,7 @@ def _processed_props(self) -> Dict[str, Any]: props.pop("select", None) return dict(props) - def _select(self, items: Dict[str, Any]) -> Dict[str, Any]: + def _select(self, items: dict[str, Any]) -> dict[str, Any]: """ Select logic. """ @@ -285,7 +285,7 @@ class Slurm(JobScheduler): """ @property - def _managed_directives(self) -> Dict[str, Any]: + def _managed_directives(self) -> dict[str, Any]: """ Returns a mapping from canonical names to scheduler-specific CLI switches. """ diff --git a/src/uwtools/strings.py b/src/uwtools/strings.py index ffd14bb67..f330da9a8 100644 --- a/src/uwtools/strings.py +++ b/src/uwtools/strings.py @@ -3,7 +3,6 @@ """ from dataclasses import dataclass, fields -from typing import Dict, List @dataclass(frozen=True) @@ -37,14 +36,14 @@ class FORMAT: yml: str = _yaml @staticmethod - def extensions() -> List[str]: + def extensions() -> list[str]: """ Returns recognized filename extensions. """ return [FORMAT.ini, FORMAT.nml, FORMAT.sh, FORMAT.yaml] @staticmethod - def formats() -> Dict[str, str]: + def formats() -> dict[str, str]: """ Returns the recognized format names. """ diff --git a/src/uwtools/tests/api/test_config.py b/src/uwtools/tests/api/test_config.py index 6d6dc98a1..8336f6c6b 100644 --- a/src/uwtools/tests/api/test_config.py +++ b/src/uwtools/tests/api/test_config.py @@ -4,8 +4,8 @@ from pathlib import Path from unittest.mock import patch -import pytest import yaml +from pytest import mark from uwtools.api import config from uwtools.config.formats.yaml import YAMLConfig @@ -29,7 +29,7 @@ def test_compare(): ) -@pytest.mark.parametrize( +@mark.parametrize( "classname,f", [ ("_FieldTableConfig", config.get_fieldtable_config), @@ -88,7 +88,7 @@ def test_realize_to_dict(): ) -@pytest.mark.parametrize("cfg", [{"foo": "bar"}, YAMLConfig(config={})]) +@mark.parametrize("cfg", [{"foo": "bar"}, YAMLConfig(config={})]) def test_validate(cfg): kwargs: dict = {"schema_file": "schema-file", "config": cfg} with patch.object(config, "_validate_yaml", return_value=True) as _validate_yaml: diff --git a/src/uwtools/tests/api/test_drivers.py b/src/uwtools/tests/api/test_drivers.py index 5fb38e231..945a8559a 100644 --- a/src/uwtools/tests/api/test_drivers.py +++ b/src/uwtools/tests/api/test_drivers.py @@ -4,7 +4,7 @@ from unittest.mock import patch import iotaa -import pytest +from pytest import mark from uwtools.api import ( chgres_cube, @@ -45,7 +45,7 @@ with_leadtime = [upp] -@pytest.mark.parametrize("module", modules) +@mark.parametrize("module", modules) def test_api_execute(module): kwbase = { "batch": True, @@ -71,12 +71,12 @@ def test_api_execute(module): ) -@pytest.mark.parametrize("module", modules) +@mark.parametrize("module", modules) def test_api_graph(module): assert module.graph is support.graph -@pytest.mark.parametrize("module", modules) +@mark.parametrize("module", modules) def test_api_tasks(module): with patch.object(iotaa, "tasknames") as tasknames: module.tasks() diff --git a/src/uwtools/tests/config/formats/test_base.py b/src/uwtools/tests/config/formats/test_base.py index 8d6458970..f2f367e8c 100644 --- a/src/uwtools/tests/config/formats/test_base.py +++ b/src/uwtools/tests/config/formats/test_base.py @@ -7,9 +7,8 @@ import os from unittest.mock import patch -import pytest import yaml -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.config import tools from uwtools.config.formats.base import Config @@ -85,7 +84,7 @@ def test_characterize_values(config): assert template == [" p3: {{ n }}"] -@pytest.mark.parametrize("fmt", [FORMAT.ini, FORMAT.nml, FORMAT.yaml]) +@mark.parametrize("fmt", [FORMAT.ini, FORMAT.nml, FORMAT.yaml]) def test_compare_config(caplog, fmt, salad_base): """ Compare two config objects. @@ -165,7 +164,7 @@ def test_derefernce_context_override(tmp_path): assert config["file"] == "gfs.t06z.atmanl.nc" -@pytest.mark.parametrize("fmt2", [FORMAT.ini, FORMAT.nml, FORMAT.sh]) +@mark.parametrize("fmt2", [FORMAT.ini, FORMAT.nml, FORMAT.sh]) def test_invalid_config(fmt2, tmp_path): """ Test that invalid config files will error when attempting to dump. diff --git a/src/uwtools/tests/config/formats/test_sh.py b/src/uwtools/tests/config/formats/test_sh.py index f24a8816e..6f9e7f901 100644 --- a/src/uwtools/tests/config/formats/test_sh.py +++ b/src/uwtools/tests/config/formats/test_sh.py @@ -3,7 +3,7 @@ Tests for uwtools.config.formats.sh module. """ -from typing import Any, Dict +from typing import Any from pytest import raises @@ -46,7 +46,7 @@ def test_sh(salad_base): """ infile = fixture_path("simple.sh") cfgobj = SHConfig(infile) - expected: Dict[str, Any] = { + expected: dict[str, Any] = { **salad_base["salad"], "how_many": "12", } diff --git a/src/uwtools/tests/config/test_jinja2.py b/src/uwtools/tests/config/test_jinja2.py index c7f9c3e1b..186472464 100644 --- a/src/uwtools/tests/config/test_jinja2.py +++ b/src/uwtools/tests/config/test_jinja2.py @@ -10,10 +10,9 @@ from types import SimpleNamespace as ns from unittest.mock import patch -import pytest import yaml from jinja2 import DebugUndefined, Environment, TemplateNotFound, UndefinedError -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.config import jinja2 from uwtools.config.jinja2 import J2Template @@ -109,13 +108,13 @@ def test_dereference_local_values(): } -@pytest.mark.parametrize("val", (True, 3.14, 88, None)) +@mark.parametrize("val", (True, 3.14, 88, None)) def test_dereference_no_op(val): # These types of values pass through dereferencing unmodified: assert jinja2.dereference(val=val, context={}) == val -@pytest.mark.parametrize( +@mark.parametrize( "logmsg,val", [ ("can only concatenate", "{{ 'str' + 11 }}"), @@ -174,7 +173,7 @@ def test_register_filters_env(): assert template.render() == "hello world" -@pytest.mark.parametrize("key", ["foo", "bar"]) +@mark.parametrize("key", ["foo", "bar"]) def test_register_filters_path_join(key): s = "{{ ['dir', %s] | path_join }}" % key template = jinja2._register_filters(Environment(undefined=DebugUndefined)).from_string(s) @@ -276,12 +275,12 @@ def test_render_values_needed(caplog, template_file, values_file): assert logged(caplog, f" {var}") -@pytest.mark.parametrize("s,status", [("foo: bar", False), ("foo: '{{ bar }} {{ baz }}'", True)]) +@mark.parametrize("s,status", [("foo: bar", False), ("foo: '{{ bar }} {{ baz }}'", True)]) def test_unrendered(s, status): assert jinja2.unrendered(s) is status -@pytest.mark.parametrize("tag", ["!float", "!int"]) +@mark.parametrize("tag", ["!float", "!int"]) def test__deref_convert_no(caplog, tag): log.setLevel(logging.DEBUG) loader = yaml.SafeLoader(os.devnull) @@ -291,7 +290,7 @@ def test__deref_convert_no(caplog, tag): assert regex_logged(caplog, "Conversion failed") -@pytest.mark.parametrize("converted,tag,value", [(3.14, "!float", "3.14"), (88, "!int", "88")]) +@mark.parametrize("converted,tag,value", [(3.14, "!float", "3.14"), (88, "!int", "88")]) def test__deref_convert_ok(caplog, converted, tag, value): log.setLevel(logging.DEBUG) loader = yaml.SafeLoader(os.devnull) diff --git a/src/uwtools/tests/config/test_support.py b/src/uwtools/tests/config/test_support.py index 337b06fa8..f36ed3520 100644 --- a/src/uwtools/tests/config/test_support.py +++ b/src/uwtools/tests/config/test_support.py @@ -6,9 +6,8 @@ import logging from collections import OrderedDict -import pytest import yaml -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.config import support from uwtools.config.formats.fieldtable import FieldTableConfig @@ -22,14 +21,12 @@ from uwtools.utils.file import FORMAT -@pytest.mark.parametrize( - "d,n", [({1: 88}, 1), ({1: {2: 88}}, 2), ({1: {2: {3: 88}}}, 3), ({1: {}}, 2)] -) +@mark.parametrize("d,n", [({1: 88}, 1), ({1: {2: 88}}, 2), ({1: {2: {3: 88}}}, 3), ({1: {}}, 2)]) def test_depth(d, n): assert support.depth(d) == n -@pytest.mark.parametrize( +@mark.parametrize( "cfgtype,fmt", [ (FieldTableConfig, FORMAT.fieldtable), diff --git a/src/uwtools/tests/config/test_tools.py b/src/uwtools/tests/config/test_tools.py index ad095eec2..2ea860181 100644 --- a/src/uwtools/tests/config/test_tools.py +++ b/src/uwtools/tests/config/test_tools.py @@ -11,9 +11,8 @@ from unittest.mock import patch import f90nml # type: ignore -import pytest import yaml -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.config import tools from uwtools.config.formats.ini import INIConfig @@ -826,8 +825,8 @@ def test__realize_config_values_needed_negative_results(caplog, tmp_path): assert "No keys have unrendered Jinja2 variables/expressions." in msgs -@pytest.mark.parametrize("input_fmt", FORMAT.extensions()) -@pytest.mark.parametrize("other_fmt", FORMAT.extensions()) +@mark.parametrize("input_fmt", FORMAT.extensions()) +@mark.parametrize("other_fmt", FORMAT.extensions()) def test__validate_format(input_fmt, other_fmt): call = lambda: tools._validate_format( other_fmt_desc="other", other_fmt=other_fmt, input_fmt=input_fmt diff --git a/src/uwtools/tests/config/test_validator.py b/src/uwtools/tests/config/test_validator.py index 566cf49f5..54ac5a1f2 100644 --- a/src/uwtools/tests/config/test_validator.py +++ b/src/uwtools/tests/config/test_validator.py @@ -5,7 +5,7 @@ import json import logging from pathlib import Path -from typing import Any, Dict, Tuple +from typing import Any from unittest.mock import patch import yaml @@ -22,7 +22,7 @@ @fixture -def assets(config, schema, tmp_path) -> Tuple[Path, Path, YAMLConfig]: +def assets(config, schema, tmp_path) -> tuple[Path, Path, YAMLConfig]: config_file = tmp_path / "config.yaml" schema_file = tmp_path / "schema.yaml" write_as_json(config, config_file) @@ -31,7 +31,7 @@ def assets(config, schema, tmp_path) -> Tuple[Path, Path, YAMLConfig]: @fixture -def config(tmp_path) -> Dict[str, Any]: +def config(tmp_path) -> dict[str, Any]: return { "color": "blue", "dir": str(tmp_path), @@ -78,7 +78,7 @@ def rocoto_assets(): @fixture -def schema() -> Dict[str, Any]: +def schema() -> dict[str, Any]: return { "properties": { "color": { @@ -115,7 +115,7 @@ def schema_file(schema, tmp_path) -> Path: # Helpers -def write_as_json(data: Dict[str, Any], path: Path) -> Path: +def write_as_json(data: dict[str, Any], path: Path) -> Path: with open(path, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) return path diff --git a/src/uwtools/tests/drivers/test_chgres_cube.py b/src/uwtools/tests/drivers/test_chgres_cube.py index 052d9b2b2..ac4f50463 100644 --- a/src/uwtools/tests/drivers/test_chgres_cube.py +++ b/src/uwtools/tests/drivers/test_chgres_cube.py @@ -10,7 +10,7 @@ import f90nml # type: ignore from iotaa import refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.chgres_cube import ChgresCube from uwtools.drivers.driver import Driver @@ -83,8 +83,9 @@ def driverobj(config, cycle): # Tests -def test_ChgresCube(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -97,8 +98,10 @@ def test_ChgresCube(): "_validate", "_write_runscript", "run", - ]: - assert getattr(ChgresCube, method) is getattr(Driver, method) + ], +) +def test_ChgresCube(method): + assert getattr(ChgresCube, method) is getattr(Driver, method) def test_ChgresCube_namelist_file(caplog, driverobj): diff --git a/src/uwtools/tests/drivers/test_driver.py b/src/uwtools/tests/drivers/test_driver.py index 265c4967c..a817a2707 100644 --- a/src/uwtools/tests/drivers/test_driver.py +++ b/src/uwtools/tests/drivers/test_driver.py @@ -9,10 +9,9 @@ from textwrap import dedent from unittest.mock import Mock, PropertyMock, patch -import pytest import yaml from iotaa import asset, task -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.config.formats.yaml import YAMLConfig from uwtools.drivers import driver @@ -138,14 +137,14 @@ def test_Assets(assetobj): assert assetobj._batch is True -@pytest.mark.parametrize("hours", [0, 24, 168]) +@mark.parametrize("hours", [0, 24, 168]) def test_Assets_cycle_leadtime_error(config, hours): with raises(UWError) as e: ConcreteAssets(config=config, leadtime=dt.timedelta(hours=hours)) assert "When leadtime is specified, cycle is required" in str(e) -@pytest.mark.parametrize("val", (True, False)) +@mark.parametrize("val", (True, False)) def test_Assets_dry_run(config, val): with patch.object(driver, "dryrun") as dryrun: ConcreteAssets(config=config, dry_run=val) @@ -178,7 +177,7 @@ def test_Assets_validate(assetobj, caplog): # Tests for private helper methods -@pytest.mark.parametrize( +@mark.parametrize( "base_file,update_values,expected", [ (False, False, {}), @@ -243,7 +242,7 @@ def test_Driver(driverobj): # Tests for workflow methods -@pytest.mark.parametrize("batch", [True, False]) +@mark.parametrize("batch", [True, False]) def test_Driver_run(batch, driverobj): driverobj._batch = batch executable = Path(driverobj._driver_config["execution"]["executable"]) @@ -259,13 +258,14 @@ def test_Driver_run(batch, driverobj): rvle.assert_called_once_with() -def test_Driver_runscript(driverobj): +@mark.parametrize( + "arg,type_", [("envcmds", list), ("envvars", dict), ("execution", list), ("scheduler", Slurm)] +) +def test_Driver_runscript(arg, driverobj, type_): with patch.object(driverobj, "_runscript") as runscript: driverobj.runscript() runscript.assert_called_once() - args = ("envcmds", "envvars", "execution", "scheduler") - types = [list, dict, list, Slurm] - assert [type(runscript.call_args.kwargs[x]) for x in args] == types + assert isinstance(runscript.call_args.kwargs[arg], type_) def test_Driver__run_via_batch_submission(driverobj): @@ -298,7 +298,7 @@ def test_Driver__run_via_local_execution(driverobj): # Tests for private helper methods -@pytest.mark.parametrize( +@mark.parametrize( "base_file,update_values,expected", [ (False, False, {}), diff --git a/src/uwtools/tests/drivers/test_esg_grid.py b/src/uwtools/tests/drivers/test_esg_grid.py index 0274e0e2c..58e2b57f8 100644 --- a/src/uwtools/tests/drivers/test_esg_grid.py +++ b/src/uwtools/tests/drivers/test_esg_grid.py @@ -9,7 +9,7 @@ import f90nml # type: ignore from iotaa import refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.esg_grid import ESGGrid @@ -62,8 +62,9 @@ def driverobj(config): # Tests -def test_ESGGrid(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -78,8 +79,10 @@ def test_ESGGrid(): "_write_runscript", "run", "runscript", - ]: - assert getattr(ESGGrid, method) is getattr(Driver, method) + ], +) +def test_ESGGrid(method): + assert getattr(ESGGrid, method) is getattr(Driver, method) def test_ESGGrid_namelist_file(caplog, driverobj): diff --git a/src/uwtools/tests/drivers/test_filter_topo.py b/src/uwtools/tests/drivers/test_filter_topo.py index 60de0e5dd..5b6ee46fc 100644 --- a/src/uwtools/tests/drivers/test_filter_topo.py +++ b/src/uwtools/tests/drivers/test_filter_topo.py @@ -8,7 +8,7 @@ import f90nml # type: ignore from iotaa import refs -from pytest import fixture +from pytest import fixture, mark from uwtools.config.support import from_od from uwtools.drivers.driver import Driver @@ -57,8 +57,9 @@ def driverobj(config): # Tests -def test_FilterTopo(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -73,8 +74,10 @@ def test_FilterTopo(): "_write_runscript", "run", "runscript", - ]: - assert getattr(FilterTopo, method) is getattr(Driver, method) + ], +) +def test_FilterTopo(method): + assert getattr(FilterTopo, method) is getattr(Driver, method) def test_FilterTopo_input_grid_file(driverobj): diff --git a/src/uwtools/tests/drivers/test_fv3.py b/src/uwtools/tests/drivers/test_fv3.py index f3afbbe53..3c7bcc375 100644 --- a/src/uwtools/tests/drivers/test_fv3.py +++ b/src/uwtools/tests/drivers/test_fv3.py @@ -8,10 +8,9 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -import pytest import yaml from iotaa import asset, external, refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.fv3 import FV3 @@ -82,8 +81,9 @@ def true(): # Tests -def test_FV3(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -96,8 +96,10 @@ def test_FV3(): "_validate", "_write_runscript", "run", - ]: - assert getattr(FV3, method) is getattr(Driver, method) + ], +) +def test_FV3(method): + assert getattr(FV3, method) is getattr(Driver, method) def test_FV3_boundary_files(driverobj): @@ -135,7 +137,7 @@ def test_FV3_field_table(driverobj): assert dst.is_file() -@pytest.mark.parametrize( +@mark.parametrize( "key,task,test", [("files_to_copy", "files_copied", "is_file"), ("files_to_link", "files_linked", "is_symlink")], ) @@ -157,7 +159,7 @@ def test_FV3_files_copied_and_linked(config, cycle, key, task, test, tmp_path): assert all(getattr(dst, test)() for dst in [atm_dst, sfc_dst]) -@pytest.mark.parametrize("base_file_exists", [True, False]) +@mark.parametrize("base_file_exists", [True, False]) def test_FV3_model_configure(base_file_exists, caplog, driverobj): log.setLevel(logging.DEBUG) src = driverobj._rundir / "model_configure.in" @@ -206,7 +208,7 @@ def test_FV3_namelist_file_missing_base_file(caplog, driverobj): assert regex_logged(caplog, "missing.nml: State: Not Ready (external asset)") -@pytest.mark.parametrize("domain", ("global", "regional")) +@mark.parametrize("domain", ("global", "regional")) def test_FV3_provisioned_run_directory(domain, driverobj): driverobj._driver_config["domain"] = domain with patch.multiple( diff --git a/src/uwtools/tests/drivers/test_global_equiv_resol.py b/src/uwtools/tests/drivers/test_global_equiv_resol.py index f7d3a265e..5ba15c5e5 100644 --- a/src/uwtools/tests/drivers/test_global_equiv_resol.py +++ b/src/uwtools/tests/drivers/test_global_equiv_resol.py @@ -6,7 +6,7 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.global_equiv_resol import GlobalEquivResol @@ -43,8 +43,9 @@ def driverobj(config): # Tests -def test_GlobalEquivResol(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -58,8 +59,10 @@ def test_GlobalEquivResol(): "_write_runscript", "run", "runscript", - ]: - assert getattr(GlobalEquivResol, method) is getattr(Driver, method) + ], +) +def test_GlobalEquivResol(method): + assert getattr(GlobalEquivResol, method) is getattr(Driver, method) def test_GlobalEquivResol_input_file(driverobj): diff --git a/src/uwtools/tests/drivers/test_ioda.py b/src/uwtools/tests/drivers/test_ioda.py index 1394322c5..29df5402c 100644 --- a/src/uwtools/tests/drivers/test_ioda.py +++ b/src/uwtools/tests/drivers/test_ioda.py @@ -6,7 +6,7 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.ioda import IODA from uwtools.drivers.jedi_base import JEDIBase @@ -67,8 +67,9 @@ def driverobj(config, cycle): # Tests -def test_IODA(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -81,8 +82,10 @@ def test_IODA(): "_write_runscript", "run", "runscript", - ]: - assert getattr(IODA, method) is getattr(JEDIBase, method) + ], +) +def test_IODA(method): + assert getattr(IODA, method) is getattr(JEDIBase, method) def test_IODA_provisioned_run_directory(driverobj): diff --git a/src/uwtools/tests/drivers/test_jedi.py b/src/uwtools/tests/drivers/test_jedi.py index 26a79929f..b0bdd4d6b 100644 --- a/src/uwtools/tests/drivers/test_jedi.py +++ b/src/uwtools/tests/drivers/test_jedi.py @@ -10,7 +10,7 @@ import yaml from iotaa import asset, external -from pytest import fixture +from pytest import fixture, mark from uwtools.config.formats.yaml import YAMLConfig from uwtools.drivers import jedi, jedi_base @@ -77,8 +77,9 @@ def driverobj(config, cycle): # Tests -def test_JEDI(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -91,8 +92,10 @@ def test_JEDI(): "_write_runscript", "run", "runscript", - ]: - assert getattr(JEDI, method) is getattr(JEDIBase, method) + ], +) +def test_JEDI(method): + assert getattr(JEDI, method) is getattr(JEDIBase, method) def test_JEDI_configuration_file(driverobj): diff --git a/src/uwtools/tests/drivers/test_make_hgrid.py b/src/uwtools/tests/drivers/test_make_hgrid.py index 390cd41f5..35559466f 100644 --- a/src/uwtools/tests/drivers/test_make_hgrid.py +++ b/src/uwtools/tests/drivers/test_make_hgrid.py @@ -5,7 +5,7 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.make_hgrid import MakeHgrid @@ -48,8 +48,9 @@ def driverobj(config): # Tests -def test_MakeHgrid(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -63,8 +64,10 @@ def test_MakeHgrid(): "_write_runscript", "run", "runscript", - ]: - assert getattr(MakeHgrid, method) is getattr(Driver, method) + ], +) +def test_MakeHgrid(method): + assert getattr(MakeHgrid, method) is getattr(Driver, method) def test_MakeHgrid_provisioned_run_directory(driverobj): diff --git a/src/uwtools/tests/drivers/test_make_solo_mosaic.py b/src/uwtools/tests/drivers/test_make_solo_mosaic.py index 2d297fcdf..f1c134fce 100644 --- a/src/uwtools/tests/drivers/test_make_solo_mosaic.py +++ b/src/uwtools/tests/drivers/test_make_solo_mosaic.py @@ -4,7 +4,7 @@ """ from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.make_solo_mosaic import MakeSoloMosaic @@ -44,8 +44,9 @@ def driverobj(config): # Tests -def test_MakeSoloMosaic(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -58,8 +59,10 @@ def test_MakeSoloMosaic(): "_write_runscript", "run", "runscript", - ]: - assert getattr(MakeSoloMosaic, method) is getattr(Driver, method) + ], +) +def test_MakeSoloMosaic(method): + assert getattr(MakeSoloMosaic, method) is getattr(Driver, method) def test_MakeSoloMosaic_provisioned_run_directory(driverobj): diff --git a/src/uwtools/tests/drivers/test_mpas.py b/src/uwtools/tests/drivers/test_mpas.py index 943ae50fb..f1ca3fffa 100644 --- a/src/uwtools/tests/drivers/test_mpas.py +++ b/src/uwtools/tests/drivers/test_mpas.py @@ -9,11 +9,10 @@ from unittest.mock import patch import f90nml # type: ignore -import pytest import yaml from iotaa import refs from lxml import etree -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.mpas import MPAS from uwtools.drivers.mpas_base import MPASBase @@ -118,8 +117,9 @@ def driverobj(config, cycle): # Tests -def test_MPAS(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -135,8 +135,10 @@ def test_MPAS(): "run", "runscript", "streams_file", - ]: - assert getattr(MPAS, method) is getattr(MPASBase, method) + ], +) +def test_MPAS(method): + assert getattr(MPAS, method) is getattr(MPASBase, method) def test_MPAS_boundary_files(driverobj, cycle): @@ -149,14 +151,13 @@ def test_MPAS_boundary_files(driverobj, cycle): infile_path = Path(driverobj._driver_config["lateral_boundary_conditions"]["path"]) infile_path.mkdir() for n in ns: - ( - infile_path / f"lbc.{(cycle+dt.timedelta(hours=n)).strftime('%Y-%m-%d_%H.%M.%S')}.nc" - ).touch() + path = infile_path / f"lbc.{(cycle+dt.timedelta(hours=n)).strftime('%Y-%m-%d_%H.%M.%S')}.nc" + path.touch() driverobj.boundary_files() assert all(link.is_symlink() for link in links) -@pytest.mark.parametrize( +@mark.parametrize( "key,task,test", [("files_to_copy", "files_copied", "is_file"), ("files_to_link", "files_linked", "is_symlink")], ) diff --git a/src/uwtools/tests/drivers/test_mpas_init.py b/src/uwtools/tests/drivers/test_mpas_init.py index c729106f8..a3af38cca 100644 --- a/src/uwtools/tests/drivers/test_mpas_init.py +++ b/src/uwtools/tests/drivers/test_mpas_init.py @@ -9,9 +9,8 @@ from unittest.mock import patch import f90nml # type: ignore -import pytest from iotaa import refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.mpas_base import MPASBase from uwtools.drivers.mpas_init import MPASInit @@ -100,8 +99,9 @@ def driverobj(config, cycle): # Tests -def test_MPASInit(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -117,8 +117,10 @@ def test_MPASInit(): "run", "runscript", "streams_file", - ]: - assert getattr(MPASInit, method) is getattr(MPASBase, method) + ], +) +def test_MPASInit(method): + assert getattr(MPASInit, method) is getattr(MPASBase, method) def test_MPASInit_boundary_files(cycle, driverobj): @@ -136,7 +138,7 @@ def test_MPASInit_boundary_files(cycle, driverobj): assert all(link.is_symlink() for link in links) -@pytest.mark.parametrize( +@mark.parametrize( "key,task,test", [("files_to_copy", "files_copied", "is_file"), ("files_to_link", "files_linked", "is_symlink")], ) diff --git a/src/uwtools/tests/drivers/test_orog_gsl.py b/src/uwtools/tests/drivers/test_orog_gsl.py index b9b17f99d..ba15bb606 100644 --- a/src/uwtools/tests/drivers/test_orog_gsl.py +++ b/src/uwtools/tests/drivers/test_orog_gsl.py @@ -6,7 +6,7 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.orog_gsl import OrogGSL @@ -51,8 +51,9 @@ def driverobj(config): # Tests -def test_OrogGSL(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -66,8 +67,10 @@ def test_OrogGSL(): "_write_runscript", "run", "runscript", - ]: - assert getattr(OrogGSL, method) is getattr(Driver, method) + ], +) +def test_OrogGSL(method): + assert getattr(OrogGSL, method) is getattr(Driver, method) def test_OrogGSL_input_grid_file(driverobj): diff --git a/src/uwtools/tests/drivers/test_schism.py b/src/uwtools/tests/drivers/test_schism.py index 88d56c3fe..abb91b0de 100644 --- a/src/uwtools/tests/drivers/test_schism.py +++ b/src/uwtools/tests/drivers/test_schism.py @@ -7,7 +7,7 @@ from unittest.mock import patch import yaml -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Assets from uwtools.drivers.schism import SCHISM @@ -43,13 +43,16 @@ def driverobj(config, cycle): # Tests -def test_SCHISM(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_taskname", "_validate", - ]: - assert getattr(SCHISM, method) is getattr(Assets, method) + ], +) +def test_SCHISM(method): + assert getattr(SCHISM, method) is getattr(Assets, method) def test_SCHISM_namelist_file(driverobj): diff --git a/src/uwtools/tests/drivers/test_sfc_climo_gen.py b/src/uwtools/tests/drivers/test_sfc_climo_gen.py index 2265786f9..186252299 100644 --- a/src/uwtools/tests/drivers/test_sfc_climo_gen.py +++ b/src/uwtools/tests/drivers/test_sfc_climo_gen.py @@ -9,7 +9,7 @@ import f90nml # type: ignore from iotaa import asset, external, refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers import sfc_climo_gen from uwtools.drivers.driver import Driver @@ -84,8 +84,9 @@ def driverobj(config): # Tests -def test_SfcClimoGen(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -100,8 +101,10 @@ def test_SfcClimoGen(): "_write_runscript", "run", "runscript", - ]: - assert getattr(SfcClimoGen, method) is getattr(Driver, method) + ], +) +def test_SfcClimoGen(method): + assert getattr(SfcClimoGen, method) is getattr(Driver, method) def test_SfcClimoGen_namelist_file(caplog, driverobj): diff --git a/src/uwtools/tests/drivers/test_shave.py b/src/uwtools/tests/drivers/test_shave.py index d3a25abca..30dc5793d 100644 --- a/src/uwtools/tests/drivers/test_shave.py +++ b/src/uwtools/tests/drivers/test_shave.py @@ -5,7 +5,7 @@ from unittest.mock import DEFAULT as D from unittest.mock import patch -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.shave import Shave @@ -49,8 +49,9 @@ def driverobj(config): # Tests -def test_Shave(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -64,8 +65,10 @@ def test_Shave(): "_write_runscript", "run", "runscript", - ]: - assert getattr(Shave, method) is getattr(Driver, method) + ], +) +def test_Shave(method): + assert getattr(Shave, method) is getattr(Driver, method) def test_Shave_provisioned_run_directory(driverobj): diff --git a/src/uwtools/tests/drivers/test_ungrib.py b/src/uwtools/tests/drivers/test_ungrib.py index e5e5bc3eb..3265c22a7 100644 --- a/src/uwtools/tests/drivers/test_ungrib.py +++ b/src/uwtools/tests/drivers/test_ungrib.py @@ -7,7 +7,7 @@ from unittest.mock import patch import f90nml # type: ignore -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers import ungrib from uwtools.drivers.driver import Driver @@ -56,8 +56,9 @@ def driverobj(config, cycle): # Tests -def test_Ungrib(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -71,8 +72,10 @@ def test_Ungrib(): "_write_runscript", "run", "runscript", - ]: - assert getattr(Ungrib, method) is getattr(Driver, method) + ], +) +def test_Ungrib(method): + assert getattr(Ungrib, method) is getattr(Driver, method) def test_Ungrib_gribfiles(driverobj, tmp_path): diff --git a/src/uwtools/tests/drivers/test_upp.py b/src/uwtools/tests/drivers/test_upp.py index 4af5ab97b..072296587 100644 --- a/src/uwtools/tests/drivers/test_upp.py +++ b/src/uwtools/tests/drivers/test_upp.py @@ -10,7 +10,7 @@ import f90nml # type: ignore from iotaa import refs -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Driver from uwtools.drivers.upp import UPP @@ -77,8 +77,9 @@ def leadtime(): # Tests -def test_UPP(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_resources", "_run_via_batch_submission", @@ -91,8 +92,10 @@ def test_UPP(): "_write_runscript", "run", "runscript", - ]: - assert getattr(UPP, method) is getattr(Driver, method) + ], +) +def test_UPP(method): + assert getattr(UPP, method) is getattr(Driver, method) def test_UPP_files_copied(driverobj): diff --git a/src/uwtools/tests/drivers/test_ww3.py b/src/uwtools/tests/drivers/test_ww3.py index 222763707..44b36d00e 100644 --- a/src/uwtools/tests/drivers/test_ww3.py +++ b/src/uwtools/tests/drivers/test_ww3.py @@ -7,7 +7,7 @@ from unittest.mock import patch import yaml -from pytest import fixture +from pytest import fixture, mark from uwtools.drivers.driver import Assets from uwtools.drivers.ww3 import WaveWatchIII @@ -43,13 +43,16 @@ def driverobj(config, cycle): # Tests -def test_WaveWatchIII(): - for method in [ +@mark.parametrize( + "method", + [ "_driver_config", "_taskname", "_validate", - ]: - assert getattr(WaveWatchIII, method) is getattr(Assets, method) + ], +) +def test_WaveWatchIII(method): + assert getattr(WaveWatchIII, method) is getattr(Assets, method) def test_WaveWatchIII_namelist_file(driverobj): diff --git a/src/uwtools/tests/test_cli.py b/src/uwtools/tests/test_cli.py index abb0b4e8c..093e52a92 100644 --- a/src/uwtools/tests/test_cli.py +++ b/src/uwtools/tests/test_cli.py @@ -7,11 +7,9 @@ from argparse import ArgumentParser as Parser from argparse import _SubParsersAction from pathlib import Path -from typing import List from unittest.mock import Mock, patch -import pytest -from pytest import fixture, raises +from pytest import fixture, mark, raises import uwtools.api import uwtools.api.config @@ -27,7 +25,7 @@ # Helpers -def actions(parser: Parser) -> List[str]: +def actions(parser: Parser) -> list[str]: # Return actions (named subparsers) belonging to the given parser. if actions := [x for x in parser._actions if isinstance(x, _SubParsersAction)]: return list(actions[0].choices.keys()) @@ -163,7 +161,7 @@ def test__add_subparser_template_translate(subparsers): assert subparsers.choices[STR.translate] -@pytest.mark.parametrize( +@mark.parametrize( "vals", [ (STR.file1path, STR.file1fmt), @@ -198,7 +196,7 @@ def test__check_file_vs_format_pass_explicit(): assert args[STR.infmt] == fmt -@pytest.mark.parametrize("fmt", FORMAT.formats()) +@mark.parametrize("fmt", FORMAT.formats()) def test__check_file_vs_format_pass_implicit(fmt): # The format is correctly deduced for a file with a known extension. args = {STR.infile: f"/path/to/input.{fmt}", STR.infmt: None} @@ -237,7 +235,7 @@ def test__check_template_render_vals_args_noop_explicit_valsfmt(): assert cli._check_template_render_vals_args(args) == args -@pytest.mark.parametrize( +@mark.parametrize( "fmt,fn,ok", [ (None, "update.txt", False), @@ -265,7 +263,7 @@ def test__check_verbosity_fail(capsys): assert "--quiet may not be used with --verbose" in capsys.readouterr().err -@pytest.mark.parametrize("flags", ([STR.quiet], [STR.verbose])) +@mark.parametrize("flags", ([STR.quiet], [STR.verbose])) def test__check_verbosity_ok(flags): args = {flag: True for flag in flags} assert cli._check_verbosity(args) == args @@ -276,7 +274,7 @@ def test__dict_from_key_eq_val_strings(): assert cli._dict_from_key_eq_val_strings(["a=1", "b=2"]) == {"a": "1", "b": "2"} -@pytest.mark.parametrize( +@mark.parametrize( "params", [ (STR.compare, "_dispatch_config_compare"), @@ -343,7 +341,7 @@ def test__dispatch_config_validate_config_obj(): _validate_yaml.assert_called_once_with(**_validate_yaml_args) -@pytest.mark.parametrize( +@mark.parametrize( "action, funcname", [(STR.copy, "_dispatch_file_copy"), (STR.link, "_dispatch_file_link")] ) def test__dispatch_file(action, funcname): @@ -383,7 +381,7 @@ def test__dispatch_file_link(args_dispatch_file): ) -@pytest.mark.parametrize( +@mark.parametrize( "params", [ (STR.realize, "_dispatch_rocoto_realize"), @@ -432,7 +430,7 @@ def test__dispatch_rocoto_validate_xml_no_optional(): validate.assert_called_once_with(xml_file=None) -@pytest.mark.parametrize( +@mark.parametrize( "params", [(STR.render, "_dispatch_template_render"), (STR.translate, "_dispatch_template_translate")], ) @@ -444,7 +442,7 @@ def test__dispatch_template(params): func.assert_called_once_with(args) -@pytest.mark.parametrize("valsneeded", [False, True]) +@mark.parametrize("valsneeded", [False, True]) def test__dispatch_template_render_fail(valsneeded): args = { STR.infile: 1, @@ -545,7 +543,7 @@ def test__dispatch_template_translate_no_optional(): ) -@pytest.mark.parametrize("hours", [0, 24, 168]) +@mark.parametrize("hours", [0, 24, 168]) def test__dispatch_to_driver(hours): name = "adriver" cycle = dt.datetime.now() @@ -577,8 +575,8 @@ def test__dispatch_to_driver(hours): ) -@pytest.mark.parametrize("quiet", [False, True]) -@pytest.mark.parametrize("verbose", [False, True]) +@mark.parametrize("quiet", [False, True]) +@mark.parametrize("verbose", [False, True]) def test_main_fail_checks(capsys, quiet, verbose): # Using mode 'template render' for testing. raw_args = ["testing", STR.template, STR.render] @@ -597,7 +595,7 @@ def test_main_fail_checks(capsys, quiet, verbose): assert e.value.code == 0 -@pytest.mark.parametrize("vals", [(True, 0), (False, 1)]) +@mark.parametrize("vals", [(True, 0), (False, 1)]) def test_main_fail_dispatch(vals): # Using mode 'template render' for testing. dispatch_retval, exit_status = vals diff --git a/src/uwtools/tests/test_file.py b/src/uwtools/tests/test_file.py index 396f53be9..13b8f529b 100644 --- a/src/uwtools/tests/test_file.py +++ b/src/uwtools/tests/test_file.py @@ -1,9 +1,8 @@ # pylint: disable=missing-function-docstring,protected-access,redefined-outer-name import iotaa -import pytest import yaml -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools import file from uwtools.exceptions import UWConfigError @@ -26,7 +25,7 @@ def assets(tmp_path): return dstdir, cfgdict, cfgfile -@pytest.mark.parametrize("source", ("dict", "file")) +@mark.parametrize("source", ("dict", "file")) def test_FileCopier(assets, source): dstdir, cfgdict, cfgfile = assets config = cfgdict if source == "dict" else cfgfile @@ -49,7 +48,7 @@ def test_FileCopier_config_file_dry_run(assets): iotaa.dryrun(False) -@pytest.mark.parametrize("source", ("dict", "file")) +@mark.parametrize("source", ("dict", "file")) def test_FileLinker(assets, source): dstdir, cfgdict, cfgfile = assets config = cfgdict if source == "dict" else cfgfile @@ -61,7 +60,7 @@ def test_FileLinker(assets, source): assert (dstdir / "subdir" / "bar").is_symlink() -@pytest.mark.parametrize("source", ("dict", "file")) +@mark.parametrize("source", ("dict", "file")) def test_FileStager(assets, source): dstdir, cfgdict, cfgfile = assets config = cfgdict if source == "dict" else cfgfile @@ -70,7 +69,7 @@ def test_FileStager(assets, source): assert stager._validate() is True -@pytest.mark.parametrize("source", ("dict", "file")) +@mark.parametrize("source", ("dict", "file")) def test_FileStager_bad_key(assets, source): dstdir, cfgdict, cfgfile = assets config = cfgdict if source == "dict" else cfgfile @@ -79,7 +78,7 @@ def test_FileStager_bad_key(assets, source): assert str(e.value) == "Failed following YAML key(s): a -> x" -@pytest.mark.parametrize("val", [None, True, False, "str", 88, 3.14, [], tuple()]) +@mark.parametrize("val", [None, True, False, "str", 88, 3.14, [], tuple()]) def test_FileStager_empty_val(assets, val): dstdir, cfgdict, _ = assets cfgdict["a"]["b"] = val diff --git a/src/uwtools/tests/test_rocoto.py b/src/uwtools/tests/test_rocoto.py index 64dd977b9..66032dc64 100644 --- a/src/uwtools/tests/test_rocoto.py +++ b/src/uwtools/tests/test_rocoto.py @@ -3,12 +3,10 @@ Tests for uwtools.rocoto module. """ -from typing import List from unittest.mock import DEFAULT as D from unittest.mock import PropertyMock, patch -import pytest -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools import rocoto from uwtools.config.formats.yaml import YAMLConfig @@ -166,7 +164,7 @@ def test__add_task(self, instance, root): mocks["_add_task_dependency"].assert_called_once_with(task, "qux") mocks["_add_task_envar"].assert_called_once_with(task, "A", "apple") - @pytest.mark.parametrize("cores", [1, "1"]) + @mark.parametrize("cores", [1, "1"]) def test__add_task_cores_int_or_str(self, cores, instance, root): # Ensure that either int or str "cores" values are accepted. config = {"command": "c", "cores": cores, "walltime": "00:00:01"} @@ -181,7 +179,7 @@ def test__add_task_dependency_and(self, instance, root): assert and_.tag == "and" assert and_.xpath("or[1]/taskdep")[0].get("task") == "foo" - @pytest.mark.parametrize( + @mark.parametrize( "value", ["/some/file", {"cyclestr": {"value": "@Y@m@d@H", "attrs": {"offset": "06:00:00"}}}], ) @@ -208,7 +206,7 @@ def test__add_task_dependency_fail_bad_operand(self, instance, root): with raises(UWConfigError): instance._add_task_dependency(e=root, config=config) - @pytest.mark.parametrize( + @mark.parametrize( "tag_config", [("and", {"strneq": {"attrs": {"left": "&RUN_GSI;", "right": "YES"}}})], ) @@ -263,7 +261,7 @@ def test__add_task_dependency_streq(self, instance, root): assert streq.tag == "streq" assert streq.get("left") == "&RUN_GSI;" - @pytest.mark.parametrize( + @mark.parametrize( "config", [ ("streq", {"attrs": {"left": "&RUN_GSI;", "right": "YES"}}), @@ -296,7 +294,7 @@ def test__add_task_dependency_taskvalid(self, instance, root): assert taskvalid.tag == "taskvalid" assert taskvalid.get("task") == "foo" - @pytest.mark.parametrize( + @mark.parametrize( "value", [ "202301031200", @@ -380,7 +378,7 @@ def test__add_workflow(self, instance): mocks["_add_workflow_tasks"].assert_called_once_with(workflow, "5") def test__add_workflow_cycledef(self, instance, root): - config: List[dict] = [ + config: list[dict] = [ {"attrs": {"group": "g1"}, "spec": "t1"}, {"attrs": {"group": "g2"}, "spec": "t2"}, ] diff --git a/src/uwtools/tests/test_scheduler.py b/src/uwtools/tests/test_scheduler.py index a8da62c79..78c0bf760 100644 --- a/src/uwtools/tests/test_scheduler.py +++ b/src/uwtools/tests/test_scheduler.py @@ -6,7 +6,7 @@ Tests for uwtools.scheduler module. """ -from typing import Any, Dict +from typing import Any from unittest.mock import patch from pytest import fixture, raises @@ -54,7 +54,7 @@ def _directive_separator(self) -> str: return directive_separator @property - def _managed_directives(self) -> Dict[str, Any]: + def _managed_directives(self) -> dict[str, Any]: return managed_directives @property diff --git a/src/uwtools/tests/test_schemas.py b/src/uwtools/tests/test_schemas.py index a99e8d152..1c4bd27f6 100644 --- a/src/uwtools/tests/test_schemas.py +++ b/src/uwtools/tests/test_schemas.py @@ -5,8 +5,7 @@ from functools import partial -import pytest -from pytest import fixture +from pytest import fixture, mark from uwtools.tests.support import schema_validator, with_del, with_set @@ -242,7 +241,7 @@ def test_schema_esg_grid_namelist(esg_grid_prop, esg_namelist): assert "not valid" in errors({}) -@pytest.mark.parametrize("key", ["delx", "dely", "lx", "ly", "pazi", "plat", "plon"]) +@mark.parametrize("key", ["delx", "dely", "lx", "ly", "pazi", "plat", "plon"]) def test_schema_esg_grid_namelist_content(key): config: dict = { "regional_grid_nml": { @@ -639,7 +638,7 @@ def test_schema_global_equiv_resol(): assert "Additional properties are not allowed" in errors({**config, "foo": "bar"}) -@pytest.mark.parametrize("schema_entry", ["run_dir", "input_grid_file"]) +@mark.parametrize("schema_entry", ["run_dir", "input_grid_file"]) def test_schema_global_equiv_resol_paths(global_equiv_resol_prop, schema_entry): errors = global_equiv_resol_prop(schema_entry) # Must be a string: diff --git a/src/uwtools/tests/utils/test_api.py b/src/uwtools/tests/utils/test_api.py index 9f1e0ada8..a06c58b8a 100644 --- a/src/uwtools/tests/utils/test_api.py +++ b/src/uwtools/tests/utils/test_api.py @@ -4,8 +4,7 @@ from pathlib import Path from unittest.mock import patch -import pytest -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.exceptions import UWError from uwtools.tests.drivers import test_driver @@ -26,7 +25,7 @@ def execute_kwargs(): } -@pytest.mark.parametrize("val", [Path("/some/path"), {"foo": 88}]) +@mark.parametrize("val", [Path("/some/path"), {"foo": 88}]) def test_ensure_data_source_passthrough(val): assert api.ensure_data_source(data_source=val, stdin_ok=False) == val @@ -106,7 +105,7 @@ def test_make_tasks(): assert list(taskmap.keys()) == ["atask", "run", "runscript", "validate"] -@pytest.mark.parametrize("val", [Path("/some/path"), {"foo": 88}]) +@mark.parametrize("val", [Path("/some/path"), {"foo": 88}]) def test_str2path_passthrough(val): assert api.str2path(val) == val @@ -118,7 +117,7 @@ def test_str2path_convert(): assert result == Path(val) -@pytest.mark.parametrize("hours", [0, 24, 168]) +@mark.parametrize("hours", [0, 24, 168]) def test__execute(execute_kwargs, hours, tmp_path): graph_file = tmp_path / "g.dot" with patch.object(test_driver, "ConcreteDriver", wraps=test_driver.ConcreteDriver) as cd: diff --git a/src/uwtools/tests/utils/test_file.py b/src/uwtools/tests/utils/test_file.py index 560a50a4f..1151e5493 100644 --- a/src/uwtools/tests/utils/test_file.py +++ b/src/uwtools/tests/utils/test_file.py @@ -9,7 +9,7 @@ from pathlib import Path from unittest.mock import patch -from pytest import fixture, raises +from pytest import fixture, mark, raises from uwtools.exceptions import UWError from uwtools.utils import file @@ -58,8 +58,9 @@ def test__stdinproxy(): assert file._stdinproxy().read() == msg1 # <-- the NEW message -def test_get_file_format(): - for ext, file_type in { +@mark.parametrize( + "ext,file_type", + { "atparse": "atparse", "bash": "sh", "cfg": "ini", @@ -70,8 +71,10 @@ def test_get_file_format(): "sh": "sh", "yaml": "yaml", "yml": "yaml", - }.items(): - assert file.get_file_format(Path(f"a.{ext}")) == file_type + }.items(), +) +def test_get_file_format(ext, file_type): + assert file.get_file_format(Path(f"a.{ext}")) == file_type def test_get_file_format_unrecognized(): diff --git a/src/uwtools/utils/api.py b/src/uwtools/utils/api.py index 6f93898c1..d6d5f6109 100644 --- a/src/uwtools/utils/api.py +++ b/src/uwtools/utils/api.py @@ -5,7 +5,7 @@ import datetime as dt import re from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any, Callable, Optional, Union from uwtools.config.formats.base import Config from uwtools.drivers.driver import Driver @@ -51,7 +51,7 @@ def execute( # pylint: disable=unused-argument batch: bool = False, dry_run: bool = False, graph_file: Optional[Union[Path, str]] = None, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, stdin_ok: bool = False, ) -> bool: return _execute( @@ -74,7 +74,7 @@ def execute_cycle( # pylint: disable=unused-argument batch: bool = False, dry_run: bool = False, graph_file: Optional[Union[Path, str]] = None, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, stdin_ok: bool = False, ) -> bool: return _execute( @@ -98,7 +98,7 @@ def execute_cycle_leadtime( # pylint: disable=unused-argument batch: bool = False, dry_run: bool = False, graph_file: Optional[Union[Path, str]] = None, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, stdin_ok: bool = False, ) -> bool: return _execute( @@ -133,14 +133,14 @@ def execute_cycle_leadtime( # pylint: disable=unused-argument return execute -def make_tasks(driver_class: type[Driver]) -> Callable[..., Dict[str, str]]: +def make_tasks(driver_class: type[Driver]) -> Callable[..., dict[str, str]]: """ Returns a function that maps task names to descriptions for the given driver. :param driver_class: The driver class whose tasks and descriptions to map. """ - def tasks() -> Dict[str, str]: + def tasks() -> dict[str, str]: """ Returns a mapping from task names to their one-line descriptions. """ @@ -170,7 +170,7 @@ def _execute( batch: bool = False, dry_run: bool = False, graph_file: Optional[Union[Path, str]] = None, - key_path: Optional[List[str]] = None, + key_path: Optional[list[str]] = None, stdin_ok: bool = False, ) -> bool: """ diff --git a/src/uwtools/utils/processing.py b/src/uwtools/utils/processing.py index b354fd782..1408fe087 100644 --- a/src/uwtools/utils/processing.py +++ b/src/uwtools/utils/processing.py @@ -4,7 +4,7 @@ from pathlib import Path from subprocess import STDOUT, CalledProcessError, check_output -from typing import Dict, Optional, Tuple, Union +from typing import Optional, Union from uwtools.logging import INDENT, log @@ -12,9 +12,9 @@ def execute( cmd: str, cwd: Optional[Union[Path, str]] = None, - env: Optional[Dict[str, str]] = None, + env: Optional[dict[str, str]] = None, log_output: Optional[bool] = False, -) -> Tuple[bool, str]: +) -> tuple[bool, str]: """ Execute a command in a subshell.