t-bug-catcher 0.4.0__tar.gz → 0.4.2__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.0 → t_bug_catcher-0.4.2}/PKG-INFO +1 -1
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/setup.cfg +1 -1
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/setup.py +3 -1
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/__init__.py +1 -1
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/config.py +3 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/jira.py +5 -5
- t_bug_catcher-0.4.2/t_bug_catcher/resources/whispers_config.yml +44 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/stack_saver.py +13 -41
- t_bug_catcher-0.4.2/t_bug_catcher/utils/common.py +69 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/PKG-INFO +1 -1
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/SOURCES.txt +1 -0
- t_bug_catcher-0.4.0/t_bug_catcher/utils/common.py +0 -17
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/MANIFEST.in +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/README.rst +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/pyproject.toml +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/requirements.txt +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/bug_catcher.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/bug_snag.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/exceptions.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/utils/__init__.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/utils/logger.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher/workitems.py +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/dependency_links.txt +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/not-zip-safe +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/requires.txt +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/t_bug_catcher.egg-info/top_level.txt +0 -0
- {t_bug_catcher-0.4.0 → t_bug_catcher-0.4.2}/tests/test_t_bug_catcher.py +0 -0
|
@@ -26,7 +26,9 @@ 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.2",
|
|
30
30
|
zip_safe=False,
|
|
31
31
|
install_requires=install_requirements,
|
|
32
|
+
include_package_data=True,
|
|
33
|
+
package_data={"": ["resources/*.yml"]},
|
|
32
34
|
)
|
|
@@ -13,9 +13,12 @@ class Config:
|
|
|
13
13
|
MAX_ISSUE_ATTACHMENTS: int = 100
|
|
14
14
|
MAX_DESCRIPTION_LENGTH: int = 250
|
|
15
15
|
SUMMARY_LENGTH: int = 120
|
|
16
|
+
STACK_SCOPE: int = 3
|
|
16
17
|
|
|
17
18
|
SUPPORT_BOARD = "AB"
|
|
18
19
|
|
|
20
|
+
KEYS_TO_REMOVE = ["credential", "password"]
|
|
21
|
+
|
|
19
22
|
RC_RUN_LINK = (
|
|
20
23
|
f"https://cloud.robocorp.com/organizations/{os.environ.get('RC_ORGANIZATION_ID')}"
|
|
21
24
|
f"/workspaces/{os.environ.get('RC_WORKSPACE_ID')}/processes"
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""JiraPoster class for interacting with the Jira API."""
|
|
2
2
|
|
|
3
|
-
import datetime
|
|
4
3
|
import hashlib
|
|
5
4
|
import json
|
|
6
5
|
import os
|
|
7
6
|
import re
|
|
8
7
|
import sys
|
|
9
8
|
import traceback
|
|
9
|
+
from datetime import datetime
|
|
10
10
|
from importlib.metadata import version
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from types import TracebackType
|
|
@@ -19,7 +19,7 @@ from retry import retry
|
|
|
19
19
|
from .config import CONFIG
|
|
20
20
|
from .exceptions import BadRequestError
|
|
21
21
|
from .utils import logger
|
|
22
|
-
from .utils.common import get_frames
|
|
22
|
+
from .utils.common import Encoder, get_frames
|
|
23
23
|
from .workitems import variables
|
|
24
24
|
|
|
25
25
|
|
|
@@ -389,7 +389,7 @@ class Jira:
|
|
|
389
389
|
},
|
|
390
390
|
{
|
|
391
391
|
"type": "text",
|
|
392
|
-
"text": str(datetime.
|
|
392
|
+
"text": str(datetime.now().strftime("%B %d, %Y %I:%M:%S %p")),
|
|
393
393
|
},
|
|
394
394
|
],
|
|
395
395
|
}
|
|
@@ -521,7 +521,7 @@ class Jira:
|
|
|
521
521
|
"content": [
|
|
522
522
|
{
|
|
523
523
|
"type": "text",
|
|
524
|
-
"text": json.dumps(metadata, indent=4),
|
|
524
|
+
"text": json.dumps(metadata, indent=4, cls=Encoder),
|
|
525
525
|
}
|
|
526
526
|
],
|
|
527
527
|
},
|
|
@@ -735,7 +735,7 @@ class Jira:
|
|
|
735
735
|
date_markup = [
|
|
736
736
|
{
|
|
737
737
|
"type": "text",
|
|
738
|
-
"text": f" at {str(datetime.
|
|
738
|
+
"text": f" at {str(datetime.now().strftime('%B %d, %Y %I:%M:%S %p'))}",
|
|
739
739
|
},
|
|
740
740
|
{"type": "hardBreak"},
|
|
741
741
|
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
include:
|
|
2
|
+
files:
|
|
3
|
+
- "**/*"
|
|
4
|
+
rules:
|
|
5
|
+
- password
|
|
6
|
+
- uri
|
|
7
|
+
- secret
|
|
8
|
+
- webhook
|
|
9
|
+
- apikey
|
|
10
|
+
- apikey-known
|
|
11
|
+
- apikey-maybe
|
|
12
|
+
- id: username
|
|
13
|
+
group: misc
|
|
14
|
+
description: Variable names refering to user
|
|
15
|
+
message: Secret
|
|
16
|
+
severity: Low
|
|
17
|
+
key:
|
|
18
|
+
regex: ^\S*(user(name?)|login|id|uid|otp)_?(hash)?[0-9]*$
|
|
19
|
+
ignorecase: True
|
|
20
|
+
value:
|
|
21
|
+
minlen: 1
|
|
22
|
+
severity:
|
|
23
|
+
- Critical
|
|
24
|
+
- High
|
|
25
|
+
- Medium
|
|
26
|
+
|
|
27
|
+
exclude:
|
|
28
|
+
files:
|
|
29
|
+
- __pycache__|\.eggs|build|dev|\.vscode|\.git
|
|
30
|
+
- .*/(locale|spec|test|mock)s?/
|
|
31
|
+
- integration|node_modules
|
|
32
|
+
- (package(-lock)?|npm-shrinkwrap)\.json
|
|
33
|
+
|
|
34
|
+
keys:
|
|
35
|
+
- .*(public|project).*
|
|
36
|
+
|
|
37
|
+
values:
|
|
38
|
+
- ^(true|false|yes|no|1|0)$
|
|
39
|
+
- .*_(user|password|token|key|placeholder|name)$
|
|
40
|
+
- ^aws_(access_key_id|secret_access_key|session_token)$
|
|
41
|
+
- ^((cn?trl|alt|shift|del|ins|esc|tab|f[\d]+) ?[\+_\-\\/] ?)+[\w]+$
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
@@ -4,35 +4,15 @@ import os
|
|
|
4
4
|
import re
|
|
5
5
|
import sys
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from json import JSONEncoder
|
|
8
7
|
from pathlib import Path
|
|
9
8
|
from types import FunctionType, ModuleType
|
|
10
9
|
from typing import Optional
|
|
11
10
|
|
|
12
11
|
import whispers
|
|
13
12
|
|
|
13
|
+
from .config import CONFIG
|
|
14
14
|
from .utils import logger
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class _Encoder(JSONEncoder):
|
|
18
|
-
"""Encoder class for encoding the Episode object to json."""
|
|
19
|
-
|
|
20
|
-
def default(self, o):
|
|
21
|
-
"""This method is used to encode the Episode object to json.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
o (object): The object to be encoded.
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
str: The json string.
|
|
28
|
-
"""
|
|
29
|
-
if hasattr(o, "__dict__"):
|
|
30
|
-
return o.__dict__
|
|
31
|
-
if isinstance(o, datetime):
|
|
32
|
-
return o.isoformat()
|
|
33
|
-
if isinstance(o, Path):
|
|
34
|
-
return str(o)
|
|
35
|
-
return JSONEncoder.default(self, o)
|
|
15
|
+
from .utils.common import Encoder, convert_keys_to_primitives
|
|
36
16
|
|
|
37
17
|
|
|
38
18
|
class StackSaver:
|
|
@@ -54,8 +34,7 @@ class StackSaver:
|
|
|
54
34
|
"""
|
|
55
35
|
return path.replace(os.getcwd(), "").strip(os.sep)
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
def serialize_frame_info(frame_info: dict) -> dict:
|
|
37
|
+
def serialize_frame_info(self, frame_info: dict) -> dict:
|
|
59
38
|
"""A static method to serialize the frame info.
|
|
60
39
|
|
|
61
40
|
Args:
|
|
@@ -64,19 +43,8 @@ class StackSaver:
|
|
|
64
43
|
Returns:
|
|
65
44
|
dict: The serialized frame info.
|
|
66
45
|
"""
|
|
67
|
-
run_locals = {}
|
|
68
|
-
run_args = {}
|
|
69
|
-
if frame_info["locals"]:
|
|
70
|
-
for key, value in frame_info["locals"].items():
|
|
71
|
-
if isinstance(value, dict):
|
|
72
|
-
run_locals[str(key)] = value
|
|
73
|
-
else:
|
|
74
|
-
run_locals[str(key)] = str(value)
|
|
75
|
-
for key, value in frame_info["args"].items():
|
|
76
|
-
if isinstance(value, dict):
|
|
77
|
-
run_args[str(key)] = value
|
|
78
|
-
else:
|
|
79
|
-
run_args[str(key)] = str(value)
|
|
46
|
+
run_locals = convert_keys_to_primitives(frame_info["locals"]) if frame_info["locals"] else {}
|
|
47
|
+
run_args = convert_keys_to_primitives(frame_info["args"]) if frame_info["args"] else {}
|
|
80
48
|
serializable_frame_info = {
|
|
81
49
|
"filename": frame_info["filename"],
|
|
82
50
|
"function_name": frame_info["function_name"],
|
|
@@ -104,7 +72,7 @@ class StackSaver:
|
|
|
104
72
|
continue
|
|
105
73
|
if isinstance(var, (ModuleType, FunctionType)):
|
|
106
74
|
continue
|
|
107
|
-
local_variables[var_name] = var
|
|
75
|
+
local_variables[str(var_name)] = var
|
|
108
76
|
return local_variables
|
|
109
77
|
|
|
110
78
|
def mask_credentials(self, file_path: str) -> None:
|
|
@@ -122,7 +90,11 @@ class StackSaver:
|
|
|
122
90
|
with open(file_path, "r") as f:
|
|
123
91
|
filedata = f.readlines()
|
|
124
92
|
|
|
125
|
-
|
|
93
|
+
config_path = Path(__file__).parent.resolve().as_posix() + "/resources/whispers_config.yml"
|
|
94
|
+
|
|
95
|
+
args = f"-c {config_path} {file_path}"
|
|
96
|
+
|
|
97
|
+
secrets = [secret for secret in whispers.secrets(args)]
|
|
126
98
|
|
|
127
99
|
for index, line in enumerate(filedata):
|
|
128
100
|
if not secrets:
|
|
@@ -162,7 +134,7 @@ class StackSaver:
|
|
|
162
134
|
continue
|
|
163
135
|
frames.append(frame)
|
|
164
136
|
tb = tb.tb_next
|
|
165
|
-
frames = frames[:
|
|
137
|
+
frames = frames[-CONFIG.LIMITS.STACK_SCOPE :]
|
|
166
138
|
|
|
167
139
|
for frame in frames:
|
|
168
140
|
frame_info = {
|
|
@@ -176,7 +148,7 @@ class StackSaver:
|
|
|
176
148
|
file_path = f"stack_details_{datetime.now().strftime('%Y%m%d%H%M%S')}.json"
|
|
177
149
|
|
|
178
150
|
with open(file_path, "w") as f:
|
|
179
|
-
json.dump(stack_details_json, f, indent=4, cls=
|
|
151
|
+
json.dump(stack_details_json, f, indent=4, cls=Encoder)
|
|
180
152
|
|
|
181
153
|
self.mask_credentials(file_path)
|
|
182
154
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from json import JSONEncoder
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import TracebackType
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from ..config import CONFIG
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Encoder(JSONEncoder):
|
|
12
|
+
"""This class is used to encode the Episode object to json."""
|
|
13
|
+
|
|
14
|
+
def default(self, o):
|
|
15
|
+
"""This method is used to encode the Episode object to json.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
o (object): The object to be encoded.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
str: The json string.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if hasattr(o, "__dict__"):
|
|
25
|
+
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)
|
|
27
|
+
]
|
|
28
|
+
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)
|
|
36
|
+
except TypeError:
|
|
37
|
+
return str(o)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_frames(exc_traceback: TracebackType) -> List:
|
|
41
|
+
"""Get the frames of the exception.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
exc_traceback (TracebackType): The traceback of the exception.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
List: The frames of the exception.
|
|
48
|
+
"""
|
|
49
|
+
return [
|
|
50
|
+
frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def convert_keys_to_primitives(data: dict) -> dict:
|
|
55
|
+
"""A function that recursively converts keys in a nested dictionary to primitives.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
data (dict): The input dictionary to convert keys.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
dict: A new dictionary with keys converted to strings.
|
|
62
|
+
"""
|
|
63
|
+
new_dict = {}
|
|
64
|
+
for key, value in data.items():
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
new_dict[str(key)] = convert_keys_to_primitives(value)
|
|
67
|
+
else:
|
|
68
|
+
new_dict[str(key)] = value
|
|
69
|
+
return new_dict
|
|
@@ -18,6 +18,7 @@ t_bug_catcher.egg-info/dependency_links.txt
|
|
|
18
18
|
t_bug_catcher.egg-info/not-zip-safe
|
|
19
19
|
t_bug_catcher.egg-info/requires.txt
|
|
20
20
|
t_bug_catcher.egg-info/top_level.txt
|
|
21
|
+
t_bug_catcher/resources/whispers_config.yml
|
|
21
22
|
t_bug_catcher/utils/__init__.py
|
|
22
23
|
t_bug_catcher/utils/common.py
|
|
23
24
|
t_bug_catcher/utils/logger.py
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import traceback
|
|
2
|
-
from types import TracebackType
|
|
3
|
-
from typing import List
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def get_frames(exc_traceback: TracebackType) -> List:
|
|
7
|
-
"""Get the frames of the exception.
|
|
8
|
-
|
|
9
|
-
Args:
|
|
10
|
-
exc_traceback (TracebackType): The traceback of the exception.
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
List: The frames of the exception.
|
|
14
|
-
"""
|
|
15
|
-
return [
|
|
16
|
-
frame for frame in traceback.extract_tb(exc_traceback) if "site-packages" not in str(frame.filename).lower()
|
|
17
|
-
]
|
|
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
|