vantage6-algorithm-tools 5.0.2rc2__tar.gz → 5.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/PKG-INFO +3 -3
  2. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/pyproject.toml +2 -2
  3. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/_test_preprocessing.py +5 -5
  4. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/algorithm/test___init__.py +1 -5
  5. vantage6_algorithm_tools-5.0.3/tests/tools/__init__.py +0 -0
  6. vantage6_algorithm_tools-5.0.3/tests/tools/test_error_handling.py +36 -0
  7. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/client/__init__.py +6 -7
  8. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/data_extraction/__init__.py +10 -10
  9. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/data_extraction/mock_extract.py +3 -2
  10. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/action.py +3 -2
  11. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/algorithm_client.py +2 -1
  12. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/data.py +5 -4
  13. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/ohdsi.py +4 -3
  14. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/client.py +55 -53
  15. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/hq.py +4 -4
  16. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/network.py +3 -1
  17. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/node.py +5 -1
  18. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/aggregation.py +3 -3
  19. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/column.py +5 -5
  20. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/datetime.py +7 -7
  21. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/encoding.py +8 -8
  22. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/filtering.py +8 -8
  23. vantage6_algorithm_tools-5.0.3/vantage6/algorithm/tools/error_handling.py +48 -0
  24. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/tools/util.py +1 -1
  25. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/tools/wrap.py +7 -6
  26. vantage6_algorithm_tools-5.0.2rc2/vantage6/algorithm/tools/error_handling.py +0 -59
  27. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/.gitignore +0 -0
  28. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/Makefile +0 -0
  29. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/README.md +0 -0
  30. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/__init__.py +0 -0
  31. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/_test_deserialization.py +0 -0
  32. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/_test_docker_wrapper.py +0 -0
  33. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/_test_serialization.py +0 -0
  34. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/algorithm/__init__.py +0 -0
  35. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/algorithm_module.py +0 -0
  36. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/base/__init__.py +0 -0
  37. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/__init__.py +0 -0
  38. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_algorithm_client.py +0 -0
  39. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_network.py +0 -0
  40. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_node.py +0 -0
  41. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_server.py +0 -0
  42. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_user_client.py +0 -0
  43. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/tests/mock/test_util.py +0 -0
  44. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/__init__.py +3 -3
  45. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/decorator/metadata.py +0 -0
  46. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/__init__.py +0 -0
  47. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/globals.py +0 -0
  48. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/mock/util.py +0 -0
  49. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/__init__.py +0 -0
  50. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/preprocessing/algorithm_json_data.py +0 -0
  51. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/tools/__init__.py +0 -0
  52. {vantage6_algorithm_tools-5.0.2rc2 → vantage6_algorithm_tools-5.0.3}/vantage6/algorithm/tools/exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: vantage6-algorithm-tools
3
- Version: 5.0.2rc2
3
+ Version: 5.0.3
4
4
  Summary: Vantage6 algorithm tools
5
5
  Author: Vantage6 Team
6
6
  Maintainer-email: Frank Martin <f.martin@iknl.nl>, Bart van Beusekom <b.vanbeusekom@iknl.nl>
@@ -14,7 +14,7 @@ Requires-Dist: pyfiglet==1.0.4
14
14
  Requires-Dist: pyjwt==2.13.0
15
15
  Requires-Dist: sparqlwrapper>=2.0.0
16
16
  Requires-Dist: sqlalchemy>=1.4.46
17
- Requires-Dist: vantage6-common==5.0.2rc2
17
+ Requires-Dist: vantage6-common==5.0.3
18
18
  Provides-Extra: dev
19
19
  Requires-Dist: black; extra == 'dev'
20
20
  Requires-Dist: coverage==7.10.2; extra == 'dev'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vantage6-algorithm-tools"
7
- version = "5.0.2rc2"
7
+ version = "5.0.3"
8
8
  description = "Vantage6 algorithm tools"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "pyfiglet==1.0.4",
28
28
  "SPARQLWrapper>=2.0.0",
29
29
  "sqlalchemy>=1.4.46",
30
- "vantage6-common==5.0.2rc2",
30
+ "vantage6-common==5.0.3",
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
@@ -6,7 +6,7 @@ python -m unittest vantage6-algorithm-tools.tests.test_preprocessing
6
6
  """
7
7
 
8
8
  import unittest
9
- from datetime import datetime, timedelta
9
+ from datetime import UTC, datetime, timedelta
10
10
 
11
11
  import numpy as np
12
12
  import pandas as pd
@@ -16,7 +16,7 @@ from vantage6.algorithm.preprocessing.filtering import select_rows
16
16
 
17
17
 
18
18
  def random_date(
19
- start: datetime, end: datetime, fmt: str = "%Y-%m-%d", seed: int = None
19
+ start: datetime, end: datetime, fmt: str = "%Y-%m-%d", seed: int | None = None
20
20
  ) -> str:
21
21
  """
22
22
  Generate a random date between start and end.
@@ -537,8 +537,8 @@ class TestPreprocessing(unittest.TestCase):
537
537
  df = get_test_dataframe()
538
538
 
539
539
  # Set start and end dates for random date generation
540
- start_date = datetime(2020, 1, 1)
541
- end_date = datetime(2022, 12, 31)
540
+ start_date = datetime(2020, 1, 1, tzinfo=UTC)
541
+ end_date = datetime(2022, 12, 31, tzinfo=UTC)
542
542
 
543
543
  # Generate a new column with random dates in string format
544
544
  magic_fmt = "(%d)+(%m)=(%Y)"
@@ -637,7 +637,7 @@ class TestPreprocessing(unittest.TestCase):
637
637
  '{"age":{"171":23,"342":23},"income":{"171":62026,"342":44074},'
638
638
  '"education":{"171":"Master","342":"Master"},"color_preference":'
639
639
  '{"171":"Green","342":"Green"},"purchased_product":{"171":1,"342":'
640
- '0},"magic_date":{"171":"2022-11-12","342":"2020-01-20"},"income\/'
640
+ r'0},"magic_date":{"171":"2022-11-12","342":"2020-01-20"},"income\/'
641
641
  'age":{"171":0.9737126326,"342":0.0147479299},"age_days":{"171":'
642
642
  '8395,"342":8395},"age_days_td":{"171":"8395 days","342":"8395 '
643
643
  'days"}}'
@@ -88,11 +88,7 @@ class TestAlgorithmClient(unittest.TestCase):
88
88
  results = self.client.result.from_task(task_id=1)
89
89
  self.assertEqual(results[0], {"foo": "bar"})
90
90
 
91
- @patch("vantage6.algorithm.client.AlgorithmClient._multi_page_request")
92
- @patch("vantage6.common.client.client_base.ClientBase._download_run_data_from_hq")
93
- def test_result_from_task_azure(
94
- self, mock_download_run_data, mock_multi_page_request
95
- ):
91
+ def test_result_from_task_azure(self):
96
92
  with (
97
93
  patch.object(
98
94
  self.client.result.parent, "_multi_page_request"
File without changes
@@ -0,0 +1,36 @@
1
+ import traceback
2
+ import unittest
3
+
4
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
5
+ from vantage6.algorithm.tools.exceptions import AlgorithmRuntimeError, UserInputError
6
+
7
+
8
+ class TestHandleDataErrors(unittest.TestCase):
9
+ def test_non_pandas_error_is_sanitized(self):
10
+ """Errors that are not raised by vantage6 should not leak any details."""
11
+
12
+ @handle_data_errors
13
+ def func():
14
+ raise ValueError("could not convert string to float: 'secret_42'")
15
+
16
+ with self.assertRaises(AlgorithmRuntimeError) as ctx:
17
+ func()
18
+
19
+ self.assertNotIn("secret_42", str(ctx.exception))
20
+ # the original error should not be printed when the traceback is logged
21
+ traceback_ = "".join(traceback.format_exception(ctx.exception))
22
+ self.assertNotIn("secret_42", traceback_)
23
+
24
+ def test_algorithm_errors_pass_through(self):
25
+ """Vantage6 errors are meant for the user and should not be replaced."""
26
+
27
+ @handle_data_errors
28
+ def func():
29
+ raise UserInputError("old_names and new_names must have the same length")
30
+
31
+ with self.assertRaises(UserInputError):
32
+ func()
33
+
34
+
35
+ if __name__ == "__main__":
36
+ unittest.main()
@@ -136,7 +136,7 @@ class AlgorithmClient(ClientBase):
136
136
 
137
137
  return self.result.from_task(task_id)
138
138
 
139
- def _multi_page_request(self, endpoint: str, params: dict = None) -> dict:
139
+ def _multi_page_request(self, endpoint: str, params: dict | None = None) -> dict:
140
140
  """
141
141
  Make multiple requests to HQ to get all pages of a list of results.
142
142
 
@@ -247,9 +247,8 @@ class AlgorithmClient(ClientBase):
247
247
  result = json.loads(
248
248
  base64s_to_bytes(response.get("result")).decode()
249
249
  )
250
- except Exception as e:
251
- self.parent.log.error("Unable to load results")
252
- self.parent.log.exception(e)
250
+ except Exception:
251
+ self.parent.log.exception("Unable to load results")
253
252
  return result
254
253
 
255
254
  def from_task(self, task_id: int) -> list[Any]:
@@ -288,7 +287,7 @@ class AlgorithmClient(ClientBase):
288
287
  return json.loads(run_data.decode(STRING_ENCODING))
289
288
  else:
290
289
  return json.loads(base64s_to_bytes(result_data).decode())
291
- except Exception as e:
290
+ except Exception as e: # noqa: BLE001
292
291
  self.parent.log.error(
293
292
  f"Unable to load results for task {task_id}: {e}"
294
293
  )
@@ -328,9 +327,9 @@ class AlgorithmClient(ClientBase):
328
327
  self,
329
328
  method: str,
330
329
  arguments: dict | None = None,
331
- organizations: list[int] = None,
330
+ organizations: list[int] | None = None,
332
331
  name: str = "subtask",
333
- description: str = None,
332
+ description: str | None = None,
334
333
  ) -> dict:
335
334
  """
336
335
  Create a new (child) task at HQ.
@@ -7,7 +7,7 @@ from sqlalchemy import create_engine
7
7
 
8
8
  from vantage6.common import error, info
9
9
 
10
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
10
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
11
11
  from vantage6.algorithm.tools.exceptions import DataReadError
12
12
 
13
13
  from vantage6.algorithm.decorator.action import data_extraction
@@ -15,7 +15,7 @@ from vantage6.algorithm.decorator.action import data_extraction
15
15
  _SPARQL_RETURN_FORMAT = CSV
16
16
 
17
17
 
18
- @handle_pandas_errors
18
+ @handle_data_errors
19
19
  @data_extraction
20
20
  def read_csv(connection_details: dict) -> pd.DataFrame:
21
21
  """
@@ -48,14 +48,14 @@ def _read_csv(connection_details: dict) -> pd.DataFrame:
48
48
  error(f"File not found: {e}")
49
49
  # pylint: disable=raise-missing-from
50
50
  raise DataReadError(f"File not found: {e}")
51
- except Exception as e:
51
+ except Exception as e: # noqa: BLE001
52
52
  error(f"Error reading CSV file: {e}")
53
53
  # pylint: disable=raise-missing-from
54
54
  raise DataReadError(f"Error reading CSV file: {e}")
55
55
  return df
56
56
 
57
57
 
58
- @handle_pandas_errors
58
+ @handle_data_errors
59
59
  @data_extraction
60
60
  def read_parquet(connection_details: dict) -> pd.DataFrame:
61
61
  """
@@ -87,14 +87,14 @@ def _read_parquet(connection_details: dict) -> pd.DataFrame:
87
87
  error(f"File not found: {e}")
88
88
  # pylint: disable=raise-missing-from
89
89
  raise DataReadError(f"File not found: {e}")
90
- except Exception as e:
90
+ except Exception as e: # noqa: BLE001
91
91
  error(f"Error reading Parquet file: {e}")
92
92
  # pylint: disable=raise-missing-from
93
93
  raise DataReadError(f"Error reading Parquet file: {e}")
94
94
  return df
95
95
 
96
96
 
97
- @handle_pandas_errors
97
+ @handle_data_errors
98
98
  @data_extraction
99
99
  def read_excel(connection_details: dict, sheet_name: str | None = None) -> pd.DataFrame:
100
100
  """
@@ -137,14 +137,14 @@ def _read_excel(
137
137
  error(f"File not found: {e}")
138
138
  # pylint: disable=raise-missing-from
139
139
  raise DataReadError(f"File not found: {e}")
140
- except Exception as e:
140
+ except Exception as e: # noqa: BLE001
141
141
  error(f"Error reading Excel file: {e}")
142
142
  # pylint: disable=raise-missing-from
143
143
  raise DataReadError(f"Error reading Excel file: {e}")
144
144
  return df
145
145
 
146
146
 
147
- @handle_pandas_errors
147
+ @handle_data_errors
148
148
  @data_extraction
149
149
  def read_sparql_database(connection_details: dict, query: str) -> pd.DataFrame:
150
150
  """
@@ -180,7 +180,7 @@ def _read_sparql_database(connection_details: dict, query: str) -> pd.DataFrame:
180
180
 
181
181
  try:
182
182
  df = pd.read_csv(io.StringIO(result))
183
- except Exception as e:
183
+ except Exception as e: # noqa: BLE001
184
184
  error(f"Error reading SPARQL data: {e}")
185
185
  # pylint: disable=raise-missing-from
186
186
  raise DataReadError(f"Error reading SPARQL data: {e}")
@@ -230,7 +230,7 @@ def _sqldb_uri_preprocess(database_uri: str) -> str:
230
230
  return database_uri
231
231
 
232
232
 
233
- @handle_pandas_errors
233
+ @handle_data_errors
234
234
  @data_extraction
235
235
  def read_sql_database(connection_details: dict, query: str) -> pd.DataFrame:
236
236
  """
@@ -1,3 +1,4 @@
1
+ import sys
1
2
  from collections.abc import Callable
2
3
 
3
4
  import pandas as pd
@@ -70,7 +71,7 @@ def load_mock_data(
70
71
  " Please check the node configuration."
71
72
  )
72
73
  info(f"Available mock database types: {', '.join(MockDatabaseType.list())}")
73
- exit(1)
74
+ sys.exit(1)
74
75
 
75
76
  connection_details = {
76
77
  "uri": database_uri,
@@ -84,7 +85,7 @@ def load_mock_data(
84
85
  ):
85
86
  if not query:
86
87
  error(f"Query is required for {database_type} databases.")
87
- exit(1)
88
+ sys.exit(1)
88
89
  return loader(connection_details, query)
89
90
  else:
90
91
  return loader(connection_details)
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  from collections.abc import Callable
3
4
  from functools import wraps
4
5
  from typing import Any
@@ -181,10 +182,10 @@ def data_extraction(func: Callable) -> Callable:
181
182
  type_ = get_env_var(ContainerEnvNames.DATABASE_TYPE.value, mock_type)
182
183
  if not uri:
183
184
  error("No database URI provided. Exiting...")
184
- exit(1)
185
+ sys.exit(1)
185
186
  if not type_:
186
187
  error("No database type provided. Exiting...")
187
- exit(1)
188
+ sys.exit(1)
188
189
 
189
190
  connection_details["uri"] = uri
190
191
  connection_details["type"] = type_
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  from collections.abc import Callable
3
4
  from functools import wraps
4
5
  from typing import TYPE_CHECKING, Optional
@@ -69,7 +70,7 @@ def _algorithm_client() -> Callable:
69
70
  "Token not found. Is the method you called started as a "
70
71
  "compute container? Exiting..."
71
72
  )
72
- exit(1)
73
+ sys.exit(1)
73
74
 
74
75
  # read HQ address from the environment
75
76
  host = os.environ[ContainerEnvNames.HOST.value]
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  from collections.abc import Callable
3
4
  from functools import wraps
4
5
 
@@ -90,7 +91,7 @@ def _validate_user_provided_dataframes(
90
91
  f"but only {len(dataframes_grouped)} were provided. "
91
92
  "Exiting..."
92
93
  )
93
- exit(1)
94
+ sys.exit(1)
94
95
  elif len(dataframes_grouped) > number_of_expected_arguments:
95
96
  warn(
96
97
  f"Algorithm requires only {number_of_expected_arguments} databases"
@@ -143,7 +144,7 @@ def _read_dataframes_from_disk(
143
144
  "for preprocessing functions. Use @preprocessing instead. "
144
145
  "Exiting..."
145
146
  )
146
- exit(1)
147
+ sys.exit(1)
147
148
  requested_dataframes = dataframes_grouped[idx_dataframes]
148
149
  data_ = {
149
150
  df_name: _read_df_from_disk(df_name) for df_name in requested_dataframes
@@ -243,13 +244,13 @@ def dataframe(*sources: str | int) -> Callable:
243
244
  "The @dataframe decorator cannot be used for data extraction "
244
245
  "functions. Exiting..."
245
246
  )
246
- exit(1)
247
+ sys.exit(1)
247
248
 
248
249
  if getattr(func, "vantage6_dataframe_decorated", False):
249
250
  error(
250
251
  "The @dataframe decorator cannot be used multiple times. Exiting..."
251
252
  )
252
- exit(1)
253
+ sys.exit(1)
253
254
 
254
255
  # read the data from the database(s)
255
256
  dataframes = _read_dataframes_from_disk(dataframes_grouped, sources, action)
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import sys
2
3
  from collections.abc import Callable
3
4
  from dataclasses import dataclass
4
5
  from functools import wraps
@@ -126,13 +127,13 @@ def _create_omop_database_connection(connection_details: dict) -> Callable:
126
127
  if not OHDSI_AVAILABLE:
127
128
  error("OHDSI/DatabaseConnector is not available.")
128
129
  error("Did you use 'algorithm-ohdsi-base' image to build this algorithm?")
129
- exit(1)
130
+ sys.exit(1)
130
131
 
131
132
  expected_keys = ["dbms", "uri", "user", "password"]
132
133
  if not set(expected_keys).issubset(connection_details):
133
134
  missing_keys = set(expected_keys) - set(connection_details)
134
135
  error(f"Missing connection details: {missing_keys}. Exiting...")
135
- exit(1)
136
+ sys.exit(1)
136
137
 
137
138
  info("Creating OHDSI database connection")
138
139
  dbms = connection_details["dbms"]
@@ -176,7 +177,7 @@ def _get_ohdsi_metadata(connection_details: dict) -> OHDSIMetaData:
176
177
  missing_env_vars = expected_env_vars - connection_details
177
178
  error(f"Missing connection details: {missing_env_vars}")
178
179
  error("Exiting...")
179
- exit(1)
180
+ sys.exit(1)
180
181
 
181
182
  tmp = Path(os.environ[ContainerEnvNames.SESSION_FOLDER.value])
182
183
  metadata = OHDSIMetaData(
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import sys
2
3
  import traceback
3
4
  from typing import TYPE_CHECKING, Any
4
5
 
@@ -29,49 +30,48 @@ class MockBaseClient:
29
30
  # Which organization do I belong to?
30
31
  self.organization_id = 0
31
32
  # Store missing attributes in a set for __getattr__ to check
32
- self._missing_attributes = set(
33
- [
34
- "_access_token",
35
- "_ClientBase__auth_url",
36
- "_ClientBase__check_algorithm_store_valid",
37
- "_ClientBase__hq_url",
38
- "_decrypt_run_data",
39
- "_decrypt_field",
40
- "_download_run_data_from_hq",
41
- "_fetch_and_decrypt_run_data",
42
- "_multi_page_request",
43
- "_refresh_token",
44
- "_upload_run_data_to_hq",
45
- "auth_url",
46
- "authenticate",
47
- "check_if_blob_store_enabled",
48
- "collaboration_id",
49
- "cryptor",
50
- "databases",
51
- "generate_path_to",
52
- "headers",
53
- "image",
54
- "log",
55
- "name",
56
- "node_id",
57
- "obtain_new_token",
58
- "request",
59
- "hq_url",
60
- "session_id",
61
- "setup_encryption",
62
- "store_id",
63
- "study_id",
64
- "token",
65
- "wait_for_task_completion",
66
- "whoami",
67
- ]
68
- )
33
+ self._missing_attributes = {
34
+ "_access_token",
35
+ "_ClientBase__auth_url",
36
+ "_ClientBase__check_algorithm_store_valid",
37
+ "_ClientBase__hq_url",
38
+ "_decrypt_run_data",
39
+ "_decrypt_field",
40
+ "_download_run_data_from_hq",
41
+ "_fetch_and_decrypt_run_data",
42
+ "_multi_page_request",
43
+ "_refresh_token",
44
+ "_upload_run_data_to_hq",
45
+ "AlgorithmStoreSubClientBase",
46
+ "auth_url",
47
+ "authenticate",
48
+ "check_if_blob_store_enabled",
49
+ "collaboration_id",
50
+ "cryptor",
51
+ "databases",
52
+ "generate_path_to",
53
+ "headers",
54
+ "image",
55
+ "log",
56
+ "name",
57
+ "node_id",
58
+ "obtain_new_token",
59
+ "request",
60
+ "hq_url",
61
+ "session_id",
62
+ "setup_encryption",
63
+ "store_id",
64
+ "study_id",
65
+ "token",
66
+ "wait_for_task_completion",
67
+ "whoami",
68
+ }
69
69
 
70
70
  def __getattr__(self, name: str):
71
71
  """Handle access to missing attributes."""
72
72
  if hasattr(self, "_missing_attributes") and name in self._missing_attributes:
73
73
  warn(f"The attribute {name} is not available in the mock client.")
74
- return None
74
+ return
75
75
  raise AttributeError(
76
76
  f"'{self.__class__.__name__}' object has no attribute '{name}'"
77
77
  )
@@ -113,7 +113,6 @@ class MockBaseClient:
113
113
 
114
114
  def missing_method(self, method: str):
115
115
  warn(f"The method {method} is not available in the mock client.")
116
- return
117
116
 
118
117
  def wait_for_results(self, task_id: int, interval: float = 1) -> list:
119
118
  """
@@ -135,7 +134,7 @@ class MockBaseClient:
135
134
  return self.result.from_task(task_id)
136
135
 
137
136
  class Study(SubClient):
138
- """ """
137
+ """Study subclient for the MockAlgorithmClient"""
139
138
 
140
139
  def __init__(self, parent) -> None:
141
140
  super().__init__(parent)
@@ -261,20 +260,20 @@ class MockBaseClient:
261
260
  "sure that the method is available in the top level of your "
262
261
  "algorithm module?"
263
262
  )
264
- exit(1)
263
+ sys.exit(1)
265
264
  except SessionActionMismatchError:
266
265
  error(
267
266
  f"The {method} method is not a computation task, are you sure "
268
267
  "you specified the correct method?"
269
268
  )
270
- exit(1)
269
+ sys.exit(1)
271
270
  except DataFrameNotFound as e:
272
271
  error(f"A dataframe you specified does not exist: {e}")
273
- exit(1)
274
- except Exception as e:
272
+ sys.exit(1)
273
+ except Exception as e: # noqa: BLE001
275
274
  error(f"Error simulating task run for organization {org_id}: {e}")
276
275
  traceback.print_exc()
277
- exit(1)
276
+ sys.exit(1)
278
277
 
279
278
  result_response = self.parent.network.hq.save_result(result, task["id"])
280
279
  self.parent.network.hq.save_run(
@@ -582,15 +581,15 @@ class MockUserClient(MockBaseClient):
582
581
  except SessionActionMismatchError:
583
582
  error(f"The function {method} is not a data extraction method.")
584
583
  error("Exiting...")
585
- exit(1)
586
- except Exception as e:
584
+ sys.exit(1)
585
+ except Exception as e: # noqa: BLE001
587
586
  error(
588
587
  "Error simulating dataframe creation for organization "
589
588
  f"{org_id}: {e}"
590
589
  )
591
590
  error("Exiting...")
592
591
  traceback.print_exc()
593
- exit(1)
592
+ sys.exit(1)
594
593
 
595
594
  dataframes.append(df_response)
596
595
 
@@ -612,7 +611,10 @@ class MockUserClient(MockBaseClient):
612
611
  def preprocess(
613
612
  self, id_: int, image: str, method: str, arguments: dict
614
613
  ) -> dict:
615
- """ """
614
+ """
615
+ Preprocess a dataframe by creating a task that runs the preprocessing
616
+ method.
617
+ """
616
618
  dataframe = self.parent.network.hq.get_dataframe(id_)
617
619
  data_frame_name = dataframe.get("name")
618
620
  if not dataframe or not data_frame_name:
@@ -635,15 +637,15 @@ class MockUserClient(MockBaseClient):
635
637
  except SessionActionMismatchError:
636
638
  error(f"The function {method} is not a preprocessing method.")
637
639
  error("Exiting...")
638
- exit(1)
639
- except Exception as e:
640
+ sys.exit(1)
641
+ except Exception as e: # noqa: BLE001
640
642
  error(
641
643
  "Error simulating dataframe preprocessing for organization "
642
644
  f"{org_id}: {e}"
643
645
  )
644
646
  error("Exiting...")
645
647
  traceback.print_exc()
646
- exit(1)
648
+ sys.exit(1)
647
649
  dataframes.append(df)
648
650
 
649
651
  result_response = self.parent.network.hq.save_result({}, task["id"])
@@ -687,7 +689,7 @@ class MockAlgorithmClient(MockBaseClient):
687
689
  "parent task."
688
690
  )
689
691
  error("Exiting...")
690
- exit(1)
692
+ sys.exit(1)
691
693
 
692
694
  # inject the mock data into the arguments
693
695
  kwargs["databases"] = self.parent.databases
@@ -1,5 +1,5 @@
1
1
  import json
2
- from datetime import datetime, timezone
2
+ from datetime import UTC, datetime
3
3
  from typing import TYPE_CHECKING, Any
4
4
 
5
5
  import pandas as pd
@@ -113,9 +113,9 @@ class MockHQ:
113
113
  last_run_id = len(self.runs) + 1
114
114
  run = {
115
115
  "id": last_run_id,
116
- "started_at": datetime.now(timezone.utc).isoformat(),
117
- "assigned_at": datetime.now(timezone.utc).isoformat(),
118
- "finished_at": datetime.now(timezone.utc).isoformat(),
116
+ "started_at": datetime.now(UTC).isoformat(),
117
+ "assigned_at": datetime.now(UTC).isoformat(),
118
+ "finished_at": datetime.now(UTC).isoformat(),
119
119
  "log": "mock_log",
120
120
  "ports": [],
121
121
  "status": "completed",
@@ -1,3 +1,5 @@
1
+ import sys
2
+
1
3
  import pandas as pd
2
4
 
3
5
  from vantage6.common import info
@@ -148,7 +150,7 @@ class MockNetwork:
148
150
  "install your algorithm package in this environment?"
149
151
  )
150
152
  error("Exiting...")
151
- exit(1)
153
+ sys.exit(1)
152
154
 
153
155
  # In the case the user provides a pandas DataFrame we need to create a dataframe
154
156
  # record in HQ.
@@ -238,7 +238,9 @@ class MockNode:
238
238
  self.dataframes[dataframe_name] = df
239
239
  return df
240
240
 
241
- def run(self, method_fn: Callable, arguments: dict, task_env_vars: dict = {}):
241
+ def run(
242
+ self, method_fn: Callable, arguments: dict, task_env_vars: dict | None = None
243
+ ):
242
244
  """
243
245
  Run a method with the given arguments and task environment variables.
244
246
 
@@ -256,6 +258,8 @@ class MockNode:
256
258
  Any
257
259
  The result of the method run.
258
260
  """
261
+ if task_env_vars is None:
262
+ task_env_vars = {}
259
263
  with env_vars(**task_env_vars):
260
264
  return method_fn(**arguments)
261
265
 
@@ -7,13 +7,13 @@ DataFrame with statistical information based on a given grouping.
7
7
 
8
8
  import pandas as pd
9
9
 
10
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
10
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
11
11
  from vantage6.algorithm.tools.exceptions import UserInputError
12
12
 
13
13
  from vantage6.algorithm.decorator.action import preprocessing
14
14
 
15
15
 
16
- @handle_pandas_errors
16
+ @handle_data_errors
17
17
  @preprocessing
18
18
  def collapse(
19
19
  df: pd.DataFrame,
@@ -160,7 +160,7 @@ def collapse(
160
160
  return agg_df.reset_index()
161
161
 
162
162
 
163
- @handle_pandas_errors
163
+ @handle_data_errors
164
164
  @preprocessing
165
165
  def group_statistics(
166
166
  df: pd.DataFrame,
@@ -6,13 +6,13 @@ expressions.
6
6
 
7
7
  import pandas as pd
8
8
 
9
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
9
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
10
10
  from vantage6.algorithm.tools.exceptions import UserInputError
11
11
 
12
12
  from vantage6.algorithm.decorator.action import preprocessing
13
13
 
14
14
 
15
- @handle_pandas_errors
15
+ @handle_data_errors
16
16
  @preprocessing
17
17
  def rename_columns(
18
18
  df: pd.DataFrame, old_names: list[str], new_names: list[str], strict: bool = False
@@ -55,7 +55,7 @@ def rename_columns(
55
55
  )
56
56
 
57
57
 
58
- @handle_pandas_errors
58
+ @handle_data_errors
59
59
  @preprocessing
60
60
  def assign_column(df: pd.DataFrame, column_name: str, expression: str) -> pd.DataFrame:
61
61
  """
@@ -96,7 +96,7 @@ def assign_column(df: pd.DataFrame, column_name: str, expression: str) -> pd.Dat
96
96
  return df
97
97
 
98
98
 
99
- @handle_pandas_errors
99
+ @handle_data_errors
100
100
  @preprocessing
101
101
  def redefine_column(
102
102
  df: pd.DataFrame, column_name: str, expression: str
@@ -140,7 +140,7 @@ def redefine_column(
140
140
  return df
141
141
 
142
142
 
143
- @handle_pandas_errors
143
+ @handle_data_errors
144
144
  @preprocessing
145
145
  def change_column_type(
146
146
  df: pd.DataFrame, columns: list[str], target_type: str
@@ -5,17 +5,17 @@ for converting strings to datetime, adding fixed or variable timedeltas, and
5
5
  calculating ages from birthdate columns.
6
6
  """
7
7
 
8
- from datetime import date
8
+ import datetime
9
9
 
10
10
  import pandas as pd
11
11
 
12
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
12
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
13
13
  from vantage6.algorithm.tools.exceptions import DataError, UserInputError
14
14
 
15
15
  from vantage6.algorithm.decorator.action import preprocessing
16
16
 
17
17
 
18
- @handle_pandas_errors
18
+ @handle_data_errors
19
19
  @preprocessing
20
20
  def to_datetime(
21
21
  df: pd.DataFrame,
@@ -81,7 +81,7 @@ def to_datetime(
81
81
  return df
82
82
 
83
83
 
84
- @handle_pandas_errors
84
+ @handle_data_errors
85
85
  @preprocessing
86
86
  def to_timedelta(
87
87
  df: pd.DataFrame,
@@ -179,7 +179,7 @@ def to_timedelta(
179
179
  return df
180
180
 
181
181
 
182
- @handle_pandas_errors
182
+ @handle_data_errors
183
183
  @preprocessing
184
184
  def timedelta(
185
185
  df: pd.DataFrame,
@@ -268,7 +268,7 @@ def timedelta(
268
268
  return df
269
269
 
270
270
 
271
- @handle_pandas_errors
271
+ @handle_data_errors
272
272
  @preprocessing
273
273
  def calculate_age(
274
274
  df: pd.DataFrame,
@@ -316,7 +316,7 @@ def calculate_age(
316
316
  1 1980-05-15 44
317
317
  """
318
318
  if reference_date is None:
319
- reference_date = date.today()
319
+ reference_date = datetime.datetime.now(datetime.UTC).date()
320
320
  else:
321
321
  try:
322
322
  reference_date = pd.to_datetime(reference_date)
@@ -9,13 +9,13 @@ standard deviations for scaling operations.
9
9
 
10
10
  import pandas as pd
11
11
 
12
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
12
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
13
13
  from vantage6.algorithm.tools.exceptions import UserInputError
14
14
 
15
15
  from vantage6.algorithm.decorator.action import preprocessing
16
16
 
17
17
 
18
- @handle_pandas_errors
18
+ @handle_data_errors
19
19
  @preprocessing
20
20
  def min_max_scale(
21
21
  df: pd.DataFrame,
@@ -89,7 +89,7 @@ def min_max_scale(
89
89
  return df_scaled
90
90
 
91
91
 
92
- @handle_pandas_errors
92
+ @handle_data_errors
93
93
  @preprocessing
94
94
  def standard_scale(
95
95
  df: pd.DataFrame,
@@ -163,7 +163,7 @@ def standard_scale(
163
163
  return df_scaled
164
164
 
165
165
 
166
- @handle_pandas_errors
166
+ @handle_data_errors
167
167
  @preprocessing
168
168
  def one_hot_encode(
169
169
  df: pd.DataFrame,
@@ -251,7 +251,7 @@ def one_hot_encode(
251
251
  return df_out
252
252
 
253
253
 
254
- @handle_pandas_errors
254
+ @handle_data_errors
255
255
  @preprocessing
256
256
  def encode(
257
257
  df: pd.DataFrame,
@@ -335,7 +335,7 @@ def encode(
335
335
  return encoded_df
336
336
 
337
337
 
338
- @handle_pandas_errors
338
+ @handle_data_errors
339
339
  @preprocessing
340
340
  def discretize_column(
341
341
  df: pd.DataFrame,
@@ -401,7 +401,7 @@ def discretize_column(
401
401
  return df
402
402
 
403
403
 
404
- @handle_pandas_errors
404
+ @handle_data_errors
405
405
  @preprocessing
406
406
  def extract_from_string(
407
407
  df: pd.DataFrame,
@@ -457,7 +457,7 @@ def extract_from_string(
457
457
  return df
458
458
 
459
459
 
460
- @handle_pandas_errors
460
+ @handle_data_errors
461
461
  @preprocessing
462
462
  def impute(
463
463
  df: pd.DataFrame,
@@ -5,12 +5,12 @@ pandas DataFrames, such as selecting and filtering rows and columns.
5
5
 
6
6
  import pandas as pd
7
7
 
8
- from vantage6.algorithm.tools.error_handling import handle_pandas_errors
8
+ from vantage6.algorithm.tools.error_handling import handle_data_errors
9
9
 
10
10
  from vantage6.algorithm.decorator.action import preprocessing
11
11
 
12
12
 
13
- @handle_pandas_errors
13
+ @handle_data_errors
14
14
  @preprocessing
15
15
  def select_rows(df: pd.DataFrame, query: str) -> pd.DataFrame:
16
16
  """
@@ -78,13 +78,13 @@ def select_rows(df: pd.DataFrame, query: str) -> pd.DataFrame:
78
78
  return df.query(query)
79
79
 
80
80
 
81
- @handle_pandas_errors
81
+ @handle_data_errors
82
82
  @preprocessing
83
83
  def filter_range(
84
84
  df: pd.DataFrame,
85
85
  column: str,
86
- min_value: float = None,
87
- max_value: float = None,
86
+ min_value: float | None = None,
87
+ max_value: float | None = None,
88
88
  include_min: bool = True,
89
89
  include_max: bool = True,
90
90
  ) -> pd.DataFrame:
@@ -162,7 +162,7 @@ def filter_range(
162
162
  return df
163
163
 
164
164
 
165
- @handle_pandas_errors
165
+ @handle_data_errors
166
166
  @preprocessing
167
167
  def select_columns(df: pd.DataFrame, columns: list) -> pd.DataFrame:
168
168
  """
@@ -218,7 +218,7 @@ def select_columns(df: pd.DataFrame, columns: list) -> pd.DataFrame:
218
218
  return df[columns]
219
219
 
220
220
 
221
- @handle_pandas_errors
221
+ @handle_data_errors
222
222
  @preprocessing
223
223
  def drop_columns(df: pd.DataFrame, columns: list) -> pd.DataFrame:
224
224
  """
@@ -266,7 +266,7 @@ def drop_columns(df: pd.DataFrame, columns: list) -> pd.DataFrame:
266
266
  return df.drop(columns, axis=1)
267
267
 
268
268
 
269
- @handle_pandas_errors
269
+ @handle_data_errors
270
270
  @preprocessing
271
271
  def filter_by_date(
272
272
  df: pd.DataFrame,
@@ -0,0 +1,48 @@
1
+ import logging
2
+ from collections.abc import Callable
3
+ from functools import wraps
4
+
5
+ from vantage6.common import logger_name
6
+
7
+ from vantage6.algorithm.tools.exceptions import AlgorithmError, AlgorithmRuntimeError
8
+
9
+ module_name = logger_name(__name__)
10
+ log = logging.getLogger(module_name)
11
+
12
+
13
+ def handle_data_errors(func: Callable) -> Callable:
14
+ """
15
+ Decorator to catch errors from data manipulation in algorithm functions and
16
+ prevent leaking privacy-sensitive data via error messages or tracebacks.
17
+
18
+ Any exception that is not a vantage6 ``AlgorithmError`` is replaced by a generic
19
+ ``AlgorithmRuntimeError``. This includes non-pandas exceptions such as
20
+ ``ValueError`` or ``KeyError``, which are commonly raised by pandas operations and
21
+ tend to contain data values in their message.
22
+
23
+ The vantage6 ``AlgorithmError`` exceptions are re-raised unchanged: their messages
24
+ are written by algorithm developers and are meant to be shown to the user.
25
+ """
26
+
27
+ @wraps(func)
28
+ def wrapper(*args, **kwargs):
29
+ try:
30
+ return func(*args, **kwargs)
31
+ except AlgorithmError:
32
+ raise
33
+ except Exception as e: # noqa: BLE001
34
+ msg = (
35
+ f"An error of type {type(e).__name__} occurred in "
36
+ f"'{func.__qualname__}'. Details have been omitted to protect privacy."
37
+ )
38
+ log.error(msg)
39
+ # `from None` suppresses the exception context: without it, the original
40
+ # exception, including its message, is still printed when the traceback
41
+ # is logged
42
+ raise AlgorithmRuntimeError(msg) from None
43
+
44
+ return wrapper
45
+
46
+
47
+ # Backwards-compatible alias
48
+ handle_pandas_errors = handle_data_errors
@@ -160,7 +160,7 @@ def _convert_envvar_to_int(envvar_name: str, envvar_value: str) -> int:
160
160
  ) from exc
161
161
 
162
162
 
163
- def check_envvar_value_positive(envvar_name: str, envvar_value: int | float) -> None:
163
+ def check_envvar_value_positive(envvar_name: str, envvar_value: float) -> None:
164
164
  """
165
165
  Check whether an environment variable is a positive integer.
166
166
 
@@ -1,6 +1,7 @@
1
1
  import importlib
2
2
  import json
3
3
  import os
4
+ import sys
4
5
  import traceback
5
6
  from typing import Any
6
7
 
@@ -47,7 +48,7 @@ def wrap_algorithm(log_traceback: bool = True) -> None:
47
48
  "No PKG_NAME specified! Make sure that the PKG_NAME environment "
48
49
  "variable is specified in the Dockerfile. Exiting..."
49
50
  )
50
- exit(1)
51
+ sys.exit(1)
51
52
  info(f"wrapper for {module}")
52
53
 
53
54
  # Decode environment variables that are encoded by the node.
@@ -112,7 +113,7 @@ def _run_algorithm_method(
112
113
  error(f"Module '{module}' can not be imported! Exiting...")
113
114
  if log_traceback:
114
115
  error(traceback.print_exc())
115
- exit(1)
116
+ sys.exit(1)
116
117
 
117
118
  # get algorithm method and attempt to load it
118
119
  try:
@@ -121,7 +122,7 @@ def _run_algorithm_method(
121
122
  error(f"Method '{method}' not found!\n")
122
123
  if log_traceback:
123
124
  error(traceback.print_exc())
124
- exit(1)
125
+ sys.exit(1)
125
126
 
126
127
  # check if the method is decorated with a vantage6 decorator. If it is not,
127
128
  # we need to raise an error. It is important to check this, because the decorator
@@ -132,18 +133,18 @@ def _run_algorithm_method(
132
133
  "algorithm functions should have a decorator such as @federated, "
133
134
  "@central, @preprocessing, @data_extraction, etc."
134
135
  )
135
- exit(1)
136
+ sys.exit(1)
136
137
 
137
138
  # try to run the method
138
139
  if not arguments:
139
140
  arguments = {}
140
141
  try:
141
142
  result = method_fn(**arguments)
142
- except Exception as exc:
143
+ except Exception as exc: # noqa: BLE001
143
144
  error(f"Error encountered while calling {method}: {exc}")
144
145
  if log_traceback:
145
146
  error(traceback.print_exc())
146
- exit(1)
147
+ sys.exit(1)
147
148
 
148
149
  return result
149
150
 
@@ -1,59 +0,0 @@
1
- import logging
2
- from collections.abc import Callable
3
- from functools import wraps
4
-
5
- import pandas as pd
6
-
7
- from vantage6.common import logger_name
8
-
9
- from vantage6.algorithm.tools.exceptions import AlgorithmRuntimeError
10
-
11
- module_name = logger_name(__name__)
12
- log = logging.getLogger(module_name)
13
-
14
-
15
- def _collect_pandas_error_types() -> tuple[type, ...]:
16
- """
17
- Collect all exception classes exported from pandas.errors.
18
-
19
- Returns a tuple of exception types to be used in an except clause.
20
- """
21
- exception_types = []
22
- errors_module = pd.errors
23
- for attr_name in errors_module.__all__:
24
- attr = getattr(errors_module, attr_name, None)
25
- if isinstance(attr, type) and issubclass(attr, Exception):
26
- exception_types.append(attr)
27
-
28
- return tuple(exception_types)
29
-
30
-
31
- PANDAS_ERROR_TYPES = _collect_pandas_error_types()
32
-
33
-
34
- def handle_pandas_errors(func: Callable) -> Callable:
35
- """
36
- Decorator to catch pandas-related errors from algorithm functions and
37
- prevent leaking privacy-sensitive data via tracebacks.
38
-
39
- - Catches pandas-specific exceptions and common data-manipulation errors.
40
- - Logs a minimal, non-identifying message (no traceback, no exception str).
41
- - Returns a safe, generic error response with 400 Bad Request.
42
- - Other unexpected exceptions continue to log with traceback for debugging.
43
- """
44
-
45
- @wraps(func)
46
- def wrapper(*args, **kwargs):
47
- try:
48
- return func(*args, **kwargs)
49
- except PANDAS_ERROR_TYPES as e:
50
- # Avoid logging tracebacks or exception messages that may contain data
51
- msg = (
52
- f"Pandas-related error of type {type(e).__name__} occurred in "
53
- "algorithm function. Details have been omitted to protect privacy."
54
- )
55
- log.error(msg)
56
- # pylint: disable=raise-missing-from
57
- raise AlgorithmRuntimeError(msg)
58
-
59
- return wrapper
@@ -15,13 +15,13 @@ from vantage6.algorithm.decorator.metadata import metadata
15
15
  from vantage6.algorithm.decorator.ohdsi import omop_data_extraction
16
16
 
17
17
  __all__ = [
18
+ "algorithm_client",
18
19
  "central",
19
20
  "data_extraction",
20
- "federated",
21
- "preprocessing",
22
- "algorithm_client",
23
21
  "dataframe",
24
22
  "dataframes",
23
+ "federated",
25
24
  "metadata",
26
25
  "omop_data_extraction",
26
+ "preprocessing",
27
27
  ]