Skip to content

Commit

Permalink
Improve error messages and fail if conda/virtualenv paths don't exist
Browse files Browse the repository at this point in the history
  • Loading branch information
rkdarst committed Jan 28, 2025
1 parent 253c927 commit e6b13c4
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 32 deletions.
45 changes: 40 additions & 5 deletions envkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import subprocess
import sys
import tempfile
import textwrap

LOG = logging.getLogger('envkernel')
LOG.setLevel(logging.INFO)
Expand Down Expand Up @@ -242,11 +243,12 @@ def install_kernel(self, kernel, name, user=False, replace=None, prefix=None, lo
user=user, replace=replace, prefix=prefix)

LOG.info("")
LOG.info(" Kernel command: %s", kernel['argv'])
try:
LOG.info(" Kernel saved to {}".format(jupyter_client.kernelspec.KernelSpecManager().get_kernel_spec(name).resource_dir))
LOG.info(" Success: Kernel saved to {}".format(jupyter_client.kernelspec.KernelSpecManager().get_kernel_spec(name).resource_dir))
except jupyter_client.kernelspec.NoSuchKernel:
LOG.info(" Note: Kernel not detected with current search path.")
LOG.info(" Command line: %s", kernel['argv'])
#LOG.info(" Kernel file:\n%s", textwrap.indent(json.dumps(kernel, sort_keys=True, indent=1), ' '))

def run(self):
"""Hook that gets run before kernel invoked"""
Expand Down Expand Up @@ -322,22 +324,48 @@ def setup(self):
args, unknown_args = parser.parse_known_args(self.argv)

kernel = self.get_kernel()
path = args.path
path = os.path.abspath(path)
kernel['argv'] = [
os.path.realpath(sys.argv[0]),
self.__class__.__name__, 'run',
*unknown_args,
args.path,
path,
'--',
*kernel['argv'],
]
if 'display_name' not in kernel:
kernel['display_name'] = "{} ({}, {})".format(
os.path.basename(args.path.strip('/')),
self.__class__.__name__,
args.path)
path)
if args.path != 'TESTTARGET': # un-expanded
if not os.path.exists(path):
print(self.notfound_message%(self.__class__.__name__, path))
LOG.critical("ERROR: %s does not exist: %s", self.__class__.__name__, path)
sys.exit(1)
if not os.path.exists(pjoin(path, 'bin')):
print(self.notfound_message%(self.__class__.__name__, path+'/bin'))
LOG.critical("ERROR: %s bin does not exist: %s/bin", self.__class__.__name__, path)
sys.exit(1)
self.install_kernel(kernel, name=self.name, user=self.user,
replace=self.replace, prefix=self.prefix)

notfound_message = """\
ERROR: %s path does not exist: %s
You need to give a path to the environment, not just the name. (A
relative path gets expanded to an absolute path.)
For conda, you can get this from:
conda env list
If it's a conda environment and it is activated, you can use
$CONDA_PREFIX like such:
envkernel conda [other arguments] $CONDA_PREFIX
"""


def run(self):
"""load modules and run:
Expand Down Expand Up @@ -385,6 +413,12 @@ def _run(self, args, rest):
os.environ['PS1'] = "(venv3) " + os.environ['PS1']

self.execvp(rest[0], rest)
notfound_message = """\
ERROR: %s path does not exist: %s
You need to give a path to the environment, not just the name. A
relative path gets expanded to an absolute path.
"""



Expand Down Expand Up @@ -533,12 +567,13 @@ def setup(self):
LOG.debug('setup: remaining args: %s', unknown_args)

kernel = self.get_kernel()
image = os.path.abspath(args.image)
kernel['argv'] = [
os.path.realpath(sys.argv[0]),
'singularity', 'run',
'--connection-file', '{connection_file}',
#*[ '--mount={}'.format(x) for x in args.mount],
args.image,
image,
*unknown_args,
'--',
*kernel['argv'],
Expand Down
54 changes: 27 additions & 27 deletions test_envkernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,21 @@ def is_sublist(list_, sublist):

@all_modes()
def test_basic(d, mode):
kern = install(d, "%s MOD1"%mode)
kern = install(d, "%s TESTTARGET"%mode)
#assert kern['argv'][0] == 'envkernel' # defined above
assert kern['ek'][1:3] == [mode, 'run']

@all_modes()
def test_display_name(d, mode):
kern = install(d, "%s --display-name=AAA MOD1"%mode)
kern = install(d, "%s --display-name=AAA TESTTARGET"%mode)
assert kern['kernel']['display_name'] == 'AAA'

@all_modes(['conda'])
def test_template(d, mode):
os.environ['JUPYTER_PATH'] = pjoin(d, 'share/jupyter')
subprocess.call("python -m ipykernel install --name=aaa-ipy --display-name=BBB --prefix=%s"%d, shell=True)
#os.environ['ENVKERNEL_TESTPATH'] = os.path.join(d, 'share/jupyter/kernels')
kern = install(d, "%s --kernel-template aaa-ipy MOD1"%mode)
kern = install(d, "%s --kernel-template aaa-ipy TESTTARGET"%mode)
assert kern['kernel']['display_name'] == 'BBB'

@all_modes(['conda'])
Expand All @@ -118,7 +118,7 @@ def test_template_copyfiles(d, mode):
f = open(pjoin(d, 'share/jupyter/kernels/', 'aaa-ipy', 'A.txt'), 'w')
f.write('LMNO')
f.close()
kern = install(d, "%s --kernel-template aaa-ipy MOD1"%mode)
kern = install(d, "%s --kernel-template aaa-ipy TESTTARGET"%mode)
assert os.path.exists(pjoin(kern['dir'], 'A.txt'))
assert open(pjoin(kern['dir'], 'A.txt')).read() == 'LMNO'

Expand All @@ -127,10 +127,10 @@ def test_template_make_path_relative(d, mode):
os.environ['JUPYTER_PATH'] = pjoin(d, 'share/jupyter')
subprocess.call("python -m ipykernel install --name=aaa-ipy --display-name=BBB --prefix=%s"%d, shell=True)
# First test it without, ensure it has the full path
kern = install(d, "%s --kernel-template aaa-ipy MOD1"%mode)
kern = install(d, "%s --kernel-template aaa-ipy TESTTARGET"%mode)
assert kern['k'][0] != 'python' # This is an absolete path
# Now test it with --kernel-make-path-relative and ensure it's relative
kern = install(d, "%s --kernel-template aaa-ipy --kernel-make-path-relative MOD1"%mode)
kern = install(d, "%s --kernel-template aaa-ipy --kernel-make-path-relative TESTTARGET"%mode)
assert kern['k'][0] == 'python' # This is an absolete path

def test_help():
Expand All @@ -148,7 +148,7 @@ def test_logging(d, caplog):
Run first without -v and make sure some stuff isn't printed.
Then, run with -v and ensure that the argument processing is output.
"""
cmd = "python3 -m envkernel lmod --name=ABC --display-name=AAA MOD1 --prefix=%s"%d
cmd = "python3 -m envkernel lmod --name=ABC --display-name=AAA LMOD --prefix=%s"%d
print(d)
env = os.environ.copy()
env['JUPYTER_PATH'] = pjoin(d, 'share/jupyter')
Expand All @@ -160,7 +160,7 @@ def test_logging(d, caplog):
print(stdout)
assert 'Namespace' not in stdout
assert 'kernel-specific' not in stdout
assert 'Command line:' in stdout
assert 'Kernel command:' in stdout
# Now test verbose (should have some debugging info)
p = subprocess.Popen(cmd+' -v', env=env,
shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Expand All @@ -169,37 +169,37 @@ def test_logging(d, caplog):
#print(stdout)
assert 'Namespace' in stdout
assert 'kernel-specific' in stdout
assert 'Command line:' in stdout
assert 'Kernel command:' in stdout

def test_umask(d):
orig_umask = os.umask(0)
# Test multiple umasks, and kerneldir + kernel.json
for umask in [0, 0o022]:
os.umask(umask)
kern = install(d, "lmod MOD1")
kern = install(d, "lmod TESTTARGET")
assert os.stat(kern['dir']).st_mode & 0o777 == 0o777&(~umask)
assert os.stat(pjoin(kern['dir'], 'kernel.json')).st_mode & 0o777 == 0o666&(~umask)
os.umask(orig_umask)


@all_modes()
def test_set_python(d, mode):
kern = install(d, "%s --python=AAA MOD1"%mode)
kern = install(d, "%s --python=AAA TESTTARGET"%mode)
assert kern['k'][0] == 'AAA'

@all_modes()
def test_set_kernel_cmd(d, mode):
kern = install(d, "%s --kernel-cmd='a b c d' MOD1"%mode)
kern = install(d, "%s --kernel-cmd='a b c d' TESTTARGET"%mode)
assert kern['k'] == ['a', 'b', 'c', 'd']

@all_modes()
def test_language(d, mode):
kern = install(d, "%s --language=AAA MOD1"%mode)
kern = install(d, "%s --language=AAA TESTTARGET"%mode)
assert kern['kernel']['language'] == 'AAA'

@all_modes()
def test_env(d, mode):
kern = install(d, "%s --env=AAA=BBB --env=CCC=DDD MOD1"%mode)
kern = install(d, "%s --env=AAA=BBB --env=CCC=DDD TESTTARGET"%mode)
assert kern['kernel']['env']['AAA'] == 'BBB'
assert kern['kernel']['env']['CCC'] == 'DDD'

Expand Down Expand Up @@ -234,28 +234,28 @@ def test_exec(_file, _args):
@all_modes()
def test_default_language(d, mode):
# default language is ipykernel
kern = install(d, "%s MOD1"%mode)
kern = install(d, "%s TESTTARGET"%mode)
assert kern['kernel']['language'] == 'python'
assert kern['k'][0] == 'python'
assert kern['k'][1:4] == ['-m', 'ipykernel_launcher', '-f']

@all_modes()
def test_ipykernel(d, mode):
kern = install(d, "%s --kernel=ipykernel MOD1"%mode)
kern = install(d, "%s --kernel=ipykernel TESTTARGET"%mode)
assert kern['kernel']['language'] == 'python'
assert kern['k'][0] == 'python'
assert kern['k'][1:4] == ['-m', 'ipykernel_launcher', '-f']

@all_modes()
def test_ir(d, mode):
kern = install(d, "%s --kernel=ir MOD1"%mode)
kern = install(d, "%s --kernel=ir TESTTARGET"%mode)
assert kern['kernel']['language'] == 'R'
assert kern['k'][0] == 'R'
assert kern['k'][1:5] == ['--slave', '-e', 'IRkernel::main()', '--args']

@all_modes()
def test_imatlab(d, mode):
kern = install(d, "%s --kernel=imatlab MOD1"%mode)
kern = install(d, "%s --kernel=imatlab TESTTARGET"%mode)
assert kern['kernel']['language'] == 'matlab'
assert kern['k'][0].endswith('python')
assert kern['k'][1:4] == ['-m', 'imatlab', '-f']
Expand All @@ -277,29 +277,29 @@ def test_lmod_purge(d):
assert kern['ek'][-1] == 'MOD3'

def test_conda(d):
kern = install(d, "conda /PATH/BBB")
kern = install(d, "conda TESTTARGET")
#assert kern['argv'][0] == 'envkernel' # defined above
assert kern['ek'][1:3] == ['conda', 'run']
assert kern['ek'][-1] == '/PATH/BBB'
assert kern['ek'][-1].endswith('TESTTARGET')

def test_virtualenv(d):
kern = install(d, "virtualenv /PATH/CCC")
kern = install(d, "virtualenv TESTTARGET")
#assert kern['argv'][0] == 'envkernel' # defined above
assert kern['ek'][1:3] == ['virtualenv', 'run']
assert kern['ek'][-1] == '/PATH/CCC'
assert kern['ek'][-1].endswith('TESTTARGET')

def test_docker(d):
kern = install(d, "docker --some-arg=AAA IMAGE1")
kern = install(d, "docker --some-arg=AAA TESTIMAGE")
#assert kern['argv'][0] == 'envkernel' # defined above
assert kern['ek'][1:3] == ['docker', 'run']
assert kern['ek'][-2] == 'IMAGE1'
assert kern['ek'][-2] == 'TESTIMAGE'
assert '--some-arg=AAA' in kern['ek']

def test_singularity(d):
kern = install(d, "singularity --some-arg=AAA /PATH/TO/IMAGE2")
kern = install(d, "singularity --some-arg=AAA /PATH/TO/TESTIMAGE2")
#assert kern['argv'][0] == 'envkernel' # defined above
assert kern['ek'][1:3] == ['singularity', 'run']
assert kern['ek'][-2] == '/PATH/TO/IMAGE2'
assert kern['ek'][-2] == '/PATH/TO/TESTIMAGE2'
assert '--some-arg=AAA' in kern['ek']


Expand Down Expand Up @@ -369,7 +369,7 @@ def test_run_singularity(d):
def test_exec(_file, argv):
assert argv[0] == 'singularity'
assert '--some-arg=AAA' in argv
assert 'IMAGE' in argv
assert os.path.join(os.getcwd(), 'IMAGE') in argv
kern = install(d, "singularity --some-arg=AAA IMAGE")
run(d, kern, test_exec)

Expand Down

0 comments on commit e6b13c4

Please sign in to comment.