socrata 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/socrata.rb ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2010 Socrata.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'logger'
18
+ require 'rubygems'
19
+ require 'httparty'
20
+ require 'curb'
21
+ require 'json'
22
+ require 'date'
23
+ require 'pp'
24
+
25
+ # Dynamically load dependencies
26
+ dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
27
+ require File.join(dir, 'socrata/data')
28
+
29
+ class Socrata
30
+ include HTTParty
31
+
32
+ attr_reader :error, :config, :batching
33
+
34
+ default_options[:headers] = {'Content-type' => 'application/json'}
35
+ format :json
36
+
37
+ DEFAULT_CONFIG = {
38
+ :base_uri => "http://www.socrata.com/api"
39
+ }
40
+
41
+ def initialize(params = {})
42
+ @config = DEFAULT_CONFIG.merge(symbolize_keys(params))
43
+ @logger = @config[:logger] || Logger.new(STDOUT)
44
+ @batching = false
45
+ @batch_queue = []
46
+
47
+ if @config[:username]
48
+ self.class.basic_auth(@config[:username],
49
+ @config[:password])
50
+ else
51
+ self.class.default_options[:basic_auth] = nil
52
+ end
53
+
54
+ self.class.base_uri @config[:base_uri]
55
+
56
+ # Keep around a self reference because we need it
57
+ @party = self.class
58
+ end
59
+
60
+ def user(uid_or_login)
61
+ return User.new(get_request("/users/#{uid_or_login}.json"), @party)
62
+ end
63
+
64
+ def view(uid)
65
+ return View.new(get_request("/views/#{uid}.json"), @party)
66
+ end
67
+
68
+ # Wrap a proc for a batch request
69
+ def batch_request()
70
+ @batching = true
71
+ @batch_queue = []
72
+ yield
73
+ @batching = false
74
+ flush_batch_queue();
75
+ end
76
+
77
+ protected
78
+ def get_request(path, options = {})
79
+ if @batching
80
+ # Batch up the request
81
+ @batch_queue << {:url => path, :requestType => "GET"}
82
+ else
83
+ # Actually execute the request
84
+ response = @party.get(path, options);
85
+ check_error! response
86
+ return response
87
+ end
88
+ end
89
+
90
+ def post_request(path, options = {})
91
+ if @batching
92
+ # Batch up the request
93
+ @batch_queue << {:url => path, :body => options[:body], :requestType => "POST"}
94
+ else
95
+ # Actually execute the request
96
+ response = @party.post(path, options)
97
+ check_error! response
98
+ return response
99
+ end
100
+ end
101
+
102
+ def put_request(path, options = {})
103
+ if @batching
104
+ # Batch up the request
105
+ @batch_queue << {:url => path, :body => options[:body], :requestType => "PUT"}
106
+ else
107
+ # Actually execute the request
108
+ response = @party.put(path, options)
109
+ check_error! response
110
+ return response
111
+ end
112
+ end
113
+
114
+ def delete_request(path, options = {})
115
+ if @batching
116
+ # Batch up the request
117
+ @batch_queue << {:url => path, :body => options[:body], :requestType => "DELETE"}
118
+ else
119
+ # Actually execute the request
120
+ response = @party.delete(path, options)
121
+ check_error! response
122
+ return response
123
+ end
124
+ end
125
+
126
+ # Flush a queued batch of requests
127
+ def flush_batch_queue
128
+ if !@batch_queue.empty?
129
+ result = @party.post('/batches', :body => {:requests => @batch_queue}.to_json)
130
+ results_parsed = JSON.parse(result.body)
131
+ if results_parsed.is_a? Array
132
+ results_parsed.each_with_index do |result, i|
133
+ if result['error']
134
+ raise "Received error in batch response for operation " +
135
+ @batch_queue[i][:requestType] + " " + @batch_queue[i][:url] + ". Error: " +
136
+ result['errorCode'] + " - " + result['errorMessage']
137
+ end
138
+ end
139
+ else
140
+ raise "Expected array response from a /batches request, and didn't get one."
141
+ end
142
+ @batch_queue.clear
143
+ end
144
+ return results_parsed
145
+ end
146
+
147
+ # Reads response and checks for error code
148
+ def check_error!(response)
149
+ if !response.nil? && response['error']
150
+ raise "Got error from server: Code: #{response['code']}, Message: #{response['message']}"
151
+ end
152
+ end
153
+
154
+ # Sends a multipart-formdata encoded POST request
155
+ def multipart_post(url, contents, field = 'file', mimetype = "application/json")
156
+ c = Curl::Easy.new(@party.default_options[:base_uri] + url.to_s)
157
+ c.multipart_form_post = true
158
+
159
+ c.userpwd = @party.default_options[:basic_auth][:username] +
160
+ ":" + @party.default_options[:basic_auth][:password]
161
+ c.http_post(Curl::PostField.content(field, contents, mimetype))
162
+
163
+ return JSON.parse(c.body_str)
164
+ end
165
+
166
+ def multipart_post_file(url, filename, field = 'file', remote_filename = filename)
167
+ c = Curl::Easy.new(@party.default_options[:base_uri] + url.to_s)
168
+ c.multipart_form_post = true
169
+
170
+ c.userpwd = @party.default_options[:basic_auth][:username] +
171
+ ":" + @party.default_options[:basic_auth][:password]
172
+ c.http_post(Curl::PostField.file(field, filename, remote_filename))
173
+
174
+ return JSON.parse(c.body_str)
175
+ end
176
+
177
+ def symbolize_keys(obj)
178
+ case obj
179
+ when Array
180
+ obj.inject([]){|res, val|
181
+ res << case val
182
+ when Hash, Array
183
+ symbolize_keys(val)
184
+ else
185
+ val
186
+ end
187
+ res
188
+ }
189
+ when Hash
190
+ obj.inject({}){|res, (key, val)|
191
+ nkey = case key
192
+ when String
193
+ key.to_sym
194
+ else
195
+ key
196
+ end
197
+ nval = case val
198
+ when Hash, Array
199
+ symbolize_keys(val)
200
+ else
201
+ val
202
+ end
203
+ res[nkey] = nval
204
+ res
205
+ }
206
+ else
207
+ obj
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,80 @@
1
+ # Copyright (c) 2010 Socrata.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class Socrata
16
+ class Data < Socrata
17
+ attr_reader :data
18
+
19
+ def initialize(data, party)
20
+ @data = data
21
+ @party = party
22
+ end
23
+
24
+ def method_missing(method)
25
+ key = @data[method.to_s]
26
+ end
27
+
28
+ def id
29
+ @data["id"]
30
+ end
31
+ end
32
+
33
+ class User < Data; end
34
+
35
+ class View < Data
36
+ # Get the rows for this View as a raw array of arrays
37
+ def raw_rows(params = {})
38
+ return get_request("/views/#{self.id}/rows.json", :query => params)
39
+ end
40
+
41
+ # Get the rows as an array of hashes by their column ID or something
42
+ # overriden with the second param
43
+ def rows(params = {}, by = "name")
44
+ # TODO: I really hate this method, I'd like to redo it.
45
+
46
+ # Get the raw rows
47
+ response = get_request("/views/#{self.id}/rows.json", :query => params)
48
+
49
+ # Create a mapping from array index to ID
50
+ columns = response["meta"]["view"]["columns"]
51
+ mapping = Hash.new
52
+ columns.each_index do |idx|
53
+ mapping[idx] = columns[idx][by]
54
+ end
55
+
56
+ # Loop over the rows, replacing each with a proper hash
57
+ rows = response["data"]
58
+ new_rows = Array.new
59
+ rows.each do |row_arr|
60
+ row_hash = Hash.new
61
+
62
+ # Loop over the row, building our hash
63
+ row_arr.each_index do |idx|
64
+ row_hash[mapping[idx]] = row_arr[idx]
65
+ end
66
+
67
+ # Add a few meta columns
68
+ row_hash["_id"] = row_arr[0]
69
+
70
+ new_rows.push row_hash
71
+ end
72
+
73
+ return new_rows
74
+ end
75
+
76
+ def get_row(row_id)
77
+ return get_request("/views/#{self.id}/rows/#{row_id}.json")
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2010 Socrata.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ #module Socrata
16
+ #class User < SocrataAPI
17
+ #def initialize(params = {})
18
+ #@user = params[:username]
19
+ #raise ArgumentError, 'No username specified' if @user.nil?
20
+ #super
21
+ #end
22
+
23
+ #def datasets
24
+ #sets = []
25
+ #self.class.get("/users/#{@user}/views.json").each do |set|
26
+ #dataset = Dataset.new(:config => @config)
27
+ #dataset.attach(set['id'])
28
+ #sets << dataset
29
+ #end
30
+ #sets
31
+ #end
32
+
33
+ #def profile
34
+ #self.class.get("/users/#{@user}.json")
35
+ #end
36
+ #end
37
+ #end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: socrata
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Aiden Scandella
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-08-21 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: curb
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 5
30
+ - 4
31
+ - 0
32
+ version: 0.5.4.0
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: json
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ segments:
43
+ - 1
44
+ - 1
45
+ - 6
46
+ version: 1.1.6
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: httparty
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ segments:
57
+ - 0
58
+ - 4
59
+ - 3
60
+ version: 0.4.3
61
+ type: :runtime
62
+ version_requirements: *id003
63
+ description: Access the Socrata data platform via Ruby
64
+ email: aiden.scandella@socrata.com
65
+ executables: []
66
+
67
+ extensions: []
68
+
69
+ extra_rdoc_files: []
70
+
71
+ files:
72
+ - lib/socrata.rb
73
+ - lib/socrata/data.rb
74
+ - lib/socrata/user.rb
75
+ has_rdoc: true
76
+ homepage: http://api.socrata.com/
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.6
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Access the Socrata data platform via Ruby
105
+ test_files: []
106
+