predictionio 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ require "predictionio/client"
@@ -0,0 +1,22 @@
1
+ module PredictionIO
2
+ # This class contains the URI path and query parameters that is consumed by PredictionIO::Connection for asynchronous HTTP requests.
3
+ class AsyncRequest
4
+
5
+ # The path portion of the request URI.
6
+ attr_reader :path
7
+
8
+ # Query parameters, or form data.
9
+ attr_reader :params
10
+
11
+ # Populates the package with request URI path, and optionally query parameters or form data.
12
+ def initialize(path, params = {})
13
+ @params = params
14
+ @path = path
15
+ end
16
+
17
+ # Returns an URI path with query parameters encoded for HTTP GET requests.
18
+ def qpath
19
+ "#{@path}?#{URI::encode_www_form(@params)}"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ require 'thread'
2
+
3
+ module PredictionIO
4
+ # This class encapsulates an asynchronous response that will block the caller until the response is available.
5
+ class AsyncResponse
6
+
7
+ # The PredictionIO::AsyncRequest instance that created the current PredictionIO::AsyncResponse instance.
8
+ attr_reader :request
9
+
10
+ # Create the response by saving the request, and optionally the Net::HTTPResponse object.
11
+ def initialize(request, response = nil)
12
+ @request = request
13
+ @response = Queue.new
14
+ if response != nil then
15
+ set(response)
16
+ end
17
+ end
18
+
19
+ # Save a Net::HTTPResponse instance to the current instance.
20
+ # This will unblock any caller that called #get.
21
+ def set(response)
22
+ @response.push(response)
23
+ end
24
+
25
+ # Get the Net::HTTPResponse instance. This will block if the response is not yet available.
26
+ def get
27
+ @response.pop
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,681 @@
1
+ # Ruby SDK for convenient access of PredictionIO Output API.
2
+ #
3
+ # Author:: TappingStone (help@tappingstone.com)
4
+ # Copyright:: Copyright (c) 2013 TappingStone
5
+ # License:: Apache License, Version 2.0
6
+
7
+ require 'date'
8
+ require 'json'
9
+ require 'net/http'
10
+ require 'predictionio/async_request'
11
+ require 'predictionio/async_response'
12
+ require 'predictionio/connection'
13
+
14
+ # The PredictionIO module contains classes that provide convenient access of the PredictionIO output API over HTTP/HTTPS.
15
+ #
16
+ # To create an app and perform predictions, please download the PredictionIO suite from http://prediction.io.
17
+ #
18
+ # Most functionality is provided by the PredictionIO::Client class.
19
+ module PredictionIO
20
+
21
+ # This class contains methods that access PredictionIO via REST requests.
22
+ #
23
+ # Many REST request methods support optional arguments.
24
+ # They can be supplied to these methods as Hash'es.
25
+ # For a complete reference, please visit http://prediction.io.
26
+ #
27
+ # == High-performance Asynchronous Backend
28
+ #
29
+ # All REST request methods come in both synchronous and asynchronous flavors.
30
+ # Both flavors accept the same set of arguments.
31
+ # In addition, all synchronous request methods can instead accept a PredictionIO::AsyncResponse object generated from asynchronous request methods as its first argument.
32
+ # In this case, the method will block until a response is received from it.
33
+ #
34
+ # Any network reconnection and request retry is automatically handled in the background.
35
+ # Exceptions will be thrown after a request times out to avoid infinite blocking.
36
+ #
37
+ # == Special Handling of Some Optional Arguments
38
+ # Some optional arguments have additional special handling:
39
+ # - For all requests that accept "itypes" as input, the value can be supplied as either an Array of String's, or a comma-delimited String.
40
+ # - For all requests that accept "latlng" as input, they will also accept "latitude" and "longitude".
41
+ # When these are supplied, they will override any existing "latlng" value.
42
+ # - All time arguments (e.g. t, startT, endT, etc) can be supplied as either a Time or Float object.
43
+ # When supplied as a Float, the SDK will interpret it as a UNIX UTC timestamp in seconds.
44
+ # The SDK will automatically round to the nearest millisecond, e.g. 3.14159 => 3.142.
45
+ #
46
+ # == Installation
47
+ # Download the PredictionIO Ruby Gem from http://prediction.io
48
+ # gem install predictionio-0.1.0.gem
49
+ #
50
+ # == Synopsis
51
+ # The recommended usage of the SDK is to fire asynchronous requests as early as you can in your code,
52
+ # and check results later when you need them.
53
+ #
54
+ # === Instantiate PredictionIO Client
55
+ # # Include the PredictionIO SDK
56
+ # require "PredictionIO"
57
+ #
58
+ # client = PredictionIO::Client.new(<appkey>)
59
+ #
60
+ # === Import a User Record from Your App (with asynchronous/non-blocking requests)
61
+ #
62
+ # #
63
+ # # (your user registration logic)
64
+ # #
65
+ #
66
+ # uid = get_user_from_your_db()
67
+ #
68
+ # # PredictionIO call to create user
69
+ # response = client.acreate_user(uid)
70
+ #
71
+ # #
72
+ # # (other work to do for the rest of the page)
73
+ # #
74
+ #
75
+ # begin
76
+ # # PredictionIO call to retrieve results from an asynchronous response
77
+ # result = client.create_user(response)
78
+ # rescue UserNotCreatedError => e
79
+ # log_and_email_error(...)
80
+ # end
81
+ #
82
+ # === Import a User Action (View) form Your App (with synchronous/blocking requests)
83
+ # # PredictionIO call to record the view action
84
+ # begin
85
+ # result = client.user_view_item(4, 15)
86
+ # rescue U2IActionNotCreatedError => e
87
+ # ...
88
+ # end
89
+ #
90
+ # === Retrieving Top N Recommendations for a User
91
+ # # PredictionIO call to get recommendations
92
+ # response = client.aget_recommendation(4, 10)
93
+ #
94
+ # #
95
+ # # work you need to do for the page (rendering, db queries, etc)
96
+ # #
97
+ #
98
+ # begin
99
+ # result = client.get_recommendations(response)
100
+ # # display results, store results, or your other work...
101
+ # rescue RecommendationsNotFoundError => e
102
+ # # graceful error handling
103
+ # end
104
+
105
+ class Client
106
+
107
+ # Appkey can be changed on-the-fly after creation of the client.
108
+ attr_accessor :appkey
109
+
110
+ # API version can be changed on-the-fly after creation of the client.
111
+ attr_accessor :apiversion
112
+
113
+ # Only JSON is currently supported as API response format.
114
+ attr_accessor :apiformat
115
+
116
+ # Raised when a user is not created after a synchronous API call.
117
+ class UserNotCreatedError < StandardError; end
118
+
119
+ # Raised when a user is not found after a synchronous API call.
120
+ class UserNotFoundError < StandardError; end
121
+
122
+ # Raised when a user is not deleted after a synchronous API call.
123
+ class UserNotDeletedError < StandardError; end
124
+
125
+ # Raised when an item is not created after a synchronous API call.
126
+ class ItemNotCreatedError < StandardError; end
127
+
128
+ # Raised when an item is not found after a synchronous API call.
129
+ class ItemNotFoundError < StandardError; end
130
+
131
+ # Raised when an item is not deleted after a synchronous API call.
132
+ class ItemNotDeletedError < StandardError; end
133
+
134
+ # Raised when ItemRec results cannot be found for a user after a synchronous API call.
135
+ class ItemRecNotFoundError < StandardError; end
136
+
137
+ # Raised when an user-to-item action is not created after a synchronous API call.
138
+ class U2IActionNotCreatedError < StandardError; end
139
+
140
+ # Create a new PredictionIO client with default:
141
+ # - API entry point at http://localhost:8000
142
+ # - API return data format of json
143
+ # - 10 concurrent HTTP(S) connections
144
+ def initialize(appkey, threads = 10, apiurl = "http://localhost:8000", apiversion = "")
145
+ @appkey = appkey
146
+ @apiversion = apiversion
147
+ @apiformat = "json"
148
+ @http = PredictionIO::Connection.new(URI(apiurl), threads)
149
+ end
150
+
151
+ # Returns the number of pending requests within the current client.
152
+ def pending_requests
153
+ @http.packages.size
154
+ end
155
+
156
+ # Returns PredictionIO's status in string.
157
+ def get_status
158
+ status = @http.aget(PredictionIO::AsyncRequest.new("/")).get()
159
+ begin
160
+ status.body
161
+ rescue Exception
162
+ status
163
+ end
164
+ end
165
+
166
+ # :category: Asynchronous Methods
167
+ # Asynchronously request to create a user and return a PredictionIO::AsyncResponse object immediately.
168
+ #
169
+ # Corresponding REST API method: POST /users
170
+ #
171
+ # See also #create_user.
172
+ def acreate_user(uid, params = {})
173
+ rparams = params
174
+ rparams["appkey"] = @appkey
175
+ rparams["uid"] = uid
176
+ if params["latitude"] != nil && params["longitude"] != nil then
177
+ rparams["latlng"] = "#{params["latitude"]},#{params["longitude"]}"
178
+ end
179
+
180
+ @http.apost(PredictionIO::AsyncRequest.new(versioned_path("/users.#{@apiformat}"), rparams))
181
+ end
182
+
183
+ # :category: Synchronous Methods
184
+ # Synchronously request to create a user and block until a response is received.
185
+ #
186
+ # See also #acreate_user.
187
+ #
188
+ # call-seq:
189
+ # create_user(uid, params = {})
190
+ # create_user(async_response)
191
+ def create_user(*args)
192
+ uid_or_res = args[0]
193
+ if uid_or_res.is_a?(PredictionIO::AsyncResponse) then
194
+ uid = uid_or_res.request.params["uid"]
195
+ response = uid_or_res.get
196
+ else
197
+ uid = uid_or_res
198
+ response = acreate_user(*args).get
199
+ end
200
+ unless response.is_a?(Net::HTTPCreated) then
201
+ begin
202
+ msg = response.body
203
+ rescue Exception
204
+ raise UserNotCreatedError, response
205
+ end
206
+ raise UserNotCreatedError, msg
207
+ end
208
+ end
209
+
210
+ # :category: Asynchronous Methods
211
+ # Asynchronously request to get a user and return a PredictionIO::AsyncResponse object immediately.
212
+ #
213
+ # Creation time of the user will be returned as a Time object.
214
+ #
215
+ # If the result contains a latlng key, both latitude and longitude will also be available as separate keys.
216
+ #
217
+ # Corresponding REST API method: GET /users/:uid
218
+ #
219
+ # See also #get_user.
220
+ def aget_user(uid)
221
+ @http.aget(PredictionIO::AsyncRequest.new(versioned_path("/users/#{uid}.#{@apiformat}"),
222
+ "appkey" => @appkey,
223
+ "uid" => uid))
224
+ end
225
+
226
+ # :category: Synchronous Methods
227
+ # Synchronously request to get a user and block until a response is received.
228
+ #
229
+ # Creation time of the user will be returned as a Time object.
230
+ #
231
+ # If the result contains a latlng key, both latitude and longitude will also be available as separate keys.
232
+ #
233
+ # See also #aget_user.
234
+ #
235
+ # call-seq:
236
+ # get_user(uid)
237
+ # get_user(async_response)
238
+ def get_user(uid_or_res)
239
+ if uid_or_res.is_a?(PredictionIO::AsyncResponse) then
240
+ response = uid_or_res.get
241
+ else
242
+ response = aget_user(uid_or_res).get
243
+ end
244
+ if response.is_a?(Net::HTTPOK) then
245
+ res = JSON.parse(response.body)
246
+ ct = Rational(res["ct"], 1000)
247
+ res["ct"] = Time.at(ct)
248
+ if res["latlng"] != nil then
249
+ latlng = res["latlng"]
250
+ res["latitude"] = latlng[0]
251
+ res["longitude"] = latlng[1]
252
+ end
253
+ res
254
+ else
255
+ begin
256
+ msg = response.body
257
+ rescue Exception
258
+ raise UserNotFoundError, response
259
+ end
260
+ raise UserNotFoundError, msg
261
+ end
262
+ end
263
+
264
+ # :category: Asynchronous Methods
265
+ # Asynchronously request to delete a user and return a PredictionIO::AsyncResponse object immediately.
266
+ #
267
+ # Corresponding REST API method: DELETE /users/:uid
268
+ #
269
+ # See also #delete_user.
270
+ def adelete_user(uid)
271
+ @http.adelete(PredictionIO::AsyncRequest.new(versioned_path("/users/#{uid}.#{@apiformat}"),
272
+ "appkey" => @appkey,
273
+ "uid" => uid))
274
+ end
275
+
276
+ # :category: Synchronous Methods
277
+ # Synchronously request to delete a user and block until a response is received.
278
+ #
279
+ # See also #adelete_user.
280
+ #
281
+ # call-seq:
282
+ # delete_user(uid)
283
+ # delete_user(async_response)
284
+ def delete_user(uid_or_res)
285
+ if uid_or_res.is_a?(PredictionIO::AsyncResponse) then
286
+ response = uid_or_res.get
287
+ else
288
+ response = adelete_user(uid_or_res).get
289
+ end
290
+ unless response.is_a?(Net::HTTPOK) then
291
+ begin
292
+ msg = response.body
293
+ rescue Exception
294
+ raise UserNotDeletedError, response
295
+ end
296
+ raise msg
297
+ end
298
+ end
299
+
300
+ # :category: Asynchronous Methods
301
+ # Asynchronously request to create an item and return a PredictionIO::AsyncResponse object immediately.
302
+ #
303
+ # Corresponding REST API method: POST /items
304
+ #
305
+ # See also #create_item.
306
+ def acreate_item(iid, itypes, params = {})
307
+ rparams = params
308
+ rparams["appkey"] = @appkey
309
+ rparams["iid"] = iid
310
+ begin
311
+ rparams["itypes"] = itypes.join(",")
312
+ rescue Exception
313
+ rparams["itypes"] = itypes
314
+ end
315
+ if params["latitude"] != nil && params["longitude"] != nil then
316
+ rparams["latlng"] = "#{params["latitude"]},#{params["longitude"]}"
317
+ end
318
+ if params["startT"] != nil then
319
+ rparams["startT"] = ((params["startT"].to_r) * 1000).round(0).to_s
320
+ end
321
+ if params["endT"] != nil then
322
+ rparams["endT"] = ((params["endT"].to_r) * 1000).round(0).to_s
323
+ end
324
+
325
+ @http.apost(PredictionIO::AsyncRequest.new(versioned_path("/items.#{@apiformat}"), rparams))
326
+ end
327
+
328
+ # :category: Synchronous Methods
329
+ # Synchronously request to create an item and block until a response is received.
330
+ #
331
+ # See #acreate_item for a description of other accepted arguments.
332
+ #
333
+ # call-seq:
334
+ # create_item(iid, itypes, params = {})
335
+ # create_item(async_response)
336
+ def create_item(*args)
337
+ iid_or_res = args[0]
338
+ if iid_or_res.is_a?(PredictionIO::AsyncResponse) then
339
+ response = iid_or_res.get
340
+ else
341
+ response = acreate_item(*args).get
342
+ end
343
+ unless response.is_a?(Net::HTTPCreated) then
344
+ begin
345
+ msg = response.body
346
+ rescue Exception
347
+ raise ItemNotCreatedError, response
348
+ end
349
+ raise ItemNotCreatedError, msg
350
+ end
351
+ end
352
+
353
+ # :category: Asynchronous Methods
354
+ # Asynchronously request to get an item and return a PredictionIO::AsyncResponse object immediately.
355
+ #
356
+ # Creation time of the user will be returned as a Time object.
357
+ #
358
+ # If the result contains a latlng key, both latitude and longitude will also be available as separate keys.
359
+ #
360
+ # Corresponding REST API method: GET /items/:iid
361
+ #
362
+ # See also #get_item.
363
+ def aget_item(iid)
364
+ @http.aget(PredictionIO::AsyncRequest.new(versioned_path("/items/#{iid}.#{@apiformat}"),
365
+ "appkey" => @appkey,
366
+ "iid" => iid))
367
+ end
368
+
369
+ # :category: Synchronous Methods
370
+ # Synchronously request to get an item and block until a response is received.
371
+ #
372
+ # Creation time of the item will be returned as a Time object.
373
+ #
374
+ # If the result contains a latlng key, both latitude and longitude will also be available as separate keys.
375
+ #
376
+ # See also #aget_item.
377
+ #
378
+ # call-seq:
379
+ # get_item(iid)
380
+ # get_item(async_response)
381
+ def get_item(iid_or_res)
382
+ if iid_or_res.is_a?(PredictionIO::AsyncResponse) then
383
+ response = iid_or_res.get
384
+ else
385
+ response = aget_item(iid_or_res).get
386
+ end
387
+ if response.is_a?(Net::HTTPOK) then
388
+ res = JSON.parse(response.body)
389
+ ct = Rational(res["ct"], 1000)
390
+ res["ct"] = Time.at(ct)
391
+ if res["latlng"] != nil then
392
+ latlng = res["latlng"]
393
+ res["latitude"] = latlng[0]
394
+ res["longitude"] = latlng[1]
395
+ end
396
+ if res["startT"] != nil then
397
+ startT = Rational(res["startT"], 1000)
398
+ res["startT"] = Time.at(startT)
399
+ end
400
+ if res["endT"] != nil then
401
+ endT = Rational(res["endT"], 1000)
402
+ res["endT"] = Time.at(endT)
403
+ end
404
+ res
405
+ else
406
+ begin
407
+ msg = response.body
408
+ rescue Exception
409
+ raise ItemNotFoundError, response
410
+ end
411
+ raise ItemNotFoundError, msg
412
+ end
413
+ end
414
+
415
+ # :category: Asynchronous Methods
416
+ # Asynchronously request to delete an item and return a PredictionIO::AsyncResponse object immediately.
417
+ #
418
+ # Corresponding REST API method: DELETE /items/:iid
419
+ #
420
+ # See also #delete_item.
421
+ def adelete_item(iid)
422
+ @http.adelete(PredictionIO::AsyncRequest.new(versioned_path("/items/#{iid}.#{@apiformat}"),
423
+ "appkey" => @appkey,
424
+ "iid" => iid))
425
+ end
426
+
427
+ # :category: Synchronous Methods
428
+ # Synchronously request to delete an item and block until a response is received.
429
+ #
430
+ # See also #adelete_item.
431
+ #
432
+ # call-seq:
433
+ # delete_item(iid)
434
+ # delete_item(async_response)
435
+ def delete_item(iid_or_res)
436
+ if iid_or_res.is_a?(PredictionIO::AsyncResponse) then
437
+ response = iid_or_res.get
438
+ else
439
+ response = adelete_item(iid_or_res).get
440
+ end
441
+ unless response.is_a?(Net::HTTPOK) then
442
+ begin
443
+ msg = response.body
444
+ rescue Exception
445
+ raise ItemNotDeletedError, response
446
+ end
447
+ raise ItemNotDeletedError, msg
448
+ end
449
+ end
450
+
451
+ # :category: Asynchronous Methods
452
+ # Asynchronously request to get the top n recommendations for a user from an ItemRec engine and return a PredictionIO::AsyncResponse object immediately.
453
+ #
454
+ # Corresponding REST API method: GET /engines/itemrec/:engine/topn
455
+ #
456
+ # See also #get_itemrec_top_n.
457
+ def aget_itemrec_top_n(engine, uid, n, params = {})
458
+ rparams = Hash.new
459
+ rparams["appkey"] = @appkey
460
+ rparams["uid"] = uid
461
+ rparams["n"] = n
462
+ if params["itypes"] != nil &&
463
+ params["itypes"].kind_of?(Array) &&
464
+ params["itypes"].length > 0 then
465
+ rparams["itypes"] = params["itypes"].join(",")
466
+ else
467
+ rparams["itypes"] = params["itypes"]
468
+ end
469
+ if params["latitude"] != nil && params["longitude"] != nil then
470
+ rparams["latlng"] = "#{params["latitude"]},#{params["longitude"]}"
471
+ end
472
+ if params["within"] != nil then
473
+ rparams["within"] = params["within"]
474
+ end
475
+ if params["unit"] != nil then
476
+ rparams["unit"] = params["unit"]
477
+ end
478
+ @http.aget(PredictionIO::AsyncRequest.new(versioned_path("/engines/itemrec/#{engine}/topn.#{@apiformat}"), rparams))
479
+ end
480
+
481
+ # :category: Synchronous Methods
482
+ # Synchronously request to get the top n recommendations for a user from an ItemRec engine and block until a response is received.
483
+ #
484
+ # See #aget_itemrec_top_n for a description of special argument handling.
485
+ #
486
+ # call-seq:
487
+ # get_recommendations(uid, n, params = {})
488
+ # get_recommendations(async_response)
489
+ def get_itemrec_top_n(*args)
490
+ uid_or_res = args[0]
491
+ if uid_or_res.is_a?(PredictionIO::AsyncResponse) then
492
+ response = uid_or_res.get
493
+ else
494
+ response = aget_itemrec_top_n(*args).get
495
+ end
496
+ if response.is_a?(Net::HTTPOK) then
497
+ res = JSON.parse(response.body)
498
+ res["iids"]
499
+ else
500
+ begin
501
+ msg = response.body
502
+ rescue Exception
503
+ raise ItemRecNotFoundError, response
504
+ end
505
+ raise ItemRecNotFoundError, msg
506
+ end
507
+ end
508
+
509
+ # :category: Asynchronous Methods
510
+ # Asynchronously request to record a user-rate-item action and return a PredictionIO::AsyncResponse object immediately.
511
+ #
512
+ # Corresponding REST API method: POST /actions/u2i/rate
513
+ #
514
+ # See also #user_rate_item.
515
+ def auser_rate_item(uid, iid, rate, params = {})
516
+ params["rate"] = rate
517
+ auser_action_item("rate", uid, iid, params)
518
+ end
519
+
520
+ # :category: Synchronous Methods
521
+ # Synchronously request to record a user-rate-item action and block until a response is received.
522
+ #
523
+ # See #auser_rate_item.
524
+ #
525
+ # call-seq:
526
+ # user_rate_item(uid, iid, rate, params = {})
527
+ # user_rate_item(async_response)
528
+ def user_rate_item(*args)
529
+ if !args[0].is_a?(PredictionIO::AsyncResponse) then
530
+ args.unshift("rate")
531
+ params = args[4]
532
+ if params == nil then
533
+ params = Hash.new
534
+ end
535
+ params["rate"] = args[3]
536
+ args[3] = params
537
+ end
538
+ user_action_item(*args)
539
+ end
540
+
541
+ # :category: Asynchronous Methods
542
+ # Asynchronously request to record a user-like-item action and return a PredictionIO::AsyncResponse object immediately.
543
+ #
544
+ # Corresponding REST API method: POST /actions/u2i/like
545
+ #
546
+ # See also #user_like_item.
547
+ def auser_like_item(uid, iid, params = {})
548
+ auser_action_item("like", uid, iid, params)
549
+ end
550
+
551
+ # :category: Synchronous Methods
552
+ # Synchronously request to record a user-like-item action and block until a response is received.
553
+ #
554
+ # See also #auser_like_item.
555
+ #
556
+ # call-seq:
557
+ # user_like_item(uid, iid, params = {})
558
+ # user_like_item(async_response)
559
+ def user_like_item(*args)
560
+ if !args[0].is_a?(PredictionIO::AsyncResponse) then
561
+ args.unshift("like")
562
+ end
563
+ user_action_item(*args)
564
+ end
565
+
566
+ # :category: Asynchronous Methods
567
+ # Asynchronously request to record a user-dislike-item action and return a PredictionIO::AsyncResponse object immediately.
568
+ #
569
+ # Corresponding REST API method: POST /actions/u2i/dislike
570
+ #
571
+ # See also #user_dislike_item.
572
+ def auser_dislike_item(uid, iid, params = {})
573
+ auser_action_item("dislike", uid, iid, params)
574
+ end
575
+
576
+ # :category: Synchronous Methods
577
+ # Synchronously request to record a user-dislike-item action and block until a response is received.
578
+ #
579
+ # See also #auser_dislike_item.
580
+ #
581
+ # call-seq:
582
+ # user_dislike_item(uid, iid, params = {})
583
+ # user_dislike_item(async_response)
584
+ def user_dislike_item(*args)
585
+ if !args[0].is_a?(PredictionIO::AsyncResponse) then
586
+ args.unshift("dislike")
587
+ end
588
+ user_action_item(*args)
589
+ end
590
+
591
+ # :category: Asynchronous Methods
592
+ # Asynchronously request to record a user-view-item action and return a PredictionIO::AsyncResponse object immediately.
593
+ #
594
+ # Corresponding REST API method: POST /actions/u2i/view
595
+ #
596
+ # See also #user_view_item.
597
+ def auser_view_item(uid, iid, params = {})
598
+ auser_action_item("view", uid, iid, params)
599
+ end
600
+
601
+ # :category: Synchronous Methods
602
+ # Synchronously request to record a user-view-item action and block until a response is received.
603
+ #
604
+ # See also #auser_view_item.
605
+ #
606
+ # call-seq:
607
+ # user_view_item(uid, iid, params = {})
608
+ # user_view_item(async_response)
609
+ def user_view_item(*args)
610
+ if !args[0].is_a?(PredictionIO::AsyncResponse) then
611
+ args.unshift("view")
612
+ end
613
+ user_action_item(*args)
614
+ end
615
+
616
+ # :category: Asynchronous Methods
617
+ # Asynchronously request to record a user-conversion-item action and return a PredictionIO::AsyncResponse object immediately.
618
+ #
619
+ # Corresponding REST API method: POST /actions/u2i/conversion
620
+ #
621
+ # See also #user_conversion_item.
622
+ def auser_conversion_item(uid, iid, params = {})
623
+ auser_action_item("conversion", uid, iid, params)
624
+ end
625
+
626
+ # :category: Synchronous Methods
627
+ # Synchronously request to record a user-conversion-item action and block until a response is received.
628
+ #
629
+ # See also #auser_conversion_item.
630
+ #
631
+ # call-seq:
632
+ # user_conversion_item(uid, iid, params = {})
633
+ # user_conversion_item(async_response)
634
+ def user_conversion_item(*args)
635
+ if !args[0].is_a?(PredictionIO::AsyncResponse) then
636
+ args.unshift("conversion")
637
+ end
638
+ user_action_item(*args)
639
+ end
640
+
641
+ # :nodoc: all
642
+ private
643
+
644
+ def versioned_path(path)
645
+ # disabled for now
646
+ # "/#{@apiversion}#{path}"
647
+ path
648
+ end
649
+
650
+ def auser_action_item(action, uid, iid, params = {})
651
+ rparams = params
652
+ rparams["appkey"] = @appkey
653
+ rparams["uid"] = uid
654
+ rparams["iid"] = iid
655
+ if params["t"] != nil then
656
+ rparams["t"] = ((params["t"].to_r) * 1000).round(0).to_s
657
+ end
658
+ if params["latitude"] != nil && params["longitude"] != nil then
659
+ rparams["latlng"] = "#{params["latitude"]},#{params["longitude"]}"
660
+ end
661
+ @http.apost(PredictionIO::AsyncRequest.new(versioned_path("/actions/u2i/#{action}.#{@apiformat}"), rparams))
662
+ end
663
+
664
+ def user_action_item(*args)
665
+ action_or_res = args[0]
666
+ if action_or_res.is_a?(PredictionIO::AsyncResponse) then
667
+ response = action_or_res.get
668
+ else
669
+ response = auser_action_item(*args).get
670
+ end
671
+ unless response.is_a?(Net::HTTPCreated) then
672
+ begin
673
+ msg = response.body
674
+ rescue Exception
675
+ raise U2IActionNotCreatedError, response
676
+ end
677
+ raise U2IActionNotCreatedError, msg
678
+ end
679
+ end
680
+ end
681
+ end
@@ -0,0 +1,112 @@
1
+ module PredictionIO
2
+
3
+ # This class handles multithreading and asynchronous requests transparently for the REST client.
4
+ class Connection
5
+
6
+ # Number of pending asynchronous request and response packages.
7
+ attr_reader :packages
8
+
9
+ # Number of connections active
10
+ attr_reader :connections
11
+
12
+ # Timeout in seconds
13
+ attr_reader :timeout
14
+
15
+ # Spawns a number of threads with persistent HTTP connection to the specified URI.
16
+ # Sets a default timeout of 5 seconds.
17
+ def initialize(uri, threads = 1, timeout = 5)
18
+ @packages = Queue.new
19
+ @counter_lock = Mutex.new
20
+ @connections = 0
21
+ @timeout = timeout
22
+ threads.times do
23
+ Thread.new do
24
+ begin
25
+ Net::HTTP.start(uri.host, uri.port,
26
+ :use_ssl => uri.scheme == 'https') do |http|
27
+ @counter_lock.synchronize do
28
+ @connections += 1
29
+ end
30
+ catch(:exit) do
31
+ http.read_timeout = @timeout
32
+ loop do
33
+ package = @packages.pop
34
+ request = package[:request]
35
+ response = package[:response]
36
+ case package[:method]
37
+ when "get"
38
+ http_req = Net::HTTP::Get.new("#{uri.path}#{request.qpath}")
39
+ begin
40
+ response.set(http.request(http_req))
41
+ rescue Exception => details
42
+ response.set(details)
43
+ end
44
+ when "post"
45
+ http_req = Net::HTTP::Post.new("#{uri.path}#{request.path}")
46
+ http_req.set_form_data(request.params)
47
+ begin
48
+ response.set(http.request(http_req))
49
+ rescue Exception => details
50
+ response.set(details)
51
+ end
52
+ when "delete"
53
+ http_req = Net::HTTP::Delete.new("#{uri.path}#{request.qpath}")
54
+ begin
55
+ response.set(http.request(http_req))
56
+ rescue Exception => details
57
+ response.set(details)
58
+ end
59
+ when "exit"
60
+ @counter_lock.synchronize do
61
+ @connections -= 1
62
+ end
63
+ throw :exit
64
+ end
65
+ end
66
+ end
67
+ end
68
+ rescue Exception => detail
69
+ @counter_lock.synchronize do
70
+ if @connections == 0 then
71
+ # Use non-blocking pop to avoid dead-locking the current
72
+ # thread when there is no request, and give it a chance to re-connect.
73
+ begin
74
+ package = @packages.pop(true)
75
+ response = package[:response]
76
+ response.set(detail)
77
+ rescue Exception
78
+ end
79
+ end
80
+ end
81
+ sleep(1)
82
+ retry
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # Create an asynchronous request and response package, put it in the pending queue, and return the response object.
89
+ def request(method, request)
90
+ response = AsyncResponse.new(request)
91
+ @packages.push(:method => method,
92
+ :request => request,
93
+ :response => response)
94
+ response
95
+ end
96
+
97
+ # Shortcut to create an asynchronous GET request with the response object returned.
98
+ def aget(areq)
99
+ request("get", areq)
100
+ end
101
+
102
+ # Shortcut to create an asynchronous POST request with the response object returned.
103
+ def apost(areq)
104
+ request("post", areq)
105
+ end
106
+
107
+ # Shortcut to create an asynchronous DELETE request with the response object returned.
108
+ def adelete(areq)
109
+ request("delete", areq)
110
+ end
111
+ end
112
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: predictionio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - The PredictionIO Team
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-25 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ! 'PredictionIO is a prediction server for building smart applications.
15
+ This gem
16
+
17
+ provides convenient access of the PredictionIO API to Ruby programmers so that
18
+
19
+ they can focus on their application logic.
20
+
21
+ '
22
+ email: support@prediction.io
23
+ executables: []
24
+ extensions: []
25
+ extra_rdoc_files: []
26
+ files:
27
+ - lib/predictionio/client.rb
28
+ - lib/predictionio/async_request.rb
29
+ - lib/predictionio/connection.rb
30
+ - lib/predictionio/async_response.rb
31
+ - lib/predictionio.rb
32
+ homepage: http://prediction.io
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options: []
36
+ require_paths:
37
+ - lib
38
+ required_ruby_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '1.9'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 1.8.23
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: PredictionIO Ruby SDK
56
+ test_files: []