tamber 0.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 +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +8 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.rdoc +71 -0
- data/Rakefile +17 -0
- data/VERSION +1 -0
- data/lib/security/ca-bundle.crt +3893 -0
- data/lib/tamber.rb +202 -0
- data/lib/tamber/api_operations/create.rb +10 -0
- data/lib/tamber/api_operations/remove.rb +10 -0
- data/lib/tamber/api_operations/request.rb +21 -0
- data/lib/tamber/api_operations/retrieve.rb +10 -0
- data/lib/tamber/api_operations/update.rb +10 -0
- data/lib/tamber/api_resource.rb +21 -0
- data/lib/tamber/behavior.rb +6 -0
- data/lib/tamber/discover.rb +54 -0
- data/lib/tamber/event.rb +24 -0
- data/lib/tamber/item.rb +8 -0
- data/lib/tamber/property.rb +6 -0
- data/lib/tamber/tamber_error.rb +10 -0
- data/lib/tamber/tamber_object.rb +35 -0
- data/lib/tamber/user.rb +7 -0
- data/lib/tamber/util.rb +90 -0
- data/lib/tamber/version.rb +3 -0
- data/tamber.gemspec +34 -0
- data/test/discover_test.rb +40 -0
- data/test/event_test.rb +53 -0
- data/test/test_helper.rb +17 -0
- data/test/user_test.rb +64 -0
- metadata +165 -0
data/lib/tamber.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# Tamber Ruby bindings
|
2
|
+
# API spec at https://tamber.com/docs/api
|
3
|
+
require 'cgi'
|
4
|
+
require 'openssl'
|
5
|
+
require 'rbconfig'
|
6
|
+
require 'set'
|
7
|
+
require 'socket'
|
8
|
+
require 'base64'
|
9
|
+
|
10
|
+
require 'rest-client'
|
11
|
+
require 'json'
|
12
|
+
|
13
|
+
require 'tamber/version'
|
14
|
+
|
15
|
+
require 'tamber/api_operations/create'
|
16
|
+
require 'tamber/api_operations/update'
|
17
|
+
require 'tamber/api_operations/remove'
|
18
|
+
require 'tamber/api_operations/retrieve'
|
19
|
+
require 'tamber/api_operations/request'
|
20
|
+
|
21
|
+
require 'tamber/tamber_object'
|
22
|
+
require 'tamber/util'
|
23
|
+
require 'tamber/api_resource'
|
24
|
+
require 'tamber/discover'
|
25
|
+
require 'tamber/event'
|
26
|
+
require 'tamber/user'
|
27
|
+
require 'tamber/item'
|
28
|
+
require 'tamber/behavior'
|
29
|
+
require 'tamber/property'
|
30
|
+
|
31
|
+
|
32
|
+
require 'tamber/tamber_error'
|
33
|
+
|
34
|
+
|
35
|
+
module Tamber
|
36
|
+
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/security/ca-bundle.crt'
|
37
|
+
|
38
|
+
@ca_bundle_path = DEFAULT_CA_BUNDLE_PATH
|
39
|
+
@verify_ssl_certs = true
|
40
|
+
|
41
|
+
@api_url = 'https://api.tamber.com/v1'
|
42
|
+
@open_timeout = 30
|
43
|
+
@read_timeout = 80
|
44
|
+
|
45
|
+
class << self
|
46
|
+
attr_accessor :api_key, :api_base, :api_version, :verify_ssl_certs, :open_timeout, :read_timeout
|
47
|
+
|
48
|
+
# attr_reader :max_network_retry_delay, :initial_network_retry_delay
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def self.api_url(api_base_url=nil, url='')
|
53
|
+
(api_base_url || @api_url) + url
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.ca_bundle_path
|
57
|
+
@ca_bundle_path
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.ca_bundle_path=(path)
|
61
|
+
@ca_bundle_path = path
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.request(method, url, params={})
|
65
|
+
api_base_url = api_base_url || @api_base
|
66
|
+
|
67
|
+
unless api_key ||= @api_key
|
68
|
+
raise TamberError.new('No API key provided. ' \
|
69
|
+
'Set your engine-specific API key using "Tamber.api_key = <ENGINE-API-KEY>". ' \
|
70
|
+
'You can get your engine\'s api key from the Tamber dashboard. ' \
|
71
|
+
'See https://dashboard.tamber.com to get your engine\'s key, or ' \
|
72
|
+
'email support@tamber.com if you have any questions.)')
|
73
|
+
end
|
74
|
+
|
75
|
+
if api_key =~ /\s/
|
76
|
+
raise TamberError.new('Your API key is invalid, as it contains ' \
|
77
|
+
'whitespace. (HINT: You can double-check your API key from the ' \
|
78
|
+
'Tamber dashboard. See https://dashboard.tamber.com to get your engine\'s key, or ' \
|
79
|
+
'email support@tamber.com if you have any questions.)')
|
80
|
+
end
|
81
|
+
|
82
|
+
if verify_ssl_certs
|
83
|
+
request_opts = {:verify_ssl => OpenSSL::SSL::VERIFY_PEER,
|
84
|
+
:ssl_ca_file => @ca_bundle_path}
|
85
|
+
else
|
86
|
+
request_opts = {:verify_ssl => false}
|
87
|
+
unless @verify_ssl_warned
|
88
|
+
@verify_ssl_warned = true
|
89
|
+
$stderr.puts("WARNING: Running without SSL cert verification. " \
|
90
|
+
"You should never do this in production. " \
|
91
|
+
"Execute 'Tamber.verify_ssl_certs = true' to enable verification.")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# params = Util.objects_to_ids(params)
|
96
|
+
url = api_url(api_base_url,url)
|
97
|
+
|
98
|
+
case method.to_s.downcase.to_sym
|
99
|
+
when :get, :head, :delete
|
100
|
+
# Make params into GET parameters
|
101
|
+
url += "#{URI.parse(url).query ? '&' : '?'}#{Util.encode_parameters(params)}" if params && params.any?
|
102
|
+
payload = nil
|
103
|
+
else
|
104
|
+
payload = Util.encode_parameters(params)
|
105
|
+
end
|
106
|
+
|
107
|
+
request_opts.update(:headers => request_headers(api_key, method),
|
108
|
+
:method => method, :open_timeout => open_timeout,
|
109
|
+
:payload => payload, :url => url, :timeout => read_timeout)
|
110
|
+
|
111
|
+
response = execute_request_with_rescues(request_opts, api_base_url)
|
112
|
+
|
113
|
+
[parse(response)]
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
|
118
|
+
def self.request_headers(api_key, method)
|
119
|
+
encoded_key = Base64.encode64(api_key + ':')
|
120
|
+
headers = {
|
121
|
+
:user_agent => "Tamber/v1 RubyBindings/#{Tamber::VERSION}",
|
122
|
+
:authorization => "Basic "+encoded_key,
|
123
|
+
:content_type => 'application/x-www-form-urlencoded'
|
124
|
+
}
|
125
|
+
|
126
|
+
headers[:tamber_version] = api_version if api_version
|
127
|
+
|
128
|
+
begin
|
129
|
+
headers.update(:x_tamber_client_user_agent => JSON.generate(user_agent))
|
130
|
+
rescue => e
|
131
|
+
headers.update(:x_tamber_client_raw_user_agent => user_agent.inspect,
|
132
|
+
:error => "#{e} (#{e.class})")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.user_agent
|
137
|
+
lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
|
138
|
+
|
139
|
+
{
|
140
|
+
:bindings_version => Tamber::VERSION,
|
141
|
+
:lang => 'ruby',
|
142
|
+
:lang_version => lang_version,
|
143
|
+
:platform => RUBY_PLATFORM,
|
144
|
+
:engine => defined?(RUBY_ENGINE) ? RUBY_ENGINE : '',
|
145
|
+
:publisher => 'tamber',
|
146
|
+
:hostname => Socket.gethostname,
|
147
|
+
}
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.general_api_error(rbody)
|
152
|
+
TamberError.new("Invalid response object from API: "+rbody.inspect)
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.execute_request(opts)
|
156
|
+
RestClient::Request.execute(opts)
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.parse(response)
|
160
|
+
begin
|
161
|
+
response = JSON.parse(response.body)
|
162
|
+
if response["success"]
|
163
|
+
response = response["result"]
|
164
|
+
else
|
165
|
+
raise TamberError.new("Error: "+response["error"])
|
166
|
+
end
|
167
|
+
rescue JSON::ParserError
|
168
|
+
raise general_api_error(response.body)
|
169
|
+
end
|
170
|
+
|
171
|
+
Util.symbolize_names(response)
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def self.execute_request_with_rescues(request_opts, api_base_url, retry_count = 0)
|
177
|
+
response = execute_request(request_opts)
|
178
|
+
# begin
|
179
|
+
# response = execute_request(request_opts)
|
180
|
+
# rescue SocketError => e
|
181
|
+
# response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
|
182
|
+
# rescue NoMethodError => e
|
183
|
+
# # Work around RestClient bug
|
184
|
+
# if e.message =~ /\WRequestFailed\W/
|
185
|
+
# e = APIConnectionError.new('Unexpected HTTP response code')
|
186
|
+
# response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
|
187
|
+
# else
|
188
|
+
# raise
|
189
|
+
# end
|
190
|
+
# rescue RestClient::ExceptionWithResponse => e
|
191
|
+
# if e.response
|
192
|
+
# handle_api_error(e.response)
|
193
|
+
# else
|
194
|
+
# response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
|
195
|
+
# end
|
196
|
+
# rescue RestClient::Exception, Errno::ECONNREFUSED => e
|
197
|
+
# response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
|
198
|
+
# end
|
199
|
+
|
200
|
+
# response
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tamber
|
2
|
+
module APIOperations
|
3
|
+
module Request
|
4
|
+
module ClassMethods
|
5
|
+
def request(method, url, params={})
|
6
|
+
response = Tamber.request(method, url, params)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def request(method, url, params={})
|
17
|
+
self.class.request(method, url, params)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Tamber
|
2
|
+
class APIResource < TamberObject
|
3
|
+
include Tamber::APIOperations::Request
|
4
|
+
|
5
|
+
def self.class_name
|
6
|
+
self.name.split('::')[-1]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.url
|
10
|
+
if self == APIResource
|
11
|
+
raise TamberError.new('APIResource is an abstract class. You should perform actions on its subclasses (Event, Discover, etc.)')
|
12
|
+
end
|
13
|
+
"/#{CGI.escape(class_name.downcase)}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def url
|
17
|
+
"#{self.class.url}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Tamber
|
2
|
+
class Discover < APIResource
|
3
|
+
|
4
|
+
def self.recommended(params={})
|
5
|
+
response = request(:get, self.recommended_url, params)
|
6
|
+
Util.convert_to_tamber_object(response)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.similar(params={})
|
10
|
+
response = request(:get, self.similar_url, params)
|
11
|
+
Util.convert_to_tamber_object(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.recommendedSimilar(params={})
|
15
|
+
response = request(:get, self.recommendedSimilar_url, params)
|
16
|
+
Util.convert_to_tamber_object(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.popular(params={})
|
20
|
+
response = request(:get, self.popular_url, params)
|
21
|
+
Util.convert_to_tamber_object(response)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.hot(params={})
|
25
|
+
response = request(:get, self.hot_url, params)
|
26
|
+
Util.convert_to_tamber_object(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.recommended_url
|
30
|
+
url + '/recommended'
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.similar_url
|
34
|
+
url + '/similar'
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.recommendedSimilar_url
|
38
|
+
url + '/recommendedSimilar'
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.popular_url
|
42
|
+
url + '/popular'
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.hot_url
|
46
|
+
url + '/hot'
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
# Discover requests return an array of 'discovery' objects
|
52
|
+
class Discovery < TamberObject
|
53
|
+
end
|
54
|
+
end
|
data/lib/tamber/event.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Tamber
|
2
|
+
class Event < APIResource
|
3
|
+
|
4
|
+
extend Tamber::APIOperations::Retrieve
|
5
|
+
|
6
|
+
def self.track_url
|
7
|
+
url + '/track'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.batch_url
|
11
|
+
url + '/batch'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.track(params={})
|
15
|
+
response = request(:post, self.track_url, params)
|
16
|
+
Util.convert_to_tamber_object(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.batch(params={})
|
20
|
+
response = request(:post, self.batch_url, params)
|
21
|
+
Util.convert_to_tamber_object(response)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/tamber/item.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tamber
|
2
|
+
class TamberObject
|
3
|
+
|
4
|
+
def initialize(values={})
|
5
|
+
values.each do |k,v|
|
6
|
+
val = self.class.curvert(v)
|
7
|
+
self.instance_variable_set("@#{k}", val)
|
8
|
+
self.singleton_class.send(:attr_accessor, k)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.construct_from(values)
|
13
|
+
values = Tamber::Util.symbolize_names(values)
|
14
|
+
self.new(values)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.curvert(values={})
|
18
|
+
case values
|
19
|
+
when Hash
|
20
|
+
if values.has_key?(:object)
|
21
|
+
Tamber::Util.object_classes.fetch(values[:object],TamberObject).new(values)
|
22
|
+
else
|
23
|
+
values.each do |k,v|
|
24
|
+
values[k] = curvert(v)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
when Array
|
28
|
+
values.map { |i| curvert(i) }
|
29
|
+
else
|
30
|
+
values
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|