Skip to content

Commit c219280

Browse files
committed
Added support for user properties. Automatically split Batch with more than 10k requests to multiple Batch requests. Added cascadeCreate parameter to Set item/user values.
1 parent bb04ba7 commit c219280

File tree

93 files changed

+591
-67
lines changed

Some content is hidden

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

93 files changed

+591
-67
lines changed

README.rst

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,41 +30,36 @@ Basic example
3030
3131
from recombee_api_client.api_client import RecombeeClient
3232
from recombee_api_client.exceptions import APIException
33-
from recombee_api_client.api_requests import AddUser, AddItem, AddPurchase, UserBasedRecommendation, Batch
33+
from recombee_api_client.api_requests import AddPurchase, UserBasedRecommendation, Batch
3434
import random
3535
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) ]
36+
client = RecombeeClient('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
4037
4138
#Generate some random purchases of items by users
4239
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})
40+
NUM = 100
41+
purchase_requests = []
4842
49-
# Use Recombee recommender
50-
client = RecombeeClient('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
43+
for user_id in ["user-%s" % i for i in range(NUM) ]:
44+
for item_id in ["item-%s" % i for i in range(NUM) ]:
45+
if random.random() < PROBABILITY_PURCHASED:
46+
47+
request = AddPurchase(user_id, item_id, cascade_create=True)
48+
purchase_requests.append(request)
5149
5250
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]))
51+
# Send the data to Recombee, use Batch for faster processing of larger data
5852
print('Send purchases')
59-
client.send(Batch([AddPurchase(p['userId'], p['itemId']) for p in my_purchases]))
53+
client.send(Batch(purchase_requests))
6054
6155
# Get recommendations for user 'user-25'
62-
print('Recommend for a user')
6356
recommended = client.send(UserBasedRecommendation('user-25', 5))
6457
print("Recommended items: %s" % recommended)
58+
6559
except APIException as e:
6660
print(e)
6761
62+
6863
---------------------
6964
Using property values
7065
---------------------
@@ -81,10 +76,10 @@ Using property values
8176
PROBABILITY_PURCHASED = 0.1
8277
8378
client = RecombeeClient('client-test', 'jGGQ6ZKa8rQ1zTAyxTc0EMn55YPF7FJLUtaMLhbsGxmvwxgTwXYqmUk5xVZFw98L')
84-
79+
8580
#Clear the entire database
8681
client.send(ResetDatabase())
87-
82+
8883
# We will use computers as items in this example
8984
# Computers have three properties
9085
# - price (floating point number)
@@ -104,11 +99,11 @@ Using property values
10499
'price': random.uniform(500, 2000),
105100
'num-cores': random.randrange(1,9),
106101
'description': 'Great computer',
107-
'!cascadeCreate': True # Use !cascadeCreate for creating item
108-
# with given itemId, if it doesn't exist
109-
}
102+
},
103+
cascade_create=True # Use cascadeCreate for creating item
104+
# with given itemId if it doesn't exist
110105
) for i in range(NUM)]
111-
106+
112107
113108
# Send catalog to the recommender system
114109
client.send(Batch(requests))

recombee_api_client/api_client.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from urllib.parse import quote
1212

1313
from recombee_api_client.exceptions import ApiTimeoutException, ResponseException
14-
14+
from recombee_api_client.api_requests import Batch
1515

1616
class RecombeeClient:
1717
"""
1818
Client for sending requests to Recombee recommender system
1919
"""
20+
BATCH_MAX_SIZE = 10000
2021

2122
def __init__(self, database_id, token, protocol = 'http', options = {}):
2223
"""
@@ -39,6 +40,10 @@ def send(self, request):
3940
"""
4041
@param request: Request to be sent to Recombee recommender
4142
"""
43+
44+
if isinstance(request, Batch) and len(request.requests) > self.BATCH_MAX_SIZE:
45+
return self.__send_multipart_batch(request)
46+
4247
timeout = request.timeout / 1000
4348
uri = self.__process_request_uri(request)
4449
uri = self.__sign_url(uri)
@@ -85,6 +90,23 @@ def __check_errors(self, response, request):
8590
return
8691
raise ResponseException(request, status_code, response.text)
8792

93+
@staticmethod
94+
def __get_list_chunks(l, n):
95+
"""Yield successive n-sized chunks from l."""
96+
97+
try: #Python 2/3 compatibility
98+
xrange
99+
except NameError:
100+
xrange = range
101+
102+
for i in xrange(0, len(l), n):
103+
yield l[i:i + n]
104+
105+
def __send_multipart_batch(self, batch):
106+
requests_parts = [rqs for rqs in self.__get_list_chunks(batch.requests, self.BATCH_MAX_SIZE)]
107+
responses = [self.send(Batch(rqs)) for rqs in requests_parts]
108+
return sum(responses, [])
109+
88110
def __process_request_uri(self, request):
89111
uri = request.path
90112
uri = uri[len('/{databaseId}/'):]

recombee_api_client/api_requests/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@
2121
from recombee_api_client.api_requests.remove_from_group import RemoveFromGroup
2222
from recombee_api_client.api_requests.add_user import AddUser
2323
from recombee_api_client.api_requests.delete_user import DeleteUser
24+
from recombee_api_client.api_requests.set_user_values import SetUserValues
25+
from recombee_api_client.api_requests.get_user_values import GetUserValues
2426
from recombee_api_client.api_requests.merge_users import MergeUsers
2527
from recombee_api_client.api_requests.list_users import ListUsers
28+
from recombee_api_client.api_requests.add_user_property import AddUserProperty
29+
from recombee_api_client.api_requests.delete_user_property import DeleteUserProperty
30+
from recombee_api_client.api_requests.get_user_property_info import GetUserPropertyInfo
31+
from recombee_api_client.api_requests.list_user_properties import ListUserProperties
2632
from recombee_api_client.api_requests.add_detail_view import AddDetailView
2733
from recombee_api_client.api_requests.delete_detail_view import DeleteDetailView
2834
from recombee_api_client.api_requests.list_item_detail_views import ListItemDetailViews
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from recombee_api_client.api_requests.request import Request
2+
3+
class AddUserProperty(Request):
4+
"""
5+
Adding an user property is somehow equivalent to adding a column to the table of users. The users may be characterized by various properties of different types.
6+
7+
"""
8+
9+
def __init__(self,property_name, type):
10+
"""
11+
Required parameters:
12+
@param property_name: Name of the user property to be created. Currently, the following names are reserved:`id`, `userid`, case insensitively. Also, the length of the property name must not exceed 63 characters.
13+
14+
15+
@param type: Value type of the user property to be created. One of: `int`, `double`, `string`, `boolean`, `timestamp`, `set`
16+
17+
18+
"""
19+
self.property_name = property_name
20+
self.type = type
21+
self.timeout = 1000
22+
self.ensure_https = False
23+
self.method = 'put'
24+
self.path = "/{databaseId}/users/properties/%s" % (self.property_name)
25+
26+
def get_body_parameters(self):
27+
"""
28+
Values of body parameters as a dictionary (name of parameter: value of the parameter).
29+
"""
30+
p = dict()
31+
return p
32+
33+
def get_query_parameters(self):
34+
"""
35+
Values of query parameters as a dictionary (name of parameter: value of the parameter).
36+
"""
37+
params = dict()
38+
params['type'] = self.type
39+
return params

recombee_api_client/api_requests/batch.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class Batch(Request):
1212
- executing the requests in a batch is equivalent as if they were executed one-by-one individually; there are, however, many optimizations to make batch execution as fast as possible,
1313
- the status code of the batch request itself is 200 even if the individual requests result in error - you have to inspect the code values in the resulting array,
1414
- if the status code of the whole batch is not 200, then there is an error in the batch request itself; in such a case, the error message returned should help you to resolve the problem,
15-
- currently, batch size is limited to **10,000** requests; if you wish to execute even larger number of requests, please split the batch into multiple parts.
1615
"""
1716

1817
def __init__(self, requests, distinctRecomms=None):
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from recombee_api_client.api_requests.request import Request
2+
3+
class DeleteUserProperty(Request):
4+
"""
5+
Deleting an user property is roughly equivalent to removing a column from the table of users.
6+
7+
"""
8+
9+
def __init__(self,property_name):
10+
"""
11+
Required parameters:
12+
@param property_name: Name of the property to be deleted.
13+
14+
"""
15+
self.property_name = property_name
16+
self.timeout = 1000
17+
self.ensure_https = False
18+
self.method = 'delete'
19+
self.path = "/{databaseId}/users/properties/%s" % (self.property_name)
20+
21+
def get_body_parameters(self):
22+
"""
23+
Values of body parameters as a dictionary (name of parameter: value of the parameter).
24+
"""
25+
p = dict()
26+
return p
27+
28+
def get_query_parameters(self):
29+
"""
30+
Values of query parameters as a dictionary (name of parameter: value of the parameter).
31+
"""
32+
params = dict()
33+
return params
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from recombee_api_client.api_requests.request import Request
2+
3+
class GetUserPropertyInfo(Request):
4+
"""
5+
Gets information about specified user property.
6+
7+
"""
8+
9+
def __init__(self,property_name):
10+
"""
11+
Required parameters:
12+
@param property_name: Name of the property about which the information is to be retrieved.
13+
14+
"""
15+
self.property_name = property_name
16+
self.timeout = 1000
17+
self.ensure_https = False
18+
self.method = 'get'
19+
self.path = "/{databaseId}/users/properties/%s" % (self.property_name)
20+
21+
def get_body_parameters(self):
22+
"""
23+
Values of body parameters as a dictionary (name of parameter: value of the parameter).
24+
"""
25+
p = dict()
26+
return p
27+
28+
def get_query_parameters(self):
29+
"""
30+
Values of query parameters as a dictionary (name of parameter: value of the parameter).
31+
"""
32+
params = dict()
33+
return params
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from recombee_api_client.api_requests.request import Request
2+
3+
class GetUserValues(Request):
4+
"""
5+
Get all the current property values of a given user.
6+
7+
"""
8+
9+
def __init__(self,user_id):
10+
"""
11+
Required parameters:
12+
@param user_id: ID of the user properties of which are to be obtained.
13+
14+
15+
"""
16+
self.user_id = user_id
17+
self.timeout = 1000
18+
self.ensure_https = False
19+
self.method = 'get'
20+
self.path = "/{databaseId}/users/%s" % (self.user_id)
21+
22+
def get_body_parameters(self):
23+
"""
24+
Values of body parameters as a dictionary (name of parameter: value of the parameter).
25+
"""
26+
p = dict()
27+
return p
28+
29+
def get_query_parameters(self):
30+
"""
31+
Values of query parameters as a dictionary (name of parameter: value of the parameter).
32+
"""
33+
params = dict()
34+
return params

recombee_api_client/api_requests/item_based_recommendation.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ def __init__(self,item_id, count, target_user_id=None, user_impact=None, filter=
1717
Optional parameters:
1818
@param target_user_id: ID of the user who will see the recommendations.
1919
20+
21+
Specifying the *targetUserId* is beneficial because:
22+
23+
24+
* It makes the recommendations personalized
25+
26+
* Allows calculations of Actions and Conversions in the graphical user interface, as Recombee can pair the user who got recommendations and who afterwards viewed/purchased an item.
27+
28+
2029
@param user_impact: If *targetUserId* parameter is present, the recommendations are biased towards the user given. Using *userImpact*, you may control this bias. For an extreme case of `userImpact=0.0`, the interactions made by the user are not taken into account at all (with the exception of history-based blacklisting), for `userImpact=1.0`, you'll get user-based recommendation. The default value is `0.1`
2130
2231
@@ -105,13 +114,13 @@ def __init__(self,item_id, count, target_user_id=None, user_impact=None, filter=
105114
@param diversity: **Expert option** Real number from [0.0, 1.0] which determines how much mutually dissimilar should the recommended items be. The default value is 0.0, i.e., no diversification. Value 1.0 means maximal diversification.
106115
107116
108-
@param min_relevance: **Expert option** Specifies the threshold of how much relevant must the recommended items be to the user. Possible values one of: "low", "medium", "high". The default value is "low", meaning that the system attempts to recommend number of items equal to *count* at any cost. If there are not enough data (such as interactions or item properties), this may even lead to bestseller-based recommendations to be appended to reach the full *count*. This behavior may be suppressed by using "medium" or "high" values. In such case, the system only recommends items of at least the requested qualit, and may return less than *count* items when there is not enough data to fulfill it.
117+
@param min_relevance: **Expert option** If the *targetUserId* is provided: Specifies the threshold of how much relevant must the recommended items be to the user. Possible values one of: "low", "medium", "high". The default value is "low", meaning that the system attempts to recommend number of items equal to *count* at any cost. If there are not enough data (such as interactions or item properties), this may even lead to bestseller-based recommendations to be appended to reach the full *count*. This behavior may be suppressed by using "medium" or "high" values. In such case, the system only recommends items of at least the requested qualit, and may return less than *count* items when there is not enough data to fulfill it.
109118
110119
111-
@param rotation_rate: **Expert option** If your users browse the system in real-time, it may easily happen that you wish to offer them recommendations multiple times. Here comes the question: how much should the recommendations change? Should they remain the same, or should they rotate? Recombee API allows you to control this per-request in backward fashion. You may penalize an item for being recommended in the near past. For the specific user, `rotationRate=1` means maximal rotation, `rotationRate=0` means absolutely no rotation. You may also use, for example `rotationRate=0.2` for only slight rotation of recommended items.
120+
@param rotation_rate: **Expert option** If the *targetUserId* is provided: If your users browse the system in real-time, it may easily happen that you wish to offer them recommendations multiple times. Here comes the question: how much should the recommendations change? Should they remain the same, or should they rotate? Recombee API allows you to control this per-request in backward fashion. You may penalize an item for being recommended in the near past. For the specific user, `rotationRate=1` means maximal rotation, `rotationRate=0` means absolutely no rotation. You may also use, for example `rotationRate=0.2` for only slight rotation of recommended items.
112121
113122
114-
@param rotation_time: **Expert option** Taking *rotationRate* into account, specifies how long time it takes to an item to fully recover from the penalization. By example, `rotationTime=7200.0` means that items recommended more than 2 hours ago are definitely not penalized anymore. Currently, the penalization is linear, so for `rotationTime=7200.0`, an item is still penalized by `0.5` to the user after 1 hour.
123+
@param rotation_time: **Expert option** If the *targetUserId* is provided: Taking *rotationRate* into account, specifies how long time it takes to an item to fully recover from the penalization. For example, `rotationTime=7200.0` means that items recommended more than 2 hours ago are definitely not penalized anymore. Currently, the penalization is linear, so for `rotationTime=7200.0`, an item is still penalized by `0.5` to the user after 1 hour.
115124
116125
117126
"""
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from recombee_api_client.api_requests.request import Request
2+
3+
class ListUserProperties(Request):
4+
"""
5+
Gets the list of all the user properties in your database.
6+
7+
"""
8+
9+
def __init__(self):
10+
"""
11+
"""
12+
self.timeout = 1000
13+
self.ensure_https = False
14+
self.method = 'get'
15+
self.path = "/{databaseId}/users/properties/list/" % ()
16+
17+
def get_body_parameters(self):
18+
"""
19+
Values of body parameters as a dictionary (name of parameter: value of the parameter).
20+
"""
21+
p = dict()
22+
return p
23+
24+
def get_query_parameters(self):
25+
"""
26+
Values of query parameters as a dictionary (name of parameter: value of the parameter).
27+
"""
28+
params = dict()
29+
return params

0 commit comments

Comments
 (0)