Skip to content

Commit cac7533

Browse files
committed
JsonSerializableMixin
Signed-off-by: Sai Shree Pradhan <saishree.pradhan@databricks.com>
1 parent 2cdb760 commit cac7533

File tree

5 files changed

+44
-72
lines changed

5 files changed

+44
-72
lines changed

src/databricks/sql/telemetry/models/event.py

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import json
2-
from dataclasses import dataclass, asdict
1+
from dataclasses import dataclass
32
from databricks.sql.telemetry.models.enums import (
43
AuthMech,
54
AuthFlow,
@@ -9,11 +8,11 @@
98
ExecutionResultFormat,
109
)
1110
from typing import Optional
12-
from databricks.sql.telemetry.utils import to_json_compact
11+
from databricks.sql.telemetry.utils import JsonSerializableMixin
1312

1413

1514
@dataclass
16-
class HostDetails:
15+
class HostDetails(JsonSerializableMixin):
1716
"""
1817
Represents the host connection details for a Databricks workspace.
1918
@@ -25,12 +24,9 @@ class HostDetails:
2524
host_url: str
2625
port: int
2726

28-
def to_json(self):
29-
return to_json_compact(self)
30-
3127

3228
@dataclass
33-
class DriverConnectionParameters:
29+
class DriverConnectionParameters(JsonSerializableMixin):
3430
"""
3531
Contains all connection parameters used to establish a connection to Databricks SQL.
3632
This includes authentication details, host information, and connection settings.
@@ -51,12 +47,9 @@ class DriverConnectionParameters:
5147
auth_flow: Optional[AuthFlow] = None
5248
socket_timeout: Optional[int] = None
5349

54-
def to_json(self):
55-
return to_json_compact(self)
56-
5750

5851
@dataclass
59-
class DriverSystemConfiguration:
52+
class DriverSystemConfiguration(JsonSerializableMixin):
6053
"""
6154
Contains system-level configuration information about the client environment.
6255
This includes details about the operating system, runtime, and driver version.
@@ -87,12 +80,9 @@ class DriverSystemConfiguration:
8780
client_app_name: Optional[str] = None
8881
locale_name: Optional[str] = None
8982

90-
def to_json(self):
91-
return to_json_compact(self)
92-
9383

9484
@dataclass
95-
class DriverVolumeOperation:
85+
class DriverVolumeOperation(JsonSerializableMixin):
9686
"""
9787
Represents a volume operation performed by the driver.
9888
Used for tracking volume-related operations in telemetry.
@@ -105,12 +95,9 @@ class DriverVolumeOperation:
10595
volume_operation_type: DriverVolumeOperationType
10696
volume_path: str
10797

108-
def to_json(self):
109-
return to_json_compact(self)
110-
11198

11299
@dataclass
113-
class DriverErrorInfo:
100+
class DriverErrorInfo(JsonSerializableMixin):
114101
"""
115102
Contains detailed information about errors that occur during driver operations.
116103
Used for error tracking and debugging in telemetry.
@@ -123,12 +110,9 @@ class DriverErrorInfo:
123110
error_name: str
124111
stack_trace: str
125112

126-
def to_json(self):
127-
return to_json_compact(self)
128-
129113

130114
@dataclass
131-
class SqlExecutionEvent:
115+
class SqlExecutionEvent(JsonSerializableMixin):
132116
"""
133117
Represents a SQL query execution event.
134118
Contains details about the query execution, including type, compression, and result format.
@@ -145,12 +129,9 @@ class SqlExecutionEvent:
145129
execution_result: ExecutionResultFormat
146130
retry_count: int
147131

148-
def to_json(self):
149-
return to_json_compact(self)
150-
151132

152133
@dataclass
153-
class TelemetryEvent:
134+
class TelemetryEvent(JsonSerializableMixin):
154135
"""
155136
Main telemetry event class that aggregates all telemetry data.
156137
Contains information about the session, system configuration, connection parameters,
@@ -177,6 +158,3 @@ class TelemetryEvent:
177158
sql_operation: Optional[SqlExecutionEvent] = None
178159
error_info: Optional[DriverErrorInfo] = None
179160
operation_latency_ms: Optional[int] = None
180-
181-
def to_json(self):
182-
return to_json_compact(self)

src/databricks/sql/telemetry/models/frontend_logs.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import json
2-
from dataclasses import dataclass, asdict
1+
from dataclasses import dataclass
32
from databricks.sql.telemetry.models.event import TelemetryEvent
4-
from databricks.sql.telemetry.utils import to_json_compact
3+
from databricks.sql.telemetry.utils import JsonSerializableMixin
54
from typing import Optional
65

76

87
@dataclass
9-
class TelemetryClientContext:
8+
class TelemetryClientContext(JsonSerializableMixin):
109
"""
1110
Contains client-side context information for telemetry events.
1211
This includes timestamp and user agent information for tracking when and how the client is being used.
@@ -19,12 +18,9 @@ class TelemetryClientContext:
1918
timestamp_millis: int
2019
user_agent: str
2120

22-
def to_json(self):
23-
return to_json_compact(self)
24-
2521

2622
@dataclass
27-
class FrontendLogContext:
23+
class FrontendLogContext(JsonSerializableMixin):
2824
"""
2925
Wrapper for client context information in frontend logs.
3026
Provides additional context about the client environment for telemetry events.
@@ -35,12 +31,9 @@ class FrontendLogContext:
3531

3632
client_context: TelemetryClientContext
3733

38-
def to_json(self):
39-
return to_json_compact(self)
40-
4134

4235
@dataclass
43-
class FrontendLogEntry:
36+
class FrontendLogEntry(JsonSerializableMixin):
4437
"""
4538
Contains the actual telemetry event data in a frontend log.
4639
Wraps the SQL driver log information for frontend processing.
@@ -51,12 +44,9 @@ class FrontendLogEntry:
5144

5245
sql_driver_log: TelemetryEvent
5346

54-
def to_json(self):
55-
return to_json_compact(self)
56-
5747

5848
@dataclass
59-
class TelemetryFrontendLog:
49+
class TelemetryFrontendLog(JsonSerializableMixin):
6050
"""
6151
Main container for frontend telemetry data.
6252
Aggregates workspace information, event ID, context, and the actual log entry.
@@ -73,6 +63,3 @@ class TelemetryFrontendLog:
7363
context: FrontendLogContext
7464
entry: FrontendLogEntry
7565
workspace_id: Optional[int] = None
76-
77-
def to_json(self):
78-
return to_json_compact(self)

src/databricks/sql/telemetry/telemetry_client.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ def export_initial_telemetry_log(self, driver_connection_params, user_agent):
265265
),
266266
)
267267

268-
self.export_event(telemetry_frontend_log)
268+
self._export_event(telemetry_frontend_log)
269+
269270
except Exception as e:
270271
logger.debug("Failed to export initial telemetry log: %s", e)
271272

@@ -292,7 +293,7 @@ def export_failure_log(self, error_name, error_message):
292293
)
293294
),
294295
)
295-
self.export_event(telemetry_frontend_log)
296+
self._export_event(telemetry_frontend_log)
296297
except Exception as e:
297298
logger.debug("Failed to export failure log: %s", e)
298299

@@ -347,9 +348,9 @@ def _handle_unhandled_exception(cls, exc_type, exc_value, exc_traceback):
347348
"""Handle unhandled exceptions by sending telemetry and flushing thread pool"""
348349
logger.debug("Handling unhandled exception: %s", exc_type.__name__)
349350

350-
# Flush existing thread pool work and wait for completion
351-
for uuid, _ in cls._clients.items():
352-
cls.close(uuid)
351+
clients_to_close = list(cls._clients.values())
352+
for client in clients_to_close:
353+
client.close()
353354

354355
# Call the original exception handler to maintain normal behavior
355356
if cls._original_excepthook:

src/databricks/sql/telemetry/utils.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
import json
22
from enum import Enum
33
from dataclasses import asdict
4+
from abc import ABC
5+
from typing import Any
6+
7+
8+
class JsonSerializableMixin(ABC):
9+
"""Mixin class to provide JSON serialization capabilities to dataclasses."""
10+
11+
def to_json(self) -> str:
12+
"""
13+
Convert the object to a JSON string, excluding None values.
14+
Handles Enum serialization and filters out None values from the output.
15+
"""
16+
return json.dumps(
17+
asdict(
18+
self,
19+
dict_factory=lambda data: {k: v for k, v in data if v is not None},
20+
),
21+
cls=EnumEncoder,
22+
)
423

524

625
class EnumEncoder(json.JSONEncoder):
@@ -14,16 +33,3 @@ def default(self, obj):
1433
if isinstance(obj, Enum):
1534
return obj.value
1635
return super().default(obj)
17-
18-
19-
def to_json_compact(dataclass_obj):
20-
"""
21-
Convert a dataclass to JSON string, excluding None values.
22-
"""
23-
return json.dumps(
24-
asdict(
25-
dataclass_obj,
26-
dict_factory=lambda data: {k: v for k, v in data if v is not None},
27-
),
28-
cls=EnumEncoder,
29-
)

tests/unit/test_telemetry.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def test_export_initial_telemetry_log(
117117

118118
client = telemetry_client_setup["client"]
119119
host_url = telemetry_client_setup["host_url"]
120-
client.export_event = MagicMock()
120+
client._export_event = MagicMock()
121121

122122
driver_connection_params = DriverConnectionParameters(
123123
http_path="test-path",
@@ -131,7 +131,7 @@ def test_export_initial_telemetry_log(
131131
client.export_initial_telemetry_log(driver_connection_params, user_agent)
132132

133133
mock_frontend_log.assert_called_once()
134-
client.export_event.assert_called_once_with(mock_frontend_log.return_value)
134+
client._export_event.assert_called_once_with(mock_frontend_log.return_value)
135135

136136
@patch("databricks.sql.telemetry.telemetry_client.TelemetryFrontendLog")
137137
@patch("databricks.sql.telemetry.telemetry_client.TelemetryHelper.get_driver_system_configuration")
@@ -155,7 +155,7 @@ def test_export_failure_log(
155155
mock_frontend_log.return_value = MagicMock()
156156

157157
client = telemetry_client_setup["client"]
158-
client.export_event = MagicMock()
158+
client._export_event = MagicMock()
159159

160160
client._driver_connection_params = "test-connection-params"
161161
client._user_agent = "test-user-agent"
@@ -172,7 +172,7 @@ def test_export_failure_log(
172172

173173
mock_frontend_log.assert_called_once()
174174

175-
client.export_event.assert_called_once_with(mock_frontend_log.return_value)
175+
client._export_event.assert_called_once_with(mock_frontend_log.return_value)
176176

177177
def test_export_event(self, telemetry_client_setup):
178178
"""Test exporting an event."""

0 commit comments

Comments
 (0)