diff --git a/doc/changelog.d/4190.added.md b/doc/changelog.d/4190.added.md new file mode 100644 index 000000000000..4026522e3bb3 --- /dev/null +++ b/doc/changelog.d/4190.added.md @@ -0,0 +1 @@ +Set string settings with allowed values via string constants \ No newline at end of file diff --git a/src/ansys/fluent/core/codegen/settingsgen.py b/src/ansys/fluent/core/codegen/settingsgen.py index a5519b79d603..52bb96d39712 100644 --- a/src/ansys/fluent/core/codegen/settingsgen.py +++ b/src/ansys/fluent/core/codegen/settingsgen.py @@ -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 @@ -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 @@ -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( @@ -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 diff --git a/src/ansys/fluent/core/services/settings.py b/src/ansys/fluent/core/services/settings.py index 720763094674..ba0c5eac6ebe 100644 --- a/src/ansys/fluent/core/services/settings.py +++ b/src/ansys/fluent/core/services/settings.py @@ -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 diff --git a/src/ansys/fluent/core/solver/flobject.py b/src/ansys/fluent/core/solver/flobject.py index 1865f5f6e6f1..fbb292af492c 100644 --- a/src/ansys/fluent/core/solver/flobject.py +++ b/src/ansys/fluent/core/solver/flobject.py @@ -55,6 +55,7 @@ import types from typing import ( Any, + Callable, Dict, ForwardRef, Generic, @@ -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: @@ -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 @@ -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 = {} @@ -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 " diff --git a/tests/test_codegen.py b/tests/test_codegen.py index 7618a9ae81fd..fa15efb35b25 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -504,6 +504,7 @@ def _get_query_settings_static_info(name, args): _InputFile, _OutputFile, _InOutFile, + _FlStringConstant, ) SHASH = "3e6d76a4601701388ea8258912d145b7b7c436699a50b6c7fe9a29f41eeff194" diff --git a/tests/test_settings_api.py b/tests/test_settings_api.py index 21421fb3720a..1fd670ae8e65 100644 --- a/tests/test_settings_api.py +++ b/tests/test_settings_api.py @@ -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, @@ -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