invisiblehand 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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