invisiblehand 0.1.3 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Sam Rose
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
- #=> A massive hash that you can find details of at:
55
- # https://developer.getinvisiblehand.com/documentation
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
- #=> A hash containing details of this item. More info at:
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
@@ -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 valid_config?
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, "/v1/products", opts
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, "/v1/products/#{CGI.escape(id)}", opts
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, "/v1/pages/live_price", opts
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 valid_config?
136
- @config[:app_id].nil? and
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
@@ -1,5 +1,3 @@
1
1
  module InvisibleHand
2
- unless const_defined? :VERSION
3
- VERSION = "0.1.3"
4
- end
2
+ VERSION = "0.2" unless const_defined? :VERSION
5
3
  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
@@ -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
@@ -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
@@ -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["results"].first }
18
- let(:product_id) { product["id"] }
19
- let(:page) { product["best_page"] }
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 { api.products }
23
- it { should be_a Hash }
24
- its(:keys) { should include "results" }
25
- its(:keys) { should include "info" }
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 { api.live_price(page["live_price_url"]) }
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["original_url"]) }
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["original_url"], :debug => true)
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.1.3
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-26 00:00:00.000000000 Z
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: 1556147975806628177
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: 1556147975806628177
128
+ hash: -4521448315867850484
123
129
  requirements: []
124
130
  rubyforge_project:
125
131
  rubygems_version: 1.8.24