4
4
# | |_) | | |_| | | |> < / __/|__ _| | _ <| |___ ___) || |
5
5
# |____/|_|\__|_| |_/_/\_\_____| |_| |_| \_\_____|____/ |_|
6
6
7
- import warnings
8
- from time import sleep
9
- from typing import Any , Dict
7
+ import asyncio
8
+ import itertools
9
+ from typing import Any , Dict , Optional
10
10
from urllib .parse import urlparse
11
11
12
- import requests
12
+ from aiohttp import ClientSession
13
13
14
14
from .exceptions import BitrixError
15
15
@@ -21,7 +21,7 @@ class Bitrix24:
21
21
Provides an easy way to communicate with Bitrix24 portal over REST without OAuth.
22
22
"""
23
23
24
- def __init__ (self , domain : str , timeout : int = 60 , safe : bool = True ):
24
+ def __init__ (self , domain : str , timeout : int = 60 ):
25
25
"""
26
26
Create Bitrix24 API object.
27
27
@@ -32,9 +32,8 @@ def __init__(self, domain: str, timeout: int = 60, safe: bool = True):
32
32
"""
33
33
self .domain = self ._prepare_domain (domain )
34
34
self .timeout = timeout
35
- self .safe = safe
36
35
37
- def _prepare_domain (self , domain : str ):
36
+ def _prepare_domain (self , domain : str ) -> str :
38
37
"""Normalize user passed domain to a valid one."""
39
38
if not domain :
40
39
raise BitrixError ("Empty domain" )
@@ -43,7 +42,7 @@ def _prepare_domain(self, domain: str):
43
42
user_id , code = o .path .split ("/" )[2 :4 ]
44
43
return "{0}://{1}/rest/{2}/{3}" .format (o .scheme , o .netloc , user_id , code )
45
44
46
- def _prepare_params (self , params : Dict [str , Any ], prev = "" ):
45
+ def _prepare_params (self , params : Dict [str , Any ], prev = "" ) -> str :
47
46
"""
48
47
Transform list of parameters to a valid bitrix array.
49
48
@@ -81,26 +80,42 @@ def _prepare_params(self, params: Dict[str, Any], prev=""):
81
80
ret += "{0}={1}&" .format (key , value )
82
81
return ret
83
82
84
- def request (self , method , p ):
85
- url = "{0}/{1}.json" .format (self .domain , method )
86
- if method .rsplit ("." , 1 )[0 ] in ["add" , "update" , "delete" , "set" ]:
87
- r = requests .post (url , data = p , timeout = self .timeout , verify = self .safe ).json ()
88
- else :
89
- r = requests .get (url , params = p , timeout = self .timeout , verify = self .safe )
90
- try :
91
- r = r .json ()
92
- except requests .exceptions .JSONDecodeError :
93
- warnings .warn ("bitrix24: JSON decode error..." )
94
- if r .status_code == 403 :
95
- warnings .warn (
96
- f"bitrix24: Forbidden: { method } . "
97
- "Check your bitrix24 webhook settings. Returning None! "
98
- )
99
- return None
100
- elif r .ok :
101
- return r .content
102
-
103
- def callMethod (self , method : str , ** params ):
83
+ async def request (self , method : str , params : str = None ) -> Dict [str , Any ]:
84
+ async with ClientSession () as session :
85
+ async with session .get (
86
+ f"{ self .domain } /{ method } .json" , params = params , timeout = self .timeout
87
+ ) as resp :
88
+ if resp .status not in [200 , 201 ]:
89
+ raise BitrixError (f"HTTP error: { resp .status } " )
90
+ response = await resp .json ()
91
+ if "error" in response :
92
+ raise BitrixError (response ["error_description" ], response ["error" ])
93
+ return response
94
+
95
+ async def call (self , method : str , params : Dict [str , Any ] = {}, start : Optional [int ] = None ):
96
+ """Async call a REST method with specified parameters.
97
+
98
+ This method is a replacement for the callMethod method, which is synchronous.
99
+
100
+ Parameters
101
+ ----------
102
+ method (str): REST method name
103
+ params (dict): Optional arguments which will be converted to a POST request string
104
+ """
105
+ if start is not None :
106
+ params ["start" ] = start
107
+
108
+ payload = self ._prepare_params (params )
109
+ res = await self .request (method , payload )
110
+
111
+ if "next" in res and start is None :
112
+ tasks = [self .call (method , params , start = start ) for start in range (res ["total" ] // 50 )]
113
+ items = await asyncio .gather (* tasks )
114
+ result = list (itertools .chain (* items ))
115
+ return res ["result" ] + result
116
+ return res ["result" ]
117
+
118
+ def callMethod (self , method : str , params : Dict [str , Any ] = {}, ** kwargs ):
104
119
"""Call a REST method with specified parameters.
105
120
106
121
Parameters
@@ -112,32 +127,16 @@ def callMethod(self, method: str, **params):
112
127
-------
113
128
Returning the REST method response as an array, an object or a scalar
114
129
"""
115
- if not method or len (method .split ("." )) < 3 :
130
+ if not method or len (method .split ("." )) < 2 :
116
131
raise BitrixError ("Wrong method name" , 400 )
117
132
118
133
try :
119
- p = self ._prepare_params (params )
120
- r = self .request (method , p )
121
- if not r :
122
- return None
123
- except ValueError :
124
- if r ["error" ] not in "QUERY_LIMIT_EXCEEDED" :
125
- raise BitrixError (message = r ["error_description" ], code = r ["error" ])
126
- # Looks like we need to wait until expires limitation time by Bitrix24 API
127
- sleep (2 )
128
- return self .callMethod (method , ** params )
129
-
130
- if "error" in r :
131
- raise BitrixError (r )
132
- if "start" not in params :
133
- params ["start" ] = 0
134
- if "next" in r and r ["total" ] > params ["start" ]:
135
- params ["start" ] += 50
136
- data = self .callMethod (method , ** params )
137
- if isinstance (r ["result" ], dict ):
138
- result = r ["result" ].copy ()
139
- result .update (data )
140
- else :
141
- result = r ["result" ] + data
142
- return result
143
- return r ["result" ]
134
+ loop = asyncio .get_running_loop ()
135
+ except RuntimeError :
136
+ loop = asyncio .new_event_loop ()
137
+ asyncio .set_event_loop (loop )
138
+ result = loop .run_until_complete (self .call (method , params or kwargs ))
139
+ loop .close ()
140
+ else :
141
+ result = asyncio .ensure_future (self .call (method , params or kwargs ))
142
+ return result
0 commit comments