Skip to content

Commit a41e447

Browse files
committed
add snowflake-telemetry-python dep optional
1 parent 878fe0e commit a41e447

File tree

3 files changed

+87
-2
lines changed

3 files changed

+87
-2
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ def run(self):
230230
"opentelemetry-api>=1.0.0, <2.0.0",
231231
"opentelemetry-sdk>=1.0.0, <2.0.0",
232232
"opentelemetry-exporter-otlp>=1.0.0, <2.0.0",
233+
"snowflake-telemetry-python",
233234
],
234235
},
235236
classifiers=[

src/snowflake/snowpark/_internal/event_table_telemetry.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from logging import getLogger
88
from typing import Dict, Optional, Tuple
99
from snowflake.connector.options import MissingOptionalDependency, ModuleLikeObject
10-
1110
import snowflake.snowpark
1211
import requests
1312

@@ -23,6 +22,24 @@ class MissingOpenTelemetry(MissingOptionalDependency):
2322
_dep_name = "opentelemetry"
2423

2524

25+
class MissingSnowflakeTelemetry(MissingOptionalDependency):
26+
_dep_name = "snowflake-telemetry-python"
27+
28+
29+
def _import_or_missing_snowflake_telemetry() -> Tuple[ModuleLikeObject, bool]:
30+
try:
31+
# "snowflake-telemetry-python" is the *distribution* name. The importable
32+
# Python modules live under the `snowflake` namespace package.
33+
snowflake_telemetry = importlib.import_module("snowflake")
34+
# Import the parent first so `snowflake.telemetry` is attached to the
35+
# returned `snowflake` module (mirrors the pattern used for OpenTelemetry).
36+
importlib.import_module("snowflake.telemetry")
37+
importlib.import_module("snowflake.telemetry.trace")
38+
return snowflake_telemetry, True
39+
except ImportError:
40+
return MissingSnowflakeTelemetry(), False
41+
42+
2643
def _import_or_missing_opentelemetry() -> Tuple[ModuleLikeObject, bool]:
2744
try:
2845
opentelemetry = importlib.import_module("opentelemetry")
@@ -39,6 +56,7 @@ def _import_or_missing_opentelemetry() -> Tuple[ModuleLikeObject, bool]:
3956

4057

4158
opentelemetry, installed_opentelemetry = _import_or_missing_opentelemetry()
59+
snowflake_telemetry, _ = _import_or_missing_snowflake_telemetry()
4260

4361
BaseLogProvider = opentelemetry._logs.LoggerProvider if installed_opentelemetry else ABC
4462
BaseTraceProvider = (
@@ -394,7 +412,8 @@ def _init_trace_level(
394412
opentelemetry.trace.set_tracer_provider(self._proxy_tracer_provider)
395413

396414
self._tracer_provider = opentelemetry.sdk.trace.TracerProvider(
397-
resource=resource
415+
resource=resource,
416+
id_generator=snowflake_telemetry.telemetry.trace.SnowflakeTraceIdGenerator(),
398417
)
399418

400419
trace_session = requests.Session()

tests/unit/test_external_telemetry.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,68 @@ def always_fail(_name):
101101

102102
assert installed is False
103103
assert isinstance(otel, ett.MissingOpenTelemetry)
104+
105+
106+
def test_import_or_missing_snowflake_telemetry(monkeypatch):
107+
# Build a fake snowflake module tree
108+
snowflake = types.ModuleType("snowflake")
109+
snowflake_telemetry = types.ModuleType("snowflake.telemetry")
110+
snowflake_trace = types.ModuleType("snowflake.telemetry.trace")
111+
112+
class SnowflakeTraceIdGenerator:
113+
pass
114+
115+
snowflake_trace.SnowflakeTraceIdGenerator = SnowflakeTraceIdGenerator
116+
117+
def fake_import_module_factory(registry):
118+
def fake_import_module(name):
119+
# Simulate ImportError if an expected dependency isn't present
120+
if name not in registry:
121+
raise ImportError(name)
122+
123+
mod = registry[name]
124+
125+
# Emulate Python import behavior: after importing X.Y, X gets attribute Y.
126+
if "." in name:
127+
parent_name, child = name.rsplit(".", 1)
128+
setattr(registry[parent_name], child, mod)
129+
130+
return mod
131+
132+
return fake_import_module
133+
134+
# Case 1: snowflake telemetry trace is missing
135+
missing_registry = {
136+
"snowflake": snowflake,
137+
"snowflake.telemetry": snowflake_telemetry,
138+
}
139+
monkeypatch.setattr(
140+
ett.importlib, "import_module", fake_import_module_factory(missing_registry)
141+
)
142+
143+
sf, installed = ett._import_or_missing_snowflake_telemetry()
144+
assert installed is False
145+
assert isinstance(sf, ett.MissingSnowflakeTelemetry)
146+
147+
# Case 2: snowflake telemetry trace is present
148+
present_registry = {
149+
"snowflake": snowflake,
150+
"snowflake.telemetry": snowflake_telemetry,
151+
"snowflake.telemetry.trace": snowflake_trace,
152+
}
153+
monkeypatch.setattr(
154+
ett.importlib, "import_module", fake_import_module_factory(present_registry)
155+
)
156+
157+
sf, installed = ett._import_or_missing_snowflake_telemetry()
158+
assert installed is True
159+
assert sf is snowflake
160+
161+
# Key regression check: no AttributeError when accessing sf.telemetry.trace
162+
assert hasattr(sf, "telemetry")
163+
assert hasattr(sf.telemetry, "trace")
164+
assert sf.telemetry.trace is snowflake_trace
165+
assert sf.telemetry.trace.SnowflakeTraceIdGenerator is SnowflakeTraceIdGenerator
166+
assert isinstance(
167+
sf.telemetry.trace.SnowflakeTraceIdGenerator(), SnowflakeTraceIdGenerator
168+
)

0 commit comments

Comments
 (0)