Skip to content
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

FEAT: Add a Message Manager for App #1068

Merged
merged 27 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
28ef38d
init
dipinknair Jan 14, 2025
7b1074c
init
dipinknair Jan 14, 2025
f9de88e
add msg for app
dipinknair Jan 22, 2025
fc16515
add test file
dipinknair Jan 22, 2025
aaa510d
add messages for app
dipinknair Jan 27, 2025
320e260
Ansys.ACT.Core
dipinknair Jan 27, 2025
aeac997
chore: adding changelog file 1068.added.md [dependabot-skip]
pyansys-ci-bot Jan 27, 2025
63de2a9
revert lic
dipinknair Jan 27, 2025
721b31f
Merge branch 'fix/add_message' of https://github.com/ansys/pymechanic…
dipinknair Jan 27, 2025
3d6e3df
Merge branch 'main' into fix/add_message
dipinknair Jan 28, 2025
46d016d
add len and remove count
dipinknair Jan 28, 2025
11fa7c1
Merge branch 'main' of https://github.com/ansys/pymechanical into fix…
dipinknair Jan 29, 2025
e976cde
review suggestion
dipinknair Jan 30, 2025
13ba3b0
Merge branch 'main' of https://github.com/ansys/pymechanical into fix…
dipinknair Jan 30, 2025
8f79b35
pr review suggestions and remove functionality
dipinknair Feb 4, 2025
2fde9f5
remove Msg class
dipinknair Feb 4, 2025
44b2b97
update tests
dipinknair Feb 4, 2025
7e59d8f
remove test.py
dipinknair Feb 5, 2025
08ec5ce
review suggestion
dipinknair Feb 5, 2025
5b9776e
Merge branch 'main' into fix/add_message
dipinknair Feb 5, 2025
1cc5dd2
add _message back
dipinknair Feb 5, 2025
a0d3358
add app.new
dipinknair Feb 5, 2025
dad3f90
add clear before all tests
dipinknair Feb 7, 2025
bde03ff
Merge branch 'main' into fix/add_message
dipinknair Feb 7, 2025
6318579
update test
dipinknair Feb 7, 2025
1cfff12
Merge branch 'fix/add_message' of https://github.com/ansys/pymechanic…
dipinknair Feb 10, 2025
17c3196
change assert for timestamp
Feb 10, 2025
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/1068.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a Message Manager for App
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dependencies = [
"psutil==6.1.1",
"tqdm>=4.45.0",
"requests>=2,<3",
"pandas==2.2.3",
]

[project.urls]
Expand Down
10 changes: 10 additions & 0 deletions src/ansys/mechanical/core/embedding/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from ansys.mechanical.core.embedding.addins import AddinConfiguration
from ansys.mechanical.core.embedding.appdata import UniqueUserProfile
from ansys.mechanical.core.embedding.imports import global_entry_points, global_variables
from ansys.mechanical.core.embedding.messages import MessageManager
dipinknair marked this conversation as resolved.
Show resolved Hide resolved
from ansys.mechanical.core.embedding.poster import Poster
from ansys.mechanical.core.embedding.ui import launch_ui
from ansys.mechanical.core.embedding.warnings import connect_warnings, disconnect_warnings
Expand Down Expand Up @@ -191,6 +192,7 @@ def __init__(self, db_file=None, private_appdata=False, **kwargs):
INSTANCES.append(self)
self._updated_scopes: typing.List[typing.Dict[str, typing.Any]] = []
self._subscribe()
self._messages = None

def __repr__(self):
"""Get the product info."""
Expand Down Expand Up @@ -437,6 +439,14 @@ def project_directory(self):
"""Returns the current project directory."""
return self.DataModel.Project.ProjectDirectory

@property
def messages(self):
dipinknair marked this conversation as resolved.
Show resolved Hide resolved
"""Lazy-load the MessageManager."""
if self._messages is None:
self._messages = MessageManager(self._app)
self._messages._update_messages_cache()
return self._messages

def _share(self, other) -> None:
"""Shares the state of self with other.

Expand Down
251 changes: 251 additions & 0 deletions src/ansys/mechanical/core/embedding/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
# Copyright (C) 2022 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Message Manager for App."""

import pandas as pd


class MessageManager:
"""Message manager for adding, fetching, and printing messages."""

def __init__(self, app):
"""Initialize the message manager."""
self._app = app

# Import necessary classes
from Ansys.Mechanical.Application import Message
from Ansys.Mechanical.DataModel.Enums import MessageSeverityType

self.MessageSeverityType = MessageSeverityType
self.Message = Message

# Initialize a local cache for messages
self._messages_df = pd.DataFrame()
self._update_messages_cache()

def _update_messages_cache(self):
"""Update the local cache of messages."""
data = {
"Severity": [],
"TimeStamp": [],
"DisplayString": [],
"Source": [],
"StringID": [],
"Location": [],
"RelatedObjects": [],
}
for msg in self._app.ExtAPI.Application.Messages:
data["Severity"].append(str(msg.Severity).upper())
data["TimeStamp"].append(msg.TimeStamp)
data["DisplayString"].append(msg.DisplayString)
data["Source"].append(msg.Source)
data["StringID"].append(msg.StringID)
data["Location"].append(msg.Location)
data["RelatedObjects"].append(msg.RelatedObjects)

self._messages_df = pd.DataFrame(data)

def __repr__(self):
"""Provide a DataFrame representation of all messages."""
return repr(self._messages_df)

def __str__(self):
"""Provide a custom string representation of the messages."""
if self._messages_df.empty:
return "No messages to display."

formatted_messages = [
f"[{row['Severity']}] : {row['DisplayString']}"
for _, row in self._messages_df.iterrows()
]
return "\n".join(formatted_messages)

def __getitem__(self, index):
"""Allow indexed access to messages."""
if self._messages_df.empty:
raise IndexError("No messages are available.")
if index >= len(self._messages_df) or index < 0:
raise IndexError("Message index out of range.")
row = self._messages_df.iloc[index]
return _Message(row)

def __len__(self):
"""Return the number of messages."""
return len(self._messages_df)

def add(self, severity: str, text: str):
"""Add a message and update the cache.

Parameters
----------
severity : str
Severity of the message. Can be "info", "warning", or "error".
text : str
Message text.

Examples
--------
>>> app.messages.add("info", "User clicked the start button.")
"""
severity_map = {
"info": self.MessageSeverityType.Info,
"warning": self.MessageSeverityType.Warning,
"error": self.MessageSeverityType.Error,
}

if severity.lower() not in severity_map:
raise ValueError(f"Invalid severity: {severity}")

_msg = self.Message(text, severity_map[severity.lower()])
self._app.ExtAPI.Application.Messages.Add(_msg)

self._update_messages_cache()

def show(self, filter="severity;message"):
"""Print all messages with full details.

dipinknair marked this conversation as resolved.
Show resolved Hide resolved
Parameters
----------
filter : str, optional
Semicolon separated list of message attributes to display.
Default is "severity;message".
if filter is "*", all available attributes will be displayed.

Examples
--------
>>> app.messages.show()
... severity: info
... message: User clicked the start button.

>>> app.messages.show(filter="time_stamp;severity;message")
... time_stamp: 1/30/2025 12:10:35 PM
... severity: info
... message: User clicked the start button.
"""
self._update_messages_cache()

if self._messages_df.empty:
print("No messages to display.")
return

for _, row in self._messages_df.iterrows():
msg = _Message(row)
msg.show(filter)

def clear(self):
"""Clear all messages."""
self._app.ExtAPI.Application.Messages.Clear()
self._update_messages_cache()


class _Message:
dipinknair marked this conversation as resolved.
Show resolved Hide resolved
"""Lightweight message object for individual message handling."""

def __init__(self, row):
"""Initialize with a row from the DataFrame."""
self.row = row

@property
def message(self):
"""Return the message text."""
return self.row["DisplayString"]

@property
def severity(self):
"""Return the message severity."""
return self.row["Severity"].lower()

@property
def time_stamp(self):
"""Return the message timestamp."""
return str(self.row["TimeStamp"])

@property
def source(self):
"""Return the message source."""
return self.row["Source"]

@property
def string_id(self):
"""Return the message string ID."""
return self.row["StringID"]

@property
def location(self):
"""Return the message location."""
return self.row["Location"]

@property
def related_objects(self):
"""Return the message related objects."""
return self.row["RelatedObjects"]

def show(self, filter="severity;message"):
"""Show the message details.

Parameters
----------
filter : str, optional
Semicolon separated list of message attributes to display.
Default is "severity;message".
if filter is "*", all available attributes will be displayed.
other options are "time_stamp", "source", "location", "related_objects".

Examples
--------
>>> app.messages[0].show()
... severity: info
... message: User clicked the start button.

>>> app.messages[0].show(filter="*")
... severity: info
... message: User clicked the start button.
... time_stamp: 1/30/2025 12:10:35 PM
... source: None
... string_id: None
... location: Ansys.ACT.Core.Utilities.SelectionInfo
... related_objects: None
"""
if filter == "*":
selected_columns = [
"time_stamp",
"severity",
"message",
"source",
"string_id",
"location",
"related_objects",
]
else:
selected_columns = [col.strip() for col in filter.split(";")]

for key in selected_columns:
print(f"{key}: {getattr(self, key, "Specified filter not found.")}")

def __str__(self):
"""Provide a string representation of the message."""
return f"[{self.row['Severity']}] {self.row['DisplayString']}"

def __repr__(self):
"""Provide a string representation of the message."""
return repr(self.row)
15 changes: 15 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from ansys.mechanical.core import App

app = App(version=251)
app.open(r"D:\PyAnsys\Repos\exp\pymechanical\tests\assets\cube-hole.mechdb")
app.message
app.message.print()
app.message.add("info", "User clicked the start button.")
app.message.print(complete_info=True)

# app.message.print() # Combines messages from ExtAPI and local messages
# app.message.add("DEBUG", "User clicked the start button.")
# app.message.print() # Combines messages from ExtAPI and local messages

# app.message.clear_local_messages()
# app.message.print() # Only messages
Loading
Loading