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