sf-veritas 0.10.3__cp313-cp313-manylinux_2_28_x86_64.whl
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.
Potentially problematic release.
This version of sf-veritas might be problematic. Click here for more details.
- sf_veritas/__init__.py +20 -0
- sf_veritas/_sffastlog.c +889 -0
- sf_veritas/_sffastlog.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnet.c +924 -0
- sf_veritas/_sffastnet.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffastnetworkrequest.c +730 -0
- sf_veritas/_sffastnetworkrequest.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan.c +2155 -0
- sf_veritas/_sffuncspan.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sffuncspan_config.c +617 -0
- sf_veritas/_sffuncspan_config.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfheadercheck.c +341 -0
- sf_veritas/_sfheadercheck.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfnetworkhop.c +1451 -0
- sf_veritas/_sfnetworkhop.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfservice.c +1175 -0
- sf_veritas/_sfservice.cpython-313-x86_64-linux-gnu.so +0 -0
- sf_veritas/_sfteepreload.c +5167 -0
- sf_veritas/app_config.py +49 -0
- sf_veritas/cli.py +336 -0
- sf_veritas/constants.py +10 -0
- sf_veritas/custom_excepthook.py +304 -0
- sf_veritas/custom_log_handler.py +129 -0
- sf_veritas/custom_output_wrapper.py +144 -0
- sf_veritas/custom_print.py +146 -0
- sf_veritas/django_app.py +5 -0
- sf_veritas/env_vars.py +186 -0
- sf_veritas/exception_handling_middleware.py +18 -0
- sf_veritas/exception_metaclass.py +69 -0
- sf_veritas/fast_frame_info.py +116 -0
- sf_veritas/fast_network_hop.py +293 -0
- sf_veritas/frame_tools.py +112 -0
- sf_veritas/funcspan_config_loader.py +556 -0
- sf_veritas/function_span_profiler.py +1174 -0
- sf_veritas/import_hook.py +62 -0
- sf_veritas/infra_details/__init__.py +3 -0
- sf_veritas/infra_details/get_infra_details.py +24 -0
- sf_veritas/infra_details/kubernetes/__init__.py +3 -0
- sf_veritas/infra_details/kubernetes/get_cluster_name.py +147 -0
- sf_veritas/infra_details/kubernetes/get_details.py +7 -0
- sf_veritas/infra_details/running_on/__init__.py +17 -0
- sf_veritas/infra_details/running_on/kubernetes.py +11 -0
- sf_veritas/interceptors.py +497 -0
- sf_veritas/libsfnettee.so +0 -0
- sf_veritas/local_env_detect.py +118 -0
- sf_veritas/package_metadata.py +6 -0
- sf_veritas/patches/__init__.py +0 -0
- sf_veritas/patches/concurrent_futures.py +19 -0
- sf_veritas/patches/constants.py +1 -0
- sf_veritas/patches/exceptions.py +82 -0
- sf_veritas/patches/multiprocessing.py +32 -0
- sf_veritas/patches/network_libraries/__init__.py +76 -0
- sf_veritas/patches/network_libraries/aiohttp.py +281 -0
- sf_veritas/patches/network_libraries/curl_cffi.py +363 -0
- sf_veritas/patches/network_libraries/http_client.py +419 -0
- sf_veritas/patches/network_libraries/httpcore.py +515 -0
- sf_veritas/patches/network_libraries/httplib2.py +204 -0
- sf_veritas/patches/network_libraries/httpx.py +515 -0
- sf_veritas/patches/network_libraries/niquests.py +211 -0
- sf_veritas/patches/network_libraries/pycurl.py +385 -0
- sf_veritas/patches/network_libraries/requests.py +633 -0
- sf_veritas/patches/network_libraries/tornado.py +341 -0
- sf_veritas/patches/network_libraries/treq.py +270 -0
- sf_veritas/patches/network_libraries/urllib_request.py +468 -0
- sf_veritas/patches/network_libraries/utils.py +398 -0
- sf_veritas/patches/os.py +17 -0
- sf_veritas/patches/threading.py +218 -0
- sf_veritas/patches/web_frameworks/__init__.py +54 -0
- sf_veritas/patches/web_frameworks/aiohttp.py +793 -0
- sf_veritas/patches/web_frameworks/async_websocket_consumer.py +317 -0
- sf_veritas/patches/web_frameworks/blacksheep.py +527 -0
- sf_veritas/patches/web_frameworks/bottle.py +502 -0
- sf_veritas/patches/web_frameworks/cherrypy.py +678 -0
- sf_veritas/patches/web_frameworks/cors_utils.py +122 -0
- sf_veritas/patches/web_frameworks/django.py +944 -0
- sf_veritas/patches/web_frameworks/eve.py +395 -0
- sf_veritas/patches/web_frameworks/falcon.py +926 -0
- sf_veritas/patches/web_frameworks/fastapi.py +724 -0
- sf_veritas/patches/web_frameworks/flask.py +520 -0
- sf_veritas/patches/web_frameworks/klein.py +501 -0
- sf_veritas/patches/web_frameworks/litestar.py +551 -0
- sf_veritas/patches/web_frameworks/pyramid.py +428 -0
- sf_veritas/patches/web_frameworks/quart.py +824 -0
- sf_veritas/patches/web_frameworks/robyn.py +697 -0
- sf_veritas/patches/web_frameworks/sanic.py +857 -0
- sf_veritas/patches/web_frameworks/starlette.py +723 -0
- sf_veritas/patches/web_frameworks/strawberry.py +813 -0
- sf_veritas/patches/web_frameworks/tornado.py +481 -0
- sf_veritas/patches/web_frameworks/utils.py +91 -0
- sf_veritas/print_override.py +13 -0
- sf_veritas/regular_data_transmitter.py +409 -0
- sf_veritas/request_interceptor.py +401 -0
- sf_veritas/request_utils.py +550 -0
- sf_veritas/server_status.py +1 -0
- sf_veritas/shutdown_flag.py +11 -0
- sf_veritas/subprocess_startup.py +3 -0
- sf_veritas/test_cli.py +145 -0
- sf_veritas/thread_local.py +970 -0
- sf_veritas/timeutil.py +114 -0
- sf_veritas/transmit_exception_to_sailfish.py +28 -0
- sf_veritas/transmitter.py +132 -0
- sf_veritas/types.py +47 -0
- sf_veritas/unified_interceptor.py +1580 -0
- sf_veritas/utils.py +39 -0
- sf_veritas-0.10.3.dist-info/METADATA +97 -0
- sf_veritas-0.10.3.dist-info/RECORD +132 -0
- sf_veritas-0.10.3.dist-info/WHEEL +5 -0
- sf_veritas-0.10.3.dist-info/entry_points.txt +2 -0
- sf_veritas-0.10.3.dist-info/top_level.txt +1 -0
- sf_veritas.libs/libbrotlicommon-6ce2a53c.so.1.0.6 +0 -0
- sf_veritas.libs/libbrotlidec-811d1be3.so.1.0.6 +0 -0
- sf_veritas.libs/libcom_err-730ca923.so.2.1 +0 -0
- sf_veritas.libs/libcrypt-52aca757.so.1.1.0 +0 -0
- sf_veritas.libs/libcrypto-bdaed0ea.so.1.1.1k +0 -0
- sf_veritas.libs/libcurl-eaa3cf66.so.4.5.0 +0 -0
- sf_veritas.libs/libgssapi_krb5-323bbd21.so.2.2 +0 -0
- sf_veritas.libs/libidn2-2f4a5893.so.0.3.6 +0 -0
- sf_veritas.libs/libk5crypto-9a74ff38.so.3.1 +0 -0
- sf_veritas.libs/libkeyutils-2777d33d.so.1.6 +0 -0
- sf_veritas.libs/libkrb5-a55300e8.so.3.3 +0 -0
- sf_veritas.libs/libkrb5support-e6594cfc.so.0.1 +0 -0
- sf_veritas.libs/liblber-2-d20824ef.4.so.2.10.9 +0 -0
- sf_veritas.libs/libldap-2-cea2a960.4.so.2.10.9 +0 -0
- sf_veritas.libs/libnghttp2-39367a22.so.14.17.0 +0 -0
- sf_veritas.libs/libpcre2-8-516f4c9d.so.0.7.1 +0 -0
- sf_veritas.libs/libpsl-99becdd3.so.5.3.1 +0 -0
- sf_veritas.libs/libsasl2-7de4d792.so.3.0.0 +0 -0
- sf_veritas.libs/libselinux-d0805dcb.so.1 +0 -0
- sf_veritas.libs/libssh-c11d285b.so.4.8.7 +0 -0
- sf_veritas.libs/libssl-60250281.so.1.1.1k +0 -0
- sf_veritas.libs/libunistring-05abdd40.so.2.1.0 +0 -0
- sf_veritas.libs/libuuid-95b83d40.so.1.3.0 +0 -0
sf_veritas/timeutil.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import threading
|
|
3
|
+
import time
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from time import sleep
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
import ntplib
|
|
9
|
+
import requests
|
|
10
|
+
from dateutil import parser
|
|
11
|
+
from .env_vars import PRINT_CONFIGURATION_STATUSES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_log_argument_supported():
|
|
15
|
+
try:
|
|
16
|
+
signature = inspect.signature(print)
|
|
17
|
+
return "log" in signature.parameters
|
|
18
|
+
except (TypeError, ValueError) as err:
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Determine if the `log` argument is supported
|
|
23
|
+
PRINT_ARGS = {"log": False} if is_log_argument_supported() else {}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TimeSync:
|
|
27
|
+
_instance = None
|
|
28
|
+
_lock = threading.Lock()
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def get_instance(cls):
|
|
32
|
+
if not cls._instance:
|
|
33
|
+
with cls._lock:
|
|
34
|
+
if not cls._instance:
|
|
35
|
+
cls._instance = cls() # Initialize singleton
|
|
36
|
+
return cls._instance
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
if getattr(self, "_initialized", False): # Avoid reinitialization
|
|
40
|
+
return
|
|
41
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
42
|
+
print("Configurating global time lock", **PRINT_ARGS)
|
|
43
|
+
self.sync_utc_datetime, self.local_time_ms = self._fetch_utc_time()
|
|
44
|
+
self._initialized = True
|
|
45
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
46
|
+
print("Configurating global time lock...DONE", **PRINT_ARGS)
|
|
47
|
+
|
|
48
|
+
def _fetch_utc_time(self) -> Tuple[datetime, int]:
|
|
49
|
+
try:
|
|
50
|
+
return self._fetch_with_retries(self._fetch_ntp_time)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
53
|
+
print(f"NTP sync failed: {e}. Falling back to HTTP APIs.", **PRINT_ARGS)
|
|
54
|
+
return self._fetch_with_retries(self._fetch_utc_time_from_apis)
|
|
55
|
+
|
|
56
|
+
def _fetch_with_retries(
|
|
57
|
+
self, fetch_func, retries=3, backoff_factor=1
|
|
58
|
+
) -> Tuple[datetime, int]:
|
|
59
|
+
for attempt in range(retries):
|
|
60
|
+
try:
|
|
61
|
+
return fetch_func()
|
|
62
|
+
except Exception as e:
|
|
63
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
64
|
+
print(f"Attempt {attempt + 1} failed: {e}", **PRINT_ARGS)
|
|
65
|
+
sleep(backoff_factor * (2**attempt))
|
|
66
|
+
raise Exception("All retries failed")
|
|
67
|
+
|
|
68
|
+
def _fetch_ntp_time(self) -> Tuple[datetime, int]:
|
|
69
|
+
client = ntplib.NTPClient()
|
|
70
|
+
response = client.request("pool.ntp.org", version=3)
|
|
71
|
+
utc_datetime = datetime.utcfromtimestamp(response.tx_time).replace(
|
|
72
|
+
tzinfo=timezone.utc
|
|
73
|
+
)
|
|
74
|
+
local_time_ms = int(time.time() * 1000)
|
|
75
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
76
|
+
print("Time successfully synced using NTP.", **PRINT_ARGS)
|
|
77
|
+
return utc_datetime, local_time_ms
|
|
78
|
+
|
|
79
|
+
def _fetch_utc_time_from_apis(self) -> Tuple[datetime, int]:
|
|
80
|
+
apis = [
|
|
81
|
+
"https://worldtimeapi.org/api/timezone/Etc/UTC",
|
|
82
|
+
"http://worldclockapi.com/api/json/utc/now",
|
|
83
|
+
]
|
|
84
|
+
for api in apis:
|
|
85
|
+
try:
|
|
86
|
+
response = requests.get(api, timeout=5)
|
|
87
|
+
response.raise_for_status()
|
|
88
|
+
data = response.json()
|
|
89
|
+
if "datetime" in data:
|
|
90
|
+
utc_datetime = parser.isoparse(data["datetime"])
|
|
91
|
+
elif "currentDateTime" in data:
|
|
92
|
+
utc_datetime = parser.isoparse(data["currentDateTime"])
|
|
93
|
+
else:
|
|
94
|
+
raise ValueError("Unexpected API response format")
|
|
95
|
+
local_time_ms = int(time.time() * 1000)
|
|
96
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
97
|
+
print(f"Time successfully synced using {api}.", **PRINT_ARGS)
|
|
98
|
+
return utc_datetime, local_time_ms
|
|
99
|
+
except Exception as e:
|
|
100
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
101
|
+
print(f"Failed to fetch time from {api}: {e}", **PRINT_ARGS)
|
|
102
|
+
if PRINT_CONFIGURATION_STATUSES:
|
|
103
|
+
print("All time sources failed. Falling back to system time.", **PRINT_ARGS)
|
|
104
|
+
utc_datetime = datetime.now(timezone.utc)
|
|
105
|
+
local_time_ms = int(time.time() * 1000)
|
|
106
|
+
return utc_datetime, local_time_ms
|
|
107
|
+
|
|
108
|
+
def get_utc_time_in_ms(self) -> int:
|
|
109
|
+
current_local_time_ms = int(time.time() * 1000)
|
|
110
|
+
elapsed_ms = current_local_time_ms - self.local_time_ms
|
|
111
|
+
current_utc_datetime = self.sync_utc_datetime + timedelta(
|
|
112
|
+
milliseconds=elapsed_ms
|
|
113
|
+
)
|
|
114
|
+
return int(current_utc_datetime.timestamp() * 1000)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from .custom_excepthook import transmit_exception
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def transmit_exception_to_sailfish(
|
|
5
|
+
exc: BaseException,
|
|
6
|
+
force_transmit: bool = False,
|
|
7
|
+
):
|
|
8
|
+
"""
|
|
9
|
+
Transmit an exception to Sailfish using the original traceback captured at the
|
|
10
|
+
point the exception was raised.
|
|
11
|
+
|
|
12
|
+
:param exc: The exception instance.
|
|
13
|
+
:param force_transmit: If True, will transmit even if the exception might
|
|
14
|
+
have already been handled or flagged.
|
|
15
|
+
"""
|
|
16
|
+
# Get the exception type and traceback from the exception itself
|
|
17
|
+
exc_type = type(exc)
|
|
18
|
+
exc_traceback = exc.__traceback__ # Automatically fetch the original traceback
|
|
19
|
+
|
|
20
|
+
# In some implementations, you might keep a `_handled` attribute to avoid double transmission.
|
|
21
|
+
if not force_transmit and getattr(exc, "_handled", False):
|
|
22
|
+
return # Already transmitted
|
|
23
|
+
|
|
24
|
+
# Actually send it over to Sailfish
|
|
25
|
+
transmit_exception(exc_type, exc, exc_traceback)
|
|
26
|
+
|
|
27
|
+
# Mark as handled to avoid re-transmission
|
|
28
|
+
setattr(exc, "_handled", True)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from .env_vars import SF_DEBUG
|
|
4
|
+
from .thread_local import get_or_set_sf_trace_id
|
|
5
|
+
|
|
6
|
+
# Try to import C extension for fast path
|
|
7
|
+
try:
|
|
8
|
+
from . import _sfservice
|
|
9
|
+
_SFSERVICE_AVAILABLE = True
|
|
10
|
+
except Exception:
|
|
11
|
+
_sfservice = None
|
|
12
|
+
_SFSERVICE_AVAILABLE = False
|
|
13
|
+
|
|
14
|
+
# Always have Python fallback available (lazy-loaded when needed)
|
|
15
|
+
collect_metadata_transmitter = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SailfishTransmitter(object):
|
|
19
|
+
@classmethod
|
|
20
|
+
def identify(
|
|
21
|
+
cls,
|
|
22
|
+
user_id: str,
|
|
23
|
+
traits: Optional[Dict[str, Any]] = None,
|
|
24
|
+
traits_json: Optional[str] = None,
|
|
25
|
+
override: bool = False,
|
|
26
|
+
) -> None:
|
|
27
|
+
if traits is not None or traits_json is not None:
|
|
28
|
+
return cls.add_or_update_metadata(user_id, traits, traits_json, override)
|
|
29
|
+
return cls.add_or_update_metadata(user_id, dict(), override=override)
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
def update_service_details(
|
|
33
|
+
cls,
|
|
34
|
+
service_identifier: Optional[str] = None,
|
|
35
|
+
service_version: Optional[str] = None,
|
|
36
|
+
service_additional_metadata: Optional[Dict[str, Any]] = None,
|
|
37
|
+
git_sha: Optional[str] = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
"""
|
|
40
|
+
Updates service details with metadata.
|
|
41
|
+
Sends mutation updateServiceDetails (Python implementation - no C extension yet).
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
service_identifier: Service identifier string
|
|
45
|
+
service_version: Service version string
|
|
46
|
+
service_additional_metadata: Dictionary of additional metadata
|
|
47
|
+
git_sha: Git SHA hash
|
|
48
|
+
"""
|
|
49
|
+
# Import here to avoid circular dependency
|
|
50
|
+
from .regular_data_transmitter import UpdateServiceIdentifierMetadata
|
|
51
|
+
|
|
52
|
+
transmitter = UpdateServiceIdentifierMetadata()
|
|
53
|
+
transmitter.send(
|
|
54
|
+
service_identifier=service_identifier,
|
|
55
|
+
service_version=service_version,
|
|
56
|
+
service_additional_metadata=service_additional_metadata,
|
|
57
|
+
git_sha=git_sha,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
if SF_DEBUG:
|
|
61
|
+
print(f"[[DEBUG]] update_service_details sent: id={service_identifier}, version={service_version}, sha={git_sha}", log=False)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def add_or_update_metadata(
|
|
65
|
+
cls,
|
|
66
|
+
user_id: str,
|
|
67
|
+
traits: Optional[Dict[str, Any]] = None,
|
|
68
|
+
traits_json: Optional[str] = None,
|
|
69
|
+
override: bool = False,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Sets traits and sends to the Sailfish AI backend
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
user_id: unique identifier for the user; common uses are username or email
|
|
76
|
+
traits: dictionary of contents to add or update in the user's traits. Defaults to None.
|
|
77
|
+
traits_json: json string of contents to add or update in the user's traits. Defaults to None.
|
|
78
|
+
"""
|
|
79
|
+
if traits is None and traits_json is None:
|
|
80
|
+
raise Exception(
|
|
81
|
+
'Must pass in either traits or traits_json to "add_or_update_traits"'
|
|
82
|
+
)
|
|
83
|
+
if SF_DEBUG:
|
|
84
|
+
print(
|
|
85
|
+
"[[DEBUG - add_or_update_traits]] starting thread [[/DEBUG]]", log=False
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
_, trace_id = get_or_set_sf_trace_id()
|
|
89
|
+
if SF_DEBUG:
|
|
90
|
+
print(
|
|
91
|
+
"add_or_update_metadata...SENDING DATA...args=",
|
|
92
|
+
(user_id, traits, traits_json, override, trace_id),
|
|
93
|
+
trace_id,
|
|
94
|
+
log=False,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Fast path: Use C extension if available
|
|
98
|
+
if _SFSERVICE_AVAILABLE and _sfservice:
|
|
99
|
+
try:
|
|
100
|
+
excluded_fields = []
|
|
101
|
+
|
|
102
|
+
# If traits is provided as dict, serialize it
|
|
103
|
+
if traits_json is None:
|
|
104
|
+
from .utils import serialize_json_with_exclusions
|
|
105
|
+
traits_json, excluded_fields = serialize_json_with_exclusions(traits)
|
|
106
|
+
|
|
107
|
+
# Call C extension
|
|
108
|
+
_sfservice.collect_metadata(
|
|
109
|
+
session_id=str(trace_id),
|
|
110
|
+
user_id=user_id,
|
|
111
|
+
traits_json=traits_json,
|
|
112
|
+
excluded_fields=excluded_fields,
|
|
113
|
+
override=override,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if SF_DEBUG:
|
|
117
|
+
print("[[DEBUG]] collect_metadata sent via _sfservice C extension", log=False)
|
|
118
|
+
return
|
|
119
|
+
except Exception as e:
|
|
120
|
+
if SF_DEBUG:
|
|
121
|
+
print(f"[[DEBUG]] Failed to send via C extension: {e}, falling back to Python", log=False)
|
|
122
|
+
# Fall through to Python implementation
|
|
123
|
+
|
|
124
|
+
# Fallback: Use Python implementation (lazy-load if needed)
|
|
125
|
+
global collect_metadata_transmitter
|
|
126
|
+
if collect_metadata_transmitter is None:
|
|
127
|
+
from .interceptors import CollectMetadataTransmitter
|
|
128
|
+
collect_metadata_transmitter = CollectMetadataTransmitter()
|
|
129
|
+
|
|
130
|
+
collect_metadata_transmitter.do_send(
|
|
131
|
+
(user_id, traits, traits_json, override, trace_id), trace_id
|
|
132
|
+
)
|
sf_veritas/types.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class FrameInfo:
|
|
8
|
+
file: str
|
|
9
|
+
line: int
|
|
10
|
+
function: str
|
|
11
|
+
code: str
|
|
12
|
+
locals: Optional[Dict[str, Any]] = field(default_factory=dict)
|
|
13
|
+
column: Optional[int] = None
|
|
14
|
+
offender: Optional[bool] = False
|
|
15
|
+
|
|
16
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
17
|
+
frame_info_dict = {
|
|
18
|
+
"file": self.file,
|
|
19
|
+
"line": self.line,
|
|
20
|
+
"function": self.function,
|
|
21
|
+
"code": self.code,
|
|
22
|
+
}
|
|
23
|
+
if self.locals:
|
|
24
|
+
frame_info_dict["locals"] = {k: str(v) for k, v in self.locals.items()}
|
|
25
|
+
if self.offender:
|
|
26
|
+
frame_info_dict["offender"] = self.offender
|
|
27
|
+
if self.column is not None:
|
|
28
|
+
frame_info_dict["column"] = self.column
|
|
29
|
+
return frame_info_dict
|
|
30
|
+
|
|
31
|
+
def to_json(self) -> str:
|
|
32
|
+
return json.dumps(self.to_dict())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_trace_from_json(data_json) -> List[FrameInfo]:
|
|
36
|
+
data = json.loads(data_json)
|
|
37
|
+
return [FrameInfo(**item) for item in data]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CustomJSONEncoderForFrameInfo(json.JSONEncoder):
|
|
41
|
+
def default(self, obj):
|
|
42
|
+
if isinstance(obj, FrameInfo):
|
|
43
|
+
return obj.to_dict()
|
|
44
|
+
try:
|
|
45
|
+
return super().default(obj)
|
|
46
|
+
except TypeError:
|
|
47
|
+
return str(obj)
|