t-bug-catcher 0.5.5__tar.gz → 0.5.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.
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/PKG-INFO +1 -1
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/setup.cfg +1 -1
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/setup.py +1 -1
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/__init__.py +3 -1
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/bug_catcher.py +10 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/bug_snag.py +0 -2
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/jira.py +29 -17
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/SOURCES.txt +0 -1
- t_bug_catcher-0.5.5/t_bug_catcher/validation.py +0 -123
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/MANIFEST.in +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/README.rst +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/pyproject.toml +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/requirements.txt +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/config.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/stack_saver.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/common.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.5.5 → t_bug_catcher-0.5.6}/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.5.
|
|
29
|
+
version="0.5.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.5.
|
|
6
|
+
__version__ = '0.5.6'
|
|
7
7
|
# fmt: on
|
|
8
8
|
|
|
9
9
|
from .bug_catcher import (
|
|
@@ -12,6 +12,7 @@ from .bug_catcher import (
|
|
|
12
12
|
attach_file_to_exception,
|
|
13
13
|
install_sys_hook,
|
|
14
14
|
uninstall_sys_hook,
|
|
15
|
+
get_errors_count,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
__all__ = [
|
|
@@ -20,4 +21,5 @@ __all__ = [
|
|
|
20
21
|
"attach_file_to_exception",
|
|
21
22
|
"install_sys_hook",
|
|
22
23
|
"uninstall_sys_hook",
|
|
24
|
+
"get_errors_count",
|
|
23
25
|
]
|
|
@@ -92,12 +92,17 @@ class BugCatcher:
|
|
|
92
92
|
self.__configurator: Configurator = Configurator(self.__jira, self.__bug_snag)
|
|
93
93
|
self.__sys_excepthook = None
|
|
94
94
|
self.__stack_saver = StackSaver()
|
|
95
|
+
self.__errors_count = 0
|
|
95
96
|
|
|
96
97
|
@property
|
|
97
98
|
def configure(self):
|
|
98
99
|
"""Configures the JiraPoster and BugSnag classes."""
|
|
99
100
|
return self.__configurator
|
|
100
101
|
|
|
102
|
+
def get_errors_count(self):
|
|
103
|
+
"""Returns the number of exceptions reported."""
|
|
104
|
+
return self.__errors_count
|
|
105
|
+
|
|
101
106
|
def report_error(
|
|
102
107
|
self,
|
|
103
108
|
exception: Optional[Exception] = None,
|
|
@@ -186,6 +191,8 @@ class BugCatcher:
|
|
|
186
191
|
exc_info = f"{os.path.basename(frames[-1].filename)}:{frames[-1].name}:{frames[-1].lineno}"
|
|
187
192
|
exception.handled_error = exc_info
|
|
188
193
|
|
|
194
|
+
self.__errors_count += 1
|
|
195
|
+
|
|
189
196
|
def report_error_to_jira(
|
|
190
197
|
self,
|
|
191
198
|
exception: Optional[Exception] = None,
|
|
@@ -286,6 +293,8 @@ class BugCatcher:
|
|
|
286
293
|
if self.__configurator.is_bugsnag_configured:
|
|
287
294
|
self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
|
|
288
295
|
|
|
296
|
+
self.__errors_count += 1
|
|
297
|
+
|
|
289
298
|
def __get_sys_hook_attribute(self, attribute: str = "bug_catcher_client"):
|
|
290
299
|
"""Checks if the system hook is installed.
|
|
291
300
|
|
|
@@ -332,3 +341,4 @@ report_error_to_jira = __bug_catcher.report_error_to_jira
|
|
|
332
341
|
report_error_to_bugsnag = __bug_catcher.report_error_to_bugsnag
|
|
333
342
|
install_sys_hook = __bug_catcher.install_sys_hook
|
|
334
343
|
uninstall_sys_hook = __bug_catcher.uninstall_sys_hook
|
|
344
|
+
get_errors_count = __bug_catcher.get_errors_count
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import sys
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from typing import Optional
|
|
4
3
|
|
|
5
4
|
import bugsnag
|
|
@@ -44,7 +43,6 @@ class BugSnag:
|
|
|
44
43
|
"Content-Type": "application/json",
|
|
45
44
|
"Bugsnag-Api-Key": api_key,
|
|
46
45
|
"Bugsnag-Payload-Version": "4",
|
|
47
|
-
"Bugsnag-Sent-At": f"{datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')}",
|
|
48
46
|
"Bugsnag-Span-Sampling": "True",
|
|
49
47
|
},
|
|
50
48
|
data='{"message": "test"}',
|
|
@@ -476,11 +476,32 @@ class Jira:
|
|
|
476
476
|
return []
|
|
477
477
|
frames = get_frames(exc_traceback)
|
|
478
478
|
file_name, line_no, _, _ = frames[-1]
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
479
|
+
try:
|
|
480
|
+
path = Path.relative_to(Path(file_name), Path.cwd())
|
|
481
|
+
code_line = (
|
|
482
|
+
f"{self._build_info['repository_url']}/src/{self._build_info['last_commit']}"
|
|
483
|
+
f"/{path.as_posix()}?at={self._build_info['branch']}#lines-{line_no}"
|
|
484
|
+
)
|
|
485
|
+
payload = [
|
|
486
|
+
{
|
|
487
|
+
"type": "text",
|
|
488
|
+
"text": f"{str(path.as_posix())}:{line_no}",
|
|
489
|
+
"marks": [
|
|
490
|
+
{
|
|
491
|
+
"type": "link",
|
|
492
|
+
"attrs": {"href": code_line},
|
|
493
|
+
}
|
|
494
|
+
],
|
|
495
|
+
},
|
|
496
|
+
]
|
|
497
|
+
except ValueError:
|
|
498
|
+
payload = [
|
|
499
|
+
{
|
|
500
|
+
"type": "text",
|
|
501
|
+
"text": "Package error.",
|
|
502
|
+
},
|
|
503
|
+
]
|
|
504
|
+
|
|
484
505
|
input_datetime = datetime.strptime(self._build_info["commit_datetime"], "%Y-%m-%d %H:%M:%S").strftime(
|
|
485
506
|
"%d %B %Y %I:%M:%S %p"
|
|
486
507
|
)
|
|
@@ -521,17 +542,8 @@ class Jira:
|
|
|
521
542
|
"type": "text",
|
|
522
543
|
"text": " > ",
|
|
523
544
|
},
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
"text": f"{str(path.as_posix())}:{line_no}",
|
|
527
|
-
"marks": [
|
|
528
|
-
{
|
|
529
|
-
"type": "link",
|
|
530
|
-
"attrs": {"href": code_line},
|
|
531
|
-
}
|
|
532
|
-
],
|
|
533
|
-
},
|
|
534
|
-
],
|
|
545
|
+
]
|
|
546
|
+
+ payload,
|
|
535
547
|
},
|
|
536
548
|
{
|
|
537
549
|
"type": "paragraph",
|
|
@@ -1405,7 +1417,7 @@ class Jira:
|
|
|
1405
1417
|
labels=["bug_catcher", "fatal_error"],
|
|
1406
1418
|
priority=priority,
|
|
1407
1419
|
)
|
|
1408
|
-
if os.path.exists(stack_trace):
|
|
1420
|
+
if stack_trace and os.path.exists(stack_trace):
|
|
1409
1421
|
os.remove(stack_trace)
|
|
1410
1422
|
return response
|
|
1411
1423
|
except Exception as ex:
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import ast
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
from t_bug_catcher.utils.common import strip_path
|
|
5
|
-
|
|
6
|
-
validation_logger = logging.getLogger("t_bug_catcher")
|
|
7
|
-
|
|
8
|
-
validation_logger.setLevel(logging.DEBUG)
|
|
9
|
-
console_handler = logging.StreamHandler()
|
|
10
|
-
console_handler.setLevel(logging.DEBUG)
|
|
11
|
-
|
|
12
|
-
formatter = logging.Formatter("%(levelname)s - %(name)s - %(message)s")
|
|
13
|
-
validation_logger.addHandler(console_handler)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class IncorrectTryBlockVisitor(ast.NodeVisitor):
|
|
17
|
-
"""A visitor that checks for incorrect try-except blocks."""
|
|
18
|
-
|
|
19
|
-
def __init__(self):
|
|
20
|
-
"""Initializes the IncorrectTryBlockVisitor class."""
|
|
21
|
-
self.errors = {}
|
|
22
|
-
|
|
23
|
-
def visit_Try(self, node):
|
|
24
|
-
"""Visits the try-except block in the AST."""
|
|
25
|
-
message = (
|
|
26
|
-
"Error not handled: specify the error type or re-raise exception or "
|
|
27
|
-
"report it with `t_bug_catcher.report_error()`"
|
|
28
|
-
)
|
|
29
|
-
for handler in node.handlers:
|
|
30
|
-
if isinstance(handler.type, ast.Name) and handler.type.id == "Exception":
|
|
31
|
-
if not self._contains_raise(handler.body) and not self._contains_correct_error_handling(handler.body):
|
|
32
|
-
self.errors[handler.lineno] = message
|
|
33
|
-
elif handler.type is None: # 'except:' that catches everything
|
|
34
|
-
if not any(isinstance(x, ast.Raise) for x in handler.body):
|
|
35
|
-
self.errors[handler.lineno] = message
|
|
36
|
-
self.generic_visit(node)
|
|
37
|
-
|
|
38
|
-
@staticmethod
|
|
39
|
-
def _contains_correct_error_handling(statements):
|
|
40
|
-
for stmt in statements:
|
|
41
|
-
if isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Call):
|
|
42
|
-
func = stmt.value.func
|
|
43
|
-
if (isinstance(func, ast.Name) and func.id == "report_error") or (
|
|
44
|
-
isinstance(func, ast.Attribute) and func.attr == "report_error"
|
|
45
|
-
):
|
|
46
|
-
return True
|
|
47
|
-
return False
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def _contains_raise(statements):
|
|
51
|
-
return any(isinstance(stmt, ast.Raise) for stmt in statements)
|
|
52
|
-
|
|
53
|
-
def get_errors(self):
|
|
54
|
-
"""Returns the errors found in the try-except blocks."""
|
|
55
|
-
return self.errors
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ValidationWarning:
|
|
59
|
-
"""Base class to represent any type of validation warning."""
|
|
60
|
-
|
|
61
|
-
def __init__(self, file_path: str, lineno: int, message: str, source_code: str):
|
|
62
|
-
"""Initializes the ValidationWarning class."""
|
|
63
|
-
self.lineno = lineno
|
|
64
|
-
self.message = message
|
|
65
|
-
self.__source_code_lines = source_code.split("\n")
|
|
66
|
-
self.code_line = self.__source_code_lines[lineno - 1]
|
|
67
|
-
self.code_lines = "\n".join(self.__source_code_lines[lineno - 3 : lineno + 2])
|
|
68
|
-
self.file_path = strip_path(file_path)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class BroadExceptionWarning(ValidationWarning):
|
|
72
|
-
"""A class to represent a broad exception warning."""
|
|
73
|
-
|
|
74
|
-
def __init__(self, file_path: str, lineno: int, message: str, source_code: str):
|
|
75
|
-
"""Initializes the BroadExceptionWarning class."""
|
|
76
|
-
super().__init__(file_path, lineno, message, source_code)
|
|
77
|
-
self.warning_code = "TBC002"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class ConfigWarning(ValidationWarning):
|
|
81
|
-
"""A class to represent a config warning."""
|
|
82
|
-
|
|
83
|
-
def __init__(self, file_path: str, lineno: int, message: str, source_code: str):
|
|
84
|
-
"""Initializes the ConfigWarning class."""
|
|
85
|
-
super().__init__(file_path, lineno, message, source_code)
|
|
86
|
-
self.warning_code = "TBC001"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
class PreRunValidation:
|
|
90
|
-
"""A class to perform pre-run validation checks."""
|
|
91
|
-
|
|
92
|
-
def __init__(self):
|
|
93
|
-
"""Initializes the PreRunValidation class."""
|
|
94
|
-
self.warnings: list[ValidationWarning] = []
|
|
95
|
-
|
|
96
|
-
def check_broad_exceptions(self, filename):
|
|
97
|
-
"""Checks for broad exception warnings in the specified file."""
|
|
98
|
-
with open(filename, "r", encoding="utf8") as source:
|
|
99
|
-
source_code = source.read()
|
|
100
|
-
|
|
101
|
-
try:
|
|
102
|
-
tree = ast.parse(source_code, filename=filename)
|
|
103
|
-
except SyntaxError:
|
|
104
|
-
return
|
|
105
|
-
except Exception as ex:
|
|
106
|
-
validation_logger.error(f"Unable to validate: {filename} - {ex}")
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
visitor = IncorrectTryBlockVisitor()
|
|
110
|
-
visitor.visit(tree)
|
|
111
|
-
|
|
112
|
-
for line, message in visitor.get_errors().items():
|
|
113
|
-
be_warn = BroadExceptionWarning(filename, line, message, source_code=source_code)
|
|
114
|
-
self.warnings.append(be_warn)
|
|
115
|
-
|
|
116
|
-
def check_configuration_exceptions(self, filename):
|
|
117
|
-
"""Checks for configurations warnings in the specified file."""
|
|
118
|
-
pass
|
|
119
|
-
|
|
120
|
-
def validate_file(self, filename):
|
|
121
|
-
"""Checks for warnings in the specified file."""
|
|
122
|
-
self.check_broad_exceptions(filename)
|
|
123
|
-
self.check_configuration_exceptions(filename)
|
|
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
|
|
File without changes
|