t-bug-catcher 0.3.1__tar.gz → 0.4.1__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 (27) hide show
  1. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/PKG-INFO +2 -1
  2. t_bug_catcher-0.3.1/t_bug_catcher.egg-info/requires.txt → t_bug_catcher-0.4.1/requirements.txt +1 -0
  3. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/setup.cfg +1 -1
  4. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/setup.py +3 -1
  5. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/__init__.py +1 -5
  6. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/bug_catcher.py +8 -1
  7. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/config.py +2 -0
  8. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/jira.py +19 -7
  9. t_bug_catcher-0.4.1/t_bug_catcher/resources/whispers_config.yml +44 -0
  10. t_bug_catcher-0.4.1/t_bug_catcher/stack_saver.py +181 -0
  11. t_bug_catcher-0.4.1/t_bug_catcher/utils/common.py +41 -0
  12. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher.egg-info/PKG-INFO +2 -1
  13. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher.egg-info/SOURCES.txt +2 -0
  14. t_bug_catcher-0.3.1/requirements.txt → t_bug_catcher-0.4.1/t_bug_catcher.egg-info/requires.txt +2 -1
  15. t_bug_catcher-0.3.1/t_bug_catcher/utils/common.py +0 -17
  16. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/MANIFEST.in +0 -0
  17. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/README.rst +0 -0
  18. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/pyproject.toml +0 -0
  19. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/bug_snag.py +0 -0
  20. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/exceptions.py +0 -0
  21. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/utils/__init__.py +0 -0
  22. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/utils/logger.py +0 -0
  23. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher/workitems.py +0 -0
  24. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  25. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  26. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/t_bug_catcher.egg-info/top_level.txt +0 -0
  27. {t_bug_catcher-0.3.1 → t_bug_catcher-0.4.1}/tests/test_t_bug_catcher.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.3.1
3
+ Version: 0.4.1
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
14
14
  Requires-Dist: requests<3.0.0,>=2.31.0
15
15
  Requires-Dist: bugsnag>=4.6.1
16
16
  Requires-Dist: retry~=0.9.2
17
+ Requires-Dist: whispers>=2.2.1
17
18
 
18
19
  t-bug-catcher
19
20
  ==============
@@ -1,3 +1,4 @@
1
1
  requests<3.0.0,>=2.31.0
2
2
  bugsnag>=4.6.1
3
3
  retry~=0.9.2
4
+ whispers>=2.2.1
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.3.1
2
+ current_version = 0.4.1
3
3
  commit = True
4
4
  tag = False
5
5
 
@@ -26,7 +26,9 @@ setup(
26
26
  packages=find_packages(include=["t_bug_catcher", "t_bug_catcher.*"]),
27
27
  test_suite="tests",
28
28
  url="https://www.thoughtful.ai/",
29
- version="0.3.1",
29
+ version="0.4.1",
30
30
  zip_safe=False,
31
31
  install_requires=install_requirements,
32
+ include_package_data=True,
33
+ package_data={"": ["resources/*.yml"]},
32
34
  )
@@ -3,14 +3,12 @@
3
3
  __author__ = """Thoughtful"""
4
4
  __email__ = "support@thoughtful.ai"
5
5
  # fmt: off
6
- __version__ = '0.3.1'
6
+ __version__ = '0.4.1'
7
7
  # fmt: on
8
8
 
9
9
  from .bug_catcher import (
10
10
  configure,
11
11
  report_error,
12
- report_error_to_jira,
13
- report_error_to_bugsnag,
14
12
  attach_file_to_exception,
15
13
  install_sys_hook,
16
14
  uninstall_sys_hook,
@@ -19,8 +17,6 @@ from .bug_catcher import (
19
17
  __all__ = [
20
18
  "configure",
21
19
  "report_error",
22
- "report_error_to_jira",
23
- "report_error_to_bugsnag",
24
20
  "attach_file_to_exception",
25
21
  "install_sys_hook",
26
22
  "uninstall_sys_hook",
@@ -8,6 +8,7 @@ from typing import List, Optional
8
8
  from .bug_snag import BugSnag
9
9
  from .config import CONFIG
10
10
  from .jira import Jira
11
+ from .stack_saver import StackSaver
11
12
  from .utils import logger
12
13
  from .utils.common import get_frames
13
14
 
@@ -89,6 +90,7 @@ class BugCatcher:
89
90
  self.__bug_snag: BugSnag = BugSnag()
90
91
  self.__configurator: Configurator = Configurator(self.__jira, self.__bug_snag)
91
92
  self.__sys_excepthook = None
93
+ self.__stack_saver = StackSaver()
92
94
 
93
95
  @property
94
96
  def configure(self):
@@ -131,11 +133,14 @@ class BugCatcher:
131
133
  logger.warning(f"Exception {handled_error} already reported.")
132
134
  return
133
135
 
136
+ stack_trace = self.__stack_saver.save_stack_trace(exception)
137
+
134
138
  if self.__configurator.is_jira_configured:
135
139
  self.__jira.report_error(
136
140
  exception=exception,
137
141
  assignee=assignee,
138
142
  attachments=attachments,
143
+ stack_trace=stack_trace,
139
144
  additional_info=description,
140
145
  metadata=metadata,
141
146
  )
@@ -243,8 +248,10 @@ class BugCatcher:
243
248
  logger.warning(f"Exception {handled_error} already reported.")
244
249
  return
245
250
 
251
+ stack_trace = self.__stack_saver.save_stack_trace(exc_value)
252
+
246
253
  if self.__configurator.is_jira_configured:
247
- self.__jira.report_unhandled_error(exc_type, exc_value, exc_traceback)
254
+ self.__jira.report_unhandled_error(exc_type, exc_value, exc_traceback, stack_trace)
248
255
  if self.__configurator.is_bugsnag_configured:
249
256
  self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
250
257
 
@@ -13,6 +13,7 @@ class Config:
13
13
  MAX_ISSUE_ATTACHMENTS: int = 100
14
14
  MAX_DESCRIPTION_LENGTH: int = 250
15
15
  SUMMARY_LENGTH: int = 120
16
+ STACK_SCOPE: int = 3
16
17
 
17
18
  SUPPORT_BOARD = "AB"
18
19
 
@@ -20,6 +21,7 @@ class Config:
20
21
  f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
21
22
  f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
22
23
  f"/{os.environ.get('RC_PROCESS_ID')}/runs/{os.environ.get('RC_PROCESS_RUN_ID')}/"
24
+ f"stepRuns/{os.environ.get('RC_ACTIVITY_RUN_ID')}/"
23
25
  )
24
26
 
25
27
  ENVIRONMENT = (
@@ -1,12 +1,12 @@
1
1
  """JiraPoster class for interacting with the Jira API."""
2
2
 
3
- import datetime
4
3
  import hashlib
5
4
  import json
6
5
  import os
7
6
  import re
8
7
  import sys
9
8
  import traceback
9
+ from datetime import datetime
10
10
  from importlib.metadata import version
11
11
  from pathlib import Path
12
12
  from types import TracebackType
@@ -19,7 +19,7 @@ from retry import retry
19
19
  from .config import CONFIG
20
20
  from .exceptions import BadRequestError
21
21
  from .utils import logger
22
- from .utils.common import get_frames
22
+ from .utils.common import Encoder, get_frames
23
23
  from .workitems import variables
24
24
 
25
25
 
@@ -389,7 +389,7 @@ class Jira:
389
389
  },
390
390
  {
391
391
  "type": "text",
392
- "text": str(datetime.datetime.now().strftime("%B %d, %Y %I:%M:%S %p")),
392
+ "text": str(datetime.now().strftime("%B %d, %Y %I:%M:%S %p")),
393
393
  },
394
394
  ],
395
395
  }
@@ -521,7 +521,7 @@ class Jira:
521
521
  "content": [
522
522
  {
523
523
  "type": "text",
524
- "text": json.dumps(metadata, indent=4),
524
+ "text": json.dumps(metadata, indent=4, cls=Encoder),
525
525
  }
526
526
  ],
527
527
  },
@@ -735,7 +735,7 @@ class Jira:
735
735
  date_markup = [
736
736
  {
737
737
  "type": "text",
738
- "text": f" at {str(datetime.datetime.now().strftime('%B %d, %Y %I:%M:%S %p'))}",
738
+ "text": f" at {str(datetime.now().strftime('%B %d, %Y %I:%M:%S %p'))}",
739
739
  },
740
740
  {"type": "hardBreak"},
741
741
  ]
@@ -959,6 +959,7 @@ class Jira:
959
959
  exception: Optional[Exception] = None,
960
960
  assignee: Optional[str] = None,
961
961
  attachments: Union[List, str, Path, None] = None,
962
+ stack_trace: Optional[str] = None,
962
963
  metadata: Optional[dict] = None,
963
964
  additional_info: Optional[str] = None,
964
965
  ) -> dict:
@@ -968,6 +969,7 @@ class Jira:
968
969
  exception (Exception, optional): The exception to be added to the Jira issue.
969
970
  assignee (str, optional): The assignee to be added to the Jira issue.
970
971
  attachments (List, optional): List of attachments to be added to the Jira issue.
972
+ stack_trace (str, optional): Stack trace to be added to the Jira issue.
971
973
  metadata (dict, optional): Metadata to be added to the Jira issue.
972
974
  additional_info (str, optional): Additional information to be added to the Jira issue.
973
975
 
@@ -1012,6 +1014,9 @@ class Jira:
1012
1014
  )
1013
1015
  return existing_ticket
1014
1016
 
1017
+ if stack_trace:
1018
+ attachments.insert(0, stack_trace)
1019
+
1015
1020
  assignee_id = None
1016
1021
  assignee = assignee if assignee else self._default_assignee
1017
1022
  if assignee:
@@ -1036,19 +1041,23 @@ class Jira:
1036
1041
  attachments=attachments,
1037
1042
  labels=["bug_catcher"],
1038
1043
  )
1044
+ if os.path.exists(stack_trace):
1045
+ os.remove(stack_trace)
1039
1046
  return response
1040
1047
  except Exception as ex:
1041
1048
  logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
1042
1049
  return False
1043
1050
 
1044
- def report_unhandled_error(self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType):
1051
+ def report_unhandled_error(
1052
+ self, exc_type: type, exc_value: Union[Exception, str], exc_traceback: TracebackType, stack_trace: str = None
1053
+ ):
1045
1054
  """Report an unhandled error to Jira.
1046
1055
 
1047
1056
  Args:
1048
1057
  exc_type (type): The type of the exception.
1049
1058
  exc_value (Exception, str): The value of the exception.
1050
1059
  exc_traceback (TracebackType): The traceback of the exception.
1051
- assignee (str, optional): The assignee to be added to the Jira issue.
1060
+ stack_trace (str, optional): Stack trace to be added to the Jira issue.
1052
1061
 
1053
1062
  Returns:
1054
1063
  The response from creating the Jira issue.
@@ -1086,8 +1095,11 @@ class Jira:
1086
1095
  summary=summary,
1087
1096
  description=description,
1088
1097
  assignee_id=assignee_id,
1098
+ attachments=[stack_trace] if stack_trace else None,
1089
1099
  labels=["bug_catcher", "fatal_error"],
1090
1100
  )
1101
+ if os.path.exists(stack_trace):
1102
+ os.remove(stack_trace)
1091
1103
  return response
1092
1104
  except Exception as ex:
1093
1105
  logger.warning(f"Failed to create Jira issue due to: {ex.__class__.__name__}: {ex}")
@@ -0,0 +1,44 @@
1
+ include:
2
+ files:
3
+ - "**/*"
4
+ rules:
5
+ - password
6
+ - uri
7
+ - secret
8
+ - webhook
9
+ - apikey
10
+ - apikey-known
11
+ - apikey-maybe
12
+ - id: username
13
+ group: misc
14
+ description: Variable names refering to user
15
+ message: Secret
16
+ severity: Low
17
+ key:
18
+ regex: ^\S*(user(name?)|login|id|uid|otp)_?(hash)?[0-9]*$
19
+ ignorecase: True
20
+ value:
21
+ minlen: 1
22
+ severity:
23
+ - Critical
24
+ - High
25
+ - Medium
26
+
27
+ exclude:
28
+ files:
29
+ - __pycache__|\.eggs|build|dev|\.vscode|\.git
30
+ - .*/(locale|spec|test|mock)s?/
31
+ - integration|node_modules
32
+ - (package(-lock)?|npm-shrinkwrap)\.json
33
+
34
+ keys:
35
+ - .*(public|project).*
36
+
37
+ values:
38
+ - ^(true|false|yes|no|1|0)$
39
+ - .*_(user|password|token|key|placeholder|name)$
40
+ - ^aws_(access_key_id|secret_access_key|session_token)$
41
+ - ^((cn?trl|alt|shift|del|ins|esc|tab|f[\d]+) ?[\+_\-\\/] ?)+[\w]+$
42
+
43
+
44
+
@@ -0,0 +1,181 @@
1
+ import inspect
2
+ import json
3
+ import os
4
+ import re
5
+ import sys
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from types import FunctionType, ModuleType
9
+ from typing import Optional
10
+
11
+ import whispers
12
+
13
+ from .config import CONFIG
14
+ from .utils import logger
15
+ from .utils.common import Encoder
16
+
17
+
18
+ class StackSaver:
19
+ """A class to save the stack trace."""
20
+
21
+ def __init__(self):
22
+ """Initializes the StackSaver class."""
23
+ pass
24
+
25
+ @staticmethod
26
+ def strip_path(path: str):
27
+ """A static method to strip the current working directory path from the input.
28
+
29
+ Args:
30
+ path (str): The path from which to strip the current working directory path.
31
+
32
+ Returns:
33
+ str: The stripped path.
34
+ """
35
+ return path.replace(os.getcwd(), "").strip(os.sep)
36
+
37
+ @staticmethod
38
+ def serialize_frame_info(frame_info: dict) -> dict:
39
+ """A static method to serialize the frame info.
40
+
41
+ Args:
42
+ frame_info (dict): The frame info to be serialized.
43
+
44
+ Returns:
45
+ dict: The serialized frame info.
46
+ """
47
+ run_locals = {}
48
+ run_args = {}
49
+ if frame_info["locals"]:
50
+ for key, value in frame_info["locals"].items():
51
+ if str(key).lower() == "credentials":
52
+ continue
53
+ if isinstance(value, dict):
54
+ run_locals[str(key)] = value
55
+ else:
56
+ run_locals[str(key)] = str(value)
57
+ for key, value in frame_info["args"].items():
58
+ if str(key).lower() == "credentials":
59
+ continue
60
+ if isinstance(value, dict):
61
+ run_args[str(key)] = value
62
+ else:
63
+ run_args[str(key)] = str(value)
64
+ serializable_frame_info = {
65
+ "filename": frame_info["filename"],
66
+ "function_name": frame_info["function_name"],
67
+ "locals": run_locals,
68
+ "args": run_args,
69
+ }
70
+ return serializable_frame_info
71
+
72
+ @staticmethod
73
+ def filter_variables(variables: dict) -> dict:
74
+ """A static method to filter the variables.
75
+
76
+ Args:
77
+ variables (dict): The variables to be filtered.
78
+
79
+ Returns:
80
+ dict: The filtered variables.
81
+ """
82
+ if not isinstance(variables, dict):
83
+ return variables
84
+ else:
85
+ local_variables = {}
86
+ for var_name, var in variables.items():
87
+ if re.match(r"^__\w+__$", var_name):
88
+ continue
89
+ if isinstance(var, (ModuleType, FunctionType)):
90
+ continue
91
+ local_variables[var_name] = var
92
+ return local_variables
93
+
94
+ def mask_credentials(self, file_path: str) -> None:
95
+ """A method to mask the credentials in the file.
96
+
97
+ Args:
98
+ file_path (str): The path of the file to be masked.
99
+
100
+ Raises:
101
+ Exception: If the masking fails.
102
+
103
+ Returns:
104
+ None
105
+ """
106
+ with open(file_path, "r") as f:
107
+ filedata = f.readlines()
108
+
109
+ config_path = Path(__file__).parent.resolve().as_posix() + "/resources/whispers_config.yml"
110
+
111
+ args = f"-c {config_path} {file_path}"
112
+
113
+ secrets = [secret for secret in whispers.secrets(args)]
114
+ unique_secrets = []
115
+ seen = set()
116
+ for secret in secrets:
117
+ item = (secret.key, secret.value, secret.line)
118
+ if item not in seen:
119
+ seen.add(item)
120
+ unique_secrets.append(secret)
121
+
122
+ for index, line in enumerate(filedata):
123
+ if not unique_secrets:
124
+ break
125
+
126
+ for secret in unique_secrets:
127
+ if secret.key in line and secret.value in line:
128
+ filedata[index] = line.replace(secret.value, secret.value[:1] + "***")
129
+ unique_secrets.pop(unique_secrets.index(secret))
130
+ break
131
+
132
+ if unique_secrets:
133
+ logger.warning("Failed to mask credentials")
134
+ os.remove(file_path)
135
+ raise Exception("Failed to mask credentials")
136
+
137
+ with open(file_path, "w") as file:
138
+ file.writelines(filedata)
139
+
140
+ def save_stack_trace(self, exception: Optional[Exception] = None):
141
+ """A method to save the stack trace.
142
+
143
+ Args:
144
+ exception (Exception, optional): The exception to be saved. Defaults to None.
145
+
146
+ Returns:
147
+ Optional[str]: The path of the saved stack trace.
148
+ """
149
+ try:
150
+ frames = []
151
+ stack_details_json = []
152
+ tb = exception.__traceback__ if exception else sys.exc_info()[2]
153
+ while tb is not None:
154
+ frame = tb.tb_frame
155
+ if "site-packages" in frame.f_code.co_filename:
156
+ tb = tb.tb_next
157
+ continue
158
+ frames.append(frame)
159
+ tb = tb.tb_next
160
+ frames = frames[: CONFIG.LIMITS.STACK_SCOPE]
161
+
162
+ for frame in frames:
163
+ frame_info = {
164
+ "filename": self.strip_path(frame.f_code.co_filename),
165
+ "function_name": frame.f_code.co_name,
166
+ "locals": self.filter_variables(frame.f_locals),
167
+ "args": self.filter_variables(inspect.getargvalues(frame)[3]),
168
+ }
169
+ stack_details_json.append(self.serialize_frame_info(frame_info))
170
+
171
+ file_path = f"stack_details_{datetime.now().strftime('%Y%m%d%H%M%S')}.json"
172
+
173
+ with open(file_path, "w") as f:
174
+ json.dump(stack_details_json, f, indent=4, cls=Encoder)
175
+
176
+ self.mask_credentials(file_path)
177
+
178
+ return file_path
179
+ except Exception as e:
180
+ logger.warning(f"Failed to save stack trace: {e}")
181
+ return
@@ -0,0 +1,41 @@
1
+ import traceback
2
+ from datetime import date, datetime
3
+ from json import JSONEncoder
4
+ from pathlib import Path
5
+ from types import TracebackType
6
+ from typing import List
7
+
8
+
9
+ class Encoder(JSONEncoder):
10
+ """This class is used to encode the Episode object to json."""
11
+
12
+ def default(self, o):
13
+ """This method is used to encode the Episode object to json.
14
+
15
+ Args:
16
+ o (object): The object to be encoded.
17
+
18
+ Returns:
19
+ str: The json string.
20
+ """
21
+ if hasattr(o, "__dict__"):
22
+ return o.__dict__
23
+ if isinstance(o, (datetime, date)):
24
+ return o.isoformat()
25
+ if isinstance(o, Path):
26
+ return str(o)
27
+ return JSONEncoder.default(self, o)
28
+
29
+
30
+ def get_frames(exc_traceback: TracebackType) -> List:
31
+ """Get the frames of the exception.
32
+
33
+ Args:
34
+ exc_traceback (TracebackType): The traceback of the exception.
35
+
36
+ Returns:
37
+ List: The frames of the exception.
38
+ """
39
+ return [
40
+ frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
41
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.3.1
3
+ Version: 0.4.1
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -14,6 +14,7 @@ Requires-Python: >=3.9
14
14
  Requires-Dist: requests<3.0.0,>=2.31.0
15
15
  Requires-Dist: bugsnag>=4.6.1
16
16
  Requires-Dist: retry~=0.9.2
17
+ Requires-Dist: whispers>=2.2.1
17
18
 
18
19
  t-bug-catcher
19
20
  ==============
@@ -10,6 +10,7 @@ t_bug_catcher/bug_snag.py
10
10
  t_bug_catcher/config.py
11
11
  t_bug_catcher/exceptions.py
12
12
  t_bug_catcher/jira.py
13
+ t_bug_catcher/stack_saver.py
13
14
  t_bug_catcher/workitems.py
14
15
  t_bug_catcher.egg-info/PKG-INFO
15
16
  t_bug_catcher.egg-info/SOURCES.txt
@@ -17,6 +18,7 @@ t_bug_catcher.egg-info/dependency_links.txt
17
18
  t_bug_catcher.egg-info/not-zip-safe
18
19
  t_bug_catcher.egg-info/requires.txt
19
20
  t_bug_catcher.egg-info/top_level.txt
21
+ t_bug_catcher/resources/whispers_config.yml
20
22
  t_bug_catcher/utils/__init__.py
21
23
  t_bug_catcher/utils/common.py
22
24
  t_bug_catcher/utils/logger.py
@@ -1,3 +1,4 @@
1
1
  requests<3.0.0,>=2.31.0
2
2
  bugsnag>=4.6.1
3
- retry~=0.9.2
3
+ retry~=0.9.2
4
+ whispers>=2.2.1
@@ -1,17 +0,0 @@
1
- import traceback
2
- from types import TracebackType
3
- from typing import List
4
-
5
-
6
- def get_frames(exc_traceback: TracebackType) -> List:
7
- """Get the frames of the exception.
8
-
9
- Args:
10
- exc_traceback (TracebackType): The traceback of the exception.
11
-
12
- Returns:
13
- List: The frames of the exception.
14
- """
15
- return [
16
- frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
17
- ]
File without changes
File without changes