retriable 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +18 -2
- data/CHANGELOG.md +30 -0
- data/Gemfile +2 -0
- data/README.md +5 -0
- data/lib/retriable/config.rb +3 -0
- data/lib/retriable/validation.rb +7 -0
- data/lib/retriable/version.rb +1 -1
- data/lib/retriable.rb +14 -5
- data/sig/retriable.rbs +29 -1
- data/spec/config_spec.rb +17 -0
- data/spec/retriable_spec.rb +32 -0
- data/spec/spec_helper.rb +3 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5961397b426eeaf2d2df8631f0a5dfe05adf52d5b9070ddbdf7b7825fd88f144
|
|
4
|
+
data.tar.gz: c982809ef09eb85ddedfcee95be53741846f6e4438af27e403c9ad4b020214c1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30d8bfeeff407ab15fa9be7c2af2993142dce86d8406318e220afdb111c728b0c8c7cd2d6b105312ed9623074a5b20c88a9c19a6c33c3169d673d2276e0b52c4
|
|
7
|
+
data.tar.gz: f333fbf26eff94629f85f5d58688245025be3f39830bcad59da7f42ab52ad1506b155c03e1d96020a728b489ed016cb8ee1fcd5cde0a7a0ce3cfe59ad078c4d8
|
data/.github/workflows/main.yml
CHANGED
|
@@ -30,8 +30,6 @@ jobs:
|
|
|
30
30
|
"4.0",
|
|
31
31
|
jruby,
|
|
32
32
|
]
|
|
33
|
-
env:
|
|
34
|
-
CC_TEST_REPORTER_ID: 20a1139ef1830b4f813a10a03d90e8aa179b5226f75e75c5a949b25756ebf558
|
|
35
33
|
|
|
36
34
|
steps:
|
|
37
35
|
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
|
@@ -63,3 +61,21 @@ jobs:
|
|
|
63
61
|
|
|
64
62
|
- name: Run rubocop
|
|
65
63
|
run: bundle exec rubocop
|
|
64
|
+
|
|
65
|
+
- name: Validate RBS
|
|
66
|
+
run: bundle exec rbs -I sig validate
|
|
67
|
+
|
|
68
|
+
audit:
|
|
69
|
+
runs-on: ubuntu-24.04
|
|
70
|
+
|
|
71
|
+
steps:
|
|
72
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
73
|
+
|
|
74
|
+
- name: Setup ruby
|
|
75
|
+
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1
|
|
76
|
+
with:
|
|
77
|
+
ruby-version: "3.3"
|
|
78
|
+
bundler-cache: true
|
|
79
|
+
|
|
80
|
+
- name: Run bundler-audit
|
|
81
|
+
run: bundle exec bundle-audit check --update
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# HEAD
|
|
2
2
|
|
|
3
|
+
## 4.1.1
|
|
4
|
+
|
|
5
|
+
### Bug fixes
|
|
6
|
+
|
|
7
|
+
- `retry_if`, `on_retry`, and `on_give_up` are now validated to be callable
|
|
8
|
+
(respond to `#call`) or falsy. A non-callable truthy value raises
|
|
9
|
+
`ArgumentError` at configuration time instead of a later `NoMethodError` on a
|
|
10
|
+
retry path. ([#140](https://github.com/kamui/retriable/pull/140))
|
|
11
|
+
|
|
12
|
+
### Internal
|
|
13
|
+
|
|
14
|
+
- Add RBS type signatures for the public API (`Retriable.configure`, `config`,
|
|
15
|
+
`retriable`, `with_override`, `with_context`, and `Retriable::Config`) and
|
|
16
|
+
validate them in CI with `rbs validate`.
|
|
17
|
+
([#142](https://github.com/kamui/retriable/pull/142))
|
|
18
|
+
- Enforce a minimum test coverage floor and add a `bundler-audit` dependency
|
|
19
|
+
audit job to CI. ([#143](https://github.com/kamui/retriable/pull/143))
|
|
20
|
+
- Remove an unused `CC_TEST_REPORTER_ID` from the CI workflow.
|
|
21
|
+
([#141](https://github.com/kamui/retriable/pull/141))
|
|
22
|
+
|
|
23
|
+
## 4.1.0
|
|
24
|
+
|
|
25
|
+
### Bug fixes
|
|
26
|
+
|
|
27
|
+
- A per-call or `with_context` `tries:` now clears an inherited `intervals:` from
|
|
28
|
+
global config or a context, matching the documented precedence. Previously
|
|
29
|
+
`Retriable.retriable(tries: 1)` was silently ignored when `intervals` was
|
|
30
|
+
configured, running `intervals.size + 1` times. Passing both `intervals:` and
|
|
31
|
+
`tries:` in the same call still lets `intervals:` win.
|
|
32
|
+
|
|
3
33
|
## 4.0.0
|
|
4
34
|
|
|
5
35
|
**This is a major release with breaking changes. Please read carefully before upgrading.**
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -206,6 +206,11 @@ end
|
|
|
206
206
|
`#configure` sets defaults only. Per-call options passed to `Retriable.retriable` and
|
|
207
207
|
`Retriable.with_context` still take precedence.
|
|
208
208
|
|
|
209
|
+
When a higher-precedence layer sets `tries:` without `intervals:`, it clears any
|
|
210
|
+
`intervals:` inherited from a lower layer (so `retriable(tries: 1)` runs once even
|
|
211
|
+
if `intervals` was configured). Within a single call, passing `intervals:` still
|
|
212
|
+
overrides `tries:`.
|
|
213
|
+
|
|
209
214
|
### Override
|
|
210
215
|
|
|
211
216
|
`#with_override` is a block-scoped API for forcing retry options that should
|
data/lib/retriable/config.rb
CHANGED
data/lib/retriable/validation.rb
CHANGED
|
@@ -28,6 +28,13 @@ module Retriable
|
|
|
28
28
|
validate_non_negative_number(name, value)
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def validate_callable(name, value)
|
|
32
|
+
return unless value # nil/false disable the callback
|
|
33
|
+
return if value.respond_to?(:call)
|
|
34
|
+
|
|
35
|
+
raise ArgumentError, "#{name} must respond to #call or be nil"
|
|
36
|
+
end
|
|
37
|
+
|
|
31
38
|
def validate_rand_factor
|
|
32
39
|
return if finite_number?(rand_factor) && rand_factor >= 0 && rand_factor <= 1
|
|
33
40
|
|
data/lib/retriable/version.rb
CHANGED
data/lib/retriable.rb
CHANGED
|
@@ -58,7 +58,7 @@ module Retriable
|
|
|
58
58
|
local_config = if opts.empty? && !override_config
|
|
59
59
|
config
|
|
60
60
|
else
|
|
61
|
-
Config.new(apply_override_options(config.to_h
|
|
61
|
+
Config.new(apply_override_options(merge_layer(config.to_h, opts), override_config))
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Config is mutable through `configure`, so validate again immediately before use.
|
|
@@ -216,9 +216,17 @@ module Retriable
|
|
|
216
216
|
def apply_override_options(options, overrides)
|
|
217
217
|
return options unless overrides
|
|
218
218
|
|
|
219
|
-
options
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
merge_layer(options, overrides)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Merge a higher-precedence option layer onto a base layer. A higher layer
|
|
223
|
+
# that sets `tries` without `intervals` clears the base layer's inherited
|
|
224
|
+
# `intervals`, so a caller's `tries:` is never silently ignored. When the
|
|
225
|
+
# higher layer supplies its own `intervals`, those win (same-call override).
|
|
226
|
+
def merge_layer(base, higher)
|
|
227
|
+
merged = base.merge(higher)
|
|
228
|
+
merged[:intervals] = nil if higher.key?(:tries) && !higher.key?(:intervals)
|
|
229
|
+
merged
|
|
222
230
|
end
|
|
223
231
|
|
|
224
232
|
def available_contexts
|
|
@@ -228,7 +236,7 @@ module Retriable
|
|
|
228
236
|
def context_options_for(context_key, options)
|
|
229
237
|
context_options = config_contexts.fetch(context_key, {})
|
|
230
238
|
context_options = {} unless context_options.is_a?(Hash)
|
|
231
|
-
context_options = context_options
|
|
239
|
+
context_options = merge_layer(context_options, options)
|
|
232
240
|
|
|
233
241
|
override_context_options = override_contexts[context_key]
|
|
234
242
|
return context_options unless override_context_options.is_a?(Hash)
|
|
@@ -262,6 +270,7 @@ module Retriable
|
|
|
262
270
|
:retriable_exception?,
|
|
263
271
|
:hash_exception_match?,
|
|
264
272
|
:apply_override_options,
|
|
273
|
+
:merge_layer,
|
|
265
274
|
:available_contexts,
|
|
266
275
|
:context_options_for,
|
|
267
276
|
:config_contexts,
|
data/sig/retriable.rbs
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
module Retriable
|
|
2
2
|
VERSION: String
|
|
3
|
-
|
|
3
|
+
OVERRIDE_THREAD_KEY: Symbol
|
|
4
|
+
|
|
5
|
+
def self.configure: () { (Config) -> void } -> void
|
|
6
|
+
def self.config: () -> Config
|
|
7
|
+
def self.with_override: (Hash[Symbol, untyped] options) { () -> untyped } -> untyped
|
|
8
|
+
def self.with_context: (Symbol context_key, ?Hash[Symbol, untyped] options) ?{ (Integer) -> untyped } -> untyped
|
|
9
|
+
def self.retriable: (?Hash[Symbol, untyped] options) { (Integer) -> untyped } -> untyped
|
|
10
|
+
|
|
11
|
+
class Config
|
|
12
|
+
ATTRIBUTES: Array[Symbol]
|
|
13
|
+
|
|
14
|
+
attr_accessor tries: Numeric
|
|
15
|
+
attr_accessor base_interval: Numeric
|
|
16
|
+
attr_accessor max_interval: Numeric
|
|
17
|
+
attr_accessor rand_factor: Numeric
|
|
18
|
+
attr_accessor multiplier: Numeric
|
|
19
|
+
attr_accessor sleep_disabled: bool
|
|
20
|
+
attr_accessor max_elapsed_time: Numeric?
|
|
21
|
+
attr_accessor intervals: Array[Numeric]?
|
|
22
|
+
attr_accessor on: untyped
|
|
23
|
+
attr_accessor retry_if: untyped
|
|
24
|
+
attr_accessor on_retry: untyped
|
|
25
|
+
attr_accessor on_give_up: untyped
|
|
26
|
+
attr_accessor contexts: Hash[Symbol, untyped]
|
|
27
|
+
|
|
28
|
+
def initialize: (?Hash[Symbol, untyped] opts) -> void
|
|
29
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
30
|
+
def validate!: () -> void
|
|
31
|
+
end
|
|
4
32
|
end
|
data/spec/config_spec.rb
CHANGED
|
@@ -163,4 +163,21 @@ describe Retriable::Config do
|
|
|
163
163
|
.to raise_error(ArgumentError, /on must be an Exception class/)
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
|
+
|
|
167
|
+
context "callable option validation" do
|
|
168
|
+
%i[retry_if on_retry on_give_up].each do |opt|
|
|
169
|
+
it "accepts a callable for #{opt}" do
|
|
170
|
+
expect { described_class.new(opt => ->(*) {}) }.not_to raise_error
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "accepts nil and false for #{opt}" do
|
|
174
|
+
expect { described_class.new(opt => nil) }.not_to raise_error
|
|
175
|
+
expect { described_class.new(opt => false) }.not_to raise_error
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "rejects a non-callable truthy value for #{opt}" do
|
|
179
|
+
expect { described_class.new(opt => 5) }.to raise_error(ArgumentError, /#{opt}.*#call/)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
166
183
|
end
|
data/spec/retriable_spec.rb
CHANGED
|
@@ -760,6 +760,38 @@ describe Retriable do
|
|
|
760
760
|
end
|
|
761
761
|
end
|
|
762
762
|
|
|
763
|
+
context "#retriable tries/intervals precedence" do
|
|
764
|
+
it "lets a per-call tries clear globally configured intervals" do
|
|
765
|
+
described_class.configure { |c| c.intervals = [0.5, 1.0] }
|
|
766
|
+
|
|
767
|
+
expect do
|
|
768
|
+
described_class.retriable(tries: 1) { increment_tries_with_exception }
|
|
769
|
+
end.to raise_error(StandardError)
|
|
770
|
+
|
|
771
|
+
expect(@tries).to eq(1)
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
it "still lets per-call intervals win when both intervals and tries are given" do
|
|
775
|
+
expect do
|
|
776
|
+
described_class.retriable(intervals: [0.5, 1.0], tries: 1) { increment_tries_with_exception }
|
|
777
|
+
end.to raise_error(StandardError)
|
|
778
|
+
|
|
779
|
+
expect(@tries).to eq(3) # intervals.size + 1
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
it "lets a with_context tries clear context intervals" do
|
|
783
|
+
described_class.configure do |c|
|
|
784
|
+
c.contexts[:api] = { intervals: [0.5, 1.0] }
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
expect do
|
|
788
|
+
described_class.with_context(:api, tries: 1) { increment_tries_with_exception }
|
|
789
|
+
end.to raise_error(StandardError)
|
|
790
|
+
|
|
791
|
+
expect(@tries).to eq(1)
|
|
792
|
+
end
|
|
793
|
+
end
|
|
794
|
+
|
|
763
795
|
context "#with_override" do
|
|
764
796
|
it "takes precedence over both global config and local options" do
|
|
765
797
|
described_class.configure { |c| c.tries = 2 }
|
data/spec/spec_helper.rb
CHANGED