t-bug-catcher 0.4.1__tar.gz → 0.4.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 (27) hide show
  1. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/PKG-INFO +1 -1
  2. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/setup.cfg +1 -1
  3. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/setup.py +1 -1
  4. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/__init__.py +1 -1
  5. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/config.py +11 -0
  6. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/jira.py +14 -0
  7. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/stack_saver.py +20 -33
  8. t_bug_catcher-0.4.3/t_bug_catcher/utils/common.py +69 -0
  9. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/PKG-INFO +1 -1
  10. t_bug_catcher-0.4.1/t_bug_catcher/utils/common.py +0 -41
  11. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/MANIFEST.in +0 -0
  12. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/README.rst +0 -0
  13. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/pyproject.toml +0 -0
  14. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/requirements.txt +0 -0
  15. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/bug_catcher.py +0 -0
  16. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/bug_snag.py +0 -0
  17. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/exceptions.py +0 -0
  18. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/resources/whispers_config.yml +0 -0
  19. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/utils/__init__.py +0 -0
  20. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/utils/logger.py +0 -0
  21. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher/workitems.py +0 -0
  22. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
  23. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  24. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  25. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/requires.txt +0 -0
  26. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/t_bug_catcher.egg-info/top_level.txt +0 -0
  27. {t_bug_catcher-0.4.1 → t_bug_catcher-0.4.3}/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.4.1
3
+ Version: 0.4.3
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.4.1
2
+ current_version = 0.4.3
3
3
  commit = True
4
4
  tag = False
5
5
 
@@ -26,7 +26,7 @@ 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.4.1",
29
+ version="0.4.3",
30
30
  zip_safe=False,
31
31
  install_requires=install_requirements,
32
32
  include_package_data=True,
@@ -3,7 +3,7 @@
3
3
  __author__ = """Thoughtful"""
4
4
  __email__ = "support@thoughtful.ai"
5
5
  # fmt: off
6
- __version__ = '0.4.1'
6
+ __version__ = '0.4.3'
7
7
  # fmt: on
8
8
 
9
9
  from .bug_catcher import (
@@ -15,8 +15,19 @@ class Config:
15
15
  SUMMARY_LENGTH: int = 120
16
16
  STACK_SCOPE: int = 3
17
17
 
18
+ class TICKET_PRIORITIES:
19
+ """Priorities class for configuring the application."""
20
+
21
+ HIGHEST: str = "1"
22
+ HIGH: str = "2"
23
+ MEDIUM: str = "3"
24
+ LOW: str = "4"
25
+ LOWEST: str = "5"
26
+
18
27
  SUPPORT_BOARD = "AB"
19
28
 
29
+ KEYS_TO_REMOVE = ["credential", "password"]
30
+
20
31
  RC_RUN_LINK = (
21
32
  f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
22
33
  f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
@@ -209,6 +209,7 @@ class Jira:
209
209
  issue_type: str,
210
210
  assignee: Optional[str] = None,
211
211
  labels: Optional[list] = None,
212
+ priority: Optional[str] = None,
212
213
  ) -> str:
213
214
  """Generates the issue body payload for creating a new issue.
214
215
 
@@ -218,6 +219,7 @@ class Jira:
218
219
  assignee (str): The assignee of the issue.
219
220
  issue_type (str): The type of the issue.
220
221
  labels (list, optional): The labels of the issue. Defaults to None.
222
+ priority (str, optional): The priority of the issue. Defaults to None.
221
223
 
222
224
  Returns:
223
225
  The JSON payload for creating a new issue.
@@ -235,6 +237,8 @@ class Jira:
235
237
  fields["fields"]["labels"] = labels
236
238
  if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
237
239
  fields["fields"]["customfield_10077"] = [CONFIG.ADMIN_CODE]
240
+ if priority:
241
+ fields["fields"]["priority"] = {"id": priority}
238
242
  payload = json.dumps(fields)
239
243
  return payload
240
244
 
@@ -890,6 +894,7 @@ class Jira:
890
894
  assignee_id: Optional[str] = None,
891
895
  attachments: Optional[List] = None,
892
896
  labels: Optional[list] = None,
897
+ priority: Optional[str] = None,
893
898
  ) -> requests.Response:
894
899
  """Create a new ticket.
895
900
 
@@ -899,6 +904,7 @@ class Jira:
899
904
  assignee_id (str, optional): The assignee of the ticket. Defaults to None.
900
905
  attachments (List, optional): The list of attachments. Defaults to None.
901
906
  labels (List, optional): The list of labels. Defaults to None.
907
+ priority (str, optional): The priority of the ticket. Defaults to None.
902
908
 
903
909
  Returns:
904
910
  The response from creating the ticket.
@@ -923,6 +929,7 @@ class Jira:
923
929
  assignee=assignee_id,
924
930
  issue_type=issue_type,
925
931
  labels=labels,
932
+ priority=priority,
926
933
  )
927
934
  response = self.post_ticket(issue=issue)
928
935
 
@@ -932,6 +939,7 @@ class Jira:
932
939
  description=description,
933
940
  assignee=assignee_id,
934
941
  issue_type=issue_type,
942
+ priority=priority,
935
943
  )
936
944
  response = self.post_ticket(issue=issue)
937
945
 
@@ -1034,12 +1042,15 @@ class Jira:
1034
1042
  metadata=metadata,
1035
1043
  )
1036
1044
 
1045
+ priority = CONFIG.TICKET_PRIORITIES.HIGH if CONFIG.STAGE.lower() == "hypercare" else None
1046
+
1037
1047
  response = self.__create_new_ticket(
1038
1048
  summary=summary,
1039
1049
  description=description,
1040
1050
  assignee_id=assignee_id,
1041
1051
  attachments=attachments,
1042
1052
  labels=["bug_catcher"],
1053
+ priority=priority,
1043
1054
  )
1044
1055
  if os.path.exists(stack_trace):
1045
1056
  os.remove(stack_trace)
@@ -1091,12 +1102,15 @@ class Jira:
1091
1102
  error_id=error_id,
1092
1103
  )
1093
1104
 
1105
+ priority = CONFIG.TICKET_PRIORITIES.HIGHEST if CONFIG.STAGE.lower() == "hypercare" else None
1106
+
1094
1107
  response = self.__create_new_ticket(
1095
1108
  summary=summary,
1096
1109
  description=description,
1097
1110
  assignee_id=assignee_id,
1098
1111
  attachments=[stack_trace] if stack_trace else None,
1099
1112
  labels=["bug_catcher", "fatal_error"],
1113
+ priority=priority,
1100
1114
  )
1101
1115
  if os.path.exists(stack_trace):
1102
1116
  os.remove(stack_trace)
@@ -1,5 +1,6 @@
1
1
  import inspect
2
2
  import json
3
+ import linecache
3
4
  import os
4
5
  import re
5
6
  import sys
@@ -12,7 +13,7 @@ import whispers
12
13
 
13
14
  from .config import CONFIG
14
15
  from .utils import logger
15
- from .utils.common import Encoder
16
+ from .utils.common import Encoder, convert_keys_to_primitives
16
17
 
17
18
 
18
19
  class StackSaver:
@@ -34,8 +35,7 @@ class StackSaver:
34
35
  """
35
36
  return path.replace(os.getcwd(), "").strip(os.sep)
36
37
 
37
- @staticmethod
38
- def serialize_frame_info(frame_info: dict) -> dict:
38
+ def serialize_frame_info(self, frame_info: dict) -> dict:
39
39
  """A static method to serialize the frame info.
40
40
 
41
41
  Args:
@@ -44,26 +44,13 @@ class StackSaver:
44
44
  Returns:
45
45
  dict: The serialized frame info.
46
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)
47
+ run_locals = convert_keys_to_primitives(frame_info["locals"]) if frame_info["locals"] else {}
48
+ run_args = convert_keys_to_primitives(frame_info["args"]) if frame_info["args"] else {}
64
49
  serializable_frame_info = {
65
50
  "filename": frame_info["filename"],
66
51
  "function_name": frame_info["function_name"],
52
+ "line_number": frame_info["line_number"],
53
+ "line": frame_info["line"],
67
54
  "locals": run_locals,
68
55
  "args": run_args,
69
56
  }
@@ -88,7 +75,7 @@ class StackSaver:
88
75
  continue
89
76
  if isinstance(var, (ModuleType, FunctionType)):
90
77
  continue
91
- local_variables[var_name] = var
78
+ local_variables[str(var_name)] = var
92
79
  return local_variables
93
80
 
94
81
  def mask_credentials(self, file_path: str) -> None:
@@ -111,25 +98,18 @@ class StackSaver:
111
98
  args = f"-c {config_path} {file_path}"
112
99
 
113
100
  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
101
 
122
102
  for index, line in enumerate(filedata):
123
- if not unique_secrets:
103
+ if not secrets:
124
104
  break
125
105
 
126
- for secret in unique_secrets:
106
+ for secret in secrets:
127
107
  if secret.key in line and secret.value in line:
128
108
  filedata[index] = line.replace(secret.value, secret.value[:1] + "***")
129
- unique_secrets.pop(unique_secrets.index(secret))
109
+ secrets.pop(secrets.index(secret))
130
110
  break
131
111
 
132
- if unique_secrets:
112
+ if secrets:
133
113
  logger.warning("Failed to mask credentials")
134
114
  os.remove(file_path)
135
115
  raise Exception("Failed to mask credentials")
@@ -149,20 +129,27 @@ class StackSaver:
149
129
  try:
150
130
  frames = []
151
131
  stack_details_json = []
132
+ seen_frames = set()
152
133
  tb = exception.__traceback__ if exception else sys.exc_info()[2]
153
134
  while tb is not None:
154
135
  frame = tb.tb_frame
136
+ if frame.f_code.co_name in seen_frames:
137
+ tb = tb.tb_next
138
+ continue
139
+ seen_frames.add(frame.f_code.co_name)
155
140
  if "site-packages" in frame.f_code.co_filename:
156
141
  tb = tb.tb_next
157
142
  continue
158
143
  frames.append(frame)
159
144
  tb = tb.tb_next
160
- frames = frames[: CONFIG.LIMITS.STACK_SCOPE]
145
+ frames = frames[-CONFIG.LIMITS.STACK_SCOPE :]
161
146
 
162
147
  for frame in frames:
163
148
  frame_info = {
164
149
  "filename": self.strip_path(frame.f_code.co_filename),
165
150
  "function_name": frame.f_code.co_name,
151
+ "line_number": frame.f_lineno,
152
+ "line": linecache.getline(frame.f_code.co_filename, frame.f_lineno).strip(),
166
153
  "locals": self.filter_variables(frame.f_locals),
167
154
  "args": self.filter_variables(inspect.getargvalues(frame)[3]),
168
155
  }
@@ -0,0 +1,69 @@
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
+ from ..config import CONFIG
9
+
10
+
11
+ class Encoder(JSONEncoder):
12
+ """This class is used to encode the Episode object to json."""
13
+
14
+ def default(self, o):
15
+ """This method is used to encode the Episode object to json.
16
+
17
+ Args:
18
+ o (object): The object to be encoded.
19
+
20
+ Returns:
21
+ str: The json string.
22
+ """
23
+ try:
24
+ if hasattr(o, "__dict__"):
25
+ keys_to_remove = [
26
+ key for key in o.__dict__.keys() if any(s in str(key).lower() for s in CONFIG.KEYS_TO_REMOVE)
27
+ ]
28
+ for key in keys_to_remove:
29
+ del o.__dict__[key]
30
+ return {str(key): str(value) for key, value in o.__dict__.items()}
31
+ if isinstance(o, (datetime, date)):
32
+ return o.isoformat()
33
+ if isinstance(o, Path):
34
+ return str(o)
35
+ return super().default(self, o)
36
+ except TypeError:
37
+ return str(o)
38
+
39
+
40
+ def get_frames(exc_traceback: TracebackType) -> List:
41
+ """Get the frames of the exception.
42
+
43
+ Args:
44
+ exc_traceback (TracebackType): The traceback of the exception.
45
+
46
+ Returns:
47
+ List: The frames of the exception.
48
+ """
49
+ return [
50
+ frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
51
+ ]
52
+
53
+
54
+ def convert_keys_to_primitives(data: dict) -> dict:
55
+ """A function that recursively converts keys in a nested dictionary to primitives.
56
+
57
+ Args:
58
+ data (dict): The input dictionary to convert keys.
59
+
60
+ Returns:
61
+ dict: A new dictionary with keys converted to strings.
62
+ """
63
+ new_dict = {}
64
+ for key, value in data.items():
65
+ if isinstance(value, dict):
66
+ new_dict[str(key)] = convert_keys_to_primitives(value)
67
+ else:
68
+ new_dict[str(key)] = value
69
+ return new_dict
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -1,41 +0,0 @@
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
- ]
File without changes
File without changes