Skip to content

Commit c09a712

Browse files
committed
singleton warning: method_is_called_from
1 parent b617a9c commit c09a712

File tree

5 files changed

+107
-37
lines changed

5 files changed

+107
-37
lines changed

configcatclient/configcatclient.py

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,13 @@
1414
import hashlib
1515
from collections import namedtuple
1616
import copy
17-
import inspect
17+
from .utils import method_is_called_from
1818

1919
log = logging.getLogger(sys.modules[__name__].__name__)
2020

2121
KeyValue = namedtuple('KeyValue', 'key value')
2222

2323

24-
def get_class_from_stack_frame(frame):
25-
args, _, _, value_dict = inspect.getargvalues(frame)
26-
# we check the first parameter for the frame function is
27-
# named 'self' or 'cls'
28-
if len(args):
29-
if args[0] == 'self':
30-
# in that case, 'self' will be referenced in value_dict
31-
instance = value_dict.get(args[0], None)
32-
if instance:
33-
# return its class
34-
return getattr(instance, '__class__', None)
35-
if args[0] == 'cls':
36-
# return the class
37-
return value_dict.get(args[0], None)
38-
39-
# return None otherwise
40-
return None
41-
42-
43-
def guard_call(allowed_classes, level=1):
44-
"""
45-
Checks if the current method is being called from a method of certain classes.
46-
"""
47-
stack_info = inspect.stack()[level + 1]
48-
frame = stack_info[0]
49-
calling_class = get_class_from_stack_frame(frame)
50-
if calling_class:
51-
for klass in allowed_classes:
52-
if issubclass(calling_class, klass):
53-
return True
54-
return False
55-
56-
5724
class ConfigCatClient(object):
5825
_instances = {}
5926

@@ -85,7 +52,7 @@ def __init__(self,
8552
sdk_key,
8653
options=ConfigCatOptions()):
8754

88-
if not guard_call([ConfigCatClient]):
55+
if not method_is_called_from(ConfigCatClient.get):
8956
log.warning('ConfigCatClient.__init__() is deprecated. '
9057
'Create the ConfigCat Client as a Singleton object with `ConfigCatClient.get()` instead')
9158

configcatclient/utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import sys
2+
import inspect
3+
from qualname import qualname
4+
5+
6+
def get_class_from_method(method):
7+
method_class = sys.modules.get(method.__module__)
8+
if method_class is None:
9+
return None
10+
for name in qualname(method).split('.')[:-1]:
11+
method_class = getattr(method_class, name)
12+
if not inspect.isclass(method_class):
13+
return None
14+
return method_class
15+
16+
17+
def get_class_from_stack_frame(frame):
18+
args, _, _, value_dict = inspect.getargvalues(frame)
19+
# we check the first parameter for the frame function is
20+
# named 'self' or 'cls'
21+
if len(args):
22+
if args[0] == 'self':
23+
# in that case, 'self' will be referenced in value_dict
24+
instance = value_dict.get(args[0], None)
25+
if instance:
26+
# return its class
27+
return getattr(instance, '__class__', None)
28+
if args[0] == 'cls':
29+
# return the class
30+
return value_dict.get(args[0], None)
31+
32+
# return None otherwise
33+
return None
34+
35+
36+
def method_is_called_from(method, level=1):
37+
"""
38+
Checks if the current method is being called from a certain method.
39+
"""
40+
stack_info = inspect.stack()[level + 1]
41+
frame = stack_info[0]
42+
calling_method_name = frame.f_code.co_name
43+
expected_method_name = method.__name__
44+
if calling_method_name != expected_method_name:
45+
return False
46+
47+
calling_class = get_class_from_stack_frame(frame)
48+
expected_class = get_class_from_method(method)
49+
if calling_class == expected_class:
50+
return True
51+
return False

configcatclienttests/test_configcatclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_ensure_singleton_per_sdk_key(self):
2828

2929
def test_without_sdk_key(self):
3030
try:
31-
ConfigCatClient.get(None)
31+
ConfigCatClient(None)
3232
self.fail('Expected ConfigCatClientException')
3333
except ConfigCatClientException:
3434
pass

configcatclienttests/test_utils.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import logging
2+
import unittest
3+
4+
from configcatclient.utils import method_is_called_from
5+
6+
logging.basicConfig(level=logging.INFO)
7+
8+
9+
def no_operation():
10+
pass
11+
12+
13+
def test_method_is_called_from():
14+
pass
15+
16+
17+
class OtherClass(object):
18+
def no_operation(self):
19+
pass
20+
21+
def test_method_is_called_from(self):
22+
pass
23+
24+
25+
class UtilsTests(unittest.TestCase):
26+
def no_operation(self):
27+
pass
28+
29+
def test_method_is_called_from(self):
30+
class TestClass(object):
31+
@classmethod
32+
def class_method(cls, method):
33+
return method_is_called_from(method)
34+
35+
def object_method(self, method):
36+
return method_is_called_from(method)
37+
38+
self.assertTrue(TestClass.class_method(UtilsTests.test_method_is_called_from))
39+
self.assertTrue(TestClass().object_method(UtilsTests.test_method_is_called_from))
40+
41+
self.assertFalse(TestClass.class_method(UtilsTests.no_operation))
42+
self.assertFalse(TestClass().object_method(UtilsTests.no_operation))
43+
44+
self.assertFalse(TestClass.class_method(no_operation))
45+
self.assertFalse(TestClass().object_method(test_method_is_called_from))
46+
self.assertFalse(TestClass.class_method(OtherClass.no_operation))
47+
self.assertFalse(TestClass().object_method(OtherClass.test_method_is_called_from))
48+
49+
50+
if __name__ == '__main__':
51+
unittest.main()

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
requests>=2.17.3
22
semver>=2.8.1
3-
enum-compat>=0.0.3
3+
enum-compat>=0.0.3
4+
qualname>=0.1.0

0 commit comments

Comments
 (0)