t-bug-catcher 0.5.4__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.4 → t_bug_catcher-0.5.6}/PKG-INFO +1 -1
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/setup.cfg +1 -1
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/setup.py +1 -1
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/__init__.py +3 -1
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/bug_catcher.py +10 -7
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/bug_snag.py +0 -2
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/jira.py +29 -17
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/workitems.py +0 -3
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/SOURCES.txt +0 -1
- t_bug_catcher-0.5.4/t_bug_catcher/validation.py +0 -123
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/MANIFEST.in +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/README.rst +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/pyproject.toml +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/requirements.txt +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/config.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/resources/whispers_config.yml +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/stack_saver.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/common.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.5.4 → t_bug_catcher-0.5.6}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.5.4 → 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
|
]
|
|
@@ -12,7 +12,6 @@ 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
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
class Configurator:
|
|
@@ -93,12 +92,17 @@ class BugCatcher:
|
|
|
93
92
|
self.__configurator: Configurator = Configurator(self.__jira, self.__bug_snag)
|
|
94
93
|
self.__sys_excepthook = None
|
|
95
94
|
self.__stack_saver = StackSaver()
|
|
95
|
+
self.__errors_count = 0
|
|
96
96
|
|
|
97
97
|
@property
|
|
98
98
|
def configure(self):
|
|
99
99
|
"""Configures the JiraPoster and BugSnag classes."""
|
|
100
100
|
return self.__configurator
|
|
101
101
|
|
|
102
|
+
def get_errors_count(self):
|
|
103
|
+
"""Returns the number of exceptions reported."""
|
|
104
|
+
return self.__errors_count
|
|
105
|
+
|
|
102
106
|
def report_error(
|
|
103
107
|
self,
|
|
104
108
|
exception: Optional[Exception] = None,
|
|
@@ -187,6 +191,8 @@ class BugCatcher:
|
|
|
187
191
|
exc_info = f"{os.path.basename(frames[-1].filename)}:{frames[-1].name}:{frames[-1].lineno}"
|
|
188
192
|
exception.handled_error = exc_info
|
|
189
193
|
|
|
194
|
+
self.__errors_count += 1
|
|
195
|
+
|
|
190
196
|
def report_error_to_jira(
|
|
191
197
|
self,
|
|
192
198
|
exception: Optional[Exception] = None,
|
|
@@ -287,6 +293,8 @@ class BugCatcher:
|
|
|
287
293
|
if self.__configurator.is_bugsnag_configured:
|
|
288
294
|
self.__bug_snag.report_unhandled_error(exc_type, exc_value, exc_traceback)
|
|
289
295
|
|
|
296
|
+
self.__errors_count += 1
|
|
297
|
+
|
|
290
298
|
def __get_sys_hook_attribute(self, attribute: str = "bug_catcher_client"):
|
|
291
299
|
"""Checks if the system hook is installed.
|
|
292
300
|
|
|
@@ -333,9 +341,4 @@ report_error_to_jira = __bug_catcher.report_error_to_jira
|
|
|
333
341
|
report_error_to_bugsnag = __bug_catcher.report_error_to_bugsnag
|
|
334
342
|
install_sys_hook = __bug_catcher.install_sys_hook
|
|
335
343
|
uninstall_sys_hook = __bug_catcher.uninstall_sys_hook
|
|
336
|
-
|
|
337
|
-
if not CONFIG.STAGE:
|
|
338
|
-
logger.warning("Implementation Stage is not configured. Please configure it before running.")
|
|
339
|
-
else:
|
|
340
|
-
if CONFIG.STAGE.lower() == "delivery":
|
|
341
|
-
pre_run_validation = PRE_RUN_VALIDATION
|
|
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,5 +1,3 @@
|
|
|
1
|
-
from .utils import logger
|
|
2
|
-
|
|
3
1
|
try:
|
|
4
2
|
from RPA.Robocorp.WorkItems import WorkItems
|
|
5
3
|
|
|
@@ -9,6 +7,5 @@ try:
|
|
|
9
7
|
variables = work_item.get("variables", dict())
|
|
10
8
|
metadata = work_item.get("metadata", dict())
|
|
11
9
|
except (ImportError, KeyError):
|
|
12
|
-
logger.warning("Workitems unavailable. Variables will be empty.")
|
|
13
10
|
variables = {}
|
|
14
11
|
metadata = {}
|
|
@@ -1,123 +0,0 @@
|
|
|
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
|
-
@property
|
|
118
|
-
def errors_count(self):
|
|
119
|
-
"""Property to define the number of errors. Used for pre-commit hook."""
|
|
120
|
-
return len(self.broad_exception_warnings)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
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
|