Skip to content

Commit 637096a

Browse files
committed
Python client for Recombee API
0 parents  commit 637096a

File tree

130 files changed

+4044
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+4044
-0
lines changed

README.rst

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
*****************
2+
RecombeeApiClient
3+
*****************
4+
5+
A Python client for easy use of the `Recombee <https://www.recombee.com/>`_ recommendation API.
6+
7+
If you don't have an account at Recombee yet, you can create a free account `here <https://www.recombee.com/>`_.
8+
9+
Documentation of the API can be found at `docs.recombee.com <https://docs.recombee.com/)>`_.
10+
11+
=============
12+
Installation
13+
=============
14+
15+
Install the client with pip:
16+
17+
.. code-block:: bash
18+
19+
$ pip install recombee-api-client
20+
21+
========
22+
Examples
23+
========
24+
25+
-------------
26+
Basic example
27+
-------------
28+
29+
.. code-block:: python
30+
31+
from recombee_api_client.api_client import RecombeeClient
32+
from recombee_api_client.exceptions import APIException
33+
from recombee_api_client.api_requests import AddUser, AddItem, AddPurchase, UserBasedRecommendation, Batch
34+
import random
35+
36+
# Prepare some items and users
37+
NUM = 100
38+
my_users = ["user-%s" % i for i in range(NUM) ]
39+
my_items = ["item-%s" % i for i in range(NUM) ]
40+
41+
#Generate some random purchases of items by users
42+
PROBABILITY_PURCHASED = 0.1
43+
my_purchases = []
44+
for user_id in my_users:
45+
p = [item_id for item_id in my_items if random.random() < PROBABILITY_PURCHASED]
46+
for item_id in p:
47+
my_purchases.append({'userId': user_id, 'itemId': item_id})
48+
49+
# Use Recombee recommender
50+
client = RecombeeClient('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
51+
52+
try:
53+
# Send the data to Recombee, use Batch for faster processing
54+
print('Send users')
55+
client.send(Batch([AddUser(user_id) for user_id in my_users]))
56+
print('Send items')
57+
client.send(Batch([AddItem(item_id) for item_id in my_items]))
58+
print('Send purchases')
59+
client.send(Batch([AddPurchase(p['userId'], p['itemId']) for p in my_purchases]))
60+
61+
# Get recommendations for user 'user-25'
62+
print('Recommend for a user')
63+
recommended = client.send(UserBasedRecommendation('user-25', 5, {'rotationRate': 0}))
64+
print("Recommended items: %s" % recommended)
65+
except APIException as e:
66+
print(e)
67+
68+
---------------------
69+
Using property values
70+
---------------------
71+
72+
.. code-block:: python
73+
74+
75+
from recombee_api_client.api_client import RecombeeClient
76+
from recombee_api_client.api_requests import AddItemProperty, SetItemValues, AddPurchase
77+
from recombee_api_client.api_requests import ItemBasedRecommendation, Batch, ResetDatabase
78+
import random
79+
80+
NUM = 100
81+
PROBABILITY_PURCHASED = 0.1
82+
83+
client = RecombeeClient('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
84+
85+
#Clear the entire database
86+
client.send(ResetDatabase())
87+
88+
# We will use computers as items in this example
89+
# Computers have three properties
90+
# - price (floating point number)
91+
# - number of processor cores (integer number)
92+
# - description (string)
93+
94+
# Add properties of items
95+
client.send(AddItemProperty('price', 'double'))
96+
client.send(AddItemProperty('num-cores', 'int'))
97+
client.send(AddItemProperty('description', 'string'))
98+
99+
# Prepare requests for setting a catalog of computers
100+
requests = [SetItemValues(
101+
"computer-%s" % i, #itemId
102+
#values:
103+
{
104+
'price': random.uniform(500, 2000),
105+
'num-cores': random.randrange(1,9),
106+
'description': 'Great computer',
107+
'!cascadeCreate': True # Use !cascadeCreate for creating item
108+
# with given itemId, if it doesn't exist
109+
}
110+
) for i in range(NUM)]
111+
112+
113+
# Send catalog to the recommender system
114+
client.send(Batch(requests))
115+
116+
# Prepare some purchases of items by users
117+
requests = []
118+
items = ["computer-%s" % i for i in range(NUM)]
119+
users = ["user-%s" % i for i in range(NUM)]
120+
121+
for item_id in items:
122+
#Use cascadeCreate to create unexisting users
123+
purchasing_users = [user_id for user_id in users if random.random() < PROBABILITY_PURCHASED]
124+
requests += [AddPurchase(user_id, item_id, {'cascadeCreate': True}) for user_id in purchasing_users]
125+
126+
# Send purchases to the recommender system
127+
client.send(Batch(requests))
128+
129+
# Get 5 recommendations for user-42, who is currently viewing computer-6
130+
recommended = client.send(ItemBasedRecommendation('computer-6', 5, {'targetUserId': 'user-42'}) )
131+
print("Recommended items: %s" % recommended)
132+
133+
# Get 5 recommendations for user-42, but recommend only computers that
134+
# have at least 3 cores
135+
recommended = client.send(
136+
ItemBasedRecommendation('computer-6', 5, {'targetUserId': 'user-42', 'filter': "'num-cores'>=3"})
137+
)
138+
print("Recommended items with at least 3 processor cores: %s" % recommended)
139+
140+
# Get 5 recommendations for user-42, but recommend only items that
141+
# are more expensive then currently viewed item (up-sell)
142+
recommended = client.send(
143+
ItemBasedRecommendation('computer-6', 5,
144+
{'targetUserId': 'user-42', 'filter': "'price' > context_item[\"price\"]"})
145+
)
146+
print("Recommended up-sell items: %s" % recommended)
147+
148+
------------------
149+
Exception handling
150+
------------------
151+
152+
For the sake of brevity, the above examples omit exception handling. However, various exceptions can occur while processing request, for example because of adding an already existing item, submitting interaction of nonexistent user or because of timeout.
153+
154+
We are doing our best to provide the fastest and most reliable service, but production-level applications must implement a fallback solution since errors can always happen. The fallback might be, for example, showing the most popular items from the current category, or not displaying recommendations at all.
155+
156+
Example:
157+
158+
.. code-block:: python
159+
160+
try:
161+
recommended = client.send(
162+
ItemBasedRecommendation('computer-6', 5,
163+
{'targetUserId': 'user-42', 'filter': "'price' > context_item[\"price\"]"})
164+
)
165+
except ResponseException as e:
166+
#Handle errorneous request => use fallback
167+
except ApiTimeoutException as e:
168+
#Handle timeout => use fallback
169+
except APIException as e:
170+
#APIException is parent of both ResponseException and ApiTimeoutException

recombee_api_client/__init__.py

Whitespace-only changes.

recombee_api_client/api_client.py

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
import time
3+
import hmac
4+
import json
5+
from hashlib import sha1
6+
import requests
7+
8+
try:
9+
from urllib import quote
10+
except ImportError:
11+
from urllib.parse import quote
12+
13+
from recombee_api_client.exceptions import ApiTimeoutException, ResponseException
14+
15+
16+
class RecombeeClient:
17+
"""
18+
Client for sending requests to Recombee recommender system
19+
"""
20+
21+
def __init__(self, database_id, token, protocol = 'http', options = {}):
22+
"""
23+
@param database_id: Name of your database_id at Recombee
24+
@param token: Secret token obtained from Recombee for signing requests
25+
@param protocol: Default protocol for sending requests. Possible values: 'http', 'https'.
26+
"""
27+
self.database_id = database_id
28+
self.token = token
29+
self.protocol = protocol
30+
31+
self.base_uri = os.environ.get('RAPI_URI')
32+
if self.base_uri is None:
33+
self.base_uri = options.get('base_uri')
34+
if self.base_uri is None:
35+
self.base_uri = 'rapi.recombee.com'
36+
37+
38+
def send(self, request):
39+
"""
40+
@param request: Request to be sent to Recombee recommender
41+
"""
42+
timeout = request.timeout / 1000
43+
uri = self.__process_request_uri(request)
44+
uri = self.__sign_url(uri)
45+
protocol = 'https' if request.ensure_https else self.protocol
46+
uri = protocol + '://' + self.base_uri + uri
47+
try:
48+
if request.method == 'put':
49+
return self.__put(request, uri, timeout)
50+
elif request.method == 'get':
51+
return self.__get(request, uri, timeout)
52+
elif request.method == 'post':
53+
return self.__post(request, uri, timeout)
54+
elif request.method == 'delete':
55+
return self.__delete(request, uri, timeout)
56+
except requests.exceptions.Timeout:
57+
raise ApiTimeoutException(request)
58+
59+
def __put(self, request, uri, timeout):
60+
response = requests.put(uri, timeout=timeout)
61+
self.__check_errors(response, request)
62+
return response.json()
63+
64+
def __get(self, request, uri, timeout):
65+
response = requests.get(uri, timeout=timeout)
66+
self.__check_errors(response, request)
67+
return response.json()
68+
69+
def __post(self, request, uri, timeout):
70+
response = requests.post(uri, data=json.dumps(request.get_body_parameters()),
71+
headers={'Content-Type': 'application/json'},
72+
timeout=timeout)
73+
self.__check_errors(response, request)
74+
return response.json()
75+
76+
def __delete(self, request, uri, timeout):
77+
response = requests.delete(uri, timeout=timeout)
78+
self.__check_errors(response, request)
79+
return response.json()
80+
81+
82+
def __check_errors(self, response, request):
83+
status_code = response.status_code
84+
if status_code == 200 or status_code == 201:
85+
return
86+
raise ResponseException(request, status_code, response.text)
87+
88+
def __process_request_uri(self, request):
89+
uri = request.path
90+
uri = uri[len('/{databaseId}/'):]
91+
uri += self.__query_parameters_to_url(request)
92+
return uri
93+
94+
95+
def __query_parameters_to_url(self, request):
96+
ps = ''
97+
query_params = request.get_query_parameters()
98+
for name in query_params:
99+
val = query_params[name]
100+
ps += '&' if ps.find('?')!=-1 else '?'
101+
ps += "%s=%s" % (name, self.__format_query_parameter_value(val))
102+
return ps
103+
104+
def __format_query_parameter_value(self, value):
105+
if isinstance(value, list):
106+
return ','.join([quote(str(v)) for v in value])
107+
return quote(str(value))
108+
109+
# Sign request with HMAC, request URI must be exacly the same
110+
# We have 30s to complete request with this token
111+
def __sign_url(self, req_part):
112+
uri = '/' + self.database_id + '/' + req_part
113+
time = self.__hmac_time(uri)
114+
sign = self.__hmac_sign(uri, time)
115+
res = uri + time + '&hmac_sign=' +sign
116+
return res
117+
118+
def __hmac_time(self, uri):
119+
res = '&' if uri.find('?')!=-1 else '?'
120+
res += "hmac_timestamp=%s" % int(time.time())
121+
return res
122+
123+
def __hmac_sign(self, uri, time):
124+
url = uri + time
125+
sign = hmac.new(str.encode(self.token), str.encode(url), sha1).hexdigest()
126+
return sign
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
from recombee_api_client.api_requests.add_item import AddItem
2+
from recombee_api_client.api_requests.delete_item import DeleteItem
3+
from recombee_api_client.api_requests.set_item_values import SetItemValues
4+
from recombee_api_client.api_requests.get_item_values import GetItemValues
5+
from recombee_api_client.api_requests.list_items import ListItems
6+
from recombee_api_client.api_requests.add_item_property import AddItemProperty
7+
from recombee_api_client.api_requests.delete_item_property import DeleteItemProperty
8+
from recombee_api_client.api_requests.get_item_property_info import GetItemPropertyInfo
9+
from recombee_api_client.api_requests.list_item_properties import ListItemProperties
10+
from recombee_api_client.api_requests.add_series import AddSeries
11+
from recombee_api_client.api_requests.delete_series import DeleteSeries
12+
from recombee_api_client.api_requests.list_series import ListSeries
13+
from recombee_api_client.api_requests.list_series_items import ListSeriesItems
14+
from recombee_api_client.api_requests.insert_to_series import InsertToSeries
15+
from recombee_api_client.api_requests.remove_from_series import RemoveFromSeries
16+
from recombee_api_client.api_requests.add_group import AddGroup
17+
from recombee_api_client.api_requests.delete_group import DeleteGroup
18+
from recombee_api_client.api_requests.list_groups import ListGroups
19+
from recombee_api_client.api_requests.list_group_items import ListGroupItems
20+
from recombee_api_client.api_requests.insert_to_group import InsertToGroup
21+
from recombee_api_client.api_requests.remove_from_group import RemoveFromGroup
22+
from recombee_api_client.api_requests.add_user import AddUser
23+
from recombee_api_client.api_requests.delete_user import DeleteUser
24+
from recombee_api_client.api_requests.merge_users import MergeUsers
25+
from recombee_api_client.api_requests.list_users import ListUsers
26+
from recombee_api_client.api_requests.add_detail_view import AddDetailView
27+
from recombee_api_client.api_requests.delete_detail_view import DeleteDetailView
28+
from recombee_api_client.api_requests.list_item_detail_views import ListItemDetailViews
29+
from recombee_api_client.api_requests.list_user_detail_views import ListUserDetailViews
30+
from recombee_api_client.api_requests.add_purchase import AddPurchase
31+
from recombee_api_client.api_requests.delete_purchase import DeletePurchase
32+
from recombee_api_client.api_requests.list_item_purchases import ListItemPurchases
33+
from recombee_api_client.api_requests.list_user_purchases import ListUserPurchases
34+
from recombee_api_client.api_requests.add_rating import AddRating
35+
from recombee_api_client.api_requests.delete_rating import DeleteRating
36+
from recombee_api_client.api_requests.list_item_ratings import ListItemRatings
37+
from recombee_api_client.api_requests.list_user_ratings import ListUserRatings
38+
from recombee_api_client.api_requests.add_cart_addition import AddCartAddition
39+
from recombee_api_client.api_requests.delete_cart_addition import DeleteCartAddition
40+
from recombee_api_client.api_requests.list_item_cart_additions import ListItemCartAdditions
41+
from recombee_api_client.api_requests.list_user_cart_additions import ListUserCartAdditions
42+
from recombee_api_client.api_requests.add_bookmark import AddBookmark
43+
from recombee_api_client.api_requests.delete_bookmark import DeleteBookmark
44+
from recombee_api_client.api_requests.list_item_bookmarks import ListItemBookmarks
45+
from recombee_api_client.api_requests.list_user_bookmarks import ListUserBookmarks
46+
from recombee_api_client.api_requests.user_based_recommendation import UserBasedRecommendation
47+
from recombee_api_client.api_requests.item_based_recommendation import ItemBasedRecommendation
48+
from recombee_api_client.api_requests.reset_database import ResetDatabase
49+
from recombee_api_client.api_requests.batch import Batch

0 commit comments

Comments
 (0)