From fdf2bc88a034c998576a97a49253a516d6712d2b Mon Sep 17 00:00:00 2001 From: MartinBelthle Date: Wed, 25 Sep 2024 17:51:59 +0200 Subject: [PATCH] release-1.0.6 (#18) --- docs/CHANGELOG.md | 6 ++++++ docs/usage.md | 14 ++++++------ src/antares/study/version/__about__.py | 2 +- .../study/version/model/study_antares.py | 2 +- .../study/version/model/study_version.py | 20 +++++++++--------- .../version/upgrade_app/upgrader_0806.py | 2 +- tests/create_app/test_create_app.py | 2 +- tests/test_cli.py | 2 +- tests/test_model.py | 18 ++++++++-------- tests/upgrade_app/test_upgrade_0806.py | 8 +++++++ .../little_study_0805.expected.zip | Bin 114653 -> 114589 bytes 11 files changed, 45 insertions(+), 31 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f6b07e5..f2d036d 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog ========= +v1.0.6 (2024-09-25) +------------------- + +- revert change in release v1.0.5 as it was a mistake +- v8.6 update puts field `enable-first-step` at False instead of True + v1.0.5 (2024-09-25) ------------------- diff --git a/docs/usage.md b/docs/usage.md index e8af5a8..a185a98 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -15,7 +15,7 @@ Using the `antares-study-version` module is straightforward: from antares.study.version import StudyVersion version = StudyVersion(8, 7, 2) # patch component is not used -print(version) # 870 +print(version) # 8.7 ``` You can also create a version object from a dotted string: @@ -24,7 +24,7 @@ You can also create a version object from a dotted string: from antares.study.version import StudyVersion version = StudyVersion.parse("8.7") -print(version) # 870 +print(version) # 8.7 ``` You can create a version object from a compact string: @@ -33,7 +33,7 @@ You can create a version object from a compact string: from antares.study.version import StudyVersion version = StudyVersion.parse("870") -print(version) # 870 +print(version) # 8.7 ``` You can create a version object from an integer: @@ -42,7 +42,7 @@ You can create a version object from an integer: from antares.study.version import StudyVersion version = StudyVersion.parse(870) -print(version) # 870 +print(version) # 8.7 ``` You can compare versions: @@ -61,10 +61,10 @@ You can convert a version to string using format specifiers: from antares.study.version import StudyVersion version = StudyVersion(8, 7) -print(f"{version}d.d.d") # 8.7 +print(version) # 8.7 print(f"{version:02d}") # 08.07 print(f"{version:03d}") # 08.07.00 -print(version) # 870 +print(f"{version:ddd}") # 870 ``` You can convert a version to an integer: @@ -84,7 +84,7 @@ Of course, the same operations can be done with `SolverVersion` objects, but wit from antares.study.version import SolverVersion version = SolverVersion(8, 7, 2) -print(version) # 872 +print(version) # 8.7.2 ``` Objects of the `StudyVersion` and `SolverVersion` classes can be compared to each other: diff --git a/src/antares/study/version/__about__.py b/src/antares/study/version/__about__.py index 5c784b9..e5ffec4 100644 --- a/src/antares/study/version/__about__.py +++ b/src/antares/study/version/__about__.py @@ -4,7 +4,7 @@ # Standard project metadata -__version__ = "1.0.5" +__version__ = "1.0.6" __author__ = "RTE, Antares Web Team" __date__ = "2024-07-06" __credits__ = "© Réseau de Transport de l’Électricité (RTE)" diff --git a/src/antares/study/version/model/study_antares.py b/src/antares/study/version/model/study_antares.py index 7c3bd58..d59297c 100644 --- a/src/antares/study/version/model/study_antares.py +++ b/src/antares/study/version/model/study_antares.py @@ -168,7 +168,7 @@ def to_ini_file(self, study_dir: t.Union[str, Path], update_save_date: bool = Tr if self.version < DOTTED_VERSION: # type: ignore # Old versions of Antares Studies used a different format for the version number - section_dict["version"] = f"{self.version:}" + section_dict["version"] = f"{self.version:ddd}" else: section_dict["version"] = f"{self.version.major}.{self.version.minor}" diff --git a/src/antares/study/version/model/study_version.py b/src/antares/study/version/model/study_version.py index 6b65af6..7407f2d 100644 --- a/src/antares/study/version/model/study_version.py +++ b/src/antares/study/version/model/study_version.py @@ -32,7 +32,12 @@ def parse(cls: t.Type[T], other: object) -> T: # Conversion methods def __str__(self) -> str: - return f"{int(self):03d}" + if self.patch: + return f"{self.major}.{self.minor}.{self.patch}" + elif self.minor: + return f"{self.major}.{self.minor}" + else: + return f"{self.major}" def __int__(self) -> int: return self.major * 100 + self.minor * 10 + self.patch @@ -90,12 +95,12 @@ def __format__(self, format_spec: str) -> str: """ Format the version number "X.Y.Z" according to the format specifier: - - "" => "XYZ" + - "" => "X.Y.Z" or "X.Y" if patch is 0, or "X" if minor is 0. - "1d" => "X", - "2d" => "X.Y", - "01d" => "0X", - "02d" => "0X.0Y" - - "d.d.d" => "X.Y.Z" or "X.Y" if patch is 0, or "X" if minor is 0. + - "d.d.d" => "XYZ" :param format_spec: format specifier. @@ -116,13 +121,8 @@ def __format__(self, format_spec: str) -> str: return f"{major:02d}.{minor:02d}" elif format_spec == "03d": return f"{major:02d}.{minor:02d}.{patch:02d}" - elif format_spec == "d.d.d": - if self.patch: - return f"{self.major}.{self.minor}.{self.patch}" - elif self.minor: - return f"{self.major}.{self.minor}" - else: - return f"{self.major}" + elif format_spec == "ddd": + return f"{int(self):03d}" else: raise ValueError(f"Invalid format specifier: '{format_spec}'") diff --git a/src/antares/study/version/upgrade_app/upgrader_0806.py b/src/antares/study/version/upgrade_app/upgrader_0806.py index 6547160..6420d39 100644 --- a/src/antares/study/version/upgrade_app/upgrader_0806.py +++ b/src/antares/study/version/upgrade_app/upgrader_0806.py @@ -26,7 +26,7 @@ def upgrade(cls, study_dir: Path) -> None: study_dir: The study directory. """ data = GeneralData.from_ini_file(study_dir) - data["adequacy patch"]["enable-first-step "] = True + data["adequacy patch"]["enable-first-step"] = False data.to_ini_file(study_dir) study_dir.joinpath("input", "st-storage", "clusters").mkdir(parents=True, exist_ok=True) diff --git a/tests/create_app/test_create_app.py b/tests/create_app/test_create_app.py index 345d78d..8aee8c6 100644 --- a/tests/create_app/test_create_app.py +++ b/tests/create_app/test_create_app.py @@ -18,7 +18,7 @@ def test_no_template_available(self, tmp_path: Path): study_dir = tmp_path.joinpath("my-new-study") study_version = StudyVersion.parse("2.8") app = CreateApp(study_dir=study_dir, caption="My New App", version=study_version, author="Robert Smith") - with pytest.raises(ApplicationError, match=re.escape(str(study_version))): + with pytest.raises(ApplicationError, match=re.escape(f"{study_version:2d}")): app() @pytest.mark.parametrize("study_version", AVAILABLE_VERSIONS) diff --git a/tests/test_cli.py b/tests/test_cli.py index a5c2507..66e434d 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -53,7 +53,7 @@ def test_cli__create(self, tmp_path: Path, study_version: StudyVersion) -> None: parser.read(study_antares_file, encoding="utf-8") section_dict = dict(parser["antares"]) if study_version < 900: - expected_version = f"{study_version:}" + expected_version = f"{study_version:ddd}" else: expected_version = f"{study_version.major}.{study_version.minor}" diff --git a/tests/test_model.py b/tests/test_model.py index 9add781..659a102 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -34,7 +34,7 @@ def test___str__(self) -> None: version = SolverVersion(4, 5, 6) result = version.__str__() assert isinstance(result, str) - assert result == "456" + assert result == "4.5.6" def test___int__(self) -> None: version = SolverVersion(4, 5, 6) @@ -51,14 +51,14 @@ def test_parse(self) -> None: @pytest.mark.parametrize( "format_spec, expected", [ - pytest.param("d.d.d", "4.5.6", id="empty"), + pytest.param("", "4.5.6", id="empty"), pytest.param("1d", "4", id="format-1d"), pytest.param("2d", "4.5", id="format-2d"), pytest.param("3d", "4.5.6", id="format-3d"), pytest.param("01d", "04", id="format-01d"), pytest.param("02d", "04.05", id="format-02d"), pytest.param("03d", "04.05.06", id="format-03d"), - pytest.param("", "456", id="format-ddd"), + pytest.param("ddd", "456", id="format-ddd"), pytest.param("X", "", marks=pytest.mark.xfail(raises=ValueError, strict=True)), ], ) @@ -93,7 +93,7 @@ def test___str__(self) -> None: version = StudyVersion(4, 5) result = version.__str__() assert isinstance(result, str) - assert result == "450" + assert result == "4.5" def test___int__(self) -> None: version = StudyVersion(4, 5) @@ -109,14 +109,14 @@ def test_parse(self) -> None: @pytest.mark.parametrize( "format_spec, expected", [ - pytest.param("d.d.d", "4.5", id="empty"), + pytest.param("", "4.5", id="empty"), pytest.param("1d", "4", id="format-1d"), pytest.param("2d", "4.5", id="format-2d"), pytest.param("3d", "4.5.0", id="format-3d"), pytest.param("01d", "04", id="format-01d"), pytest.param("02d", "04.05", id="format-02d"), pytest.param("03d", "04.05.00", id="format-03d"), - pytest.param("", "450", id="format-ddd"), + pytest.param("ddd", "450", id="format-ddd"), pytest.param("X", "", marks=pytest.mark.xfail(raises=ValueError, strict=True)), ], ) @@ -154,7 +154,7 @@ def test_ini_file(self): # We can write the study version to an INI file version = StudyVersion(10, 2) - config["antares"]["version"] = version.__format__("d.d.d") + config["antares"]["version"] = str(version) stream.seek(0) config.write(stream) @@ -252,7 +252,7 @@ def test_version_str(self): assert version == StudyVersion(8, 7) # We can convert a version number to a string - assert str(version) == "870" + assert str(version) == "8.7" # We can compare string versions using the standard comparison operators assert version == "8.7" @@ -272,7 +272,7 @@ def test_version_str(self): # We can construct a version number from a string with a single number version = StudyVersion.parse("8") assert version == StudyVersion(8) - assert str(version) == "800" + assert str(version) == "8" # noinspection PyDataclass,PyTypeChecker def test_version_triplet(self): diff --git a/tests/upgrade_app/test_upgrade_0806.py b/tests/upgrade_app/test_upgrade_0806.py index 87e0bb9..9bced8d 100644 --- a/tests/upgrade_app/test_upgrade_0806.py +++ b/tests/upgrade_app/test_upgrade_0806.py @@ -1,3 +1,4 @@ +from antares.study.version.ini_reader import IniReader from antares.study.version.upgrade_app.upgrader_0806 import UpgradeTo0806 from tests.conftest import StudyAssets from tests.helpers import are_same_dir @@ -15,3 +16,10 @@ def test_nominal_case(study_assets: StudyAssets): actual_input_path = study_assets.study_dir.joinpath("input") expected_input_path = study_assets.expected_dir.joinpath("input") assert are_same_dir(actual_input_path, expected_input_path) + + # compare generaldata.ini + actual_path = study_assets.study_dir.joinpath("settings/generaldata.ini") + actual = IniReader().read(actual_path) + expected_path = study_assets.expected_dir.joinpath("settings/generaldata.ini") + expected = IniReader().read(expected_path) + assert actual == expected diff --git a/tests/upgrade_app/upgrade_0806/nominal_case/little_study_0805.expected.zip b/tests/upgrade_app/upgrade_0806/nominal_case/little_study_0805.expected.zip index 4cb3dc548d41c1b56e8e7c4b67cf156c16e5e51e..d9a807441c21835ebf318f81fea75a1063b22430 100644 GIT binary patch delta 6088 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