Skip to content

Commit 461d488

Browse files
Tankanowhaotianw465
authored andcommitted
ISSUE-80: Add patch support for psycopg2 (#83)
* ISSUE-80: Basic psycopg2 patcher and test * ISSUE-80: Add pool test * ISSUE-80: Update dbname; Add host and user * ISSUE-80: Add testing resources to tox.ini * ISSUE-80: Update meta keys to match supported keys * ISSUE-80: Fix failing tests; simply assert database_version rather than compare versions * ISSUE-80: Add psycopg2 to NO_DOUBLE_PATCH * ISSUE-80: Remove debug print statements
1 parent f6436f8 commit 461d488

File tree

6 files changed

+159
-0
lines changed

6 files changed

+159
-0
lines changed

aws_xray_sdk/core/patcher.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
'mysql',
1111
'httplib',
1212
'pymongo',
13+
'psycopg2',
1314
)
1415

1516
NO_DOUBLE_PATCH = (
@@ -18,6 +19,7 @@
1819
'sqlite3',
1920
'mysql',
2021
'pymongo',
22+
'psycopg2',
2123
)
2224

2325
_PATCHED_MODULES = set()

aws_xray_sdk/ext/psycopg2/__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
2+
3+
4+
__all__ = ['patch']

aws_xray_sdk/ext/psycopg2/patch.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import re
2+
import wrapt
3+
4+
from aws_xray_sdk.ext.dbapi2 import XRayTracedConn
5+
6+
7+
def patch():
8+
9+
wrapt.wrap_function_wrapper(
10+
'psycopg2',
11+
'connect',
12+
_xray_traced_connect
13+
)
14+
15+
16+
def _xray_traced_connect(wrapped, instance, args, kwargs):
17+
18+
conn = wrapped(*args, **kwargs)
19+
host = kwargs['host'] if 'host' in kwargs else re.search(r'host=(\S+)\b', args[0]).groups()[0]
20+
dbname = kwargs['dbname'] if 'dbname' in kwargs else re.search(r'dbname=(\S+)\b', args[0]).groups()[0]
21+
port = kwargs['port'] if 'port' in kwargs else re.search(r'port=(\S+)\b', args[0]).groups()[0]
22+
user = kwargs['user'] if 'user' in kwargs else re.search(r'user=(\S+)\b', args[0]).groups()[0]
23+
meta = {
24+
'database_type': 'PostgreSQL',
25+
'url': 'postgresql://{}@{}:{}/{}'.format(user, host, port, dbname),
26+
'user': user,
27+
'database_version': str(conn.server_version),
28+
'driver_version': 'Psycopg 2'
29+
}
30+
31+
return XRayTracedConn(conn, meta)

tests/ext/psycopg2/__init__.py

Whitespace-only changes.

tests/ext/psycopg2/test_psycopg2.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import psycopg2
2+
import psycopg2.pool
3+
4+
import pytest
5+
import testing.postgresql
6+
7+
from aws_xray_sdk.core import patch
8+
from aws_xray_sdk.core import xray_recorder
9+
from aws_xray_sdk.core.context import Context
10+
11+
patch(('psycopg2',))
12+
13+
14+
@pytest.fixture(autouse=True)
15+
def construct_ctx():
16+
"""
17+
Clean up context storage on each test run and begin a segment
18+
so that later subsegment can be attached. After each test run
19+
it cleans up context storage again.
20+
"""
21+
xray_recorder.configure(service='test', sampling=False, context=Context())
22+
xray_recorder.clear_trace_entities()
23+
xray_recorder.begin_segment('name')
24+
yield
25+
xray_recorder.clear_trace_entities()
26+
27+
28+
def test_execute_dsn_kwargs():
29+
q = 'SELECT 1'
30+
with testing.postgresql.Postgresql() as postgresql:
31+
url = postgresql.url()
32+
dsn = postgresql.dsn()
33+
conn = psycopg2.connect(dbname=dsn['database'],
34+
user=dsn['user'],
35+
password='',
36+
host=dsn['host'],
37+
port=dsn['port'])
38+
cur = conn.cursor()
39+
cur.execute(q)
40+
41+
subsegment = xray_recorder.current_segment().subsegments[0]
42+
assert subsegment.name == 'execute'
43+
sql = subsegment.sql
44+
assert sql['database_type'] == 'PostgreSQL'
45+
assert sql['user'] == dsn['user']
46+
assert sql['url'] == url
47+
assert sql['database_version']
48+
49+
50+
def test_execute_dsn_string():
51+
q = 'SELECT 1'
52+
with testing.postgresql.Postgresql() as postgresql:
53+
url = postgresql.url()
54+
dsn = postgresql.dsn()
55+
conn = psycopg2.connect('dbname=' + dsn['database'] +
56+
' password=mypassword' +
57+
' host=' + dsn['host'] +
58+
' port=' + str(dsn['port']) +
59+
' user=' + dsn['user'])
60+
cur = conn.cursor()
61+
cur.execute(q)
62+
63+
subsegment = xray_recorder.current_segment().subsegments[0]
64+
assert subsegment.name == 'execute'
65+
sql = subsegment.sql
66+
assert sql['database_type'] == 'PostgreSQL'
67+
assert sql['user'] == dsn['user']
68+
assert sql['url'] == url
69+
assert sql['database_version']
70+
71+
72+
def test_execute_in_pool():
73+
q = 'SELECT 1'
74+
with testing.postgresql.Postgresql() as postgresql:
75+
url = postgresql.url()
76+
dsn = postgresql.dsn()
77+
pool = psycopg2.pool.SimpleConnectionPool(1, 1,
78+
dbname=dsn['database'],
79+
user=dsn['user'],
80+
password='',
81+
host=dsn['host'],
82+
port=dsn['port'])
83+
cur = pool.getconn(key=dsn['user']).cursor()
84+
cur.execute(q)
85+
86+
subsegment = xray_recorder.current_segment().subsegments[0]
87+
assert subsegment.name == 'execute'
88+
sql = subsegment.sql
89+
assert sql['database_type'] == 'PostgreSQL'
90+
assert sql['user'] == dsn['user']
91+
assert sql['url'] == url
92+
assert sql['database_version']
93+
94+
95+
def test_execute_bad_query():
96+
q = 'SELECT blarg'
97+
with testing.postgresql.Postgresql() as postgresql:
98+
url = postgresql.url()
99+
dsn = postgresql.dsn()
100+
conn = psycopg2.connect(dbname=dsn['database'],
101+
user=dsn['user'],
102+
password='',
103+
host=dsn['host'],
104+
port=dsn['port'])
105+
cur = conn.cursor()
106+
try:
107+
cur.execute(q)
108+
except Exception:
109+
pass
110+
111+
subsegment = xray_recorder.current_segment().subsegments[0]
112+
assert subsegment.name == 'execute'
113+
sql = subsegment.sql
114+
assert sql['database_type'] == 'PostgreSQL'
115+
assert sql['user'] == dsn['user']
116+
assert sql['url'] == url
117+
assert sql['database_version']
118+
119+
exception = subsegment.cause['exceptions'][0]
120+
assert exception.type == 'ProgrammingError'

tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ deps =
1919
# the sdk doesn't support earlier version of django
2020
django >= 1.10, <2.0
2121
pynamodb
22+
psycopg2
23+
testing.postgresql
2224

2325
# Python2 only deps
2426
py{27}: enum34

0 commit comments

Comments
 (0)