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.
@@ -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