Skip to content

Commit f3f6f83

Browse files
committed
Avoid automatically patching classmethods
1 parent 6459ca0 commit f3f6f83

File tree

4 files changed

+33
-24
lines changed

4 files changed

+33
-24
lines changed

aws_xray_sdk/core/patcher.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import re
77
import sys
88
import wrapt
9-
from .utils.compat import PY2, is_instance_method
9+
10+
from .utils.compat import PY2, is_classmethod, is_instance_method
1011

1112
log = logging.getLogger(__name__)
1213

@@ -136,7 +137,13 @@ def _patch_class(module, cls):
136137
for member_name, member in inspect.getmembers(cls, inspect.ismethod):
137138
if member.__module__ == module.__name__:
138139
# Only patch methods of the class defined in the module, ignore other modules
139-
_patch_func(cls, member_name, member)
140+
if is_classmethod(member):
141+
# classmethods are internally generated through descriptors. The classmethod
142+
# decorator must be the last applied, so we cannot apply another one on top
143+
log.warning('Cannot automatically patch classmethod %s.%s, '
144+
'please apply decorator manually', cls.__name__, member_name)
145+
else:
146+
_patch_func(cls, member_name, member)
140147

141148
for member_name, member in inspect.getmembers(cls, inspect.isfunction):
142149
if member.__module__ == module.__name__:

aws_xray_sdk/core/utils/compat.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
string_types = str
1414

1515

16+
def is_classmethod(func):
17+
return getattr(func, '__self__', None) is not None
18+
19+
1620
def is_instance_method(parent_class, func_name, func):
1721
try:
1822
func_from_dict = parent_class.__dict__[func_name]
@@ -24,4 +28,4 @@ def is_instance_method(parent_class, func_name, func):
2428
else:
2529
return True
2630

27-
return getattr(func, '__self__', None) is None and not isinstance(func_from_dict, staticmethod)
31+
return not is_classmethod(func) and not isinstance(func_from_dict, staticmethod)

tests/mock_module/mock_submodule/mock_subfile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def mock_method(self):
1919

2020
@classmethod
2121
def mock_classmethod(cls):
22+
# Should not be automatically patched
2223
pass
2324

2425
@staticmethod

tests/test_patcher.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,15 @@ def test_external_module():
105105

106106
_call_all_mock_functions()
107107

108-
assert len(xray_recorder.current_segment().subsegments) == 9
108+
assert len(xray_recorder.current_segment().subsegments) == 8
109109
assert xray_recorder.current_segment().subsegments[0].name == 'mock_subinit'
110110
assert xray_recorder.current_segment().subsegments[1].name == 'mock_subfunc'
111111
assert xray_recorder.current_segment().subsegments[2].name == 'mock_no_doublepatch' # Should appear only once
112-
assert xray_recorder.current_segment().subsegments[3].name == 'mock_classmethod'
113-
assert xray_recorder.current_segment().subsegments[4].name == 'mock_staticmethod'
114-
assert xray_recorder.current_segment().subsegments[5].name == 'MockClass.__init__'
115-
assert xray_recorder.current_segment().subsegments[6].name == 'mock_method'
116-
assert xray_recorder.current_segment().subsegments[7].name == 'MockSubclass.__init__'
117-
assert xray_recorder.current_segment().subsegments[8].name == 'mock_submethod'
112+
assert xray_recorder.current_segment().subsegments[3].name == 'mock_staticmethod'
113+
assert xray_recorder.current_segment().subsegments[4].name == 'MockClass.__init__'
114+
assert xray_recorder.current_segment().subsegments[5].name == 'mock_method'
115+
assert xray_recorder.current_segment().subsegments[6].name == 'MockSubclass.__init__'
116+
assert xray_recorder.current_segment().subsegments[7].name == 'mock_submethod'
118117

119118

120119
def test_external_submodules_full():
@@ -126,18 +125,17 @@ def test_external_submodules_full():
126125

127126
_call_all_mock_functions()
128127

129-
assert len(xray_recorder.current_segment().subsegments) == 11
128+
assert len(xray_recorder.current_segment().subsegments) == 10
130129
assert xray_recorder.current_segment().subsegments[0].name == 'mock_init'
131130
assert xray_recorder.current_segment().subsegments[1].name == 'mock_subinit'
132131
assert xray_recorder.current_segment().subsegments[2].name == 'mock_func'
133132
assert xray_recorder.current_segment().subsegments[3].name == 'mock_subfunc'
134133
assert xray_recorder.current_segment().subsegments[4].name == 'mock_no_doublepatch'
135-
assert xray_recorder.current_segment().subsegments[5].name == 'mock_classmethod'
136-
assert xray_recorder.current_segment().subsegments[6].name == 'mock_staticmethod'
137-
assert xray_recorder.current_segment().subsegments[7].name == 'MockClass.__init__'
138-
assert xray_recorder.current_segment().subsegments[8].name == 'mock_method'
139-
assert xray_recorder.current_segment().subsegments[9].name == 'MockSubclass.__init__'
140-
assert xray_recorder.current_segment().subsegments[10].name == 'mock_submethod'
134+
assert xray_recorder.current_segment().subsegments[5].name == 'mock_staticmethod'
135+
assert xray_recorder.current_segment().subsegments[6].name == 'MockClass.__init__'
136+
assert xray_recorder.current_segment().subsegments[7].name == 'mock_method'
137+
assert xray_recorder.current_segment().subsegments[8].name == 'MockSubclass.__init__'
138+
assert xray_recorder.current_segment().subsegments[9].name == 'mock_submethod'
141139

142140

143141
def test_external_submodules_ignores_file():
@@ -149,17 +147,16 @@ def test_external_submodules_ignores_file():
149147

150148
_call_all_mock_functions()
151149

152-
assert len(xray_recorder.current_segment().subsegments) == 10
150+
assert len(xray_recorder.current_segment().subsegments) == 9
153151
assert xray_recorder.current_segment().subsegments[0].name == 'mock_init'
154152
assert xray_recorder.current_segment().subsegments[1].name == 'mock_subinit'
155153
assert xray_recorder.current_segment().subsegments[2].name == 'mock_subfunc'
156154
assert xray_recorder.current_segment().subsegments[3].name == 'mock_no_doublepatch'
157-
assert xray_recorder.current_segment().subsegments[4].name == 'mock_classmethod'
158-
assert xray_recorder.current_segment().subsegments[5].name == 'mock_staticmethod'
159-
assert xray_recorder.current_segment().subsegments[6].name == 'MockClass.__init__'
160-
assert xray_recorder.current_segment().subsegments[7].name == 'mock_method'
161-
assert xray_recorder.current_segment().subsegments[8].name == 'MockSubclass.__init__'
162-
assert xray_recorder.current_segment().subsegments[9].name == 'mock_submethod'
155+
assert xray_recorder.current_segment().subsegments[4].name == 'mock_staticmethod'
156+
assert xray_recorder.current_segment().subsegments[5].name == 'MockClass.__init__'
157+
assert xray_recorder.current_segment().subsegments[6].name == 'mock_method'
158+
assert xray_recorder.current_segment().subsegments[7].name == 'MockSubclass.__init__'
159+
assert xray_recorder.current_segment().subsegments[8].name == 'mock_submethod'
163160

164161

165162
def test_external_submodules_ignores_module():

0 commit comments

Comments
 (0)