chimps 0.2.2 → 0.3.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.
- data/Gemfile +3 -9
- data/Gemfile.lock +14 -10
- data/README.rdoc +146 -240
- data/Rakefile +4 -33
- data/VERSION +1 -1
- data/lib/chimps/config.rb +35 -21
- data/lib/chimps/{utils/error.rb → error.rb} +1 -12
- data/lib/chimps/query_request.rb +67 -0
- data/lib/chimps/request.rb +82 -108
- data/lib/chimps/response.rb +62 -22
- data/lib/chimps/utils/typewriter.rb +90 -0
- data/lib/chimps/utils/uses_curl.rb +22 -12
- data/lib/chimps/utils.rb +50 -6
- data/lib/chimps/workflows/download.rb +72 -0
- data/lib/chimps/workflows/upload.rb +113 -0
- data/lib/chimps.rb +12 -12
- data/spec/chimps/query_request_spec.rb +44 -0
- data/spec/chimps/request_spec.rb +92 -0
- data/spec/chimps/response_spec.rb +0 -1
- data/spec/chimps/workflows/download_spec.rb +48 -0
- data/spec/spec_helper.rb +2 -19
- metadata +46 -91
- data/.document +0 -5
- data/.gitignore +0 -32
- data/CHANGELOG.textile +0 -4
- data/bin/chimps +0 -5
- data/lib/chimps/cli.rb +0 -28
- data/lib/chimps/commands/base.rb +0 -65
- data/lib/chimps/commands/batch.rb +0 -40
- data/lib/chimps/commands/create.rb +0 -31
- data/lib/chimps/commands/destroy.rb +0 -26
- data/lib/chimps/commands/download.rb +0 -46
- data/lib/chimps/commands/help.rb +0 -100
- data/lib/chimps/commands/list.rb +0 -41
- data/lib/chimps/commands/query.rb +0 -82
- data/lib/chimps/commands/search.rb +0 -48
- data/lib/chimps/commands/show.rb +0 -30
- data/lib/chimps/commands/test.rb +0 -39
- data/lib/chimps/commands/update.rb +0 -34
- data/lib/chimps/commands/upload.rb +0 -50
- data/lib/chimps/commands.rb +0 -125
- data/lib/chimps/typewriter.rb +0 -349
- data/lib/chimps/utils/log.rb +0 -48
- data/lib/chimps/utils/uses_model.rb +0 -34
- data/lib/chimps/utils/uses_yaml_data.rb +0 -93
- data/lib/chimps/workflows/batch.rb +0 -127
- data/lib/chimps/workflows/downloader.rb +0 -102
- data/lib/chimps/workflows/up.rb +0 -149
- data/lib/chimps/workflows/upload/bundler.rb +0 -249
- data/lib/chimps/workflows/upload/notifier.rb +0 -59
- data/lib/chimps/workflows/upload/token.rb +0 -77
- data/lib/chimps/workflows/upload/uploader.rb +0 -51
- data/lib/chimps/workflows.rb +0 -12
- data/spec/chimps/typewriter_spec.rb +0 -114
- data/spec/chimps/workflows/upload/bundler_spec.rb +0 -75
- data/spec/chimps/workflows/upload/token_spec.rb +0 -6
data/lib/chimps/config.rb
CHANGED
@@ -4,48 +4,62 @@ module Chimps
|
|
4
4
|
|
5
5
|
# Chimps configuration. Managed by
|
6
6
|
# Configliere[http://github.com/infochimps/configliere]
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Config
|
15
|
-
Config.define :verbose, :description => "Be verbose", :flag => :v, :type => :boolean
|
16
|
-
|
17
|
-
Config.define 'query.host', :description => "Host to send Query API requests to", :type => String, :default => "http://api.infochimps.com", :no_help => true, :env_var => "CHIMPS_QUERY_HOST"
|
18
|
-
Config.define 'query.key', :description => "API key for the Query API", :type => String, :no_help => true
|
7
|
+
#
|
8
|
+
# @return [Configliere::Param]
|
9
|
+
def self.config
|
10
|
+
@config ||= Configliere.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Backwards compatibility for version < 0.3.0.
|
14
|
+
Config = config
|
19
15
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
def self.define_config
|
17
|
+
config.define :site_config, :description => "Path to site-wide configuration file", :env_var => "CHIMPS_ETC", :default => "/etc/chimps/chimps.yaml", :type => String, :no_help => true
|
18
|
+
config.define :config, :description => "Path to user configuration file", :env_var => "CHIMPS_RC", :default => (ENV["HOME"] && File.expand_path("~/.chimps")), :flag => :c, :type => String
|
19
|
+
config.define :log, :description => "Path to log file", :flag => :l, :type => String
|
20
|
+
config.define :timestamp_format, :description => "Format for timestamps", :type => String, :no_help => true, :default => "%Y%m%d-%H%M%S"
|
21
|
+
config.define :plugin_dirs, :description => "List of directories from which to load plugins", :type => Array, :no_help => true, :default => ['/usr/share/chimps', '/usr/local/share/chimps']
|
22
|
+
config.define :skip_plugins, :description => "Don't load any plugins", :flag => :q, :type => :boolean
|
23
|
+
config.define :verbose, :description => "Be verbose", :flag => :v, :type => :boolean
|
24
|
+
|
25
|
+
config.define 'query.host', :description => "Host to send Query API requests to", :type => String, :default => "http://api.infochimps.com", :no_help => true, :env_var => "APEYEYE", :no_help => true
|
26
|
+
config.define 'query.key', :description => "API key for the Query API", :type => String, :no_help => true
|
27
|
+
|
28
|
+
config.define 'dataset.username', :description => "Your Infochimps username", :type => String
|
29
|
+
config.define 'dataset.host', :description => "Host to send Dataset API requests to", :type => String, :default => "http://www.infochimps.com", :env_var => "GEORGE", :no_help => true
|
30
|
+
config.define 'dataset.key', :description => "API key for the Dataset API", :type => String
|
31
|
+
config.define 'dataset.secret', :description => "API secret for the Dataset API", :type => String
|
32
|
+
end
|
33
|
+
define_config
|
24
34
|
|
25
35
|
# Is Chimps in verbose mode?
|
26
36
|
#
|
27
37
|
# @return [true, false, nil]
|
28
38
|
def self.verbose?
|
29
|
-
|
39
|
+
config[:verbose]
|
30
40
|
end
|
31
41
|
|
32
42
|
# The username Chimps will pass to Infochimps.
|
33
43
|
#
|
34
44
|
# @return [String]
|
35
45
|
def self.username
|
36
|
-
|
46
|
+
config[:dataset][:username] or raise AuthenticationError.new("No Dataset API username set in #{Chimps.config[:config]} or #{Chimps.config[:site_config]}")
|
37
47
|
end
|
38
48
|
|
49
|
+
# The current Chimps library version.
|
50
|
+
#
|
51
|
+
# @return [String]
|
39
52
|
def self.version
|
53
|
+
return @version if @version
|
40
54
|
version_path = File.join(File.dirname(__FILE__), '../../VERSION')
|
41
55
|
@version ||= File.exist?(version_path) && File.new(version_path).read
|
42
56
|
end
|
43
57
|
|
44
58
|
# Require all Ruby files in the directory
|
45
|
-
# Chimps
|
59
|
+
# Chimps.config[:plugin_dirs].
|
46
60
|
def self.load_plugins
|
47
|
-
return if Chimps
|
48
|
-
plugin_dirs = Chimps
|
61
|
+
return if Chimps.config[:skip_plugins]
|
62
|
+
plugin_dirs = Chimps.config[:plugin_dirs]
|
49
63
|
plugin_dirs.each do |dir|
|
50
64
|
Dir[File.expand_path(dir) + "/*.rb"].each { |plugin| require plugin }
|
51
65
|
end
|
@@ -3,9 +3,6 @@ module Chimps
|
|
3
3
|
# subclasses of Chimps::Error so they can be easily caught.
|
4
4
|
Error = Class.new(StandardError)
|
5
5
|
|
6
|
-
# Raised when the user provides bad input on the command line.
|
7
|
-
CLIError = Class.new(Error)
|
8
|
-
|
9
6
|
# Raised when the user hasn't specified any API credentials or the
|
10
7
|
# server rejects the user's API credentials.
|
11
8
|
#
|
@@ -18,9 +15,6 @@ module Chimps
|
|
18
15
|
# Roughly corresponds to HTTP status code 5xx.
|
19
16
|
ServerError = Class.new(Error)
|
20
17
|
|
21
|
-
# Raised when IMW fails to properly package files to upload.
|
22
|
-
PackagingError = Class.new(Error)
|
23
|
-
|
24
18
|
# Raised when there is an error in uploading to S3 or in notifiying
|
25
19
|
# Infochimps of the new package.
|
26
20
|
UploadError = Class.new(Error)
|
@@ -29,12 +23,7 @@ module Chimps
|
|
29
23
|
# methods.
|
30
24
|
NotImplementedError = Class.new(Error)
|
31
25
|
|
32
|
-
# Raised when the response from Infochimps isn't well-formed
|
33
|
-
# unexpected.
|
26
|
+
# Raised when the response from Infochimps isn't well-formed.
|
34
27
|
ParseError = Class.new(Error)
|
35
|
-
|
36
|
-
# Raised when Chimps encounters response data it doesn't know how to
|
37
|
-
# pretty print.
|
38
|
-
PrintingError = Class.new(Error)
|
39
28
|
end
|
40
29
|
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Chimps
|
2
|
+
|
3
|
+
# A class to encapsulate requests made against the Infochimps paid
|
4
|
+
# query API.
|
5
|
+
class QueryRequest < Request
|
6
|
+
|
7
|
+
# Is this request authentiable (has the Chimps user specified an
|
8
|
+
# API key and secret in their configuration file)?
|
9
|
+
#
|
10
|
+
# @return [true, false]
|
11
|
+
def authenticable?
|
12
|
+
!Chimps.config[:query][:key].blank?
|
13
|
+
end
|
14
|
+
|
15
|
+
def url_with_query_string
|
16
|
+
qs = (query_string && query_string.size > 0 ? query_string : nil)
|
17
|
+
case
|
18
|
+
when qs.nil?
|
19
|
+
base_url
|
20
|
+
when base_url.include?("?")
|
21
|
+
base_url + "&#{qs}"
|
22
|
+
else
|
23
|
+
base_url + "?#{qs}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# The host to send requests to.
|
28
|
+
#
|
29
|
+
# @return [String]
|
30
|
+
def host
|
31
|
+
@host ||= Chimps.config[:query][:host]
|
32
|
+
end
|
33
|
+
|
34
|
+
# All Query API requests must be signed.
|
35
|
+
#
|
36
|
+
# @return [true]
|
37
|
+
def authenticate?
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Authenticate this request by stuffing the <tt>:requested_at</tt>
|
42
|
+
# and <tt>:apikey</tt> properties into its <tt>:query_params</tt>
|
43
|
+
# hash.
|
44
|
+
#
|
45
|
+
# Will do nothing at all if Chimps::Request#authenticate? returns
|
46
|
+
# false.
|
47
|
+
def authenticate_if_necessary!
|
48
|
+
return unless authenticate? && should_encode?
|
49
|
+
raise Chimps::AuthenticationError.new("Query API key (Chimps.config[:query][:key]) from #{Chimps.config[:config]} or #{Chimps.config[:site_config]}") unless authenticable?
|
50
|
+
query_params[:requested_at] = Time.now.to_i.to_s
|
51
|
+
query_params[:apikey] = Chimps.config[:query][:key]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Append the signature to the unsigned query string.
|
55
|
+
#
|
56
|
+
# The signature made from the Chimps user's API secret and either
|
57
|
+
# the query string text (stripped of <tt>&</tt> and <tt>=</tt>)
|
58
|
+
# for GET and DELETE requests or the request body for POST and PUT
|
59
|
+
# requests.
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def signed_query_string
|
63
|
+
unsigned_query_string
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
data/lib/chimps/request.rb
CHANGED
@@ -12,25 +12,28 @@ module Chimps
|
|
12
12
|
# Default headers to pass with every request.
|
13
13
|
DEFAULT_HEADERS = { :content_type => 'application/json', :accept => 'application/json', :user_agent => "Chimps #{Chimps.version}" }
|
14
14
|
|
15
|
-
# Path of the URL to submit to. Must be a String.
|
15
|
+
# Path of the URL to submit to. Must be a String. Can start with
|
16
|
+
# an initial '/' or not -- no big deal ;)
|
16
17
|
attr_accessor :path
|
17
18
|
|
18
19
|
# Parameters to include in the query string of the URL to submit
|
19
|
-
# to.
|
20
|
+
# to. Can be a string or a Hash
|
20
21
|
attr_accessor :query_params
|
21
22
|
|
22
|
-
# Data to include in the body of the request.
|
23
|
-
|
23
|
+
# Data to include in the body of the request. Can be a Hash or a
|
24
|
+
# String.
|
25
|
+
attr_accessor :body
|
24
26
|
|
25
27
|
# Initialize a Request to the given +path+.
|
26
28
|
#
|
27
29
|
# Query parameters and data can be passed in as hashes named
|
28
|
-
# <tt>:params</tt> and <tt>:
|
30
|
+
# <tt>:params</tt> and <tt>:body</tt>, respectively.
|
29
31
|
#
|
30
32
|
# If <tt>:sign</tt> is passed in the +options+ then the URL of
|
31
33
|
# this request will be signed with the Chimps user's Infochimps
|
32
|
-
# API key and secret.
|
33
|
-
#
|
34
|
+
# API key and secret. Signing a request which doesn't need to be
|
35
|
+
# signed is just fine. Forgetting to sign a request which needs
|
36
|
+
# to be signed will result in a 401 error from Infochimps.
|
34
37
|
#
|
35
38
|
# If <tt>:sign_if_possible</tt> is passed in the +options+ then an
|
36
39
|
# attemp to sign the URL will be made though an error will _not_
|
@@ -38,21 +41,36 @@ module Chimps
|
|
38
41
|
#
|
39
42
|
# @param [String] path
|
40
43
|
# @param [Hash] options
|
41
|
-
# @option options [Hash]
|
42
|
-
# @option options [Hash]
|
44
|
+
# @option options [Hash] query Query parameters to include in the URL
|
45
|
+
# @option options [Hash] body Data to include in the request body
|
43
46
|
# @option options [true, false] sign Sign this request, raising an error on failure
|
44
47
|
# @option options [true, false] sign_if_possible Sign this request, no error on failure
|
48
|
+
# @option potions [true, false] raw If raw then encoding the query string and request body is up to you.
|
45
49
|
# @return [Chimps::Request]
|
46
50
|
def initialize path, options={}
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
self.path = path
|
52
|
+
self.query_params = options[:query_params] || options[:query] || options[:params] || {}
|
53
|
+
self.body = options[:body] || options[:data] || {}
|
54
|
+
|
55
|
+
@authentication_required = [:authenticate, :authenticated, :authenticate_if_possible, :sign, :signed, :sign_if_possible].any? { |key| options[key] }
|
51
56
|
@forgive_authentication_error = options[:sign_if_possible] || options[:authenticate_if_possible]
|
57
|
+
@raw = options[:raw]
|
52
58
|
authenticate_if_necessary!
|
53
|
-
super
|
59
|
+
super(url_with_query_string, {:headers => DEFAULT_HEADERS.merge(options[:headers] || {})})
|
54
60
|
end
|
55
61
|
|
62
|
+
# Return the URL for this request with the (signed, if necessary)
|
63
|
+
# query string appended.
|
64
|
+
#
|
65
|
+
# @return [String]
|
66
|
+
def url_with_query_string
|
67
|
+
if query_string && query_string.size > 0
|
68
|
+
base_url + "?#{query_string}"
|
69
|
+
else
|
70
|
+
base_url
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
56
74
|
# Should the request be authenticated?
|
57
75
|
#
|
58
76
|
# @return [true, false]
|
@@ -61,12 +79,30 @@ module Chimps
|
|
61
79
|
end
|
62
80
|
alias_method :sign?, :authenticate?
|
63
81
|
|
82
|
+
# Should the query string and request body be encoded?
|
83
|
+
#
|
84
|
+
# Control this by passing in the <tt>:raw</tt> keyword when
|
85
|
+
# initializing this Request.
|
86
|
+
#
|
87
|
+
# @return [true, false]
|
88
|
+
def should_encode?
|
89
|
+
!@raw
|
90
|
+
end
|
91
|
+
|
92
|
+
# Should this be considered a raw request in which neither the
|
93
|
+
# query string nor the body should be encoded or escaped?
|
94
|
+
#
|
95
|
+
# @return [true, false]
|
96
|
+
def raw?
|
97
|
+
!!@raw
|
98
|
+
end
|
99
|
+
|
64
100
|
# Is this request authentiable (has the Chimps user specified an
|
65
101
|
# API key and secret in their configuration file)?
|
66
102
|
#
|
67
103
|
# @return [true, false]
|
68
104
|
def authenticable?
|
69
|
-
!Chimps
|
105
|
+
!Chimps.config[:dataset][:key].blank? && !Chimps.config[:dataset][:secret].blank?
|
70
106
|
end
|
71
107
|
alias_method :signable?, :authenticable?
|
72
108
|
|
@@ -74,17 +110,15 @@ module Chimps
|
|
74
110
|
#
|
75
111
|
# @return [String]
|
76
112
|
def host
|
77
|
-
@host ||= Chimps
|
113
|
+
@host ||= Chimps.config[:dataset][:host]
|
78
114
|
end
|
79
115
|
|
80
|
-
# Return the URL for this request
|
81
|
-
# query string
|
116
|
+
# Return the base URL for this request, consisting of the host and
|
117
|
+
# path but *not* the query string.
|
82
118
|
#
|
83
119
|
# @return [String]
|
84
|
-
def
|
85
|
-
|
86
|
-
base_url += "?#{query_string}" unless query_string.blank?
|
87
|
-
base_url
|
120
|
+
def base_url
|
121
|
+
File.join(host, path)
|
88
122
|
end
|
89
123
|
|
90
124
|
# Return the query string for this request, signed if necessary.
|
@@ -93,7 +127,7 @@ module Chimps
|
|
93
127
|
def query_string
|
94
128
|
(authenticate? && authenticable?) ? signed_query_string : unsigned_query_string
|
95
129
|
end
|
96
|
-
|
130
|
+
|
97
131
|
# Perform a GET request to this URL, returning a parsed response.
|
98
132
|
#
|
99
133
|
# Any headers in +options+ will passed to
|
@@ -101,10 +135,10 @@ module Chimps
|
|
101
135
|
#
|
102
136
|
# @param [Hash] options
|
103
137
|
# @return [Chimps::Response]
|
104
|
-
def get options={}
|
138
|
+
def get options={}, &block
|
105
139
|
handle_exceptions do
|
106
140
|
Chimps.log.info("GET #{url}")
|
107
|
-
Response.new(super(
|
141
|
+
Response.new(super(options, &block))
|
108
142
|
end
|
109
143
|
end
|
110
144
|
|
@@ -115,10 +149,10 @@ module Chimps
|
|
115
149
|
#
|
116
150
|
# @param [Hash] options
|
117
151
|
# @return [Chimps::Response]
|
118
|
-
def post options={}
|
152
|
+
def post options={}, &block
|
119
153
|
handle_exceptions do
|
120
154
|
Chimps.log.info("POST #{url}")
|
121
|
-
Response.new(super(
|
155
|
+
Response.new(super(encoded_body, options, &block))
|
122
156
|
end
|
123
157
|
end
|
124
158
|
|
@@ -129,10 +163,10 @@ module Chimps
|
|
129
163
|
#
|
130
164
|
# @param [Hash] options
|
131
165
|
# @return [Chimps::Response]
|
132
|
-
def put options={}
|
166
|
+
def put options={}, &block
|
133
167
|
handle_exceptions do
|
134
168
|
Chimps.log.info("PUT #{url}")
|
135
|
-
Response.new(super(
|
169
|
+
Response.new(super(encoded_body, options, &block))
|
136
170
|
end
|
137
171
|
end
|
138
172
|
|
@@ -144,21 +178,20 @@ module Chimps
|
|
144
178
|
#
|
145
179
|
# @param [Hash] options
|
146
180
|
# @return [Chimps::Response]
|
147
|
-
def delete options={}
|
181
|
+
def delete options={}, &block
|
148
182
|
handle_exceptions do
|
149
183
|
Chimps.log.info("DELETE #{url}")
|
150
|
-
Response.new(super(
|
184
|
+
Response.new(super(options, &block))
|
151
185
|
end
|
152
186
|
end
|
153
187
|
|
154
|
-
protected
|
155
188
|
# Yield to +block+ but rescue any RestClient errors by wrapping
|
156
189
|
# them in a Chimps::Response.
|
157
190
|
def handle_exceptions &block
|
158
191
|
begin
|
159
192
|
yield
|
160
193
|
rescue RestClient::Exception => e
|
161
|
-
|
194
|
+
Response.new(e.response, :error => e.message)
|
162
195
|
end
|
163
196
|
end
|
164
197
|
|
@@ -169,17 +202,17 @@ module Chimps
|
|
169
202
|
# Will do nothing at all if Chimps::Request#authenticate? returns
|
170
203
|
# false.
|
171
204
|
def authenticate_if_necessary!
|
172
|
-
return unless authenticate?
|
173
|
-
raise Chimps::AuthenticationError.new("API key or secret missing from #{
|
205
|
+
return unless authenticate? && should_encode?
|
206
|
+
raise Chimps::AuthenticationError.new("Dataset API key (Chimps.config[:dataset][:key]) or secret (Chimps.config[:dataset][:secret]) missing from #{Chimps.config[:config]} or #{Chimps.config[:site_config]}") unless (authenticable? || @forgive_authentication_error)
|
174
207
|
query_params[:requested_at] = Time.now.to_i.to_s
|
175
|
-
query_params[:apikey]
|
208
|
+
query_params[:apikey] = Chimps.config[:dataset][:key]
|
176
209
|
end
|
177
210
|
|
178
211
|
# Return an unsigned query string for this request.
|
179
212
|
#
|
180
213
|
# @return [String]
|
181
214
|
def unsigned_query_string
|
182
|
-
RestClient::Payload.generate(query_params)
|
215
|
+
(should_encode? ? RestClient::Payload.generate(query_params) : query_params).to_s
|
183
216
|
end
|
184
217
|
|
185
218
|
# Return an unsigned query string for this request without the
|
@@ -193,14 +226,18 @@ module Chimps
|
|
193
226
|
@query_params_text ||= obj_to_stripped_string(query_params)
|
194
227
|
end
|
195
228
|
|
196
|
-
# Return
|
229
|
+
# Return this Requests's body as a suitably encoded string.
|
197
230
|
#
|
198
231
|
# This is the text that will be signed for POST and PUT requests.
|
199
232
|
#
|
200
233
|
# @return [String]
|
201
|
-
def
|
234
|
+
def encoded_body
|
235
|
+
@encoded_body ||= should_encode? ? encode(body) : body.to_s
|
236
|
+
end
|
237
|
+
|
238
|
+
def encode obj
|
202
239
|
require 'json'
|
203
|
-
|
240
|
+
JSON.generate((obj == true ? {} : obj), {:max_nesting => false})
|
204
241
|
end
|
205
242
|
|
206
243
|
# Sign +string+ by concatenting it with the secret and computing
|
@@ -209,9 +246,9 @@ module Chimps
|
|
209
246
|
# @param [String]
|
210
247
|
# @return [String]
|
211
248
|
def sign string
|
212
|
-
raise Chimps::AuthenticationError.new("No API secret stored in #{
|
249
|
+
raise Chimps::AuthenticationError.new("No API secret stored in #{Chimps.config[:config]} or #{Chimps.config[:site_config]}.") unless (authenticable? || @forgive_authentication_error)
|
213
250
|
require 'digest/md5'
|
214
|
-
Digest::MD5.hexdigest(string +
|
251
|
+
Digest::MD5.hexdigest(string + Chimps.config[:dataset][:secret])
|
215
252
|
end
|
216
253
|
|
217
254
|
# Append the signature to the unsigned query string.
|
@@ -223,7 +260,9 @@ module Chimps
|
|
223
260
|
#
|
224
261
|
# @return [String]
|
225
262
|
def signed_query_string
|
226
|
-
|
263
|
+
return unsigned_query_string unless should_encode?
|
264
|
+
text_to_sign = ((body == true || (! body.blank?)) ? encoded_body : unsigned_query_string_stripped)
|
265
|
+
signature = sign(text_to_sign)
|
227
266
|
"#{unsigned_query_string}&signature=#{signature}"
|
228
267
|
end
|
229
268
|
|
@@ -240,69 +279,4 @@ module Chimps
|
|
240
279
|
end
|
241
280
|
|
242
281
|
end
|
243
|
-
|
244
|
-
# A class to encapsulate requests made against the Infochimps paid
|
245
|
-
# query API.
|
246
|
-
class QueryRequest < Request
|
247
|
-
|
248
|
-
# Is this request authentiable (has the Chimps user specified an
|
249
|
-
# API key and secret in their configuration file)?
|
250
|
-
#
|
251
|
-
# @return [true, false]
|
252
|
-
def authenticable?
|
253
|
-
!Chimps::Config[:query][:key].blank?
|
254
|
-
end
|
255
|
-
|
256
|
-
# The host to send requests to.
|
257
|
-
#
|
258
|
-
# @return [String]
|
259
|
-
def host
|
260
|
-
@host ||= Chimps::Config[:query][:host]
|
261
|
-
end
|
262
|
-
|
263
|
-
# All Query API requests must be signed.
|
264
|
-
#
|
265
|
-
# @return [true]
|
266
|
-
def authenticate?
|
267
|
-
return true
|
268
|
-
end
|
269
|
-
|
270
|
-
# Authenticate this request by stuffing the <tt>:requested_at</tt>
|
271
|
-
# and <tt>:apikey</tt> properties into its <tt>:query_params</tt>
|
272
|
-
# hash.
|
273
|
-
#
|
274
|
-
# Will do nothing at all if Chimps::Request#authenticate? returns
|
275
|
-
# false.
|
276
|
-
def authenticate_if_necessary!
|
277
|
-
return unless authenticate?
|
278
|
-
raise Chimps::AuthenticationError.new("API key or secret missing from #{Config[:config]} or #{Config[:site_config]}") unless (authenticable? || @forgive_authentication_error)
|
279
|
-
query_params[:requested_at] = Time.now.to_i.to_s
|
280
|
-
query_params[:apikey] = Chimps::Config[:query][:key]
|
281
|
-
end
|
282
|
-
|
283
|
-
# Sign +string+ by concatenting it with the secret and computing
|
284
|
-
# the MD5 digest of the whole thing.
|
285
|
-
#
|
286
|
-
# @param [String]
|
287
|
-
# @return [String]
|
288
|
-
def sign string
|
289
|
-
raise Chimps::AuthenticationError.new("No API secret stored in #{Config[:config]} or #{Config[:site_config]}.") unless (authenticable? || @forgive_authentication_error)
|
290
|
-
require 'digest/md5'
|
291
|
-
Digest::MD5.hexdigest(string + Config[:query][:secret])
|
292
|
-
end
|
293
|
-
|
294
|
-
# Append the signature to the unsigned query string.
|
295
|
-
#
|
296
|
-
# The signature made from the Chimps user's API secret and either
|
297
|
-
# the query string text (stripped of <tt>&</tt> and <tt>=</tt>)
|
298
|
-
# for GET and DELETE requests or the request body for POST and PUT
|
299
|
-
# requests.
|
300
|
-
#
|
301
|
-
# @return [String]
|
302
|
-
def signed_query_string
|
303
|
-
unsigned_query_string
|
304
|
-
end
|
305
|
-
|
306
|
-
end
|
307
|
-
|
308
282
|
end
|
data/lib/chimps/response.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
|
1
4
|
module Chimps
|
2
5
|
|
3
6
|
# A class to wrap responses from the Infochimps API.
|
@@ -27,7 +30,6 @@ module Chimps
|
|
27
30
|
super()
|
28
31
|
@body = body
|
29
32
|
@error = options[:error]
|
30
|
-
parse!
|
31
33
|
end
|
32
34
|
|
33
35
|
# The HTTP status code of the response.
|
@@ -59,15 +61,27 @@ module Chimps
|
|
59
61
|
end
|
60
62
|
|
61
63
|
# Parse the response from Infochimps.
|
64
|
+
#
|
65
|
+
# @return [Chimps::Response]
|
62
66
|
def parse!
|
63
67
|
data = parse_response_body
|
64
68
|
case data
|
65
69
|
# hack...sometimes we get back an array instead of a
|
66
70
|
# hash...should change the API at Chimps end
|
67
71
|
when Hash then merge!(data)
|
68
|
-
when Array then self[:array] = data
|
72
|
+
when Array then self[:array] = data
|
69
73
|
when String then self[:string] = data
|
74
|
+
else nil
|
70
75
|
end
|
76
|
+
@parsed = true
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Parse the response from Infochimps -- will do nothing if the
|
81
|
+
# response has already been parsed.
|
82
|
+
def parse
|
83
|
+
return if @parsed
|
84
|
+
parse!
|
71
85
|
end
|
72
86
|
|
73
87
|
# Was this response a success?
|
@@ -91,7 +105,7 @@ module Chimps
|
|
91
105
|
#
|
92
106
|
# @return [Hash]
|
93
107
|
def data
|
94
|
-
|
108
|
+
{}.tap do |d|
|
95
109
|
each_pair do |key, value|
|
96
110
|
d[key] = value
|
97
111
|
end
|
@@ -100,16 +114,43 @@ module Chimps
|
|
100
114
|
|
101
115
|
# Print this response.
|
102
116
|
#
|
103
|
-
# Options are also passed to Chimps::Typewriter.new; consult for
|
104
|
-
# details.
|
105
|
-
#
|
106
117
|
# @param [Hash] options
|
107
|
-
# @option options
|
118
|
+
# @option options [true, false] pretty whether to pretty print the response
|
108
119
|
def print options={}
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
120
|
+
$stderr.puts(diagnostic_line) if error? || Chimps.verbose?
|
121
|
+
output = (options[:to] || $stdout)
|
122
|
+
if error?
|
123
|
+
parse!
|
124
|
+
output.puts self['errors'] if self['errors']
|
125
|
+
output.puts self['message'] if self['message']
|
126
|
+
else
|
127
|
+
case
|
128
|
+
when options[:yaml]
|
129
|
+
parse!
|
130
|
+
output.puts self.to_yaml
|
131
|
+
when options[:json] && options[:pretty]
|
132
|
+
parse!
|
133
|
+
if options[:pretty]
|
134
|
+
output.puts JSON.pretty_generate(self)
|
135
|
+
else
|
136
|
+
output.puts self.to_json
|
137
|
+
end
|
138
|
+
when headers[:content_type] =~ /json/i && options[:pretty]
|
139
|
+
parse!
|
140
|
+
output.puts JSON.pretty_generate(self)
|
141
|
+
when headers[:content_type] =~ /tab/i && options[:pretty]
|
142
|
+
Utils::Typewriter.new(self).print
|
143
|
+
else
|
144
|
+
output.puts body unless body.chomp.strip.size == 0
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def print_headers options={}
|
150
|
+
output = (options[:output] || $stdout)
|
151
|
+
self.body.raw_headers.each_pair do |name, value|
|
152
|
+
output.puts "#{name}: #{value}"
|
153
|
+
end
|
113
154
|
end
|
114
155
|
|
115
156
|
protected
|
@@ -126,9 +167,11 @@ module Chimps
|
|
126
167
|
|
127
168
|
# Raise a Chimps::ParseError, optionally including the response
|
128
169
|
# body in the error message if Chimps is verbose.
|
129
|
-
def parse_error!
|
130
|
-
|
131
|
-
|
170
|
+
def parse_error! error
|
171
|
+
if Chimps.verbose?
|
172
|
+
puts "\n\n#{body}"
|
173
|
+
end
|
174
|
+
nil
|
132
175
|
end
|
133
176
|
|
134
177
|
# Parse the body of this response using the YAML or JSON libraries
|
@@ -138,21 +181,18 @@ module Chimps
|
|
138
181
|
def parse_response_body
|
139
182
|
return {} if body.blank? || body == 'null'
|
140
183
|
if content_type == :yaml
|
141
|
-
require 'yaml'
|
142
184
|
begin
|
143
|
-
YAML.
|
185
|
+
YAML.load(StringIO.new(body))
|
144
186
|
rescue YAML::ParseError => e
|
145
|
-
parse_error!
|
146
|
-
rescue ArgumentError => e
|
147
|
-
|
148
|
-
parse_error!
|
187
|
+
parse_error! e
|
188
|
+
rescue ArgumentError => e
|
189
|
+
parse_error! e
|
149
190
|
end
|
150
191
|
else
|
151
|
-
require 'json'
|
152
192
|
begin
|
153
193
|
JSON.parse(body)
|
154
194
|
rescue JSON::ParserError => e
|
155
|
-
parse_error!
|
195
|
+
parse_error! e
|
156
196
|
end
|
157
197
|
end
|
158
198
|
end
|