44# | |_) | | |_| | | |> < / __/|__ _| | _ <| |___ ___) || |
55# |____/|_|\__|_| |_/_/\_\_____| |_| |_| \_\_____|____/ |_|
66
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
1010from urllib .parse import urlparse
1111
12- import requests
12+ from aiohttp import ClientSession
1313
1414from .exceptions import BitrixError
1515
@@ -21,7 +21,7 @@ class Bitrix24:
2121 Provides an easy way to communicate with Bitrix24 portal over REST without OAuth.
2222 """
2323
24- def __init__ (self , domain : str , timeout : int = 60 , safe : bool = True ):
24+ def __init__ (self , domain : str , timeout : int = 60 ):
2525 """
2626 Create Bitrix24 API object.
2727
@@ -32,9 +32,8 @@ def __init__(self, domain: str, timeout: int = 60, safe: bool = True):
3232 """
3333 self .domain = self ._prepare_domain (domain )
3434 self .timeout = timeout
35- self .safe = safe
3635
37- def _prepare_domain (self , domain : str ):
36+ def _prepare_domain (self , domain : str ) -> str :
3837 """Normalize user passed domain to a valid one."""
3938 if not domain :
4039 raise BitrixError ("Empty domain" )
@@ -43,7 +42,7 @@ def _prepare_domain(self, domain: str):
4342 user_id , code = o .path .split ("/" )[2 :4 ]
4443 return "{0}://{1}/rest/{2}/{3}" .format (o .scheme , o .netloc , user_id , code )
4544
46- def _prepare_params (self , params : Dict [str , Any ], prev = "" ):
45+ def _prepare_params (self , params : Dict [str , Any ], prev = "" ) -> str :
4746 """
4847 Transform list of parameters to a valid bitrix array.
4948
@@ -81,26 +80,42 @@ def _prepare_params(self, params: Dict[str, Any], prev=""):
8180 ret += "{0}={1}&" .format (key , value )
8281 return ret
8382
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 ):
104119 """Call a REST method with specified parameters.
105120
106121 Parameters
@@ -112,32 +127,16 @@ def callMethod(self, method: str, **params):
112127 -------
113128 Returning the REST method response as an array, an object or a scalar
114129 """
115- if not method or len (method .split ("." )) < 3 :
130+ if not method or len (method .split ("." )) < 2 :
116131 raise BitrixError ("Wrong method name" , 400 )
117132
118133 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