xqueue_ruby 0.0.1

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.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ OGViMTc0NDRmNTM2MGUyMWFhMWIxZWYyMTA1NzdhOTg1ZDJjMjVhNQ==
5
+ data.tar.gz: !binary |-
6
+ NjhmYmUxYzE3Y2MyMjI4MDY1OTE0ZmVhMWIzNjlhOTM4NWViY2E1Zg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YzQ2ZWM2MzdkMjhmMDZjYmVmNzgzZDkzODQ1YzNkMWI3MTZlM2M5ODc2NjVh
10
+ MmE4ODRkODFiN2NkZjE0MzA5ZDA4NjQ2MzU4YmUxN2MxOWY2MzVlNTllMjAx
11
+ N2I0YzUwMDQ5NWM4N2NlMWE3NmFhOTVmYmZhMTU2NmE5YjcwNzk=
12
+ data.tar.gz: !binary |-
13
+ MDQ0ZDAyNTViODVmMjgwOTY2NmZlY2FiMmQyNWFkZGNkMmUxNjg1OThhNDIx
14
+ YjYzY2NlNGY5ZDE3OWQwZTBmZmFhYmMwODRjZWE1NjUxMGJjYjQ0ODIzN2M0
15
+ MDBjNTFjZjRlNGE1OGQ5MzRhYWZiODQ4MGQxNmNmM2NmNDdlZWE=
@@ -0,0 +1,8 @@
1
+ require 'mechanize'
2
+ require 'json'
3
+
4
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__)))
5
+
6
+ require 'xqueue_ruby/xqueue_ruby'
7
+ require 'xqueue_ruby/xqueue_submission'
8
+
@@ -0,0 +1,184 @@
1
+ class XQueue
2
+ require 'mechanize'
3
+ require 'json'
4
+ require 'ruby-debug'
5
+
6
+ # Ruby interface to the Open edX XQueue class for external checkers
7
+ # (autograders). Lets you pull student-submitted work products from a
8
+ # named queue and post back the results of grading them.
9
+ #
10
+ # All responses from the XQueue server have a JSON object with
11
+ # +return_code+ and +content+ slots. A +return_code+ of 0 normally
12
+ # means success.
13
+ #
14
+ # == Example
15
+ #
16
+ # You need two sets of credentials to authenticate yourself to the
17
+ # xqueue server. For historical reasons, they are called
18
+ # (+django_name+, +django_pass+) and (+user_name+, +user_pass+).
19
+ # You also need to name the queue you want to use; edX creates queues
20
+ # for you. Each +XQueue+ instance is tied to a single queue name.
21
+ #
22
+ # === Retrieving an assignment:
23
+ #
24
+ # queue = XQueue.new('dj_name', 'dj_pass', 'u_name', 'u_pass', 'my_q')
25
+ # queue.length # => an integer showing queue length
26
+ # assignment = queue.get_submission # => returns new +XQueueSubmission+ object
27
+ #
28
+ # === Posting results back
29
+ #
30
+ # The submission includes a secret key that is used in postback,
31
+ # so you should use the +#postback+ method defined on the submission.
32
+ #
33
+
34
+ # The base URI of the production Xqueue server.
35
+ XQUEUE_DEFAULT_BASE_URI = 'https://xqueue.edx.org'
36
+
37
+ # Error message, if any, associated with last unsuccessful operation
38
+ attr_reader :error
39
+
40
+ # Queue from which to pull, established in constructor. You need a
41
+ # new +XQueue+ object if you want to use a different queue.
42
+ attr_reader :queue_name
43
+
44
+ # The base URI used for this queue; won't change for this queue even
45
+ # if you later change the value of +XQueue.base_uri+
46
+ attr_reader :base_uri
47
+
48
+ # The base URI used when new queue instances are created
49
+ def self.base_uri
50
+ @@base_uri ||= URI(XQUEUE_DEFAULT_BASE_URI)
51
+ end
52
+ def self.base_uri=(uri)
53
+ @@base_uri = URI(uri)
54
+ end
55
+
56
+ class XQueueError < StandardError ; end
57
+ # Ancestor class for all XQueue-related exceptions
58
+ class AuthenticationError < XQueueError ; end
59
+ # Raised if XQueue authentication fails
60
+ class IOError < XQueueError ; end
61
+ # Raised if there are network or I/O errors connecting to queue server
62
+ class NoSuchQueueError < XQueueError ; end
63
+ # Raised if queue name doesn't exist
64
+ class UpdateFailedError < XQueueError ; end
65
+ # Raised if a postback to the queue (to post grade) fails at
66
+ # application level
67
+
68
+ # Creates a new instance and attempts to authenticate to the
69
+ # queue server.
70
+ # * +django_name+, +django_pass+: first set of auth credentials (see
71
+ # above)
72
+ # * +user_name+, +user_pass+: second set of auth credentials (see
73
+ # above)
74
+ # * +queue_name+: logical name of the queue
75
+ def initialize(django_name, django_pass, user_name, user_pass, queue_name)
76
+ @queue_name = queue_name
77
+ @base_uri = XQueue.base_uri
78
+ @django_auth = {'username' => django_name, 'password' => django_pass}
79
+ @session = Mechanize.new
80
+ @session.add_auth(@base_uri, user_name, user_pass)
81
+ @valid_queues = nil
82
+ @error = nil
83
+ @authenticated = nil
84
+ end
85
+
86
+ # Authenticates to the server. You can call this explicitly, but it
87
+ # is called automatically if necessary on the first request in a new
88
+ # session.
89
+ def authenticate
90
+ response = request :post, '/xqueue/login/', @django_auth
91
+ if response['return_code'] == 0
92
+ @authenticated = true
93
+ else
94
+
95
+ raise(AuthenticationError, "Authentication failure: #{response['content']}")
96
+ end
97
+ end
98
+
99
+ # Returns +true+ if the session has been properly authenticated to
100
+ # server, that is, after a successful call to +authenticate+ or to any
101
+ # of the request methods that may have called +authenticate+ automatically.
102
+ def authenticated? ; @authenticated ; end
103
+
104
+ # Returns length of the queue as an integer >= 0.
105
+ def queue_length
106
+ authenticate unless authenticated?
107
+ response = request(:get, '/xqueue/get_queuelen/', {:queue_name => @queue_name})
108
+ if response['return_code'] == 0 # success
109
+ response['content'].to_i
110
+ elsif response['return_code'] == 1 && response['content'] =~ /^Valid queue names are: (.*)/i
111
+ @valid_queues = $1.split(/,\s+/)
112
+ raise NoSuchQueueError, "No such queue: valid queues are #{$1}"
113
+ else
114
+ raise IOError, response['content']
115
+ end
116
+ end
117
+
118
+ def list_queues
119
+ authenticate unless authenticated?
120
+ if @valid_queues.nil?
121
+ old, @queue_name = @queue_name, 'I_AM_NOT_A_QUEUE'
122
+ begin queue_length rescue nil end
123
+ end
124
+ @valid_queues
125
+ end
126
+
127
+ # Retrieve a submission from this queue. Returns nil if queue is empty,
128
+ # otherwise a new +XQueue::Submission+ instance.
129
+ def get_submission
130
+ authenticate unless authenticated?
131
+ if queue_length > 0
132
+ begin
133
+ json_response = request(:get, '/xqueue/get_submission/', {:queue_name => @queue_name})
134
+ XQueueSubmission.parse_JSON(self, json_response)
135
+ rescue StandardError => e # TODO: do something more interesting with the error.
136
+ raise e
137
+ end
138
+ else
139
+ nil
140
+ end
141
+ end
142
+ # Record a result of grading something. It may be easier to use
143
+ # +XQueue::Submission#post_back+, which marshals the information
144
+ # needed here automatically.
145
+ #
146
+ # * +header+: secret header key (from 'xqueue_header' slot in the
147
+ # 'content' object of the original retrieved submission)
148
+ # * +score+: integer number of points (not scaled)
149
+ # * +correct+: true (default) means show green checkmark, else red 'x'
150
+ # * +message+: (optional) plain text feedback; will be coerced to UTF-8
151
+
152
+ def put_result(header, score, correct=true, message='')
153
+ payload = JSON.generate({
154
+ :xqueue_header => header,
155
+ :xqueue_body => {
156
+ :correct => (!!correct).to_s.capitalize,
157
+ :score => score,
158
+ :message => message.encode('UTF-8',
159
+ :invalid => :replace, :undef => :replace, :replace => '?'),
160
+ }
161
+ })
162
+ response = request :post, '/xqueue/put_result', payload
163
+ if response['return_code'] != 0
164
+ raise UpdateFailedError, response['content']
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ # :nodoc:
171
+ def request(method, path, args={})
172
+ begin
173
+ response = @session.send(method, @base_uri + path, args)
174
+ response_json = JSON(response.body)
175
+
176
+ rescue Mechanize::ResponseCodeError => e
177
+ raise IOError, "Error communicating with server: #{e.message}"
178
+ rescue JSON::ParserError => e
179
+ raise IOError, "Non-JSON response from server: #{response.body.force_encoding('UTF-8')}"
180
+ rescue Exception => e
181
+ raise IOError, e.message
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,61 @@
1
+ require 'active_model'
2
+ require 'json'
3
+ require 'debugger'
4
+
5
+ class XQueueSubmission
6
+ include ActiveModel::Validations
7
+
8
+ class InvalidSubmissionError < StandardError ; end
9
+
10
+ attr_reader :queue
11
+ # The +XQueue+ from which this assignment was retrieved (and to which the grade should be posted back)
12
+ attr_reader :secret
13
+ # XQueue-server-supplied nonce that will be needed to post back a grade for this submission
14
+ attr_accessor :files
15
+ attr_reader :submission_time
16
+ # When student submitted assignment via edX (a Time object)
17
+ attr_reader :student_id
18
+ # one-way hash of edX student ID
19
+ attr_accessor :score
20
+ # Numeric: score reported by autograder
21
+ attr_accessor :message
22
+ # String: textual feedback from autograder
23
+ attr_accessor :correct
24
+ # Boolean: if true when posted back, shows green checkmark, otherwise red X
25
+
26
+ validates_presence_of :student_id
27
+ validates_presence_of :submission_time
28
+ validates_presence_of :secret
29
+
30
+ DEFAULTS = {correct: false, score: 0, message: '', errors: ''}
31
+ def initialize(hash)
32
+ begin
33
+ fields_hash = DEFAULTS.merge(hash)
34
+ fields_hash.each {|key, value| instance_variable_set("@#{key}", value)}
35
+ rescue NoMethodError => e
36
+ if e.message == "undefined method `[]' for nil:NilClass"
37
+ raise InvalidSubmissionError, "Missing element(s) in JSON: #{hash}"
38
+ end
39
+ raise StandardError 'yoloswag'
40
+ end
41
+ end
42
+
43
+ def post_back()
44
+ @queue.put_result(@secret, @score, @correct, @message)
45
+ end
46
+
47
+ def self.parse_JSON(xqueue, json_response)
48
+ parsed = JSON.parse(json_response)
49
+ header, files, body = parsed['xqueue_header'], parsed['xqueue_files'], parsed['xqueue_body']
50
+ grader_payload = body['grader_payload']
51
+ anonymous_student_id, submission_time = body['student_info']['anonymous_student_id'], Time.parse(body['student_info']['submission_time'])
52
+ XQueueSubmission.new({queue: xqueue, secret: header, files: files, student_id: anonymous_student_id, submission_time: submission_time })
53
+ end
54
+
55
+ def expand_files
56
+ # @files = @files.map each do
57
+ # do something
58
+ # end
59
+ # self
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xqueue_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Armando Fox
8
+ - Aaron Zhang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-06-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: builder
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ! '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: getopt
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ! '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: Pull interface to Open edX XQueue
43
+ email: fox@cs.berkeley.edu
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/xqueue_ruby.rb
49
+ - lib/xqueue_ruby/xqueue_ruby.rb
50
+ - lib/xqueue_ruby/xqueue_submission.rb
51
+ homepage: http://github.com/saasbook/x_queue
52
+ licenses:
53
+ - BSD
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 2.4.8
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: Pull interface to Open edX XQueue
75
+ test_files: []