sucker 1.0.0.beta.1 → 1.0.0.beta.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/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
|
-

|
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
|
|