11import os
22import time
3- import hmac
43import json
5- from hashlib import sha1
6- import requests
4+ import hmac
5+ from typing import Union
6+ from enum import Enum
77
8- try :
9- from urllib import quote
10- except ImportError :
11- from urllib .parse import quote
8+ import requests
9+ from hashlib import sha1
10+ from urllib .parse import quote
1211
1312from recombee_api_client .exceptions import ApiTimeoutException , ResponseException
14- from recombee_api_client .api_requests import Batch
13+ from recombee_api_client .api_requests import Batch , Request
14+
15+
16+ class Region (Enum ):
17+ """
18+ Region of the Recombee cluster
19+ """
20+ AP_SE = 1
21+ CA_EAST = 2
22+ EU_WEST = 3
23+ US_WEST = 4
24+
1525
1626class RecombeeClient :
1727 """
@@ -22,22 +32,19 @@ class RecombeeClient:
2232 :param token: Secret token obtained from Recombee for signing requests
2333
2434 :param protocol: Default protocol for sending requests. Possible values: 'http', 'https'.
35+
36+ :param region: region of the Recombee cluster where the database is located
2537 """
2638 BATCH_MAX_SIZE = 10000
2739
28- def __init__ (self , database_id , token , protocol = 'https' , options = {} ):
40+ def __init__ (self , database_id : str , token : str , protocol : str = 'https' , options : dict = None , region : Region = None ):
2941 self .database_id = database_id
3042 self .token = token
3143 self .protocol = protocol
3244
33- self .base_uri = os .environ .get ('RAPI_URI' )
34- if self .base_uri is None :
35- self .base_uri = options .get ('base_uri' )
36- if self .base_uri is None :
37- self .base_uri = 'rapi.recombee.com'
38-
45+ self .base_uri = self .__get_base_uri (options = options or {}, region = region )
3946
40- def send (self , request ) :
47+ def send (self , request : Request ) -> Union [ dict , str , list ] :
4148 """
4249 :param request: Request to be sent to Recombee recommender
4350 """
@@ -63,101 +70,119 @@ def send(self, request):
6370 raise ApiTimeoutException (request )
6471
6572 @staticmethod
66- def __get_http_headers (additional_headers = None ):
67- headers = {'User-Agent' : 'recombee-python-api-client/3.2.0' }
73+ def __get_regional_base_uri (region : Region ) -> str :
74+ uri = {
75+ Region .AP_SE : 'rapi-ap-se.recombee.com' ,
76+ Region .CA_EAST : 'rapi-ca-east.recombee.com' ,
77+ Region .EU_WEST : 'rapi-eu-west.recombee.com' ,
78+ Region .US_WEST : 'rapi-us-west.recombee.com'
79+ }.get (region )
80+
81+ if uri is None :
82+ raise ValueError ('Unknown region given' )
83+ return uri
84+
85+ @staticmethod
86+ def __get_base_uri (options : dict , region : str ) -> str :
87+ base_uri = os .environ .get ('RAPI_URI' ) or options .get ('base_uri' )
88+ if region is not None :
89+ if base_uri :
90+ raise ValueError ('base_uri and region cannot be specified at the same time' )
91+ base_uri = RecombeeClient .__get_regional_base_uri (region )
92+
93+ return base_uri or 'rapi.recombee.com'
94+
95+ @staticmethod
96+ def __get_http_headers (additional_headers : dict = None ) -> dict :
97+ headers = {'User-Agent' : 'recombee-python-api-client/4.0.0' }
6898 if additional_headers :
6999 headers .update (additional_headers )
70100 return headers
71101
72- def __put (self , request , uri , timeout ):
102+ def __put (self , request : Request , uri : str , timeout : int ):
73103 response = requests .put (uri ,
74104 data = json .dumps (request .get_body_parameters ()),
75- headers = self .__get_http_headers ({'Content-Type' : 'application/json' }),
105+ headers = self .__get_http_headers ({'Content-Type' : 'application/json' }),
76106 timeout = timeout )
77107 self .__check_errors (response , request )
78108 return response .json ()
79109
80- def __get (self , request , uri , timeout ):
110+ def __get (self , request : Request , uri : str , timeout : int ):
81111 response = requests .get (uri ,
82- headers = self .__get_http_headers (),
112+ headers = self .__get_http_headers (),
83113 timeout = timeout )
84114 self .__check_errors (response , request )
85115 return response .json ()
86116
87- def __post (self , request , uri , timeout ):
117+ def __post (self , request : Request , uri : str , timeout : int ):
88118 response = requests .post (uri ,
89- data = json .dumps (request .get_body_parameters ()),
90- headers = self .__get_http_headers ({'Content-Type' : 'application/json' }),
91- timeout = timeout )
119+ data = json .dumps (request .get_body_parameters ()),
120+ headers = self .__get_http_headers ({'Content-Type' : 'application/json' }),
121+ timeout = timeout )
92122 self .__check_errors (response , request )
93123 return response .json ()
94124
95- def __delete (self , request , uri , timeout ):
125+ def __delete (self , request : Request , uri : str , timeout : int ):
96126 response = requests .delete (uri ,
97- headers = self .__get_http_headers (),
98- timeout = timeout )
127+ data = json .dumps (request .get_body_parameters ()),
128+ headers = self .__get_http_headers ({'Content-Type' : 'application/json' }),
129+ timeout = timeout )
99130 self .__check_errors (response , request )
100131 return response .json ()
101132
102-
103- def __check_errors (self , response , request ):
133+ def __check_errors (self , response , request : Request ):
104134 status_code = response .status_code
105135 if status_code == 200 or status_code == 201 :
106136 return
107137 raise ResponseException (request , status_code , response .text )
108138
109139 @staticmethod
110- def __get_list_chunks (l , n ) :
140+ def __get_list_chunks (l : list , n : int ) -> list :
111141 """Yield successive n-sized chunks from l."""
112142
113- try : #Python 2/3 compatibility
114- xrange
115- except NameError :
116- xrange = range
117-
118- for i in xrange (0 , len (l ), n ):
143+ for i in range (0 , len (l ), n ):
119144 yield l [i :i + n ]
120145
121- def __send_multipart_batch (self , batch ) :
146+ def __send_multipart_batch (self , batch : Batch ) -> list :
122147 requests_parts = [rqs for rqs in self .__get_list_chunks (batch .requests , self .BATCH_MAX_SIZE )]
123148 responses = [self .send (Batch (rqs )) for rqs in requests_parts ]
124149 return sum (responses , [])
125150
126- def __process_request_uri (self , request ) :
151+ def __process_request_uri (self , request : Request ) -> str :
127152 uri = request .path
128153 uri += self .__query_parameters_to_url (request )
129154 return uri
130155
156+ def __query_parameters_to_url (self , request : Request ) -> str :
157+ ps = ''
158+ query_params = request .get_query_parameters ()
159+ for name in query_params :
160+ val = query_params [name ]
161+ ps += '&' if ps .find ('?' ) != - 1 else '?'
162+ ps += "%s=%s" % (name , self .__format_query_parameter_value (val ))
163+ return ps
131164
132- def __query_parameters_to_url (self , request ):
133- ps = ''
134- query_params = request .get_query_parameters ()
135- for name in query_params :
136- val = query_params [name ]
137- ps += '&' if ps .find ('?' )!= - 1 else '?'
138- ps += "%s=%s" % (name , self .__format_query_parameter_value (val ))
139- return ps
140-
141- def __format_query_parameter_value (self , value ):
142- if isinstance (value , list ):
143- return ',' .join ([quote (str (v )) for v in value ])
144- return quote (str (value ))
165+ @staticmethod
166+ def __format_query_parameter_value (value ) -> str :
167+ if isinstance (value , list ):
168+ return ',' .join ([quote (str (v )) for v in value ])
169+ return quote (str (value ))
145170
146- # Sign request with HMAC, request URI must be exacly the same
171+ # Sign request with HMAC, request URI must be exactly the same
147172 # We have 30s to complete request with this token
148- def __sign_url (self , req_part ) :
173+ def __sign_url (self , req_part : str ) -> str :
149174 uri = '/' + self .database_id + req_part
150- time = self .__hmac_time (uri )
151- sign = self .__hmac_sign (uri , time )
152- res = uri + time + '&hmac_sign=' + sign
175+ time_part = self .__hmac_time (uri )
176+ sign = self .__hmac_sign (uri , time_part )
177+ res = uri + time_part + '&hmac_sign=' + sign
153178 return res
154179
155- def __hmac_time (self , uri ) :
156- res = '&' if uri .find ('?' )!= - 1 else '?'
180+ def __hmac_time (self , uri : str ) -> str :
181+ res = '&' if uri .find ('?' ) != - 1 else '?'
157182 res += "hmac_timestamp=%s" % int (time .time ())
158183 return res
159184
160- def __hmac_sign (self , uri , time ) :
161- url = uri + time
185+ def __hmac_sign (self , uri : str , time_part : str ) -> str :
186+ url = uri + time_part
162187 sign = hmac .new (str .encode (self .token ), str .encode (url ), sha1 ).hexdigest ()
163188 return sign
0 commit comments