sidekiq-delay_extensions 7.0.0 → 7.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95dda38ae3c61ad156898807a993f2e4ed94a1e9fa84e0acefc55e9eda8c563f
4
- data.tar.gz: c01d2df0317cb993a90ea42edf54c8cd8555a47abc6e9fc5aeca30cd3c609970
3
+ metadata.gz: '09822a56faaa3bddfd6accbbff7d36e709cd62d41123b3b42c54523ef74460a7'
4
+ data.tar.gz: f4c9f5b96cd11aff2f7790b5384b698f52c331a99260651f16f106632a96d15b
5
5
  SHA512:
6
- metadata.gz: c8ff4ae1215e5f1feb5b69c776711e9bdbd788d485d67f539e6b04aa47712149eff81f3a8148ad18fde812254107d1954754e67559847821cfd22cb414d64321
7
- data.tar.gz: 1c829791c9f5c30a59fd236864f07d9e3ae635bd928bc4df8d25bce692b1323f0a8f851692d49cafac4da446b8c87b4f84d8d33fcfe8e8151201f5de7ade08a4
6
+ metadata.gz: 1ec8ced317007db2bd65436b7c575c01d741efc86273f2d8407637612864636f4757bf8efe5c28f021435fa6f88d20b86f33915bc0bf81b22bdebeb5d4aa145f
7
+ data.tar.gz: 9f43ee40a9aad89506d1a2aa51ed041b97ce499e8fb8d156e67c4b6e3c21e308e3337eb46ac0f8b1d156d39fe0e61ceaee88533cc840f5dcf20fd99bcd2875bd
data/Changes.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  [See Sidekiq for its changes](https://github.com/mperham/sidekiq/blob/main/Changes.md)
4
4
 
5
+ Unreleased
6
+ ---------
7
+
8
+ 7.1.0
9
+ ---------
10
+
11
+ - New `Sidekiq::DelayExtensions::GenericJob` superclass for DelayedMailer, DelayedModel, DelayedClass
12
+ - it has a `_perform` method which accepts the unmarshalled and processed
13
+ `(target, method_name, *args, **kwargs)` and can be overridden or extended as needed.
14
+ - New (opt-in) `Sidekiq::DelayExtensions::GenericProxy` which can parse JSON or YAML delayed arguments
15
+ into a `target`, `method_name`, `args`, and `kwargs` before.
16
+ - New (opt-in) setting `Sidekiq::DelayExtensions.use_generic_proxy` (defaults to false).
17
+ - When false, there is no delayed proxy changes; the original `Sidekiq::DelayExtensions::Proxy` is used.
18
+ - When true, the new `Sidekiq::DelayExtensions::GenericProxy` is used, which handles both `*args` and `**kwargs` more naturally.
19
+ Be sure to test this works for you as expected when turning this on.
20
+ - Chore: Load YAML consistently via `::Sidekiq::DelayExtensions::YAML`
21
+
5
22
  7.0.0
6
23
  ---------
7
24
 
data/README.md CHANGED
@@ -32,6 +32,8 @@ In your initializers, include the line:
32
32
  Upgrading (IMPORTANT): Also add
33
33
 
34
34
  # To handle any existing delayed jobs at time of upgrade.
35
+ module Sidekiq::Extensions
36
+ end
35
37
  Sidekiq::Extensions::DelayedClass = Sidekiq::DelayExtensions::DelayedClass
36
38
  Sidekiq::Extensions::DelayedModel = Sidekiq::DelayExtensions::DelayedModel
37
39
  Sidekiq::Extensions::DelayedMailer = Sidekiq::DelayExtensions::DelayedMailer
@@ -12,12 +12,14 @@ module Sidekiq
12
12
  # UserMailer.delay.send_welcome_email(new_user)
13
13
  # UserMailer.delay_for(5.days).send_welcome_email(new_user)
14
14
  # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
15
- class DelayedMailer
16
- include Sidekiq::Job
17
-
18
- def perform(yml)
19
- (target, method_name, args) = YAML.load(yml)
20
- msg = target.public_send(method_name, *args)
15
+ class DelayedMailer < GenericJob
16
+ def _perform(target, method_name, *args, **kwargs)
17
+ msg =
18
+ if kwargs.empty?
19
+ target.public_send(method_name, *args)
20
+ else
21
+ target.public_send(method_name, *args, **kwargs)
22
+ end
21
23
  # The email method can return nil, which causes ActionMailer to return
22
24
  # an undeliverable empty message.
23
25
  if msg
@@ -29,16 +31,24 @@ module Sidekiq
29
31
  end
30
32
 
31
33
  module ActionMailer
34
+ def sidekiq_delay_proxy
35
+ if Sidekiq::DelayExtensions.use_generic_proxy
36
+ GenericProxy
37
+ else
38
+ Proxy
39
+ end
40
+ end
41
+
32
42
  def sidekiq_delay(options = {})
33
- Proxy.new(DelayedMailer, self, options)
43
+ sidekiq_delay_proxy.new(DelayedMailer, self, options)
34
44
  end
35
45
 
36
46
  def sidekiq_delay_for(interval, options = {})
37
- Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f))
47
+ sidekiq_delay_proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f))
38
48
  end
39
49
 
40
50
  def sidekiq_delay_until(timestamp, options = {})
41
- Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f))
51
+ sidekiq_delay_proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f))
42
52
  end
43
53
  alias_method :delay, :sidekiq_delay
44
54
  alias_method :delay_for, :sidekiq_delay_for
@@ -14,26 +14,28 @@ module Sidekiq
14
14
  # Please note, this is not recommended as this will serialize the entire
15
15
  # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
16
16
  # This is here for backwards compatibility with Delayed::Job only.
17
- class DelayedModel
18
- include Sidekiq::Job
19
-
20
- def perform(yml)
21
- (target, method_name, args) = YAML.load(yml)
22
- target.__send__(method_name, *args)
23
- end
17
+ class DelayedModel < GenericJob
24
18
  end
25
19
 
26
20
  module ActiveRecord
21
+ def sidekiq_delay_proxy
22
+ if Sidekiq::DelayExtensions.use_generic_proxy
23
+ GenericProxy
24
+ else
25
+ Proxy
26
+ end
27
+ end
28
+
27
29
  def sidekiq_delay(options = {})
28
- Proxy.new(DelayedModel, self, options)
30
+ sidekiq_delay_proxy.new(DelayedModel, self, options)
29
31
  end
30
32
 
31
33
  def sidekiq_delay_for(interval, options = {})
32
- Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f))
34
+ sidekiq_delay_proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f))
33
35
  end
34
36
 
35
37
  def sidekiq_delay_until(timestamp, options = {})
36
- Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f))
38
+ sidekiq_delay_proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f))
37
39
  end
38
40
  alias_method :delay, :sidekiq_delay
39
41
  alias_method :delay_for, :sidekiq_delay_for
@@ -41,12 +41,13 @@ module Sidekiq
41
41
  # vs.
42
42
  # https://github.com/mperham/sidekiq/blob/v7.0.1/lib/sidekiq/api.rb#L374-L411
43
43
  def safe_load(content, default)
44
- yield(*YAML.load(content))
44
+ yield(*Sidekiq::DelayExtensions::YAML.unsafe_load(content))
45
45
  rescue => ex
46
46
  # #1761 in dev mode, it's possible to have jobs enqueued which haven't been loaded into
47
47
  # memory yet so the YAML can't be loaded.
48
48
  # TODO is this still necessary? Zeitwerk reloader should handle?
49
- Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless Sidekiq.options[:environment] == "development"
49
+ sidekiq_env = ::Sidekiq.default_configuration[:environment] || ENV["APP_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"]
50
+ ::Sidekiq.logger.warn "Unable to load YAML: #{ex.message}" unless sidekiq_env == "development"
50
51
  default
51
52
  end
52
53
  end
@@ -12,26 +12,28 @@ module Sidekiq
12
12
  # User.delay.delete_inactive
13
13
  # Wikipedia.delay.download_changes_for(Date.today)
14
14
  #
15
- class DelayedClass
16
- include Sidekiq::Job
17
-
18
- def perform(yml)
19
- (target, method_name, args) = YAML.load(yml)
20
- target.__send__(method_name, *args)
21
- end
15
+ class DelayedClass < GenericJob
22
16
  end
23
17
 
24
18
  module Klass
19
+ def sidekiq_delay_proxy
20
+ if Sidekiq::DelayExtensions.use_generic_proxy
21
+ GenericProxy
22
+ else
23
+ Proxy
24
+ end
25
+ end
26
+
25
27
  def sidekiq_delay(options = {})
26
- Proxy.new(DelayedClass, self, options)
28
+ sidekiq_delay_proxy.new(DelayedClass, self, options)
27
29
  end
28
30
 
29
31
  def sidekiq_delay_for(interval, options = {})
30
- Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f))
32
+ sidekiq_delay_proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f))
31
33
  end
32
34
 
33
35
  def sidekiq_delay_until(timestamp, options = {})
34
- Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f))
36
+ sidekiq_delay_proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f))
35
37
  end
36
38
  alias_method :delay, :sidekiq_delay
37
39
  alias_method :delay_for, :sidekiq_delay_for
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module DelayExtensions
5
+ class GenericJob
6
+ include Sidekiq::Job
7
+
8
+ def perform(yml)
9
+ if !Sidekiq::DelayExtensions.use_generic_proxy
10
+ (target, method_name, args) = ::Sidekiq::DelayExtensions::YAML.unsafe_load(yml)
11
+ return _perform(target, method_name, *args)
12
+ end
13
+ (target, method_name, args, kwargs) = ::Sidekiq::DelayExtensions::YAML.unsafe_load(yml)
14
+ if target.is_a?(String)
15
+ target_klass = target.safe_constantize
16
+ if target_klass
17
+ target = target_klass
18
+ else
19
+ fail NameError, "uninitialized constant #{target}. Peforming: #{yml.inspect}"
20
+ end
21
+ end
22
+ has_no_kwargs = kwargs.nil? || kwargs.empty? # rubocop:disable Rails/Blank
23
+ if has_no_kwargs
24
+ if args.is_a?(Array) && args.last.is_a?(Hash) # && args.last.keys.any? { |key| key.is_a?(Symbol) }
25
+ # rehydrate keys
26
+ kwargs = args.pop.symbolize_keys
27
+ has_no_kwargs = kwargs.empty?
28
+ elsif args.is_a?(Hash)
29
+ # rehydrate keys
30
+ kwargs = args.symbolize_keys
31
+ args = []
32
+ has_no_kwargs = kwargs.empty?
33
+ end
34
+ end
35
+ if has_no_kwargs
36
+ _perform(target, method_name, *args)
37
+ else
38
+ _perform(target, method_name, *args, **kwargs)
39
+ end
40
+ end
41
+
42
+ def _perform(target, method_name, *args, **kwargs)
43
+ if kwargs.empty?
44
+ target.__send__(method_name, *args)
45
+ else
46
+ target.__send__(method_name, *args, **kwargs)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -6,6 +6,9 @@ module Sidekiq
6
6
  module DelayExtensions
7
7
  SIZE_LIMIT = 8_192
8
8
 
9
+ singleton_class.attr_accessor :use_generic_proxy
10
+ self.use_generic_proxy = false
11
+
9
12
  class Proxy < BasicObject
10
13
  def initialize(performable, target, options = {})
11
14
  @performable = performable
@@ -29,5 +32,59 @@ module Sidekiq
29
32
  "display_class" => "#{@target}.#{name}"}.merge(@opts))
30
33
  end
31
34
  end
35
+
36
+ class GenericProxy < BasicObject
37
+ def initialize(performable, target, options = {})
38
+ @performable = performable
39
+ @target = target
40
+ @opts = options
41
+ end
42
+
43
+ def method_missing(name, *args, **kwargs) # rubocop:disable Style/MissingRespondToMissing
44
+ begin
45
+ has_no_kwargs = kwargs.nil? || kwargs.empty?
46
+ valid_json_args =
47
+ if has_no_kwargs
48
+ # if args.last.is_a?(Hash) && args.last.keys.any? { |key| key.is_a?(Symbol) }
49
+ # kwargs = args.pop.symbolize_keys
50
+ [*args]
51
+ else
52
+ [*args, ::JSON.parse(::JSON.dump(kwargs))]
53
+ end
54
+ obj = [@target.name, name&.to_s, valid_json_args]
55
+ marshalled = ::JSON.dump(obj)
56
+ rescue ::JSON::ParserError
57
+ obj = if kwargs&.any?
58
+ [@target.name, name&.to_s, *args, **kwargs]
59
+ else
60
+ [@target.name, name&.to_s, *args]
61
+ end
62
+ ::Sidekiq.logger.warn {
63
+ "Non-JSON args passed to Sidekiq delayed job. obj=#{obj.inspect}"
64
+ }
65
+ marshalled = ::YAML.dump(obj)
66
+ end
67
+ # Debug notes:
68
+ # marshalled = ::YAML.dump(valid_json_args)
69
+ # marshalled = ::YAML.to_json(valid_json_args)
70
+ # value = {
71
+ # target_name: @target.name,
72
+ # name: name,
73
+ # args: args,
74
+ # kwargs: kwargs,
75
+ # valid_json_args: valid_json_args,
76
+ # obj: obj,
77
+ # marshalled: marshalled,
78
+ # }
79
+ # ::STDOUT.puts value.inspect
80
+ if marshalled.size > SIZE_LIMIT
81
+ ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
82
+ end
83
+ valid_opts = @opts.stringify_keys
84
+ @performable.client_push({"class" => @performable,
85
+ "args" => [marshalled],
86
+ "display_class" => "#{@target}.#{name}"}.merge(valid_opts))
87
+ end
88
+ end
32
89
  end
33
90
  end
@@ -14,7 +14,7 @@ module Sidekiq
14
14
  jobs.select do |job|
15
15
  marshalled = job["args"][0]
16
16
  next unless marshalled.index(klass.to_s)
17
- YAML.load(marshalled)
17
+ ::Sidekiq::DelayExtensions::YAML.unsafe_load(marshalled)
18
18
  end
19
19
  end
20
20
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Sidekiq
4
4
  module DelayExtensions
5
- VERSION = "7.0.0"
5
+ VERSION = "7.1.0"
6
6
  end
7
7
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sidekiq
4
+ module DelayExtensions
5
+ module YAML
6
+ STDLIB_YAML = ::YAML
7
+
8
+ if STDLIB_YAML.respond_to?(:unsafe_load)
9
+ # https://github.com/ruby/psych/blob/v4.0.3/lib/psych.rb#L271-L323
10
+ # def self.unsafe_load(yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false)
11
+ def self.unsafe_load(yaml, **kwargs)
12
+ STDLIB_YAML.unsafe_load(yaml, **kwargs)
13
+ end
14
+
15
+ # def self.safe_load(yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false)
16
+ def self.safe_load(yaml, **kwargs)
17
+ STDLIB_YAML.safe_load(yaml, **kwargs)
18
+ end
19
+
20
+ # def self.unsafe_load_file(filename, **kwargs)
21
+ def self.unsafe_load_file(filename, **kwargs)
22
+ STDLIB_YAML.unsafe_load_file(filename, **kwargs)
23
+ end
24
+
25
+ # def self.safe_load_file(filename, **kwargs)
26
+ def self.safe_load_file(filename, **kwargs)
27
+ STDLIB_YAML.safe_load_file(filename, **kwargs)
28
+ end
29
+ else
30
+ # https://github.com/ruby/psych/blob/v3.1.0/lib/psych.rb#L271-L328
31
+ # vs.
32
+ # https://github.com/ruby/psych/blob/v4.0.3/lib/psych.rb#L271-L323
33
+ # def self.load( yaml, filename: nil, fallback: false, symbolize_names: false)
34
+ # vs.
35
+ # def self.unsafe_load(yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false)
36
+ def self.unsafe_load(yaml, **kwargs)
37
+ STDLIB_YAML.load(yaml, **kwargs)
38
+ end
39
+
40
+ # def self.safe_load(yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false)
41
+ # vs.
42
+ # def self.safe_load(yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false)
43
+ def self.safe_load(yaml, **kwargs)
44
+ STDLIB_YAML.safe_load(yaml, **kwargs)
45
+ end
46
+
47
+ # def self.load_file filename, fallback: false
48
+ # vs.
49
+ # def self.unsafe_load_file(filename, **kwargs)
50
+ def self.unsafe_load_file(filename, **kwargs)
51
+ STDLIB_YAML.load_file(filename, **kwargs)
52
+ end
53
+
54
+ # n/a
55
+ # vs.
56
+ # def self.safe_load_file(filename, **kwargs)
57
+ def self.safe_load_file(filename, **kwargs)
58
+ STDLIB_YAML.load_file(filename, **kwargs)
59
+ end
60
+ end
61
+ # NOTE(BF): In case someone calls YAML.load
62
+ # from within the gem.
63
+ singleton_class.alias_method :load, :unsafe_load
64
+ end
65
+ end
66
+ end
@@ -5,6 +5,10 @@ require "sidekiq"
5
5
  module Sidekiq
6
6
  module DelayExtensions
7
7
  def self.enable_delay!
8
+ require "sidekiq/delay_extensions/yaml"
9
+
10
+ require "sidekiq/delay_extensions/generic_job"
11
+
8
12
  if defined?(::ActiveSupport)
9
13
  require "sidekiq/delay_extensions/active_record"
10
14
  require "sidekiq/delay_extensions/action_mailer"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-delay_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.0.0
4
+ version: 7.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2024-01-26 00:00:00.000000000 Z
12
+ date: 2024-02-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -42,9 +42,11 @@ files:
42
42
  - lib/sidekiq/delay_extensions/active_record.rb
43
43
  - lib/sidekiq/delay_extensions/api.rb
44
44
  - lib/sidekiq/delay_extensions/class_methods.rb
45
+ - lib/sidekiq/delay_extensions/generic_job.rb
45
46
  - lib/sidekiq/delay_extensions/generic_proxy.rb
46
47
  - lib/sidekiq/delay_extensions/testing.rb
47
48
  - lib/sidekiq/delay_extensions/version.rb
49
+ - lib/sidekiq/delay_extensions/yaml.rb
48
50
  - sidekiq-delay_extensions.gemspec
49
51
  homepage: https://github.com/gemhome/sidekiq-delay_extensions/wiki/Delayed-extensions
50
52
  licenses: