tamber 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Create
4
+ def create(params={})
5
+ response = request(:post, url+"/create", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Remove
4
+ def remove(params={})
5
+ response = request(:delete, url+"/remove", params)
6
+ # initialize_from(response, opts)
7
+ end
8
+ end
9
+ end
10
+ 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,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Retrieve
4
+ def retrieve(params={})
5
+ response = request(:post, url+"/retrieve", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ module APIOperations
3
+ module Update
4
+ def update(params={})
5
+ response = request(:post, url+"/update", params)
6
+ Util.convert_to_tamber_object(response)
7
+ end
8
+ end
9
+ end
10
+ 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,6 @@
1
+ module Tamber
2
+ class Behavior < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Retrieve
5
+ end
6
+ 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
@@ -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
@@ -0,0 +1,8 @@
1
+ module Tamber
2
+ class Item < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Update
5
+ extend Tamber::APIOperations::Retrieve
6
+ extend Tamber::APIOperations::Remove
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Tamber
2
+ class Property < APIResource
3
+ extend Tamber::APIOperations::Create
4
+ extend Tamber::APIOperations::Retrieve
5
+ end
6
+ end
@@ -0,0 +1,10 @@
1
+ module Tamber
2
+ class TamberError < StandardError
3
+ attr_reader :message
4
+
5
+ def initialize(message=nil)
6
+ @message = message
7
+ end
8
+
9
+ end
10
+ end
@@ -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