Skip to content

Commit

Permalink
Merge pull request #7583 from telamonian/add-app-clean-flags
Browse files Browse the repository at this point in the history
Fixes #6734: --extensions, --settings, --static, --all flags for `jupyter lab clean`
  • Loading branch information
blink1073 committed Dec 5, 2019
2 parents 8dc5877 + 8cc3dab commit 5e68c8a
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 14 deletions.
49 changes: 42 additions & 7 deletions jupyterlab/commands.py
Expand Up @@ -346,6 +346,8 @@ def _default_registry(self):

def _ensure_options(options, **kwargs):
"""Helper to use deprecated kwargs for AppOption"""
optClass = options.__class__ if issubclass(options.__class__, AppOptions) else AppOptions

# Filter out default-value kwargs
kwargs = dict(filter(lambda item: item[1] is not None, kwargs.items()))
# Warn for deprecated kwargs usage
Expand All @@ -355,11 +357,11 @@ def _ensure_options(options, **kwargs):
"deprecated, use the options argument instead: %r" % (kwargs,),
DeprecationWarning)
if options is None:
return AppOptions(**kwargs)
return optClass(**kwargs)
# Also support mixed use of options and kwargs:
opt_args = {name: getattr(options, name) for name in options.trait_names()}
kwargs.update(**opt_args)
return AppOptions(**kwargs)
return optClass(**kwargs)


def watch(app_dir=None, logger=None, core_config=None, app_options=None):
Expand Down Expand Up @@ -434,16 +436,31 @@ def clean(app_dir=None, logger=None, app_options=None):
handler = _AppHandler(app_options)
logger = app_options.logger
app_dir = app_options.app_dir

logger.info('Cleaning %s...', app_dir)
if app_dir == pjoin(HERE, 'dev'):
raise ValueError('Cannot clean the dev app')
if app_dir == pjoin(HERE, 'core'):
raise ValueError('Cannot clean the core app')
for name in ['staging']:
target = pjoin(app_dir, name)
if osp.exists(target):
_rmtree(target, logger)

if app_options.all:
logger.info('Removing everything in %s...', app_dir)
_rmtree_star(app_dir, logger)
else:
possibleTargets = ['extensions', 'settings', 'staging', 'static']
targets = [t for t in possibleTargets if getattr(app_options, t)]

for name in targets:
target = pjoin(app_dir, name)
if osp.exists(target):
logger.info('Removing %s...', name)
_rmtree(target, logger)
else:
logger.info('%s not present, skipping...', name)

logger.info('Success!')
if app_options.all or app_options.extensions:
logger.info('All of your extensions have been removed, and will need to be reinstalled')


def build(app_dir=None, name=None, version=None, static_url=None,
Expand Down Expand Up @@ -1809,11 +1826,29 @@ def _normalize_path(extension):
def _rmtree(path, logger):
"""Remove a tree, logging errors"""
def onerror(*exc_info):
logger.debug('Error in rmtree', exc_info=exc_info)
logger.debug('Error in shutil.rmtree', exc_info=exc_info)

shutil.rmtree(path, onerror=onerror)


def _unlink(path, logger):
"""Remove a file, logging errors"""
try:
os.unlink(path)
except Exception:
logger.debug('Error in os.unlink', exc_info=sys.exc_info())


def _rmtree_star(path, logger):
"""Remove all files/trees within a dir, logging errors"""
for filename in os.listdir(path):
file_path = os.path.join(path, filename)
if os.path.isfile(file_path) or os.path.islink(file_path):
_unlink(file_path, logger)
elif os.path.isdir(file_path):
_rmtree(file_path, logger)


def _validate_extension(data):
"""Detect if a package is an extension using its metadata.
Expand Down
50 changes: 43 additions & 7 deletions jupyterlab/labapp.py
Expand Up @@ -10,7 +10,7 @@
from os.path import join as pjoin
import sys

from jupyter_core.application import JupyterApp, base_aliases
from jupyter_core.application import JupyterApp, base_aliases, base_flags
from jupyterlab_server import slugify, WORKSPACE_EXTENSION
from notebook.notebookapp import NotebookApp, aliases, flags
from notebook.utils import url_path_join as ujoin
Expand Down Expand Up @@ -101,26 +101,63 @@ def start(self):
clean_aliases = dict(base_aliases)
clean_aliases['app-dir'] = 'LabCleanApp.app_dir'

ext_warn_msg = "WARNING: this will delete all of your extensions, which will need to be reinstalled"

clean_flags = dict(base_flags)
clean_flags['extensions'] = ({'LabCleanApp': {'extensions': True}},
'Also delete <app-dir>/extensions.\n%s' % ext_warn_msg)
clean_flags['settings'] = ({'LabCleanApp': {'settings': True}}, 'Also delete <app-dir>/settings')
clean_flags['static'] = ({'LabCleanApp': {'static': True}}, 'Also delete <app-dir>/static')
clean_flags['all'] = ({'LabCleanApp': {'all': True}},
'Delete the entire contents of the app directory.\n%s' % ext_warn_msg)


class LabCleanAppOptions(AppOptions):
extensions = Bool(False)
settings = Bool(False)
staging = Bool(True)
static = Bool(False)
all = Bool(False)


class LabCleanApp(JupyterApp):
version = version
description = """
Clean the JupyterLab application
This will clean the app directory by removing the `staging` and `static`
directories.
This will clean the app directory by removing the `staging` directories.
Optionally, the `extensions`, `settings`, and/or `static` directories,
or the entire contents of the app directory, can also be removed.
"""
aliases = clean_aliases
flags = clean_flags

# Not configurable!
core_config = Instance(CoreConfig, allow_none=True)

app_dir = Unicode('', config=True, help='The app directory to clean')

extensions = Bool(False, config=True,
help="Also delete <app-dir>/extensions.\n%s" % ext_warn_msg)

settings = Bool(False, config=True, help="Also delete <app-dir>/settings")

static = Bool(False, config=True, help="Also delete <app-dir>/static")

all = Bool(False, config=True,
help="Delete the entire contents of the app directory.\n%s" % ext_warn_msg)

def start(self):
clean(app_options=AppOptions(
app_dir=self.app_dir, logger=self.log,
core_config=self.core_config))
app_options = LabCleanAppOptions(
logger=self.log,
core_config=self.core_config,
app_dir=self.app_dir,
extensions=self.extensions,
settings=self.settings,
static=self.static,
all=self.all
)
clean(app_options=app_options)


class LabPathApp(JupyterApp):
Expand All @@ -137,7 +174,6 @@ class LabPathApp(JupyterApp):
environment variable or it will fall back to
'/lab/workspaces' in the default Jupyter configuration directory.
"""

def start(self):
print('Application directory: %s' % get_app_dir())
print('User Settings directory: %s' % get_user_settings_dir())
Expand Down
7 changes: 7 additions & 0 deletions scripts/ci_script.sh
Expand Up @@ -230,6 +230,13 @@ if [[ $GROUP == usage ]]; then
sleep 5
kill $TASK_PID
wait $TASK_PID

# Make sure we can clean various bits of the app dir
jupyter lab clean
jupyter lab clean --extensions
jupyter lab clean --settings
jupyter lab clean --static
jupyter lab clean --all
fi

if [[ $GROUP == nonode ]]; then
Expand Down

0 comments on commit 5e68c8a

Please sign in to comment.