glebprop 0.0.2 → 0.0.3
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.
- data/lib/glebprop/version.rb +1 -1
- data/lib/glebprop.rb +114 -5
- metadata +3 -4
- data/lib/prop.rb +0 -118
data/lib/glebprop/version.rb
CHANGED
data/lib/glebprop.rb
CHANGED
@@ -1,8 +1,117 @@
|
|
1
|
-
puts 'loading glebprop gem'
|
2
|
-
|
3
1
|
require "glebprop/version"
|
4
|
-
require '
|
2
|
+
require 'digest/md5'
|
5
3
|
|
6
|
-
|
7
|
-
|
4
|
+
class Object
|
5
|
+
def define_prop_class_method(name, &blk)
|
6
|
+
(class << self; self; end).instance_eval { define_method(name, &blk) }
|
7
|
+
end
|
8
8
|
end
|
9
|
+
|
10
|
+
class Glebprop
|
11
|
+
class RateLimitExceededError < RuntimeError
|
12
|
+
attr_accessor :handle, :retry_after
|
13
|
+
|
14
|
+
def self.create(handle, key, threshold)
|
15
|
+
error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
|
16
|
+
error.handle = handle
|
17
|
+
error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
|
18
|
+
raise error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :handles, :reader, :writer
|
24
|
+
|
25
|
+
def read(&blk)
|
26
|
+
self.reader = blk
|
27
|
+
end
|
28
|
+
|
29
|
+
def write(&blk)
|
30
|
+
self.writer = blk
|
31
|
+
end
|
32
|
+
|
33
|
+
def increment(&blk)
|
34
|
+
self.incrementer = blk
|
35
|
+
end
|
36
|
+
|
37
|
+
def incrementer=(value)
|
38
|
+
@incrementer = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def incrementer
|
42
|
+
@incrementer ? Proc.new { |key, inc| @incrementer.call(key, inc) } :
|
43
|
+
Proc.new { |key, inc| self.writer.call(key, (self.reader.call(key) || 0).to_i + inc) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def defaults(handle, defaults)
|
47
|
+
raise RuntimeError.new("Invalid threshold setting") unless defaults[:threshold].to_i > 0
|
48
|
+
raise RuntimeError.new("Invalid interval setting") unless defaults[:interval].to_i > 0
|
49
|
+
|
50
|
+
self.handles ||= {}
|
51
|
+
self.handles[handle] = defaults
|
52
|
+
end
|
53
|
+
|
54
|
+
def throttle!(handle, key = nil, options = {})
|
55
|
+
options = sanitized_prop_options(handle, key, options)
|
56
|
+
cache_key = sanitized_prop_key(key, options[:interval])
|
57
|
+
counter = reader.call(cache_key).to_i
|
58
|
+
|
59
|
+
incrementer.call(cache_key, [ 1, options[:increment].to_i ].max)
|
60
|
+
|
61
|
+
if counter >= options[:threshold]
|
62
|
+
raise Glebprop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def throttled?(handle, key = nil, options = {})
|
67
|
+
options = sanitized_prop_options(handle, key, options)
|
68
|
+
cache_key = sanitized_prop_key(key, options[:interval])
|
69
|
+
|
70
|
+
reader.call(cache_key).to_i >= options[:threshold]
|
71
|
+
end
|
72
|
+
|
73
|
+
def reset(handle, key = nil, options = {})
|
74
|
+
options = sanitized_prop_options(handle, key, options)
|
75
|
+
cache_key = sanitized_prop_key(key, options[:interval])
|
76
|
+
|
77
|
+
writer.call(cache_key, 0)
|
78
|
+
end
|
79
|
+
|
80
|
+
def query(handle, key = nil, options = {})
|
81
|
+
options = sanitized_prop_options(handle, key, options)
|
82
|
+
cache_key = sanitized_prop_key(key, options[:interval])
|
83
|
+
|
84
|
+
reader.call(cache_key).to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Builds the expiring cache key
|
90
|
+
def sanitized_prop_key(key, interval)
|
91
|
+
window = (Time.now.to_i / interval)
|
92
|
+
cache_key = "#{normalize_cache_key(key)}/#{ window }"
|
93
|
+
"prop/#{Digest::MD5.hexdigest(cache_key)}"
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sanitizes the option set and sets defaults
|
97
|
+
def sanitized_prop_options(handle, key, options)
|
98
|
+
defaults = (handles || {})[handle] || {}
|
99
|
+
return {
|
100
|
+
:key => normalize_cache_key(key),
|
101
|
+
:increment => defaults[:increment],
|
102
|
+
:threshold => defaults[:threshold].to_i,
|
103
|
+
:interval => defaults[:interval].to_i
|
104
|
+
}.merge(options)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Simple key expansion only supports arrays and primitives
|
108
|
+
def normalize_cache_key(key)
|
109
|
+
if key.is_a?(Array)
|
110
|
+
key.map { |part| normalize_cache_key(part) }.join('/')
|
111
|
+
else
|
112
|
+
key.to_s
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: glebprop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Gleb Pomykalov
|
@@ -34,7 +34,6 @@ files:
|
|
34
34
|
- glebprop.gemspec
|
35
35
|
- lib/glebprop.rb
|
36
36
|
- lib/glebprop/version.rb
|
37
|
-
- lib/prop.rb
|
38
37
|
homepage: ""
|
39
38
|
licenses: []
|
40
39
|
|
data/lib/prop.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
puts 'loading prop'
|
2
|
-
|
3
|
-
require 'digest/md5'
|
4
|
-
|
5
|
-
class Object
|
6
|
-
def define_prop_class_method(name, &blk)
|
7
|
-
(class << self; self; end).instance_eval { define_method(name, &blk) }
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
class Prop
|
12
|
-
class RateLimitExceededError < RuntimeError
|
13
|
-
attr_accessor :handle, :retry_after
|
14
|
-
|
15
|
-
def self.create(handle, key, threshold)
|
16
|
-
error = new("#{handle} threshold of #{threshold} exceeded for key '#{key}'")
|
17
|
-
error.handle = handle
|
18
|
-
error.retry_after = threshold - Time.now.to_i % threshold if threshold > 0
|
19
|
-
raise error
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
class << self
|
24
|
-
attr_accessor :handles, :reader, :writer
|
25
|
-
|
26
|
-
def read(&blk)
|
27
|
-
self.reader = blk
|
28
|
-
end
|
29
|
-
|
30
|
-
def write(&blk)
|
31
|
-
self.writer = blk
|
32
|
-
end
|
33
|
-
|
34
|
-
def increment(&blk)
|
35
|
-
self.incrementer = blk
|
36
|
-
end
|
37
|
-
|
38
|
-
def incrementer=(value)
|
39
|
-
@incrementer = value
|
40
|
-
end
|
41
|
-
|
42
|
-
def incrementer
|
43
|
-
@incrementer ? Proc.new { |key, inc| @incrementer.call(key, inc) } :
|
44
|
-
Proc.new { |key, inc| self.writer.call(key, (self.reader.call(key) || 0).to_i + inc) }
|
45
|
-
end
|
46
|
-
|
47
|
-
def defaults(handle, defaults)
|
48
|
-
raise RuntimeError.new("Invalid threshold setting") unless defaults[:threshold].to_i > 0
|
49
|
-
raise RuntimeError.new("Invalid interval setting") unless defaults[:interval].to_i > 0
|
50
|
-
|
51
|
-
self.handles ||= {}
|
52
|
-
self.handles[handle] = defaults
|
53
|
-
end
|
54
|
-
|
55
|
-
def throttle!(handle, key = nil, options = {})
|
56
|
-
options = sanitized_prop_options(handle, key, options)
|
57
|
-
cache_key = sanitized_prop_key(key, options[:interval])
|
58
|
-
counter = reader.call(cache_key).to_i
|
59
|
-
|
60
|
-
incrementer.call(cache_key, [ 1, options[:increment].to_i ].max)
|
61
|
-
|
62
|
-
if counter >= options[:threshold]
|
63
|
-
raise Prop::RateLimitExceededError.create(handle, normalize_cache_key(key), options[:threshold])
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def throttled?(handle, key = nil, options = {})
|
68
|
-
options = sanitized_prop_options(handle, key, options)
|
69
|
-
cache_key = sanitized_prop_key(key, options[:interval])
|
70
|
-
|
71
|
-
reader.call(cache_key).to_i >= options[:threshold]
|
72
|
-
end
|
73
|
-
|
74
|
-
def reset(handle, key = nil, options = {})
|
75
|
-
options = sanitized_prop_options(handle, key, options)
|
76
|
-
cache_key = sanitized_prop_key(key, options[:interval])
|
77
|
-
|
78
|
-
writer.call(cache_key, 0)
|
79
|
-
end
|
80
|
-
|
81
|
-
def query(handle, key = nil, options = {})
|
82
|
-
options = sanitized_prop_options(handle, key, options)
|
83
|
-
cache_key = sanitized_prop_key(key, options[:interval])
|
84
|
-
|
85
|
-
reader.call(cache_key).to_i
|
86
|
-
end
|
87
|
-
|
88
|
-
private
|
89
|
-
|
90
|
-
# Builds the expiring cache key
|
91
|
-
def sanitized_prop_key(key, interval)
|
92
|
-
window = (Time.now.to_i / interval)
|
93
|
-
cache_key = "#{normalize_cache_key(key)}/#{ window }"
|
94
|
-
"prop/#{Digest::MD5.hexdigest(cache_key)}"
|
95
|
-
end
|
96
|
-
|
97
|
-
# Sanitizes the option set and sets defaults
|
98
|
-
def sanitized_prop_options(handle, key, options)
|
99
|
-
defaults = (handles || {})[handle] || {}
|
100
|
-
return {
|
101
|
-
:key => normalize_cache_key(key),
|
102
|
-
:increment => defaults[:increment],
|
103
|
-
:threshold => defaults[:threshold].to_i,
|
104
|
-
:interval => defaults[:interval].to_i
|
105
|
-
}.merge(options)
|
106
|
-
end
|
107
|
-
|
108
|
-
# Simple key expansion only supports arrays and primitives
|
109
|
-
def normalize_cache_key(key)
|
110
|
-
if key.is_a?(Array)
|
111
|
-
key.map { |part| normalize_cache_key(part) }.join('/')
|
112
|
-
else
|
113
|
-
key.to_s
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
end
|