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.
- checksums.yaml +7 -0
- data/lib/empire.rb +190 -0
- data/lib/exceptions.rb +22 -0
- data/lib/walkthrough.rb +101 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -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
|
data/lib/empire.rb
ADDED
@@ -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
|
data/lib/exceptions.rb
ADDED
@@ -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
|
data/lib/walkthrough.rb
ADDED
@@ -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: []
|