Skip to content

Commit 13e9ab3

Browse files
Add snowflake integration (#2901)
* scaffold snowflake integration * setup snowflake in CircleCI * scaffold tests * work on snowflake tests * get mocked up authentication flow * fix tests * fix circleci * fix contirb_job mapping * remove unused import * use machine executor * update riotfile for snowflake 2.3.0 * add more tests * add parallelism to circleci job * add to monkey.py * add release note * Add to docs * fix link * Update ddtrace/contrib/snowflake/patch.py Co-authored-by: Kyle Verhoog <[email protected]> * Update snowflake docs * remove todo comment * remove docstring * fix passing pin and config to TracedConnection * add new json fixtures * fix service env fixture * fix service from env test * remove unused import * Do not assert on err Co-authored-by: Kyle Verhoog <[email protected]>
1 parent 38ddd31 commit 13e9ab3

26 files changed

+1069
-0
lines changed

.circleci/config.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,14 @@ jobs:
748748
- run_tox_scenario:
749749
pattern: '^sanic_contrib-'
750750

751+
snowflake:
752+
<<: *machine_executor
753+
parallelism: 4
754+
steps:
755+
- run_test:
756+
pattern: "snowflake"
757+
snapshot: true
758+
751759
starlette:
752760
<<: *machine_executor
753761
steps:
@@ -991,6 +999,7 @@ requires_tests: &requires_tests
991999
- requests
9921000
- rq
9931001
- sanic
1002+
- snowflake
9941003
- sqlalchemy
9951004
- sqlite3
9961005
- starlette
@@ -1074,6 +1083,7 @@ workflows:
10741083
- requests: *requires_base_venvs
10751084
- rq: *requires_base_venvs
10761085
- sanic: *requires_base_venvs
1086+
- snowflake: *requires_base_venvs
10771087
- starlette: *requires_base_venvs
10781088
- sqlalchemy: *requires_base_venvs
10791089
- sqlite3: *requires_base_venvs
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
The snowflake integration instruments the ``snowflake-connector-python`` library to trace Snowflake queries.
3+
4+
Enabling
5+
~~~~~~~~
6+
7+
The integration is enabled automatically when using
8+
:ref:`ddtrace-run<ddtracerun>` or :ref:`patch_all()<patch_all>`.
9+
10+
Or use :ref:`patch()<patch>` to manually enable the integration::
11+
12+
from ddtrace import patch
13+
patch(snowflake=True)
14+
15+
16+
Global Configuration
17+
~~~~~~~~~~~~~~~~~~~~
18+
19+
.. py:data:: ddtrace.config.snowflake["service"]
20+
21+
The service name reported by default for snowflake spans.
22+
23+
This option can also be set with the ``DD_SNOWFLAKE_SERVICE`` environment
24+
variable.
25+
26+
Default: ``"snowflake"``
27+
28+
.. py:data:: ddtrace.config.snowflake["trace_fetch_methods"]
29+
30+
Whether or not to trace fetch methods.
31+
32+
Can also configured via the ``DD_SNOWFLAKE_TRACE_FETCH_METHODS`` environment variable.
33+
34+
Default: ``False``
35+
36+
37+
Instance Configuration
38+
~~~~~~~~~~~~~~~~~~~~~~
39+
40+
To configure the integration on an per-connection basis use the
41+
``Pin`` API::
42+
43+
from ddtrace import Pin
44+
from snowflake.connector import connect
45+
46+
# This will report a span with the default settings
47+
conn = connect(user="alice", password="b0b", account="dev")
48+
49+
# Use a pin to override the service name for this connection.
50+
Pin.override(conn, service="snowflake-dev")
51+
52+
53+
cursor = conn.cursor()
54+
cursor.execute("SELECT current_version()")
55+
"""
56+
from ...utils.importlib import require_modules
57+
58+
59+
required_modules = ["snowflake.connector"]
60+
61+
with require_modules(required_modules) as missing_modules:
62+
if not missing_modules:
63+
from .patch import patch
64+
from .patch import unpatch
65+
66+
__all__ = ["patch", "unpatch"]

ddtrace/contrib/snowflake/patch.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from ddtrace import Pin
2+
from ddtrace import config
3+
from ddtrace.vendor import wrapt
4+
5+
from ...ext import db
6+
from ...ext import net
7+
from ...utils.formats import asbool
8+
from ...utils.formats import get_env
9+
from ...utils.wrappers import unwrap
10+
from ..dbapi import TracedConnection
11+
12+
13+
config._add(
14+
"snowflake",
15+
dict(
16+
_default_service="snowflake",
17+
trace_fetch_methods=asbool(get_env("snowflake", "trace_fetch_methods", default=False)),
18+
),
19+
)
20+
21+
22+
def patch():
23+
import snowflake.connector
24+
25+
if getattr(snowflake.connector, "_datadog_patch", False):
26+
return
27+
setattr(snowflake.connector, "_datadog_patch", True)
28+
29+
wrapt.wrap_function_wrapper(snowflake.connector, "Connect", patched_connect)
30+
wrapt.wrap_function_wrapper(snowflake.connector, "connect", patched_connect)
31+
32+
33+
def unpatch():
34+
import snowflake.connector
35+
36+
if getattr(snowflake.connector, "_datadog_patch", False):
37+
setattr(snowflake.connector, "_datadog_patch", False)
38+
39+
unwrap(snowflake.connector, "Connect")
40+
unwrap(snowflake.connector, "connect")
41+
42+
43+
def patched_connect(connect_func, _, args, kwargs):
44+
conn = connect_func(*args, **kwargs)
45+
if isinstance(conn, TracedConnection):
46+
return conn
47+
48+
# Add default tags to each query
49+
tags = {
50+
net.TARGET_HOST: conn.host,
51+
net.TARGET_PORT: conn.port,
52+
db.NAME: conn.database,
53+
db.USER: conn.user,
54+
"db.application": conn.application,
55+
"db.schema": conn.schema,
56+
"db.warehouse": conn.warehouse,
57+
}
58+
59+
pin = Pin(tags=tags)
60+
traced_conn = TracedConnection(conn, pin=pin, cfg=config.snowflake)
61+
pin.onto(traced_conn)
62+
return traced_conn

ddtrace/monkey.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"requests": True,
5454
"rq": True,
5555
"sanic": True,
56+
"snowflake": True,
5657
"sqlalchemy": False, # Prefer DB client instrumentation
5758
"sqlite3": True,
5859
"aiohttp": True, # requires asyncio (Python 3.4+)

docs/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ contacting support.
136136
+--------------------------------------------------+---------------+----------------+
137137
| :ref:`sanic` | >= 19.6.0 | Yes [4]_ |
138138
+--------------------------------------------------+---------------+----------------+
139+
| :ref:`snowflake` | >= 2.0.0 | Yes |
140+
+--------------------------------------------------+---------------+----------------+
139141
| :ref:`sqlalchemy` | >= 1.0 | No |
140142
+--------------------------------------------------+---------------+----------------+
141143
| :ref:`sqlite` | \* | Yes |

docs/integrations.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,13 @@ Sanic
345345
.. automodule:: ddtrace.contrib.sanic
346346

347347

348+
.. _snowflake:
349+
350+
Snowflake
351+
^^^^^^^^^
352+
.. automodule:: ddtrace.contrib.snowflake
353+
354+
348355
.. _starlette:
349356

350357
Starlette
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
features:
3+
- |
4+
Add support for `snowflake-connector-python <https://pypi.org/project/snowflake-connector-python/>`_ >= 2.0.0.

riotfile.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,5 +1204,53 @@ def select_pys(min_version=MIN_PYTHON_VERSION, max_version=MAX_PYTHON_VERSION):
12041204
],
12051205
},
12061206
),
1207+
Venv(
1208+
name="snowflake",
1209+
command="pytest {cmdargs} tests/contrib/snowflake",
1210+
pkgs={
1211+
"responses": latest,
1212+
},
1213+
venvs=[
1214+
Venv(
1215+
# 2.2.0 dropped 2.7 support
1216+
pys=select_pys(),
1217+
pkgs={
1218+
"snowflake-connector-python": [
1219+
"~=2.0.0",
1220+
"~=2.1.0",
1221+
],
1222+
},
1223+
),
1224+
Venv(
1225+
# 2.3.7 dropped 3.5 support
1226+
pys=select_pys(min_version="3.5"),
1227+
pkgs={
1228+
"snowflake-connector-python": [
1229+
"~=2.2.0",
1230+
],
1231+
},
1232+
),
1233+
Venv(
1234+
# 2.3.x needs pyarrow >=0.17,<0.18 which does not install on Python 3.9
1235+
pys=select_pys(min_version="3.6", max_version="3.8"),
1236+
pkgs={
1237+
"snowflake-connector-python": [
1238+
"~=2.3.0",
1239+
],
1240+
},
1241+
),
1242+
Venv(
1243+
pys=select_pys(min_version="3.6"),
1244+
pkgs={
1245+
"snowflake-connector-python": [
1246+
"~=2.4.0",
1247+
"~=2.5.0",
1248+
"~=2.6.0",
1249+
latest,
1250+
],
1251+
},
1252+
),
1253+
],
1254+
),
12071255
],
12081256
)

tests/contrib/snowflake/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)