Skip to content

Commit 17b033e

Browse files
authored
Merge pull request #396 from Dilski/master
add support for psycopg3
2 parents 39303a5 + bb2cfd7 commit 17b033e

File tree

7 files changed

+188
-0
lines changed

7 files changed

+188
-0
lines changed

aws_xray_sdk/core/patcher.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
'pymongo',
2424
'pymysql',
2525
'psycopg2',
26+
'psycopg',
2627
'pg8000',
2728
'sqlalchemy_core',
2829
'httpx',
@@ -38,6 +39,7 @@
3839
'pymongo',
3940
'pymysql',
4041
'psycopg2',
42+
'psycopg',
4143
'pg8000',
4244
'sqlalchemy_core',
4345
'httpx',

aws_xray_sdk/ext/psycopg/__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/psycopg/patch.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import wrapt
2+
from operator import methodcaller
3+
4+
from aws_xray_sdk.ext.dbapi2 import XRayTracedConn
5+
6+
7+
def patch():
8+
wrapt.wrap_function_wrapper(
9+
'psycopg',
10+
'connect',
11+
_xray_traced_connect
12+
)
13+
14+
wrapt.wrap_function_wrapper(
15+
'psycopg_pool.pool',
16+
'ConnectionPool._connect',
17+
_xray_traced_connect
18+
)
19+
20+
21+
def _xray_traced_connect(wrapped, instance, args, kwargs):
22+
conn = wrapped(*args, **kwargs)
23+
parameterized_dsn = {c[0]: c[-1] for c in map(methodcaller('split', '='), conn.info.dsn.split(' '))}
24+
meta = {
25+
'database_type': 'PostgreSQL',
26+
'url': 'postgresql://{}@{}:{}/{}'.format(
27+
parameterized_dsn.get('user', 'unknown'),
28+
parameterized_dsn.get('host', 'unknown'),
29+
parameterized_dsn.get('port', 'unknown'),
30+
parameterized_dsn.get('dbname', 'unknown'),
31+
),
32+
'user': parameterized_dsn.get('user', 'unknown'),
33+
'database_version': str(conn.info.server_version),
34+
'driver_version': 'Psycopg 3'
35+
}
36+
37+
return XRayTracedConn(conn, meta)

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Currently supported web frameworks and libraries:
2929
* mysql-connector
3030
* pg8000
3131
* psycopg2
32+
* psycopg (psycopg3)
3233
* pymongo
3334
* pymysql
3435
* pynamodb

tests/ext/psycopg/__init__.py

Whitespace-only changes.

tests/ext/psycopg/test_psycopg.py

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

tox.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ envlist =
3030

3131
py{37,38,39,310,311,312}-ext-psycopg2
3232

33+
py{37,38,39,310,311}-ext-psycopg
34+
3335
py{37,38,39,310,311,312}-ext-pymysql
3436

3537
py{37,38,39,310,311,312}-ext-pynamodb
@@ -99,6 +101,10 @@ deps =
99101
ext-psycopg2: psycopg2
100102
ext-psycopg2: testing.postgresql
101103

104+
ext-psycopg: psycopg
105+
ext-psycopg: psycopg[pool]
106+
ext-psycopg: testing.postgresql
107+
102108
ext-pg8000: pg8000 <= 1.20.0
103109
ext-pg8000: testing.postgresql
104110

@@ -136,6 +142,8 @@ commands =
136142
ext-pg8000: coverage run --append --source aws_xray_sdk -m pytest tests/ext/pg8000 {posargs}
137143

138144
ext-psycopg2: coverage run --append --source aws_xray_sdk -m pytest tests/ext/psycopg2 {posargs}
145+
146+
ext-psycopg: coverage run --append --source aws_xray_sdk -m pytest tests/ext/psycopg {posargs}
139147

140148
ext-pymysql: coverage run --append --source aws_xray_sdk -m pytest tests/ext/pymysql {posargs}
141149

0 commit comments

Comments
 (0)