Skip to content

Commit c261713

Browse files
authored
Merge pull request #768 from NOAA-GFDL/copilot/add-fre-version-to-model-yaml
Add fre_cli_version to model YAML and enforce version compatibility
2 parents 836559e + edbd167 commit c261713

File tree

4 files changed

+102
-3
lines changed

4 files changed

+102
-3
lines changed

docs/usage/yaml_dev/model_yaml.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
The model yaml defines reusable variables and paths to compile, post-processing, analysis, and cmor yamls. Required fields in the model yaml include: ``fre_properties``, ``build``, and ``experiments``.
22

3+
* `fre_cli_version`: Version of fre-cli this yaml is compatible with
4+
5+
- value type: string
6+
- when specified, fre-cli will verify that the installed version matches this value
7+
- if the versions do not match, a clear error message will be displayed
8+
- if not specified, a warning will be logged recommending its addition
9+
310
* `fre_properties`: Reusable variables
411

512
- list of variables
@@ -14,6 +21,8 @@ The model.yaml can follow the structure below:
1421

1522
.. code-block::
1623
24+
fre_cli_version: "2025.04"
25+
1726
fre_properties:
1827
- &variable1 "value1" (string)
1928
- &variable2 "value2" (string)

fre/yamltools/combine_yamls_script.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from fre.yamltools.info_parsers import compile_info_parser as cip
3838
from fre.yamltools.info_parsers import pp_info_parser as ppip
3939
from fre.yamltools.info_parsers import analysis_info_parser as aip
40-
from fre.yamltools.helpers import output_yaml
40+
from fre.yamltools.helpers import output_yaml, check_fre_version
4141

4242
from . import *
4343

@@ -161,6 +161,10 @@ def consolidate_yamls(yamlfile:str, experiment:str, platform:str,
161161
..note:: The output file name should include a .yaml extension to indicate
162162
it is a YAML configuration file
163163
"""
164+
# Check fre_cli_version compatibility before any YAML combining
165+
fre_logger.info('checking fre_cli_version compatibility...')
166+
check_fre_version(yamlfile)
167+
164168
if use == "compile":
165169
fre_logger.info('initializing a compile yaml instance...')
166170
compilecombined = cip.InitCompileYaml(yamlfile, platform, target)

fre/yamltools/helpers.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from . import *
1616

1717

18+
import fre
19+
1820
fre_logger = logging.getLogger(__name__)
1921

2022
def yaml_load(yamlfile):
@@ -33,6 +35,45 @@ def yaml_load(yamlfile):
3335
return y
3436

3537

38+
def check_fre_version(yamlfile: str) -> None:
39+
"""
40+
Check that the fre_cli_version specified in the model yaml matches the
41+
installed version of fre-cli. If fre_cli_version is not specified, a
42+
warning is logged. If fre_cli_version does not match, a ValueError is raised.
43+
44+
:param yamlfile: Path to model YAML configuration file
45+
:type yamlfile: str
46+
:raises ValueError: if the fre_cli_version in the YAML does not match the installed fre-cli version
47+
"""
48+
try:
49+
loaded_yaml = yaml_load(yamlfile)
50+
except Exception as exc:
51+
fre_logger.warning(
52+
"Could not load %s for fre_cli_version check: %s",
53+
yamlfile, exc
54+
)
55+
return
56+
57+
yaml_fre_version = loaded_yaml.get("fre_cli_version")
58+
installed_version = fre.version
59+
60+
if yaml_fre_version is None:
61+
fre_logger.info(
62+
"fre_cli_version not specified in %s. "
63+
"It is recommended to add 'fre_cli_version' to your model yaml "
64+
"to ensure compatibility with the correct version of fre-cli.",
65+
yamlfile
66+
)
67+
return
68+
69+
if str(yaml_fre_version) != str(installed_version):
70+
raise ValueError(
71+
f"The fre_cli_version specified in the model yaml ({yaml_fre_version}) "
72+
f"does not match the installed version of fre-cli ({installed_version}). "
73+
f"Please update your model yaml or install the correct version of fre-cli."
74+
)
75+
76+
3677
def output_yaml(cleaned_yaml, output):
3778
"""
3879
Write out the combined yaml dictionary info
@@ -131,7 +172,7 @@ def clean_yaml(yml_dict):
131172
"""
132173
# Clean the yaml
133174
# If keys exists, delete:
134-
keys_clean=["fre_properties", "experiments"]
175+
keys_clean=["fre_properties", "fre_cli_version", "experiments"]
135176
for kc in keys_clean:
136177
if kc in yml_dict.keys():
137178
del yml_dict[kc]

fre/yamltools/tests/test_helpers.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import pytest
55
import yaml
66

7-
from fre.yamltools.helpers import yaml_load
7+
import fre
8+
from fre.yamltools.helpers import yaml_load, check_fre_version
89

910

1011
@pytest.fixture
@@ -24,3 +25,47 @@ def test_yaml_load_reads_yaml_file_correctly(temp_path):
2425
def test_yaml_load_raises_file_not_found():
2526
with pytest.raises(FileNotFoundError):
2627
yaml_load("this_file_should_not_exist.yml")
28+
29+
## fre_cli_version checks
30+
@pytest.fixture
31+
def yaml_with_matching_version(tmp_path):
32+
"""Create a YAML file with the correct fre_cli_version."""
33+
data = {'fre_cli_version': fre.version, 'fre_properties': []}
34+
path = tmp_path / "matching_version.yaml"
35+
with open(path, 'w') as f:
36+
yaml.dump(data, f)
37+
return str(path)
38+
39+
@pytest.fixture
40+
def yaml_with_wrong_version(tmp_path):
41+
"""Create a YAML file with an incorrect fre_cli_version."""
42+
data = {'fre_cli_version': '0000.00', 'fre_properties': []}
43+
path = tmp_path / "wrong_version.yaml"
44+
with open(path, 'w') as f:
45+
yaml.dump(data, f)
46+
return str(path)
47+
48+
@pytest.fixture
49+
def yaml_without_version(tmp_path):
50+
"""Create a YAML file without fre_cli_version."""
51+
data = {'fre_properties': []}
52+
path = tmp_path / "no_version.yaml"
53+
with open(path, 'w') as f:
54+
yaml.dump(data, f)
55+
return str(path)
56+
57+
def test_check_fre_version_matching(yaml_with_matching_version):
58+
"""check_fre_version should pass when fre_cli_version matches installed version."""
59+
check_fre_version(yaml_with_matching_version)
60+
61+
def test_check_fre_version_mismatch(yaml_with_wrong_version):
62+
"""check_fre_version should raise ValueError when fre_cli_version does not match."""
63+
with pytest.raises(ValueError, match="does not match the installed version"):
64+
check_fre_version(yaml_with_wrong_version)
65+
66+
def test_check_fre_version_missing(yaml_without_version, caplog):
67+
"""check_fre_version should log info but not error when fre_cli_version is missing."""
68+
import logging
69+
with caplog.at_level(logging.INFO):
70+
check_fre_version(yaml_without_version)
71+
assert "fre_cli_version not specified" in caplog.text

0 commit comments

Comments
 (0)