From 8044bf1b18ecc23f8d918b5abc6a701f44af6ad4 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Wed, 27 Nov 2024 10:43:12 -0500 Subject: [PATCH 1/4] feat: Event info classes --- .../streaming_services/events_streaming.py | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) diff --git a/src/ansys/fluent/core/streaming_services/events_streaming.py b/src/ansys/fluent/core/streaming_services/events_streaming.py index a7b79a6f59e..39de3e7e757 100644 --- a/src/ansys/fluent/core/streaming_services/events_streaming.py +++ b/src/ansys/fluent/core/streaming_services/events_streaming.py @@ -1,5 +1,6 @@ """Module for events management.""" +from dataclasses import dataclass from enum import Enum from functools import partial import inspect @@ -70,6 +71,208 @@ def _missing_(cls, value: str): return _missing_for_events(cls, value) +@dataclass +class TimestepStartedEventInfo: + """Information about the event triggered when a timestep is started. + Attributes + ---------- + index : int + Timestep index. + size : float + Timestep size. + """ + + index: int + size: float + + +@dataclass +class TimestepEndedEventInfo: + """Information about the event triggered when a timestep is ended. + Attributes + ---------- + index : int + Timestep index. + size : float + Timestep size. + """ + + index: int + size: float + + +@dataclass +class IterationEndedEventInfo: + """Information about the event triggered when an iteration is ended. + Attributes + ---------- + index : int + Iteration index. + """ + + index: int + + +class CalculationsStartedEventInfo: + """Information about the event triggered when calculations are started.""" + + +class CalculationsEndedEventInfo: + """Information about the event triggered when calculations are ended.""" + + +class CalculationsPausedEventInfo: + """Information about the event triggered when calculations are paused.""" + + +class CalculationsResumedEventInfo: + """Information about the event triggered when calculations are resumed.""" + + +@dataclass +class AboutToLoadCaseEventInfo: + """Information about the event triggered just before a case file is loaded. + Attributes + ---------- + case_file : str + Case file path. + """ + + case_file: str + + +@dataclass +class CaseLoadedEventInfo: + """Information about the event triggered after a case file is loaded. + Attributes + ---------- + case_file : str + Case file path. + """ + + case_file: str + + +@dataclass +class AboutToLoadDataEventInfo: + """Information about the event triggered just before a data file is loaded. + Attributes + ---------- + data_file : str + Data file path. + """ + + data_file: str + + +@dataclass +class DataLoadedEventInfo: + """Information about the event triggered after a data file is loaded. + Attributes + ---------- + data_file : str + Data file path. + """ + + data_file: str + + +class AboutToInitializeSolutionEventInfo: + """Information about the event triggered just before solution is initialized.""" + + +class SolutionInitializedEventInfo: + """Information about the event triggered after solution is initialized.""" + + +@dataclass +class ReportDefinitionUpdatedEventInfo: + """Information about the event triggered when a report definition is updated. + Attributes + ---------- + report_name : str + Report name. + """ + + report_name: str + + +@dataclass +class ReportPlotSetUpdatedEventInfo: + """Information about the event triggered when a report plot set is updated. + Attributes + ---------- + plot_set_name : str + Plot set name. + """ + + plot_set_name: str + + +class ResidualPlotUpdatedEventInfo: + """Information about the event triggered when residual plots are updated.""" + + +class SettingsClearedEventInfo: + """Information about the event triggered when settings are cleared.""" + + +@dataclass +class SolutionPausedEventInfo: + """Information about the event triggered when solution is paused. + Attributes + ---------- + level : str + Level of the pause event. + index : int + Index of the pause event. + """ + + level: str + index: int + + +@dataclass +class ProgressUpdatedEventInfo: + """Information about the event triggered when progress is updated. + Attributes + ---------- + message : str + percentage : int + """ + + message: str + percentage: int + + +@dataclass +class SolverTimeEstimateUpdatedEventInfo: + """Information about the event triggered when solver time estimate is updated. + Attributes + ---------- + hours : float + minutes : float + seconds : float + """ + + hours: float + minutes: float + seconds: float + + +@dataclass +class FatalErrorEventInfo: + """Information about the event triggered when a fatal error occurs. + Attributes + ---------- + message : str + error_code : int + """ + + message: str + error_code: int + + TEvent = TypeVar("TEvent") From 94404663dad921f5865cf5e2c58df97fa585153e Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Wed, 27 Nov 2024 12:43:44 -0500 Subject: [PATCH 2/4] feat: event info classes --- src/ansys/fluent/core/__init__.py | 6 +- .../streaming_services/events_streaming.py | 138 ++++++++++++++---- 2 files changed, 107 insertions(+), 37 deletions(-) diff --git a/src/ansys/fluent/core/__init__.py b/src/ansys/fluent/core/__init__.py index 61d4651a2d2..6fcdacbf8d1 100644 --- a/src/ansys/fluent/core/__init__.py +++ b/src/ansys/fluent/core/__init__.py @@ -31,11 +31,7 @@ from ansys.fluent.core.search import search # noqa: F401 from ansys.fluent.core.services.batch_ops import BatchOps # noqa: F401 from ansys.fluent.core.session import BaseSession as Fluent # noqa: F401 -from ansys.fluent.core.streaming_services.events_streaming import ( # noqa: F401 - Event, - MeshingEvent, - SolverEvent, -) +from ansys.fluent.core.streaming_services.events_streaming import * # noqa: F401, F403 from ansys.fluent.core.utils import fldoc, get_examples_download_dir 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 diff --git a/src/ansys/fluent/core/streaming_services/events_streaming.py b/src/ansys/fluent/core/streaming_services/events_streaming.py index 39de3e7e757..68b110854d6 100644 --- a/src/ansys/fluent/core/streaming_services/events_streaming.py +++ b/src/ansys/fluent/core/streaming_services/events_streaming.py @@ -1,6 +1,6 @@ """Module for events management.""" -from dataclasses import dataclass +from dataclasses import dataclass, fields from enum import Enum from functools import partial import inspect @@ -8,11 +8,40 @@ from typing import Callable, Generic, Literal, Type, TypeVar import warnings +from google.protobuf.json_format import MessageToDict + from ansys.api.fluent.v0 import events_pb2 as EventsProtoModule from ansys.fluent.core.exceptions import InvalidArgument from ansys.fluent.core.streaming_services.streaming import StreamingService from ansys.fluent.core.warnings import PyFluentDeprecationWarning +__all__ = [ + "Event", + "SolverEvent", + "MeshingEvent", + "TimestepStartedEventInfo", + "TimestepEndedEventInfo", + "IterationEndedEventInfo", + "CalculationsStartedEventInfo", + "CalculationsEndedEventInfo", + "CalculationsPausedEventInfo", + "CalculationsResumedEventInfo", + "AboutToLoadCaseEventInfo", + "CaseLoadedEventInfo", + "AboutToLoadDataEventInfo", + "DataLoadedEventInfo", + "AboutToInitializeSolutionEventInfo", + "SolutionInitializedEventInfo", + "ReportDefinitionUpdatedEventInfo", + "ReportPlotSetUpdatedEventInfo", + "ResidualPlotUpdatedEventInfo", + "SettingsClearedEventInfo", + "SolutionPausedEventInfo", + "ProgressUpdatedEventInfo", + "SolverTimeEstimateUpdatedEventInfo", + "FatalErrorEventInfo", +] + network_logger = logging.getLogger("pyfluent.networking") @@ -71,8 +100,23 @@ def _missing_(cls, value: str): return _missing_for_events(cls, value) +class EventInfoBase: + """Base class for event information classes.""" + + derived_classes = {} + + def __init_subclass__(cls, event, **kwargs): + super().__init_subclass__(**kwargs) + cls.derived_classes[event] = cls + + def __post_init__(self): + for field in fields(self): + # Cast to the correct type + setattr(self, field.name, field.type(getattr(self, field.name))) + + @dataclass -class TimestepStartedEventInfo: +class TimestepStartedEventInfo(EventInfoBase, event=SolverEvent.TIMESTEP_STARTED): """Information about the event triggered when a timestep is started. Attributes ---------- @@ -87,7 +131,7 @@ class TimestepStartedEventInfo: @dataclass -class TimestepEndedEventInfo: +class TimestepEndedEventInfo(EventInfoBase, event=SolverEvent.TIMESTEP_ENDED): """Information about the event triggered when a timestep is ended. Attributes ---------- @@ -102,7 +146,7 @@ class TimestepEndedEventInfo: @dataclass -class IterationEndedEventInfo: +class IterationEndedEventInfo(EventInfoBase, event=SolverEvent.ITERATION_ENDED): """Information about the event triggered when an iteration is ended. Attributes ---------- @@ -113,24 +157,28 @@ class IterationEndedEventInfo: index: int -class CalculationsStartedEventInfo: +class CalculationsStartedEventInfo( + EventInfoBase, event=SolverEvent.CALCULATIONS_STARTED +): """Information about the event triggered when calculations are started.""" -class CalculationsEndedEventInfo: +class CalculationsEndedEventInfo(EventInfoBase, event=SolverEvent.CALCULATIONS_ENDED): """Information about the event triggered when calculations are ended.""" -class CalculationsPausedEventInfo: +class CalculationsPausedEventInfo(EventInfoBase, event=SolverEvent.CALCULATIONS_PAUSED): """Information about the event triggered when calculations are paused.""" -class CalculationsResumedEventInfo: +class CalculationsResumedEventInfo( + EventInfoBase, event=SolverEvent.CALCULATIONS_RESUMED +): """Information about the event triggered when calculations are resumed.""" @dataclass -class AboutToLoadCaseEventInfo: +class AboutToLoadCaseEventInfo(EventInfoBase, event=SolverEvent.ABOUT_TO_LOAD_CASE): """Information about the event triggered just before a case file is loaded. Attributes ---------- @@ -142,7 +190,7 @@ class AboutToLoadCaseEventInfo: @dataclass -class CaseLoadedEventInfo: +class CaseLoadedEventInfo(EventInfoBase, event=SolverEvent.CASE_LOADED): """Information about the event triggered after a case file is loaded. Attributes ---------- @@ -154,7 +202,7 @@ class CaseLoadedEventInfo: @dataclass -class AboutToLoadDataEventInfo: +class AboutToLoadDataEventInfo(EventInfoBase, event=SolverEvent.ABOUT_TO_LOAD_DATA): """Information about the event triggered just before a data file is loaded. Attributes ---------- @@ -166,7 +214,7 @@ class AboutToLoadDataEventInfo: @dataclass -class DataLoadedEventInfo: +class DataLoadedEventInfo(EventInfoBase, event=SolverEvent.DATA_LOADED): """Information about the event triggered after a data file is loaded. Attributes ---------- @@ -177,16 +225,22 @@ class DataLoadedEventInfo: data_file: str -class AboutToInitializeSolutionEventInfo: +class AboutToInitializeSolutionEventInfo( + EventInfoBase, event=SolverEvent.ABOUT_TO_INITIALIZE_SOLUTION +): """Information about the event triggered just before solution is initialized.""" -class SolutionInitializedEventInfo: +class SolutionInitializedEventInfo( + EventInfoBase, event=SolverEvent.SOLUTION_INITIALIZED +): """Information about the event triggered after solution is initialized.""" @dataclass -class ReportDefinitionUpdatedEventInfo: +class ReportDefinitionUpdatedEventInfo( + EventInfoBase, event=SolverEvent.REPORT_DEFINITION_UPDATED +): """Information about the event triggered when a report definition is updated. Attributes ---------- @@ -198,7 +252,9 @@ class ReportDefinitionUpdatedEventInfo: @dataclass -class ReportPlotSetUpdatedEventInfo: +class ReportPlotSetUpdatedEventInfo( + EventInfoBase, event=SolverEvent.REPORT_PLOT_SET_UPDATED +): """Information about the event triggered when a report plot set is updated. Attributes ---------- @@ -209,16 +265,18 @@ class ReportPlotSetUpdatedEventInfo: plot_set_name: str -class ResidualPlotUpdatedEventInfo: +class ResidualPlotUpdatedEventInfo( + EventInfoBase, event=SolverEvent.RESIDUAL_PLOT_UPDATED +): """Information about the event triggered when residual plots are updated.""" -class SettingsClearedEventInfo: +class SettingsClearedEventInfo(EventInfoBase, event=SolverEvent.SETTINGS_CLEARED): """Information about the event triggered when settings are cleared.""" @dataclass -class SolutionPausedEventInfo: +class SolutionPausedEventInfo(EventInfoBase, event=SolverEvent.SOLUTION_PAUSED): """Information about the event triggered when solution is paused. Attributes ---------- @@ -233,7 +291,7 @@ class SolutionPausedEventInfo: @dataclass -class ProgressUpdatedEventInfo: +class ProgressUpdatedEventInfo(EventInfoBase, event=SolverEvent.PROGRESS_UPDATED): """Information about the event triggered when progress is updated. Attributes ---------- @@ -246,7 +304,9 @@ class ProgressUpdatedEventInfo: @dataclass -class SolverTimeEstimateUpdatedEventInfo: +class SolverTimeEstimateUpdatedEventInfo( + EventInfoBase, event=SolverEvent.SOLVER_TIME_ESTIMATE_UPDATED +): """Information about the event triggered when solver time estimate is updated. Attributes ---------- @@ -261,7 +321,7 @@ class SolverTimeEstimateUpdatedEventInfo: @dataclass -class FatalErrorEventInfo: +class FatalErrorEventInfo(EventInfoBase, event=SolverEvent.FATAL_ERROR): """Information about the event triggered when a fatal error occurs. Attributes ---------- @@ -303,6 +363,18 @@ def __init__( self._session = session self._sync_event_ids = {} + def _construct_event_info( + self, response: EventsProtoModule.BeginStreamingResponse, event: TEvent + ): + event_info_msg = getattr(response, event.value.lower()) + event_info_dict = MessageToDict( + event_info_msg, including_default_value_fields=True + ) + solver_event = SolverEvent(event.value) + event_info_cls = EventInfoBase.derived_classes.get(solver_event) + # Key names can be different, but their order is the same + return event_info_cls(*event_info_dict.values()) + def _process_streaming( self, service, id, stream_begin_method, started_evt, *args, **kwargs ): @@ -332,7 +404,7 @@ def _process_streaming( for callback in callbacks_map.values(): callback( session=self._session, - event_info=getattr(response, event_name.value.lower()), + event_info=self._construct_event_info(response, event_name), ) except StopIteration: break @@ -450,7 +522,7 @@ def _register_solution_event_sync_callback( callback_id: str, callback: Callable, ) -> tuple[Literal[SolverEvent.SOLUTION_PAUSED], Callable]: - unique_id = self._session.scheme_eval.scheme_eval( + unique_id: int = self._session.scheme_eval.scheme_eval( f""" (let ((ids @@ -480,14 +552,16 @@ def _register_solution_event_sync_callback( """ ) - def on_pause(session, event_info: EventsProtoModule.AutoPauseEvent): - if unique_id == event_info.level: - event_info_cls = ( - EventsProtoModule.TimestepEndedEvent - if event_type == SolverEvent.TIMESTEP_ENDED - else EventsProtoModule.IterationEndedEvent - ) - event_info = event_info_cls(index=event_info.index) + def on_pause(session, event_info: SolutionPausedEventInfo): + if unique_id == int(event_info.level): + if event_type == SolverEvent.ITERATION_ENDED: + event_info = IterationEndedEventInfo(index=event_info.index) + else: + event_info = TimestepEndedEventInfo( + # TODO: Timestep size is currently not available + index=event_info.index, + size=0, + ) try: callback(session, event_info) except Exception as e: From fc354bcbbcd69a9581f23a7c8cc97fa29ee27c56 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Wed, 27 Nov 2024 15:51:08 -0500 Subject: [PATCH 3/4] feat: backward campat --- .../streaming_services/events_streaming.py | 55 ++++++++++++------- tests/test_events_manager.py | 14 +++-- tests/test_launcher.py | 3 +- 3 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/ansys/fluent/core/streaming_services/events_streaming.py b/src/ansys/fluent/core/streaming_services/events_streaming.py index 68b110854d6..1d73b02c6aa 100644 --- a/src/ansys/fluent/core/streaming_services/events_streaming.py +++ b/src/ansys/fluent/core/streaming_services/events_streaming.py @@ -1,6 +1,6 @@ """Module for events management.""" -from dataclasses import dataclass, fields +from dataclasses import dataclass, field, fields from enum import Enum from functools import partial import inspect @@ -110,9 +110,19 @@ def __init_subclass__(cls, event, **kwargs): cls.derived_classes[event] = cls def __post_init__(self): - for field in fields(self): + for f in fields(self): # Cast to the correct type - setattr(self, field.name, field.type(getattr(self, field.name))) + setattr(self, f.name, f.type(getattr(self, f.name))) + + def __getattr__(self, name): + for f in fields(self): + if f.metadata.get("deprecated_name") == name: + warnings.warn( + f"'{name}' is deprecated. Use '{f.name}' instead.", + PyFluentDeprecationWarning, + ) + return getattr(self, f.name) + return self.__getattribute__(name) @dataclass @@ -182,11 +192,11 @@ class AboutToLoadCaseEventInfo(EventInfoBase, event=SolverEvent.ABOUT_TO_LOAD_CA """Information about the event triggered just before a case file is loaded. Attributes ---------- - case_file : str - Case file path. + case_file_name : str + Case filename. """ - case_file: str + case_file_name: str = field(metadata=dict(deprecated_name="casefilepath")) @dataclass @@ -194,11 +204,11 @@ class CaseLoadedEventInfo(EventInfoBase, event=SolverEvent.CASE_LOADED): """Information about the event triggered after a case file is loaded. Attributes ---------- - case_file : str - Case file path. + case_file_name : str + Case filename. """ - case_file: str + case_file_name: str = field(metadata=dict(deprecated_name="casefilepath")) @dataclass @@ -206,11 +216,11 @@ class AboutToLoadDataEventInfo(EventInfoBase, event=SolverEvent.ABOUT_TO_LOAD_DA """Information about the event triggered just before a data file is loaded. Attributes ---------- - data_file : str - Data file path. + data_file_name : str + Data filename. """ - data_file: str + data_file_name: str = field(metadata=dict(deprecated_name="datafilepath")) @dataclass @@ -218,11 +228,11 @@ class DataLoadedEventInfo(EventInfoBase, event=SolverEvent.DATA_LOADED): """Information about the event triggered after a data file is loaded. Attributes ---------- - data_file : str - Data file path. + data_file_name : str + Data filename. """ - data_file: str + data_file_name: str = field(metadata=dict(deprecated_name="datafilepath")) class AboutToInitializeSolutionEventInfo( @@ -248,7 +258,7 @@ class ReportDefinitionUpdatedEventInfo( Report name. """ - report_name: str + report_name: str = field(metadata=dict(deprecated_name="reportdefinitionname")) @dataclass @@ -262,7 +272,7 @@ class ReportPlotSetUpdatedEventInfo( Plot set name. """ - plot_set_name: str + plot_set_name: str = field(metadata=dict(deprecated_name="plotsetname")) class ResidualPlotUpdatedEventInfo( @@ -296,11 +306,13 @@ class ProgressUpdatedEventInfo(EventInfoBase, event=SolverEvent.PROGRESS_UPDATED Attributes ---------- message : str + Progress message. percentage : int + Progress percentage. """ message: str - percentage: int + percentage: int = field(metadata=dict(deprecated_name="percentComplete")) @dataclass @@ -311,8 +323,11 @@ class SolverTimeEstimateUpdatedEventInfo( Attributes ---------- hours : float + Hours of solver time estimate. minutes : float + Minutes of solver time estimate. seconds : float + Seconds of solver time estimate. """ hours: float @@ -326,11 +341,13 @@ class FatalErrorEventInfo(EventInfoBase, event=SolverEvent.FATAL_ERROR): Attributes ---------- message : str + Error message. error_code : int + Error code. """ message: str - error_code: int + error_code: int = field(metadata=dict(deprecated_name="errorCode")) TEvent = TypeVar("TEvent") diff --git a/tests/test_events_manager.py b/tests/test_events_manager.py index 11bd0ca1b27..02b4b8a2451 100644 --- a/tests/test_events_manager.py +++ b/tests/test_events_manager.py @@ -1,7 +1,10 @@ +from pathlib import Path + import pytest import ansys.fluent.core as pyfluent from ansys.fluent.core import MeshingEvent, SolverEvent, examples +from ansys.fluent.core.warnings import PyFluentDeprecationWarning def test_receive_events_on_case_loaded(new_solver_session) -> None: @@ -18,6 +21,9 @@ def on_case_loaded_old_with_args(x, y, session_id, event_info): def on_case_loaded(session, event_info): on_case_loaded.loaded = True + assert Path(event_info.case_file_name).name == Path(case_file_name).name + with pytest.warns(PyFluentDeprecationWarning): + assert Path(event_info.casefilepath).name == Path(case_file_name).name on_case_loaded.loaded = False @@ -62,6 +68,10 @@ def on_case_loaded_with_args(x, y, session, event_info): def test_receive_meshing_events_on_case_loaded(new_meshing_session) -> None: + case_file_name = examples.download_file( + "mixing_elbow.cas.h5", "pyfluent/mixing_elbow" + ) + def on_case_loaded(session, event_info): on_case_loaded.loaded = True @@ -71,10 +81,6 @@ def on_case_loaded(session, event_info): meshing.events.register_callback(MeshingEvent.CASE_LOADED, on_case_loaded) - case_file_name = examples.download_file( - "mixing_elbow.cas.h5", "pyfluent/mixing_elbow" - ) - assert not on_case_loaded.loaded meshing.tui.file.read_case(case_file_name) diff --git a/tests/test_launcher.py b/tests/test_launcher.py index 114bfdd4de5..90be7b30019 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -412,9 +412,8 @@ def test_build_journal_argument(topy, journal_file_names, result, raises): assert _build_journal_argument(topy, journal_file_names) == result -@pytest.mark.filterwarnings("error::FutureWarning") def test_show_gui_raises_warning(): - with pytest.raises(PyFluentDeprecationWarning): + with pytest.warns(PyFluentDeprecationWarning): pyfluent.launch_fluent(show_gui=True) From 9f1d29fee1fcdc32de581da4ff37a6cc49e0dcf4 Mon Sep 17 00:00:00 2001 From: Mainak Kundu Date: Thu, 28 Nov 2024 09:33:44 -0500 Subject: [PATCH 4/4] fix: test --- tests/test_events_manager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_events_manager.py b/tests/test_events_manager.py index 02b4b8a2451..925ed39de3e 100644 --- a/tests/test_events_manager.py +++ b/tests/test_events_manager.py @@ -3,7 +3,7 @@ import pytest import ansys.fluent.core as pyfluent -from ansys.fluent.core import MeshingEvent, SolverEvent, examples +from ansys.fluent.core import FluentVersion, MeshingEvent, SolverEvent, examples from ansys.fluent.core.warnings import PyFluentDeprecationWarning @@ -21,9 +21,10 @@ def on_case_loaded_old_with_args(x, y, session_id, event_info): def on_case_loaded(session, event_info): on_case_loaded.loaded = True - assert Path(event_info.case_file_name).name == Path(case_file_name).name - with pytest.warns(PyFluentDeprecationWarning): - assert Path(event_info.casefilepath).name == Path(case_file_name).name + if session.get_fluent_version() >= FluentVersion.v232: + assert Path(event_info.case_file_name).name == Path(case_file_name).name + with pytest.warns(PyFluentDeprecationWarning): + assert Path(event_info.casefilepath).name == Path(case_file_name).name on_case_loaded.loaded = False