socrata 0.1.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/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
+