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