potluck.context_utils
Support functions for contexts that are also needed by modules which the
potluck.contexts
module depends on.
context_utils.py
1""" 2Support functions for contexts that are also needed by modules which the 3`potluck.contexts` module depends on. 4 5context_utils.py 6""" 7 8import io, os 9 10from . import html_tools 11 12 13#---------# 14# Globals # 15#---------# 16 17BASE_CONTEXT_SLOTS = [ 18 "task_info", 19 "username", 20 "submission_root", 21 "default_file", 22 "actual_file" 23] 24""" 25The context slots which can be expected to always be available, and which 26are provided not via a `Context` object but by the evaluation system 27itself. 28""" 29 30 31#---------------# 32# Error classes # 33#---------------# 34 35class ContextError(Exception): 36 """ 37 Custom exception class to use when context builders fail. 38 """ 39 pass 40 41 42class MissingContextError(ContextError): 43 """ 44 Error indicating that required context is missing during 45 testing/checking (i.e., an internal error in the construction of a 46 Goal or Rubric). 47 """ 48 pass 49 50 51class ContextCreationError(ContextError): 52 """ 53 Error indicating that context could not be created, usually because a 54 submission was missing the required function or module, or because 55 attempting to evaluate a function/module caused an error. 56 """ 57 def __init__(self, context, message, cause=None): 58 """ 59 Supply a Context object within which this error was generated, a 60 message describing the error, and if there is one, an earlier 61 Exception that caused this error. 62 """ 63 self.context = context 64 self.message = message 65 self.cause = cause 66 67 def __str__(self): 68 return ( 69 "(arising in context '{}') {}" 70 ).format( 71 self.context.feedback_topic(), # errors won't appear in rubric 72 ( 73 "<br>\n caused by: {}".format(self.cause) 74 if self.cause 75 else "" 76 ) 77 ) 78 79 def __repr__(self): 80 return "ContextCreationError({}, {}, {})".format( 81 repr(self.context), repr(self.message), repr(self.cause) 82 ) 83 84 def explanation(self): 85 """ 86 Chains through causes to return an HTML string describing why 87 this context could not be established. Makes use of the html_tb 88 property of our cause if it's present and the cause is not a 89 ContextCreationError. 90 """ 91 result = "Could not get '{}' because:".format( 92 self.context.feedback_topic() # errors won't appear in rubric 93 ) 94 if self.cause: 95 # Short-circuit to the root of the chain if we can 96 current = self 97 chain = [] 98 while hasattr(current, "cause") and current.cause: 99 chain.append(current) 100 current = current.cause 101 102 # Add the root cause's message first 103 root_cause = current 104 result += "<br>\n" + str(root_cause) 105 106 # Try to get an HTML traceback for the root cause 107 root_tb = str(root_cause) 108 if hasattr(root_cause, "html_tb"): 109 root_tb = root_cause.html_tb 110 elif hasattr(root_cause, "__traceback__"): 111 if self.context.cached_value is not None: 112 linkable = linkmap(self.context.cached_value) 113 elif self.context.working_from is not None: 114 linkable = linkmap(self.context.working_from) 115 else: 116 linkable = None 117 root_tb = html_tools.html_traceback( 118 root_cause, 119 linkable=linkable 120 ) 121 122 result += html_tools.build_html_details( 123 "Details (click me):", 124 html_tools.build_list( 125 [ 126 "{}: {}".format( 127 error.context.feedback_topic(), 128 error.message 129 ) 130 for error in chain 131 ] + [ root_tb ] 132 ) 133 ) 134 else: 135 result += "<br>\n" + self.message 136 return result 137 138 139#-------# 140# Utils # 141#-------# 142 143def extract(context, slot): 144 """ 145 Returns the value for the given slot of the given Context, raising a 146 MissingContextError if there is no such value. 147 """ 148 if slot in context: 149 return context[slot] 150 else: 151 raise MissingContextError( 152 f"Required context slot '{slot}' not found." 153 ) 154 155 156def linkmap(context): 157 """ 158 Returns a link dictionary suitable for the `linkable` parameter of 159 `html_traceback`, based on the "task_info" and "filename" slots of 160 the given context dictionary, or just based on the "task_info" if 161 "filename" is not present. Returns None if "task_info" is missing. 162 """ 163 if "task_info" in context and "filename" in context: 164 return { context["filename"]: context["task_info"]["id"] } 165 elif "task_info" in context: 166 ti = context["task_info"] 167 return { ti["target"]: ti["id"] } 168 else: 169 return None 170 171 172class MashEnter(io.TextIOBase): 173 """ 174 A fake input designed to be used in place of stdin. Reading anything 175 just returns newlines. 176 """ 177 def read(size=-1): 178 """ 179 Returns a newline, or if a specific size is specified, that many 180 newlines. Behavior is weird since normally you can't call read 181 multiple times. 182 """ 183 if size == -1: 184 return '\n' 185 else: 186 return '\n' * size 187 188 def readline(size=-1): 189 """ 190 Returns a newline. 191 """ 192 return '\n' 193 194 195class AllOnes(io.TextIOBase): 196 """ 197 A fake input designed to be used in place of stdin. Reading anything 198 just returns the string '1' with a newline at the end. 199 """ 200 def read(size=-1): 201 """ 202 Returns a string containing the digit 1 followed by a newline, or 203 if a specific size is specified, that many ones minus one, plus a 204 single newline. Behavior is weird since normally you can't call 205 read multiple times. 206 """ 207 if size == -1: 208 return '1\n' 209 else: 210 return '1' * (size - 1) + '\n' 211 212 def readline(size=-1): 213 """ 214 Returns the digit 1 followed by a newline. 215 """ 216 return '1\n' 217 218 219def sandbox_filemap(spec): 220 """ 221 Extracts the standard symlink mapping for sandbox files from the 222 given specification, based on its helper_files and starter_path 223 properties. The result maps absolute filenames to sandbox-relative 224 filenames and specifies how to copy helper files into a sandbox. 225 """ 226 return { 227 os.path.abspath(os.path.join(spec.starter_path, helper)): helper 228 for helper in spec.helper_files 229 } 230 231 232#---------------------------# 233# ContextualValue machinery # 234#---------------------------# 235 236class ContextualValue: 237 """ 238 This class and its subclasses represent values that may appear as 239 part of arguments in a code behavior test (see 240 create_code_behavior_context_builder). Before testing, those 241 arguments will be replaced (using the instance's replace method). The 242 replace method will receive the current context as its first 243 argument, along with a boolean indicating whether the value is being 244 requested to test the submitted module (True) or solution module 245 (False). 246 247 The default behavior (this class) is to accept a function when 248 constructed and run that function on the provided context dictionary, 249 using the function's return value as the actual argument to the 250 function being tested. 251 252 Your extractor function should ideally raise MissingContextError in 253 cases where a context value that it depends on is not present, 254 although this is not critical. 255 """ 256 def __init__(self, value_extractor): 257 """ 258 There is one required argument: the value extractor function, 259 which will be given a context dictionary and a boolean indicating 260 submitted vs. solution testing, and will be expected to produce 261 an argument value. 262 """ 263 self.extractor = value_extractor 264 265 def __str__(self): 266 return "a contextual value based on {}".format( 267 self.extractor.__name__ 268 ) 269 270 def __repr__(self): 271 return "<a ContextualValue based on {}>".format( 272 self.extractor.__name__ 273 ) 274 275 def replace(self, context): 276 """ 277 This method is used to provide an actual argument value to take 278 the place of this object. 279 280 The argument is the context to use to retrieve a value. 281 282 This implementation simply runs the provided extractor function 283 on the two arguments it gets. 284 """ 285 return self.extractor(context) 286 287 288class ContextSlot(ContextualValue): 289 """ 290 A special case ContextualValue where the value to be used is simply 291 stored in a slot in the context dictionary, with no extra processing 292 necessary. This class just needs the string name of the slot to be 293 used. 294 """ 295 def __init__(self, slot_name): 296 """ 297 One required argument: the name of the context slot to use. 298 """ 299 self.slot = slot_name 300 301 def __str__(self): 302 return "the current '{}' value".format(self.slot) 303 304 def __repr__(self): 305 return "<a ContextSlot for " + str(self) + ">" 306 307 def replace(self, context): 308 """ 309 We retrieve the slot value from the context. Notice that if the 310 value is missing, we generate a MissingContextError that should 311 eventually bubble out. 312 """ 313 # Figure out which slot we're using 314 slot = self.slot 315 316 if slot not in context: 317 raise MissingContextError( 318 ( 319 "Context slot '{}' is required by a ContextSlot dynamic" 320 " value, but it is not present in the testing context." 321 ).format(slot) 322 ) 323 return context[slot] 324 325 326class ModuleValue(ContextualValue): 327 """ 328 A kind of ContextualValue that evaluates a string containing Python 329 code within the module stored in the "module" context slot. 330 """ 331 def __init__(self, expression): 332 """ 333 One required argument: the expression to evaluate, which must be 334 a string that contains a valid Python expression. 335 """ 336 self.expression = expression 337 338 def __str__(self): 339 return "the result of " + self.expression 340 341 def __repr__(self): 342 return "<a ModuleValue based on {}>".format(str(self)) 343 344 def replace(self, context): 345 """ 346 We retrieve the "module" slot value from the provided context. 347 348 We then evaluate our expression within the retrieved module, and 349 return that result. 350 """ 351 if "module" not in context: 352 raise MissingContextError( 353 "ModuleValue argument requires a 'module' context" 354 + " key, but there isn't one." 355 ) 356 module = context["module"] 357 358 return eval(self.expression, module.__dict__) 359 360 361class SolnValue(ContextualValue): 362 """ 363 Like a ModuleValue, but *always* takes the value from the solution 364 module, even when we're testing submitted code. 365 """ 366 def __init__(self, expression): 367 """ 368 One required argument: the expression to evaluate, which must be 369 a string that contains a valid Python expression. 370 """ 371 self.expression = expression 372 373 def __str__(self): 374 return "the correct value of " + self.expression 375 376 def __repr__(self): 377 return "<a SolnValue based on {}>".format(str(self)) 378 379 def replace(self, context): 380 """ 381 We retrieve the "ref_module" slot value from the provided context. 382 383 We then evaluate our expression within the retrieved module, and 384 return that result. 385 """ 386 if "ref_module" not in context: 387 raise MissingContextError( 388 f"SolnValue argument requires a 'ref_module' context" 389 f" key, but there isn't one in: {list(context.keys())}" 390 ) 391 module = context["ref_module"] 392 393 return eval(self.expression, module.__dict__)
The context slots which can be expected to always be available, and which
are provided not via a Context
object but by the evaluation system
itself.
36class ContextError(Exception): 37 """ 38 Custom exception class to use when context builders fail. 39 """ 40 pass
Custom exception class to use when context builders fail.
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
43class MissingContextError(ContextError): 44 """ 45 Error indicating that required context is missing during 46 testing/checking (i.e., an internal error in the construction of a 47 Goal or Rubric). 48 """ 49 pass
Error indicating that required context is missing during testing/checking (i.e., an internal error in the construction of a Goal or Rubric).
Inherited Members
- builtins.Exception
- Exception
- builtins.BaseException
- with_traceback
52class ContextCreationError(ContextError): 53 """ 54 Error indicating that context could not be created, usually because a 55 submission was missing the required function or module, or because 56 attempting to evaluate a function/module caused an error. 57 """ 58 def __init__(self, context, message, cause=None): 59 """ 60 Supply a Context object within which this error was generated, a 61 message describing the error, and if there is one, an earlier 62 Exception that caused this error. 63 """ 64 self.context = context 65 self.message = message 66 self.cause = cause 67 68 def __str__(self): 69 return ( 70 "(arising in context '{}') {}" 71 ).format( 72 self.context.feedback_topic(), # errors won't appear in rubric 73 ( 74 "<br>\n caused by: {}".format(self.cause) 75 if self.cause 76 else "" 77 ) 78 ) 79 80 def __repr__(self): 81 return "ContextCreationError({}, {}, {})".format( 82 repr(self.context), repr(self.message), repr(self.cause) 83 ) 84 85 def explanation(self): 86 """ 87 Chains through causes to return an HTML string describing why 88 this context could not be established. Makes use of the html_tb 89 property of our cause if it's present and the cause is not a 90 ContextCreationError. 91 """ 92 result = "Could not get '{}' because:".format( 93 self.context.feedback_topic() # errors won't appear in rubric 94 ) 95 if self.cause: 96 # Short-circuit to the root of the chain if we can 97 current = self 98 chain = [] 99 while hasattr(current, "cause") and current.cause: 100 chain.append(current) 101 current = current.cause 102 103 # Add the root cause's message first 104 root_cause = current 105 result += "<br>\n" + str(root_cause) 106 107 # Try to get an HTML traceback for the root cause 108 root_tb = str(root_cause) 109 if hasattr(root_cause, "html_tb"): 110 root_tb = root_cause.html_tb 111 elif hasattr(root_cause, "__traceback__"): 112 if self.context.cached_value is not None: 113 linkable = linkmap(self.context.cached_value) 114 elif self.context.working_from is not None: 115 linkable = linkmap(self.context.working_from) 116 else: 117 linkable = None 118 root_tb = html_tools.html_traceback( 119 root_cause, 120 linkable=linkable 121 ) 122 123 result += html_tools.build_html_details( 124 "Details (click me):", 125 html_tools.build_list( 126 [ 127 "{}: {}".format( 128 error.context.feedback_topic(), 129 error.message 130 ) 131 for error in chain 132 ] + [ root_tb ] 133 ) 134 ) 135 else: 136 result += "<br>\n" + self.message 137 return result
Error indicating that context could not be created, usually because a submission was missing the required function or module, or because attempting to evaluate a function/module caused an error.
58 def __init__(self, context, message, cause=None): 59 """ 60 Supply a Context object within which this error was generated, a 61 message describing the error, and if there is one, an earlier 62 Exception that caused this error. 63 """ 64 self.context = context 65 self.message = message 66 self.cause = cause
Supply a Context object within which this error was generated, a message describing the error, and if there is one, an earlier Exception that caused this error.
85 def explanation(self): 86 """ 87 Chains through causes to return an HTML string describing why 88 this context could not be established. Makes use of the html_tb 89 property of our cause if it's present and the cause is not a 90 ContextCreationError. 91 """ 92 result = "Could not get '{}' because:".format( 93 self.context.feedback_topic() # errors won't appear in rubric 94 ) 95 if self.cause: 96 # Short-circuit to the root of the chain if we can 97 current = self 98 chain = [] 99 while hasattr(current, "cause") and current.cause: 100 chain.append(current) 101 current = current.cause 102 103 # Add the root cause's message first 104 root_cause = current 105 result += "<br>\n" + str(root_cause) 106 107 # Try to get an HTML traceback for the root cause 108 root_tb = str(root_cause) 109 if hasattr(root_cause, "html_tb"): 110 root_tb = root_cause.html_tb 111 elif hasattr(root_cause, "__traceback__"): 112 if self.context.cached_value is not None: 113 linkable = linkmap(self.context.cached_value) 114 elif self.context.working_from is not None: 115 linkable = linkmap(self.context.working_from) 116 else: 117 linkable = None 118 root_tb = html_tools.html_traceback( 119 root_cause, 120 linkable=linkable 121 ) 122 123 result += html_tools.build_html_details( 124 "Details (click me):", 125 html_tools.build_list( 126 [ 127 "{}: {}".format( 128 error.context.feedback_topic(), 129 error.message 130 ) 131 for error in chain 132 ] + [ root_tb ] 133 ) 134 ) 135 else: 136 result += "<br>\n" + self.message 137 return result
Chains through causes to return an HTML string describing why this context could not be established. Makes use of the html_tb property of our cause if it's present and the cause is not a ContextCreationError.
Inherited Members
- builtins.BaseException
- with_traceback
144def extract(context, slot): 145 """ 146 Returns the value for the given slot of the given Context, raising a 147 MissingContextError if there is no such value. 148 """ 149 if slot in context: 150 return context[slot] 151 else: 152 raise MissingContextError( 153 f"Required context slot '{slot}' not found." 154 )
Returns the value for the given slot of the given Context, raising a MissingContextError if there is no such value.
157def linkmap(context): 158 """ 159 Returns a link dictionary suitable for the `linkable` parameter of 160 `html_traceback`, based on the "task_info" and "filename" slots of 161 the given context dictionary, or just based on the "task_info" if 162 "filename" is not present. Returns None if "task_info" is missing. 163 """ 164 if "task_info" in context and "filename" in context: 165 return { context["filename"]: context["task_info"]["id"] } 166 elif "task_info" in context: 167 ti = context["task_info"] 168 return { ti["target"]: ti["id"] } 169 else: 170 return None
Returns a link dictionary suitable for the linkable
parameter of
html_traceback
, based on the "task_info" and "filename" slots of
the given context dictionary, or just based on the "task_info" if
"filename" is not present. Returns None if "task_info" is missing.
173class MashEnter(io.TextIOBase): 174 """ 175 A fake input designed to be used in place of stdin. Reading anything 176 just returns newlines. 177 """ 178 def read(size=-1): 179 """ 180 Returns a newline, or if a specific size is specified, that many 181 newlines. Behavior is weird since normally you can't call read 182 multiple times. 183 """ 184 if size == -1: 185 return '\n' 186 else: 187 return '\n' * size 188 189 def readline(size=-1): 190 """ 191 Returns a newline. 192 """ 193 return '\n'
A fake input designed to be used in place of stdin. Reading anything just returns newlines.
178 def read(size=-1): 179 """ 180 Returns a newline, or if a specific size is specified, that many 181 newlines. Behavior is weird since normally you can't call read 182 multiple times. 183 """ 184 if size == -1: 185 return '\n' 186 else: 187 return '\n' * size
Returns a newline, or if a specific size is specified, that many newlines. Behavior is weird since normally you can't call read multiple times.
Inherited Members
- _io._TextIOBase
- detach
- write
- encoding
- newlines
- errors
- _io._IOBase
- seek
- tell
- truncate
- flush
- close
- seekable
- readable
- writable
- fileno
- isatty
- readlines
- writelines
196class AllOnes(io.TextIOBase): 197 """ 198 A fake input designed to be used in place of stdin. Reading anything 199 just returns the string '1' with a newline at the end. 200 """ 201 def read(size=-1): 202 """ 203 Returns a string containing the digit 1 followed by a newline, or 204 if a specific size is specified, that many ones minus one, plus a 205 single newline. Behavior is weird since normally you can't call 206 read multiple times. 207 """ 208 if size == -1: 209 return '1\n' 210 else: 211 return '1' * (size - 1) + '\n' 212 213 def readline(size=-1): 214 """ 215 Returns the digit 1 followed by a newline. 216 """ 217 return '1\n'
A fake input designed to be used in place of stdin. Reading anything just returns the string '1' with a newline at the end.
201 def read(size=-1): 202 """ 203 Returns a string containing the digit 1 followed by a newline, or 204 if a specific size is specified, that many ones minus one, plus a 205 single newline. Behavior is weird since normally you can't call 206 read multiple times. 207 """ 208 if size == -1: 209 return '1\n' 210 else: 211 return '1' * (size - 1) + '\n'
Returns a string containing the digit 1 followed by a newline, or if a specific size is specified, that many ones minus one, plus a single newline. Behavior is weird since normally you can't call read multiple times.
213 def readline(size=-1): 214 """ 215 Returns the digit 1 followed by a newline. 216 """ 217 return '1\n'
Returns the digit 1 followed by a newline.
Inherited Members
- _io._TextIOBase
- detach
- write
- encoding
- newlines
- errors
- _io._IOBase
- seek
- tell
- truncate
- flush
- close
- seekable
- readable
- writable
- fileno
- isatty
- readlines
- writelines
220def sandbox_filemap(spec): 221 """ 222 Extracts the standard symlink mapping for sandbox files from the 223 given specification, based on its helper_files and starter_path 224 properties. The result maps absolute filenames to sandbox-relative 225 filenames and specifies how to copy helper files into a sandbox. 226 """ 227 return { 228 os.path.abspath(os.path.join(spec.starter_path, helper)): helper 229 for helper in spec.helper_files 230 }
Extracts the standard symlink mapping for sandbox files from the given specification, based on its helper_files and starter_path properties. The result maps absolute filenames to sandbox-relative filenames and specifies how to copy helper files into a sandbox.
237class ContextualValue: 238 """ 239 This class and its subclasses represent values that may appear as 240 part of arguments in a code behavior test (see 241 create_code_behavior_context_builder). Before testing, those 242 arguments will be replaced (using the instance's replace method). The 243 replace method will receive the current context as its first 244 argument, along with a boolean indicating whether the value is being 245 requested to test the submitted module (True) or solution module 246 (False). 247 248 The default behavior (this class) is to accept a function when 249 constructed and run that function on the provided context dictionary, 250 using the function's return value as the actual argument to the 251 function being tested. 252 253 Your extractor function should ideally raise MissingContextError in 254 cases where a context value that it depends on is not present, 255 although this is not critical. 256 """ 257 def __init__(self, value_extractor): 258 """ 259 There is one required argument: the value extractor function, 260 which will be given a context dictionary and a boolean indicating 261 submitted vs. solution testing, and will be expected to produce 262 an argument value. 263 """ 264 self.extractor = value_extractor 265 266 def __str__(self): 267 return "a contextual value based on {}".format( 268 self.extractor.__name__ 269 ) 270 271 def __repr__(self): 272 return "<a ContextualValue based on {}>".format( 273 self.extractor.__name__ 274 ) 275 276 def replace(self, context): 277 """ 278 This method is used to provide an actual argument value to take 279 the place of this object. 280 281 The argument is the context to use to retrieve a value. 282 283 This implementation simply runs the provided extractor function 284 on the two arguments it gets. 285 """ 286 return self.extractor(context)
This class and its subclasses represent values that may appear as part of arguments in a code behavior test (see create_code_behavior_context_builder). Before testing, those arguments will be replaced (using the instance's replace method). The replace method will receive the current context as its first argument, along with a boolean indicating whether the value is being requested to test the submitted module (True) or solution module (False).
The default behavior (this class) is to accept a function when constructed and run that function on the provided context dictionary, using the function's return value as the actual argument to the function being tested.
Your extractor function should ideally raise MissingContextError in cases where a context value that it depends on is not present, although this is not critical.
257 def __init__(self, value_extractor): 258 """ 259 There is one required argument: the value extractor function, 260 which will be given a context dictionary and a boolean indicating 261 submitted vs. solution testing, and will be expected to produce 262 an argument value. 263 """ 264 self.extractor = value_extractor
There is one required argument: the value extractor function, which will be given a context dictionary and a boolean indicating submitted vs. solution testing, and will be expected to produce an argument value.
276 def replace(self, context): 277 """ 278 This method is used to provide an actual argument value to take 279 the place of this object. 280 281 The argument is the context to use to retrieve a value. 282 283 This implementation simply runs the provided extractor function 284 on the two arguments it gets. 285 """ 286 return self.extractor(context)
This method is used to provide an actual argument value to take the place of this object.
The argument is the context to use to retrieve a value.
This implementation simply runs the provided extractor function on the two arguments it gets.
289class ContextSlot(ContextualValue): 290 """ 291 A special case ContextualValue where the value to be used is simply 292 stored in a slot in the context dictionary, with no extra processing 293 necessary. This class just needs the string name of the slot to be 294 used. 295 """ 296 def __init__(self, slot_name): 297 """ 298 One required argument: the name of the context slot to use. 299 """ 300 self.slot = slot_name 301 302 def __str__(self): 303 return "the current '{}' value".format(self.slot) 304 305 def __repr__(self): 306 return "<a ContextSlot for " + str(self) + ">" 307 308 def replace(self, context): 309 """ 310 We retrieve the slot value from the context. Notice that if the 311 value is missing, we generate a MissingContextError that should 312 eventually bubble out. 313 """ 314 # Figure out which slot we're using 315 slot = self.slot 316 317 if slot not in context: 318 raise MissingContextError( 319 ( 320 "Context slot '{}' is required by a ContextSlot dynamic" 321 " value, but it is not present in the testing context." 322 ).format(slot) 323 ) 324 return context[slot]
A special case ContextualValue where the value to be used is simply stored in a slot in the context dictionary, with no extra processing necessary. This class just needs the string name of the slot to be used.
296 def __init__(self, slot_name): 297 """ 298 One required argument: the name of the context slot to use. 299 """ 300 self.slot = slot_name
One required argument: the name of the context slot to use.
308 def replace(self, context): 309 """ 310 We retrieve the slot value from the context. Notice that if the 311 value is missing, we generate a MissingContextError that should 312 eventually bubble out. 313 """ 314 # Figure out which slot we're using 315 slot = self.slot 316 317 if slot not in context: 318 raise MissingContextError( 319 ( 320 "Context slot '{}' is required by a ContextSlot dynamic" 321 " value, but it is not present in the testing context." 322 ).format(slot) 323 ) 324 return context[slot]
We retrieve the slot value from the context. Notice that if the value is missing, we generate a MissingContextError that should eventually bubble out.
327class ModuleValue(ContextualValue): 328 """ 329 A kind of ContextualValue that evaluates a string containing Python 330 code within the module stored in the "module" context slot. 331 """ 332 def __init__(self, expression): 333 """ 334 One required argument: the expression to evaluate, which must be 335 a string that contains a valid Python expression. 336 """ 337 self.expression = expression 338 339 def __str__(self): 340 return "the result of " + self.expression 341 342 def __repr__(self): 343 return "<a ModuleValue based on {}>".format(str(self)) 344 345 def replace(self, context): 346 """ 347 We retrieve the "module" slot value from the provided context. 348 349 We then evaluate our expression within the retrieved module, and 350 return that result. 351 """ 352 if "module" not in context: 353 raise MissingContextError( 354 "ModuleValue argument requires a 'module' context" 355 + " key, but there isn't one." 356 ) 357 module = context["module"] 358 359 return eval(self.expression, module.__dict__)
A kind of ContextualValue that evaluates a string containing Python code within the module stored in the "module" context slot.
332 def __init__(self, expression): 333 """ 334 One required argument: the expression to evaluate, which must be 335 a string that contains a valid Python expression. 336 """ 337 self.expression = expression
One required argument: the expression to evaluate, which must be a string that contains a valid Python expression.
345 def replace(self, context): 346 """ 347 We retrieve the "module" slot value from the provided context. 348 349 We then evaluate our expression within the retrieved module, and 350 return that result. 351 """ 352 if "module" not in context: 353 raise MissingContextError( 354 "ModuleValue argument requires a 'module' context" 355 + " key, but there isn't one." 356 ) 357 module = context["module"] 358 359 return eval(self.expression, module.__dict__)
We retrieve the "module" slot value from the provided context.
We then evaluate our expression within the retrieved module, and return that result.
362class SolnValue(ContextualValue): 363 """ 364 Like a ModuleValue, but *always* takes the value from the solution 365 module, even when we're testing submitted code. 366 """ 367 def __init__(self, expression): 368 """ 369 One required argument: the expression to evaluate, which must be 370 a string that contains a valid Python expression. 371 """ 372 self.expression = expression 373 374 def __str__(self): 375 return "the correct value of " + self.expression 376 377 def __repr__(self): 378 return "<a SolnValue based on {}>".format(str(self)) 379 380 def replace(self, context): 381 """ 382 We retrieve the "ref_module" slot value from the provided context. 383 384 We then evaluate our expression within the retrieved module, and 385 return that result. 386 """ 387 if "ref_module" not in context: 388 raise MissingContextError( 389 f"SolnValue argument requires a 'ref_module' context" 390 f" key, but there isn't one in: {list(context.keys())}" 391 ) 392 module = context["ref_module"] 393 394 return eval(self.expression, module.__dict__)
Like a ModuleValue, but always takes the value from the solution module, even when we're testing submitted code.
367 def __init__(self, expression): 368 """ 369 One required argument: the expression to evaluate, which must be 370 a string that contains a valid Python expression. 371 """ 372 self.expression = expression
One required argument: the expression to evaluate, which must be a string that contains a valid Python expression.
380 def replace(self, context): 381 """ 382 We retrieve the "ref_module" slot value from the provided context. 383 384 We then evaluate our expression within the retrieved module, and 385 return that result. 386 """ 387 if "ref_module" not in context: 388 raise MissingContextError( 389 f"SolnValue argument requires a 'ref_module' context" 390 f" key, but there isn't one in: {list(context.keys())}" 391 ) 392 module = context["ref_module"] 393 394 return eval(self.expression, module.__dict__)
We retrieve the "ref_module" slot value from the provided context.
We then evaluate our expression within the retrieved module, and return that result.