invisiblehand 0.1.3 → 0.2
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.
- data/LICENSE +2 -2
- data/README.md +16 -6
- data/lib/invisiblehand/api.rb +40 -11
- data/lib/invisiblehand/page.rb +38 -0
- data/lib/invisiblehand/product.rb +44 -0
- data/lib/invisiblehand/response.rb +28 -0
- data/lib/invisiblehand/version.rb +1 -3
- data/samples/live_price.rb +12 -0
- data/samples/product.rb +20 -0
- data/samples/products.rb +16 -0
- data/spec/api_spec.rb +26 -10
- metadata +10 -4
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
1
|
+
Copyright (c) 2013 InvisibleHand Software Limited
|
2
2
|
|
3
3
|
MIT License
|
4
4
|
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -51,8 +51,9 @@ api = InvisibleHand::API.new "path/to/invisiblehand.yml"
|
|
51
51
|
api.products({
|
52
52
|
:query => "ipad"
|
53
53
|
})
|
54
|
-
#=>
|
55
|
-
#
|
54
|
+
#=> An InvisibleHand::Response object that contains the methods #results and
|
55
|
+
# #info. #results is an array of InvisibleHand::Product objects and #info is
|
56
|
+
# a Hash of meta information on the request (number of results, etc.)
|
56
57
|
|
57
58
|
# Search for products with sorting and ordering and a specific size.
|
58
59
|
api.products({
|
@@ -61,8 +62,6 @@ api.products({
|
|
61
62
|
:order => "desc", # Direction to order results by
|
62
63
|
:size => "100" # Number of results to return
|
63
64
|
})
|
64
|
-
#=> A massive hash that you can find details of at:
|
65
|
-
# https://developer.getinvisiblehand.com/documentation
|
66
65
|
|
67
66
|
# Do a live price search on a product (price comes back as the currency in the
|
68
67
|
# URL you specify. On amazon.com you get dollars, amazon.co.uk you get pounds.)
|
@@ -75,11 +74,22 @@ api.live_price "http://api.invisiblehand.co.uk/v1/pages/live_price?url=http%3A%2
|
|
75
74
|
|
76
75
|
# Search for a specific product by its Invisible Hand ID
|
77
76
|
api.product "f619c3e117d50d1a2b10930e5b202336"
|
78
|
-
#=>
|
79
|
-
# https://developer.getinvisiblehand.com/documentation
|
77
|
+
#=> An InvisibleHand::Product object.
|
80
78
|
|
81
79
|
```
|
82
80
|
|
81
|
+
### Region
|
82
|
+
|
83
|
+
A little clarification surrounding what the `:region` parameter is for in the
|
84
|
+
`invisiblehand.sample.yml` file.
|
85
|
+
|
86
|
+
Because product data differs depending on where that product is being sold (for
|
87
|
+
example, `amazon.com` and `amazon.co.uk` can differ in price and such), the API
|
88
|
+
returns you only the data relevant to the region that you specify.
|
89
|
+
|
90
|
+
A product that exists in our dataset and has only pages in the UK will not be
|
91
|
+
returned for queries that are done on the US API endpoint.
|
92
|
+
|
83
93
|
### Errors
|
84
94
|
|
85
95
|
If the API returns any error information, an `InvisibleHand::Error::APIError` is
|
data/lib/invisiblehand/api.rb
CHANGED
@@ -36,7 +36,7 @@ module InvisibleHand
|
|
36
36
|
|
37
37
|
# The @config[:development] flag exists to bypass the app_id and app_key
|
38
38
|
# check in this gem (not on the server) for internal testing reasons.
|
39
|
-
if
|
39
|
+
if invalid_config? and !@config[:development]
|
40
40
|
message = "Your config does not contain an app_id and app_key. " +
|
41
41
|
"Both are required to make API calls."
|
42
42
|
|
@@ -44,14 +44,37 @@ module InvisibleHand
|
|
44
44
|
end
|
45
45
|
|
46
46
|
@config[:protocol] = @config[:use_ssl] == false ? "http://" : "https://"
|
47
|
+
@config[:version] ||= "1"
|
47
48
|
end
|
48
49
|
|
49
50
|
def products opts = {}
|
50
|
-
api_call :get, "/
|
51
|
+
response = api_call :get, "/products", opts
|
52
|
+
|
53
|
+
if opts[:raw]
|
54
|
+
response
|
55
|
+
else
|
56
|
+
Response.new(response, self)
|
57
|
+
end
|
51
58
|
end
|
52
59
|
|
53
60
|
def product id, opts = {}
|
54
|
-
api_call :get, "/
|
61
|
+
response = api_call :get, "/products/#{CGI.escape(id)}", opts
|
62
|
+
|
63
|
+
if opts[:raw]
|
64
|
+
response
|
65
|
+
else
|
66
|
+
Product.new(response, self)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def page url, opts = {}
|
71
|
+
response = api_call :get, "/pages/?url=#{CGI.escape(url)}", opts
|
72
|
+
|
73
|
+
if opts[:raw]
|
74
|
+
response
|
75
|
+
else
|
76
|
+
Page.new(response, self)
|
77
|
+
end
|
55
78
|
end
|
56
79
|
|
57
80
|
def live_price url, opts = {}
|
@@ -61,14 +84,21 @@ module InvisibleHand
|
|
61
84
|
json["price"]
|
62
85
|
else
|
63
86
|
opts[:url] = url
|
64
|
-
json = api_call :get, "/
|
87
|
+
json = api_call :get, "/pages/live_price", opts
|
65
88
|
json["price"]
|
66
89
|
end
|
67
90
|
end
|
68
91
|
|
69
92
|
def api_call method, path, opts = {}
|
93
|
+
if !@config[:development]
|
94
|
+
opts.merge!({
|
95
|
+
:app_id => @config[:app_id],
|
96
|
+
:app_key => @config[:app_key],
|
97
|
+
})
|
98
|
+
end
|
99
|
+
|
70
100
|
query = url_params_from opts
|
71
|
-
url = "#{@config[:protocol]}#{endpoint}#{path}?#{query}"
|
101
|
+
url = "#{@config[:protocol]}#{endpoint}/v#{@config[:version]}#{path}?#{query}"
|
72
102
|
|
73
103
|
if opts[:debug]
|
74
104
|
debug { api_raw_request method, url }
|
@@ -96,8 +126,9 @@ module InvisibleHand
|
|
96
126
|
old_log_level = logger.level
|
97
127
|
logger.level = ::Logger::DEBUG
|
98
128
|
result = block.call
|
99
|
-
logger.level = old_log_level
|
100
129
|
result
|
130
|
+
ensure
|
131
|
+
logger.level = old_log_level
|
101
132
|
end
|
102
133
|
|
103
134
|
def api_raw_request method, url
|
@@ -124,7 +155,7 @@ module InvisibleHand
|
|
124
155
|
params = hash.map do |key, value|
|
125
156
|
# There are some parameters that will be passed to an API call that we
|
126
157
|
# don't want to include in the query string. Filter them out here.
|
127
|
-
next if [:debug].include? key.to_sym
|
158
|
+
next if [:debug, :raw].include? key.to_sym
|
128
159
|
|
129
160
|
"#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
|
130
161
|
end
|
@@ -132,10 +163,8 @@ module InvisibleHand
|
|
132
163
|
params.compact.join('&')
|
133
164
|
end
|
134
165
|
|
135
|
-
def
|
136
|
-
@config[:app_id].nil?
|
137
|
-
@config[:app_key].nil? and
|
138
|
-
!@config[:development]
|
166
|
+
def invalid_config?
|
167
|
+
(@config[:app_id].nil? or @config[:app_key].nil?)
|
139
168
|
end
|
140
169
|
end
|
141
170
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module InvisibleHand
|
2
|
+
class Page
|
3
|
+
# Array of fields that exist on a page returned from the InvisibleHand
|
4
|
+
# API.
|
5
|
+
FIELDS = [
|
6
|
+
:title,
|
7
|
+
:deeplink,
|
8
|
+
:original_url,
|
9
|
+
:price,
|
10
|
+
:currency,
|
11
|
+
:pnp,
|
12
|
+
:image_url,
|
13
|
+
:retailer_name,
|
14
|
+
:price_confidence,
|
15
|
+
:live_price_url,
|
16
|
+
:ean,
|
17
|
+
:upc,
|
18
|
+
:brand,
|
19
|
+
:model,
|
20
|
+
:mpn,
|
21
|
+
:isbn,
|
22
|
+
:asin,
|
23
|
+
:category,
|
24
|
+
]
|
25
|
+
|
26
|
+
attr_accessor(*FIELDS)
|
27
|
+
|
28
|
+
def initialize raw, api
|
29
|
+
@api = api
|
30
|
+
@raw = raw
|
31
|
+
FIELDS.each { |key| self.send("#{key}=", @raw[key.to_s]) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def live_price opts = {}
|
35
|
+
@api.live_price(self.live_price_url, opts)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module InvisibleHand
|
2
|
+
class Product
|
3
|
+
# Array of fields that exist on a product returned from the InvisibleHand
|
4
|
+
# API.
|
5
|
+
FIELDS = [
|
6
|
+
:pages,
|
7
|
+
:ebay_pages,
|
8
|
+
:best_page,
|
9
|
+
:id,
|
10
|
+
:title,
|
11
|
+
:resource,
|
12
|
+
:upcs,
|
13
|
+
:eans,
|
14
|
+
:mpns,
|
15
|
+
:isbns,
|
16
|
+
:asins,
|
17
|
+
:brands,
|
18
|
+
:models,
|
19
|
+
:categories,
|
20
|
+
:image_url,
|
21
|
+
:number_of_pages,
|
22
|
+
]
|
23
|
+
|
24
|
+
attr_accessor(*FIELDS)
|
25
|
+
|
26
|
+
def initialize raw, api
|
27
|
+
@api = api
|
28
|
+
@raw = raw
|
29
|
+
FIELDS.each { |key| self.send("#{key}=", @raw[key.to_s]) }
|
30
|
+
|
31
|
+
if @raw["pages"]
|
32
|
+
self.pages = @raw["pages"].map { |json| Page.new(json, @api) }
|
33
|
+
end
|
34
|
+
|
35
|
+
if @raw["ebay_pages"]
|
36
|
+
self.ebay_pages = @raw["ebay_pages"].map { |json| Page.new(json, @api) }
|
37
|
+
end
|
38
|
+
|
39
|
+
if @raw["best_page"]
|
40
|
+
self.best_page = Page.new(@raw["best_page"], @api)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module InvisibleHand
|
2
|
+
class Response
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
# Array of fields that exist on a response returned from the InvisibleHand
|
6
|
+
# API.
|
7
|
+
FIELDS = [
|
8
|
+
:info,
|
9
|
+
:results,
|
10
|
+
]
|
11
|
+
|
12
|
+
attr_accessor(*FIELDS)
|
13
|
+
|
14
|
+
def initialize raw, api
|
15
|
+
@api = api
|
16
|
+
@raw = raw
|
17
|
+
FIELDS.each { |key| self.send("#{key}=", @raw[key.to_s]) }
|
18
|
+
|
19
|
+
if @raw["results"]
|
20
|
+
self.results = @raw["results"].map { |json| Product.new(json, @api) }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def each &block
|
25
|
+
self.results.each(&block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'invisiblehand'
|
3
|
+
|
4
|
+
api = InvisibleHand::API.new :app_id => "", :api_key => "", :region => "uk"
|
5
|
+
|
6
|
+
begin
|
7
|
+
price = api.live_price "http://www.amazon.co.uk/Here-And-Now/dp/B0064Y9L9C"
|
8
|
+
|
9
|
+
puts "£%.2f" % price
|
10
|
+
rescue InvisibleHand::Error::APIError => e
|
11
|
+
puts "Oh noes! There was an error: #{e.message}"
|
12
|
+
end
|
data/samples/product.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'invisiblehand'
|
3
|
+
|
4
|
+
api = InvisibleHand::API.new :app_id => "", :api_key => "", :region => "uk"
|
5
|
+
|
6
|
+
begin
|
7
|
+
# The "78b4fb8fa97b8bc746726a32d3393833" bit is a product ID in our database.
|
8
|
+
# You aren't expected to know these beforehand, they will come from other API
|
9
|
+
# calls.
|
10
|
+
product = api.product "78b4fb8fa97b8bc746726a32d3393833"
|
11
|
+
|
12
|
+
product["pages"].each do |page|
|
13
|
+
puts "Retailer: %s" % page["retailer_name"]
|
14
|
+
puts "Title: %s" % page["title"]
|
15
|
+
puts "Price: £%.2f" % page["price"]
|
16
|
+
puts
|
17
|
+
end
|
18
|
+
rescue InvisibleHand::Error::APIError => e
|
19
|
+
puts "Oh noes! There was an error: #{e.message}"
|
20
|
+
end
|
data/samples/products.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'invisiblehand'
|
3
|
+
|
4
|
+
api = InvisibleHand::API.new :app_id => "", :api_key => "", :region => "uk"
|
5
|
+
|
6
|
+
begin
|
7
|
+
response = api.products(:query => "nickelback", :size => 5)
|
8
|
+
|
9
|
+
response["results"].each do |product|
|
10
|
+
puts "Title: %s" % product["best_page"]["title"]
|
11
|
+
puts "Price: £%.2f" % product["best_page"]["price"]
|
12
|
+
puts
|
13
|
+
end
|
14
|
+
rescue InvisibleHand::Error::APIError => e
|
15
|
+
puts "Oh noes! There was an error: #{e.message}"
|
16
|
+
end
|
data/spec/api_spec.rb
CHANGED
@@ -5,6 +5,15 @@ describe InvisibleHand::API do
|
|
5
5
|
# yourself if you wish to run tests.
|
6
6
|
let(:api_config) { File.join(File.dirname(__FILE__), 'invisiblehand.yml') }
|
7
7
|
|
8
|
+
before do
|
9
|
+
unless File.exist?(api_config)
|
10
|
+
puts "Your test config file does not exist. It should be at " +
|
11
|
+
"#{api_config}."
|
12
|
+
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
8
17
|
["uk", "us", "ca", "de"].each do |region|
|
9
18
|
describe "Region: #{region}" do
|
10
19
|
let :api do
|
@@ -14,15 +23,22 @@ describe InvisibleHand::API do
|
|
14
23
|
InvisibleHand::API.new(conf)
|
15
24
|
end
|
16
25
|
|
17
|
-
let(:product) { api.products
|
18
|
-
let(:product_id) { product
|
19
|
-
let(:page) { product
|
26
|
+
let(:product) { api.products.results.first }
|
27
|
+
let(:product_id) { product.id }
|
28
|
+
let(:page) { product.best_page }
|
20
29
|
|
21
30
|
describe "#products" do
|
22
|
-
subject
|
23
|
-
it
|
24
|
-
its(:
|
25
|
-
its(:
|
31
|
+
subject { api.products }
|
32
|
+
it { should be_a InvisibleHand::Response }
|
33
|
+
its(:results) { should be_a Array }
|
34
|
+
its(:info) { should be_a Hash }
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#products :raw => true" do
|
38
|
+
subject { api.products :raw => true }
|
39
|
+
it { should be_a Hash }
|
40
|
+
its(:keys) { should include "results" }
|
41
|
+
its(:keys) { should include "info" }
|
26
42
|
end
|
27
43
|
|
28
44
|
describe "#product" do
|
@@ -32,12 +48,12 @@ describe InvisibleHand::API do
|
|
32
48
|
|
33
49
|
describe "#live_price" do
|
34
50
|
describe "with live price url" do
|
35
|
-
subject {
|
51
|
+
subject { page.live_price }
|
36
52
|
it { should be_a Float }
|
37
53
|
end
|
38
54
|
|
39
55
|
describe "with vanilla page url" do
|
40
|
-
subject { api.live_price(page
|
56
|
+
subject { api.live_price(page.original_url) }
|
41
57
|
it { should be_a Float }
|
42
58
|
end
|
43
59
|
end
|
@@ -45,7 +61,7 @@ describe InvisibleHand::API do
|
|
45
61
|
describe "ad-hoc debug flag" do
|
46
62
|
specify "the debug option to a single call should not break things" do
|
47
63
|
expect do
|
48
|
-
api.live_price(page
|
64
|
+
api.live_price(page.original_url, :debug => true)
|
49
65
|
end.to_not raise_error
|
50
66
|
end
|
51
67
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: invisiblehand
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.2'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -93,7 +93,13 @@ files:
|
|
93
93
|
- lib/invisiblehand/api.rb
|
94
94
|
- lib/invisiblehand/errors.rb
|
95
95
|
- lib/invisiblehand/logger.rb
|
96
|
+
- lib/invisiblehand/page.rb
|
97
|
+
- lib/invisiblehand/product.rb
|
98
|
+
- lib/invisiblehand/response.rb
|
96
99
|
- lib/invisiblehand/version.rb
|
100
|
+
- samples/live_price.rb
|
101
|
+
- samples/product.rb
|
102
|
+
- samples/products.rb
|
97
103
|
- spec/api_spec.rb
|
98
104
|
- spec/spec_helper.rb
|
99
105
|
homepage: ''
|
@@ -110,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
116
|
version: '0'
|
111
117
|
segments:
|
112
118
|
- 0
|
113
|
-
hash:
|
119
|
+
hash: -4521448315867850484
|
114
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
121
|
none: false
|
116
122
|
requirements:
|
@@ -119,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
125
|
version: '0'
|
120
126
|
segments:
|
121
127
|
- 0
|
122
|
-
hash:
|
128
|
+
hash: -4521448315867850484
|
123
129
|
requirements: []
|
124
130
|
rubyforge_project:
|
125
131
|
rubygems_version: 1.8.24
|