vacuum 2.2.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de7766702e1ee1d5084f61f8a337ff64b9e1210fd70167ee8e932b742eb522e5
4
- data.tar.gz: b2a917e6f5d91bf0177471a65d319bc0d4438b773643d977f53c148d7d2f3770
3
+ metadata.gz: a4fabab10f086746082267c99d3c91d1b5efded68a7a39bac4e963125d1979d9
4
+ data.tar.gz: 8e8b4fc57cbd15da857f867b4ac3a0986a2664157047b0192272140c96a5475f
5
5
  SHA512:
6
- metadata.gz: 276f630beba5150eb04b20c81ab9feb41db2488773e548da9d9f0df8c3b6a4197eb1948b84d467614de450b74e81023ef8ce5533abb03f4ebf8fec27a322b8a5
7
- data.tar.gz: 87b683c87cb51462030f0ba2880e54e1b683723179a658f94dec92cad496b5ae5133c8cfd70b2844b187a108162adca3a3f4153cc5c45a5301826342e9040507
6
+ metadata.gz: 6075c9237d5b5556312e5afbce35d5e4cd573cf705de305e04ffa3e9a853202d6f4499937f991eda1c65c33a84c5e8625f043eb391161fd96e4a827aa9f6f5a5
7
+ data.tar.gz: e031688b51fed66acd8b8442e2475c1b0d6ec7ed991e74e6df63c427017407da0799cbca46baa0a496b480fd9725d80f8e5ad379fb872c4448d6fd43bc419621
data/README.md CHANGED
@@ -1,211 +1,179 @@
1
1
  # Vacuum
2
- [![Travis](https://travis-ci.org/hakanensari/vacuum.svg)](https://travis-ci.org/hakanensari/vacuum)
3
2
 
4
- Vacuum is a fast, light-weight Ruby wrapper to the [Amazon Product Advertising API](https://affiliate-program.amazon.com/gp/advertising/api/detail/main.html).
3
+ [![Build](https://github.com/hakanensari/vacuum/workflows/build/badge.svg)](https://github.com/hakanensari/vacuum/actions)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/ffbab4aa5fedf5780332/maintainability)](https://codeclimate.com/github/hakanensari/vacuum/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/ffbab4aa5fedf5780332/test_coverage)](https://codeclimate.com/github/hakanensari/vacuum/test_coverage)
5
6
 
6
- ![vacuum](http://f.cl.ly/items/2k2X0e2u0G3k1c260D2u/vacuum.png)
7
+ Vacuum is a Ruby wrapper to [Amazon Product Advertising API 5.0](https://webservices.amazon.com/paapi5/documentation/). The API provides programmatic access to query product information on the Amazon marketplaces.
7
8
 
8
- ## Usage
9
+ Cart Form functionality is not covered by this gem but is a primary focus on [carriage gem](https://github.com/skatkov/carriage)
9
10
 
10
- Refer to the [API docs](https://docs.aws.amazon.com/AWSECommerceService/latest/DG/CHAP_ApiReference.html) as necessary.
11
+ You need to [register first](https://webservices.amazon.com/paapi5/documentation/register-for-pa-api.html) to use the API.
11
12
 
12
- ### Prerequisite
13
+ ![vacuum](http://f.cl.ly/items/2k2X0e2u0G3k1c260D2u/vacuum.png)
13
14
 
14
- You must [register as an affiliate](https://affiliate-program.amazon.com) to access the API. Amazon will issue your AWS credentials when you register.
15
+ ## Usage
15
16
 
16
- ### Setup
17
+ ### Getting Started
17
18
 
18
- Create a request:
19
+ Create a request with your marketplace credentials. Set the marketplace by passing its two-letter country code.
19
20
 
20
21
  ```ruby
21
- request = Vacuum.new
22
+ request = Vacuum.new(marketplace: 'US',
23
+ access_key: '<ACCESS_KEY>',
24
+ secret_key: '<SECRET_KEY>',
25
+ partner_tag: '<PARTNER_TAG>')
22
26
  ```
23
27
 
24
- The locale will default to the US. To use another locale, reference its two-letter country code:
28
+ You can now access the API using the available operations.
25
29
 
26
30
  ```ruby
27
- request = Vacuum.new('GB')
31
+ response = request.search_items(title: 'lean startup')
32
+ puts response.to_h
28
33
  ```
29
34
 
30
- Configure the request credentials:
35
+ Create a persistent connection to make multiple requests.
31
36
 
32
37
  ```ruby
33
- request.configure(
34
- aws_access_key_id: 'key',
35
- aws_secret_access_key: 'secret',
36
- associate_tag: 'tag'
37
- )
38
+ request.persistent
38
39
  ```
39
40
 
40
- You can omit the above if you set your key and secret as environment variables:
41
+ ### Operations
41
42
 
42
- ```sh
43
- export AWS_ACCESS_KEY_ID=key
44
- export AWS_SECRET_ACCESS_KEY=secret
45
- ```
43
+ Refer to the [API docs](https://webservices.amazon.com/paapi5/documentation/) for more detailed information.
46
44
 
47
- You will still need to set an associate tag:
45
+ #### GetBrowseNodes
46
+
47
+ Given a BrowseNodeId, the `GetBrowseNodes` operation returns details about the specified browse node, like name, children and ancestors, depending on the resources specified in the request. The names and browse node IDs of the children and ancestor browse nodes are also returned. `GetBrowseNodes` enables you to traverse the browse node hierarchy to find a browse node.
48
48
 
49
49
  ```ruby
50
- request.associate_tag = 'tag'
50
+ request.get_browse_nodes(
51
+ browse_node_ids: ['283155', '3040'],
52
+ resources: ['BrowseNodes.Ancestor', 'BrowseNodes.Children']
53
+ )
51
54
  ```
52
55
 
53
- Provided you are looking to earn commission, you have to register independently with each locale you query. Otherwise, you may reuse any dummy associate tag.
56
+ #### GetItems
54
57
 
55
- The API version defaults to `2013-08-01`. To use another version, reference its date string:
58
+ Given an Item identifier, the `GetItems` operation returns the item attributes, based on the resources specified in the request.
56
59
 
57
60
  ```ruby
58
- request.version = '2011-08-01'
61
+ request.get_items(
62
+ item_ids: ['B0199980K4', 'B000HZD168', 'B01180YUXS', 'B00BKQTA4A'],
63
+ resources: ['Images.Primary.Small', 'ItemInfo.Title', 'ItemInfo.Features',
64
+ 'Offers.Summaries.HighestPrice' , 'ParentASIN']
65
+ )
59
66
  ```
60
67
 
61
- ### Request
68
+ #### GetVariations
62
69
 
63
- #### Browse Node Lookup
64
-
65
- **BrowseNodeLookup** returns a specified browse node’s name and ancestors:
70
+ Given an ASIN, the `GetVariations` operation returns a set of items that are the same product, but differ according to a consistent theme, for example size and color. These items which differ according to a consistent theme are called variations. A variation is a child ASIN. The parent ASIN is an abstraction of the children items. For example, a shirt is a parent ASIN and parent ASINs cannot be sold. A child ASIN would be a blue shirt, size 16, sold by MyApparelStore. This child ASIN is one of potentially many variations. The ways in which variations differ are called dimensions.
66
71
 
67
72
  ```ruby
68
- response = request.browse_node_lookup(
69
- query: {
70
- 'BrowseNodeId' => 123
71
- }
73
+ request.get_variations(
74
+ asin: 'B00422MCUS',
75
+ resources: ['ItemInfo.Title', 'VariationSummary.Price.HighestPrice',
76
+ 'VariationSummary.Price.LowestPrice',
77
+ 'VariationSummary.VariationDimension']
72
78
  )
73
79
  ```
74
80
 
75
- #### Cart Operations
81
+ #### SearchItems
76
82
 
77
- The **CartCreate** operation creates a remote shopping cart:
83
+ The `SearchItems` operation searches for items on Amazon based on a search query. The API returns up to ten items per search request.
78
84
 
79
85
  ```ruby
80
- response = request.cart_create(
81
- query: {
82
- 'HMAC' => 'secret',
83
- 'Item.1.OfferListingId' => '123',
84
- 'Item.1.Quantity' => 1
85
- }
86
- )
86
+ request.search_items(keywords: 'harry potter')
87
87
  ```
88
88
 
89
- The **CartAdd** operation adds items to an existing remote shopping cart:
89
+ ### Response
90
+
91
+ Consume a response by parsing it into a Ruby hash.
90
92
 
91
93
  ```ruby
92
- response = request.cart_add(
93
- query: {
94
- 'CartId' => '123',
95
- 'HMAC' => 'secret',
96
- 'Item.1.OfferListingId' => '123',
97
- 'Item.1.Quantity' => 1
98
- }
99
- )
94
+ response.to_h
100
95
  ```
101
96
 
102
- The **CartClear** operation removes all of the items in a remote shopping cart:
97
+ You can also `#dig` into this hash.
103
98
 
104
99
  ```ruby
105
- response = request.cart_clear(
106
- query: {
107
- 'CartId' => '123',
108
- 'HMAC' => 'secret'
109
- }
110
- )
100
+ response.dig('ItemsResult', 'Items')
111
101
  ```
112
102
 
113
- The **CartGet** operation retrieves the IDs, quantities, and prices of the items, including SavedForLater ones, in a remote shopping cart:
103
+ ### Logging
104
+
105
+ Write requests and reponses to a logger using the logging feature of the [HTTP gem](https://github.com/httprb/http) under the hood:
114
106
 
115
107
  ```ruby
116
- response = request.cart_get(
117
- query: {
118
- 'CartId' => '123',
119
- 'HMAC' => 'secret',
120
- 'CartItemId' => '123'
121
- }
122
- )
123
- ```
108
+ require 'logger'
124
109
 
125
- #### Item Lookup
110
+ logger = Logger.new(STDOUT)
111
+ request.use(logging: {logger: logger})
112
+ ```
126
113
 
127
- The **ItemLookup** operation returns some or all of the attributes of an item, depending on the response group specified in the request. By default, the operation returns an item’s ASIN, manufacturer, product group, and title.
114
+ ### Bring your parser
115
+ You can extend Vacuum with a custom parser. Just swap the original with a class or module that responds to `.parse`.
128
116
 
129
117
  ```ruby
130
- response = request.item_lookup(
131
- query: {
132
- 'ItemId' => '0679753354'
133
- }
134
- )
118
+ response.parser = MyParser
119
+ response.parse
135
120
  ```
136
121
 
137
- #### Item Search
122
+ If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
123
+
124
+ ### VCR
138
125
 
139
- The **ItemSearch** operation returns items that satisfy the search criteria, including one or more search indices.
126
+ If you are using [VCR](https://github.com/vcr/vcr) to test an app that accesses the API, you can use the custom VCR matcher of Vacuum to stub requests.
140
127
 
141
128
  ```ruby
142
- response = request.item_search(
143
- query: {
144
- 'Keywords' => 'Architecture',
145
- 'SearchIndex' => 'Books'
146
- }
147
- )
148
- ```
129
+ require 'vacuum/matcher'
149
130
 
150
- #### Similarity Lookup
131
+ # in your test
132
+ VCR.insert_cassette('cassette_name',
133
+ match_requests_on: [Vacuum::Matcher])
134
+ ```
151
135
 
152
- The **SimilarityLookup** operation returns up to ten products per page that are similar to one or more items specified in the request. This operation is typically used to pique a customer’s interest in buying something similar to what they’ve already ordered.
136
+ In RSpec, use the `:paapi` metadata.
153
137
 
154
138
  ```ruby
155
- response = request.similarity_lookup(
156
- query: {
157
- 'ItemId' => '0679753354'
158
- }
159
- )
160
- ```
139
+ require 'vacuum/matcher'
161
140
 
162
- #### Configuring a request
141
+ # in your test
142
+ it 'queries Amazon', :paapi do
143
+ end
144
+ ```
163
145
 
164
- Vacuum wraps [Excon](https://github.com/geemus/excon). Use the latter's API to tweak your request.
146
+ ## Development
165
147
 
166
- For example, to use a persistent connection:
148
+ Clone the repo and install dependencies.
167
149
 
168
- ```ruby
169
- response = request.item_search(
170
- query: {
171
- 'ItemId' => '0679753354'
172
- },
173
- persistent: true
174
- )
150
+ ```sh
151
+ bundle install
175
152
  ```
176
153
 
177
- ### Response
178
-
179
- The quick and dirty way to consume a response is to parse into a Ruby hash:
154
+ Tests and Rubocop should now pass as-is.
180
155
 
181
- ```ruby
182
- response.to_h
156
+ ```sh
157
+ bundle exec rake
183
158
  ```
184
159
 
185
- You can also `#dig` into returned Hash:
160
+ By default, the tests stub requests. Use the `RECORD` env var to record new interactions.
186
161
 
187
- ```ruby
188
- response.dig('ItemSearchResponse', 'Items', 'Item')
162
+ ```sh
163
+ RECORD=true bundle exec rake test
189
164
  ```
190
165
 
191
- In production, you may prefer to use a custom parser to do some XML heavy-lifting:
166
+ You can set the `LIVE` env var to run all tests against live data.
192
167
 
193
- ```ruby
194
- class MyParser
195
- # A parser has to respond to this.
196
- def self.parse(body)
197
- new(body)
198
- end
168
+ ```sh
169
+ LIVE=true bundle exec rake test
170
+ ```
199
171
 
200
- def initialize(body)
201
- @body = body
202
- end
172
+ In either case, add actual API credentials to a [`locales.yml`](https://github.com/hakanensari/vacuum/blob/master/test/locales.yml.example) file under `test`.
203
173
 
204
- # Implement parser here.
205
- end
174
+ ## Getting Help
175
+
176
+ * Ask specific questions about the API on the [Amazon forum](https://forums.aws.amazon.com/forum.jspa?forumID=9).
177
+ * Report bugs and discuss potential features in [GitHub issues](https://github.com/hakanensari/vacuum/issues).
206
178
 
207
- response.parser = MyParser
208
- response.parse
209
- ```
210
179
 
211
- If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+
4
5
  require 'vacuum/request'
5
6
  require 'vacuum/version'
6
7
 
7
- # Vacuum is a Ruby wrapper to the Amazon Product Advertising API.
8
+ # Ruby wrapper to the Amazon Product Advertising API
8
9
  module Vacuum
9
10
  class << self
10
11
  extend Forwardable
11
12
 
12
- def_delegator Vacuum::Request, :new
13
+ # @!method new
14
+ # Delegates to {Request} to create a new request
15
+ #
16
+ # @return [Request]
17
+ # @see Request#initialize
18
+ def_delegator 'Vacuum::Request', :new
13
19
  end
14
20
  end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vacuum
4
+ # The target locale
5
+ #
6
+ # @see https://webservices.amazon.com/paapi5/documentation/common-request-parameters.html#host-and-region
7
+ class Locale
8
+ # Raised when the provided marketplace does not correspond to an existing
9
+ # Amazon locale
10
+ class NotFound < KeyError; end
11
+
12
+ HOSTS_AND_REGIONS = {
13
+ au: ['webservices.amazon.com.au', 'us-west-2'],
14
+ br: ['webservices.amazon.com.br', 'us-east-1'],
15
+ ca: ['webservices.amazon.ca', 'us-east-1'],
16
+ fr: ['webservices.amazon.fr', 'eu-west-1'],
17
+ de: ['webservices.amazon.de', 'eu-west-1'],
18
+ in: ['webservices.amazon.in', 'eu-west-1'],
19
+ it: ['webservices.amazon.it', 'eu-west-1'],
20
+ jp: ['webservices.amazon.co.jp', 'us-west-2'],
21
+ mx: ['webservices.amazon.com.mx', 'us-east-1'],
22
+ nl: ['webservices.amazon.nl', 'eu-west-1'],
23
+ sg: ['webservices.amazon.sg', 'us-west-2'],
24
+ es: ['webservices.amazon.es', 'eu-west-1'],
25
+ tr: ['webservices.amazon.com.tr', 'eu-west-1'],
26
+ ae: ['webservices.amazon.ae', 'eu-west-1'],
27
+ gb: ['webservices.amazon.co.uk', 'eu-west-1'],
28
+ us: ['webservices.amazon.com', 'us-east-1']
29
+ }.freeze
30
+ private_constant :HOSTS_AND_REGIONS
31
+
32
+ # @return [String]
33
+ attr_reader :host, :region, :access_key, :secret_key, :partner_tag,
34
+ :partner_type
35
+
36
+ # Creates a locale
37
+ #
38
+ # @param [Symbol,String] marketplace
39
+ # @param [String] access_key
40
+ # @param [String] secret_key
41
+ # @param [String] partner_tag
42
+ # @param [String] partner_type
43
+ # @raise [NotFound] if marketplace is not found
44
+ def initialize(marketplace, access_key:, secret_key:, partner_tag:,
45
+ partner_type: 'Associates')
46
+ @host, @region = find_host_and_region(marketplace)
47
+ @access_key = access_key
48
+ @secret_key = secret_key
49
+ @partner_tag = partner_tag
50
+ @partner_type = partner_type
51
+ end
52
+
53
+ private
54
+
55
+ def find_host_and_region(marketplace)
56
+ marketplace = marketplace.to_sym.downcase
57
+ marketplace = :gb if marketplace == :uk
58
+
59
+ HOSTS_AND_REGIONS.fetch(marketplace)
60
+ rescue KeyError
61
+ raise NotFound, "marketplace not found: :#{marketplace}"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Vacuum
6
+ # Custom VCR matcher for stubbing calls to the Product Advertising API
7
+ #
8
+ # The matcher is not required by default.
9
+ #
10
+ # @example
11
+ # require 'vacuum/matcher'
12
+ #
13
+ # # in your test
14
+ # VCR.insert_cassette('cassette_name',
15
+ # match_requests_on: [Vacuum::Matcher])
16
+ #
17
+ # @see https://relishapp.com/vcr/vcr/v/5-0-0/docs/request-matching/register-and-use-a-custom-matcher
18
+ class Matcher
19
+ IGNORED_KEYS = %w[PartnerTag].freeze
20
+ private_constant :IGNORED_KEYS
21
+
22
+ # @!visibility private
23
+ attr_reader :requests
24
+
25
+ # @!visibility private
26
+ def self.call(*requests)
27
+ new(*requests).compare
28
+ end
29
+
30
+ # @!visibility private
31
+ def initialize(*requests)
32
+ @requests = requests
33
+ end
34
+
35
+ # @!visibility private
36
+ def compare
37
+ uris.reduce(:==) && bodies.reduce(:==)
38
+ end
39
+
40
+ private
41
+
42
+ def uris
43
+ requests.map(&:uri)
44
+ end
45
+
46
+ def bodies
47
+ requests.map do |req|
48
+ params = JSON.parse(req.body)
49
+ IGNORED_KEYS.each { |k| params.delete(k) }
50
+
51
+ params
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ if defined?(RSpec)
58
+ RSpec.configure do |config|
59
+ config.around do |example|
60
+ if example.metadata[:paapi]
61
+ metadata = example.metadata[:paapi]
62
+ metadata = {} if metadata == true
63
+ example.metadata[:vcr] = metadata.merge(
64
+ match_requests_on: [Vacuum::Matcher]
65
+ )
66
+ end
67
+
68
+ example.run
69
+ end
70
+ end
71
+ end