Skip to content

Commit

Permalink
Protect execution from specific exceptions (#2677)
Browse files Browse the repository at this point in the history
* Protecting exit from I/O error

* Adding condition to decorator.
  • Loading branch information
germa89 authored Jan 22, 2024
1 parent b9aa292 commit 20c4c73
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 12 deletions.
50 changes: 50 additions & 0 deletions src/ansys/mapdl/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from functools import wraps
import signal
import threading
from typing import Callable, Optional

from grpc._channel import _InactiveRpcError, _MultiThreadedRendezvous

Expand Down Expand Up @@ -336,3 +337,52 @@ def wrapper(*args, **kwargs):
return out

return wrapper


def protect_from(
exception, match: Optional[str] = None, condition: Optional[bool] = None
) -> Callable:
"""Protect the decorated method from raising an exception of
of a given type.
You can filter the exceptions by using 'match' and/or 'condition'. If both
are given, **both** need to be fulfilled. If you only need one or the other,
you can use multiple decorators.
Parameters
----------
exception : Exception
Exception to catch.
match : optional
String against to match the exception, by default None
condition : optional
Condition that needs to be fulfil to catch the exception, by default None
Returns
-------
Callable
Decorated function
Raises
------
e
The given exception if not caught by the internal try.
"""

def decorator(function):
@wraps(function)
def wrapper(self, *args, **kwargs):
try:
return function(self, *args, **kwargs)
except exception as e:
if (match is None or match in str(e)) and (
condition is None or condition
):
pass
# everything else raises
else:
raise e

return wrapper

return decorator
2 changes: 2 additions & 0 deletions src/ansys/mapdl/core/mapdl_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
MapdlConnectionError,
MapdlExitedError,
MapdlRuntimeError,
protect_from,
protect_grpc,
)
from ansys.mapdl.core.mapdl import MapdlBase
Expand Down Expand Up @@ -963,6 +964,7 @@ def _threaded_heartbeat(self):
except Exception:
continue

@protect_from(ValueError, "I/O operation on closed file.")
def exit(self, save=False, force=False):
"""Exit MAPDL.
Expand Down
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,17 @@ def __exit__(self, *args) -> None:
pymapdl.RUNNING_TESTS = not self._state


class NullContext:
def __enter__(self):
pass

def __exit__(self, *args):
pass

def __init__(self):
pass


@pytest.fixture(scope="function")
def running_test():
return Running_test
Expand Down
42 changes: 42 additions & 0 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
MapdlException,
MapdlInvalidRoutineError,
MapdlRuntimeError,
protect_from,
)
from conftest import NullContext

error_shape_error_limits = """
Expand Down Expand Up @@ -144,3 +146,43 @@ def test_error_handler(mapdl):
text = "*** ERROR ***\n This is my own errorrrr"
with pytest.raises(MapdlRuntimeError, match="errorrrr"):
mapdl._raise_errors(text)


@pytest.mark.parametrize(
# Tests inputs
"message,condition,context",
[
(None, None, NullContext()),
("My custom error", None, NullContext()),
("my error", None, pytest.raises(ValueError)),
(None, None, NullContext()),
(None, True, NullContext()),
(None, False, pytest.raises(ValueError)),
("my error", False, pytest.raises(ValueError)),
("my error", True, pytest.raises(ValueError)),
("My custom error", False, pytest.raises(ValueError)),
("My custom error", True, NullContext()),
],
# Test ids
ids=[
"Match any message. No condition",
"Match message. No condition",
"Raises an exception. No condition (raise internal exception)",
"No message. No condition",
"No message. True condition",
"No message. False condition (raise internal exception)",
"Different error message. False condition (raise internal exception)",
"Different error message. True condition (raise internal exception)",
"Same error message. False condition (raise internal exception)",
"Same error message. True condition",
],
)
def test_protect_from(message, condition, context):
class myclass:
@protect_from(ValueError, message, condition)
def raising(self):
raise ValueError("My custom error")

with context:
myobj = myclass()
myobj.raising()
13 changes: 1 addition & 12 deletions tests/test_importing_geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
MapdlInvalidRoutineError,
MapdlRuntimeError,
)
from conftest import ON_CI, ON_LOCAL, ON_UBUNTU
from conftest import ON_CI, ON_LOCAL, ON_UBUNTU, NullContext

PATH = os.path.dirname(os.path.abspath(__file__))

Expand Down Expand Up @@ -74,17 +74,6 @@ def clear_wkdir_from_cads(mapdl):
pass


class NullContext:
def __enter__(self):
pass

def __exit__(self, *args):
pass

def __init__(self):
pass


## IGES
#
def test_readin_igs(mapdl, cleared):
Expand Down

0 comments on commit 20c4c73

Please sign in to comment.