empire 0.3.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.
Files changed (5) hide show
  1. checksums.yaml +7 -0
  2. data/lib/empire.rb +190 -0
  3. data/lib/exceptions.rb +22 -0
  4. data/lib/walkthrough.rb +101 -0
  5. metadata +105 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ad55abf95507256a7620a5acff613f78606c5a62
4
+ data.tar.gz: 3a632ad359d69984f71aeae5ee9232f97504fe38
5
+ SHA512:
6
+ metadata.gz: c9e9981bf7a3bebff5370dedb20216f6512ae0e9dab0caf8f066f56ee01a7fd72620929756e26cb8b5bd0c75fa5eca209d4b3a9a858835f78de9f137ad7c5f92
7
+ data.tar.gz: dac5a05b81926c2570b20de41f5588058bd59dcc8b7d561e68b3c22750e77024f397bbd7ca4ccb3ea3f659c2137948ba49c927c9cb9b0202abe7bb7d588d7a2f
@@ -0,0 +1,190 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'date'
4
+
5
+ require 'httpclient'
6
+ require 'irb-pager'
7
+
8
+ require_relative 'walkthrough.rb'
9
+ require_relative 'exceptions.rb'
10
+
11
+ class Empire
12
+ attr_reader :base_url
13
+
14
+ # app_key is your Empire application key, and is necessary for using the API
15
+ # opts can include any of:
16
+ # * :api_server => the server to connect to (default: api.empiredata.com)
17
+ # * :enduser => a string identifying the end user, required for any operations on views (default: nil)
18
+ # * :secrets_yaml => the path to a YAML file generated by https://login.empiredata.co (default: nil)
19
+ def initialize(app_key = nil, opts = {})
20
+ api_server = opts[:api_server] || 'api.empiredata.co'
21
+
22
+ @app_key = app_key
23
+ @enduser = opts[:enduser]
24
+ @session_key = nil
25
+
26
+ @http_client = HTTPClient.new
27
+
28
+ protocol = api_server.start_with?('localhost') ? 'http' : 'https'
29
+ @base_url = "#{protocol}://#{api_server}/empire/"
30
+
31
+ @service_secrets = nil
32
+ if opts[:secrets_yaml]
33
+ @service_secrets = YAML.load_file opts[:secrets_yaml]
34
+ end
35
+ end
36
+
37
+ # Connect to specific service
38
+ # service: service name
39
+ # secrets: hash with service secrets (optional if the Empire instance was initialized with a secrets_yaml)
40
+ def connect(service, secrets = nil)
41
+ path = "services/#{service}/connect"
42
+
43
+ unless secrets
44
+ unless @service_secrets
45
+ raise Empire::MissingSecretsError.new
46
+ end
47
+
48
+ secrets = {}
49
+ @service_secrets[service]['option'].each{|k, v|
50
+ secrets[k] = v['value']
51
+ }
52
+ end
53
+
54
+ request path, :post, {}, secrets
55
+ end
56
+
57
+ # Describe all services, all tables within a given service, or a given table
58
+ def describe(service = nil, table = nil)
59
+ path = 'services'
60
+ if service and table
61
+ path += "/#{service}/#{table}"
62
+ elsif service and !table
63
+ path += "/#{service}"
64
+ elsif !service and table
65
+ raise Empire::MissingServiceError.new("Service must be specified if table is specified")
66
+ end
67
+
68
+ request path
69
+ end
70
+
71
+ # Paginated printing of an SQL query
72
+ def print_query(sql)
73
+ IRB::Pager.pager {
74
+ query(sql) do |l|
75
+ puts l
76
+ end
77
+ }
78
+ end
79
+
80
+ # Issue a SQL query, yielding each row
81
+ def query(sql)
82
+ path = 'query'
83
+ io = request path, :post, {}, {query: sql}, stream: true
84
+ io.each do |l|
85
+ yield l
86
+ end
87
+ end
88
+
89
+ # Insert a new row into this service table. The row should be a hash of {column: value}
90
+ def insert(service, table, row)
91
+ path = "services/#{service}/#{table}"
92
+ request path, :post, {}, row
93
+ end
94
+
95
+ # Materialize a SQL query as a view. This creates or updates a view.
96
+ def materialize_view(name, sql)
97
+ unless @enduser
98
+ raise Empire::MissingEnduserError.new
99
+ end
100
+ path = "view/#{name}"
101
+ data = {'query' => sql}
102
+ request path, :put, {}, data
103
+ end
104
+
105
+ # Delete a materialized view of SQL query
106
+ def drop_view(name)
107
+ unless @enduser
108
+ raise Empire::MissingEnduserError.new
109
+ end
110
+ path = "view/#{name}"
111
+ request path, :delete
112
+ end
113
+
114
+ # Boolean check if a materialized view is ready for querying.
115
+ # @note The user is expected to check view_ready? before querying a view with query()
116
+ def view_ready?(name)
117
+ status = view_status(name)
118
+
119
+ case status['viewStatus']
120
+ when 'ready' then true
121
+ when 'pending' then false
122
+ else
123
+ raise APIError.new("Unknown view status: #{response['viewStatus']}")
124
+ end
125
+ end
126
+
127
+ # Datetime that this view was materialized at.
128
+ # nil if the materialization is currently pending.
129
+ def view_materialized_at(name)
130
+ status = view_status(name)
131
+ Date.parse(status['materializedAt']) rescue nil
132
+ end
133
+
134
+ # emulate default Object#inspect method but only display the object_id, not the properties
135
+ # to make things cleaner and more similar to Python client
136
+ def inspect
137
+ "#<Empire:#{(object_id << 1).to_s(16)}>"
138
+ end
139
+
140
+ private
141
+
142
+ def view_status(name)
143
+ unless @enduser
144
+ raise Empire::MissingEnduserError.new
145
+ end
146
+ path = "view/#{name}/status"
147
+ request path, :get
148
+ end
149
+
150
+ def request(path, method = :get, headers = {}, data = {}, opts = {})
151
+ create_session unless @sessionkey
152
+ headers_with_session = headers.merge({'Authorization' => "Empire sessionkey=\"#{@sessionkey}\""})
153
+ do_request path, method, headers_with_session, data, opts
154
+ end
155
+
156
+ def create_session
157
+ headers = {'Authorization' => "Empire appkey=\"#{@app_key}\""}
158
+ session_url = "session/create"
159
+ if @enduser
160
+ session_url = session_url + "?enduser=#{@enduser}"
161
+ end
162
+ data = do_request session_url, :post, headers
163
+ @sessionkey = data['sessionkey']
164
+ end
165
+
166
+ def do_request(path, method = :get, headers = {}, data = {}, opts = {})
167
+ url = @base_url + path
168
+ headers.merge!({'Content-Type' => 'application/json', 'Accept' => '*/*'})
169
+ if opts[:stream]
170
+ # return an IO object representing the streamed content
171
+ conn = @http_client.request_async method, url, nil, data.to_json, headers
172
+ conn.pop.content
173
+ else
174
+ # return the response body, parsed as JSON
175
+
176
+ response = @http_client.request method, url, body: data.to_json, header: headers
177
+
178
+ begin
179
+ json = JSON.parse(response.body)
180
+ rescue => e
181
+ raise Empire::APIError.new("#{e} #{response.body}")
182
+ end
183
+
184
+ if json["status"] != "OK"
185
+ raise Empire::APIError.new(json["error"])
186
+ end
187
+ json
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,22 @@
1
+
2
+ class Empire
3
+ class APIError < RuntimeError; end
4
+
5
+ class MissingSecretsError < RuntimeError
6
+ def initialize(msg = "Secrets must be provided when connecting to a service, or a secrets YAML file must be given when constructing this instance")
7
+ super
8
+ end
9
+ end
10
+
11
+ class MissingEnduserError < RuntimeError
12
+ def initialize(msg = "Cannot use a materialized view within a session initiated without an enduser")
13
+ super
14
+ end
15
+ end
16
+
17
+ class MissingServiceError < RuntimeError
18
+ def initialize(msg = "Service must be specified")
19
+ super
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,101 @@
1
+
2
+ class Empire
3
+
4
+ # Run automatic test of all services from YAML
5
+ def walkthrough
6
+ @last_service = nil
7
+ @last_table = nil
8
+
9
+ unless @service_secrets
10
+ puts "Please connect some services in https://login.empiredata.co, and download the new yaml file"
11
+ return
12
+ end
13
+
14
+ @service_secrets.each do |secret|
15
+ service = secret[0]
16
+ walkthrough_service(service)
17
+ end
18
+
19
+ walkthrough_materialized_view(@last_service, @last_table)
20
+
21
+ nil
22
+ end
23
+
24
+ private
25
+
26
+ def walkthrough_service(service)
27
+ puts "empire.connect '#{service}'"
28
+ begin
29
+ connect service
30
+ rescue
31
+ puts "Problem connecting to #{service}"
32
+ return
33
+ end
34
+
35
+ tables = describe service
36
+
37
+ unless tables and tables['service'] and tables['service']['tables']
38
+ puts "Can't find tables belonging to #{service}"
39
+ return
40
+ end
41
+
42
+ tables['service']['tables'].each do |table_data|
43
+ table = table_data['table']
44
+ walkthrough_table(service, table)
45
+ end
46
+
47
+ @last_service = service
48
+ end
49
+
50
+ def walkthrough_table(service, table)
51
+ if service == "mailchimp"
52
+ # These mailchimp tables can only be queried when filtering by a particular list.
53
+ if ["list_member", "campaign", "campaign_sent_to", "campaign_opened"].include? table
54
+ return
55
+ end
56
+ end
57
+
58
+ begin
59
+ sql = "SELECT * FROM #{service}.#{table} LIMIT 5"
60
+ puts "empire.query '#{sql}'"
61
+ query(sql) do |row|
62
+ print_row(row)
63
+ end
64
+ rescue Exception => e
65
+ puts "Problem with #{service}.#{table}"
66
+ end
67
+
68
+ @last_table = table
69
+ end
70
+
71
+ def walkthrough_materialized_view(service, table)
72
+ unless @enduser
73
+ puts "Please specify an enduser parameter when instantiating the client, so that you can try materialized views"
74
+ return
75
+ end
76
+
77
+ puts "empire.materialize_view('view_name', 'SELECT * FROM #{service}.#{table} LIMIT 5')"
78
+ materialize_view('view_name', "SELECT * FROM #{service}.#{table} LIMIT 5")
79
+
80
+ puts "until empire.view_ready? 'view_name'\n sleep 0.01\nend"
81
+ until view_ready? 'view_name'
82
+ sleep 0.01
83
+ end
84
+
85
+ puts "empire.query 'SELECT * FROM view_name'"
86
+ query('SELECT * FROM view_name') do |row|
87
+ print_row(row)
88
+ end
89
+
90
+ puts "empire.drop_view 'view_name'"
91
+ drop_view 'view_name'
92
+ end
93
+
94
+ def print_row(row, max_length = 70)
95
+ fragment = row.slice(0, max_length)
96
+ if fragment.length == max_length
97
+ fragment = fragment + "..."
98
+ end
99
+ puts " #{fragment}"
100
+ end
101
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: empire
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.3
5
+ platform: ruby
6
+ authors:
7
+ - UPSHOT Data, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httpclient
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: irb-pager
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.18'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '1.18'
69
+ description: Ruby client for the Empire API. Empire is an API for accessing enterprise
70
+ SaaS services such as Salesforce, Zendesk, Google Apps, etc. Empire provides a uniform,
71
+ database-like interface to every service that it supports. Empire makes it easy
72
+ to integrate data from multiple enterprise services into your own enterprise app.
73
+ email: hello@empiredata.co
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - lib/empire.rb
79
+ - lib/exceptions.rb
80
+ - lib/walkthrough.rb
81
+ homepage: http://empiredata.co
82
+ licenses:
83
+ - Apache License, Version 2.0
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: 1.9.3
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.2.2
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Ruby client for the Empire API
105
+ test_files: []