t-bug-catcher 0.4.7__tar.gz → 0.4.9__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.
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/PKG-INFO +1 -1
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/setup.cfg +1 -1
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/setup.py +1 -1
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/__init__.py +1 -1
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/bug_catcher.py +4 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/jira.py +4 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/stack_saver.py +2 -14
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/utils/common.py +13 -0
- t_bug_catcher-0.4.9/t_bug_catcher/validation.py +118 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/SOURCES.txt +1 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/MANIFEST.in +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/README.rst +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/pyproject.toml +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/requirements.txt +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/config.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.4.7 → t_bug_catcher-0.4.9}/tests/test_t_bug_catcher.py +0 -0
|
@@ -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.
|
|
29
|
+
version="0.4.9",
|
|
30
30
|
zip_safe=False,
|
|
31
31
|
install_requires=install_requirements,
|
|
32
32
|
include_package_data=True,
|
|
@@ -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,
|
|
@@ -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":
|
|
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
|
|
@@ -74,3 +75,15 @@ def convert_keys_to_primitives(data: dict) -> dict:
|
|
|
74
75
|
else:
|
|
75
76
|
new_dict[str(key)] = value
|
|
76
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,118 @@
|
|
|
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
|
+
validation_logger.setLevel(logging.DEBUG)
|
|
10
|
+
console_handler = logging.StreamHandler()
|
|
11
|
+
console_handler.setLevel(logging.DEBUG)
|
|
12
|
+
|
|
13
|
+
formatter = logging.Formatter("%(levelname)s - %(name)s - %(message)s")
|
|
14
|
+
validation_logger.addHandler(console_handler)
|
|
15
|
+
|
|
16
|
+
EXCLUDED_DIRS = [".venv", "venv", "site-packages"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IncorrectTryBlockVisitor(ast.NodeVisitor):
|
|
20
|
+
"""A visitor that checks for incorrect try-except blocks."""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
"""Initializes the IncorrectTryBlockVisitor class."""
|
|
24
|
+
self.errors = {}
|
|
25
|
+
|
|
26
|
+
def visit_Try(self, node):
|
|
27
|
+
"""Visits the try-except block in the AST."""
|
|
28
|
+
message = (
|
|
29
|
+
"Error not handled: specify the error type or re-raise exception or "
|
|
30
|
+
"report it with `t_bug_catcher.report_error()`"
|
|
31
|
+
)
|
|
32
|
+
for handler in node.handlers:
|
|
33
|
+
if isinstance(handler.type, ast.Name) and handler.type.id == "Exception":
|
|
34
|
+
if not self._contains_raise(handler.body) and not self._contains_correct_error_handling(handler.body):
|
|
35
|
+
self.errors[handler.lineno] = message
|
|
36
|
+
elif handler.type is None: # 'except:' that catches everything
|
|
37
|
+
if not any(isinstance(x, ast.Raise) for x in handler.body):
|
|
38
|
+
self.errors[handler.lineno] = message
|
|
39
|
+
self.generic_visit(node)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def _contains_correct_error_handling(statements):
|
|
43
|
+
for stmt in statements:
|
|
44
|
+
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
|
|
45
|
+
func = stmt.value.func
|
|
46
|
+
if (isinstance(func, ast.Name) and func.id == "report_error") or (
|
|
47
|
+
isinstance(func, ast.Attribute) and func.attr == "report_error"
|
|
48
|
+
):
|
|
49
|
+
return True
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def _contains_raise(statements):
|
|
54
|
+
return any(isinstance(stmt, ast.Raise) for stmt in statements)
|
|
55
|
+
|
|
56
|
+
def get_errors(self):
|
|
57
|
+
"""Returns the errors found in the try-except blocks."""
|
|
58
|
+
return self.errors
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BroadExceptionWarning:
|
|
62
|
+
"""A class to represent a broad exception warning."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, file_path: str, lineno: int, message: str, source_code: str):
|
|
65
|
+
"""Initializes the BroadExceptionWarning class."""
|
|
66
|
+
self.lineno = lineno
|
|
67
|
+
self.message = message
|
|
68
|
+
self.__source_code_lines = source_code.split("\n")
|
|
69
|
+
self.code_line = self.__source_code_lines[lineno - 1]
|
|
70
|
+
self.code_lines = "\n".join(self.__source_code_lines[lineno - 3 : lineno + 2])
|
|
71
|
+
self.file_path = strip_path(file_path)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PreRunValidation:
|
|
75
|
+
"""A class to perform pre-run validation checks."""
|
|
76
|
+
|
|
77
|
+
def __init__(self):
|
|
78
|
+
"""Initializes the PreRunValidation class."""
|
|
79
|
+
self.broad_exception_warnings: list[BroadExceptionWarning] = []
|
|
80
|
+
self.analyze_project(os.getcwd())
|
|
81
|
+
self.log_warnings()
|
|
82
|
+
|
|
83
|
+
def analyze_project(self, project_dir: str):
|
|
84
|
+
"""Analyzes the project directory for broad exception warnings."""
|
|
85
|
+
for root, dirs, files in os.walk(project_dir):
|
|
86
|
+
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
|
|
87
|
+
for file in files:
|
|
88
|
+
if file.endswith(".py"):
|
|
89
|
+
file_path = os.path.join(root, file)
|
|
90
|
+
|
|
91
|
+
self.check_broad_exceptions(file_path)
|
|
92
|
+
|
|
93
|
+
def check_broad_exceptions(self, filename):
|
|
94
|
+
"""Checks for broad exception warnings in the specified file."""
|
|
95
|
+
with open(filename, "r", encoding="utf8") as source:
|
|
96
|
+
source_code = source.read()
|
|
97
|
+
try:
|
|
98
|
+
tree = ast.parse(source_code, filename=filename)
|
|
99
|
+
except SyntaxError:
|
|
100
|
+
return
|
|
101
|
+
except Exception as ex:
|
|
102
|
+
validation_logger.error(f"Unable to validate: {filename} - {ex}")
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
visitor = IncorrectTryBlockVisitor()
|
|
106
|
+
visitor.visit(tree)
|
|
107
|
+
|
|
108
|
+
for line, message in visitor.get_errors().items():
|
|
109
|
+
be_warn = BroadExceptionWarning(filename, line, message, source_code=source_code)
|
|
110
|
+
self.broad_exception_warnings.append(be_warn)
|
|
111
|
+
|
|
112
|
+
def log_warnings(self):
|
|
113
|
+
"""Logs the broad exception warnings."""
|
|
114
|
+
for be_warn in self.broad_exception_warnings:
|
|
115
|
+
validation_logger.warning(f"{be_warn.file_path}:{be_warn.lineno}: {be_warn.message}")
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
PRE_RUN_VALIDATION = PreRunValidation()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|