Skip to content

feat(framework): expose operator changelog #2324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
207 changes: 207 additions & 0 deletions src/ansys/dpf/core/changelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# 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
from ansys.dpf.core.server_types import AnyServerType


class Changelog:
"""Changelog of an operator.

Requires DPF 11.0 (2026 R1) or above.

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: AnyServerType = 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
self._server = server

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 | str) -> 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 isinstance(version, str):
version = Version(version)

Check warning on line 144 in src/ansys/dpf/core/changelog.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/changelog.py#L144

Added line #L144 was not covered by tests
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 | 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(
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, x in enumerate(versions_sf.scoping.ids):
if Version(versions_list[i]) == version:
return changes_sf.get_entity_data_by_id(x)[0]
raise ValueError(f"Changelog has no version '{version}'.")

Check warning on line 198 in src/ansys/dpf/core/changelog.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/changelog.py#L198

Added line #L198 was not covered by tests

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
39 changes: 39 additions & 0 deletions src/ansys/dpf/core/custom_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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


Expand Down Expand Up @@ -400,3 +403,39 @@
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

Check warning on line 419 in src/ansys/dpf/core/custom_operator.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/custom_operator.py#L419

Added line #L419 was not covered by tests

return Changelog(operator_changelog(operator_name=self.name).eval())

Check warning on line 421 in src/ansys/dpf/core/custom_operator.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/custom_operator.py#L421

Added line #L421 was not covered by tests

@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)

Check warning on line 431 in src/ansys/dpf/core/custom_operator.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/custom_operator.py#L431

Added line #L431 was not covered by tests

@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

Check warning on line 441 in src/ansys/dpf/core/custom_operator.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/custom_operator.py#L441

Added line #L441 was not covered by tests
31 changes: 31 additions & 0 deletions src/ansys/dpf/core/dpf_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@
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
from ansys.dpf.core.check_version import (
server_meet_version,
server_meet_version_and_raise,
Expand Down Expand Up @@ -931,6 +933,35 @@ def specification(self):
else:
return Specification(operator_name=self.name, server=self._server)

@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(
gdc=operator_changelog(operator_name=self.name, server=self._server).eval(),
server=self._server,
)

@property
@version_requires("11.0")
def version(self) -> Version:
"""Return the current version of the operator.

Requires DPF 11.0 (2026 R1) or above.

"""
return self.changelog.last_version

def __truediv__(self, inpt):
"""
Perform division with another operator or a scalar.
Expand Down
10 changes: 10 additions & 0 deletions src/ansys/dpf/core/operator_specification.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -497,6 +498,15 @@
)
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)

Check warning on line 508 in src/ansys/dpf/core/operator_specification.py

View check run for this annotation

Codecov / codecov/patch

src/ansys/dpf/core/operator_specification.py#L508

Added line #L508 was not covered by tests


class CustomConfigOptionSpec(ConfigOptionSpec):
"""Custom documentation of a configuration option available for a given operator."""
Expand Down
4 changes: 4 additions & 0 deletions src/ansys/dpf/gate/operator_specification_grpcapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down
Loading
Loading