safe_memoize 0.9.0 → 1.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 +4 -4
- data/.yardopts +10 -0
- data/CHANGELOG.md +29 -0
- data/README.md +181 -2
- data/ROADMAP.md +5 -36
- data/Rakefile +14 -1
- data/UPGRADING.md +197 -0
- data/lib/safe_memoize/adapters/opentelemetry.rb +25 -0
- data/lib/safe_memoize/adapters/statsd.rb +31 -0
- data/lib/safe_memoize/cache_metrics_methods.rb +1 -0
- data/lib/safe_memoize/cache_record_methods.rb +1 -0
- data/lib/safe_memoize/cache_store_methods.rb +1 -0
- data/lib/safe_memoize/class_methods.rb +173 -19
- data/lib/safe_memoize/configuration.rb +47 -2
- data/lib/safe_memoize/custom_key_methods.rb +1 -0
- data/lib/safe_memoize/hooks_methods.rb +1 -0
- data/lib/safe_memoize/inspection_methods.rb +2 -1
- data/lib/safe_memoize/instance_methods.rb +1 -0
- data/lib/safe_memoize/lru_methods.rb +1 -0
- data/lib/safe_memoize/public_custom_key_methods.rb +23 -0
- data/lib/safe_memoize/public_methods.rb +162 -5
- data/lib/safe_memoize/public_metrics_methods.rb +20 -0
- data/lib/safe_memoize/rails/middleware.rb +2 -0
- data/lib/safe_memoize/rails/request_scoped.rb +3 -0
- data/lib/safe_memoize/release_tooling.rb +1 -0
- data/lib/safe_memoize/stores/base.rb +85 -0
- data/lib/safe_memoize/stores/memory.rb +70 -0
- data/lib/safe_memoize/stores/rails_cache.rb +128 -0
- data/lib/safe_memoize/stores/redis.rb +111 -0
- data/lib/safe_memoize/version.rb +2 -1
- data/lib/safe_memoize.rb +56 -0
- data/rbi/safe_memoize.rbi +245 -0
- data/sig/safe_memoize.rbs +43 -6
- metadata +8 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "safe_memoize"
|
|
4
|
+
|
|
5
|
+
module SafeMemoize
|
|
6
|
+
module Stores
|
|
7
|
+
# Cache store adapter backed by Redis.
|
|
8
|
+
#
|
|
9
|
+
# Not auto-required. Add to your application:
|
|
10
|
+
# require "safe_memoize/stores/redis"
|
|
11
|
+
#
|
|
12
|
+
# Requires a Redis-compatible client that responds to +#get+, +#set+,
|
|
13
|
+
# +#del+, and +#scan_each+. Compatible with the +redis+ gem (v4+) and
|
|
14
|
+
# any drop-in replacement.
|
|
15
|
+
#
|
|
16
|
+
# Values and keys are serialized with +Marshal+ (Base64-encoded via
|
|
17
|
+
# +Array#pack("m0")+) so that any Ruby object, including +nil+ and
|
|
18
|
+
# +false+, can be stored and retrieved faithfully. TTL is forwarded to
|
|
19
|
+
# Redis as the +PX+ option (milliseconds, rounded up to the nearest
|
|
20
|
+
# millisecond; minimum 1 ms) to preserve sub-second precision.
|
|
21
|
+
#
|
|
22
|
+
# @example Basic setup
|
|
23
|
+
# require "redis"
|
|
24
|
+
# require "safe_memoize/stores/redis"
|
|
25
|
+
#
|
|
26
|
+
# REDIS_STORE = SafeMemoize::Stores::Redis.new(::Redis.new)
|
|
27
|
+
#
|
|
28
|
+
# class MyService
|
|
29
|
+
# prepend SafeMemoize
|
|
30
|
+
# def fetch(id) = http_get(id)
|
|
31
|
+
# memoize :fetch, store: REDIS_STORE, ttl: 300
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example With a custom namespace
|
|
35
|
+
# STORE = SafeMemoize::Stores::Redis.new(::Redis.new, namespace: "myapp:memo")
|
|
36
|
+
class Redis < Base
|
|
37
|
+
# @param client [Object] a Redis-compatible client responding to
|
|
38
|
+
# +#get+, +#set+, +#del+, and +#scan_each+
|
|
39
|
+
# @param namespace [String] key prefix used to scope all entries in Redis;
|
|
40
|
+
# defaults to +"safe_memoize"+
|
|
41
|
+
def initialize(client, namespace: "safe_memoize")
|
|
42
|
+
@client = client
|
|
43
|
+
@namespace = namespace
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param key [Object] cache key (serialized with Marshal + Base64)
|
|
47
|
+
# @return [Object] the stored value, or {MISS} if absent
|
|
48
|
+
def read(key)
|
|
49
|
+
raw = @client.get(redis_key(key))
|
|
50
|
+
return MISS if raw.nil?
|
|
51
|
+
|
|
52
|
+
Marshal.load(raw) # rubocop:disable Security/MarshalLoad
|
|
53
|
+
rescue TypeError, ArgumentError
|
|
54
|
+
MISS
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @param key [Object] cache key
|
|
58
|
+
# @param value [Object] value to store (may be +nil+ or +false+)
|
|
59
|
+
# @param expires_in [Numeric, nil] TTL in seconds forwarded to Redis as +PX+
|
|
60
|
+
# (milliseconds, rounded up; minimum 1 ms); +nil+ means no expiry
|
|
61
|
+
# @return [void]
|
|
62
|
+
def write(key, value, expires_in: nil)
|
|
63
|
+
raw = Marshal.dump(value)
|
|
64
|
+
if expires_in
|
|
65
|
+
@client.set(redis_key(key), raw, px: [(expires_in * 1000).ceil, 1].max)
|
|
66
|
+
else
|
|
67
|
+
@client.set(redis_key(key), raw)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param key [Object]
|
|
72
|
+
# @return [void]
|
|
73
|
+
def delete(key)
|
|
74
|
+
@client.del(redis_key(key))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Removes all entries written by this adapter (scoped to the namespace).
|
|
78
|
+
# Uses +SCAN+ internally to avoid blocking Redis.
|
|
79
|
+
# @return [void]
|
|
80
|
+
def clear
|
|
81
|
+
to_delete = []
|
|
82
|
+
@client.scan_each(match: "#{@namespace}:*") { |k| to_delete << k }
|
|
83
|
+
@client.del(*to_delete) unless to_delete.empty?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns all live keys in the namespace, deserialized back to their
|
|
87
|
+
# original Ruby form. Entries that cannot be deserialized are silently
|
|
88
|
+
# skipped. Because Redis handles TTL natively, every key returned by
|
|
89
|
+
# +SCAN+ is live.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<Object>]
|
|
92
|
+
def keys
|
|
93
|
+
prefix = "#{@namespace}:"
|
|
94
|
+
result = []
|
|
95
|
+
@client.scan_each(match: "#{@namespace}:*") do |rk|
|
|
96
|
+
encoded = rk.delete_prefix(prefix)
|
|
97
|
+
result << Marshal.load(encoded.unpack1("m0")) # rubocop:disable Security/MarshalLoad
|
|
98
|
+
rescue ArgumentError, TypeError
|
|
99
|
+
# skip keys that cannot be deserialized (e.g. written by another serializer)
|
|
100
|
+
end
|
|
101
|
+
result
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def redis_key(key)
|
|
107
|
+
"#{@namespace}:#{[Marshal.dump(key)].pack("m0")}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
data/lib/safe_memoize/version.rb
CHANGED
data/lib/safe_memoize.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "safe_memoize/version"
|
|
4
4
|
require_relative "safe_memoize/configuration"
|
|
5
|
+
require_relative "safe_memoize/stores/base"
|
|
6
|
+
require_relative "safe_memoize/stores/memory"
|
|
5
7
|
require_relative "safe_memoize/adapters/statsd"
|
|
6
8
|
require_relative "safe_memoize/adapters/opentelemetry"
|
|
7
9
|
require_relative "safe_memoize/class_methods"
|
|
@@ -17,27 +19,81 @@ require_relative "safe_memoize/public_custom_key_methods"
|
|
|
17
19
|
require_relative "safe_memoize/lru_methods"
|
|
18
20
|
require_relative "safe_memoize/instance_methods"
|
|
19
21
|
|
|
22
|
+
# Thread-safe memoization for Ruby that correctly handles +nil+ and +false+ values.
|
|
23
|
+
#
|
|
24
|
+
# Prepend this module into any class, then call {ClassMethods#memoize} to wrap
|
|
25
|
+
# instance methods with a per-instance cache backed by a +Mutex+.
|
|
26
|
+
#
|
|
27
|
+
# @example Basic usage
|
|
28
|
+
# class UserService
|
|
29
|
+
# prepend SafeMemoize
|
|
30
|
+
#
|
|
31
|
+
# def current_user
|
|
32
|
+
# User.find_by(session_id: session_id)
|
|
33
|
+
# end
|
|
34
|
+
# memoize :current_user
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# @example With TTL and LRU cap
|
|
38
|
+
# class ApiClient
|
|
39
|
+
# prepend SafeMemoize
|
|
40
|
+
#
|
|
41
|
+
# def fetch(id)
|
|
42
|
+
# http_get("/items/#{id}")
|
|
43
|
+
# end
|
|
44
|
+
# memoize :fetch, ttl: 60, max_size: 500
|
|
45
|
+
# end
|
|
46
|
+
#
|
|
47
|
+
# @see ClassMethods#memoize
|
|
48
|
+
# @see https://github.com/eclectic-coding/safe_memoize README
|
|
20
49
|
module SafeMemoize
|
|
50
|
+
# Base class for all SafeMemoize-specific exceptions.
|
|
51
|
+
# Rescue this to catch any error raised by the library itself.
|
|
21
52
|
class Error < StandardError; end
|
|
22
53
|
|
|
23
54
|
include InstanceMethods
|
|
24
55
|
|
|
56
|
+
# @api private
|
|
25
57
|
def self.prepended(base)
|
|
26
58
|
base.extend(ClassMethods)
|
|
27
59
|
end
|
|
28
60
|
|
|
61
|
+
# Yields the global {Configuration} object for mutation.
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# SafeMemoize.configure do |c|
|
|
65
|
+
# c.default_ttl = 300
|
|
66
|
+
# end
|
|
67
|
+
#
|
|
68
|
+
# @yield [config] The current {Configuration} instance.
|
|
69
|
+
# @yieldparam config [Configuration]
|
|
70
|
+
# @return [void]
|
|
29
71
|
def self.configure
|
|
30
72
|
yield configuration
|
|
31
73
|
end
|
|
32
74
|
|
|
75
|
+
# Returns the global {Configuration} instance, creating it on first access.
|
|
76
|
+
#
|
|
77
|
+
# @return [Configuration]
|
|
33
78
|
def self.configuration
|
|
34
79
|
@configuration ||= Configuration.new
|
|
35
80
|
end
|
|
36
81
|
|
|
82
|
+
# Resets the global configuration to all defaults.
|
|
83
|
+
#
|
|
84
|
+
# Useful in test suites to prevent configuration leaking between examples.
|
|
85
|
+
#
|
|
86
|
+
# @return [Configuration] the new blank configuration
|
|
37
87
|
def self.reset_configuration!
|
|
38
88
|
@configuration = Configuration.new
|
|
39
89
|
end
|
|
40
90
|
|
|
91
|
+
# Emits a structured deprecation warning through the configured handler.
|
|
92
|
+
#
|
|
93
|
+
# @param subject [String] short identifier of the deprecated symbol
|
|
94
|
+
# @param message [String] migration instructions
|
|
95
|
+
# @param horizon [String] version when the symbol will be removed (e.g. +"v2.0.0"+)
|
|
96
|
+
# @return [void]
|
|
41
97
|
def self.deprecate(subject, message:, horizon:)
|
|
42
98
|
text = "[SafeMemoize] #{subject} is deprecated and will be removed in #{horizon}. #{message}"
|
|
43
99
|
handler = configuration.on_deprecation
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
|
|
3
|
+
# Sorbet type stubs for the safe_memoize gem.
|
|
4
|
+
#
|
|
5
|
+
# These stubs cover every symbol listed in the public API guarantee
|
|
6
|
+
# (see README "Public API and versioning guarantee"). Opt-in extensions
|
|
7
|
+
# (SafeMemoize::Rails, SafeMemoize::Adapters::*) are included as well,
|
|
8
|
+
# but they are not part of the v1.0.0 semver guarantee.
|
|
9
|
+
#
|
|
10
|
+
# Usage: add `require "safe_memoize"` to your Sorbet ignore list if you
|
|
11
|
+
# use tapioca, or copy this file into sorbet/rbi/shims/ for manual setups.
|
|
12
|
+
|
|
13
|
+
module SafeMemoize
|
|
14
|
+
VERSION = T.let(T.unsafe(nil), String)
|
|
15
|
+
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
|
|
18
|
+
sig { params(base: T::Class[T.anything]).void }
|
|
19
|
+
def self.prepended(base); end
|
|
20
|
+
|
|
21
|
+
sig { params(blk: T.proc.params(config: SafeMemoize::Configuration).void).void }
|
|
22
|
+
def self.configure(&blk); end
|
|
23
|
+
|
|
24
|
+
sig { returns(SafeMemoize::Configuration) }
|
|
25
|
+
def self.configuration; end
|
|
26
|
+
|
|
27
|
+
sig { returns(SafeMemoize::Configuration) }
|
|
28
|
+
def self.reset_configuration!; end
|
|
29
|
+
|
|
30
|
+
sig { params(subject: String, message: String, horizon: String).void }
|
|
31
|
+
def self.deprecate(subject, message:, horizon:); end
|
|
32
|
+
|
|
33
|
+
class Configuration
|
|
34
|
+
sig { returns(T.nilable(Numeric)) }
|
|
35
|
+
attr_accessor :default_ttl
|
|
36
|
+
|
|
37
|
+
sig { returns(T.nilable(Integer)) }
|
|
38
|
+
attr_accessor :default_max_size
|
|
39
|
+
|
|
40
|
+
sig { returns(T.nilable(T.proc.params(message: String).void)) }
|
|
41
|
+
attr_accessor :on_deprecation
|
|
42
|
+
|
|
43
|
+
sig { returns(T.nilable(T.proc.params(error: Exception, hook_type: Symbol, cache_key: T.untyped).void)) }
|
|
44
|
+
attr_accessor :on_hook_error
|
|
45
|
+
|
|
46
|
+
sig { returns(T::Boolean) }
|
|
47
|
+
attr_accessor :active_support_notifications
|
|
48
|
+
|
|
49
|
+
sig { returns(T.untyped) }
|
|
50
|
+
attr_accessor :statsd_client
|
|
51
|
+
|
|
52
|
+
sig { returns(T.untyped) }
|
|
53
|
+
attr_accessor :opentelemetry_tracer
|
|
54
|
+
|
|
55
|
+
sig { void }
|
|
56
|
+
def initialize; end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
module ClassMethods
|
|
60
|
+
sig do
|
|
61
|
+
params(
|
|
62
|
+
method_name: T.any(Symbol, String),
|
|
63
|
+
ttl: T.nilable(Numeric),
|
|
64
|
+
max_size: T.nilable(Integer),
|
|
65
|
+
ttl_refresh: T::Boolean,
|
|
66
|
+
if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
|
|
67
|
+
unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
|
|
68
|
+
shared: T::Boolean,
|
|
69
|
+
key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
|
|
70
|
+
).void
|
|
71
|
+
end
|
|
72
|
+
def memoize(method_name, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
|
|
73
|
+
|
|
74
|
+
sig do
|
|
75
|
+
params(
|
|
76
|
+
except: T::Array[T.any(Symbol, String)],
|
|
77
|
+
only: T::Array[T.any(Symbol, String)],
|
|
78
|
+
include_protected: T::Boolean,
|
|
79
|
+
include_private: T::Boolean,
|
|
80
|
+
ttl: T.nilable(Numeric),
|
|
81
|
+
max_size: T.nilable(Integer),
|
|
82
|
+
ttl_refresh: T::Boolean,
|
|
83
|
+
if: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
|
|
84
|
+
unless: T.nilable(T.proc.params(result: T.untyped).returns(T.untyped)),
|
|
85
|
+
shared: T::Boolean,
|
|
86
|
+
key: T.nilable(T.proc.params(args: T.untyped).returns(T.untyped))
|
|
87
|
+
).void
|
|
88
|
+
end
|
|
89
|
+
def memoize_all(except: [], only: [], include_protected: false, include_private: false, ttl: nil, max_size: nil, ttl_refresh: false, if: nil, unless: nil, shared: false, key: nil); end
|
|
90
|
+
|
|
91
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
|
|
92
|
+
def reset_shared_memo(method_name, *args, **kwargs); end
|
|
93
|
+
|
|
94
|
+
sig { void }
|
|
95
|
+
def reset_all_shared_memos; end
|
|
96
|
+
|
|
97
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
|
|
98
|
+
def shared_memoized?(method_name, *args, **kwargs); end
|
|
99
|
+
|
|
100
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
|
|
101
|
+
def shared_memo_count(method_name = nil); end
|
|
102
|
+
|
|
103
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
|
|
104
|
+
def shared_memo_age(method_name, *args, **kwargs); end
|
|
105
|
+
|
|
106
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
|
|
107
|
+
def shared_memo_stale?(method_name, *args, **kwargs); end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
module PublicMethods
|
|
111
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped, blk: T.nilable(T.proc.returns(T.untyped))).returns(T::Boolean) }
|
|
112
|
+
def memoized?(method_name, *args, **kwargs, &blk); end
|
|
113
|
+
|
|
114
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(T.any(Float, Integer))) }
|
|
115
|
+
def memo_ttl_remaining(method_name, *args, **kwargs); end
|
|
116
|
+
|
|
117
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(Integer) }
|
|
118
|
+
def memo_count(method_name = nil); end
|
|
119
|
+
|
|
120
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
|
|
121
|
+
def memo_keys(method_name = nil); end
|
|
122
|
+
|
|
123
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Array[T.untyped]) }
|
|
124
|
+
def memo_values(method_name = nil); end
|
|
125
|
+
|
|
126
|
+
sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
|
|
127
|
+
def on_memo_expire(&blk); end
|
|
128
|
+
|
|
129
|
+
sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
|
|
130
|
+
def on_memo_evict(&blk); end
|
|
131
|
+
|
|
132
|
+
sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
|
|
133
|
+
def on_memo_hit(&blk); end
|
|
134
|
+
|
|
135
|
+
sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
|
|
136
|
+
def on_memo_miss(&blk); end
|
|
137
|
+
|
|
138
|
+
sig { params(blk: T.proc.params(cache_key: T.untyped, record: T::Hash[Symbol, T.untyped]).returns(T.untyped)).void }
|
|
139
|
+
def on_memo_store(&blk); end
|
|
140
|
+
|
|
141
|
+
sig { params(hook_type: T.nilable(Symbol)).void }
|
|
142
|
+
def clear_memo_hooks(hook_type = nil); end
|
|
143
|
+
|
|
144
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped, blk: T.proc.returns(T.untyped)).returns(T.untyped) }
|
|
145
|
+
def warm_memo(method_name, *args, ttl: nil, **kwargs, &blk); end
|
|
146
|
+
|
|
147
|
+
sig { params(method_name: T.any(Symbol, String), arg_sets: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
|
148
|
+
def memo_preload(method_name, *arg_sets); end
|
|
149
|
+
|
|
150
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).returns(T::Hash[T.untyped, T.untyped]) }
|
|
151
|
+
def dump_memo(method_name = nil); end
|
|
152
|
+
|
|
153
|
+
sig { params(snapshot: T::Hash[T.untyped, T.untyped]).void }
|
|
154
|
+
def load_memo(snapshot); end
|
|
155
|
+
|
|
156
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, ttl: T.nilable(Numeric), kwargs: T.untyped).returns(T::Boolean) }
|
|
157
|
+
def memo_touch(method_name, *args, ttl: nil, **kwargs); end
|
|
158
|
+
|
|
159
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.untyped) }
|
|
160
|
+
def memo_refresh(method_name, *args, **kwargs); end
|
|
161
|
+
|
|
162
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T.nilable(Float)) }
|
|
163
|
+
def memo_age(method_name, *args, **kwargs); end
|
|
164
|
+
|
|
165
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).returns(T::Boolean) }
|
|
166
|
+
def memo_stale?(method_name, *args, **kwargs); end
|
|
167
|
+
|
|
168
|
+
sig { params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped).void }
|
|
169
|
+
def reset_memo(method_name, *args, **kwargs); end
|
|
170
|
+
|
|
171
|
+
sig { void }
|
|
172
|
+
def reset_all_memos; end
|
|
173
|
+
|
|
174
|
+
sig do
|
|
175
|
+
params(method_name: T.any(Symbol, String), args: T.untyped, kwargs: T.untyped)
|
|
176
|
+
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
|
177
|
+
end
|
|
178
|
+
def memo_inspect(method_name, *args, **kwargs); end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
module PublicMetricsMethods
|
|
182
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
183
|
+
def cache_stats; end
|
|
184
|
+
|
|
185
|
+
sig { params(method_name: T.any(Symbol, String)).returns(T::Hash[Symbol, T.untyped]) }
|
|
186
|
+
def cache_stats_for(method_name); end
|
|
187
|
+
|
|
188
|
+
sig { returns(Float) }
|
|
189
|
+
def cache_hit_rate; end
|
|
190
|
+
|
|
191
|
+
sig { returns(Float) }
|
|
192
|
+
def cache_miss_rate; end
|
|
193
|
+
|
|
194
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
|
|
195
|
+
def cache_metrics_reset(method_name = nil); end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
module PublicCustomKeyMethods
|
|
199
|
+
sig { params(method_name: T.any(Symbol, String), blk: T.proc.params(args: T.untyped).returns(T.untyped)).void }
|
|
200
|
+
def memoize_with_custom_key(method_name, &blk); end
|
|
201
|
+
|
|
202
|
+
sig { params(method_name: T.nilable(T.any(Symbol, String))).void }
|
|
203
|
+
def clear_custom_keys(method_name = nil); end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
module Adapters
|
|
207
|
+
module StatsD
|
|
208
|
+
METRIC_NAMES = T.let(T.unsafe(nil), T::Hash[Symbol, String])
|
|
209
|
+
|
|
210
|
+
sig { params(client: T.untyped, hook_type: Symbol, cache_key: T.untyped, class_name: T.nilable(String)).void }
|
|
211
|
+
def self.dispatch(client, hook_type, cache_key, class_name); end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
module OpenTelemetry
|
|
215
|
+
SPAN_NAME = T.let(T.unsafe(nil), String)
|
|
216
|
+
|
|
217
|
+
sig { params(tracer: T.untyped, method_name: T.any(Symbol, String), class_name: T.nilable(String), blk: T.proc.returns(T.untyped)).returns(T.untyped) }
|
|
218
|
+
def self.trace(tracer, method_name, class_name, &blk); end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
module Rails
|
|
223
|
+
sig { params(instance: T.untyped).void }
|
|
224
|
+
def self.track(instance); end
|
|
225
|
+
|
|
226
|
+
sig { void }
|
|
227
|
+
def self.reset_tracked!; end
|
|
228
|
+
|
|
229
|
+
module RequestScoped
|
|
230
|
+
sig { params(base: Module).void }
|
|
231
|
+
def self.included(base); end
|
|
232
|
+
|
|
233
|
+
sig { void }
|
|
234
|
+
def reset_request_memos; end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
class Middleware
|
|
238
|
+
sig { params(app: T.untyped).void }
|
|
239
|
+
def initialize(app); end
|
|
240
|
+
|
|
241
|
+
sig { params(env: T.untyped).returns(T.untyped) }
|
|
242
|
+
def call(env); end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
data/sig/safe_memoize.rbs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
module SafeMemoize
|
|
2
2
|
VERSION: String
|
|
3
|
+
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
end
|
|
6
|
+
|
|
3
7
|
include InstanceMethods
|
|
4
8
|
|
|
5
9
|
type default_memo_key = [Symbol, Array[untyped], Hash[Symbol, untyped]]
|
|
@@ -28,12 +32,13 @@ module SafeMemoize
|
|
|
28
32
|
attr_accessor active_support_notifications: bool
|
|
29
33
|
attr_accessor statsd_client: untyped
|
|
30
34
|
attr_accessor opentelemetry_tracer: untyped
|
|
35
|
+
attr_accessor default_store: Stores::Base?
|
|
31
36
|
|
|
32
37
|
def initialize: () -> void
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
module ClassMethods
|
|
36
|
-
def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?ttl_refresh: bool, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?) -> void
|
|
41
|
+
def memoize: (Symbol | String method_name, ?ttl: Numeric?, ?max_size: Integer?, ?ttl_refresh: bool, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?, ?store: Stores::Base?) -> void
|
|
37
42
|
def memoize_all: (?except: Array[Symbol | String], ?only: Array[Symbol | String], ?include_protected: bool, ?include_private: bool, ?ttl: Numeric?, ?max_size: Integer?, ?if: (^(untyped result) -> boolish)?, ?unless: (^(untyped result) -> boolish)?, ?shared: bool, ?key: (^(*untyped args, **untyped kwargs) -> untyped)?) -> void
|
|
38
43
|
def reset_shared_memo: (Symbol | String method_name, *untyped args, **untyped kwargs) -> void
|
|
39
44
|
def reset_all_shared_memos: () -> void
|
|
@@ -56,15 +61,15 @@ module SafeMemoize
|
|
|
56
61
|
|
|
57
62
|
def memoized?: (Symbol | String method_name, *untyped args, **untyped kwargs) ?{ () -> untyped } -> bool
|
|
58
63
|
def memo_ttl_remaining: (Symbol | String method_name, *untyped args, **untyped kwargs) -> (Float | Integer | nil)
|
|
59
|
-
def memo_count: (
|
|
60
|
-
def memo_keys: (
|
|
61
|
-
def memo_values: (
|
|
64
|
+
def memo_count: (?(Symbol | String) method_name) -> Integer
|
|
65
|
+
def memo_keys: (?(Symbol | String) method_name) -> Array[untyped]
|
|
66
|
+
def memo_values: (?(Symbol | String) method_name) -> Array[untyped]
|
|
62
67
|
def on_memo_expire: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
63
68
|
def on_memo_evict: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
64
69
|
def on_memo_hit: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
65
70
|
def on_memo_miss: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
66
71
|
def on_memo_store: { (memo_key cache_key, memo_record record) -> untyped } -> void
|
|
67
|
-
def clear_memo_hooks: (Symbol
|
|
72
|
+
def clear_memo_hooks: (?Symbol hook_type) -> void
|
|
68
73
|
def warm_memo: (Symbol | String method_name, *untyped args, ?ttl: Numeric?, **untyped kwargs) { () -> untyped } -> untyped
|
|
69
74
|
def memo_preload: (Symbol | String method_name, *Array[untyped] arg_sets) -> Array[untyped]
|
|
70
75
|
def dump_memo: (?Symbol | String method_name) -> Hash[memo_key, untyped]
|
|
@@ -167,7 +172,7 @@ module SafeMemoize
|
|
|
167
172
|
|
|
168
173
|
module PublicCustomKeyMethods
|
|
169
174
|
def memoize_with_custom_key: (Symbol | String method_name) { (*untyped args, **untyped kwargs) -> untyped } -> void
|
|
170
|
-
def clear_custom_keys: (Symbol | String
|
|
175
|
+
def clear_custom_keys: (?(Symbol | String) method_name) -> void
|
|
171
176
|
end
|
|
172
177
|
|
|
173
178
|
module LruMethods
|
|
@@ -195,7 +200,39 @@ module SafeMemoize
|
|
|
195
200
|
include LruMethods
|
|
196
201
|
end
|
|
197
202
|
|
|
203
|
+
module Stores
|
|
204
|
+
class Base
|
|
205
|
+
MISS: Object
|
|
206
|
+
|
|
207
|
+
def read: (untyped key) -> untyped
|
|
208
|
+
def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
|
|
209
|
+
def delete: (untyped key) -> void
|
|
210
|
+
def clear: () -> void
|
|
211
|
+
def keys: () -> Array[untyped]
|
|
212
|
+
def exist?: (untyped key) -> bool
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
class Memory < Base
|
|
216
|
+
def initialize: () -> void
|
|
217
|
+
def read: (untyped key) -> untyped
|
|
218
|
+
def write: (untyped key, untyped value, ?expires_in: Numeric?) -> void
|
|
219
|
+
def delete: (untyped key) -> void
|
|
220
|
+
def clear: () -> void
|
|
221
|
+
def keys: () -> Array[untyped]
|
|
222
|
+
|
|
223
|
+
private
|
|
224
|
+
|
|
225
|
+
def expired?: ({ expires_at: Float?, value: untyped, cached_at: Float }) -> bool
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
198
229
|
module Adapters
|
|
230
|
+
module StatsD
|
|
231
|
+
METRIC_NAMES: Hash[Symbol, String]
|
|
232
|
+
|
|
233
|
+
def self.dispatch: (untyped client, Symbol hook_type, memo_key cache_key, String? class_name) -> void
|
|
234
|
+
end
|
|
235
|
+
|
|
199
236
|
module OpenTelemetry
|
|
200
237
|
SPAN_NAME: String
|
|
201
238
|
def self.trace: (untyped tracer, Symbol | String method_name, String? class_name) { () -> untyped } -> untyped
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: safe_memoize
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chuck Smith
|
|
@@ -41,11 +41,13 @@ extra_rdoc_files: []
|
|
|
41
41
|
files:
|
|
42
42
|
- ".github/workflows/ci.yml"
|
|
43
43
|
- ".github/workflows/release.yml"
|
|
44
|
+
- ".yardopts"
|
|
44
45
|
- CHANGELOG.md
|
|
45
46
|
- LICENSE.txt
|
|
46
47
|
- README.md
|
|
47
48
|
- ROADMAP.md
|
|
48
49
|
- Rakefile
|
|
50
|
+
- UPGRADING.md
|
|
49
51
|
- benchmarks/README.md
|
|
50
52
|
- benchmarks/benchmark.rb
|
|
51
53
|
- codecov.yml
|
|
@@ -69,7 +71,12 @@ files:
|
|
|
69
71
|
- lib/safe_memoize/rails/middleware.rb
|
|
70
72
|
- lib/safe_memoize/rails/request_scoped.rb
|
|
71
73
|
- lib/safe_memoize/release_tooling.rb
|
|
74
|
+
- lib/safe_memoize/stores/base.rb
|
|
75
|
+
- lib/safe_memoize/stores/memory.rb
|
|
76
|
+
- lib/safe_memoize/stores/rails_cache.rb
|
|
77
|
+
- lib/safe_memoize/stores/redis.rb
|
|
72
78
|
- lib/safe_memoize/version.rb
|
|
79
|
+
- rbi/safe_memoize.rbi
|
|
73
80
|
- sig/safe_memoize.rbs
|
|
74
81
|
homepage: https://github.com/eclectic-coding/safe_memoize
|
|
75
82
|
licenses:
|