invisiblehand 0.1.0

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/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .rvmrc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ invisiblehand.yml
20
+ spec/invisiblehand.yml
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in invisiblehand.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Sam Rose
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
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.
data/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # InvisibleHand API Client Ruby
2
+
3
+ A Ruby client library to the InvisibleHand API. Allows for very easy
4
+ programmatic access to the Invisible Hand API from Ruby.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'invisiblehand'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Alternately you can just install directly through the `gem` command:
17
+
18
+ $ gem install invisiblehand
19
+
20
+ ## Usage
21
+
22
+ The first thing you must do in order to use this gem is create a configuration
23
+ that contains your app ID and app key. You can obtain these from the Invisible
24
+ Hand API website:
25
+ [https://developer.getinvisiblehand.com/](https://developer.getinvisiblehand.com/).
26
+
27
+ The sample configuration in this repository gives you a taste of what the config
28
+ should look like. The gem will look for the config in the following locations in
29
+ the following order:
30
+
31
+ - The path or Hash sent to the constructor
32
+ - An ENV variable called INVISIBLEHAND_CONFIG
33
+ - The current working directory at: ./invisiblehand.yml
34
+
35
+ Some example usage:
36
+
37
+ ``` ruby
38
+ require 'invisiblehand'
39
+
40
+ # Instantiate the API object using the default config location
41
+ # ./invisiblehand.yml
42
+ api = InvisibleHand::API.new
43
+
44
+ # Instantiate the API object with a hard coded, not-in-file config.
45
+ api = InvisibleHand::API.new :app_id => "id", :app_key => "key"
46
+
47
+ # Instantiate the API object from a path to a file.
48
+ api = InvisibleHand::API.new "path/to/invisiblehand.yml"
49
+
50
+ # Search for products that match the query "ipad"
51
+ api.products({
52
+ :query => "ipad"
53
+ })
54
+ #=> A massive hash that you can find details of at:
55
+ # https://developer.getinvisiblehand.com/documentation
56
+
57
+ # Search for products in a different region with a load of other relevant
58
+ # options
59
+ api.products({
60
+ :query => "galaxy", # Search term
61
+ :region => "ca", # Which region to query
62
+ :sort => "popularity", # What to order results by
63
+ :order => "desc", # Direction to order results by
64
+ :size => "100" # Number of results to return
65
+ })
66
+ #=> A massive hash that you can find details of at:
67
+ # https://developer.getinvisiblehand.com/documentation
68
+
69
+ # Do a live price search on a product (price comes back as the currency in the
70
+ # URL you specify. On amazon.com you get dollars, amazon.co.uk you get pounds.)
71
+ api.live_price "http://www.amazon.com/gp/product/B005SUHRZS"
72
+ #=> 11.25
73
+
74
+ # You can also specify an Invisible Hand API link to the live_price method and
75
+ # it will work fine.
76
+ api.live_price "http://api.invisiblehand.co.uk/v1/pages/live_price?url=http%3A%2F%2Fwww.amazon.com%2Fgp%2Fproduct%2FB007PRHNHO"
77
+
78
+ # Search for a specific product by its Invisible Hand ID
79
+ api.product "f619c3e117d50d1a2b10930e5b202336"
80
+ #=> A hash containing details of this item. More info at:
81
+ # https://developer.getinvisiblehand.com/documentation
82
+
83
+ ```
84
+
85
+ ### Errors
86
+
87
+ If the API returns any error information, an `InvisibleHand::Error::APIError` is
88
+ thrown and the `#message` method of the error object will contain the error
89
+ output from the API.
90
+
91
+ ### Logging
92
+
93
+ The InvisibleHand gem does have debug logging that goes to an internal `Logger`
94
+ object. It should not output anything higher than the debug level, which it does
95
+ when the `DEBUG` environment variable is set.
96
+
97
+ If you wish to override the default logging object it builds internally, which
98
+ outputs to STDOUT, you can do so with the following code:
99
+
100
+ ``` ruby
101
+ require 'invisiblehand'
102
+
103
+ # Ignore all InvisibleHand logging.
104
+ InvisibleHand.logger = Logger.new('/dev/null')
105
+ ```
106
+
107
+ ## Debugging
108
+
109
+ The gem looks for a DEBUG environment variable. If DEBUG is set, debugging
110
+ information will be printed out to the screen. This includes URL information
111
+ every time an API call is made.
112
+
113
+ ## Development
114
+
115
+ To run tests, first you will need a valid `invisiblehand.yml` config file inside
116
+ the `spec/` directory. The config you specify must be able to make API calls.
117
+
118
+ Once you have confirmed this, you can run tests with the following command:
119
+
120
+ $ rake
121
+
122
+ And if you wish to see debugging information:
123
+
124
+ $ DEBUG=true rake
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ task :default => [:test]
6
+
7
+ desc "Run all tests"
8
+ RSpec::Core::RakeTask.new(:test) do |t|
9
+ t.rspec_opts = '-cfs'
10
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/invisiblehand/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Sam Rose"]
6
+ gem.email = ["developers@getinvisiblehand.com"]
7
+ gem.description = %q{Ruby API client library to the InvisibleHand API.}
8
+ gem.summary = %q{Allows for easy programmatic access to the InvisibleHand API.}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "invisiblehand"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = InvisibleHand::VERSION
17
+
18
+ # Dependencies
19
+ gem.add_dependency('rest-client')
20
+ gem.add_dependency('json')
21
+
22
+ gem.add_development_dependency('rake')
23
+ gem.add_development_dependency('rspec')
24
+ end
@@ -0,0 +1,11 @@
1
+ :app_id: "your app id here"
2
+ :app_key: "your app key here"
3
+
4
+ # The endpoint is the region of the API you would like to hit. The regions are
5
+ # detailed in the developer documentation:
6
+ #
7
+ # https://developer.getinvisiblehand.com/documentation#intro
8
+ #
9
+ # The option will default to the US if you do not explicitly specify it.
10
+ #
11
+ # :endpoint: us.api.invisiblehand.co.uk
@@ -0,0 +1,23 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH << libdir unless $LOAD_PATH.include? libdir
3
+
4
+ require 'cgi'
5
+ require 'yaml'
6
+ require 'logger'
7
+ require 'rest_client'
8
+ require 'json'
9
+ require 'benchmark'
10
+
11
+ # Requires all .rb file in a given directory.
12
+ def require_all path
13
+ Dir[File.join(File.dirname(__FILE__), path, '*.rb')].each { |f| require f }
14
+ end
15
+
16
+ # Logger is required early because it's a dependency of other classes.
17
+ require 'invisiblehand/logger'
18
+
19
+ require_all 'invisiblehand'
20
+
21
+ module InvisibleHand
22
+ DEBUG = !!ENV['DEBUG']
23
+ end
@@ -0,0 +1,110 @@
1
+ module InvisibleHand
2
+ class API
3
+ include InvisibleHand::Logger
4
+ attr_accessor :config
5
+
6
+ # When initializing a new instance of `InvisibleHand::API`, you can specify
7
+ # configuration in one of three ways:
8
+ #
9
+ # require 'invisiblehand'
10
+ #
11
+ # # This looks first for an environment variable called
12
+ # # "INVISIBLEHAND_CONFIG", which should contain a file path to a config
13
+ # # YAML file. Failing that, "./invisiblehand.yml" is used, looking for
14
+ # # the config YAML file in the current directory.
15
+ # api = InvisibleHand::API.new
16
+ #
17
+ # # This one takes a string argument which should be a valid file path to
18
+ # # the YAML config file.
19
+ # api = InvisibleHand::API.new "path/to/invisiblehand.yml"
20
+ #
21
+ # # Or you can do a literal hash config. This requires no YAML config
22
+ # # file.
23
+ # api = InvisibleHand::API.new :api_key => "...", :app_id => "..."
24
+ #
25
+ # Examples of the configuration variables you can pass in can be found in
26
+ # the "invisiblehand.sample.yml" file in this gem's GitHub repository.
27
+ def initialize conf = nil
28
+ if conf.is_a? Hash
29
+ @config = conf
30
+ elsif conf.is_a? String
31
+ @config = YAML.load_file(conf)
32
+ else
33
+ conf ||= ENV['INVISIBLEHAND_CONFIG'] || './invisiblehand.yml'
34
+ @config = YAML.load_file(conf)
35
+ end
36
+
37
+ # The @config[:development] flag exists to bypass the app_id and app_key
38
+ # check in this gem (not on the server) for internal testing reasons.
39
+ if valid_config?
40
+ raise Error::InvalidConfig.new
41
+ "Your config does not contain an app_id and app_key. " +
42
+ "Both are required to make API calls."
43
+ end
44
+
45
+ @config[:protocol] = @config[:use_ssl] == false ? "http://" : "https://"
46
+ @config[:endpoint] ||= "us.api.invisiblehand.co.uk"
47
+ end
48
+
49
+ def products opts = {}
50
+ api_call :get, "/v1/products", opts
51
+ end
52
+
53
+ def product id, opts = {}
54
+ api_call :get, "/v1/products/#{CGI.escape(id)}", opts
55
+ end
56
+
57
+ def live_price url, opts = {}
58
+ if url =~ /http:\/\/api\.invisiblehand/
59
+ url += url_params_from opts
60
+ json = api_raw_request :get, url
61
+ json["price"]
62
+ else
63
+ opts[:url] = url
64
+ json = api_call :get, "/v1/pages/live_price", opts
65
+ json["price"]
66
+ end
67
+ end
68
+
69
+ def api_call method, path, opts = {}
70
+ query = url_params_from opts
71
+ url = "#{@config[:protocol]}#{@config[:endpoint]}#{path}?#{query}"
72
+
73
+ api_raw_request method, url
74
+ end
75
+
76
+ private
77
+
78
+ def api_raw_request method, url
79
+ logger.debug "API call URL: #{url}"
80
+
81
+ # Declare these early to avoid scoping programs in the timing block.
82
+ response = nil
83
+ json = nil
84
+
85
+ elapsed = Benchmark.realtime do
86
+ response = RestClient.send(method, url) { |resp, req, res| resp }
87
+ json = JSON.parse(response.body)
88
+ end
89
+
90
+ logger.debug "API call took #{elapsed.round(3)} seconds."
91
+ logger.debug "API json response: #{json.inspect}"
92
+
93
+ raise Error::APIError.new(json["error"]) if json["error"]
94
+
95
+ json
96
+ end
97
+
98
+ def url_params_from hash
99
+ hash.map do |key, value|
100
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
101
+ end.join("&")
102
+ end
103
+
104
+ def valid_config?
105
+ @config[:app_id].nil? and
106
+ @config[:app_key].nil? and
107
+ !@config[:development]
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,6 @@
1
+ module InvisibleHand
2
+ module Error
3
+ class InvalidConfig < StandardError; end;
4
+ class APIError < StandardError; end;
5
+ end
6
+ end
@@ -0,0 +1,32 @@
1
+ module InvisibleHand
2
+ def self.logger
3
+ unless @logger
4
+ @logger = ::Logger.new(STDOUT)
5
+
6
+ if DEBUG
7
+ @logger.level = ::Logger::DEBUG
8
+ else
9
+ @logger.level = ::Logger::FATAL
10
+ end
11
+ end
12
+
13
+ @logger
14
+ end
15
+
16
+ # Override the InvisibleHand logger if you wish to have finer grained control
17
+ # over where your application's output is going.
18
+ #
19
+ # InvisibleHand.logger = Logger.new('my/app/logs.log')
20
+ def self.logger= new_logger
21
+ @logger = new_logger
22
+ end
23
+
24
+ # Helper module for classes to include if they want to use the main
25
+ # InvisibleHand logger. Should be used for all in-gem logging as it is
26
+ # configurable by the end-user of the gem.
27
+ module Logger
28
+ def logger
29
+ InvisibleHand.logger
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,5 @@
1
+ module InvisibleHand
2
+ unless const_defined? :VERSION
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
data/spec/api_spec.rb ADDED
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe InvisibleHand::API do
4
+ # This file is not committed to this repository. You will have to create it
5
+ # yourself if you wish to run tests.
6
+ let(:api_config) { File.join(File.dirname(__FILE__), 'invisiblehand.yml') }
7
+
8
+ let(:api) { InvisibleHand::API.new(api_config) }
9
+ let(:product) { api.products["results"].first }
10
+ let(:product_id) { product["id"] }
11
+ let(:page) { product["best_page"] }
12
+
13
+ describe "#products" do
14
+ subject { api.products }
15
+ it { should be_a Hash }
16
+ its(:keys) { should include "results" }
17
+ its(:keys) { should include "info" }
18
+ end
19
+
20
+ describe "#product" do
21
+ subject { product }
22
+ it { should_not be_nil }
23
+ end
24
+
25
+ describe "#live_price" do
26
+ describe "with live price url" do
27
+ subject { api.live_price(page["live_price_url"]) }
28
+ it { should be_a Float }
29
+ end
30
+
31
+ describe "with vanilla page url" do
32
+ subject { api.live_price(page["original_url"]) }
33
+ it { should be_a Float }
34
+ end
35
+ end
36
+
37
+ describe "invalid config" do
38
+ specify "no app_id or api_key should throw error" do
39
+ expect do
40
+ InvisibleHand::API.new
41
+ end.to raise_error InvisibleHand::Error::InvalidConfig
42
+ end
43
+ end
44
+
45
+ describe "invalid api calls" do
46
+ describe "#product" do
47
+ specify "should throw InvisibleHand::Error::APIError on invalid ID" do
48
+ expect do
49
+ api.product "not a real id at all, lol"
50
+ end.to raise_error InvisibleHand::Error::APIError
51
+ end
52
+ end
53
+
54
+ describe "#live_price" do
55
+ specify "should throw InvisibleHand::Error::APIError on invalid URL" do
56
+ expect do
57
+ api.live_price "not a real url, rofl"
58
+ end.to raise_error InvisibleHand::Error::APIError
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'invisiblehand')
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: invisiblehand
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam Rose
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rest-client
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: json
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Ruby API client library to the InvisibleHand API.
79
+ email:
80
+ - developers@getinvisiblehand.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - Gemfile
87
+ - LICENSE
88
+ - README.md
89
+ - Rakefile
90
+ - invisiblehand.gemspec
91
+ - invisiblehand.sample.yml
92
+ - lib/invisiblehand.rb
93
+ - lib/invisiblehand/api.rb
94
+ - lib/invisiblehand/errors.rb
95
+ - lib/invisiblehand/logger.rb
96
+ - lib/invisiblehand/version.rb
97
+ - spec/api_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: ''
100
+ licenses: []
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ segments:
112
+ - 0
113
+ hash: 3768390383107493245
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ segments:
121
+ - 0
122
+ hash: 3768390383107493245
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.24
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Allows for easy programmatic access to the InvisibleHand API.
129
+ test_files:
130
+ - spec/api_spec.rb
131
+ - spec/spec_helper.rb