t-bug-catcher 0.4.4__tar.gz → 0.4.6__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 (26) hide show
  1. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/PKG-INFO +1 -1
  2. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/setup.cfg +1 -1
  3. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/setup.py +1 -1
  4. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/__init__.py +1 -1
  5. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/bug_catcher.py +25 -2
  6. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/jira.py +134 -1
  7. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/utils/common.py +14 -10
  8. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/PKG-INFO +1 -1
  9. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/tests/test_t_bug_catcher.py +1 -1
  10. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/MANIFEST.in +0 -0
  11. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/README.rst +0 -0
  12. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/pyproject.toml +0 -0
  13. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/requirements.txt +0 -0
  14. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/bug_snag.py +0 -0
  15. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/config.py +0 -0
  16. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/exceptions.py +0 -0
  17. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/resources/whispers_config.yml +0 -0
  18. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/stack_saver.py +0 -0
  19. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/utils/__init__.py +0 -0
  20. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/utils/logger.py +0 -0
  21. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher/workitems.py +0 -0
  22. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/SOURCES.txt +0 -0
  23. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  24. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  25. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/requires.txt +0 -0
  26. {t_bug_catcher-0.4.4 → t_bug_catcher-0.4.6}/t_bug_catcher.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.4.4
3
+ Version: 0.4.6
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.4
2
+ current_version = 0.4.6
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.4",
29
+ version="0.4.6",
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.4'
6
+ __version__ = '0.4.6'
7
7
  # fmt: on
8
8
 
9
9
  from .bug_catcher import (
@@ -1,5 +1,6 @@
1
1
  """JiraPoster class for interacting with the Jira API."""
2
2
 
3
+ import inspect
3
4
  import os
4
5
  import sys
5
6
  from types import TracebackType
@@ -129,11 +130,33 @@ class BugCatcher:
129
130
  _, exception, _ = sys.exc_info()
130
131
 
131
132
  if not isinstance(exception, Exception):
132
- logger.warning("Exception must be an instance of Exception.")
133
+ logger.warning("Implementation error. Incorrect exception type.")
134
+ inspected_frame = inspect.currentframe().f_back
135
+ if self.__configurator.is_jira_configured:
136
+ self.__jira.warning_message(
137
+ summary="bug_catcher implementation warning ⚠️",
138
+ inspected_frame=inspected_frame,
139
+ message=(
140
+ "Incorrect exception type. Check the variable that holds the exception "
141
+ "and make sure that report_error() in the try/except block is called."
142
+ ),
143
+ assignee=assignee,
144
+ )
133
145
  return
134
146
 
135
147
  if not getattr(exception, "__traceback__", None):
136
- logger.warning("No traceback available. Please provide a traceback.")
148
+ logger.warning("Implementation error. No traceback available.")
149
+ inspected_frame = inspect.currentframe().f_back
150
+ if self.__configurator.is_jira_configured:
151
+ self.__jira.warning_message(
152
+ summary="bug_catcher implementation warning ⚠️",
153
+ inspected_frame=inspected_frame,
154
+ message=(
155
+ "No traceback is available. Please, use an exception with a traceback in the try/except block. "
156
+ "Not just called exceptions."
157
+ ),
158
+ assignee=assignee,
159
+ )
137
160
  return
138
161
 
139
162
  handled_error = getattr(exception, "handled_error", None)
@@ -2,6 +2,7 @@
2
2
 
3
3
  import hashlib
4
4
  import json
5
+ import linecache
5
6
  import os
6
7
  import re
7
8
  import sys
@@ -375,6 +376,42 @@ class Jira:
375
376
  }
376
377
  ]
377
378
 
379
+ @staticmethod
380
+ def __warning_markup(warning_message: str, message: str) -> List[dict]:
381
+ """Create the error string markup.
382
+
383
+ Args:
384
+ warning_message (str): The warning message.
385
+
386
+ Returns:
387
+ dict: The error string markup.
388
+ """
389
+ return [
390
+ {
391
+ "type": "panel",
392
+ "attrs": {"panelType": "warning"},
393
+ "content": [
394
+ {
395
+ "type": "paragraph",
396
+ "content": [
397
+ {
398
+ "type": "text",
399
+ "text": warning_message,
400
+ "marks": [{"type": "code"}],
401
+ },
402
+ ],
403
+ },
404
+ {
405
+ "type": "paragraph",
406
+ "content": [
407
+ {"type": "text", "text": "Message: ", "marks": [{"type": "strong"}]},
408
+ {"type": "text", "text": message},
409
+ ],
410
+ },
411
+ ],
412
+ }
413
+ ]
414
+
378
415
  @staticmethod
379
416
  def __date_markup() -> List[dict]:
380
417
  """Create the date markup.
@@ -671,6 +708,33 @@ class Jira:
671
708
  + self.__error_markup(error_id),
672
709
  }
673
710
 
711
+ def __create_warning_description_markup(
712
+ self,
713
+ warning_message: str,
714
+ message: str,
715
+ warning_id: str,
716
+ ) -> dict:
717
+ """Create a warning description.
718
+
719
+ Args:
720
+ warning_message (str): The warning message.
721
+ warning_id (str): The warning ID.
722
+
723
+ Returns:
724
+ dict: A dictionary containing the version, type, and content.
725
+ """
726
+ return {
727
+ "version": 1,
728
+ "type": "doc",
729
+ "content": []
730
+ + self.__warning_markup(warning_message, message)
731
+ + self.__bot_name_markup()
732
+ + self.__date_markup()
733
+ + self.__runlink_markup()
734
+ + self.__environment_markup()
735
+ + self.__error_markup(warning_id),
736
+ }
737
+
674
738
  def __create_transtion_markup(self, issue_status: str) -> dict:
675
739
  """Create a transition markup.
676
740
 
@@ -850,7 +914,7 @@ class Jira:
850
914
  if issue_status.lower() not in ["to do", "open"]:
851
915
  self.issue_transition(
852
916
  ticket_id=existing_ticket["id"],
853
- transition_id=self._transition_types.get("to do", "open"),
917
+ transition_id=self._transition_types.get("to do") or self._transition_types.get("open"),
854
918
  )
855
919
  self.update_comment(
856
920
  ticket_id=existing_ticket["id"],
@@ -962,6 +1026,55 @@ class Jira:
962
1026
  self.move_ticket_to_board(ticket_id)
963
1027
  return response
964
1028
 
1029
+ def warning_message(self, summary: str, inspected_frame, message: str, assignee: Optional[str] = None) -> None:
1030
+ """Create a new ticket with warning message.
1031
+
1032
+ Args:
1033
+ summary (str): The summary of the ticket.
1034
+ inspected_frame (frame): The frame of the warning.
1035
+ assignee (str, optional): The assignee of the ticket. Defaults to None.
1036
+
1037
+ Returns:
1038
+ The response from creating the ticket.
1039
+ """
1040
+ if self._project_key == CONFIG.SUPPORT_BOARD and CONFIG.ADMIN_CODE:
1041
+ summary = CONFIG.ADMIN_CODE + " - " + summary
1042
+
1043
+ warning_id, warning_message = self.__generate_warning_id(inspected_frame)
1044
+
1045
+ existing_ticket = self.filter_tickets(
1046
+ all_tickets=self.get_issues()["issues"],
1047
+ error_id=warning_id,
1048
+ )
1049
+ if existing_ticket:
1050
+ self.__update_existing_ticket(
1051
+ existing_ticket=existing_ticket,
1052
+ summary=summary,
1053
+ )
1054
+ return existing_ticket
1055
+
1056
+ assignee_id = None
1057
+ assignee = assignee if assignee else self._default_assignee
1058
+ if assignee:
1059
+ try:
1060
+ assignee_id = self.__get_assignee(assignee)
1061
+ except Exception as ex:
1062
+ logger.info(f"Failed to get assignee {assignee} due to: {ex}")
1063
+
1064
+ description = self.__create_warning_description_markup(
1065
+ warning_message=warning_message,
1066
+ message=message,
1067
+ warning_id=warning_id,
1068
+ )
1069
+ response = self.__create_new_ticket(
1070
+ summary=summary,
1071
+ description=description,
1072
+ assignee_id=assignee_id,
1073
+ labels=["bug_catcher_warning"],
1074
+ )
1075
+
1076
+ return response
1077
+
965
1078
  def report_error(
966
1079
  self,
967
1080
  exception: Optional[Exception] = None,
@@ -1271,6 +1384,26 @@ class Jira:
1271
1384
  error_id = f"{path}-{exception_chain}-{frames[-1].line}-" f"{exc_type.__module__}-{exc_type.__name__}"
1272
1385
  return hashlib.md5(error_id.encode()).hexdigest()
1273
1386
 
1387
+ @staticmethod
1388
+ def __generate_warning_id(inspected_frame) -> tuple:
1389
+ """Generates an error string ID using the exception, function name, and error string.
1390
+
1391
+ Args:
1392
+ inspected_frame (frame): The frame of the warning.
1393
+
1394
+ Returns:
1395
+ tuple: The generated error string ID and the warning message.
1396
+ """
1397
+ lineno = inspected_frame.f_lineno
1398
+ filename = inspected_frame.f_code.co_filename
1399
+ func = inspected_frame.f_code.co_name
1400
+ line = linecache.getline(filename, lineno).strip()
1401
+ rel_path = os.path.relpath(filename, os.getcwd())
1402
+
1403
+ warning_message = f"{line}\n{rel_path}:{func}:{lineno}"
1404
+ warning_id = hashlib.md5(f"{filename}-{lineno}-{func}-{line}".encode()).hexdigest()
1405
+ return warning_id, warning_message
1406
+
1274
1407
  def __get_assignee(self, assignee: str) -> str:
1275
1408
  """Get assignee Jira user by ID.
1276
1409
 
@@ -1,3 +1,4 @@
1
+ import copy
1
2
  import traceback
2
3
  from datetime import date, datetime
3
4
  from json import JSONEncoder
@@ -20,21 +21,24 @@ class Encoder(JSONEncoder):
20
21
  Returns:
21
22
  str: The json string.
22
23
  """
24
+ object_copy = copy.deepcopy(o)
23
25
  try:
24
- if hasattr(o, "__dict__"):
26
+ if hasattr(object_copy, "__dict__"):
25
27
  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)
28
+ key
29
+ for key in object_copy.__dict__.keys()
30
+ if any(s in str(key).lower() for s in CONFIG.KEYS_TO_REMOVE)
27
31
  ]
28
32
  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)
33
+ del object_copy.__dict__[key]
34
+ return {str(key): str(value) for key, value in object_copy.__dict__.items()}
35
+ if isinstance(object_copy, (datetime, date)):
36
+ return object_copy.isoformat()
37
+ if isinstance(object_copy, Path):
38
+ return str(object_copy)
39
+ return super().default(self, object_copy)
36
40
  except TypeError:
37
- return str(o)
41
+ return str(object_copy)
38
42
 
39
43
 
40
44
  def get_frames(exc_traceback: TracebackType) -> List:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -65,7 +65,7 @@ class TestBugCatcher(unittest.TestCase):
65
65
  actual_call = mock_warning.call_args
66
66
  warning_message = actual_call[0][0]
67
67
  self.assertTrue(warning_message.startswith("Failed to create Jira issue due to"))
68
- self.assertEqual(mock_warning.call_count, 1)
68
+ self.assertEqual(mock_warning.call_count, 2)
69
69
 
70
70
 
71
71
  if __name__ == "__main__":
File without changes
File without changes