shopify_api_retry 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/ci.yml +22 -0
- data/README.md +51 -12
- data/Rakefile +7 -0
- data/lib/shopify_api_retry.rb +108 -16
- data/shopify_api_retry.gemspec +4 -3
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 949d65d19ed205d18a64ea886ef817bcd9d8b26895036228dddc3dcf4d91f0ca
|
4
|
+
data.tar.gz: 6b776cf5985be1593e791a81c6f4ce31c88ca4f029c7ba03bd8e24ca5f44f26f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 781e898074a0f3b9aaed28f1202087173e42483920375f7921384854437f9ed40ad77a0ef37be5d1221f5e76141c988a35e50368da22e83b5e8722c185558e70
|
7
|
+
data.tar.gz: 243547c486e88cfabed0ea4a2c4fbba9524aa44234c5852110cf4a485f28c551309e78a055222576da2bcc6240232d783706807ebeee31c41956dddd6e130495
|
@@ -0,0 +1,22 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
- push
|
5
|
+
- pull_request
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
strategy:
|
11
|
+
matrix:
|
12
|
+
ruby: [3.0.0, 2.7.2, 2.6.6, 2.5.8, 2.4.10]
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v2
|
16
|
+
- uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
bundler-cache: true
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
20
|
+
|
21
|
+
- run: bundle install
|
22
|
+
- run: bundle exec rake
|
data/README.md
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
# ShopifyAPIRetry
|
2
2
|
|
3
|
-
|
3
|
+
![CI](https://github.com/ScreenStaring/shopify_api_retry/workflows/CI/badge.svg)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
```rb
|
8
|
-
ShopifyAPIRetry.retry { customer.update_attribute(:tags, "foo") }
|
9
|
-
ShopifyAPIRetry.retry(30) { customer.update_attribute(:tags, "foo") } # Retry after 30 seconds on HTTP 429
|
10
|
-
customer = ShopifyAPIRetry.retry { ShopifyAPI::Customer.find(id) }
|
11
|
-
```
|
12
|
-
|
13
|
-
If no retry time is provided the value of the HTTP header `Retry-After` is used. If it's not given (it always is) `2` is used.
|
14
|
-
|
15
|
-
If the retry fails the original error is raised (`ActiveResource::ClientError` or subclass).
|
5
|
+
Simple Ruby module to retry a [Shopify API request](https://github.com/Shopify/shopify_api) if rate limited (HTTP 429) or other errors
|
6
|
+
occur.
|
16
7
|
|
17
8
|
## Installation
|
18
9
|
|
@@ -28,6 +19,54 @@ Gem:
|
|
28
19
|
gem install shopify_api_retry
|
29
20
|
```
|
30
21
|
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
By default requests are retried when a Shopify rate limit error is returned. The retry happens once after waiting for
|
25
|
+
[the seconds given by the HTTP `Retry-After` header](https://shopify.dev/concepts/about-apis/rate-limits):
|
26
|
+
```rb
|
27
|
+
require "shopify_api_retry" # requires "shopify_api" for you
|
28
|
+
|
29
|
+
ShopifyAPIRetry.retry { customer.update_attribute(:tags, "foo") }
|
30
|
+
customer = ShopifyAPIRetry.retry { ShopifyAPI::Customer.find(id) }
|
31
|
+
```
|
32
|
+
|
33
|
+
You can override this:
|
34
|
+
```rb
|
35
|
+
ShopifyAPIRetry.retry(:wait => 3, :tries => 5) { customer.update_attribute(:tags, "foo") }
|
36
|
+
```
|
37
|
+
This will try the request 5 times, waiting 3 seconds between each attempt. If a retry fails after the given number
|
38
|
+
of `:tries` the original error will be raised.
|
39
|
+
|
40
|
+
You can also retry requests when other errors occur:
|
41
|
+
```rb
|
42
|
+
ShopifyAPIRetry.retry "5XX" => { :wait => 10, :tries => 2 } do
|
43
|
+
customer.update_attribute(:tags, "foo")
|
44
|
+
end
|
45
|
+
```
|
46
|
+
This still retries rate limit requests, but also all HTTP 5XX errors.
|
47
|
+
|
48
|
+
Classes can be specified too:
|
49
|
+
```rb
|
50
|
+
ShopifyAPIRetry.retry SocketError => { :wait => 1, :tries => 5 } do
|
51
|
+
customer.update_attribute(:tags, "foo")
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Global defaults can be set as well:
|
56
|
+
```rb
|
57
|
+
ShopifyAPIRetry.configure do |config|
|
58
|
+
config.default_wait = 2.5
|
59
|
+
config.default_tries = 10
|
60
|
+
|
61
|
+
# Use defaults for these
|
62
|
+
config.on ["5XX", Net::TimeoutError]
|
63
|
+
|
64
|
+
config.on SocketError, :tries => 2, :wait => 1
|
65
|
+
end
|
66
|
+
|
67
|
+
ShopifyAPIRetry.retry { customer.update_attribute(:tags, "foo") }
|
68
|
+
```
|
69
|
+
|
31
70
|
## License
|
32
71
|
|
33
72
|
Released under the MIT License: www.opensource.org/licenses/MIT
|
data/Rakefile
CHANGED
data/lib/shopify_api_retry.rb
CHANGED
@@ -1,35 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "shopify_api"
|
2
4
|
|
3
5
|
module ShopifyAPIRetry
|
4
|
-
VERSION = "0.0
|
5
|
-
|
6
|
+
VERSION = "0.1.0"
|
7
|
+
|
8
|
+
HTTP_RETRY_AFTER = "Retry-After"
|
9
|
+
HTTP_RETRY_STATUS = "429"
|
10
|
+
|
11
|
+
class Config
|
12
|
+
attr_writer :default_wait
|
13
|
+
attr_writer :default_tries
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@settings = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_wait
|
20
|
+
@default_wait ||= nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def default_tries
|
24
|
+
@default_tries ||= 2
|
25
|
+
end
|
26
|
+
|
27
|
+
def on(errors, options = nil)
|
28
|
+
options = (options || {}).dup
|
29
|
+
options[:wait] ||= default_wait
|
30
|
+
options[:tries] ||= default_tries
|
31
|
+
|
32
|
+
Array(errors).each do |status_or_class|
|
33
|
+
@settings[status_or_class] = options
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear
|
38
|
+
@settings.clear
|
39
|
+
@default_wait = nil
|
40
|
+
@default_tries = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
settings = { HTTP_RETRY_STATUS => { :tries => default_tries, :wait => default_wait } }
|
45
|
+
@settings.each_with_object(settings) { |(k, v), o| o[k.to_s] = v.dup }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@config = Config.new
|
6
50
|
|
51
|
+
def self.configure
|
52
|
+
return @config unless block_given?
|
53
|
+
yield @config
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.config
|
58
|
+
@config
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Execute the provided block. If an HTTP 429 response is returned try
|
63
|
+
# it again. If any errors are provided try them according to their wait/return spec.
|
7
64
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# is used. If it's not given (it always is) +2+ is used.
|
65
|
+
# If no spec is provided the value of the HTTP header <code>Retry-After</code>
|
66
|
+
# is waited for before retrying. If it's not given (it always is) +2+ is used.
|
11
67
|
#
|
12
|
-
# If retry fails the original error is raised
|
68
|
+
# If retry fails the original error is raised.
|
13
69
|
#
|
14
70
|
# Returns the value of the block.
|
15
71
|
#
|
16
|
-
def retry(
|
72
|
+
def retry(cfg = nil)
|
17
73
|
raise ArgumentError, "block required" unless block_given?
|
18
|
-
raise ArgumentError, "seconds to wait must be > 0" unless seconds_to_wait.nil? || seconds_to_wait > 0
|
19
74
|
|
20
|
-
|
21
|
-
retried = false
|
75
|
+
attempts = build_config(cfg)
|
22
76
|
|
23
77
|
begin
|
24
78
|
result = yield
|
25
|
-
rescue
|
26
|
-
|
27
|
-
raise
|
79
|
+
rescue => e
|
80
|
+
handler = attempts[e.class.name]
|
81
|
+
raise if handler.nil? && (!e.is_a?(ActiveResource::ClientError) || !e.response.respond_to?(:code))
|
28
82
|
|
29
|
-
|
30
|
-
|
83
|
+
handler ||= attempts[e.response.code] || attempts["#{e.response.code[0]}XX"]
|
84
|
+
handler[:wait] ||= e.response[HTTP_RETRY_AFTER] || config.default_wait if e.response.code == HTTP_RETRY_STATUS
|
85
|
+
|
86
|
+
handler[:attempts] ||= 1
|
87
|
+
raise if handler[:attempts] == handler[:tries]
|
88
|
+
|
89
|
+
snooze = handler[:wait].to_i
|
90
|
+
waited = sleep snooze
|
91
|
+
snooze -= waited
|
92
|
+
# Worth looping?
|
93
|
+
sleep snooze if snooze > 0
|
94
|
+
|
95
|
+
handler[:attempts] += 1
|
31
96
|
|
32
|
-
retried = true
|
33
97
|
retry
|
34
98
|
end
|
35
99
|
|
@@ -37,4 +101,32 @@ module ShopifyAPIRetry
|
|
37
101
|
end
|
38
102
|
|
39
103
|
module_function :retry
|
104
|
+
|
105
|
+
def self.build_config(userconfig)
|
106
|
+
config = ShopifyAPIRetry.config.to_h
|
107
|
+
return config unless userconfig
|
108
|
+
|
109
|
+
if userconfig.is_a?(Integer)
|
110
|
+
userconfig = { :wait => cfg }
|
111
|
+
warn "passing an Integer to retry is deprecated and will be removed, use an :wait => #{cfg} instead"
|
112
|
+
elsif !userconfig.is_a?(Hash)
|
113
|
+
raise ArgumentError, "config must be a Hash"
|
114
|
+
end
|
115
|
+
|
116
|
+
userconfig.each do |k, v|
|
117
|
+
if v.is_a?(Hash)
|
118
|
+
config[k.to_s] = v.dup
|
119
|
+
else
|
120
|
+
config[HTTP_RETRY_STATUS][k] = v
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
config.values.each do |cfg|
|
125
|
+
raise ArgumentError, "seconds to wait must be >= 0" if cfg[:wait] && cfg[:wait] < 0
|
126
|
+
end
|
127
|
+
|
128
|
+
config
|
129
|
+
end
|
130
|
+
|
131
|
+
private_class_method :build_config
|
40
132
|
end
|
data/shopify_api_retry.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "shopify_api_retry"
|
3
|
-
spec.version = "0.0
|
3
|
+
spec.version = "0.1.0"
|
4
4
|
spec.authors = ["Skye Shaw"]
|
5
5
|
spec.email = ["skye.shaw@gmail.com"]
|
6
6
|
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_dependency "shopify_api", ">= 4.0"
|
22
22
|
|
23
|
-
spec.add_development_dependency "
|
24
|
-
spec.add_development_dependency "
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
24
|
+
spec.add_development_dependency "bundler"
|
25
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
25
26
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shopify_api_retry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Skye Shaw
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: shopify_api
|
@@ -25,33 +25,47 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '5.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '5.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - "~>"
|
46
60
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
61
|
+
version: '12.0'
|
48
62
|
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
68
|
+
version: '12.0'
|
55
69
|
description: Simple module to retry a ShopifyAPI request if an HTTP 429 (too many
|
56
70
|
requests) is returned. No monkey patching.
|
57
71
|
email:
|
@@ -60,6 +74,7 @@ executables: []
|
|
60
74
|
extensions: []
|
61
75
|
extra_rdoc_files: []
|
62
76
|
files:
|
77
|
+
- ".github/workflows/ci.yml"
|
63
78
|
- ".gitignore"
|
64
79
|
- ".travis.yml"
|
65
80
|
- Gemfile
|
@@ -88,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
103
|
version: '0'
|
89
104
|
requirements: []
|
90
105
|
rubyforge_project:
|
91
|
-
rubygems_version: 2.6
|
106
|
+
rubygems_version: 2.7.6
|
92
107
|
signing_key:
|
93
108
|
specification_version: 4
|
94
109
|
summary: Retry a ShopifyAPI request if an HTTP 429 (too many requests) is returned
|