From cbf755877669442040fee6c555d355aee58b5c61 Mon Sep 17 00:00:00 2001 From: Mayank Kumargit Date: Wed, 11 Feb 2026 12:45:45 +0530 Subject: [PATCH 01/11] refactor: re-export 'using' in ansys.fluent.core.__init__ --- doc/source/user_guide/session/session.rst | 65 ++++++++++++++++++++++- src/ansys/fluent/core/__init__.py | 1 + 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/session/session.rst b/doc/source/user_guide/session/session.rst index 6590e36783a3..14b04bb7f1af 100644 --- a/doc/source/user_guide/session/session.rst +++ b/doc/source/user_guide/session/session.rst @@ -168,11 +168,74 @@ You can also create a :obj:`~ansys.fluent.core.session_pure_meshing.PureMeshing` >>> pure_meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.PURE_MESHING) -The only difference between the two meshing session types is that a pure session cannot be +Thet only difference between the two meshing session types is that a pure session canno be switched to solution mode directly. The existence of the pure session type promotes creation of minimal server images, which becomes significant in the context of containerization. +Context manager for active sessions +----------------------------------- + +When working with generated settings and functions, you often need to pass the current session explicitly via the +``settings_source`` or a session argument. The ``using(session)`` context manager lets you set an "active session" +so you can call top-level settings objects and functions without wiring the session each time. This works for both +:obj:`~ansys.fluent.core.session_solver.Solver` and :obj:`~ansys.fluent.core.session_meshing.Meshing` sessions and is +thread-safe. + +Note: You can import the context manager as ``from ansys.fluent.core import using``. It also remains available via +``from ansys.fluent.core.solver import using`` for backward compatibility. + +Solver context manager +~~~~~~~~~~~~~~~~~~~~~~ + +Use ``using(solver_session)`` to make a solver the active session inside a ``with`` block: + +.. code:: python + + >>> import ansys.fluent.core as pyfluent + >>> from ansys.fluent.core.examples import download_file + >>> from ansys.fluent.core.solver import using + >>> solver = pyfluent.launch_fluent() + >>> case_file = download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow") + >>> with using(solver): + ... # Call file I/O and settings without passing solver explicitly + ... read_case(file_name=case_file) + ... # Access models directly + ... viscous_model = Viscous() + ... viscous_model.model() # returns the current viscous model state + 'k-omega' + +Thread safety: The active session is maintained per thread, so each thread can safely set and use its own session: + +.. code:: python + + >>> import threading + >>> def work(sess): + ... with using(sess): + ... assert Setup() == sess.setup + >>> t = threading.Thread(target=work, args=(solver,)) + >>> t.start(); t.join() + +Meshing context manager +~~~~~~~~~~~~~~~~~~~~~~~ + +Use ``using(meshing_session)`` to make a meshing session active and interact with workflows without passing the session: + +.. code:: python + + >>> import ansys.fluent.core as pyfluent + >>> from ansys.fluent.core.solver import using + >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) + >>> with using(meshing): + ... wt = meshing.watertight() + ... import_geometry = wt.import_geometry + ... # Set states and execute tasks here + ... # e.g., import_geometry.file_name.set_state(); import_geometry() + +Outside of a ``with using(...)`` block, you can continue to pass sessions explicitly when you need to operate on +multiple sessions in the same scope or prefer explicit control. + + Switching between sessions -------------------------- diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index 0865486f5313..3f44c5ebf918 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -78,6 +78,7 @@ ) from ansys.fluent.core.streaming_services.events_streaming import * # noqa: F401, F403 from ansys.fluent.core.utils import fldoc +from ansys.fluent.core.utils.context_managers import using # noqa: F401 from ansys.fluent.core.utils.fluent_version import FluentVersion # noqa: F401 from ansys.fluent.core.utils.setup_for_fluent import setup_for_fluent # noqa: F401 From 971de4ae890c2086d4c8df1f2623e7dde9e06644 Mon Sep 17 00:00:00 2001 From: Mayank Kumargit Date: Wed, 11 Feb 2026 12:54:00 +0530 Subject: [PATCH 02/11] docs: minor change --- doc/source/user_guide/session/session.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/session/session.rst b/doc/source/user_guide/session/session.rst index 14b04bb7f1af..614f632c0f4c 100644 --- a/doc/source/user_guide/session/session.rst +++ b/doc/source/user_guide/session/session.rst @@ -168,7 +168,7 @@ You can also create a :obj:`~ansys.fluent.core.session_pure_meshing.PureMeshing` >>> pure_meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.PURE_MESHING) -Thet only difference between the two meshing session types is that a pure session canno be +The only difference between the two meshing session types is that a pure session cannot be switched to solution mode directly. The existence of the pure session type promotes creation of minimal server images, which becomes significant in the context of containerization. @@ -224,7 +224,7 @@ Use ``using(meshing_session)`` to make a meshing session active and interact wit .. code:: python >>> import ansys.fluent.core as pyfluent - >>> from ansys.fluent.core.solver import using + >>> from ansys.fluent.core import using >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) >>> with using(meshing): ... wt = meshing.watertight() From faf42da03143505addccb2ccbedfc124f9db8934 Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Thu, 12 Feb 2026 06:48:01 +0000 Subject: [PATCH 03/11] chore: adding changelog file 4921.miscellaneous.md [dependabot-skip] From 69f47ce40c14fe098ab12227148962af75853dee Mon Sep 17 00:00:00 2001 From: Mayank Kumargit Date: Thu, 12 Feb 2026 19:05:31 +0530 Subject: [PATCH 04/11] fix: Added the library & minor changes --- doc/source/user_guide/session/session.rst | 93 ++++++++++++----------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/doc/source/user_guide/session/session.rst b/doc/source/user_guide/session/session.rst index 614f632c0f4c..2cf0baeac8e1 100644 --- a/doc/source/user_guide/session/session.rst +++ b/doc/source/user_guide/session/session.rst @@ -176,64 +176,71 @@ of minimal server images, which becomes significant in the context of containeri Context manager for active sessions ----------------------------------- -When working with generated settings and functions, you often need to pass the current session explicitly via the -``settings_source`` or a session argument. The ``using(session)`` context manager lets you set an "active session" -so you can call top-level settings objects and functions without wiring the session each time. This works for both -:obj:`~ansys.fluent.core.session_solver.Solver` and :obj:`~ansys.fluent.core.session_meshing.Meshing` sessions and is -thread-safe. +The ``using(session)`` context manager sets an active session so you can call top-level settings objects and functions +without explicitly passing the session each time. -Note: You can import the context manager as ``from ansys.fluent.core import using``. It also remains available via -``from ansys.fluent.core.solver import using`` for backward compatibility. +Example: -Solver context manager -~~~~~~~~~~~~~~~~~~~~~~ +.. code:: python + + >>> from ansys.fluent.core import using + >>> from ansys.fluent.core.solver import ReadCase, Viscous + >>> with using(solver_session): + ... ReadCase()(file_name=) + ... print(Viscous().model()) + k-omega + +.. note:: -Use ``using(solver_session)`` to make a solver the active session inside a ``with`` block: + Settings trees are inactive outside an active session. Set or query state inside ``with using(session)`` or + pass the session explicitly via ``settings_source=...`` on the object. + + +Multiple sessions in one script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When working with multiple sessions in the same script, use separate ``with`` blocks to make each session active only +within its intended scope: .. code:: python >>> import ansys.fluent.core as pyfluent >>> from ansys.fluent.core.examples import download_file - >>> from ansys.fluent.core.solver import using - >>> solver = pyfluent.launch_fluent() + >>> from ansys.fluent.core import using + >>> from ansys.fluent.core.solver import ReadCase, Viscous + >>> solver_session_1 = pyfluent.launch_fluent() + >>> solver_session_2 = pyfluent.launch_fluent() >>> case_file = download_file("mixing_elbow.cas.h5", "pyfluent/mixing_elbow") - >>> with using(solver): - ... # Call file I/O and settings without passing solver explicitly - ... read_case(file_name=case_file) - ... # Access models directly - ... viscous_model = Viscous() - ... viscous_model.model() # returns the current viscous model state - 'k-omega' - -Thread safety: The active session is maintained per thread, so each thread can safely set and use its own session: + >>> with using(solver_session_1): + ... ReadCase()(file_name=case_file) + ... Viscous().model.set_state("laminar") + ... print(Viscous().model()) + laminar + >>> with using(solver_session_2): + ... ReadCase()(file_name=case_file) + ... Viscous().model.set_state("k-omega") + ... print(Viscous().model()) + k-omega + +Thread-local behavior +~~~~~~~~~~~~~~~~~~~~~ + +Each thread can set and use its own active session. Sessions set in one thread are not visible to other threads: .. code:: python >>> import threading - >>> def work(sess): - ... with using(sess): - ... assert Setup() == sess.setup - >>> t = threading.Thread(target=work, args=(solver,)) + >>> from ansys.fluent.core import using + >>> from ansys.fluent.core.solver import Viscous + >>> def work(session): + ... with using(session): + ... print(Viscous().model()) + k-omega + >>> t = threading.Thread(target=work, args=(solver_session,)) >>> t.start(); t.join() -Meshing context manager -~~~~~~~~~~~~~~~~~~~~~~~ - -Use ``using(meshing_session)`` to make a meshing session active and interact with workflows without passing the session: - -.. code:: python - - >>> import ansys.fluent.core as pyfluent - >>> from ansys.fluent.core import using - >>> meshing = pyfluent.launch_fluent(mode=pyfluent.FluentMode.MESHING) - >>> with using(meshing): - ... wt = meshing.watertight() - ... import_geometry = wt.import_geometry - ... # Set states and execute tasks here - ... # e.g., import_geometry.file_name.set_state(); import_geometry() - -Outside of a ``with using(...)`` block, you can continue to pass sessions explicitly when you need to operate on -multiple sessions in the same scope or prefer explicit control. +Outside of a ``with using(...)`` block, pass sessions explicitly when operating on multiple sessions in the same scope +or when working with multi-threaded code. Switching between sessions From 520ea55e7bca16929b7f3cecc91b88bcfc52059a Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:57:53 +0000 Subject: [PATCH 05/11] chore: adding changelog file 4938.added.md [dependabot-skip] --- doc/changelog.d/4938.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4938.added.md diff --git a/doc/changelog.d/4938.added.md b/doc/changelog.d/4938.added.md new file mode 100644 index 000000000000..565998ddb30c --- /dev/null +++ b/doc/changelog.d/4938.added.md @@ -0,0 +1 @@ +Add VariableDescriptor support to field data interfaces From 9d90e296f04973f4742e06ad68b366918725cc74 Mon Sep 17 00:00:00 2001 From: Mayank Kumargit Date: Tue, 17 Feb 2026 21:05:42 +0530 Subject: [PATCH 06/11] feat: added VariableDescriptor support to field data --- .../fluent/core/field_data_interfaces.py | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/ansys/fluent/core/field_data_interfaces.py b/src/ansys/fluent/core/field_data_interfaces.py index 1928ec4d3d7e..27cef9a1985e 100644 --- a/src/ansys/fluent/core/field_data_interfaces.py +++ b/src/ansys/fluent/core/field_data_interfaces.py @@ -34,8 +34,10 @@ from ansys.fluent.core.variable_strategies import ( FluentFieldDataNamingStrategy as naming_strategy, ) +from ansys.units.variable_descriptor import VariableDescriptor -_to_field_name_str = naming_strategy().to_string +_naming_strategy_instance = naming_strategy() +_to_field_name_str = _naming_strategy_instance.to_string class SurfaceDataType(Enum): @@ -367,9 +369,42 @@ def is_active(self, field_name): return False def allowed_values(self): - """Lists available scalar or vector field names.""" + """Lists available scalar or vector field names as strings.""" return list(self._available_field_names()) + def is_allowed_variable(self, variable: VariableDescriptor | str) -> bool: + """Check if a variable is in the allowed list. + + Parameters + ---------- + variable : VariableDescriptor | str + The variable to check. Can be a VariableDescriptor or a string. + + Returns + ------- + bool + True if the variable is allowed, False otherwise. + """ + # _to_field_name_str handles both VariableDescriptor and str + field_str = _to_field_name_str(variable) + return field_str in self._available_field_names() + + def allowed_variables(self) -> list[VariableDescriptor]: + """Return allowed field names as VariableDescriptor objects. + + Returns + ------- + list[VariableDescriptor] + List of VariableDescriptor objects for all allowed fields. + Fields without a corresponding VariableDescriptor are excluded. + """ + result = [] + for field_name in self._available_field_names(): + descriptor = _naming_strategy_instance.to_variable_descriptor(field_name) + if descriptor is not None: + result.append(descriptor) + return result + def __call__(self): return self._available_field_names() From 0a8baf376c6ac635cc919f5a28a1450cb6d18a96 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Mon, 2 Mar 2026 19:09:29 +0530 Subject: [PATCH 07/11] feat:added the tests & updated the code --- .../fluent/core/field_data_interfaces.py | 38 +++++++++---------- tests/test_field_data.py | 38 +++++++++++++++++++ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/ansys/fluent/core/field_data_interfaces.py b/src/ansys/fluent/core/field_data_interfaces.py index 27cef9a1985e..f5eca65ed970 100644 --- a/src/ansys/fluent/core/field_data_interfaces.py +++ b/src/ansys/fluent/core/field_data_interfaces.py @@ -362,8 +362,14 @@ class _Fields: def __init__(self, available_field_names): self._available_field_names = available_field_names - def is_active(self, field_name): - """Check whether a field is active in the given context.""" + def is_active(self, field_name: VariableDescriptor | str) -> bool: + """Check whether a field is active in the given context. + + Parameters + ---------- + field_name : VariableDescriptor | str + Field name to check. Can be a VariableDescriptor or a string. + """ if _to_field_name_str(field_name) in self._available_field_names(): return True return False @@ -372,23 +378,6 @@ def allowed_values(self): """Lists available scalar or vector field names as strings.""" return list(self._available_field_names()) - def is_allowed_variable(self, variable: VariableDescriptor | str) -> bool: - """Check if a variable is in the allowed list. - - Parameters - ---------- - variable : VariableDescriptor | str - The variable to check. Can be a VariableDescriptor or a string. - - Returns - ------- - bool - True if the variable is allowed, False otherwise. - """ - # _to_field_name_str handles both VariableDescriptor and str - field_str = _to_field_name_str(variable) - return field_str in self._available_field_names() - def allowed_variables(self) -> list[VariableDescriptor]: """Return allowed field names as VariableDescriptor objects. @@ -398,9 +387,18 @@ def allowed_variables(self) -> list[VariableDescriptor]: List of VariableDescriptor objects for all allowed fields. Fields without a corresponding VariableDescriptor are excluded. """ + converter = getattr(_naming_strategy_instance, "to_variable_descriptor", None) + if converter is None or not callable(converter): + warnings.warn( + "Naming strategy does not support conversion to VariableDescriptor; " + "returning an empty list from allowed_variables().", + RuntimeWarning, + ) + return [] + result = [] for field_name in self._available_field_names(): - descriptor = _naming_strategy_instance.to_variable_descriptor(field_name) + descriptor = converter(field_name) if descriptor is not None: result.append(descriptor) return result diff --git a/tests/test_field_data.py b/tests/test_field_data.py index 9629303606bb..78a69e6130d6 100644 --- a/tests/test_field_data.py +++ b/tests/test_field_data.py @@ -35,14 +35,17 @@ ) from ansys.fluent.core.examples.downloads import download_file from ansys.fluent.core.exceptions import DisallowedValuesError +import ansys.fluent.core.field_data_interfaces as field_data_interfaces_module from ansys.fluent.core.field_data_interfaces import ( FieldUnavailable, + _Fields, ) from ansys.fluent.core.services.field_data import ( CellElementType, ZoneType, ) from ansys.fluent.core.solver import VelocityInlet, VelocityInlets, WallBoundaries +from ansys.units.variable_descriptor import VariableCatalog HOT_INLET_TEMPERATURE = 313.15 @@ -718,6 +721,41 @@ def test_field_data_errors(new_solver_session) -> None: ) +def test_fields_is_active_accepts_variable_descriptor() -> None: + fields = _Fields(lambda: ["temperature"]) + + assert fields.is_active("temperature") + assert fields.is_active(VariableCatalog.TEMPERATURE) + assert not fields.is_active(VariableCatalog.VELOCITY) + + +def test_fields_allowed_variables_filters_unmapped_names() -> None: + fields = _Fields(lambda: ["temperature", "not-a-mapped-field"]) + + allowed_variables = fields.allowed_variables() + + assert len(allowed_variables) == 1 + assert allowed_variables[0] == VariableCatalog.TEMPERATURE + + +def test_fields_allowed_variables_without_converter_warns_and_returns_empty( + monkeypatch, +) -> None: + fields = _Fields(lambda: ["temperature"]) + + class NamingStrategyWithoutConverter: + pass + + monkeypatch.setattr( + field_data_interfaces_module, + "_naming_strategy_instance", + NamingStrategyWithoutConverter(), + ) + + with pytest.warns(RuntimeWarning, match="does not support conversion"): + assert fields.allowed_variables() == [] + + @pytest.mark.skip(reason=SKIP_INVESTIGATING) # https://github.com/ansys/pyfluent/issues/2404 @pytest.mark.fluent_version(">=24.2") From 8ecb31bb47c8ee49d1e34982ac5f4ad8fcc097d1 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Wed, 4 Mar 2026 17:48:26 +0530 Subject: [PATCH 08/11] fix: added the none type in multiple files --- .../core/codegen/builtin_settingsgen.py | 6 ++-- .../fluent/core/field_data_interfaces.py | 6 ++-- src/ansys/fluent/core/file_session.py | 4 +-- src/ansys/fluent/core/launcher/launcher.py | 2 +- .../fluent/core/launcher/slurm_launcher.py | 4 +-- .../core/launcher/standalone_launcher.py | 2 +- .../core/meshing/meshing_workflow_new.py | 2 +- src/ansys/fluent/core/search.py | 2 +- src/ansys/fluent/core/services/field_data.py | 6 ++-- src/ansys/fluent/core/services/scheme_eval.py | 2 +- src/ansys/fluent/core/session_base_meshing.py | 2 +- src/ansys/fluent/core/session_utilities.py | 2 +- src/ansys/fluent/core/utils/deprecate.py | 2 ++ tests/test_deprecate.py | 31 +++++++++++++++++++ 14 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/ansys/fluent/core/codegen/builtin_settingsgen.py b/src/ansys/fluent/core/codegen/builtin_settingsgen.py index f09525abfe31..f47c77e83092 100644 --- a/src/ansys/fluent/core/codegen/builtin_settingsgen.py +++ b/src/ansys/fluent/core/codegen/builtin_settingsgen.py @@ -116,9 +116,11 @@ def generate(version: str): f.write(f", {named_object}: str") f.write(", settings_source: SettingsBase | Solver | None = None") if kind == "NonCreatableNamedObject": - f.write(", name: str = None") + f.write(", name: str | None = None") elif kind == "CreatableNamedObject": - f.write(", name: str = None, new_instance_name: str = None") + f.write( + ", name: str | None = None, new_instance_name: str | None = None" + ) f.write("):\n") f.write(" super().__init__(settings_source=settings_source") if kind == "NonCreatableNamedObject": diff --git a/src/ansys/fluent/core/field_data_interfaces.py b/src/ansys/fluent/core/field_data_interfaces.py index f5eca65ed970..c20416a0b9ab 100644 --- a/src/ansys/fluent/core/field_data_interfaces.py +++ b/src/ansys/fluent/core/field_data_interfaces.py @@ -113,7 +113,7 @@ class BaseFieldInfo(ABC): @abstractmethod def get_scalar_field_range( - self, field: str, node_value: bool = False, surface_ids: List[int] = None + self, field: str, node_value: bool = False, surface_ids: List[int] | None = None ) -> List[float]: """ Retrieve the range (minimum and maximum values) of a scalar field. @@ -413,14 +413,14 @@ def __init__(self, available_field_names, field_info): self._field_info = field_info def range( - self, field: str, node_value: bool = False, surface_ids: list[int] = None + self, field: str, node_value: bool = False, surface_ids: list[int] | None = None ) -> list[float]: """Get the range (minimum and maximum values) of the field. Parameters ---------- field: str - Field name + Field namex node_value: bool surface_ids : List[int], optional List of surface IDS for the surface data. diff --git a/src/ansys/fluent/core/file_session.py b/src/ansys/fluent/core/file_session.py index 8c7690a699d4..e06fecb212c0 100644 --- a/src/ansys/fluent/core/file_session.py +++ b/src/ansys/fluent/core/file_session.py @@ -966,7 +966,7 @@ def __init__(self, file_session): self._file_session = file_session def get_scalar_field_range( - self, field: str, node_value: bool = False, surface_ids: List[int] = None + self, field: str, node_value: bool = False, surface_ids: List[int] | None = None ) -> List[float]: """Get the range (minimum and maximum values) of the field. @@ -990,7 +990,7 @@ def get_scalar_field_range( return self._get_scalar_field_range(field, node_value, surface_ids) def _get_scalar_field_range( - self, field: str, node_value: bool = False, surface_ids: List[int] = None + self, field: str, node_value: bool = False, surface_ids: List[int] | None = None ) -> List[float]: minimum = None maximum = None diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index b82544eb24f1..6e47c2ad43cb 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -176,7 +176,7 @@ def launch_fluent( precision: Precision | str | None = None, processor_count: int | None = None, journal_file_names: None | str | list[str] = None, - start_timeout: int = None, + start_timeout: int | None = None, additional_arguments: str = "", env: Dict[str, Any] | None = None, start_container: bool | None = None, diff --git a/src/ansys/fluent/core/launcher/slurm_launcher.py b/src/ansys/fluent/core/launcher/slurm_launcher.py index 4f25aa0e2415..1f9f22f64ed4 100644 --- a/src/ansys/fluent/core/launcher/slurm_launcher.py +++ b/src/ansys/fluent/core/launcher/slurm_launcher.py @@ -321,7 +321,7 @@ def done(self) -> bool: return self._get_state() in ["", "CANCELLED", "COMPLETED"] def result( - self, timeout: int = None + self, timeout: int | None = None ) -> Meshing | PureMeshing | Solver | SolverIcing: """Return the session instance corresponding to the Fluent launch. If Fluent hasn't yet launched, then this method will wait up to timeout seconds. If Fluent @@ -345,7 +345,7 @@ def result( """ return self._future.result(timeout) - def exception(self, timeout: int = None) -> Exception: + def exception(self, timeout: int | None = None) -> Exception: """Return the exception raised by the Fluent launch. If Fluent hasn't yet launched, then this method will wait up to timeout seconds. If Fluent hasn't launched in timeout seconds, then a TimeoutError will be raised. If timeout is diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index 7812a2534586..ab624d76b741 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -82,7 +82,7 @@ def __init__( mode: FluentMode | str | None = None, ui_mode: UIMode | str | None = None, graphics_driver: ( - FluentWindowsGraphicsDriver | FluentLinuxGraphicsDriver | str + FluentWindowsGraphicsDriver | FluentLinuxGraphicsDriver | str | None ) = None, product_version: FluentVersion | str | float | int | None = None, dimension: Dimension | int | None = None, diff --git a/src/ansys/fluent/core/meshing/meshing_workflow_new.py b/src/ansys/fluent/core/meshing/meshing_workflow_new.py index db2ff719fb8d..75fc2b5d47da 100644 --- a/src/ansys/fluent/core/meshing/meshing_workflow_new.py +++ b/src/ansys/fluent/core/meshing/meshing_workflow_new.py @@ -271,7 +271,7 @@ def __init__( workflow: PyMenuGeneric, meshing: PyMenuGeneric, fluent_version: FluentVersion, - file_path: PathType = None, + file_path: "PathType | None" = None, initialize: bool = True, ) -> None: """Initialize a ``LoadWorkflow`` instance. diff --git a/src/ansys/fluent/core/search.py b/src/ansys/fluent/core/search.py index 8c66d1dba507..0386b966c541 100644 --- a/src/ansys/fluent/core/search.py +++ b/src/ansys/fluent/core/search.py @@ -426,7 +426,7 @@ def _search_whole_word( search_string: str, match_case: bool = False, match_whole_word: bool = True, - api_tree_data: dict = None, + api_tree_data: dict | None = None, api_path: str | None = None, ): """Perform exact search for a word through the Fluent's object hierarchy. diff --git a/src/ansys/fluent/core/services/field_data.py b/src/ansys/fluent/core/services/field_data.py index a624864f89dc..85e4028719f5 100644 --- a/src/ansys/fluent/core/services/field_data.py +++ b/src/ansys/fluent/core/services/field_data.py @@ -176,7 +176,7 @@ def __init__( self._is_data_valid = is_data_valid def get_scalar_field_range( - self, field: str, node_value: bool = False, surface_ids: List[int] = None + self, field: str, node_value: bool = False, surface_ids: List[int] | None = None ) -> List[float]: """Get the range (minimum and maximum values) of the field. @@ -200,7 +200,7 @@ def get_scalar_field_range( return self._get_scalar_field_range(field, node_value, surface_ids) def _get_scalar_field_range( - self, field: str, node_value: bool = False, surface_ids: List[int] = None + self, field: str, node_value: bool = False, surface_ids: List[int] | None = None ) -> List[float]: if not surface_ids: surface_ids = [] @@ -1105,7 +1105,7 @@ class ChunkParser: field : numpy array """ - def __init__(self, callbacks_provider: object = None): + def __init__(self, callbacks_provider: object | None = None): """__init__ method of ChunkParser class.""" self._callbacks_provider = callbacks_provider diff --git a/src/ansys/fluent/core/services/scheme_eval.py b/src/ansys/fluent/core/services/scheme_eval.py index 4428ae66e291..f6c3355828d4 100644 --- a/src/ansys/fluent/core/services/scheme_eval.py +++ b/src/ansys/fluent/core/services/scheme_eval.py @@ -88,7 +88,7 @@ def string_eval( def scheme_eval( self, request: SchemeEvalProtoModule.SchemeEvalRequest, - metadata: list[tuple[str, str]] = None, + metadata: list[tuple[str, str]] | None = None, ) -> SchemeEvalProtoModule.SchemeEvalResponse: """SchemeEval RPC of SchemeEval service.""" new_metadata = self.__metadata diff --git a/src/ansys/fluent/core/session_base_meshing.py b/src/ansys/fluent/core/session_base_meshing.py index 7a93cd3b5f4f..f438e038c214 100644 --- a/src/ansys/fluent/core/session_base_meshing.py +++ b/src/ansys/fluent/core/session_base_meshing.py @@ -333,7 +333,7 @@ def topology_based_meshing_workflow( def load_workflow( self, - file_path: PathType = None, + file_path: "PathType | None" = None, initialize: bool = True, legacy: bool | None = None, ): diff --git a/src/ansys/fluent/core/session_utilities.py b/src/ansys/fluent/core/session_utilities.py index fff2926411f3..66a3f9f878bb 100644 --- a/src/ansys/fluent/core/session_utilities.py +++ b/src/ansys/fluent/core/session_utilities.py @@ -62,7 +62,7 @@ def from_install( cls, ui_mode: UIMode | str | None = None, graphics_driver: ( - FluentWindowsGraphicsDriver | FluentLinuxGraphicsDriver | str + FluentWindowsGraphicsDriver | FluentLinuxGraphicsDriver | str | None ) = None, product_version: FluentVersion | str | float | int | None = None, dimension: Dimension | int | None = None, diff --git a/src/ansys/fluent/core/utils/deprecate.py b/src/ansys/fluent/core/utils/deprecate.py index 60cde218b29c..83088f3ac5f0 100644 --- a/src/ansys/fluent/core/utils/deprecate.py +++ b/src/ansys/fluent/core/utils/deprecate.py @@ -135,6 +135,7 @@ def wrapper(*args, **kwargs): ) return deprecated_func(*args, **kwargs) + wrapper.__signature__ = inspect.signature(func) return wrapper return decorator @@ -178,6 +179,7 @@ def wrapper(*args, **kwargs): warnings.warn(reason, warning_cls, stacklevel=2) return decorated(*args, **kwargs) + wrapper.__signature__ = inspect.signature(func) return wrapper return decorator diff --git a/tests/test_deprecate.py b/tests/test_deprecate.py index 6c5c2b7ba783..39aee2396c6d 100644 --- a/tests/test_deprecate.py +++ b/tests/test_deprecate.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import inspect import warnings import pytest @@ -155,3 +156,33 @@ def new_add(a, b): assert "3.0.0" in str(warning.message) assert result == 3 + + +def test_deprecate_arguments_preserves_signature(): + """Test that @deprecate_arguments preserves the original function signature.""" + + @deprecate_arguments(old_args="old_x", new_args="x", version="3.0.0") + def my_func(x: int, y: str = "hello") -> bool: + return True + + sig = inspect.signature(my_func) + param_names = list(sig.parameters.keys()) + assert param_names == ["x", "y"] + assert sig.parameters["x"].annotation is int + assert sig.parameters["y"].default == "hello" + assert sig.return_annotation is bool + + +def test_deprecate_function_preserves_signature(): + """Test that @deprecate_function preserves the original function signature.""" + + @deprecate_function(version="3.0.0", new_func="new_fn") + def old_fn(a: int, b: str = "world") -> float: + return 1.0 + + sig = inspect.signature(old_fn) + param_names = list(sig.parameters.keys()) + assert param_names == ["a", "b"] + assert sig.parameters["a"].annotation is int + assert sig.parameters["b"].default == "world" + assert sig.return_annotation is float From 4e537a3b4d06528e1c10c3e6e0147bec0ae9455d Mon Sep 17 00:00:00 2001 From: pyansys-ci-bot <92810346+pyansys-ci-bot@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:32:41 +0000 Subject: [PATCH 09/11] chore: adding changelog file 4973.fixed.md [dependabot-skip] --- doc/changelog.d/4973.fixed.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changelog.d/4973.fixed.md diff --git a/doc/changelog.d/4973.fixed.md b/doc/changelog.d/4973.fixed.md new file mode 100644 index 000000000000..4d311bbb4e5b --- /dev/null +++ b/doc/changelog.d/4973.fixed.md @@ -0,0 +1 @@ +Make type annotations runtime-safe for bear-type compatibility From e2ababe35ea16dd8a3eb1b5a468372d4850b58ba Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 12 Mar 2026 10:49:34 +0530 Subject: [PATCH 10/11] fix: make type annotatioons --- pyproject.toml | 1 + src/ansys/fluent/core/_types.py | 2 -- src/ansys/fluent/core/fluent_connection.py | 2 -- .../fluent/core/meshing/meshing_workflow.py | 2 -- .../core/meshing/meshing_workflow_new.py | 4 +-- src/ansys/fluent/core/solver/flobject.py | 2 -- src/ansys/fluent/core/solver/flunits.py | 2 -- src/ansys/fluent/core/ui/standalone_web_ui.py | 2 -- src/ansys/fluent/core/workflow.py | 28 +++++++++---------- src/ansys/fluent/core/workflow_new.py | 16 +++++------ 10 files changed, 22 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 606972420141..e184b3fe951a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ classifiers = [ dependencies = [ "ansys-api-fluent>=0.3.37", "ansys-platform-instancemanagement~=1.1", + "beartype>=0.18.0", "ansys-tools-common>=0.4.0", "ansys-tools-filetransfer>=0.2,<1.0", "ansys-units~=0.10.0", diff --git a/src/ansys/fluent/core/_types.py b/src/ansys/fluent/core/_types.py index bf074d4253e0..8b82c00dba8e 100644 --- a/src/ansys/fluent/core/_types.py +++ b/src/ansys/fluent/core/_types.py @@ -26,8 +26,6 @@ This module centralizes reusable typing constructs """ -from __future__ import annotations - import os from typing import TypeAlias diff --git a/src/ansys/fluent/core/fluent_connection.py b/src/ansys/fluent/core/fluent_connection.py index 926f86d80222..f11c077e0bb4 100644 --- a/src/ansys/fluent/core/fluent_connection.py +++ b/src/ansys/fluent/core/fluent_connection.py @@ -22,8 +22,6 @@ """Provides a module for Fluent connection functionality.""" -from __future__ import annotations - from contextlib import suppress import ctypes from ctypes import c_int, sizeof diff --git a/src/ansys/fluent/core/meshing/meshing_workflow.py b/src/ansys/fluent/core/meshing/meshing_workflow.py index 1ac53d825343..f57e0eaf87ad 100644 --- a/src/ansys/fluent/core/meshing/meshing_workflow.py +++ b/src/ansys/fluent/core/meshing/meshing_workflow.py @@ -23,8 +23,6 @@ """Meshing workflow specialization of the Workflow module that wraps and extends the core functionality.""" -from __future__ import annotations - from enum import Enum import os diff --git a/src/ansys/fluent/core/meshing/meshing_workflow_new.py b/src/ansys/fluent/core/meshing/meshing_workflow_new.py index 75fc2b5d47da..82897faac835 100644 --- a/src/ansys/fluent/core/meshing/meshing_workflow_new.py +++ b/src/ansys/fluent/core/meshing/meshing_workflow_new.py @@ -23,8 +23,6 @@ """Meshing workflow specialization of the Workflow module that wraps and extends the core functionality.""" -from __future__ import annotations - from enum import Enum import os @@ -271,7 +269,7 @@ def __init__( workflow: PyMenuGeneric, meshing: PyMenuGeneric, fluent_version: FluentVersion, - file_path: "PathType | None" = None, + file_path: PathType | None = None, initialize: bool = True, ) -> None: """Initialize a ``LoadWorkflow`` instance. diff --git a/src/ansys/fluent/core/solver/flobject.py b/src/ansys/fluent/core/solver/flobject.py index 5fb82cf9c500..c48fb45e680a 100644 --- a/src/ansys/fluent/core/solver/flobject.py +++ b/src/ansys/fluent/core/solver/flobject.py @@ -38,8 +38,6 @@ >>> r.boundary_conditions.velocity_inlet['inlet'].vmag.constant = 20 """ -from __future__ import annotations - import collections from contextlib import contextmanager, nullcontext, suppress import fnmatch diff --git a/src/ansys/fluent/core/solver/flunits.py b/src/ansys/fluent/core/solver/flunits.py index 2be657a15507..4a76fdfbb023 100644 --- a/src/ansys/fluent/core/solver/flunits.py +++ b/src/ansys/fluent/core/solver/flunits.py @@ -124,8 +124,6 @@ 'wave-length': 'Angstrom'} """ -from __future__ import annotations - from typing import TypeVar _fl_unit_table = { diff --git a/src/ansys/fluent/core/ui/standalone_web_ui.py b/src/ansys/fluent/core/ui/standalone_web_ui.py index c3f6385b96ca..1f938081aea6 100644 --- a/src/ansys/fluent/core/ui/standalone_web_ui.py +++ b/src/ansys/fluent/core/ui/standalone_web_ui.py @@ -22,8 +22,6 @@ """Web UI for Fluent settings using Panel with lazy loading and batched property access.""" -from __future__ import annotations - from typing import Any, Callable, Dict, List try: diff --git a/src/ansys/fluent/core/workflow.py b/src/ansys/fluent/core/workflow.py index 98a5d09050f6..6070763004f9 100644 --- a/src/ansys/fluent/core/workflow.py +++ b/src/ansys/fluent/core/workflow.py @@ -22,8 +22,6 @@ """Workflow module that wraps and extends the core functionality.""" -from __future__ import annotations - from contextlib import suppress import logging import re @@ -203,7 +201,7 @@ class BaseTask: def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize BaseTask. @@ -621,7 +619,7 @@ class TaskContainer(PyCallableStateObject): __dir__() """ - def __init__(self, command_source: ClassicWorkflow) -> None: + def __init__(self, command_source: "ClassicWorkflow") -> None: """Initialize TaskContainer. Parameters @@ -969,7 +967,7 @@ class CommandTask(BaseTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize CommandTask. @@ -984,7 +982,7 @@ def __init__( super().__init__(command_source, task) @property - def command_arguments(self) -> ReadOnlyObject: + def command_arguments(self) -> "ReadOnlyObject": """Get the task's arguments in read-only form (deprecated). Returns @@ -996,7 +994,7 @@ def command_arguments(self) -> ReadOnlyObject: return self._refreshed_command() @property - def _command_arguments(self) -> ReadOnlyObject: + def _command_arguments(self) -> "ReadOnlyObject": return self._refreshed_command() @property @@ -1010,7 +1008,7 @@ def arguments(self) -> ArgumentsWrapper: """ return ArgumentsWrapper(self) - def _refreshed_command(self) -> ReadOnlyObject: + def _refreshed_command(self) -> "ReadOnlyObject": task_arg_state = self._task.Arguments.get_state() cmd = self._command() if task_arg_state: @@ -1038,7 +1036,7 @@ class SimpleTask(CommandTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize SimpleTask. @@ -1066,7 +1064,7 @@ class CompoundChild(SimpleTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize CompoundChild. @@ -1111,7 +1109,7 @@ class CompositeTask(BaseTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize CompositeTask. @@ -1126,7 +1124,7 @@ def __init__( super().__init__(command_source, task) @property - def command_arguments(self) -> ReadOnlyObject: + def command_arguments(self) -> "ReadOnlyObject": """Get the task's arguments in read-only form (deprecated). Returns @@ -1138,7 +1136,7 @@ def command_arguments(self) -> ReadOnlyObject: return {} @property - def _command_arguments(self) -> ReadOnlyObject: + def _command_arguments(self) -> "ReadOnlyObject": return {} @property @@ -1163,7 +1161,7 @@ class ConditionalTask(CommandTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize ConditionalTask. @@ -1198,7 +1196,7 @@ class CompoundTask(CommandTask): def __init__( self, - command_source: Workflow, + command_source: "Workflow", task: str, ) -> None: """Initialize CompoundTask. diff --git a/src/ansys/fluent/core/workflow_new.py b/src/ansys/fluent/core/workflow_new.py index cb7edce33a03..ff8556ad33cf 100644 --- a/src/ansys/fluent/core/workflow_new.py +++ b/src/ansys/fluent/core/workflow_new.py @@ -43,8 +43,6 @@ simulation workflows, with automatic dependency management and validation. """ -from __future__ import annotations - from collections import OrderedDict from functools import wraps import inspect @@ -360,7 +358,7 @@ def task_names(self): """ return [name.split(":")[0] for name in self._workflow.task_object()] - def children(self) -> list[TaskObject]: + def children(self) -> "list[TaskObject]": """Get the top-level tasks in the workflow in display order. Returns an ordered list of the workflow's main tasks (those directly under @@ -396,7 +394,7 @@ def children(self) -> list[TaskObject]: return wrapped_tasks - def first_child(self) -> TaskObject | None: + def first_child(self) -> "TaskObject | None": """Get the first top-level task in the workflow. Returns @@ -437,7 +435,7 @@ def first_child(self) -> TaskObject | None: self._command_source, ) - def last_child(self) -> TaskObject | None: + def last_child(self) -> "TaskObject | None": """Get the last top-level task in the workflow. Returns @@ -506,7 +504,7 @@ def _ordered_tasks(self): return sorted_dict - def delete_tasks(self, list_of_tasks: list[TaskObject]): + def delete_tasks(self, list_of_tasks: "list[TaskObject]"): """Delete multiple tasks from the workflow. Removes the specified tasks from the workflow. Tasks are identified by TaskObject instances. @@ -537,7 +535,7 @@ def delete_tasks(self, list_of_tasks: list[TaskObject]): self._workflow.general.delete_tasks(list_of_tasks=items_to_be_deleted) @property - def insertable_tasks(self) -> FirstTask: + def insertable_tasks(self) -> "FirstTask": """Tasks that can be inserted into an empty workflow. Returns a helper that exposes the set of valid starting tasks for a blank @@ -688,7 +686,7 @@ def __init__( task_object: PyMenu, base_name: str, workflow: PyMenu, - parent: Workflow | TaskObject, + parent: "Workflow | TaskObject", meshing_root: PyMenu, ): """Initialize a TaskObject wrapper. @@ -812,7 +810,7 @@ def __init__(self, base_task): setattr(self, item, insertable_task) self._insertable_tasks.append(insertable_task) - def __call__(self) -> list[_Insert]: + def __call__(self) -> "list[_Insert]": """Get list of all insertable task objects. Returns From 58a86b3a858234ec1e643533a61d4cfb146961e7 Mon Sep 17 00:00:00 2001 From: mayankansys Date: Thu, 12 Mar 2026 12:04:31 +0530 Subject: [PATCH 11/11] fix: updated the beartype compatibility to multipe files --- src/ansys/fluent/core/__init__.py | 7 ++++++ src/ansys/fluent/core/_types.py | 2 +- src/ansys/fluent/core/examples/downloads.py | 4 ++-- src/ansys/fluent/core/filereader/case_file.py | 4 ++-- src/ansys/fluent/core/launcher/launcher.py | 19 +++++++++------ .../fluent/core/launcher/slurm_launcher.py | 8 +++---- .../core/launcher/standalone_launcher.py | 15 +++++++----- src/ansys/fluent/core/session.py | 10 ++++++++ src/ansys/fluent/core/session_base_meshing.py | 2 +- src/ansys/fluent/core/session_utilities.py | 8 +++---- src/ansys/fluent/core/solver/flobject.py | 24 +++++++++++++++++++ tests/test_field_data.py | 1 - 12 files changed, 76 insertions(+), 28 deletions(-) diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index cc94783aa26e..b1d7346957c3 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -22,6 +22,13 @@ """A package providing Fluent's Solver and Meshing capabilities in Python.""" +# NOTE: beartype_this_package() is intentionally NOT used here. +# `ansys` and `ansys.fluent` are implicit PEP 420 namespace packages +# (no __init__.py at those levels). beartype's package-level hook +# requires a regular package with __init__.py at the namespace root. +# The @beartype decorator is applied manually to public API functions instead. +# Upstream tracking: https://github.com/beartype/beartype/issues/286 + import os import pydoc import warnings diff --git a/src/ansys/fluent/core/_types.py b/src/ansys/fluent/core/_types.py index 8b82c00dba8e..8c2332ae0673 100644 --- a/src/ansys/fluent/core/_types.py +++ b/src/ansys/fluent/core/_types.py @@ -29,5 +29,5 @@ import os from typing import TypeAlias -PathType: TypeAlias = "os.PathLike[str] | os.PathLike[bytes] | str | bytes" +PathType: TypeAlias = os.PathLike[str] | os.PathLike[bytes] | str | bytes """Type alias for file system paths.""" diff --git a/src/ansys/fluent/core/examples/downloads.py b/src/ansys/fluent/core/examples/downloads.py index 199881d1c2b0..d1d894b12edd 100644 --- a/src/ansys/fluent/core/examples/downloads.py +++ b/src/ansys/fluent/core/examples/downloads.py @@ -73,7 +73,7 @@ def _get_file_url(file_name: str, directory: str | None = None) -> str: def _retrieve_file( url: str, file_name: str, - save_path: "PathType | None" = None, + save_path: PathType | None = None, return_without_path: bool | None = False, ) -> str: """Download specified file from specified URL.""" @@ -120,7 +120,7 @@ def _retrieve_file( def download_file( file_name: str, directory: str | None = None, - save_path: "PathType | None" = None, + save_path: PathType | None = None, return_without_path: bool | None = None, ) -> str: """Download specified example file from the Ansys example data repository. diff --git a/src/ansys/fluent/core/filereader/case_file.py b/src/ansys/fluent/core/filereader/case_file.py index cd0ad4a4e5d9..3518a4a5a92f 100644 --- a/src/ansys/fluent/core/filereader/case_file.py +++ b/src/ansys/fluent/core/filereader/case_file.py @@ -617,8 +617,8 @@ class CaseFile(RPVarProcessor): def __init__( self, - case_file_name: "PathType | None" = None, - project_file_name: "PathType | None" = None, + case_file_name: PathType | None = None, + project_file_name: PathType | None = None, ) -> None: """Initialize a CaseFile object. Exactly one file path argument must be specified. diff --git a/src/ansys/fluent/core/launcher/launcher.py b/src/ansys/fluent/core/launcher/launcher.py index d2ab19a2ddbd..3e1a31106205 100644 --- a/src/ansys/fluent/core/launcher/launcher.py +++ b/src/ansys/fluent/core/launcher/launcher.py @@ -29,9 +29,11 @@ import inspect import logging import os -from typing import Any, Dict +from typing import Any from warnings import warn +from beartype import beartype + from ansys.fluent.core._types import PathType from ansys.fluent.core.exceptions import DisallowedValuesError from ansys.fluent.core.fluent_connection import FluentConnection @@ -77,8 +79,9 @@ logger = logging.getLogger("pyfluent.launcher") +@beartype def create_launcher( - fluent_launch_mode: LaunchMode = LaunchMode.STANDALONE, **kwargs + fluent_launch_mode: LaunchMode = LaunchMode.STANDALONE, **kwargs: Any ) -> DockerLauncher | PIMLauncher | SlurmLauncher | StandaloneLauncher: """Use the factory function to create a launcher for supported launch modes. @@ -170,6 +173,7 @@ def _custom_converter_dimension(kwargs): version="v0.22.0", converter=_custom_converter_dimension, ) +@beartype def launch_fluent( product_version: FluentVersion | str | float | int | None = None, dimension: Dimension | int | None = None, @@ -178,7 +182,7 @@ def launch_fluent( journal_file_names: None | str | list[str] = None, start_timeout: int | None = None, additional_arguments: str = "", - env: Dict[str, Any] | None = None, + env: dict[str, Any] | None = None, start_container: bool | None = None, container_dict: dict | None = None, dry_run: bool = False, @@ -188,14 +192,14 @@ def launch_fluent( graphics_driver: ( FluentWindowsGraphicsDriver | FluentLinuxGraphicsDriver | str | None ) = None, - case_file_name: "PathType | None" = None, - case_data_file_name: "PathType | None" = None, + case_file_name: PathType | None = None, + case_data_file_name: PathType | None = None, lightweight_mode: bool | None = None, mode: FluentMode | str | None = None, py: bool | None = None, gpu: bool | list[int] | None = None, - cwd: "PathType | None" = None, - fluent_path: "PathType | None" = None, + cwd: PathType | None = None, + fluent_path: PathType | None = None, topy: str | list | None = None, start_watchdog: bool | None = None, scheduler_options: dict | None = None, @@ -410,6 +414,7 @@ def _mode_to_launcher_type(fluent_launch_mode: LaunchMode): return launcher() +@beartype def connect_to_fluent( ip: str | None = None, port: int | None = None, diff --git a/src/ansys/fluent/core/launcher/slurm_launcher.py b/src/ansys/fluent/core/launcher/slurm_launcher.py index 5cb2b67818ec..bedff9918650 100644 --- a/src/ansys/fluent/core/launcher/slurm_launcher.py +++ b/src/ansys/fluent/core/launcher/slurm_launcher.py @@ -397,13 +397,13 @@ def __init__( env: Dict[str, Any] | None = None, cleanup_on_exit: bool = True, start_transcript: bool = True, - case_file_name: "PathType | None" = None, - case_data_file_name: "PathType | None" = None, + case_file_name: PathType | None = None, + case_data_file_name: PathType | None = None, lightweight_mode: bool | None = None, py: bool | None = None, gpu: bool | None = None, - cwd: "PathType | None" = None, - fluent_path: "PathType | None" = None, + cwd: PathType | None = None, + fluent_path: PathType | None = None, topy: str | list | None = None, start_watchdog: bool | None = None, scheduler_options: dict | None = None, diff --git a/src/ansys/fluent/core/launcher/standalone_launcher.py b/src/ansys/fluent/core/launcher/standalone_launcher.py index ab624d76b741..3aac2fb1f161 100644 --- a/src/ansys/fluent/core/launcher/standalone_launcher.py +++ b/src/ansys/fluent/core/launcher/standalone_launcher.py @@ -40,7 +40,9 @@ import os from pathlib import Path import subprocess -from typing import Any, Dict +from typing import Any + +from beartype import beartype from ansys.fluent.core._types import PathType from ansys.fluent.core.launcher.error_handler import ( @@ -77,6 +79,7 @@ class StandaloneLauncher: """Instantiates Fluent session in standalone mode.""" + @beartype def __init__( self, mode: FluentMode | str | None = None, @@ -91,17 +94,17 @@ def __init__( journal_file_names: None | str | list[str] = None, start_timeout: int = 60, additional_arguments: str = "", - env: Dict[str, Any] | None = None, + env: dict[str, Any] | None = None, cleanup_on_exit: bool = True, dry_run: bool = False, start_transcript: bool = True, - case_file_name: "PathType | None" = None, - case_data_file_name: "PathType | None" = None, + case_file_name: PathType | None = None, + case_data_file_name: PathType | None = None, lightweight_mode: bool | None = None, py: bool | None = None, gpu: bool | None = None, - cwd: "PathType | None" = None, - fluent_path: "PathType | None" = None, + cwd: PathType | None = None, + fluent_path: PathType | None = None, topy: str | list | None = None, start_watchdog: bool | None = None, file_transfer_service: Any | None = None, diff --git a/src/ansys/fluent/core/session.py b/src/ansys/fluent/core/session.py index 423fa213b0c3..6d80d4a7a157 100644 --- a/src/ansys/fluent/core/session.py +++ b/src/ansys/fluent/core/session.py @@ -30,6 +30,7 @@ import warnings import weakref +from beartype import beartype from deprecated.sphinx import deprecated from ansys.fluent.core._types import PathType @@ -263,6 +264,7 @@ def _build_from_fluent_connection( self._fluent_connection.register_finalizer_cb(obj.stop) @deprecate_function(version="v0.38.0", new_func="is_active") + @beartype def is_server_healthy(self) -> bool: """Whether the current session is healthy (i.e. the server is 'SERVING').""" return self._is_server_healthy() @@ -271,6 +273,7 @@ def _is_server_healthy(self) -> bool: """Whether the current session is healthy (i.e. the server is 'SERVING').""" return self._health_check.is_serving + @beartype def is_active(self) -> bool: """Whether the current session is active.""" return self._fluent_connection is not None and self._is_server_healthy() @@ -308,6 +311,7 @@ def field_data_streaming(self): return self.fields.field_data_streaming @property + @beartype def id(self) -> str: """Return the session ID.""" return self._fluent_connection._id @@ -379,10 +383,12 @@ def _create_from_server_info_file( ) return session + @beartype def execute_tui(self, command: str) -> None: """Executes a tui command.""" self.scheme.eval(f"(ti-menu-load-string {json.dumps(command)})") + @beartype def get_fluent_version(self) -> FluentVersion: """Gets and returns the fluent version.""" return FluentVersion(self.scheme.version) @@ -415,6 +421,7 @@ def wait_process_finished(self, wait: float | int | bool = 60): """ return self._fluent_connection_backup.wait_process_finished() + @beartype def exit(self, **kwargs) -> None: """Exit session. @@ -431,11 +438,13 @@ def _exit(self, **kwargs) -> None: self._fluent_connection.exit(**kwargs) self._fluent_connection = None + @beartype def force_exit(self) -> None: """Forces the Fluent session to exit, losing unsaved progress and data.""" self._exit_compose_service() self._fluent_connection.force_exit() + @beartype def file_exists_on_remote(self, file_name: str) -> bool: """Check if remote file exists. @@ -493,6 +502,7 @@ def download(self, file_name: str, local_directory: str | None = None): ) return self._file_transfer_service.download(file_name, local_directory) + @beartype def chdir(self, path: PathType) -> None: """Change Fluent working directory. diff --git a/src/ansys/fluent/core/session_base_meshing.py b/src/ansys/fluent/core/session_base_meshing.py index f438e038c214..1b0799eb1e76 100644 --- a/src/ansys/fluent/core/session_base_meshing.py +++ b/src/ansys/fluent/core/session_base_meshing.py @@ -333,7 +333,7 @@ def topology_based_meshing_workflow( def load_workflow( self, - file_path: "PathType | None" = None, + file_path: PathType | None = None, initialize: bool = True, legacy: bool | None = None, ): diff --git a/src/ansys/fluent/core/session_utilities.py b/src/ansys/fluent/core/session_utilities.py index 66a3f9f878bb..57fea5f56151 100644 --- a/src/ansys/fluent/core/session_utilities.py +++ b/src/ansys/fluent/core/session_utilities.py @@ -75,13 +75,13 @@ def from_install( cleanup_on_exit: bool = True, dry_run: bool = False, start_transcript: bool = True, - case_file_name: "PathType | None" = None, - case_data_file_name: "PathType | None" = None, + case_file_name: PathType | None = None, + case_data_file_name: PathType | None = None, lightweight_mode: bool | None = None, py: bool | None = None, gpu: bool | None = None, - cwd: "PathType | None" = None, - fluent_path: "PathType | None" = None, + cwd: PathType | None = None, + fluent_path: PathType | None = None, topy: str | list | None = None, start_watchdog: bool | None = None, file_transfer_service: Any | None = None, diff --git a/src/ansys/fluent/core/solver/flobject.py b/src/ansys/fluent/core/solver/flobject.py index c48fb45e680a..4c79cc540919 100644 --- a/src/ansys/fluent/core/solver/flobject.py +++ b/src/ansys/fluent/core/solver/flobject.py @@ -65,6 +65,7 @@ _eval_type, get_args, get_origin, + no_type_check, ) import warnings import weakref @@ -274,6 +275,9 @@ def _is_deprecated(obj) -> bool | None: ) +# no_type_check: __setattr__ unconditionally raises AttributeError, preventing +# beartype from writing wrapper attributes on instances or subclasses. +@no_type_check class Base: """Provides the base class for settings and command objects. @@ -795,6 +799,9 @@ def _create_child(cls, name, parent: weakref.CallableProxyType, alias_path=None) return cls(name, parent) +# no_type_check: generic proxy base; instances communicate with a live gRPC +# service; attribute writes intercepted by Base.__setattr__. +@no_type_check class SettingsBase(Base, Generic[StateT]): """Base class for settings objects. @@ -1053,6 +1060,9 @@ def _get_type_for_completer_info(cls) -> str: return cls.__bases__[0].__name__ +# no_type_check: defines __setattr__ and __getattribute__ proxying to gRPC; +# beartype inspection would trigger unintended network calls. +@no_type_check class Group(SettingsBase[DictStateType]): """A ``Group`` container object. @@ -1234,6 +1244,8 @@ def __setattr__(self, name: str, value): raise +# no_type_check: defines __getattr__ proxying wildcard paths to gRPC. +@no_type_check class WildcardPath(Group): """Class wrapping a wildcard path to perform get_var and set_var on flproxy.""" @@ -1330,6 +1342,9 @@ def __setitem__(self, name, value): ChildTypeT = TypeVar("ChildTypeT") +# no_type_check: defines __getattr__ proxying to gRPC; subclasses generated +# dynamically by get_cls() factory and are not statically analysable. +@no_type_check class NamedObject(SettingsBase[DictStateType], Generic[ChildTypeT]): """A ``NamedObject`` container is a container object similar to a Python dictionary object. Generally, many such objects can be created with different names. @@ -1593,6 +1608,9 @@ def _rename(obj: NamedObject | _Alias, new: str, old: str): obj._create_child_object(new) +# no_type_check: defines __getattr__ proxying to gRPC; subclasses generated +# dynamically by get_cls() factory. +@no_type_check class ListObject(SettingsBase[ListStateType], Generic[ChildTypeT]): """A ``ListObject`` container is a container object, similar to a Python list object. Generally, many such objects can be created. @@ -1737,6 +1755,9 @@ def _get_new_keywords(obj, *args, **kwds): return newkwds +# no_type_check: defines __getattr__; Command/Query subclasses are +# dynamically generated by get_cls() factory. +@no_type_check class Action(Base): """Intermediate Base class for Command and Query class.""" @@ -2148,6 +2169,9 @@ def __set__(self, instance, value): # pylint: disable=missing-raises-doc +# no_type_check: runtime class factory using type(); produces classes +# dynamically from Fluent metadata and is not statically analysable. +@no_type_check def get_cls(name, info, parent=None, version=None, parent_taboo=None): """Create a class for the object identified by "path".""" try: diff --git a/tests/test_field_data.py b/tests/test_field_data.py index ff1fe73d80ee..233ba86c2faa 100644 --- a/tests/test_field_data.py +++ b/tests/test_field_data.py @@ -37,7 +37,6 @@ ) from ansys.fluent.core.examples.downloads import download_file from ansys.fluent.core.exceptions import DisallowedValuesError -import ansys.fluent.core.field_data_interfaces as field_data_interfaces_module from ansys.fluent.core.field_data_interfaces import ( FieldUnavailable, _Fields,