|
| 1 | +from pythonwhat.Reporter import Reporter |
| 2 | +from pythonwhat.Test import Test, EqualTest |
| 3 | +from pythonwhat.Feedback import Feedback |
| 4 | +from pythonwhat.State import State |
| 5 | +from functools import singledispatch |
| 6 | +from pythonwhat.check_funcs import check_part_index |
| 7 | + |
| 8 | +MSG_INCORRECT_LOOP = "FMT:Have you used the correct iterator variable names? Was expecting `{sol_vars}` but got `{stu_vars}`." |
| 9 | +MSG_INCORRECT_WITH = "FMT:Make sure to use the correct context variable names. Was expecting `{sol_vars}` but got `{stu_vars}`." |
| 10 | + |
| 11 | +def has_context(incorrect_msg=None, exact_names=False, state=None): |
| 12 | + # call _has_context, since the built-in singledispatch can only use 1st pos arg |
| 13 | + return _has_context(state, incorrect_msg, exact_names) |
| 14 | + |
| 15 | +def _test(state, incorrect_msg, exact_names, tv_name, highlight_name): |
| 16 | + rep = Reporter.active_reporter |
| 17 | + # get parts for testing from state |
| 18 | + # TODO: this could be rewritten to use check_part_index -> has_equal_part, etc.. |
| 19 | + stu_vars = state.student_parts[tv_name] |
| 20 | + sol_vars = state.solution_parts[tv_name] |
| 21 | + stu_target = state.student_parts.get(highlight_name) # TODO should be node? |
| 22 | + |
| 23 | + # variables exposed to messages |
| 24 | + d = { 'stu_vars': stu_vars, |
| 25 | + 'sol_vars': sol_vars, |
| 26 | + 'num_vars': len(sol_vars)} |
| 27 | + |
| 28 | + if exact_names: |
| 29 | + # message for wrong iter var names |
| 30 | + _msg = state.build_message(incorrect_msg, d) |
| 31 | + # test |
| 32 | + rep.do_test(EqualTest(stu_vars, sol_vars, Feedback(_msg, stu_target))) |
| 33 | + else: |
| 34 | + # message for wrong number of iter vars |
| 35 | + _msg = state.build_message(incorrect_msg, d) |
| 36 | + # test |
| 37 | + rep.do_test(EqualTest(len(stu_vars), len(sol_vars), Feedback(_msg, stu_target))) |
| 38 | + |
| 39 | + return state |
| 40 | + |
| 41 | + |
| 42 | +@singledispatch |
| 43 | +def _has_context(state, incorrect_msg, exact_names): |
| 44 | + raise BaseException("first argument to _has_context must be a State instance or subclass") |
| 45 | + |
| 46 | +@_has_context.register(State) |
| 47 | +def has_context_state(*args, **kwargs): |
| 48 | + return _test(*args, tv_name='target_vars', highlight_name='highlight', **kwargs) |
| 49 | + |
| 50 | +@_has_context.register(State.SUBCLASSES['for_loops']) |
| 51 | +@_has_context.register(State.SUBCLASSES['whiles']) |
| 52 | +@_has_context.register(State.SUBCLASSES['dict_comps']) |
| 53 | +@_has_context.register(State.SUBCLASSES['generator_exps']) |
| 54 | +@_has_context.register(State.SUBCLASSES['list_comps']) |
| 55 | +def has_context_loop(state, incorrect_msg, exact_names): |
| 56 | + """When dispatched on loops, has_context the target vars are the attribute _target_vars. |
| 57 | +
|
| 58 | + Note: This is to allow people to call has_context on a node (e.g. for_loop) rather than |
| 59 | + one of its attributes (e.g. body). Purely for convenience. |
| 60 | + """ |
| 61 | + return _test(state, incorrect_msg or MSG_INCORRECT_LOOP, exact_names, |
| 62 | + tv_name='_target_vars', highlight_name='target') |
| 63 | + |
| 64 | +@_has_context.register(State.SUBCLASSES['withs']) |
| 65 | +def has_context_with(state, incorrect_msg, exact_names): |
| 66 | + """When dispatched on with statements, has_context loops over each context manager. |
| 67 | +
|
| 68 | + Note: This is to allow people to call has_context on the with statement, rather than |
| 69 | + having to manually loop over each context manager. |
| 70 | +
|
| 71 | + e.g. Ex().check_with(0).has_context() vs Ex().check_with(0).check_context(0).has_context() |
| 72 | + """ |
| 73 | + |
| 74 | + for i in range(len(state.solution_parts['context'])): |
| 75 | + ctxt_state = check_part_index('context', i, '{ordinal} context', state=state) |
| 76 | + _has_context(ctxt_state, incorrect_msg or MSG_INCORRECT_WITH, exact_names) |
| 77 | + |
| 78 | + return state |
0 commit comments