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 +4 -4
- data/README.md +96 -128
- data/lib/vacuum.rb +8 -2
- data/lib/vacuum/locale.rb +64 -0
- data/lib/vacuum/matcher.rb +71 -0
- data/lib/vacuum/operation.rb +75 -0
- data/lib/vacuum/request.rb +165 -98
- data/lib/vacuum/resource.rb +62 -0
- data/lib/vacuum/response.rb +21 -9
- data/lib/vacuum/version.rb +1 -1
- data/test/cassettes/vacuum.yml +1904 -765
- data/test/helper.rb +10 -0
- data/test/integration/test_requests.rb +62 -0
- data/test/integration_helper.rb +51 -0
- data/test/locales.rb +23 -0
- data/test/locales.yml +28 -0
- data/test/locales.yml.example +28 -0
- data/test/unit/test_locale.rb +43 -0
- data/test/unit/test_operation.rb +31 -0
- data/test/unit/test_request.rb +61 -0
- data/test/unit/test_resource.rb +12 -0
- data/test/unit/test_response.rb +26 -0
- metadata +58 -20
- data/test/test_integration.rb +0 -69
- data/test/test_unit.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4fabab10f086746082267c99d3c91d1b5efded68a7a39bac4e963125d1979d9
|
4
|
+
data.tar.gz: 8e8b4fc57cbd15da857f867b4ac3a0986a2664157047b0192272140c96a5475f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
+
You need to [register first](https://webservices.amazon.com/paapi5/documentation/register-for-pa-api.html) to use the API.
|
11
12
|
|
12
|
-
|
13
|
+
![vacuum](http://f.cl.ly/items/2k2X0e2u0G3k1c260D2u/vacuum.png)
|
13
14
|
|
14
|
-
|
15
|
+
## Usage
|
15
16
|
|
16
|
-
###
|
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
|
-
|
28
|
+
You can now access the API using the available operations.
|
25
29
|
|
26
30
|
```ruby
|
27
|
-
|
31
|
+
response = request.search_items(title: 'lean startup')
|
32
|
+
puts response.to_h
|
28
33
|
```
|
29
34
|
|
30
|
-
|
35
|
+
Create a persistent connection to make multiple requests.
|
31
36
|
|
32
37
|
```ruby
|
33
|
-
request.
|
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
|
-
|
41
|
+
### Operations
|
41
42
|
|
42
|
-
|
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
|
-
|
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.
|
50
|
+
request.get_browse_nodes(
|
51
|
+
browse_node_ids: ['283155', '3040'],
|
52
|
+
resources: ['BrowseNodes.Ancestor', 'BrowseNodes.Children']
|
53
|
+
)
|
51
54
|
```
|
52
55
|
|
53
|
-
|
56
|
+
#### GetItems
|
54
57
|
|
55
|
-
|
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.
|
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
|
-
|
68
|
+
#### GetVariations
|
62
69
|
|
63
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
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
|
-
####
|
81
|
+
#### SearchItems
|
76
82
|
|
77
|
-
The
|
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
|
-
|
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
|
-
|
89
|
+
### Response
|
90
|
+
|
91
|
+
Consume a response by parsing it into a Ruby hash.
|
90
92
|
|
91
93
|
```ruby
|
92
|
-
response
|
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
|
-
|
97
|
+
You can also `#dig` into this hash.
|
103
98
|
|
104
99
|
```ruby
|
105
|
-
response
|
106
|
-
query: {
|
107
|
-
'CartId' => '123',
|
108
|
-
'HMAC' => 'secret'
|
109
|
-
}
|
110
|
-
)
|
100
|
+
response.dig('ItemsResult', 'Items')
|
111
101
|
```
|
112
102
|
|
113
|
-
|
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
|
-
|
117
|
-
query: {
|
118
|
-
'CartId' => '123',
|
119
|
-
'HMAC' => 'secret',
|
120
|
-
'CartItemId' => '123'
|
121
|
-
}
|
122
|
-
)
|
123
|
-
```
|
108
|
+
require 'logger'
|
124
109
|
|
125
|
-
|
110
|
+
logger = Logger.new(STDOUT)
|
111
|
+
request.use(logging: {logger: logger})
|
112
|
+
```
|
126
113
|
|
127
|
-
|
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 =
|
131
|
-
|
132
|
-
'ItemId' => '0679753354'
|
133
|
-
}
|
134
|
-
)
|
118
|
+
response.parser = MyParser
|
119
|
+
response.parse
|
135
120
|
```
|
136
121
|
|
137
|
-
|
122
|
+
If no custom parser is set, `Vacuum::Response#parse` delegates to `#to_h`.
|
123
|
+
|
124
|
+
### VCR
|
138
125
|
|
139
|
-
|
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
|
-
|
143
|
-
query: {
|
144
|
-
'Keywords' => 'Architecture',
|
145
|
-
'SearchIndex' => 'Books'
|
146
|
-
}
|
147
|
-
)
|
148
|
-
```
|
129
|
+
require 'vacuum/matcher'
|
149
130
|
|
150
|
-
|
131
|
+
# in your test
|
132
|
+
VCR.insert_cassette('cassette_name',
|
133
|
+
match_requests_on: [Vacuum::Matcher])
|
134
|
+
```
|
151
135
|
|
152
|
-
|
136
|
+
In RSpec, use the `:paapi` metadata.
|
153
137
|
|
154
138
|
```ruby
|
155
|
-
|
156
|
-
query: {
|
157
|
-
'ItemId' => '0679753354'
|
158
|
-
}
|
159
|
-
)
|
160
|
-
```
|
139
|
+
require 'vacuum/matcher'
|
161
140
|
|
162
|
-
|
141
|
+
# in your test
|
142
|
+
it 'queries Amazon', :paapi do
|
143
|
+
end
|
144
|
+
```
|
163
145
|
|
164
|
-
|
146
|
+
## Development
|
165
147
|
|
166
|
-
|
148
|
+
Clone the repo and install dependencies.
|
167
149
|
|
168
|
-
```
|
169
|
-
|
170
|
-
query: {
|
171
|
-
'ItemId' => '0679753354'
|
172
|
-
},
|
173
|
-
persistent: true
|
174
|
-
)
|
150
|
+
```sh
|
151
|
+
bundle install
|
175
152
|
```
|
176
153
|
|
177
|
-
|
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
|
-
```
|
182
|
-
|
156
|
+
```sh
|
157
|
+
bundle exec rake
|
183
158
|
```
|
184
159
|
|
185
|
-
|
160
|
+
By default, the tests stub requests. Use the `RECORD` env var to record new interactions.
|
186
161
|
|
187
|
-
```
|
188
|
-
|
162
|
+
```sh
|
163
|
+
RECORD=true bundle exec rake test
|
189
164
|
```
|
190
165
|
|
191
|
-
|
166
|
+
You can set the `LIVE` env var to run all tests against live data.
|
192
167
|
|
193
|
-
```
|
194
|
-
|
195
|
-
|
196
|
-
def self.parse(body)
|
197
|
-
new(body)
|
198
|
-
end
|
168
|
+
```sh
|
169
|
+
LIVE=true bundle exec rake test
|
170
|
+
```
|
199
171
|
|
200
|
-
|
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
|
-
|
205
|
-
|
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`.
|
data/lib/vacuum.rb
CHANGED
@@ -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
|
-
#
|
8
|
+
# Ruby wrapper to the Amazon Product Advertising API
|
8
9
|
module Vacuum
|
9
10
|
class << self
|
10
11
|
extend Forwardable
|
11
12
|
|
12
|
-
|
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
|