sucker 1.0.0.beta.1 → 1.0.0.beta.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +24 -29
- data/lib/sucker.rb +8 -1
- data/lib/sucker/request.rb +47 -28
- data/lib/sucker/response.rb +28 -2
- data/lib/sucker/version.rb +1 -2
- data/spec/integration/errors_spec.rb +0 -1
- data/spec/integration/images_spec.rb +0 -1
- data/spec/integration/item_lookup_spec.rb +0 -1
- data/spec/integration/item_search_spec.rb +0 -2
- data/spec/integration/multiple_locales_spec.rb +40 -0
- data/spec/integration/related_items_spec.rb +0 -1
- data/spec/integration/seller_listing_search_spec.rb +1 -2
- data/spec/integration/{twenty_items_in_one_request_spec.rb → twenty_items_spec.rb} +0 -1
- data/spec/spec_helper.rb +0 -1
- data/spec/support/amazon_credentials.rb +0 -1
- data/spec/support/asins.rb +0 -1
- data/spec/support/vcr.rb +0 -1
- data/spec/unit/sucker/request_spec.rb +3 -10
- data/spec/unit/sucker_spec.rb +0 -1
- metadata +8 -34
- data/spec/fixtures/cassette_library/integration/errors.yml +0 -27
- data/spec/fixtures/cassette_library/integration/france.yml +0 -27
- data/spec/fixtures/cassette_library/integration/images.yml +0 -27
- data/spec/fixtures/cassette_library/integration/item_lookup/multiple.yml +0 -27
- data/spec/fixtures/cassette_library/integration/item_lookup/single.yml +0 -27
- data/spec/fixtures/cassette_library/integration/item_search.yml +0 -27
- data/spec/fixtures/cassette_library/integration/japan.yml +0 -27
- data/spec/fixtures/cassette_library/integration/related_items/child.yml +0 -27
- data/spec/fixtures/cassette_library/integration/related_items/parent.yml +0 -27
- data/spec/fixtures/cassette_library/integration/seller_listings_search.yml +0 -27
- data/spec/fixtures/cassette_library/integration/twenty_items.yml +0 -27
- data/spec/fixtures/cassette_library/unit/sucker/request.yml +0 -32
- data/spec/fixtures/cassette_library/unit/sucker/response.yml +0 -27
- data/spec/support/amazon.yml +0 -2
data/README.markdown
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Sucker
|
2
2
|
======
|
3
3
|
|
4
|
-
Sucker is a Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html). It runs on
|
4
|
+
Sucker is a Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html). It runs on [curb](http://github.com/taf2/curb) and the Nokogiri implementation of the XML Mini module in Active Support. It is blazing fast and supports __the entire API__.
|
5
5
|
|
6
|
-
![Sucker](http://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/FEMA_-_32011_-_FEMA_Joint_Field_Office_%28JFO%29_preparation_in_Ohio.jpg/
|
6
|
+
![Sucker](http://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/FEMA_-_32011_-_FEMA_Joint_Field_Office_%28JFO%29_preparation_in_Ohio.jpg/480px-FEMA_-_32011_-_FEMA_Joint_Field_Office_%28JFO%29_preparation_in_Ohio.jpg)
|
7
7
|
|
8
8
|
Examples
|
9
9
|
--------
|
@@ -15,11 +15,7 @@ Set up a worker.
|
|
15
15
|
:key => "API KEY",
|
16
16
|
:secret => "API SECRET")
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
worker.curl { |c| c.interface = "eth0:0" }
|
21
|
-
|
22
|
-
Set up a request.
|
18
|
+
Prep a request.
|
23
19
|
|
24
20
|
asin_batch = %w{
|
25
21
|
0816614024 0143105825 0485113600 0816616779 0942299078
|
@@ -30,45 +26,44 @@ Set up a request.
|
|
30
26
|
"ItemId" => asin_batch,
|
31
27
|
"ResponseGroup" => ["ItemAttributes", "OfferFull"] }
|
32
28
|
|
33
|
-
|
29
|
+
Perform the request.
|
34
30
|
|
35
31
|
response = worker.get
|
36
32
|
|
37
|
-
|
38
|
-
|
39
|
-
p response.code,
|
40
|
-
response.time,
|
41
|
-
response.body,
|
42
|
-
response.xml
|
43
|
-
|
44
|
-
Confirm response is valid.
|
33
|
+
Debug.
|
45
34
|
|
46
|
-
response.valid?
|
35
|
+
if response.valid?
|
36
|
+
p response.code,
|
37
|
+
response.time,
|
38
|
+
response.body,
|
39
|
+
response.xml,
|
40
|
+
response.to_hash
|
41
|
+
end
|
47
42
|
|
48
|
-
|
43
|
+
Say you performed an item lookup. Iterate over all items and errors.
|
49
44
|
|
50
|
-
|
45
|
+
response.node("Item").each { |item| ... }
|
46
|
+
response.node("Error").each { |error| ... }
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
response.node("Item"),
|
55
|
-
response.node("Error")
|
56
|
-
|
57
|
-
Fetch another ASIN in a more DSL-y way.
|
48
|
+
Perform a new request in a more DSL-y way.
|
58
49
|
|
59
50
|
worker << { "ItemId" => "0486454398" }
|
60
|
-
|
51
|
+
worker.get.node("Item").each { |item| ... }
|
61
52
|
|
62
53
|
Repeat ad infinitum.
|
63
54
|
|
64
|
-
Check the [integration specs](http://github.com/papercavalier/sucker/tree/master/spec/integration/) for more examples
|
55
|
+
Check the [integration specs](http://github.com/papercavalier/sucker/tree/master/spec/integration/) for more examples. In particullar, see [twenty items](http://github.com/papercavalier/sucker/tree/master/spec/integration/twenty_items_spec.rb) and [multiple locales](http://github.com/papercavalier/sucker/tree/master/spec/integration/multiple_locales_spec.rb) for advanced usage.
|
56
|
+
|
57
|
+
Finally, dive into the [API docs](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html) to construct your own queries.
|
65
58
|
|
66
59
|
Stubbing
|
67
60
|
--------
|
68
61
|
|
69
|
-
Use [VCR](http://github.com/myronmarston/vcr). [This](http://github.com/papercavalier/sucker/blob/master/spec/support/
|
62
|
+
Use [VCR](http://github.com/myronmarston/vcr) to stub your requests. [This file](http://github.com/papercavalier/sucker/blob/master/spec/support/vcr.rb) should help you set up VCR with RSpec.
|
63
|
+
|
64
|
+
Don't match requests on URI. The parameters include a timestamp that results in each URI being unique.
|
70
65
|
|
71
66
|
Compatibility
|
72
67
|
-------------
|
73
68
|
|
74
|
-
Specs pass against Ruby 1.8.7
|
69
|
+
Specs pass against Ruby 1.8.7, Ruby 1.9.2, and Rubinius 1.1.
|
data/lib/sucker.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require "active_support/xml_mini/nokogiri"
|
3
2
|
require "curb"
|
4
3
|
require "digest/md5"
|
@@ -7,10 +6,18 @@ require "sucker/response"
|
|
7
6
|
require "uri"
|
8
7
|
|
9
8
|
# = Sucker
|
9
|
+
#
|
10
10
|
# Sucker is a Ruby wrapper to the Amazon Product Advertising API.
|
11
11
|
module Sucker
|
12
12
|
CURRENT_AMAZON_API_VERSION = "2010-09-01"
|
13
13
|
|
14
|
+
# Initializes a request object
|
15
|
+
#
|
16
|
+
# worker = Sucker.new(
|
17
|
+
# :locale => "us",
|
18
|
+
# :key => "API KEY",
|
19
|
+
# :secret => "API SECRET")
|
20
|
+
#
|
14
21
|
def self.new(args={})
|
15
22
|
Sucker::Request.new(args)
|
16
23
|
end
|
data/lib/sucker/request.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
module Sucker #:nodoc
|
3
2
|
|
4
3
|
# A wrapper around the API request
|
@@ -21,10 +20,17 @@ module Sucker #:nodoc
|
|
21
20
|
# The hash of parameters to query Amazon with
|
22
21
|
attr_accessor :parameters
|
23
22
|
|
23
|
+
# Initializes a request object
|
24
|
+
#
|
25
|
+
# worker = Sucker.new(
|
26
|
+
# :locale => "us",
|
27
|
+
# :key => "API KEY",
|
28
|
+
# :secret => "API SECRET")
|
29
|
+
#
|
24
30
|
def initialize(args)
|
25
31
|
self.parameters = {
|
26
32
|
"Service" => "AWSECommerceService",
|
27
|
-
"Version" =>
|
33
|
+
"Version" => CURRENT_AMAZON_API_VERSION
|
28
34
|
}
|
29
35
|
|
30
36
|
args.each { |k, v| send("#{k}=", v) }
|
@@ -35,22 +41,20 @@ module Sucker #:nodoc
|
|
35
41
|
self.parameters.merge!(hash)
|
36
42
|
end
|
37
43
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
# Set Amazon API version.
|
44
|
-
def api_version=(version)
|
45
|
-
@api_version = version
|
46
|
-
end
|
47
|
-
|
48
|
-
# A helper method that sets the associate tag
|
44
|
+
# Sets the associate tag
|
45
|
+
#
|
46
|
+
# worker = Sucker.new
|
47
|
+
# worker.associate_tag = 'foo-bar'
|
48
|
+
#
|
49
49
|
def associate_tag=(token)
|
50
50
|
parameters["AssociateTag"] = token
|
51
51
|
end
|
52
52
|
|
53
|
-
# A
|
53
|
+
# A configurable curl object
|
54
|
+
#
|
55
|
+
# worker = Sucker.new
|
56
|
+
# worker.curl { |c| c.interface = "eth1" }
|
57
|
+
#
|
54
58
|
def curl
|
55
59
|
@curl ||= Curl::Easy.new
|
56
60
|
yield @curl if block_given?
|
@@ -58,6 +62,10 @@ module Sucker #:nodoc
|
|
58
62
|
end
|
59
63
|
|
60
64
|
# Performs the request and returns a response object
|
65
|
+
#
|
66
|
+
# worker = Sucker.new
|
67
|
+
# response = worker.get
|
68
|
+
#
|
61
69
|
def get
|
62
70
|
curl.url = uri.to_s
|
63
71
|
curl.perform
|
@@ -65,11 +73,24 @@ module Sucker #:nodoc
|
|
65
73
|
Response.new(curl)
|
66
74
|
end
|
67
75
|
|
68
|
-
#
|
76
|
+
# Sets the AWS Access Key ID
|
77
|
+
#
|
78
|
+
# worker = Sucker.new
|
79
|
+
# worker.key = 'foo'
|
80
|
+
#
|
69
81
|
def key=(token)
|
70
82
|
parameters["AWSAccessKeyId"] = token
|
71
83
|
end
|
72
84
|
|
85
|
+
# Sets Amazon API version.
|
86
|
+
#
|
87
|
+
# worker = Sucker.new
|
88
|
+
# worker.version = '"2010-06-01"'
|
89
|
+
#
|
90
|
+
def version=(version)
|
91
|
+
self.parameters["Version"] = version
|
92
|
+
end
|
93
|
+
|
73
94
|
private
|
74
95
|
|
75
96
|
# Timestamps parameters and concatenates them into a query string
|
@@ -83,19 +104,6 @@ module Sucker #:nodoc
|
|
83
104
|
join("&")
|
84
105
|
end
|
85
106
|
|
86
|
-
def escape(string)
|
87
|
-
|
88
|
-
# Shamelessly plagiarized from ruby_aaws, which in turn plagiarizes
|
89
|
-
# from the Ruby CGI library. All to please Amazon.
|
90
|
-
string.gsub( /([^a-zA-Z0-9_.~-]+)/ ) do
|
91
|
-
'%' + $1.unpack( 'H2' * $1.bytesize ).join( '%' ).upcase
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def host
|
96
|
-
HOSTS[locale.to_sym]
|
97
|
-
end
|
98
|
-
|
99
107
|
# Returns a signed and timestamped query string
|
100
108
|
def build_signed_query
|
101
109
|
query = build_query
|
@@ -107,6 +115,17 @@ module Sucker #:nodoc
|
|
107
115
|
query + "&Signature=" + escape([hmac].pack("m").chomp)
|
108
116
|
end
|
109
117
|
|
118
|
+
# Plagiarized from the Ruby CGI library via ruby_aaws
|
119
|
+
def escape(string)
|
120
|
+
string.gsub( /([^a-zA-Z0-9_.~-]+)/ ) do
|
121
|
+
'%' + $1.unpack( 'H2' * $1.bytesize ).join( '%' ).upcase
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def host
|
126
|
+
HOSTS[locale.to_sym]
|
127
|
+
end
|
128
|
+
|
110
129
|
def uri
|
111
130
|
URI::HTTP.build(
|
112
131
|
:host => host,
|
data/lib/sucker/response.rb
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
module Sucker #:nodoc
|
3
2
|
|
4
3
|
# A Nokogiri-driven wrapper around the cURL response
|
5
4
|
class Response
|
6
|
-
|
5
|
+
|
6
|
+
# The response body
|
7
|
+
attr_accessor :body
|
8
|
+
|
9
|
+
# The HTTP status code of the response
|
10
|
+
attr_accessor :code
|
11
|
+
|
12
|
+
# Transaction time in seconds for request
|
13
|
+
attr_accessor :time
|
7
14
|
|
8
15
|
def initialize(curl)
|
9
16
|
self.body = curl.body_str
|
@@ -12,27 +19,46 @@ module Sucker #:nodoc
|
|
12
19
|
end
|
13
20
|
|
14
21
|
# Queries an xpath and returns result as an array of hashes
|
22
|
+
#
|
23
|
+
# For instance, to get all items in an ItemLookup query:
|
24
|
+
#
|
25
|
+
# response = worker.get
|
26
|
+
# response.node("Item").each { |item| ... }
|
27
|
+
#
|
15
28
|
def node(path)
|
16
29
|
xml.xpath("//xmlns:#{path}").map { |node| strip_content(node.to_hash[path]) }
|
17
30
|
end
|
18
31
|
|
19
32
|
# Parses the response into a simple hash
|
33
|
+
#
|
34
|
+
# response = worker.get
|
35
|
+
# response.to_hash
|
36
|
+
#
|
20
37
|
def to_hash
|
21
38
|
strip_content(xml.to_hash)
|
22
39
|
end
|
23
40
|
|
24
41
|
# Checks if the HTTP response is OK
|
42
|
+
#
|
43
|
+
# response = worker.get
|
44
|
+
# p response.valid?
|
45
|
+
# => true
|
46
|
+
#
|
25
47
|
def valid?
|
26
48
|
code == 200
|
27
49
|
end
|
28
50
|
|
29
51
|
# The XML document
|
52
|
+
#
|
53
|
+
# response = worker.get
|
54
|
+
# response.xml
|
30
55
|
def xml
|
31
56
|
@xml ||= Nokogiri::XML(body)
|
32
57
|
end
|
33
58
|
|
34
59
|
private
|
35
60
|
|
61
|
+
# Let's massage the somewhat-verbose XML Mini hash into better shape
|
36
62
|
def strip_content(node)
|
37
63
|
case node
|
38
64
|
when Array
|
data/lib/sucker/version.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
module Sucker
|
5
|
+
describe "Multiple locales" do
|
6
|
+
use_vcr_cassette "integration/multiple_locales", :record => :new_episodes
|
7
|
+
|
8
|
+
it "threads multiple requests" do
|
9
|
+
locales = %w{us uk de ca fr jp}
|
10
|
+
|
11
|
+
params = {
|
12
|
+
"Operation" => "ItemLookup",
|
13
|
+
"IdType" => "ASIN",
|
14
|
+
"Condition" => "All",
|
15
|
+
"MerchantId" => "All",
|
16
|
+
"ResponseGroup" => "ItemAttributes",
|
17
|
+
"ItemId" => "0816614024" }
|
18
|
+
|
19
|
+
threads = locales.map do |locale|
|
20
|
+
Thread.new do
|
21
|
+
worker = Sucker.new(
|
22
|
+
:locale => locale,
|
23
|
+
:key => amazon["key"],
|
24
|
+
:secret => amazon["secret"])
|
25
|
+
worker << params
|
26
|
+
Thread.current[:response] = worker.get
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
bindings = []
|
31
|
+
threads.each do |thread|
|
32
|
+
thread.join
|
33
|
+
item = thread[:response].node("Item").first
|
34
|
+
bindings << item["ItemAttributes"]["Binding"]
|
35
|
+
end
|
36
|
+
|
37
|
+
bindings.uniq.should =~ %w{ Paperback Taschenbuch Broché ペーパーバック }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require "spec_helper"
|
3
2
|
|
4
3
|
module Sucker
|
@@ -13,7 +12,7 @@ module Sucker
|
|
13
12
|
|
14
13
|
worker << {
|
15
14
|
"Operation" => "SellerListingSearch",
|
16
|
-
"SellerId" => "
|
15
|
+
"SellerId" => "A2JYSO6W6KEP83" }
|
17
16
|
|
18
17
|
worker.get.node("SellerListings").first
|
19
18
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/asins.rb
CHANGED
data/spec/support/vcr.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
require "spec_helper"
|
3
2
|
|
4
3
|
module Sucker
|
@@ -28,16 +27,10 @@ module Sucker
|
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
31
|
-
context "
|
32
|
-
it "has a default value" do
|
33
|
-
worker.api_version.should_not be_nil
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
context "#api_version=" do
|
30
|
+
context "#version=" do
|
38
31
|
it "sets the Amazon API version" do
|
39
|
-
worker.
|
40
|
-
worker.
|
32
|
+
worker.version = "foo"
|
33
|
+
worker.parameters["Version"].should eql "foo"
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|