canvas-api 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +83 -1
  2. data/lib/canvas-api.rb +103 -13
  3. metadata +1 -1
data/README.md CHANGED
@@ -46,6 +46,29 @@ canvas.get("/api/v1/users/self/profile")
46
46
  # => {id: 90210, name: "Annie Wilson", ... }
47
47
  ```
48
48
 
49
+ For POST and PUT requests the second parameter is the form parameters to append, either as a hash or
50
+ an array of arrays:
51
+
52
+ ```ruby
53
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
54
+ canvas.put("/api/v1/users/self", {'user[name]' => 'Dixon Wilson', 'user[short_name]' => 'Dixon'})
55
+ # => {id: 90210, name: "Dixon Wilson", ... }
56
+ canvas.put("/api/v1/users/self", {'user' => {'name' => 'Dixon Wilson', 'short_name' => 'Dixon'}}) # this is synonymous with the previous call
57
+ # => {id: 90210, name: "Dixon Wilson", ... }
58
+ canvas.put("/api/v1/users/self", [['user[name]', 'Dixon Wilson'],['user[short_name]', 'Dixon']]) # this is synonymous with the previous call
59
+ # => {id: 90210, name: "Dixon Wilson", ... }
60
+ ```
61
+
62
+ On GET requests you can either append query parameters to the actual path or as a hashed second argument:
63
+
64
+ ```ruby
65
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
66
+ canvas.get("/api/v1/users/self/enrollments?type[]=TeacherEnrollment&type[]=TaEnrollment")
67
+ # => [{id: 1234, course_id: 5678, ... }, {id: 2345, course_id: 6789, ...}]
68
+ canvas.get("/api/v1/users/self/enrollments", {'type' => ['TeacherEnrollment', 'TaEnrollment']}) # this is synonymous with the previous call
69
+ # => [{id: 1234, course_id: 5678, ... }, {id: 2345, course_id: 6789, ...}]
70
+ ```
71
+
49
72
  ### Pagination
50
73
 
51
74
  API endpoints that return lists are often paginated, meaning they will only return the first X results
@@ -75,7 +98,66 @@ There are also some helper methods that can make some of the other tricky parts
75
98
 
76
99
  #### File Uploads
77
100
 
78
- TODO...
101
+ Uploading files ia typically a multi-step process. There are three different ways to upload
102
+ files.
103
+
104
+ Upload a file from the local file system:
105
+
106
+
107
+ ```ruby
108
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
109
+ canvas.upload_file_from_local("/api/v1/users/self/files", File.open("/path/to/file.jpg"), :content_type => "image/jpeg")
110
+ # => {id: 1, display_name: "file.jpg", ... }
111
+ ```
112
+
113
+ Upload a file synchronously from a remote URL:
114
+
115
+ ```ruby
116
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
117
+ canvas.upload_file_from_url("/api/v1/users/self/files", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
118
+ # => {id: 1, display_name: "image.jpg", ... }
119
+ ```
120
+
121
+ Upload a file asynchronouysly from a remote URL:
122
+
123
+ ```ruby
124
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
125
+ status_url = canvas.upload_file_from_url("/api/v1/users/self/files", :asynch => true, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
126
+ # => "/api/v1/file_status/url"
127
+ canvas.get(status_url)
128
+ # => {upload_status: "pending"}
129
+ canvas.get(status_url)
130
+ # => {upload_status: "ready", attachment: {id: 1, display_name: "image.jpg", ... } }
131
+ ```
132
+
133
+ ```ruby
134
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
135
+ status_url = canvas.upload_file_from_url("/api/v1/users/self/files", :asynch => true, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
136
+ # => "/api/v1/file_status/url"
137
+ canvas.get(status_url)
138
+ # => {upload_status: "errored", message: "Invalid response code, expected 200 got 404"}
139
+ ```
140
+
141
+ For any of these upload types you can optionally provide additional configuration parameters if
142
+ the upload endpoint is to an area of Canvas that supports folders (user files, course files, etc.)
143
+
144
+ ```ruby
145
+ canvas = Canvas::API.new(:host => "https://canvas.example.com", :token => "qwert")
146
+ #
147
+ # upload the file to a known folder with id 1234
148
+ canvas.upload_file_from_url("/api/v1/users/self/files", :parent_folder_id => 1234, :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
149
+ # => {id: 1, display_name: "image.jpg", ... }
150
+ #
151
+ # upload the file to a folder with the path "/friends"
152
+ canvas.upload_file_from_url("/api/v1/users/self/files", :parent_folder_path => "/friends", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
153
+ # => {id: 1, display_name: "image.jpg", ... }
154
+ #
155
+ # rename this file instead of overwriting a file with the same name (overwrite is the default)
156
+ canvas.upload_file_from_url("/api/v1/users/self/files", :on_duplicate => "rename", :name => "image.jpg", :size => 12345, :url => "http://www.example.com/image.jpg")
157
+ # => {id: 1, display_name: "image.jpg", ... }
158
+ ```
159
+
160
+
79
161
 
80
162
  #### SIS ID Encoding
81
163
 
data/lib/canvas-api.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  require 'uri'
2
2
  require 'cgi'
3
3
  require 'net/http'
4
+ require 'net/http/post/multipart'
4
5
  require 'json'
5
6
 
7
+
6
8
  module Canvas
7
9
  class API
8
10
  def initialize(args={})
@@ -79,12 +81,22 @@ module Canvas
79
81
  raise "invalid endpoint" unless (URI.parse(endpoint) rescue nil)
80
82
  end
81
83
 
82
- def generate_uri(endpoint)
84
+ def generate_uri(endpoint, params=nil)
83
85
  validate_call(endpoint)
84
86
  unless @token == "ignore"
85
87
  endpoint += (endpoint.match(/\?/) ? "&" : "?") + "access_token=" + @token
86
88
  endpoint += "&as_user_id=" + @as_user_id.to_s if @as_user_id
87
89
  end
90
+ (params || {}).each do |key, value|
91
+ if value.is_a?(Array)
92
+ key = key + "[]" unless key.match(/\[\]/)
93
+ value.each do |val|
94
+ endpoint += (endpoint.match(/\?/) ? "&" : "?") + "#{CGI.escape(key.to_s)}=#{CGI.escape(val.to_s)}"
95
+ end
96
+ else
97
+ endpoint += (endpoint.match(/\?/) ? "&" : "?") + "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
98
+ end
99
+ end
88
100
  @uri = URI.parse(@host + endpoint)
89
101
  @http = Net::HTTP.new(@uri.host, @uri.port)
90
102
  @http.use_ssl = @uri.scheme == 'https'
@@ -121,8 +133,8 @@ module Canvas
121
133
  Net::HTTP::Get.new(@uri.request_uri)
122
134
  end
123
135
 
124
- def get(endpoint)
125
- generate_uri(endpoint)
136
+ def get(endpoint, params=nil)
137
+ generate_uri(endpoint, params)
126
138
  request = get_request(endpoint)
127
139
  retrieve_response(request)
128
140
  end
@@ -134,30 +146,108 @@ module Canvas
134
146
  end
135
147
 
136
148
  def put(endpoint, params={})
137
- generate_uri(endpoint)
149
+ generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
138
150
  request = Net::HTTP::Put.new(@uri.request_uri)
139
- request.set_form_data(params)
151
+ request.set_form_data(clean_params(params))
140
152
  retrieve_response(request)
141
153
  end
142
154
 
143
155
  def post(endpoint, params={})
144
- generate_uri(endpoint)
156
+ generate_uri(endpoint, params['query_parameters'] || params[:query_parameters])
145
157
  request = Net::HTTP::Post.new(@uri.request_uri)
146
- request.set_form_data(params)
158
+ request.set_form_data(clean_params(params))
147
159
  retrieve_response(request)
148
160
  end
161
+
162
+ def clean_params(params, prefix=nil)
163
+ params ||= {}
164
+ return params if params.is_a?(Array)
165
+ return nil unless params.is_a?(Hash)
166
+ params.delete(:query_parameters)
167
+ res = []
168
+ params.each do |key, val|
169
+ if val.is_a?(Array)
170
+ raise "No support for nested array parameters currently"
171
+ elsif val.is_a?(Hash)
172
+ res += clean_params(val, prefix ? (prefix + "[" + key.to_s + "]") : key.to_s)
173
+ else
174
+ if prefix
175
+ res << [prefix + "[" + key.to_s + "]", val.to_s]
176
+ else
177
+ res << [key.to_s, val.to_s]
178
+ end
179
+ end
180
+ end
181
+ res
182
+ end
149
183
 
150
- def upload_file_from_local
151
- # TODO
184
+ def upload_file_from_local(endpoint, file, opts={})
185
+ raise "Missing File object" unless file.is_a?(File)
186
+ params = {
187
+ :size => file.size,
188
+ :name => opts[:name] || opts['name'] || File.basename(file.path),
189
+ :content_type => opts[:content_type] || opts['content_type'] || "application/octet-stream",
190
+ :parent_folder_id => opts[:parent_folder_id] || opts['parent_folder_id'],
191
+ :parent_folder_path => opts[:parent_folder_path] || opts['parent_folder_path'],
192
+ :on_duplicate => opts[:on_duplicate] || opts['on_duplicate']
193
+ }
194
+ res = post(endpoint, params)
195
+ if !res['upload_url']
196
+ raise ApiError.new("Unexpected error: #{res['message'] || 'no upload URL returned'}")
197
+ end
198
+ status_url = multipart_upload(res['upload_url'], res['upload_params'], params, file)
199
+ status_path = "/" + status_url.split(/\//, 4)[-1]
200
+ res = get(status_path)
201
+ res
202
+ end
203
+
204
+ def multipart_upload(url, upload_params, params, file)
205
+ uri = URI.parse(url)
206
+ @multipart_args = upload_params.merge({})
207
+ @multipart_args['file'] = UploadIO.new(file, params[:content_type], params[:name])
208
+ http = Net::HTTP.new(uri.host, uri.port)
209
+ http.use_ssl = uri.scheme == 'https'
210
+ req = Net::HTTP::Post::Multipart.new(uri.path, @multipart_args)
211
+ res = http.start do |stream_http|
212
+ stream_http.request(req)
213
+ end
214
+ raise ApiError.new("Unexpected error: #{res.body}") if !res['Location']
215
+ res['Location']
152
216
  end
153
217
 
154
- def upload_file_from_url
155
- # TODO
218
+ def upload_file_from_url(endpoint, opts)
219
+ asynch = opts.delete('asynch') || opts.delete(:asynch)
220
+ ['url', 'name', 'size'].each do |k|
221
+ raise "Missing value: #{k}" unless opts[k.to_s] || opts[k.to_sym]
222
+ end
223
+
224
+ res = post(endpoint, opts)
225
+ status_url = res['status_url']
226
+ if !status_url
227
+ raise ApiError.new("Unexpected error: #{res['message'] || 'no status URL returned'}")
228
+ end
229
+ status_path = "/" + status_url.split(/\//, 4)[-1]
230
+ if asynch
231
+ return status_path
232
+ else
233
+ attachment = nil
234
+ while !attachment
235
+ res = get(status_path)
236
+ if res['upload_status'] == 'errored'
237
+ raise ApiError.new(res['message'])
238
+ elsif !res['upload_status']
239
+ raise ApiError.new("Unexpected response")
240
+ end
241
+ status_path = res['status_path'] if res['status_path']
242
+ attachment = res['attachment']
243
+ sleep (defined?(SLEEP_TIME) ? SLEEP_TIME : 5) unless attachment
244
+ end
245
+ attachment
246
+ end
156
247
  end
157
248
  end
158
249
 
159
- class ApiError < StandardError
160
- end
250
+ class ApiError < StandardError; end
161
251
 
162
252
  class ResultSet < Array
163
253
  def initialize(api, arr)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: canvas-api
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: '0.6'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: