Skip to content

Commit

Permalink
Merge pull request #168 from Carreau/activate
Browse files Browse the repository at this point in the history
[Work in progress] Command install activate nbgrader extension.
  • Loading branch information
jhamrick committed Mar 26, 2015
2 parents a7e54f7 + c37106b commit 6c862b2
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 16 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ install:
- pip install -r requirements.txt
- pip install nose coverage coveralls invoke selenium
- if [[ $GROUP == 'docs' ]]; then python setup.py install; else pip install -e .; fi
- cp nbextensions/* $(ipython locate)/nbextensions/
script:
- invoke tests --group="${GROUP}" --python-version="${TRAVIS_PYTHON_VERSION}" --pull-request="${TRAVIS_PULL_REQUEST}" --github-token="${GH_TOKEN}" --git-name="${GIT_NAME}" --git-email="${GIT_EMAIL}"
after_success:
Expand Down
2 changes: 2 additions & 0 deletions nbgrader/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from nbgrader.install import main
main()
181 changes: 181 additions & 0 deletions nbgrader/install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
from __future__ import print_function, absolute_import

"""
Misc utils to deals wit installing nbgrader on a system
"""

import sys
import io
import os.path
import json
import argparse

import IPython.html.nbextensions as nbe
from IPython.utils.path import locate_profile, get_ipython_dir
from IPython.core.profiledir import ProfileDir
from IPython.utils.py3compat import cast_unicode_py2


def _get_profile_dir(profile, ipython_dir):
if not ipython_dir:
pdir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile).location
else:
ipython_dir = os.path.expanduser(ipython_dir)
pdir = ProfileDir.find_profile_dir_by_name(ipython_dir, profile).location
return pdir

def install(profile='default', symlink=True, user=False, prefix=None,
verbose=False, ipython_dir=None):
"""Install and activate nbgrader on a profile
"""

dname = os.path.dirname(__file__)
# might want to check if already installed and overwrite if exist
if symlink and verbose:
print('I will try to symlink the extension instead of copying files')
if prefix and verbose:
print("I will install in prefix:", prefix)
if not ipython_dir:
nbextensions_dir = None
else:
nbextensions_dir = os.path.join(ipython_dir,'nbextensions')

nbe.install_nbextension(os.path.join(dname, 'nbextensions', 'nbgrader'),
user=user,
prefix=prefix,
symlink=symlink,
nbextensions_dir=nbextensions_dir
)


# TODO pass prefix as argument.
def activate(profile=None, ipython_dir=None):
"""
Manually modify the frontend json-config to load nbgrader extension.
"""

pdir = _get_profile_dir(profile, ipython_dir)
json_dir = os.path.join(pdir, 'nbconfig')
json_file = os.path.expanduser(os.path.join(json_dir, 'notebook.json'))

try:
with io.open(json_file, 'r') as f:
config = json.loads(f.read())
except IOError:
# file doesn't exist yet. IPython might have never been launched.
config = {}

if not config.get('load_extensions', None):
config['load_extensions'] = {}
config['load_extensions']['nbgrader/create_assignment'] = True

if not os.path.exists(json_dir):
os.mkdir(json_dir)

with io.open(json_file, 'w+') as f:
f.write(cast_unicode_py2(json.dumps(config, indent=2), 'utf-8'))


def deactivate(profile=None, ipython_dir=None):
"""
Manually modify the frontend json-config to load nbgrader extension.
"""

pdir = _get_profile_dir(profile, ipython_dir)
json_dir = os.path.join(pdir, 'nbconfig')
json_file = os.path.expanduser(os.path.join(json_dir, 'notebook.json'))

try:
with io.open(json_file, 'r') as f:
config = json.loads(f.read())
except IOError:
# file doesn't exist yet. IPython might have never been launched.
return

if 'load_extensions' not in config:
return
if 'nbgrader/create_assignment' not in config['load_extensions']:
return

# deactivation require the delete the key.
del config['load_extensions']['nbgrader/create_assignment']

# prune if last extension.
if config['load_extensions']:
del config['load_extensions']

with io.open(json_file, 'w+') as f:
f.write(cast_unicode_py2(json.dumps(config, indent=2), 'utf-8'))


def main(argv=None):
"""Parse sys argv.args and install nbgrader extensions"""

# this is what argparse does if set to None,
# and allows us to test this function with nose.
if not argv:
argv = sys.argv[1:]

prog = '{} -m nbgrader'.format(os.path.basename(sys.executable))
parser = argparse.ArgumentParser(prog=prog,
description='''Install and activate nbgrader notebook extension for a given profile ''')
parser.add_argument('profile', nargs='?', default=None, metavar=('<profile_name>'))

parser.add_argument("--install", help="Install nbgrader notebook extension for given profile",
action="store_true")

parser.add_argument("--activate", help="Activate nbgrader notebook extension for given profile",
action="store_true")

parser.add_argument("--deactivate", help="Deactivate nbgrader extension for given profile",
action="store_true")

parser.add_argument("-v", "--verbose", help="Increase verbosity",
action='store_true')

parser.add_argument("--user", help="Force install in user land",
action="store_true")

parser.add_argument("--no-symlink", help="Do not symlink at install time, but copy the files",
action="store_false", dest='symlink', default=True)

parser.add_argument("--path", help="Explicit path to the ipython-dir to use",
action='store', default=None)

parser.add_argument("--prefix", help="Prefix where to install extension",
action='store', default=None)

args = parser.parse_args(argv)

help_and_exit = False
if args.activate and args.deactivate:
print("Cannot activate and deactivate extension as the same time", file=sys.stderr)
help_and_exit = True

if args.user and not args.install:
print("--user can only be used in conjunction with the --install flag.")
help_and_exit = True

if not args.symlink and not args.install:
print("--no-symlink can only be used in conjunction with the --install flag.")
help_and_exit = True

if not args.profile or help_and_exit:
parser.print_help()
sys.exit(1)

if args.install:
install( ipython_dir=args.path,
user=args.user,
prefix=args.prefix,
profile=args.profile,
symlink=args.symlink,
verbose=args.verbose,
)

if args.activate :
activate( ipython_dir=args.path,
profile=args.profile)
if args.deactivate :
deactivate( ipython_dir=args.path,
profile=args.profile)
File renamed without changes.
File renamed without changes.
91 changes: 79 additions & 12 deletions nbgrader/tests/test_nbextension.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
import tempfile
import os
import shutil
import json
from copy import copy

from nose.tools import assert_equal
from nose.tools import assert_equal, assert_raises

from selenium import webdriver
from selenium.webdriver.common.by import By
Expand All @@ -12,6 +14,19 @@
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys

from nbgrader.install import main

def _assert_is_deactivated(config_file):
with open(config_file, 'r') as fh:
config = json.load(fh)
assert_raises(KeyError, lambda:config['load_extensions']['nbgrader/create_assignment'])

def _assert_is_activated(config_file):
with open(config_file, 'r') as fh:
config = json.load(fh)
assert config['load_extensions']['nbgrader/create_assignment']


class TestCreateAssignmentNbExtension(object):

@classmethod
Expand All @@ -21,11 +36,19 @@ def setup_class(cls):
cls.origdir = os.getcwd()
os.chdir(cls.tempdir)

# ensure IPython dir exists.
sp.call(['ipython', 'profile', 'create', '--ipython-dir', cls.ipythondir])

# bug in IPython cannot use --profile-dir
# that does not set it for everything.
# still this does not allow to have things that work.
env = copy(os.environ)
env['IPYTHONDIR'] = cls.ipythondir

cls.nbserver = sp.Popen([
"ipython", "notebook",
"--ipython-dir", cls.ipythondir,
"--no-browser",
"--port", "9000"], stdout=sp.PIPE, stderr=sp.STDOUT)
"--port", "9000"], stdout=sp.PIPE, stderr=sp.STDOUT, env=env)

@classmethod
def teardown_class(cls):
Expand All @@ -43,15 +66,11 @@ def setup(self):
def teardown(self):
self.browser.quit()

def _load_extension(self):
def _activate_toolbar(self, name="Create Assignment"):
# wait for the celltoolbar menu to appear
WebDriverWait(self.browser, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#ctb_select')))

# load the nbextension
self.browser.execute_script("IPython.load_extensions('nbgrader')")

def _activate_toolbar(self, name="Create Assignment"):
# activate the Create Assignment toolbar
element = self.browser.find_element_by_css_selector("#ctb_select")
select = Select(element)
Expand All @@ -66,7 +85,7 @@ def _click_solution(self):
"""
)

def _click_grade(self):
def _click_grade(self):
self.browser.execute_script(
"""
var cell = IPython.notebook.get_cell(0);
Expand Down Expand Up @@ -103,8 +122,58 @@ def _get_metadata(self):
"""
)

def test_00_install_extension(self):
main([
'--install',
'--activate',
'--verbose',
'--no-symlink',
'--path={}'.format(self.ipythondir),
'default'
])

# check the extension file were copied
nbextension_dir = os.path.join(self.ipythondir, "nbextensions", "nbgrader")
assert os.path.isfile(os.path.join(nbextension_dir, "create_assignment.js"))
assert os.path.isfile(os.path.join(nbextension_dir, "nbgrader.css"))

# check that it is activated
config_file = os.path.join(self.ipythondir, 'profile_default', 'nbconfig', 'notebook.json')
_assert_is_activated(config_file)


def test_01_deactivate_extension(self):
# check that it is activated
config_file = os.path.join(self.ipythondir, 'profile_default', 'nbconfig', 'notebook.json')
_assert_is_activated(config_file)

main([
'--deactivate',
'--verbose',
'--path={}'.format(self.ipythondir),
'default'
])

# check that it is deactivated
_assert_is_deactivated(config_file)

def test_02_activate_extension(self):
# check that it is deactivated
config_file = os.path.join(self.ipythondir, 'profile_default', 'nbconfig', 'notebook.json')
_assert_is_deactivated(config_file)

main([
'--activate',
'--verbose',
'--path={}'.format(self.ipythondir),
'default'
])

# check that it is activated
_assert_is_activated(config_file)


def test_create_assignment(self):
self._load_extension()
self._activate_toolbar()

# make sure the toolbar appeared
Expand Down Expand Up @@ -146,7 +215,6 @@ def test_create_assignment(self):
assert not self._get_metadata()['grade']

def test_grade_cell_css(self):
self._load_extension()
self._activate_toolbar()

# click the "grade?" checkbox
Expand Down Expand Up @@ -180,7 +248,6 @@ def test_grade_cell_css(self):
assert_equal(len(elements), 0)

def test_tabbing(self):
self._load_extension()
self._activate_toolbar()

# click the "grade?" checkbox
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
'nbgrader.exporters'
],
package_data={
'': [
'nbextensions/*.js',
'nbextensions/*.css'
'nbgrader': [
'nbextensions/nbgrader/*.js',
'nbextensions/nbgrader/*.css'
],
'nbgrader.html': static_files,
'nbgrader.tests': [
Expand Down

0 comments on commit 6c862b2

Please sign in to comment.