From 416f834c8aa679f5a4a43b4a81ed131032f35b88 Mon Sep 17 00:00:00 2001 From: kdysput Date: Tue, 10 Sep 2024 12:03:02 +0000 Subject: [PATCH 1/3] Move code from init to client/report/utils --- backtracepython/__init__.py | 325 +----------------- .../attributes/attribute_manager.py | 3 + backtracepython/client.py | 152 ++++++++ backtracepython/report.py | 162 +++++++++ backtracepython/utils.py | 13 + tests/test_report_attributes.py | 2 +- tests/test_submission_url.py | 2 +- 7 files changed, 335 insertions(+), 324 deletions(-) create mode 100644 backtracepython/client.py create mode 100644 backtracepython/report.py create mode 100644 backtracepython/utils.py diff --git a/backtracepython/__init__.py b/backtracepython/__init__.py index 869697b..6c5c440 100644 --- a/backtracepython/__init__.py +++ b/backtracepython/__init__.py @@ -1,333 +1,14 @@ -import os -import platform -import subprocess -import sys -import threading -import time -import uuid - -import simplejson as json - -from backtracepython.attributes.attribute_manager import AttributeManager - from .version import version, version_string - -if sys.version_info.major >= 3: - from urllib.parse import urlencode -else: - from urllib import urlencode +from .report import BacktraceReport +from .client import initialize, finalize, finalize,send_last_exception, send_report __all__ = [ "BacktraceReport", "initialize", "finalize", - "terminate", + "finalize", "version", "version_string", "send_last_exception", "send_report", ] - -attribute_manager = AttributeManager() - - -class globs: - endpoint = None - next_except_hook = None - debug_backtrace = False - timeout = None - tab_width = None - attributes = {} - context_line_count = None - worker = None - next_source_code_id = 0 - - -child_py_path = os.path.join(os.path.dirname(__file__), "child.py") - - -def get_python_version(): - return "{} {}.{}.{}-{}".format( - platform.python_implementation(), - sys.version_info.major, - sys.version_info.minor, - sys.version_info.micro, - sys.version_info.releaselevel, - ) - - -def send_worker_msg(msg): - payload = json.dumps(msg, ignore_nan=True).encode("utf-8") - globs.worker.stdin.write(payload) - globs.worker.stdin.write("\n".encode("utf-8")) - globs.worker.stdin.flush() - - -def walk_tb_backwards(tb): - while tb is not None: - yield tb.tb_frame, tb.tb_lineno - tb = tb.tb_next - - -def walk_tb(tb): - return reversed(list(walk_tb_backwards(tb))) - - -def make_unique_source_code_id(): - result = str(globs.next_source_code_id) - globs.next_source_code_id += 1 - return result - - -def add_source_code(source_path, source_code_dict, source_path_dict, line): - try: - the_id = source_path_dict[source_path] - except KeyError: - the_id = make_unique_source_code_id() - source_path_dict[source_path] = the_id - source_code_dict[the_id] = { - "minLine": line, - "maxLine": line, - "path": source_path, - } - return the_id - - if line < source_code_dict[the_id]["minLine"]: - source_code_dict[the_id]["minLine"] = line - if line > source_code_dict[the_id]["maxLine"]: - source_code_dict[the_id]["maxLine"] = line - return the_id - - -def process_frame(tb_frame, line, source_code_dict, source_path_dict): - source_file = os.path.abspath(tb_frame.f_code.co_filename) - frame = { - "funcName": tb_frame.f_code.co_name, - "line": line, - "sourceCode": add_source_code( - source_file, source_code_dict, source_path_dict, line - ), - } - return frame - - -def get_main_thread(): - if sys.version_info.major >= 3: - return threading.main_thread() - first = None - for thread in threading.enumerate(): - if thread.name == "MainThread": - return thread - if first is None: - first = thread - return first - - -class BacktraceReport: - def __init__(self): - self.fault_thread = threading.current_thread() - self.source_code = {} - self.source_path_dict = {} - entry_source_code_id = None - import __main__ - - cwd_path = os.path.abspath(os.getcwd()) - entry_thread = get_main_thread() - if hasattr(__main__, "__file__"): - entry_source_code_id = ( - add_source_code( - __main__.__file__, self.source_code, self.source_path_dict, 1 - ) - if hasattr(__main__, "__file__") - else None - ) - - init_attrs = {"error.type": "Exception"} - init_attrs.update(attribute_manager.get()) - - self.log_lines = [] - - self.report = { - "uuid": str(uuid.uuid4()), - "timestamp": int(time.time()), - "lang": "python", - "langVersion": get_python_version(), - "agent": "backtrace-python", - "agentVersion": version_string, - "mainThread": str(self.fault_thread.ident), - "entryThread": str(entry_thread.ident), - "cwd": cwd_path, - "attributes": init_attrs, - "annotations": { - "Environment Variables": dict(os.environ), - }, - } - if entry_source_code_id is not None: - self.report["entrySourceCode"] = entry_source_code_id - - def set_exception(self, garbage, ex_value, ex_traceback): - self.report["classifiers"] = [ex_value.__class__.__name__] - self.report["attributes"]["error.message"] = str(ex_value) - - threads = {} - for thread in threading.enumerate(): - if thread.ident == self.fault_thread.ident: - threads[str(self.fault_thread.ident)] = { - "name": self.fault_thread.name, - "stack": [ - process_frame( - frame, line, self.source_code, self.source_path_dict - ) - for frame, line in walk_tb(ex_traceback) - ], - } - else: - threads[str(thread.ident)] = { - "name": thread.name, - } - - self.report["threads"] = threads - - def capture_last_exception(self): - self.set_exception(*sys.exc_info()) - - def set_attribute(self, key, value): - self.report["attributes"][key] = value - - def set_dict_attributes(self, target_dict): - self.report["attributes"].update(target_dict) - - def set_annotation(self, key, value): - self.report["annotations"][key] = value - - def get_attributes(self): - return self.report["attributes"] - - def set_dict_annotations(self, target_dict): - self.report["annotations"].update(target_dict) - - def log(self, line): - self.log_lines.append( - { - "ts": time.time(), - "msg": line, - } - ) - - def send(self): - if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: - self.report["annotations"]["Log"] = self.log_lines - send_worker_msg( - { - "id": "send", - "report": self.report, - "context_line_count": globs.context_line_count, - "timeout": globs.timeout, - "endpoint": globs.endpoint, - "tab_width": globs.tab_width, - "debug_backtrace": globs.debug_backtrace, - "source_code": self.source_code, - } - ) - - -def create_and_send_report(ex_type, ex_value, ex_traceback): - report = BacktraceReport() - report.set_exception(ex_type, ex_value, ex_traceback) - report.set_attribute("error.type", "Unhandled exception") - report.send() - - -def bt_except_hook(ex_type, ex_value, ex_traceback): - if globs.debug_backtrace: - # Go back to normal exceptions while we do our work here. - sys.excepthook = globs.next_except_hook - - # Now if this fails we'll get a normal exception. - create_and_send_report(ex_type, ex_value, ex_traceback) - - # Put our exception handler back in place, and then also - # pass the exception down the chain. - sys.excepthook = bt_except_hook - else: - # Failure here is silent. - try: - create_and_send_report(ex_type, ex_value, ex_traceback) - except: - pass - - # Send the exception on to the next thing in the chain. - globs.next_except_hook(ex_type, ex_value, ex_traceback) - - -def initialize(**kwargs): - globs.endpoint = construct_submission_url( - kwargs["endpoint"], kwargs.get("token", None) - ) - globs.debug_backtrace = kwargs.get("debug_backtrace", False) - globs.timeout = kwargs.get("timeout", 4) - globs.tab_width = kwargs.get("tab_width", 8) - globs.context_line_count = kwargs.get("context_line_count", 200) - - attribute_manager.add(kwargs.get("attributes", {})) - stdio_value = None if globs.debug_backtrace else subprocess.PIPE - globs.worker = subprocess.Popen( - [sys.executable, child_py_path], - stdin=subprocess.PIPE, - stdout=stdio_value, - stderr=stdio_value, - ) - - disable_global_handler = kwargs.get("disable_global_handler", False) - if not disable_global_handler: - globs.next_except_hook = sys.excepthook - sys.excepthook = bt_except_hook - - -def construct_submission_url(endpoint, token): - if "submit.backtrace.io" in endpoint or token is None: - return endpoint - - return "{}/post?{}".format( - endpoint, - urlencode( - { - "token": token, - "format": "json", - } - ), - ) - - -def finalize(): - send_worker_msg({"id": "terminate"}) - if not globs.debug_backtrace: - globs.worker.stdout.close() - globs.worker.stderr.close() - globs.worker.wait() - - -def send_last_exception(**kwargs): - report = BacktraceReport() - report.capture_last_exception() - report.set_dict_attributes(kwargs.get("attributes", {})) - report.set_dict_annotations(kwargs.get("annotations", {})) - report.set_attribute("error.type", "Exception") - report.send() - - -def make_an_exception(): - try: - raise Exception - except: - return sys.exc_info() - - -def send_report(msg, **kwargs): - report = BacktraceReport() - report.set_exception(*make_an_exception()) - report.set_dict_attributes(kwargs.get("attributes", {})) - report.set_dict_annotations(kwargs.get("annotations", {})) - report.set_attribute("error.message", msg) - report.set_attribute("error.type", "Message") - report.send() diff --git a/backtracepython/attributes/attribute_manager.py b/backtracepython/attributes/attribute_manager.py index 3f46308..e5c361e 100644 --- a/backtracepython/attributes/attribute_manager.py +++ b/backtracepython/attributes/attribute_manager.py @@ -63,3 +63,6 @@ def get_predefined_dynamic_attribute_providers(self): result.append(LinuxMemoryAttributeProvider()) return result + + +attribute_manager = AttributeManager() diff --git a/backtracepython/client.py b/backtracepython/client.py new file mode 100644 index 0000000..8c9fbe7 --- /dev/null +++ b/backtracepython/client.py @@ -0,0 +1,152 @@ +import os +import sys +import simplejson as json +import subprocess + +from backtracepython.attributes.attribute_manager import attribute_manager +from backtracepython.report import BacktraceReport + +if sys.version_info.major >= 3: + from urllib.parse import urlencode +else: + from urllib import urlencode + + +class globs: + endpoint = None + next_except_hook = None + debug_backtrace = False + timeout = None + tab_width = None + attributes = {} + context_line_count = None + worker = None + + +child_py_path = os.path.join(os.path.dirname(__file__), "child.py") + +def get_attributes(): + return attribute_manager.get() + +def send_worker_report(report, source_code): + send_worker_msg({ + "id": "send", + "report": report, + "context_line_count": globs.context_line_count, + "timeout": globs.timeout, + "endpoint": globs.endpoint, + "tab_width": globs.tab_width, + "debug_backtrace": globs.debug_backtrace, + "source_code": source_code, + }) + + +def send_worker_msg(msg): + payload = json.dumps(msg, ignore_nan=True).encode("utf-8") + globs.worker.stdin.write(payload) + globs.worker.stdin.write("\n".encode("utf-8")) + globs.worker.stdin.flush() + + +def create_and_send_report(ex_type, ex_value, ex_traceback): + report = BacktraceReport() + report.set_exception(ex_type, ex_value, ex_traceback) + report.set_attribute("error.type", "Unhandled exception") + report.send() + + + + +def bt_except_hook(ex_type, ex_value, ex_traceback): + if globs.debug_backtrace: + # Go back to normal exceptions while we do our work here. + sys.excepthook = globs.next_except_hook + + # Now if this fails we'll get a normal exception. + create_and_send_report(ex_type, ex_value, ex_traceback) + + # Put our exception handler back in place, and then also + # pass the exception down the chain. + sys.excepthook = bt_except_hook + else: + # Failure here is silent. + try: + create_and_send_report(ex_type, ex_value, ex_traceback) + except: + pass + + # Send the exception on to the next thing in the chain. + globs.next_except_hook(ex_type, ex_value, ex_traceback) + + +def initialize(**kwargs): + globs.endpoint = construct_submission_url( + kwargs["endpoint"], kwargs.get("token", None) + ) + globs.debug_backtrace = kwargs.get("debug_backtrace", False) + globs.timeout = kwargs.get("timeout", 4) + globs.tab_width = kwargs.get("tab_width", 8) + globs.context_line_count = kwargs.get("context_line_count", 200) + + attribute_manager.add(kwargs.get("attributes", {})) + stdio_value = None if globs.debug_backtrace else subprocess.PIPE + globs.worker = subprocess.Popen( + [sys.executable, child_py_path], + stdin=subprocess.PIPE, + stdout=stdio_value, + stderr=stdio_value, + ) + + disable_global_handler = kwargs.get("disable_global_handler", False) + if not disable_global_handler: + globs.next_except_hook = sys.excepthook + sys.excepthook = bt_except_hook + + +def construct_submission_url(endpoint, token): + if "submit.backtrace.io" in endpoint or token is None: + return endpoint + + return "{}/post?{}".format( + endpoint, + urlencode( + { + "token": token, + "format": "json", + } + ), + ) + + +def finalize(): + send_worker_msg({"id": "terminate"}) + if not globs.debug_backtrace: + globs.worker.stdout.close() + globs.worker.stderr.close() + globs.worker.wait() + + +def send_last_exception(**kwargs): + report = BacktraceReport() + report.capture_last_exception() + report.set_dict_attributes(kwargs.get("attributes", {})) + report.set_dict_annotations(kwargs.get("annotations", {})) + report.set_attribute("error.type", "Exception") + report.send() + + +def make_an_exception(): + try: + raise Exception + except: + return sys.exc_info() + + +def send_report(msg, **kwargs): + report = BacktraceReport() + report.set_exception(*make_an_exception()) + report.set_dict_attributes(kwargs.get("attributes", {})) + report.set_dict_annotations(kwargs.get("annotations", {})) + report.set_attribute("error.message", msg) + report.set_attribute("error.type", "Message") + report.send() diff --git a/backtracepython/report.py b/backtracepython/report.py new file mode 100644 index 0000000..275b8b4 --- /dev/null +++ b/backtracepython/report.py @@ -0,0 +1,162 @@ +import threading +import os +import sys +import threading +import time +import uuid + +from backtracepython.attributes.attribute_manager import attribute_manager +from backtracepython.utils import python_version +from backtracepython.version import version_string + +next_source_code_id = 1 +print(next_source_code_id) + +def add_source_code(source_path, source_code_dict, source_path_dict, line): + try: + the_id = source_path_dict[source_path] + except KeyError: + the_id = str(uuid.uuid4()) + source_path_dict[source_path] = the_id + source_code_dict[the_id] = { + "minLine": line, + "maxLine": line, + "path": source_path, + } + return the_id + + if line < source_code_dict[the_id]["minLine"]: + source_code_dict[the_id]["minLine"] = line + if line > source_code_dict[the_id]["maxLine"]: + source_code_dict[the_id]["maxLine"] = line + return the_id + +def process_frame(tb_frame, line, source_code_dict, source_path_dict): + source_file = os.path.abspath(tb_frame.f_code.co_filename) + frame = { + "funcName": tb_frame.f_code.co_name, + "line": line, + "sourceCode": add_source_code( + source_file, source_code_dict, source_path_dict, line + ), + } + return frame + + + +def get_main_thread(): + if sys.version_info.major >= 3: + return threading.main_thread() + first = None + for thread in threading.enumerate(): + if thread.name == "MainThread": + return thread + if first is None: + first = thread + return first + +def walk_tb_backwards(tb): + while tb is not None: + yield tb.tb_frame, tb.tb_lineno + tb = tb.tb_next + + +def walk_tb(tb): + return reversed(list(walk_tb_backwards(tb))) + +class BacktraceReport: + def __init__(self): + self.fault_thread = threading.current_thread() + self.source_code = {} + self.source_path_dict = {} + entry_source_code_id = None + import __main__ + + cwd_path = os.path.abspath(os.getcwd()) + entry_thread = get_main_thread() + if hasattr(__main__, "__file__"): + entry_source_code_id = ( + add_source_code( + __main__.__file__, self.source_code, self.source_path_dict, 1 + ) + if hasattr(__main__, "__file__") + else None + ) + + init_attrs = {"error.type": "Exception"} + init_attrs.update(attribute_manager.get()) + + self.log_lines = [] + + self.report = { + "uuid": str(uuid.uuid4()), + "timestamp": int(time.time()), + "lang": "python", + "langVersion": python_version, + "agent": "backtrace-python", + "agentVersion": version_string, + "mainThread": str(self.fault_thread.ident), + "entryThread": str(entry_thread.ident), + "cwd": cwd_path, + "attributes": init_attrs, + "annotations": { + "Environment Variables": dict(os.environ), + }, + } + if entry_source_code_id is not None: + self.report["entrySourceCode"] = entry_source_code_id + + def set_exception(self, garbage, ex_value, ex_traceback): + self.report["classifiers"] = [ex_value.__class__.__name__] + self.report["attributes"]["error.message"] = str(ex_value) + + threads = {} + for thread in threading.enumerate(): + if thread.ident == self.fault_thread.ident: + threads[str(self.fault_thread.ident)] = { + "name": self.fault_thread.name, + "stack": [ + process_frame( + frame, line, self.source_code, self.source_path_dict + ) + for frame, line in walk_tb(ex_traceback) + ], + } + else: + threads[str(thread.ident)] = { + "name": thread.name, + } + + self.report["threads"] = threads + + def capture_last_exception(self): + self.set_exception(*sys.exc_info()) + + def set_attribute(self, key, value): + self.report["attributes"][key] = value + + def set_dict_attributes(self, target_dict): + self.report["attributes"].update(target_dict) + + def set_annotation(self, key, value): + self.report["annotations"][key] = value + + def get_attributes(self): + return self.report["attributes"] + + def set_dict_annotations(self, target_dict): + self.report["annotations"].update(target_dict) + + def log(self, line): + self.log_lines.append( + { + "ts": time.time(), + "msg": line, + } + ) + + def send(self): + if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: + self.report["annotations"]["Log"] = self.log_lines + from backtracepython.client import send_worker_report + send_worker_report(self.report, self.source_code) diff --git a/backtracepython/utils.py b/backtracepython/utils.py new file mode 100644 index 0000000..de8be83 --- /dev/null +++ b/backtracepython/utils.py @@ -0,0 +1,13 @@ +import platform +import sys + +python_version = "{} {}.{}.{}-{}".format( + platform.python_implementation(), + sys.version_info.major, + sys.version_info.minor, + sys.version_info.micro, + sys.version_info.releaselevel, + ) + +def get_python_version(): + return python_version diff --git a/tests/test_report_attributes.py b/tests/test_report_attributes.py index fdae45a..339e73f 100644 --- a/tests/test_report_attributes.py +++ b/tests/test_report_attributes.py @@ -1,4 +1,4 @@ -from backtracepython import BacktraceReport +from backtracepython.report import BacktraceReport report = BacktraceReport() diff --git a/tests/test_submission_url.py b/tests/test_submission_url.py index 8a73350..fae693c 100644 --- a/tests/test_submission_url.py +++ b/tests/test_submission_url.py @@ -1,4 +1,4 @@ -from backtracepython import construct_submission_url +from backtracepython.client import construct_submission_url universe = "test" hostname = "https://{}.sp.backtrace.io".format(universe) From 8fabb626a4cf48ac0cc77a0643d76f2d74ab3665 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Tue, 10 Sep 2024 14:05:07 +0200 Subject: [PATCH 2/3] Format --- backtracepython/__init__.py | 2 +- backtracepython/client.py | 28 +++++++++++++++------------- backtracepython/report.py | 8 ++++---- backtracepython/utils.py | 13 +++++++------ 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/backtracepython/__init__.py b/backtracepython/__init__.py index 6c5c440..8d006c4 100644 --- a/backtracepython/__init__.py +++ b/backtracepython/__init__.py @@ -1,6 +1,6 @@ from .version import version, version_string from .report import BacktraceReport -from .client import initialize, finalize, finalize,send_last_exception, send_report +from .client import initialize, finalize, finalize, send_last_exception, send_report __all__ = [ "BacktraceReport", diff --git a/backtracepython/client.py b/backtracepython/client.py index 8c9fbe7..bf2b5bb 100644 --- a/backtracepython/client.py +++ b/backtracepython/client.py @@ -25,21 +25,25 @@ class globs: child_py_path = os.path.join(os.path.dirname(__file__), "child.py") + def get_attributes(): return attribute_manager.get() + def send_worker_report(report, source_code): - send_worker_msg({ - "id": "send", - "report": report, - "context_line_count": globs.context_line_count, - "timeout": globs.timeout, - "endpoint": globs.endpoint, - "tab_width": globs.tab_width, - "debug_backtrace": globs.debug_backtrace, - "source_code": source_code, - }) - + send_worker_msg( + { + "id": "send", + "report": report, + "context_line_count": globs.context_line_count, + "timeout": globs.timeout, + "endpoint": globs.endpoint, + "tab_width": globs.tab_width, + "debug_backtrace": globs.debug_backtrace, + "source_code": source_code, + } + ) + def send_worker_msg(msg): payload = json.dumps(msg, ignore_nan=True).encode("utf-8") @@ -55,8 +59,6 @@ def create_and_send_report(ex_type, ex_value, ex_traceback): report.send() - - def bt_except_hook(ex_type, ex_value, ex_traceback): if globs.debug_backtrace: # Go back to normal exceptions while we do our work here. diff --git a/backtracepython/report.py b/backtracepython/report.py index 275b8b4..aca3bbb 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -1,4 +1,3 @@ -import threading import os import sys import threading @@ -9,8 +8,6 @@ from backtracepython.utils import python_version from backtracepython.version import version_string -next_source_code_id = 1 -print(next_source_code_id) def add_source_code(source_path, source_code_dict, source_path_dict, line): try: @@ -31,6 +28,7 @@ def add_source_code(source_path, source_code_dict, source_path_dict, line): source_code_dict[the_id]["maxLine"] = line return the_id + def process_frame(tb_frame, line, source_code_dict, source_path_dict): source_file = os.path.abspath(tb_frame.f_code.co_filename) frame = { @@ -43,7 +41,6 @@ def process_frame(tb_frame, line, source_code_dict, source_path_dict): return frame - def get_main_thread(): if sys.version_info.major >= 3: return threading.main_thread() @@ -55,6 +52,7 @@ def get_main_thread(): first = thread return first + def walk_tb_backwards(tb): while tb is not None: yield tb.tb_frame, tb.tb_lineno @@ -64,6 +62,7 @@ def walk_tb_backwards(tb): def walk_tb(tb): return reversed(list(walk_tb_backwards(tb))) + class BacktraceReport: def __init__(self): self.fault_thread = threading.current_thread() @@ -159,4 +158,5 @@ def send(self): if len(self.log_lines) != 0 and "Log" not in self.report["annotations"]: self.report["annotations"]["Log"] = self.log_lines from backtracepython.client import send_worker_report + send_worker_report(self.report, self.source_code) diff --git a/backtracepython/utils.py b/backtracepython/utils.py index de8be83..12358cc 100644 --- a/backtracepython/utils.py +++ b/backtracepython/utils.py @@ -2,12 +2,13 @@ import sys python_version = "{} {}.{}.{}-{}".format( - platform.python_implementation(), - sys.version_info.major, - sys.version_info.minor, - sys.version_info.micro, - sys.version_info.releaselevel, - ) + platform.python_implementation(), + sys.version_info.major, + sys.version_info.minor, + sys.version_info.micro, + sys.version_info.releaselevel, +) + def get_python_version(): return python_version From adec9e03a752ad8d1419f6ac38db783d13159a55 Mon Sep 17 00:00:00 2001 From: Konrad Dysput Date: Wed, 11 Sep 2024 16:00:35 +0200 Subject: [PATCH 3/3] Remove duplicates and organize imports --- backtracepython/__init__.py | 5 ++--- backtracepython/client.py | 6 ++++-- backtracepython/report.py | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/backtracepython/__init__.py b/backtracepython/__init__.py index 8d006c4..57e0033 100644 --- a/backtracepython/__init__.py +++ b/backtracepython/__init__.py @@ -1,12 +1,11 @@ -from .version import version, version_string +from .client import finalize, initialize, send_last_exception, send_report from .report import BacktraceReport -from .client import initialize, finalize, finalize, send_last_exception, send_report +from .version import version, version_string __all__ = [ "BacktraceReport", "initialize", "finalize", - "finalize", "version", "version_string", "send_last_exception", diff --git a/backtracepython/client.py b/backtracepython/client.py index bf2b5bb..46ee862 100644 --- a/backtracepython/client.py +++ b/backtracepython/client.py @@ -1,10 +1,12 @@ import os +import subprocess import sys + import simplejson as json -import subprocess from backtracepython.attributes.attribute_manager import attribute_manager -from backtracepython.report import BacktraceReport + +from .report import BacktraceReport if sys.version_info.major >= 3: from urllib.parse import urlencode diff --git a/backtracepython/report.py b/backtracepython/report.py index aca3bbb..691e446 100644 --- a/backtracepython/report.py +++ b/backtracepython/report.py @@ -5,8 +5,9 @@ import uuid from backtracepython.attributes.attribute_manager import attribute_manager -from backtracepython.utils import python_version -from backtracepython.version import version_string + +from .utils import python_version +from .version import version_string def add_source_code(source_path, source_code_dict, source_path_dict, line):