-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor e2xgrader apps #177
Changes from 30 commits
628c27e
cb7a17d
0fb7ae4
07573dc
29e755d
317ef0f
10b0fa6
72ebadd
79cf4e5
faba50c
c706890
a5902bc
ff11d1a
8ed48e7
12a7840
5c7b306
ee64313
e18c602
eb6d549
95410c9
869f60d
a243550
7f77019
8f2cf8b
6a3ed6e
323e257
63bd6d5
28a8ffa
fc01a70
0f79c8a
3cc7365
cd716b8
6f45ea6
11af8fe
76c98f9
927d83c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from .activatemodeapp import ActivateModeApp | ||
from .baseapp import E2xGrader | ||
from .deactivatemodeapp import DeactivateModeApp | ||
from .e2xgraderapp import E2xGraderApp | ||
from .showmodeapp import ShowModeApp | ||
from .togglemodeapp import ToggleModeApp | ||
|
||
__all__ = [ | ||
"E2xGrader", | ||
"E2xGraderApp", | ||
"ActivateModeApp", | ||
"DeactivateModeApp", | ||
"ShowModeApp", | ||
"ToggleModeApp", | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from .togglemodeapp import ToggleModeApp | ||
|
||
|
||
class ActivateModeApp(ToggleModeApp): | ||
description = "Activate a specific mode (teacher, student, student_exam)" | ||
|
||
def start(self) -> None: | ||
super().start() | ||
if len(self.extra_args) != 1: | ||
self.fail("Exactly one mode has to be specified") | ||
self.mode = self.extra_args[0] | ||
self.activate_mode() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from jupyter_core.application import JupyterApp | ||
from traitlets import Bool, Enum | ||
|
||
from ..utils.mode import infer_e2xgrader_mode | ||
|
||
|
||
class E2xGrader(JupyterApp): | ||
|
||
sys_prefix = Bool(False, help="Install extensions to sys.prefix", config=True) | ||
|
||
user = Bool(False, help="Install extensions to the user space", config=True) | ||
|
||
mode = Enum( | ||
values=["teacher", "student", "student_exam", "None"], | ||
default_value="None", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so E2xGrader can be active with |
||
help="Which mode is activated, can be teacher, student or student_exam", | ||
) | ||
|
||
def fail(self, msg, *args): | ||
self.log.error(msg, *args) | ||
self.exit(1) | ||
|
||
def initialize(self, argv=None): | ||
try: | ||
mode = infer_e2xgrader_mode() | ||
self.mode = mode | ||
except ValueError as e: | ||
self.log.error(str(e)) | ||
super().initialize(argv) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from .togglemodeapp import ToggleModeApp | ||
|
||
|
||
class DeactivateModeApp(ToggleModeApp): | ||
description = "Deactivate all e2xgrader extensions" | ||
|
||
def start(self) -> None: | ||
super().start() | ||
if len(self.extra_args) != 0: | ||
self.fail("e2xgrader deactivate does not take any arguments.") | ||
self.mode = "None" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe |
||
self.activate_mode() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,105 +1,48 @@ | ||
import sys | ||
from argparse import ArgumentParser | ||
from textwrap import dedent | ||
|
||
from ..extensions import E2xExtensionManager | ||
from .activatemodeapp import ActivateModeApp | ||
from .baseapp import E2xGrader | ||
from .deactivatemodeapp import DeactivateModeApp | ||
from .showmodeapp import ShowModeApp | ||
|
||
|
||
class Manager: | ||
def __init__(self): | ||
self.extension_manager = E2xExtensionManager() | ||
parser = ArgumentParser( | ||
description="E2X extension manager.", | ||
usage=dedent( | ||
""" | ||
e2xgrader <command> [<args>] | ||
|
||
Available sub commands are: | ||
activate activate a specific mode (teacher, student, student-exam) | ||
deactivate deactivate all extensions""" | ||
), | ||
) | ||
|
||
parser.add_argument("command", help="Subcommand to run") | ||
class E2xGraderApp(E2xGrader): | ||
|
||
args = parser.parse_args(sys.argv[1:2]) | ||
if not hasattr(self, args.command): | ||
print("Unrecognized command") | ||
parser.print_help() | ||
exit(1) | ||
getattr(self, args.command)() | ||
|
||
def activate(self): | ||
parser = ArgumentParser( | ||
description="Activate different modes", | ||
usage=dedent( | ||
subcommands = dict( | ||
activate=( | ||
ActivateModeApp, | ||
dedent( | ||
"""\ | ||
Activate a specific mode (teacher, student, student_exam) | ||
""" | ||
e2xgrader activate <mode> [--sys-prefix] [--user] | ||
|
||
Available modes are: | ||
teacher activate the grader and all teaching extensions | ||
student activate the student extensions | ||
student_exam activate the student extensions in exam mode""" | ||
), | ||
) | ||
# prefixing the argument with -- means it's optional | ||
parser.add_argument( | ||
"mode", | ||
help="Which mode to activate, can be teacher, student or student-exam", | ||
) | ||
parser.add_argument( | ||
"--sys-prefix", | ||
action="store_true", | ||
help="If the extensions should be installed to sys.prefix", | ||
) | ||
parser.add_argument( | ||
"--user", | ||
action="store_true", | ||
help="If the extensions should be installed to the user space", | ||
) | ||
|
||
args = parser.parse_args(sys.argv[2:]) | ||
if not hasattr(self.extension_manager, f"activate_{args.mode}"): | ||
print("Unrecognized mode") | ||
parser.print_help() | ||
exit(1) | ||
sys_prefix = False | ||
user = False | ||
if args.sys_prefix: | ||
sys_prefix = True | ||
if args.user: | ||
user = True | ||
getattr(self.extension_manager, f"activate_{args.mode}")( | ||
sys_prefix=sys_prefix, user=user | ||
) | ||
|
||
def deactivate(self): | ||
parser = ArgumentParser( | ||
description="Deactivate extensions", | ||
usage=dedent("python -m e2xgrader deactivate [--sys-prefix] [--user]"), | ||
) | ||
# prefixing the argument with -- means it's optional | ||
parser.add_argument( | ||
"--sys-prefix", | ||
action="store_true", | ||
help="If the extensions should be uninstalled from sys.prefix", | ||
) | ||
parser.add_argument( | ||
"--user", | ||
action="store_true", | ||
help="If the extensions should be uninstalled from the user space", | ||
) | ||
|
||
args = parser.parse_args(sys.argv[2:]) | ||
).strip(), | ||
), | ||
deactivate=( | ||
DeactivateModeApp, | ||
dedent( | ||
"""\ | ||
Deactivate all e2xgrader extensions | ||
""" | ||
).strip(), | ||
), | ||
show_mode=( | ||
ShowModeApp, | ||
dedent( | ||
"""\ | ||
Show the currently active mode | ||
""" | ||
).strip(), | ||
), | ||
) | ||
|
||
sys_prefix = False | ||
user = False | ||
if args.sys_prefix: | ||
sys_prefix = True | ||
if args.user: | ||
user = True | ||
self.extension_manager.deactivate(sys_prefix=sys_prefix, user=user) | ||
def start(self) -> None: | ||
super().start() | ||
if self.subapp is None: | ||
print( | ||
"No subcommand given (run with --help for options). List of subcommands:\n" | ||
) | ||
self.print_subcommands() | ||
|
||
|
||
def main(): | ||
Manager() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this method called by some convention or a leftover? |
||
E2xGraderApp.launch_instance() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from .baseapp import E2xGrader | ||
|
||
|
||
class ShowModeApp(E2xGrader): | ||
description = "Show the currently active mode" | ||
|
||
def start(self) -> None: | ||
super().start() | ||
print(f"Current mode: {self.mode}") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from ..extensions import E2xExtensionManager | ||
from ..utils.mode import infer_e2xgrader_mode | ||
from .baseapp import E2xGrader | ||
|
||
|
||
class ToggleModeApp(E2xGrader): | ||
|
||
flags = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isn't that inferred now? |
||
"sys-prefix": ( | ||
{"E2xGrader": {"sys_prefix": True}}, | ||
"Install extensions to sys.prefix", | ||
), | ||
"user": ( | ||
{"E2xGrader": {"user": True}}, | ||
"Install extensions to the user space", | ||
), | ||
} | ||
|
||
def activate_mode(self): | ||
""" | ||
Activates the specified mode by activating the corresponding extensions | ||
using the E2xExtensionManager. | ||
|
||
If the mode is "None", it deactivates all e2xgrader extensions. | ||
""" | ||
extension_manager = E2xExtensionManager() | ||
if self.mode == "None": | ||
print( | ||
f"Deactivating e2xgrader extensions with sys_prefix={self.sys_prefix} " | ||
f"and user={self.user}" | ||
) | ||
extension_manager.deactivate(sys_prefix=self.sys_prefix, user=self.user) | ||
else: | ||
print( | ||
f"Activating mode {self.mode} with sys_prefix={self.sys_prefix} " | ||
f"and user={self.user}" | ||
) | ||
getattr(extension_manager, f"activate_{self.mode}")( | ||
sys_prefix=self.sys_prefix, user=self.user | ||
) | ||
self.log.info(f"Activated mode {self.mode}. ") | ||
try: | ||
mode = infer_e2xgrader_mode() | ||
if mode != self.mode: | ||
self.log.warning( | ||
f"The activated mode {self.mode} does not match the infered mode {mode}. \n" | ||
f"The mode {mode} may be activated on a higher level." | ||
) | ||
except ValueError as e: | ||
self.log.error(str(e)) | ||
|
||
def start(self) -> None: | ||
super().start() | ||
if self.sys_prefix and self.user: | ||
self.fail("Cannot install in both sys-prefix and user space") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import unittest | ||
from unittest.mock import patch | ||
|
||
from jupyter_core.application import NoStart | ||
from traitlets import TraitError | ||
|
||
from e2xgrader.apps.activatemodeapp import ActivateModeApp | ||
|
||
|
||
class TestActivateModeApp(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.app = ActivateModeApp() | ||
self.app.initialize([]) | ||
|
||
def test_fail_without_args(self): | ||
with self.assertRaises(SystemExit): | ||
self.app.initialize([]) | ||
self.app.start() | ||
|
||
def test_fail_with_invalid_mode(self): | ||
with self.assertRaises(TraitError): | ||
self.app.initialize(["invalid_mode"]) | ||
self.app.start() | ||
|
||
@patch("e2xgrader.apps.togglemodeapp.ToggleModeApp.activate_mode") | ||
def test_activate_mode(self, mock_activate_mode): | ||
try: | ||
self.app.initialize(["teacher"]) | ||
self.app.start() | ||
except NoStart: | ||
pass | ||
finally: | ||
self.assertEqual(self.app.mode, "teacher") | ||
mock_activate_mode.assert_called_once() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import unittest | ||
from unittest.mock import patch | ||
|
||
from traitlets import TraitError | ||
|
||
from e2xgrader.apps.baseapp import E2xGrader | ||
|
||
|
||
class TestE2xGrader(unittest.TestCase): | ||
|
||
def setUp(self): | ||
self.app = E2xGrader() | ||
self.app.initialize([]) | ||
|
||
def test_set_invalid_mode(self): | ||
with self.assertRaises(TraitError): | ||
self.app.mode = "invalid_mode" | ||
|
||
def test_non_matching_mode_causes_log_error(self): | ||
with patch( | ||
"e2xgrader.apps.baseapp.infer_e2xgrader_mode" | ||
) as mock_infer_e2xgrader_mode: | ||
mock_infer_e2xgrader_mode.side_effect = ValueError("error") | ||
with patch("e2xgrader.apps.baseapp.E2xGrader.log") as mock_log: | ||
self.app.initialize([]) | ||
mock_log.error.assert_called_once_with("error") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how are these called from where? (maybe update docs?)