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.
Files changed (34) hide show
  1. data/README.markdown +24 -29
  2. data/lib/sucker.rb +8 -1
  3. data/lib/sucker/request.rb +47 -28
  4. data/lib/sucker/response.rb +28 -2
  5. data/lib/sucker/version.rb +1 -2
  6. data/spec/integration/errors_spec.rb +0 -1
  7. data/spec/integration/images_spec.rb +0 -1
  8. data/spec/integration/item_lookup_spec.rb +0 -1
  9. data/spec/integration/item_search_spec.rb +0 -2
  10. data/spec/integration/multiple_locales_spec.rb +40 -0
  11. data/spec/integration/related_items_spec.rb +0 -1
  12. data/spec/integration/seller_listing_search_spec.rb +1 -2
  13. data/spec/integration/{twenty_items_in_one_request_spec.rb → twenty_items_spec.rb} +0 -1
  14. data/spec/spec_helper.rb +0 -1
  15. data/spec/support/amazon_credentials.rb +0 -1
  16. data/spec/support/asins.rb +0 -1
  17. data/spec/support/vcr.rb +0 -1
  18. data/spec/unit/sucker/request_spec.rb +3 -10
  19. data/spec/unit/sucker_spec.rb +0 -1
  20. metadata +8 -34
  21. data/spec/fixtures/cassette_library/integration/errors.yml +0 -27
  22. data/spec/fixtures/cassette_library/integration/france.yml +0 -27
  23. data/spec/fixtures/cassette_library/integration/images.yml +0 -27
  24. data/spec/fixtures/cassette_library/integration/item_lookup/multiple.yml +0 -27
  25. data/spec/fixtures/cassette_library/integration/item_lookup/single.yml +0 -27
  26. data/spec/fixtures/cassette_library/integration/item_search.yml +0 -27
  27. data/spec/fixtures/cassette_library/integration/japan.yml +0 -27
  28. data/spec/fixtures/cassette_library/integration/related_items/child.yml +0 -27
  29. data/spec/fixtures/cassette_library/integration/related_items/parent.yml +0 -27
  30. data/spec/fixtures/cassette_library/integration/seller_listings_search.yml +0 -27
  31. data/spec/fixtures/cassette_library/integration/twenty_items.yml +0 -27
  32. data/spec/fixtures/cassette_library/unit/sucker/request.yml +0 -32
  33. data/spec/fixtures/cassette_library/unit/sucker/response.yml +0 -27
  34. 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 cURL and the Nokogiri implementation of the XML Mini module in Active Support. It is blazing fast and supports __the entire API__.
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/540px-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
- Optionally, fiddle with cURL. Say you want to query Amazon through a different network interface:
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
- Hit Amazon.
29
+ Perform the request.
34
30
 
35
31
  response = worker.get
36
32
 
37
- View the internals of the response object.
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
- Cast response as a hash:
43
+ Say you performed an item lookup. Iterate over all items and errors.
49
44
 
50
- pp response.to_hash
45
+ response.node("Item").each { |item| ... }
46
+ response.node("Error").each { |error| ... }
51
47
 
52
- Grab a node:
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
- pp worker.get.node("Item")
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 and then dive into the [API docs](https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html).
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/sucker.rb) is my RSpec helper.
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 and 1.9.2.
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
@@ -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" => api_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
- # Gets Amazon API version.
39
- def api_version
40
- @api_version ||= CURRENT_AMAZON_API_VERSION
41
- end
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 reusable, configurable cURL object
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
- # A helper method that sets the AWS Access Key ID
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,
@@ -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
- attr_accessor :body, :code, :time
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
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  module Sucker #:nodoc
3
- VERSION = "1.0.0.beta.1"
2
+ VERSION = "1.0.0.beta.2"
4
3
  end
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "spec_helper"
3
2
 
4
3
  module Sucker
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "spec_helper"
3
2
 
4
3
  module Sucker
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "spec_helper"
3
2
 
4
3
  module Sucker
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  # http://github.com/papercavalier/sucker/issues#issue/2
4
2
 
5
3
  require "spec_helper"
@@ -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
@@ -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" => "A31N271NVIORU3" }
15
+ "SellerId" => "A2JYSO6W6KEP83" }
17
16
 
18
17
  worker.get.node("SellerListings").first
19
18
  end
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "spec_helper"
3
2
 
4
3
  module Sucker
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require "rubygems"
3
2
  require "bundler/setup"
4
3
  require "rspec"
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  def amazon
3
2
  @amazon ||= YAML::load_file(File.dirname(__FILE__) + "/amazon.yml")
4
3
  end
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  def asins_fixture
3
2
  File.new(File.expand_path("../../fixtures/asins.txt", __FILE__), "r").map { |line| line.chomp }
4
3
  end
data/spec/support/vcr.rb CHANGED
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require 'vcr'
3
2
 
4
3
  VCR.config do |c|
@@ -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 "api_version" do
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.api_version = "foo"
40
- worker.api_version.should eql "foo"
32
+ worker.version = "foo"
33
+ worker.parameters["Version"].should eql "foo"
41
34
  end
42
35
  end
43
36