From 3b74663c9f7bc87a5c507d8a7a2f6d65aa0a951c Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 12:35:50 +0200 Subject: [PATCH 01/17] Expose operator_specification_set_changelog in LegacyGrpc --- src/ansys/dpf/gate/operator_specification_grpcapi.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ansys/dpf/gate/operator_specification_grpcapi.py b/src/ansys/dpf/gate/operator_specification_grpcapi.py index e21a8fa2d87..23f84b8eaba 100644 --- a/src/ansys/dpf/gate/operator_specification_grpcapi.py +++ b/src/ansys/dpf/gate/operator_specification_grpcapi.py @@ -169,3 +169,7 @@ def operator_specification_get_config_printable_default_value(specification, num def operator_specification_get_config_description(specification, numOption): option = specification._internal_obj.config_spec.config_options_spec[numOption] return option.document + + @staticmethod + def operator_specification_set_changelog(specification, changelog): + specification._internal_obj.changelog = changelog From b074ff9060f2f80c0f8e5d3468411923f53ecfda Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 12:38:16 +0200 Subject: [PATCH 02/17] Add Operator.changelog property --- src/ansys/dpf/core/dpf_operator.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index 5ccc4192445..a2a91afa2ce 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -31,6 +31,7 @@ import numpy from ansys.dpf.core import server as server_module +from ansys.dpf.core.changelog import Changelog from ansys.dpf.core.check_version import ( server_meet_version, server_meet_version_and_raise, @@ -931,6 +932,19 @@ def specification(self): else: return Specification(operator_name=self.name, server=self._server) + @property + def changelog(self) -> Changelog: + """Return the changelog of this operator. + + Returns + ------- + changelog: + Changelog of the operator. + """ + from ansys.dpf.core.operators.utility.operator_changelog import operator_changelog + + return Changelog(operator_changelog(operator_name=self.name, server=self._server).eval()) + def __truediv__(self, inpt): """ Perform division with another operator or a scalar. From dc91503819a0f161a52eaead2c7516e1292b5783 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 12:56:49 +0200 Subject: [PATCH 03/17] Add Operator.version property --- src/ansys/dpf/core/dpf_operator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index a2a91afa2ce..557ca473bfc 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -29,6 +29,7 @@ import warnings import numpy +from packaging.version import Version from ansys.dpf.core import server as server_module from ansys.dpf.core.changelog import Changelog @@ -945,6 +946,11 @@ def changelog(self) -> Changelog: return Changelog(operator_changelog(operator_name=self.name, server=self._server).eval()) + @property + def version(self) -> Version: + """Return the current version of the operator.""" + return self.changelog.last_version + def __truediv__(self, inpt): """ Perform division with another operator or a scalar. From e2d8bf80ab582b5db401821d7732cbe71f75129c Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 15:46:01 +0200 Subject: [PATCH 04/17] Add changelog.py --- src/ansys/dpf/core/changelog.py | 189 ++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/ansys/dpf/core/changelog.py diff --git a/src/ansys/dpf/core/changelog.py b/src/ansys/dpf/core/changelog.py new file mode 100644 index 00000000000..c9f89a8ed24 --- /dev/null +++ b/src/ansys/dpf/core/changelog.py @@ -0,0 +1,189 @@ +# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +"""Provides classes for changelogs.""" + +from __future__ import annotations + +from packaging.version import Version + +import ansys.dpf.core as dpf + + +class Changelog: + """Changelog of an operator. + + Parameters + ---------- + gdc: + An optional GenericDataContainer to initialize the changelog with. + server: + The server to create the changelog on. Defaults to the current global server. + """ + + def __init__(self, gdc: dpf.GenericDataContainer = None, server=None): + if gdc is None: + gdc = dpf.GenericDataContainer(server=server) + versions_sf = dpf.StringField(server=server) + versions_sf.append(data=["0.0.0"], scopingid=1) + changes_sf = dpf.StringField(server=server) + changes_sf.append(data=["Initial version."], scopingid=1) + gdc.set_property(property_name="versions", prop=versions_sf) + gdc.set_property(property_name="changes", prop=changes_sf) + gdc.set_property(property_name="class", prop="Changelog") + self.gdc = gdc + + def append(self, version: Version, changes: str): + """Append a version and associated changes description to the changelog.""" + versions_sf: dpf.StringField = self.gdc.get_property( + property_name="versions", output_type=dpf.StringField + ) + new_id = versions_sf.scoping.size + 1 + versions_sf.append(data=[str(version)], scopingid=new_id) + changes_sf: dpf.StringField = self.gdc.get_property( + property_name="changes", output_type=dpf.StringField + ) + changes_sf.append(data=[changes], scopingid=new_id) + + def patch_bump(self, changes: str) -> Changelog: + """Bump the patch of the current version with associated changes description. + + Parameters + ---------- + changes: + Description of the changes associated to the patch bump. + + Returns + ------- + changelog: + Returns the current changelog to allow for chaining calls to bumps. + """ + current_version = self.last_version + new_version = Version( + f"{current_version.major}.{current_version.minor}.{current_version.micro+1}" + ) + self.append(version=new_version, changes=changes) + return self + + def minor_bump(self, changes: str) -> Changelog: + """Bump the minor of the current version with associated changes description. + + Parameters + ---------- + changes: + Description of the changes associated to the minor bump. + + Returns + ------- + changelog: + Returns the current changelog to allow for chaining calls to bumps. + """ + current_version = self.last_version + new_version = Version(f"{current_version.major}.{current_version.minor+1}.0") + self.append(version=new_version, changes=changes) + return self + + def major_bump(self, changes: str) -> Changelog: + """Bump the major of the current version with associated changes description. + + Parameters + ---------- + changes: + Description of the changes associated to the major bump. + + Returns + ------- + changelog: + Returns the current changelog to allow for chaining calls to bumps. + """ + current_version = self.last_version + new_version = Version(f"{current_version.major+1}.0.0") + self.append(version=new_version, changes=changes) + return self + + def expect_version(self, version: Version) -> Changelog: + """Check the current latest version of the changelog. + + Useful when chaining version bumps to check the resulting version is as expected. + Adds readability to the specification of the operator. + + Parameters + ---------- + version: + Expected current latest version of the changelog. + + Returns + ------- + changelog: + Returns the current changelog to allow for chaining calls to bumps. + """ + if self.last_version != version: + raise ValueError( + f"Last version in the changelog ({self.last_version}) does not match expected version ({version})." + ) + return self + + @property + def last_version(self) -> Version: + """Highest version in the changelog. + + Returns + ------- + version: + Highest version in the changelog. + """ + return self.versions[-1] + + @property + def versions(self) -> [Version]: + """List of all versions for which the changelog stores descriptions.""" + versions_sf: dpf.StringField = self.gdc.get_property( + property_name="versions", output_type=dpf.StringField + ) + return [Version(version) for version in versions_sf.data_as_list] + + def __getitem__(self, item: Version) -> str: + """Return changes description for a specific version in the changelog.""" + return self.changes_for_version(item) + + def changes_for_version(self, version: Version) -> str: + """Return changes description for a specific version in the changelog.""" + versions_sf: dpf.StringField = self.gdc.get_property( + property_name="versions", output_type=dpf.StringField + ) + changes_sf: dpf.StringField = self.gdc.get_property( + property_name="changes", output_type=dpf.StringField + ) + versions_list = versions_sf.data_as_list + for i in range(len(versions_sf.scoping.ids)): + if Version(versions_list[i]) == version: + return changes_sf.get_entity_data_by_id(versions_sf.scoping.ids[i])[0] + raise ValueError(f"Changelog has no version '{version}'.") + + def __str__(self): + """Create string representation of the changelog.""" + string = "Changelog:\n" + string += "Version Changes\n" + string += "------- -------\n" + for version in self.versions: + string += f"{str(version): <15}" + self[version].replace("\n", f"\n{'': >15}") + "\n" + return string From 4cc8c6778cdf38844449e4565d601434fa0527af Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 15:46:57 +0200 Subject: [PATCH 05/17] Add test_changelog.py --- tests/test_changelog.py | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/test_changelog.py diff --git a/tests/test_changelog.py b/tests/test_changelog.py new file mode 100644 index 00000000000..a858ab11a40 --- /dev/null +++ b/tests/test_changelog.py @@ -0,0 +1,62 @@ +# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import pytest +from trame_common.decorators.klass import change + +from ansys.dpf.core.changelog import Changelog +import conftest + + +@conftest.raises_for_servers_version_under("11.0") +def test_changelog_new(server_type): + from packaging.version import Version + + changelog = Changelog(server=server_type) + assert changelog.last_version == Version("0.0.0") + assert changelog[changelog.last_version] == "Initial version." + print(changelog) + + +@conftest.raises_for_servers_version_under("11.0") +def test_changelog_updates(server_type): + from packaging.version import Version + + changelog = Changelog(server=server_type) + changelog.major_bump("Major bump").minor_bump("Minor bump").patch_bump("Patch \nbump") + with pytest.raises(ValueError): + changelog.expect_version(Version("0.0.0")) + changelog.expect_version(Version("1.1.1")) + assert changelog[changelog.last_version] == "Patch \nbump" + changelog.patch_bump("Patch 2") + assert ( + str(changelog) + == """Changelog: +Version Changes +------- ------- +0.0.0 Initial version. +1.0.0 Major bump +1.1.0 Minor bump +1.1.1 Patch + bump +1.1.2 Patch 2 +""" + ) From c63fbaa98cd352725c2e03843d57e7213ae4d730 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 15:47:59 +0200 Subject: [PATCH 06/17] Add test for operator.changelog and operator.version in test_operator.py --- tests/test_operator.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_operator.py b/tests/test_operator.py index aa254744144..1a6b28d0884 100644 --- a/tests/test_operator.py +++ b/tests/test_operator.py @@ -1533,3 +1533,18 @@ def test_operator_find_outputs_corresponding_pins_any(server_type): f1 = ops.utility.forward() f2 = ops.utility.forward() f2.inputs.any.connect(f1.outputs.any) + + +@conftest.raises_for_servers_version_under("11.0") +def test_operator_changelog(server_type): + from packaging.version import Version + + changelog = dpf.core.operators.math.add(server=server_type).changelog + assert changelog[Version("0.0.0")] == "New" + + +@conftest.raises_for_servers_version_under("11.0") +def test_operator_version(server_type): + from packaging.version import Version + + assert isinstance(dpf.core.operators.math.add(server=server_type).version, Version) From d1960d0c1226eb89fd00df23d664b133c959c65d Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 16:14:04 +0200 Subject: [PATCH 07/17] Mark as requiring DPF 11.0 --- src/ansys/dpf/core/changelog.py | 4 ++++ src/ansys/dpf/core/dpf_operator.py | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ansys/dpf/core/changelog.py b/src/ansys/dpf/core/changelog.py index c9f89a8ed24..ae7b18e7027 100644 --- a/src/ansys/dpf/core/changelog.py +++ b/src/ansys/dpf/core/changelog.py @@ -27,11 +27,15 @@ from packaging.version import Version import ansys.dpf.core as dpf +from ansys.dpf.core.check_version import version_requires +@version_requires("11.0") class Changelog: """Changelog of an operator. + Requires DPF 11.0 (2026 R1) or above. + Parameters ---------- gdc: diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index 557ca473bfc..d659b50c61a 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -933,10 +933,13 @@ def specification(self): else: return Specification(operator_name=self.name, server=self._server) + @version_requires("11.0") @property def changelog(self) -> Changelog: """Return the changelog of this operator. + Requires DPF 11.0 (2026 R1) or above. + Returns ------- changelog: @@ -946,9 +949,14 @@ def changelog(self) -> Changelog: return Changelog(operator_changelog(operator_name=self.name, server=self._server).eval()) + @version_requires("11.0") @property def version(self) -> Version: - """Return the current version of the operator.""" + """Return the current version of the operator. + + Requires DPF 11.0 (2026 R1) or above. + + """ return self.changelog.last_version def __truediv__(self, inpt): From 4c5a2b18f644f61f915836f8e7a85e95b0c822d2 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 17:38:06 +0200 Subject: [PATCH 08/17] Expose changelog in custom operators --- src/ansys/dpf/core/changelog.py | 6 +- src/ansys/dpf/core/custom_operator.py | 39 ++++++++++++ src/ansys/dpf/core/dpf_operator.py | 9 ++- src/ansys/dpf/core/operator_specification.py | 10 +++ tests/test_python_plugins.py | 20 ++++++ .../pythonPlugins/operator_with_changelog.py | 63 +++++++++++++++++++ 6 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 tests/testfiles/pythonPlugins/operator_with_changelog.py diff --git a/src/ansys/dpf/core/changelog.py b/src/ansys/dpf/core/changelog.py index ae7b18e7027..101a68eb525 100644 --- a/src/ansys/dpf/core/changelog.py +++ b/src/ansys/dpf/core/changelog.py @@ -30,7 +30,6 @@ from ansys.dpf.core.check_version import version_requires -@version_requires("11.0") class Changelog: """Changelog of an operator. @@ -55,6 +54,7 @@ def __init__(self, gdc: dpf.GenericDataContainer = None, server=None): gdc.set_property(property_name="changes", prop=changes_sf) gdc.set_property(property_name="class", prop="Changelog") self.gdc = gdc + self._server = server def append(self, version: Version, changes: str): """Append a version and associated changes description to the changelog.""" @@ -124,7 +124,7 @@ def major_bump(self, changes: str) -> Changelog: self.append(version=new_version, changes=changes) return self - def expect_version(self, version: Version) -> Changelog: + def expect_version(self, version: Version | str) -> Changelog: """Check the current latest version of the changelog. Useful when chaining version bumps to check the resulting version is as expected. @@ -140,6 +140,8 @@ def expect_version(self, version: Version) -> Changelog: changelog: Returns the current changelog to allow for chaining calls to bumps. """ + if isinstance(version, str): + version = Version(version) if self.last_version != version: raise ValueError( f"Last version in the changelog ({self.last_version}) does not match expected version ({version})." diff --git a/src/ansys/dpf/core/custom_operator.py b/src/ansys/dpf/core/custom_operator.py index 3b6f9e3dc02..6142c9da0d2 100644 --- a/src/ansys/dpf/core/custom_operator.py +++ b/src/ansys/dpf/core/custom_operator.py @@ -37,6 +37,7 @@ import zipfile import numpy +from packaging.version import Version from ansys.dpf import core as dpf from ansys.dpf.core import ( @@ -55,6 +56,8 @@ external_operator_api, functions_registry, ) +from ansys.dpf.core.changelog import Changelog +from ansys.dpf.core.check_version import version_requires from ansys.dpf.gate import capi, dpf_vector, integral_types, object_handler @@ -400,3 +403,39 @@ def name(self) -> str: This name can then be used to instantiate the Operator. """ pass + + @property + @version_requires("11.0") + def changelog(self) -> Changelog: + """Return the changelog of this operator. + + Requires DPF 11.0 (2026 R1) or above. + + Returns + ------- + changelog: + Changelog of the operator. + """ + from ansys.dpf.core.operators.utility.operator_changelog import operator_changelog + + return Changelog(operator_changelog(operator_name=self.name).eval()) + + @changelog.setter + @version_requires("11.0") + def changelog(self, changelog: Changelog): + """Set the changelog of this operator. + + Requires DPF 11.0 (2026 R1) or above. + + """ + self.specification.set_changelog(changelog) + + @property + @version_requires("11.0") + def version(self) -> Version: + """Return the current version of the operator based on its changelog. + + Requires DPF 11.0 (2026 R1) or above. + + """ + return self.changelog.last_version diff --git a/src/ansys/dpf/core/dpf_operator.py b/src/ansys/dpf/core/dpf_operator.py index d659b50c61a..b621db50d6c 100644 --- a/src/ansys/dpf/core/dpf_operator.py +++ b/src/ansys/dpf/core/dpf_operator.py @@ -933,8 +933,8 @@ def specification(self): else: return Specification(operator_name=self.name, server=self._server) - @version_requires("11.0") @property + @version_requires("11.0") def changelog(self) -> Changelog: """Return the changelog of this operator. @@ -947,10 +947,13 @@ def changelog(self) -> Changelog: """ from ansys.dpf.core.operators.utility.operator_changelog import operator_changelog - return Changelog(operator_changelog(operator_name=self.name, server=self._server).eval()) + return Changelog( + gdc=operator_changelog(operator_name=self.name, server=self._server).eval(), + server=self._server, + ) - @version_requires("11.0") @property + @version_requires("11.0") def version(self) -> Version: """Return the current version of the operator. diff --git a/src/ansys/dpf/core/operator_specification.py b/src/ansys/dpf/core/operator_specification.py index 1bbe5aaaad6..1eab43bde28 100644 --- a/src/ansys/dpf/core/operator_specification.py +++ b/src/ansys/dpf/core/operator_specification.py @@ -32,6 +32,7 @@ from typing import Union from ansys.dpf.core import common, mapping_types, server as server_module +from ansys.dpf.core.changelog import Changelog from ansys.dpf.core.check_version import server_meet_version, version_requires from ansys.dpf.gate import ( integral_types, @@ -497,6 +498,15 @@ def config_specification(self) -> ConfigSpecification: ) return self._config_specification + @version_requires("11.0") + def set_changelog(self, changelog: Changelog): + """Set the changelog for this operator specification. + + Requires DPF 11.0 (2026 R1) or above. + + """ + self._api.operator_specification_set_changelog(self, changelog.gdc) + class CustomConfigOptionSpec(ConfigOptionSpec): """Custom documentation of a configuration option available for a given operator.""" diff --git a/tests/test_python_plugins.py b/tests/test_python_plugins.py index a13892612f4..04c97cc4290 100644 --- a/tests/test_python_plugins.py +++ b/tests/test_python_plugins.py @@ -28,6 +28,7 @@ import pytest from ansys.dpf import core as dpf +from ansys.dpf.core.changelog import Changelog from ansys.dpf.core.custom_operator import update_virtual_environment_for_custom_operators from ansys.dpf.core.errors import DPFServerException from ansys.dpf.core.operator_specification import ( @@ -408,3 +409,22 @@ def test_custom_op_with_spec(server_type_remote_process, testfiles_dir): outf = op.outputs.field() expected = np.ones((3, 3), dtype=np.float64) + 4.0 assert np.allclose(outf.data, expected) + + +@conftest.raises_for_servers_version_under("11.0") +def test_custom_op_changelog(server_type_remote_process, testfiles_dir): + from packaging.version import Version + + dpf.load_library( + dpf.path_utilities.to_server_os( + Path(testfiles_dir) / "pythonPlugins", server_type_remote_process + ), + "py_operator_with_changelog", + "load_operators", + server=server_type_remote_process, + ) + op = dpf.Operator("custom_add_to_field", server=server_type_remote_process) + changelog = op.changelog + assert isinstance(changelog, Changelog) + assert changelog.last_version == Version("1.0.0") + assert changelog[Version("1.0.0")] == "Major bump" diff --git a/tests/testfiles/pythonPlugins/operator_with_changelog.py b/tests/testfiles/pythonPlugins/operator_with_changelog.py new file mode 100644 index 00000000000..4e50d55a7a2 --- /dev/null +++ b/tests/testfiles/pythonPlugins/operator_with_changelog.py @@ -0,0 +1,63 @@ +# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from ansys.dpf.core import Field +from ansys.dpf.core.changelog import Changelog +from ansys.dpf.core.custom_operator import CustomOperatorBase, record_operator +from ansys.dpf.core.operator_specification import ( + CustomSpecification, + PinSpecification, + SpecificationProperties, +) + + +class AddFloatToFieldData(CustomOperatorBase): + def run(self): + field = self.get_input(0, Field) + to_add = self.get_input(1, float) + data = field.data + data += to_add + self.set_output(0, field) + self.set_succeeded() + + @property + def specification(self): + spec = CustomSpecification() + spec.description = "Add a custom value to all the data of an input Field" + spec.inputs = { + 0: PinSpecification("field", [Field], "Field on which float value is added."), + 1: PinSpecification("to_add", [float], "Data to add."), + } + spec.outputs = { + 0: PinSpecification("field", [Field], "Field on which the float value is added.") + } + spec.properties = SpecificationProperties("custom add to field", "math") + spec.set_changelog(changelog=Changelog().major_bump("Major bump").expect_version("1.0.0")) + return spec + + @property + def name(self): + return "custom_add_to_field" + + +def load_operators(*args): + record_operator(AddFloatToFieldData, *args) From 77d17d648634892575be107960cd77192a6290eb Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 17:52:30 +0200 Subject: [PATCH 09/17] Cover CustomOperator.version in tests --- tests/test_python_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_python_plugins.py b/tests/test_python_plugins.py index 04c97cc4290..eb4a4b36f26 100644 --- a/tests/test_python_plugins.py +++ b/tests/test_python_plugins.py @@ -428,3 +428,4 @@ def test_custom_op_changelog(server_type_remote_process, testfiles_dir): assert isinstance(changelog, Changelog) assert changelog.last_version == Version("1.0.0") assert changelog[Version("1.0.0")] == "Major bump" + assert op.version == Version("1.0.0") From b8d425ee0b4822d85435759c023be7a094716900 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 17:57:02 +0200 Subject: [PATCH 10/17] Remove wrong import --- tests/test_changelog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_changelog.py b/tests/test_changelog.py index a858ab11a40..95cf98e4d95 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -20,7 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. import pytest -from trame_common.decorators.klass import change from ansys.dpf.core.changelog import Changelog import conftest From 99d5fc4b353df24d29111351cdfbfabb39bd1a26 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 18:22:00 +0200 Subject: [PATCH 11/17] Require DPF>7.0 for testing with GDC --- tests/test_python_plugins.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_python_plugins.py b/tests/test_python_plugins.py index eb4a4b36f26..0ef60a41ffa 100644 --- a/tests/test_python_plugins.py +++ b/tests/test_python_plugins.py @@ -411,6 +411,9 @@ def test_custom_op_with_spec(server_type_remote_process, testfiles_dir): assert np.allclose(outf.data, expected) +@pytest.mark.skipif( + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Gdc available for servers >=7.0" +) @conftest.raises_for_servers_version_under("11.0") def test_custom_op_changelog(server_type_remote_process, testfiles_dir): from packaging.version import Version From 5be2bb06a7653fa14b9360ed136baefce6073241 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 18:46:41 +0200 Subject: [PATCH 12/17] Remove print from test --- tests/test_changelog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_changelog.py b/tests/test_changelog.py index 95cf98e4d95..12b7c1a48d1 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -32,7 +32,6 @@ def test_changelog_new(server_type): changelog = Changelog(server=server_type) assert changelog.last_version == Version("0.0.0") assert changelog[changelog.last_version] == "Initial version." - print(changelog) @conftest.raises_for_servers_version_under("11.0") From 5961ef374abc83eb4fcaa3ccd8230792d37a908d Mon Sep 17 00:00:00 2001 From: PProfizi Date: Mon, 2 Jun 2025 18:50:05 +0200 Subject: [PATCH 13/17] Fix skip conditional --- tests/conftest.py | 3 +++ tests/test_python_plugins.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 634396aee91..f241ee23aa4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -329,6 +329,9 @@ def return_ds(server=None): return return_ds +SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_11_0 = meets_version( + get_server_version(core._global_server()), "11.0" +) SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_10_0 = meets_version( get_server_version(core._global_server()), "10.0" ) diff --git a/tests/test_python_plugins.py b/tests/test_python_plugins.py index 0ef60a41ffa..3abfe1a33c2 100644 --- a/tests/test_python_plugins.py +++ b/tests/test_python_plugins.py @@ -41,6 +41,7 @@ from conftest import ( SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_4_0, SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, + SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_11_0, ) if not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_4_0: @@ -412,9 +413,8 @@ def test_custom_op_with_spec(server_type_remote_process, testfiles_dir): @pytest.mark.skipif( - not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_7_0, reason="Gdc available for servers >=7.0" + not SERVERS_VERSION_GREATER_THAN_OR_EQUAL_TO_11_0, reason="Available for servers >=11.0" ) -@conftest.raises_for_servers_version_under("11.0") def test_custom_op_changelog(server_type_remote_process, testfiles_dir): from packaging.version import Version From 5189cdce15c5c74144148233d7b8568c099455dd Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 3 Jun 2025 09:48:58 +0200 Subject: [PATCH 14/17] Fix QA --- src/ansys/dpf/core/changelog.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/core/changelog.py b/src/ansys/dpf/core/changelog.py index 101a68eb525..497ad39ed9c 100644 --- a/src/ansys/dpf/core/changelog.py +++ b/src/ansys/dpf/core/changelog.py @@ -27,7 +27,6 @@ from packaging.version import Version import ansys.dpf.core as dpf -from ansys.dpf.core.check_version import version_requires class Changelog: @@ -180,9 +179,9 @@ def changes_for_version(self, version: Version) -> str: property_name="changes", output_type=dpf.StringField ) versions_list = versions_sf.data_as_list - for i in range(len(versions_sf.scoping.ids)): + for i, x in enumerate(versions_sf.scoping.ids): if Version(versions_list[i]) == version: - return changes_sf.get_entity_data_by_id(versions_sf.scoping.ids[i])[0] + return changes_sf.get_entity_data_by_id(x)[0] raise ValueError(f"Changelog has no version '{version}'.") def __str__(self): From 158c5dc4374eb53772433ac25d4c2588c01310f5 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 3 Jun 2025 10:44:47 +0200 Subject: [PATCH 15/17] Add helpful dunder methods to Changelog class --- src/ansys/dpf/core/changelog.py | 19 ++++++++++++++++--- tests/test_changelog.py | 10 ++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/ansys/dpf/core/changelog.py b/src/ansys/dpf/core/changelog.py index 497ad39ed9c..ab64e7c745d 100644 --- a/src/ansys/dpf/core/changelog.py +++ b/src/ansys/dpf/core/changelog.py @@ -27,6 +27,7 @@ from packaging.version import Version import ansys.dpf.core as dpf +from ansys.dpf.core.server_types import AnyServerType class Changelog: @@ -42,7 +43,7 @@ class Changelog: The server to create the changelog on. Defaults to the current global server. """ - def __init__(self, gdc: dpf.GenericDataContainer = None, server=None): + def __init__(self, gdc: dpf.GenericDataContainer = None, server: AnyServerType = None): if gdc is None: gdc = dpf.GenericDataContainer(server=server) versions_sf = dpf.StringField(server=server) @@ -166,10 +167,22 @@ def versions(self) -> [Version]: ) return [Version(version) for version in versions_sf.data_as_list] - def __getitem__(self, item: Version) -> str: - """Return changes description for a specific version in the changelog.""" + def __getitem__(self, item: Version | int) -> str | [Version, str]: + """Return item at the given index or changes description for the given version.""" + if isinstance(item, int): + if item > len(self) - 1: + raise IndexError(f"Index {item} out of range for changelog of size {len(self)}.") + return self.versions[item], self.changes_for_version(self.versions[item]) return self.changes_for_version(item) + def __len__(self): + """Return the number of items in the changelog.""" + return len(self.versions) + + def __contains__(self, item: Version): + """Check if version is in the changelog.""" + return item in self.versions + def changes_for_version(self, version: Version) -> str: """Return changes description for a specific version in the changelog.""" versions_sf: dpf.StringField = self.gdc.get_property( diff --git a/tests/test_changelog.py b/tests/test_changelog.py index 12b7c1a48d1..be9ef1a5583 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -58,3 +58,13 @@ def test_changelog_updates(server_type): 1.1.2 Patch 2 """ ) + assert len(changelog) == 5 + assert changelog[0] == (Version("0.0.0"), "Initial version.") + assert changelog[-1] == (Version("1.1.2"), "Patch 2") + for i, v in enumerate(changelog): + if i == 2: + assert v == (Version("1.1.0"), "Minor bump") + with pytest.raises(IndexError): + _ = changelog[8] + assert Version("0.0.0") in changelog + assert Version("1.5.2") not in changelog From 8eef599f56a029d30139e9eb70f802c06699f13a Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 3 Jun 2025 15:10:52 +0200 Subject: [PATCH 16/17] Test for reset of lower indices for minor bump and major bump --- tests/test_changelog.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_changelog.py b/tests/test_changelog.py index be9ef1a5583..0d7d962a06d 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -39,7 +39,9 @@ def test_changelog_updates(server_type): from packaging.version import Version changelog = Changelog(server=server_type) - changelog.major_bump("Major bump").minor_bump("Minor bump").patch_bump("Patch \nbump") + changelog.patch_bump("Patch 1").minor_bump("Minor bump 1").major_bump("Major bump").minor_bump( + "Minor bump" + ).patch_bump("Patch \nbump") with pytest.raises(ValueError): changelog.expect_version(Version("0.0.0")) changelog.expect_version(Version("1.1.1")) @@ -51,6 +53,8 @@ def test_changelog_updates(server_type): Version Changes ------- ------- 0.0.0 Initial version. +0.0.1 Patch 1 +0.1.0 Minor bump 1 1.0.0 Major bump 1.1.0 Minor bump 1.1.1 Patch @@ -58,11 +62,11 @@ def test_changelog_updates(server_type): 1.1.2 Patch 2 """ ) - assert len(changelog) == 5 + assert len(changelog) == 7 assert changelog[0] == (Version("0.0.0"), "Initial version.") assert changelog[-1] == (Version("1.1.2"), "Patch 2") for i, v in enumerate(changelog): - if i == 2: + if i == 4: assert v == (Version("1.1.0"), "Minor bump") with pytest.raises(IndexError): _ = changelog[8] From 3182abcb0ec5e5a0c2971fa2ca2580982cc6af0e Mon Sep 17 00:00:00 2001 From: PProfizi Date: Tue, 3 Jun 2025 15:12:55 +0200 Subject: [PATCH 17/17] Test for reset of lower indices for minor bump and major bump --- tests/test_changelog.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_changelog.py b/tests/test_changelog.py index 0d7d962a06d..e59c87d8540 100644 --- a/tests/test_changelog.py +++ b/tests/test_changelog.py @@ -39,9 +39,9 @@ def test_changelog_updates(server_type): from packaging.version import Version changelog = Changelog(server=server_type) - changelog.patch_bump("Patch 1").minor_bump("Minor bump 1").major_bump("Major bump").minor_bump( - "Minor bump" - ).patch_bump("Patch \nbump") + changelog.patch_bump("Patch 1").minor_bump("Minor bump 1").patch_bump("Patch 2").major_bump( + "Major bump" + ).minor_bump("Minor bump").patch_bump("Patch \nbump") with pytest.raises(ValueError): changelog.expect_version(Version("0.0.0")) changelog.expect_version(Version("1.1.1")) @@ -55,6 +55,7 @@ def test_changelog_updates(server_type): 0.0.0 Initial version. 0.0.1 Patch 1 0.1.0 Minor bump 1 +0.1.1 Patch 2 1.0.0 Major bump 1.1.0 Minor bump 1.1.1 Patch @@ -62,11 +63,11 @@ def test_changelog_updates(server_type): 1.1.2 Patch 2 """ ) - assert len(changelog) == 7 + assert len(changelog) == 8 assert changelog[0] == (Version("0.0.0"), "Initial version.") assert changelog[-1] == (Version("1.1.2"), "Patch 2") for i, v in enumerate(changelog): - if i == 4: + if i == 5: assert v == (Version("1.1.0"), "Minor bump") with pytest.raises(IndexError): _ = changelog[8]