-
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
Merged
+870
−183
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
628c27e
Add E2xGrader baseapp
tmetzl cb7a17d
Add app to toggle e2xgrader modes
tmetzl 0fb7ae4
Add app to activate e2xgrader modes
tmetzl 07573dc
Add app to deactivate e2xgrader extensions
tmetzl 29e755d
Add app to show the current e2xgrader mode
tmetzl 317ef0f
Add e2xgrader app
tmetzl 10b0fa6
Add __init__.py
tmetzl 72ebadd
Add test for get_jupyter_config_path function
tmetzl 79cf4e5
Update package-lock.json
tmetzl faba50c
Add test for baseapp
tmetzl c706890
Add test for e2xgraderapp
tmetzl a5902bc
Add test for togglemodeapp
tmetzl ff11d1a
Add test for deactivatemodeapp
tmetzl 8ed48e7
Add test for activatemodeapp
tmetzl 12a7840
Add test for showmodeapp
tmetzl 5c7b306
Use get_nbgrader_config from e2xcore
tmetzl ee64313
Remove get_nbgrader_config function
tmetzl e18c602
Add function get_e2xgrader_mode
tmetzl eb6d549
Add test for get_e2xgrader_mode
tmetzl 95410c9
Add function to get BaseJSONConfigManager
tmetzl 869f60d
Infer the mode from extensions instead of from config
tmetzl a243550
Add tests for new mode utils
tmetzl 7f77019
Extend tests for extension utils
tmetzl 8f2cf8b
Use infer_e2xgrader_mode instead of config file in baseapp
tmetzl 6a3ed6e
Do not write config_file in togglemodeapp
tmetzl 323e257
Adapt tests for baseapp
tmetzl 63bd6d5
Remove test for deleted function get_jupyter_config_path
tmetzl 28a8ffa
Adapt test for togglemodeapp
tmetzl fc01a70
Use get_notebook_config_manager in test_e2xmanager
tmetzl 0f79c8a
Add docs for e2xgrader show_mode
tmetzl 3cc7365
Move sys-prefix and user to togglemodeapp
tmetzl cd716b8
Add enum for e2xgrader modes
tmetzl 6f45ea6
Adapt tests for mode
tmetzl 11af8fe
Use e2xgrader mode enum in baseapp
tmetzl 76c98f9
Use e2xgrader mode enum in apps
tmetzl 927d83c
Adapt tests for apps
tmetzl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from ..utils.mode import E2xGraderMode | ||
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") | ||
if self.extra_args[0] not in [ | ||
E2xGraderMode.TEACHER.value, | ||
E2xGraderMode.STUDENT.value, | ||
E2xGraderMode.STUDENT_EXAM.value, | ||
]: | ||
self.fail( | ||
f"Mode {self.extra_args[0]} is not a valid mode that can be activated." | ||
) | ||
self.mode = self.extra_args[0] | ||
|
||
self.activate_mode() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from textwrap import dedent | ||
|
||
from jupyter_core.application import JupyterApp | ||
from traitlets import Enum | ||
|
||
from ..utils.mode import E2xGraderMode, infer_e2xgrader_mode | ||
|
||
|
||
class E2xGrader(JupyterApp): | ||
|
||
mode = Enum( | ||
values=[mode.value for mode in E2xGraderMode], | ||
default_value=E2xGraderMode.INACTIVE.value, | ||
help=dedent( | ||
""" | ||
Which mode is activated, can be teacher, student, student_exam or deactivated. | ||
Is set to invalid if the mode cannot be inferred. | ||
""" | ||
), | ||
) | ||
|
||
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)) | ||
self.mode = E2xGraderMode.INVALID.value | ||
super().initialize(argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from ..utils.mode import E2xGraderMode | ||
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 = E2xGraderMode.INACTIVE.value | ||
self.activate_mode() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
from traitlets import Bool | ||
|
||
from ..extensions import E2xExtensionManager | ||
from ..utils.mode import E2xGraderMode, infer_e2xgrader_mode | ||
from .baseapp import E2xGrader | ||
|
||
|
||
class ToggleModeApp(E2xGrader): | ||
|
||
sys_prefix = Bool(False, help="Install extensions to sys.prefix", config=True) | ||
|
||
user = Bool(False, help="Install extensions to the user space", config=True) | ||
|
||
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": ( | ||
{"ToggleModeApp": {"sys_prefix": True}}, | ||
"Install extensions to sys.prefix", | ||
), | ||
"user": ( | ||
{"ToggleModeApp": {"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 == E2xGraderMode.INACTIVE.value: | ||
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)) | ||
self.mode = E2xGraderMode.INVALID.value | ||
|
||
def start(self) -> None: | ||
super().start() | ||
if self.sys_prefix and self.user: | ||
self.fail("Cannot install in both sys-prefix and user space") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 e2xgrader.apps.activatemodeapp import ActivateModeApp | ||
from e2xgrader.utils.mode import E2xGraderMode | ||
|
||
|
||
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(SystemExit): | ||
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([E2xGraderMode.TEACHER.value]) | ||
self.app.start() | ||
except NoStart: | ||
pass | ||
finally: | ||
self.assertEqual(self.app.mode, E2xGraderMode.TEACHER.value) | ||
mock_activate_mode.assert_called_once() |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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?)