Client.make_call()   F
last analyzed

Complexity

Conditions 11

Size

Total Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
c 0
b 0
f 0
dl 0
loc 51
rs 3.6

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Client.make_call() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# pylint: disable=no-member
2
import json
3
import urllib
4
import signal
5
import sys
6
import time
7
import pycurl
8
9
POST_PARAMS = {}
10
MAX_ATTEMPTS = 3
11
12
13
class Client:
14
    def __init__(self, action, api_url, api_user, api_password, method):
15
        self.conn = None
16
        self.action = action
17
        self.keep_trying = 1
18
        self.api_url = api_url
19
        self.api_user = api_user
20
        self.api_password = api_password
21
        self.method = method
22
        self.buffer = ''
23
        signal.signal(signal.SIGINT, self.handle_ctrl_c)
24
25
    def handle_ctrl_c(self, signal, frame):
26
        sys.stderr.write('SIGINT receivied')
27
        sys.stderr.write('You pressed Ctrl+C?!')
28
        self.abort_session()
29
30
    def abort_session(self):
31
        self.keep_trying = 0
32
        self.conn.close()
33
34
    def setup_connection(self):
35
36
        if self.conn:
37
            self.conn.close()
38
        self.headers = ['Accept: application/json', 'Expect:', 'Connection: close']
39
        self.conn = pycurl.Curl()
40
        self.conn.setopt(pycurl.SSL_VERIFYHOST, False)
41
        self.conn.setopt(pycurl.SSL_VERIFYPEER, False)
42
        self.conn.setopt(pycurl.USERPWD, "%s:%s" % (self.api_user, self.api_password))
43
        self.conn.setopt(pycurl.URL, self.api_url)
44
        self.conn.setopt(pycurl.VERBOSE, 0)
45
        self.conn.setopt(pycurl.WRITEFUNCTION, self.on_receive)
46
        self.conn.setopt(pycurl.NOSIGNAL, 1)
47
        self.conn.setopt(pycurl.NOPROGRESS, 1)
48
        self.conn.setopt(pycurl.HTTPHEADER, self.headers)
49
        if self.method == 'post':
50
            self.conn.setopt(pycurl.POST, 1)
51
            self.conn.setopt(pycurl.POSTFIELDS, urllib.urlencode(POST_PARAMS))
52
        elif self.method == 'get':
53
            self.conn.setopt(pycurl.POST, 0)
54
55
    def on_receive(self, data):
56
        # self.action.logger.debug('Icinga2GetStatus: client on_receive, data: %s', data)
57
        # complete message received
58
        self.buffer += data
59
        # sys.stderr.write(data)
60
        try:
61
            event = json.loads(self.buffer)
62
            update_body = True
63
        except:
64
            # no json found
65
            update_body = False
66
67
        if update_body:
68
            if self.action is not None:
69
                # sys.stderr.write('Updating body\n')
70
                self.action.set_body(event)
71
            else:
72
                print event
73
74
    def __del__(self):
75
        self.conn.close()
76
77
    def make_call(self):
78
        backoff_network_error = 0.25
79
        backoff_http_error = 1
80
        backoff_rate_limit = 60
81
        attempt = 0
82
        while True and self.keep_trying == 1 and attempt < MAX_ATTEMPTS:
83
            attempt += 1
84
            self.setup_connection()
85
            try:
86
                self.conn.perform()
87
            except:
88
                # Network error, use linear back off up to 16 seconds
89
                if self.keep_trying == 0:
90
                    continue
91
                sys.stderr.write('Network error: %s' % self.conn.errstr())
92
                sys.stderr.write('Waiting %s seconds before trying again' % backoff_network_error)
93
                time.sleep(backoff_network_error)
94
                backoff_network_error = min(backoff_network_error + 1, 16)
95
                continue
96
            # HTTP Error
97
            sc = self.conn.getinfo(pycurl.HTTP_CODE)
98
            if sc == 200:
99
                # sys.stderr.write('HTTP request successful.')
100
                self.conn.close()
101
                break
102
            elif sc == 420:
103
                # Rate limit, use exponential back off starting with 1 minute then doubling
104
                sys.stderr.write('Rate limit, waiting %s seconds' % backoff_rate_limit)
105
                time.sleep(backoff_rate_limit)
106
                backoff_rate_limit *= 2
107
            elif sc == 401:
108
                # Authentication error
109
                sys.stderr.write('Authentication error, check user/password.')
110
                self.action.error = 1
111
                break
112
            elif sc == 404:
113
                # Authorization error
114
                sys.stderr.write('Object not found. Verify request and/or check permissions.')
115
                self.action.error = 2
116
                break
117
            elif sc == 400:
118
                # Authorization error
119
                sys.stderr.write('Bad request.')
120
                self.action.error = 3
121
                break
122
            else:
123
                # HTTP error, use exponential back off up to 320 seconds
124
                sys.stderr.write('HTTP error %s, %s' % (sc, self.conn.errstr()))
125
                sys.stderr.write('Waiting %s seconds' % backoff_http_error)
126
                time.sleep(backoff_http_error)
127
                backoff_http_error = min(backoff_http_error * 2, 10)
128