t-bug-catcher 0.4.6__tar.gz → 0.4.8__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.6 → t_bug_catcher-0.4.8}/PKG-INFO +1 -1
  2. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/setup.cfg +1 -1
  3. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/setup.py +1 -1
  4. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/__init__.py +1 -1
  5. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/bug_catcher.py +4 -0
  6. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/jira.py +5 -1
  7. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/stack_saver.py +2 -14
  8. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/utils/common.py +17 -1
  9. t_bug_catcher-0.4.8/t_bug_catcher/validation.py +105 -0
  10. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher.egg-info/PKG-INFO +1 -1
  11. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher.egg-info/SOURCES.txt +1 -0
  12. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/tests/test_t_bug_catcher.py +1 -1
  13. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/MANIFEST.in +0 -0
  14. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/README.rst +0 -0
  15. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/pyproject.toml +0 -0
  16. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/requirements.txt +0 -0
  17. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/bug_snag.py +0 -0
  18. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/config.py +0 -0
  19. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/exceptions.py +0 -0
  20. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/resources/whispers_config.yml +0 -0
  21. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/utils/__init__.py +0 -0
  22. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/utils/logger.py +0 -0
  23. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher/workitems.py +0 -0
  24. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
  25. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher.egg-info/not-zip-safe +0 -0
  26. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/t_bug_catcher.egg-info/requires.txt +0 -0
  27. {t_bug_catcher-0.4.6 → t_bug_catcher-0.4.8}/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.6
3
+ Version: 0.4.8
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.6
2
+ current_version = 0.4.8
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.6",
29
+ version="0.4.8",
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.6'
6
+ __version__ = '0.4.8'
7
7
  # fmt: on
8
8
 
9
9
  from .bug_catcher import (
@@ -12,6 +12,7 @@ from .jira import Jira
12
12
  from .stack_saver import StackSaver
13
13
  from .utils import logger
14
14
  from .utils.common import get_frames
15
+ from .validation import PRE_RUN_VALIDATION
15
16
 
16
17
 
17
18
  class Configurator:
@@ -332,3 +333,6 @@ report_error_to_jira = __bug_catcher.report_error_to_jira
332
333
  report_error_to_bugsnag = __bug_catcher.report_error_to_bugsnag
333
334
  install_sys_hook = __bug_catcher.install_sys_hook
334
335
  uninstall_sys_hook = __bug_catcher.uninstall_sys_hook
336
+
337
+ if CONFIG.STAGE.lower() == "delivery":
338
+ pre_run_validation = PRE_RUN_VALIDATION
@@ -692,6 +692,10 @@ class Jira:
692
692
  exc_traceback_info: str = (
693
693
  f"Traceback (most recent call last):\n{''.join(traceback.format_tb(exc_traceback))}{exc_info}"
694
694
  )
695
+ if len(exc_traceback_info) > 30000:
696
+ exc_traceback_info: str = (
697
+ f"Traceback (most recent call last):\n{''.join(traceback.format_tb(exc_traceback)[-1])}{exc_info}"
698
+ )
695
699
 
696
700
  return {
697
701
  "version": 1,
@@ -1165,7 +1169,7 @@ class Jira:
1165
1169
  labels=["bug_catcher"],
1166
1170
  priority=priority,
1167
1171
  )
1168
- if os.path.exists(stack_trace):
1172
+ if stack_trace and os.path.exists(stack_trace):
1169
1173
  os.remove(stack_trace)
1170
1174
  return response
1171
1175
  except Exception as ex:
@@ -13,7 +13,7 @@ import whispers
13
13
 
14
14
  from .config import CONFIG
15
15
  from .utils import logger
16
- from .utils.common import Encoder, convert_keys_to_primitives
16
+ from .utils.common import Encoder, convert_keys_to_primitives, strip_path
17
17
 
18
18
 
19
19
  class StackSaver:
@@ -23,18 +23,6 @@ class StackSaver:
23
23
  """Initializes the StackSaver class."""
24
24
  pass
25
25
 
26
- @staticmethod
27
- def strip_path(path: str):
28
- """A static method to strip the current working directory path from the input.
29
-
30
- Args:
31
- path (str): The path from which to strip the current working directory path.
32
-
33
- Returns:
34
- str: The stripped path.
35
- """
36
- return path.replace(os.getcwd(), "").strip(os.sep)
37
-
38
26
  def serialize_frame_info(self, frame_info: dict) -> dict:
39
27
  """A static method to serialize the frame info.
40
28
 
@@ -146,7 +134,7 @@ class StackSaver:
146
134
 
147
135
  for frame in frames:
148
136
  frame_info = {
149
- "filename": self.strip_path(frame.f_code.co_filename),
137
+ "filename": strip_path(frame.f_code.co_filename),
150
138
  "function_name": frame.f_code.co_name,
151
139
  "line_number": frame.f_lineno,
152
140
  "line": linecache.getline(frame.f_code.co_filename, frame.f_lineno).strip(),
@@ -1,4 +1,5 @@
1
1
  import copy
2
+ import os
2
3
  import traceback
3
4
  from datetime import date, datetime
4
5
  from json import JSONEncoder
@@ -21,7 +22,10 @@ class Encoder(JSONEncoder):
21
22
  Returns:
22
23
  str: The json string.
23
24
  """
24
- object_copy = copy.deepcopy(o)
25
+ try:
26
+ object_copy = copy.deepcopy(o)
27
+ except TypeError:
28
+ return str(o)
25
29
  try:
26
30
  if hasattr(object_copy, "__dict__"):
27
31
  keys_to_remove = [
@@ -71,3 +75,15 @@ def convert_keys_to_primitives(data: dict) -> dict:
71
75
  else:
72
76
  new_dict[str(key)] = value
73
77
  return new_dict
78
+
79
+
80
+ def strip_path(path: str):
81
+ """A function to strip the current working directory path from the input.
82
+
83
+ Args:
84
+ path (str): The path from which to strip the current working directory path.
85
+
86
+ Returns:
87
+ str: The stripped path.
88
+ """
89
+ return path.replace(os.getcwd(), "").strip(os.sep)
@@ -0,0 +1,105 @@
1
+ import ast
2
+ import logging
3
+ import os
4
+
5
+ from t_bug_catcher.utils.common import strip_path
6
+
7
+ validation_logger = logging.getLogger("t_bug_catcher")
8
+
9
+ EXCLUDED_DIRS = [".venv", "venv", "site-packages"]
10
+
11
+
12
+ class IncorrectTryBlockVisitor(ast.NodeVisitor):
13
+ """A visitor that checks for incorrect try-except blocks."""
14
+
15
+ def __init__(self):
16
+ """Initializes the IncorrectTryBlockVisitor class."""
17
+ self.errors = {}
18
+
19
+ def visit_Try(self, node):
20
+ """Visits the try-except block in the AST."""
21
+ message = (
22
+ "Error not handled: specify the error type or re-raise exception or "
23
+ "report it with `t_bug_catcher.report_error()`"
24
+ )
25
+ for handler in node.handlers:
26
+ if isinstance(handler.type, ast.Name) and handler.type.id == "Exception":
27
+ if not self._contains_raise(handler.body) and not self._contains_correct_error_handling(handler.body):
28
+ self.errors[handler.lineno] = message
29
+ elif handler.type is None: # 'except:' that catches everything
30
+ if not any(isinstance(x, ast.Raise) for x in handler.body):
31
+ self.errors[handler.lineno] = message
32
+ self.generic_visit(node)
33
+
34
+ @staticmethod
35
+ def _contains_correct_error_handling(statements):
36
+ for stmt in statements:
37
+ if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
38
+ func = stmt.value.func
39
+ if (isinstance(func, ast.Name) and func.id == "report_error") or (
40
+ isinstance(func, ast.Attribute) and func.attr == "report_error"
41
+ ):
42
+ return True
43
+ return False
44
+
45
+ @staticmethod
46
+ def _contains_raise(statements):
47
+ return any(isinstance(stmt, ast.Raise) for stmt in statements)
48
+
49
+ def get_errors(self):
50
+ """Returns the errors found in the try-except blocks."""
51
+ return self.errors
52
+
53
+
54
+ class BroadExceptionWarning:
55
+ """A class to represent a broad exception warning."""
56
+
57
+ def __init__(self, file_path: str, lineno: int, message: str, source_code: str):
58
+ """Initializes the BroadExceptionWarning class."""
59
+ self.lineno = lineno
60
+ self.message = message
61
+ self.__source_code_lines = source_code.split("\n")
62
+ self.code_line = self.__source_code_lines[lineno - 1]
63
+ self.code_lines = "\n".join(self.__source_code_lines[lineno - 3 : lineno + 2])
64
+ self.file_path = strip_path(file_path)
65
+
66
+
67
+ class PreRunValidation:
68
+ """A class to perform pre-run validation checks."""
69
+
70
+ def __init__(self):
71
+ """Initializes the PreRunValidation class."""
72
+ self.broad_exception_warnings: list[BroadExceptionWarning] = []
73
+ self.analyze_project(os.getcwd())
74
+ self.log_warnings()
75
+
76
+ def analyze_project(self, project_dir: str):
77
+ """Analyzes the project directory for broad exception warnings."""
78
+ for root, dirs, files in os.walk(project_dir):
79
+ dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
80
+ for file in files:
81
+ if file.endswith(".py"):
82
+ file_path = os.path.join(root, file)
83
+
84
+ self.check_broad_exceptions(file_path)
85
+
86
+ def check_broad_exceptions(self, filename):
87
+ """Checks for broad exception warnings in the specified file."""
88
+ with open(filename, "r", encoding="utf8") as source:
89
+ source_code = source.read()
90
+ tree = ast.parse(source_code, filename=filename)
91
+
92
+ visitor = IncorrectTryBlockVisitor()
93
+ visitor.visit(tree)
94
+
95
+ for line, message in visitor.get_errors().items():
96
+ be_warn = BroadExceptionWarning(filename, line, message, source_code=source_code)
97
+ self.broad_exception_warnings.append(be_warn)
98
+
99
+ def log_warnings(self):
100
+ """Logs the broad exception warnings."""
101
+ for be_warn in self.broad_exception_warnings:
102
+ validation_logger.warning(f"{be_warn.file_path}:{be_warn.lineno}: {be_warn.message}")
103
+
104
+
105
+ PRE_RUN_VALIDATION = PreRunValidation()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: t_bug_catcher
3
- Version: 0.4.6
3
+ Version: 0.4.8
4
4
  Summary: Bug catcher
5
5
  Home-page: https://www.thoughtful.ai/
6
6
  Author: Thoughtful
@@ -11,6 +11,7 @@ t_bug_catcher/config.py
11
11
  t_bug_catcher/exceptions.py
12
12
  t_bug_catcher/jira.py
13
13
  t_bug_catcher/stack_saver.py
14
+ t_bug_catcher/validation.py
14
15
  t_bug_catcher/workitems.py
15
16
  t_bug_catcher.egg-info/PKG-INFO
16
17
  t_bug_catcher.egg-info/SOURCES.txt
@@ -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, 2)
68
+ self.assertEqual(mock_warning.call_count, 1)
69
69
 
70
70
 
71
71
  if __name__ == "__main__":
File without changes
File without changes