Skip to content

Commit 1299992

Browse files
RedansysPProfizi
andauthored
feat: improve server errors handling (#2638)
Co-authored-by: Paul Profizi <[email protected]>
1 parent 2427af6 commit 1299992

File tree

3 files changed

+78
-9
lines changed

3 files changed

+78
-9
lines changed

src/ansys/dpf/gate/errors.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
import types
2+
import sys
23
from functools import wraps
34

45
class DPFServerException(Exception):
56
"""Error raised when the DPF server has encountered an error."""
67

78
def __init__(self, msg=""):
8-
Exception.__init__(self, msg)
9+
message_parts = msg.rsplit('<-', maxsplit=1)
10+
error_note = ""
11+
if len(message_parts) == 1:
12+
error_message = message_parts[0]
13+
else:
14+
error_note, error_message = message_parts
15+
16+
Exception.__init__(self, error_message)
17+
if sys.version_info >= (3, 11): #add_note method is supported only in python >= 3.11
18+
self.add_note(error_note)
19+
else:
20+
if not hasattr(self, "__notes__"): #if the system is python < 3.11 we custom our own notes property
21+
self.__notes__ = []
22+
self.__notes__.append(error_note)
923

1024

1125
class DPFServerNullObject(Exception):

tests/test_server_errors.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright (C) 2020 - 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
import pytest
24+
25+
import ansys.dpf.core as dpf
26+
from ansys.dpf.core import errors, operators as ops
27+
28+
29+
def test_server_exception_from_operator():
30+
ds = dpf.DataSources(r"dummy/file.rst")
31+
op = ops.result.displacement(data_sources=ds)
32+
with pytest.raises(errors.DPFServerException) as exception_note:
33+
op.eval()
34+
35+
exception = exception_note.value
36+
assert hasattr(exception, "__notes__"), "The exception does not contain any note"
37+
assert exception.__notes__
38+
39+
40+
def test_server_exception_from_workflow():
41+
op = dpf.operators.result.displacement(data_sources=dpf.DataSources("toto.rst"))
42+
43+
wf = dpf.Workflow()
44+
wf.add_operator(op)
45+
wf.set_output_name("out", op.outputs.fields_container)
46+
47+
with pytest.raises(errors.DPFServerException) as exception_note:
48+
wf.get_output("out", output_type=dpf.FieldsContainer)
49+
50+
exception = exception_note.value
51+
assert hasattr(exception, "__notes__"), "The exception does not contain any note"
52+
assert exception.__notes__

tox.ini

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
[tox]
3030
description = Default tox environment list and core configurations
3131

32-
envlist = pretest,test-{api,documentation,launcher,server,local_server,multi_server,api_entry,custom_type_field,operators,workflow,remote_workflow,remote_operator,service},posttest,kill-servers
32+
envlist = pretest,test-{api,documentation,launcher,server,local_server,multi_server,api_entry,custom_type_field,operators,workflow,remote_workflow,remote_operator,service,server_errors},posttest,kill-servers
3333

3434
labels =
35-
localparalleltests = pretest,test-{api,documentation,launcher,server,local_server,multi_server,custom_type_field,operators},posttest,kill-servers
35+
localparalleltests = pretest,test-{api,documentation,launcher,server,local_server,multi_server,custom_type_field,operators,server_errors},posttest,kill-servers
3636
othertests = pretest,test-{workflow,remote_workflow,remote_operator,service},posttest,kill-servers
37-
ciparalleltests = test-{api,documentation,launcher,local_server,multi_server,custom_type_field,operators},kill-servers
37+
ciparalleltests = test-{api,documentation,launcher,local_server,multi_server,custom_type_field,operators,server_errors},kill-servers
3838

3939
isolated_build_env = build
4040

@@ -70,7 +70,7 @@ commands =
7070
[testenv:kill-servers]
7171
description = Environment for clearing running servers
7272

73-
depends = test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators}
73+
depends = test-{api,documentation,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators,server_errors}
7474

7575
deps =
7676
psutil
@@ -95,14 +95,14 @@ commands_pre =
9595
commands =
9696
python -c "\
9797
import os, shutil; \
98-
test_data=['test_documentation','test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service','test_custom_type_field']; \
98+
test_data=['test_documentation','test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service','test_custom_type_field','test_server_errors']; \
9999
[(os.makedirs(d, exist_ok=True), shutil.copy('tests/conftest.py', d), shutil.copy(f'tests/\{d}.py', d) if os.path.exists(f'tests/\{d}.py') else None) for d in test_data]; \
100100
[os.remove(f'tests/\{d}.py') for d in test_data if os.path.exists(f'tests/\{d}.py')]"
101101

102102
[testenv:posttest]
103103
description = Environment to revert test files to original state after testing
104104

105-
depends = pretest, test-{api,documentation,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators}
105+
depends = pretest, test-{api,documentation,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators,server_errors}
106106

107107
skip_install = True
108108

@@ -113,11 +113,11 @@ commands_pre =
113113
commands =
114114
python -c "\
115115
import os, shutil; \
116-
test_data=['test_documentation','test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service', 'test_custom_type_field']; \
116+
test_data=['test_documentation','test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service', 'test_custom_type_field','test_server_errors']; \
117117
[shutil.move(f'\{d}/\{d}.py', f'tests/\{d}.py') for d in test_data if os.path.exists(f'\{d}/\{d}.py')]; \
118118
[shutil.rmtree(d) for d in test_data if os.path.exists(d)]"
119119

120-
[testenv:test-{api,documentation,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators}]
120+
[testenv:test-{api,documentation,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators,server_errors}]
121121
description =
122122
Environment for running
123123
api: api tests
@@ -133,6 +133,7 @@ description =
133133
api_entry: api entry tests
134134
custom_type_field: custom-type field tests
135135
operators: operators tests
136+
server_errors: server error wrapping tests
136137

137138
depends = pretest
138139

@@ -156,6 +157,7 @@ setenv =
156157
custom_type_field: JUNITXML = --junitxml=tests/junit/test-results11.xml -o junit_family=legacy
157158
operators: JUNITXML = --junitxml=tests/junit/test-results12.xml -o junit_family=legacy
158159
documentation: JUNITXML = --junitxml=tests/junit/test-results13.xml -o junit_family=legacy
160+
server_errors: JUNITXML = --junitxml=tests/junit/test-results14.xml -o junit_family=legacy
159161

160162
# Tests sets
161163
api: PYTEST_PYTHON_FILES = tests
@@ -171,6 +173,7 @@ setenv =
171173
custom_type_field: PYTEST_PYTHON_FILES = test_custom_type_field
172174
operators: PYTEST_PYTHON_FILES = tests/operators
173175
documentation: PYTEST_PYTHON_FILES = test_documentation
176+
server_errors: PYTEST_PYTHON_FILES = test_server_errors
174177

175178
TEMP = {env_tmp_dir}
176179
TMP = {env_tmp_dir}

0 commit comments

Comments
 (0)