canvas-api 0.5 → 0.6

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.
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: