1
1
import os
2
2
import time
3
- import hmac
4
3
import json
5
- from hashlib import sha1
6
- import requests
4
+ import hmac
5
+ from typing import Union
6
+ from enum import Enum
7
7
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
12
11
13
12
from 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
+
15
25
16
26
class RecombeeClient :
17
27
"""
@@ -22,22 +32,19 @@ class RecombeeClient:
22
32
:param token: Secret token obtained from Recombee for signing requests
23
33
24
34
: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
25
37
"""
26
38
BATCH_MAX_SIZE = 10000
27
39
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 ):
29
41
self .database_id = database_id
30
42
self .token = token
31
43
self .protocol = protocol
32
44
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 )
39
46
40
- def send (self , request ) :
47
+ def send (self , request : Request ) -> Union [ dict , str , list ] :
41
48
"""
42
49
:param request: Request to be sent to Recombee recommender
43
50
"""
@@ -63,101 +70,119 @@ def send(self, request):
63
70
raise ApiTimeoutException (request )
64
71
65
72
@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' }
68
98
if additional_headers :
69
99
headers .update (additional_headers )
70
100
return headers
71
101
72
- def __put (self , request , uri , timeout ):
102
+ def __put (self , request : Request , uri : str , timeout : int ):
73
103
response = requests .put (uri ,
74
104
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' }),
76
106
timeout = timeout )
77
107
self .__check_errors (response , request )
78
108
return response .json ()
79
109
80
- def __get (self , request , uri , timeout ):
110
+ def __get (self , request : Request , uri : str , timeout : int ):
81
111
response = requests .get (uri ,
82
- headers = self .__get_http_headers (),
112
+ headers = self .__get_http_headers (),
83
113
timeout = timeout )
84
114
self .__check_errors (response , request )
85
115
return response .json ()
86
116
87
- def __post (self , request , uri , timeout ):
117
+ def __post (self , request : Request , uri : str , timeout : int ):
88
118
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 )
92
122
self .__check_errors (response , request )
93
123
return response .json ()
94
124
95
- def __delete (self , request , uri , timeout ):
125
+ def __delete (self , request : Request , uri : str , timeout : int ):
96
126
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 )
99
130
self .__check_errors (response , request )
100
131
return response .json ()
101
132
102
-
103
- def __check_errors (self , response , request ):
133
+ def __check_errors (self , response , request : Request ):
104
134
status_code = response .status_code
105
135
if status_code == 200 or status_code == 201 :
106
136
return
107
137
raise ResponseException (request , status_code , response .text )
108
138
109
139
@staticmethod
110
- def __get_list_chunks (l , n ) :
140
+ def __get_list_chunks (l : list , n : int ) -> list :
111
141
"""Yield successive n-sized chunks from l."""
112
142
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 ):
119
144
yield l [i :i + n ]
120
145
121
- def __send_multipart_batch (self , batch ) :
146
+ def __send_multipart_batch (self , batch : Batch ) -> list :
122
147
requests_parts = [rqs for rqs in self .__get_list_chunks (batch .requests , self .BATCH_MAX_SIZE )]
123
148
responses = [self .send (Batch (rqs )) for rqs in requests_parts ]
124
149
return sum (responses , [])
125
150
126
- def __process_request_uri (self , request ) :
151
+ def __process_request_uri (self , request : Request ) -> str :
127
152
uri = request .path
128
153
uri += self .__query_parameters_to_url (request )
129
154
return uri
130
155
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
131
164
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 ))
145
170
146
- # Sign request with HMAC, request URI must be exacly the same
171
+ # Sign request with HMAC, request URI must be exactly the same
147
172
# 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 :
149
174
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
153
178
return res
154
179
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 '?'
157
182
res += "hmac_timestamp=%s" % int (time .time ())
158
183
return res
159
184
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
162
187
sign = hmac .new (str .encode (self .token ), str .encode (url ), sha1 ).hexdigest ()
163
188
return sign
0 commit comments