Skip to content

Commit 88406fd

Browse files
authored
Merge pull request #215 from anil-grexit/patch_pymysql
Add patch support for pymysql (pure Python driver)
2 parents 4c9261d + 98087bd commit 88406fd

File tree

6 files changed

+140
-0
lines changed

6 files changed

+140
-0
lines changed

aws_xray_sdk/core/patcher.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'mysql',
2222
'httplib',
2323
'pymongo',
24+
'pymysql',
2425
'psycopg2',
2526
'pg8000',
2627
)
@@ -33,6 +34,7 @@
3334
'sqlite3',
3435
'mysql',
3536
'pymongo',
37+
'pymysql',
3638
'psycopg2',
3739
'pg8000',
3840
)

aws_xray_sdk/ext/pymysql/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .patch import patch, unpatch
2+
3+
4+
__all__ = ['patch', 'unpatch']

aws_xray_sdk/ext/pymysql/patch.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import pymysql
2+
import wrapt
3+
4+
from aws_xray_sdk.ext.dbapi2 import XRayTracedConn
5+
from aws_xray_sdk.core.patcher import _PATCHED_MODULES
6+
from aws_xray_sdk.ext.util import unwrap
7+
8+
9+
def patch():
10+
11+
wrapt.wrap_function_wrapper(
12+
'pymysql',
13+
'connect',
14+
_xray_traced_connect
15+
)
16+
17+
# patch alias
18+
if hasattr(pymysql, 'Connect'):
19+
pymysql.Connect = pymysql.connect
20+
21+
22+
def _xray_traced_connect(wrapped, instance, args, kwargs):
23+
24+
conn = wrapped(*args, **kwargs)
25+
meta = {
26+
'database_type': 'MySQL',
27+
'user': conn.user.decode('utf-8'),
28+
'driver_version': 'PyMySQL'
29+
}
30+
31+
if hasattr(conn, 'server_version'):
32+
version = sanitize_db_ver(getattr(conn, 'server_version'))
33+
if version:
34+
meta['database_version'] = version
35+
36+
return XRayTracedConn(conn, meta)
37+
38+
39+
def sanitize_db_ver(raw):
40+
41+
if not raw or not isinstance(raw, tuple):
42+
return raw
43+
44+
return '.'.join(str(num) for num in raw)
45+
46+
47+
def unpatch():
48+
"""
49+
Unpatch any previously patched modules.
50+
This operation is idempotent.
51+
"""
52+
_PATCHED_MODULES.discard('pymysql')
53+
unwrap(pymysql, 'connect')

tests/ext/pymysql/__init__.py

Whitespace-only changes.

tests/ext/pymysql/test_pymysql.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import pymysql
2+
3+
import pytest
4+
import testing.mysqld
5+
6+
from aws_xray_sdk.core import patch
7+
from aws_xray_sdk.core import xray_recorder
8+
from aws_xray_sdk.core.context import Context
9+
from aws_xray_sdk.ext.pymysql import unpatch
10+
11+
12+
@pytest.fixture(scope='module', autouse=True)
13+
def patch_module():
14+
patch(('pymysql',))
15+
yield
16+
unpatch()
17+
18+
19+
@pytest.fixture(autouse=True)
20+
def construct_ctx():
21+
"""
22+
Clean up context storage on each test run and begin a segment
23+
so that later subsegment can be attached. After each test run
24+
it cleans up context storage again.
25+
"""
26+
xray_recorder.configure(service='test', sampling=False, context=Context())
27+
xray_recorder.clear_trace_entities()
28+
xray_recorder.begin_segment('name')
29+
yield
30+
xray_recorder.clear_trace_entities()
31+
32+
33+
def test_execute_dsn_kwargs():
34+
q = 'SELECT 1'
35+
with testing.mysqld.Mysqld() as mysqld:
36+
dsn = mysqld.dsn()
37+
conn = pymysql.connect(database=dsn['db'],
38+
user=dsn['user'],
39+
password='',
40+
host=dsn['host'],
41+
port=dsn['port'])
42+
cur = conn.cursor()
43+
cur.execute(q)
44+
45+
subsegment = xray_recorder.current_segment().subsegments[-1]
46+
assert subsegment.name == 'execute'
47+
sql = subsegment.sql
48+
assert sql['database_type'] == 'MySQL'
49+
assert sql['user'] == dsn['user']
50+
assert sql['driver_version'] == 'PyMySQL'
51+
assert sql['database_version']
52+
53+
54+
def test_execute_bad_query():
55+
q = "SELECT blarg"
56+
with testing.mysqld.Mysqld() as mysqld:
57+
dsn = mysqld.dsn()
58+
conn = pymysql.connect(database=dsn['db'],
59+
user=dsn['user'],
60+
password='',
61+
host=dsn['host'],
62+
port=dsn['port'])
63+
64+
cur = conn.cursor()
65+
try:
66+
cur.execute(q)
67+
except Exception:
68+
pass
69+
70+
subsegment = xray_recorder.current_segment().subsegments[-1]
71+
assert subsegment.name == "execute"
72+
sql = subsegment.sql
73+
assert sql['database_type'] == 'MySQL'
74+
assert sql['user'] == dsn['user']
75+
assert sql['driver_version'] == 'PyMySQL'
76+
assert sql['database_version']
77+
78+
exception = subsegment.cause['exceptions'][0]
79+
assert exception.type == 'InternalError'

tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ deps =
2222
django >= 1.10
2323
django-fake-model
2424
pynamodb >= 3.3.1
25+
pymysql
2526
psycopg2
2627
pg8000
2728
testing.postgresql
29+
testing.mysqld
2830
webtest
2931

3032
# Python2 only deps

0 commit comments

Comments
 (0)