-
-
Notifications
You must be signed in to change notification settings - Fork 28
Expand file tree
/
Copy pathbitrix24.py
More file actions
168 lines (130 loc) · 5.7 KB
/
bitrix24.py
File metadata and controls
168 lines (130 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# -*- coding: utf-8 -*-
"""
Bitrix24
~~~~~~~~~~~~
This module implements the Bitrix24 REST API.
:copyright: (c) 2019 by Akop Kesheshyan.
"""
import requests
from time import sleep
from urllib.parse import urlparse
from .exceptions import BitrixError
class Bitrix24(object):
"""A user-created :class:`Bitrix24 <Bitrix24>` object.
Used to sent to the server.
:param domain: REST call domain, including account name, user ID and secret code.
:param timeout: (Optional) waiting for a response after a given number of seconds.
Usage::
>>> from bitrix24 import Bitrix24
>>> bx24 = Bitrix24('https://example.bitrix24.com/rest/1/33olqeits4avuyqu')
>>> bx24.callMethod('crm.product.list')
"""
def __init__(self, domain, timeout=60):
"""Create Bitrix24 API object
:param domain: str Bitrix24 webhook domain
:param timeout: int Timeout for API request in seconds
"""
self.domain = self._prepare_domain(domain)
self.timeout = timeout
def _prepare_domain(self, domain):
"""Normalize user passed domain to a valid one."""
if domain == '' or not isinstance(domain, str):
raise Exception('Empty domain')
o = urlparse(domain)
user_id, code = o.path.split('/')[2:4]
return "{0}://{1}/rest/{2}/{3}".format(o.scheme, o.netloc, user_id, code)
def _prepare_params(self, params, prev=''):
"""Transforms list of params to a valid bitrix array."""
ret = ''
if isinstance(params, dict):
for key, value in params.items():
if isinstance(value, dict):
if prev:
key = "{0}[{1}]".format(prev, key)
ret += self._prepare_params(value, key)
elif (isinstance(value, list) or isinstance(value, tuple)) and len(value) > 0:
for offset, val in enumerate(value):
if isinstance(val, dict):
ret += self._prepare_params(
val, "{0}[{1}][{2}]".format(prev, key, offset))
else:
if prev:
ret += "{0}[{1}][{2}]={3}&".format(
prev, key, offset, val)
else:
ret += "{0}[{1}]={2}&".format(key, offset, val)
else:
if prev:
ret += "{0}[{1}]={2}&".format(prev, key, value)
else:
ret += "{0}={1}&".format(key, value)
return ret
def _call_method(self, method, **params):
"""Calls a REST method with specified parameters."""
try:
url = '{0}/{1}.json'.format(self.domain, method)
p = self._prepare_params(params)
if method.rsplit('.', 1)[0] in ['add', 'update', 'delete', 'set']:
r = requests.post(url, data=p, timeout=self.timeout).json()
else:
r = requests.get(url, params=p, timeout=self.timeout).json()
except ValueError:
if r['error'] not in 'QUERY_LIMIT_EXCEEDED':
raise BitrixError(r)
# Looks like we need to wait until expires limitation time by Bitrix24 API
sleep(2)
return self._call_method(method, **params)
if 'error' in r:
raise BitrixError(r)
return r
def callMethodIter(self, method, **params):
"""Calls a REST method with specified parameters.
:param url: REST method name.
:param \*\*params: Optional arguments which will be converted to a POST request string.
:return: Returning the REST method response generator
"""
if 'start' not in params:
params['start'] = 0
r = self._call_method(method, **params)
if 'next' in r and r['total'] > params['start']:
params['start'] += 50
yield from self.callMethodIter(method, **params)
yield r['result']
def callMethod(self, method, **params):
"""Calls a REST method with specified parameters.
:param url: REST method name.
:param \*\*params: Optional arguments which will be converted to a POST request string.
:return: Returning the REST method response as list, dict or scalar
"""
result_dict = {}
result_list = []
for r in self.callMethodIter(method, **params):
if (isinstance(r, dict)):
result_dict.update(r)
elif (isinstance(r, list)):
result_list.extend(r)
return result_dict or result_list or r
def callMethodList(self, method, id=0, key=None, **params):
"""Calls a REST method with specified parameters and fast fetch list of all records.
:param url: REST method name.
:param id: id of start record.
:param key: Result access key, used if method result is a dict, not a list.
:param \*\*params: Optional arguments which will be converted to a POST request string.
:return: Returning the REST method response list of records
"""
params['start'] = -1
params['order'] = {'ID': 'asc'}
if 'filter' not in params:
params['filter'] = {}
result = []
while True:
params['filter'].update({'>ID': id})
r = self._call_method(method, **params)['result']
if isinstance(r, dict):
r = r.get(key, [])
if r:
result.extend(r)
id = r[-1]['id']
else:
break
return result