Skip to content

feat: Set string settings with allowed values via string constants #4190

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 6 commits into from
Jul 8, 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
1 change: 1 addition & 0 deletions doc/changelog.d/4190.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Set string settings with allowed values via string constants
13 changes: 12 additions & 1 deletion src/ansys/fluent/core/codegen/settingsgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
ListObject,
NamedObject,
get_cls,
to_constant_name,
to_python_name,
)
from ansys.fluent.core.utils.fix_doc import fix_settings_doc
Expand Down Expand Up @@ -151,6 +152,7 @@ def _populate_data(cls, api_tree: dict, version: str) -> dict:
data["child_object_type"]["doc"] = f"'child_object_type' of {cls.__name__}."
else:
data["child_object_type"] = None
data["allowed_values"] = getattr(cls, "_allowed_values", [])
return data


Expand Down Expand Up @@ -307,7 +309,15 @@ def _write_data(cls_name: str, python_name: str, data: dict, f: IO, f_stub: IO |
if return_type:
s.write(f" return_type = {return_type!r}\n")
s_stub.write(" return_type: str\n")
for allowed_value in data["allowed_values"]:
s.write(
f" {to_constant_name(allowed_value)} = _FlStringConstant({allowed_value!r})\n"
)
s_stub.write(
f" {to_constant_name(allowed_value)}: Final[str] = {allowed_value!r}\n"
)
s.write("\n")
s_stub.write("\n")
for name, (python_name, data, hash_, should_write_stub) in classes_to_write.items():
if name not in _CLASS_WRITTEN:
_write_data(
Expand Down Expand Up @@ -371,10 +381,11 @@ def generate(version: str, static_infos: dict, verbose: bool = False) -> None:
header.write(" _InputFile,\n")
header.write(" _OutputFile,\n")
header.write(" _InOutFile,\n")
header.write(" _FlStringConstant,\n")
header.write(")\n\n")
f.write(header.getvalue())
f_stub.write(header.getvalue())
f_stub.write("from typing import Any\n\n")
f_stub.write("from typing import Any, Final\n\n")
f.write(f'SHASH = "{shash}"\n\n')
name = data["name"]
_NAME_BY_HASH[_gethash(data)] = name
Expand Down
1 change: 1 addition & 0 deletions src/ansys/fluent/core/services/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ def get_static_info(self) -> dict[str, Any]:
"""
request = SettingsModule.GetStaticInfoRequest()
request.root = "fluent"
request.optional_attrs.append("allowed-values")
response = self._service_impl.get_static_info(request)
# The RPC calls no longer raise an exception. Force an exception if
# type is empty
Expand Down
54 changes: 53 additions & 1 deletion src/ansys/fluent/core/solver/flobject.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import types
from typing import (
Any,
Callable,
Dict,
ForwardRef,
Generic,
Expand Down Expand Up @@ -187,7 +188,7 @@ def assert_type(val, tp):
def to_python_name(fluent_name: str) -> str:
"""Convert a scheme string to a Python variable name.

This function replaces symbols with _. Any ``?`` symbols are
This function replaces symbols with _. ``'`` and ``?`` symbols are
ignored.
"""
if not fluent_name:
Expand All @@ -198,6 +199,20 @@ def to_python_name(fluent_name: str) -> str:
return name


def to_constant_name(fluent_name: str) -> str:
"""Convert a scheme string to a Python constant name.

This function replaces symbols and spaces with _ and converts the name to uppercase.
``'`` and ``?`` symbols are ignored.
"""
fluent_name = fluent_name.replace(" ", "_")
name = fluent_name.translate(_ttable).upper()
if name[0].isdigit():
# If the first character is a digit, prepend "CASE_"
name = "CASE_" + name
return name


_to_field_name_str = naming_strategy().to_string


Expand Down Expand Up @@ -2029,6 +2044,33 @@ def allowed_values(self):
return []


class _MaybeActiveString(str):
"""A string class with an is_active() method."""

def __new__(cls, value, is_active: Callable[[], bool]):
return super().__new__(cls, value)

def __init__(self, value, is_active: Callable[[], bool]):
super().__init__()
self.is_active = is_active


class _FlStringConstant:
"""A descriptor class to hold a constant string value."""

def __init__(self, value):
self._value = value

def __get__(self, instance, owner):
def is_active():
return self._value in instance.allowed_values()

return _MaybeActiveString(self._value, is_active=is_active)

def __set__(self, instance, value):
raise AttributeError("Cannot set a constant value.")


_bases_by_class = {}


Expand Down Expand Up @@ -2217,6 +2259,16 @@ def _process_cls_names(info_dict, names, write_doc=False):
k,
)

allowed_values = info.get("allowed-values") or info.get("allowed_values", [])
if allowed_values:
for allowed_value in allowed_values:
setattr(
cls,
to_constant_name(allowed_value),
_FlStringConstant(allowed_value),
)
cls._allowed_values = allowed_values

except Exception:
print(
f"Unable to construct class for '{name}' of "
Expand Down
1 change: 1 addition & 0 deletions tests/test_codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ def _get_query_settings_static_info(name, args):
_InputFile,
_OutputFile,
_InOutFile,
_FlStringConstant,
)

SHASH = "3e6d76a4601701388ea8258912d145b7b7c436699a50b6c7fe9a29f41eeff194"
Expand Down
26 changes: 26 additions & 0 deletions tests/test_settings_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from ansys.fluent.core.examples import download_file
from ansys.fluent.core.pyfluent_warnings import PyFluentUserWarning
from ansys.fluent.core.solver import Viscous
from ansys.fluent.core.solver.flobject import (
DeprecatedSettingWarning,
_Alias,
Expand Down Expand Up @@ -762,3 +763,28 @@ def test_runtime_python_classes(
].general.material()
== "water-liquid"
)


@pytest.mark.fluent_version(">=26.1")
def test_setting_string_constants(mixing_elbow_settings_session):
solver = mixing_elbow_settings_session
viscous = Viscous(solver)

# viscous.model.INVISCID is a string constant
assert viscous.model.INVISCID == "inviscid"
assert isinstance(viscous.model.INVISCID, str)
with pytest.raises(AttributeError):
viscous.model.INVISCID = "invalid"

# Setting using string constants
viscous.model = viscous.model.INVISCID
assert viscous.model() == "inviscid"
viscous.model = viscous.model.K_EPSILON
assert viscous.model() == "k-epsilon"
viscous.k_epsilon_model = viscous.k_epsilon_model.RNG
assert viscous.k_epsilon_model.RNG.is_active() is True
assert viscous.k_epsilon_model() == "rng"
assert viscous.k_epsilon_model.EASM.is_active() is False

with pytest.raises(ValueError):
viscous.k_epsilon_model = viscous.k_epsilon_model.EASM