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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9f6d412f4f64e2d2ac4d7e741008526b7a71b767
4
- data.tar.gz: 1db395165fe1466210334c70efb1db523242837b
2
+ SHA256:
3
+ metadata.gz: 949d65d19ed205d18a64ea886ef817bcd9d8b26895036228dddc3dcf4d91f0ca
4
+ data.tar.gz: 6b776cf5985be1593e791a81c6f4ce31c88ca4f029c7ba03bd8e24ca5f44f26f
5
5
  SHA512:
6
- metadata.gz: 871762fa4d151434d30a088cdaad6c2e2da6d5207b3a2671fe0e7ee1076b9f1b7a179e238596841463eb9dbde4023e4d0c542ae350dfd06f7d85cce6b87226c6
7
- data.tar.gz: b0d9c33fbf726213a3eba185df8dac8839539a29867c53a15b5730dd535946efbd4f19bac143788c4084f17cec0bcbac9a35edaecf19369edd5ee6f49ebb2e09
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
- Simple module to retry a [ShopifyAPI](https://github.com/Shopify/shopify_api) request if an HTTP 429 (too many requests) is returned. No monkey patching.
3
+ ![CI](https://github.com/ScreenStaring/shopify_api_retry/workflows/CI/badge.svg)
4
4
 
5
- ## Usage
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
@@ -1 +1,8 @@
1
+ require "rake"
2
+ require "rake/testtask"
1
3
  require "bundler/gem_tasks"
4
+
5
+ task :default => :test
6
+ Rake::TestTask.new do |t|
7
+ t.pattern = "spec/*_spec.rb"
8
+ end
@@ -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.1".freeze
5
- HTTP_RETRY_AFTER = "Retry-After".freeze
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
- # Execute the provided block. If an HTTP 429 response is return try
9
- # it again. If no retry time is provided the value of the HTTP header <code>Retry-After</code>
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 (`ActiveResource::ClientError` or subclass).
68
+ # If retry fails the original error is raised.
13
69
  #
14
70
  # Returns the value of the block.
15
71
  #
16
- def retry(seconds_to_wait = nil)
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
- result = nil
21
- retried = false
75
+ attempts = build_config(cfg)
22
76
 
23
77
  begin
24
78
  result = yield
25
- rescue ActiveResource::ClientError => e
26
- # Not 100% if we need to check for code method, I think I saw a NoMethodError...
27
- raise unless !retried && e.response.respond_to?(:code) && e.response.code.to_i == 429
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
- seconds_to_wait = (e.response[HTTP_RETRY_AFTER] || 2).to_i unless seconds_to_wait
30
- sleep seconds_to_wait
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "shopify_api_retry"
3
- spec.version = "0.0.1"
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 "bundler", "~> 1.16"
24
- spec.add_development_dependency "rake", "~> 10.0"
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.1
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: 2019-07-08 00:00:00.000000000 Z
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: bundler
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.16'
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: '1.16'
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: '10.0'
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: '10.0'
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.14
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