chimps 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|