Skip to content

Commit 19f1fdd

Browse files
authored
Merge pull request #29 from janschulz/new_activate
Updates so that env loading actually works
2 parents 3b0e7d3 + 7d0dc12 commit 19f1fdd

File tree

6 files changed

+92
-116
lines changed

6 files changed

+92
-116
lines changed

environment_kernels/core.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os.path
66

77
from jupyter_client.kernelspec import (KernelSpecManager, NoSuchKernel)
8-
from traitlets import List, Unicode, Bool
8+
from traitlets import List, Unicode, Bool, Int
99

1010
from .envs_conda import get_conda_env_data
1111
from .envs_virtualenv import get_virtualenv_env_data
@@ -33,8 +33,7 @@ class EnvironmentKernelSpecManager(KernelSpecManager):
3333

3434
# Check for the CONDA_ENV_PATH variable and add it to the list if set.
3535
if os.environ.get('CONDA_ENV_PATH', False):
36-
_default_conda_dirs.append(os.environ['CONDA_ENV_PATH'].split('envs')[
37-
0])
36+
_default_conda_dirs.append(os.environ['CONDA_ENV_PATH'].split('envs')[0])
3837

3938
# If we are running inside the root conda env can get all the env dirs:
4039
if HAVE_CONDA:
@@ -95,13 +94,34 @@ class EnvironmentKernelSpecManager(KernelSpecManager):
9594
config=True,
9695
help="Probe for conda environments by calling conda itself. Only relevant if find_conda_envs is True.")
9796

97+
refresh_interval = Int(
98+
3,
99+
config=True,
100+
help="Interval (in minutes) to refresh the list of environment kernels. Setting it to '0' disables the refresh.")
101+
98102
find_virtualenv_envs = Bool(True,
99103
config=True,
100104
help="Probe for virtualenv environments.")
101105

102106
def __init__(self, *args, **kwargs):
103107
super(EnvironmentKernelSpecManager, self).__init__(*args, **kwargs)
104108
self.log.info("Using EnvironmentKernelSpecManager...")
109+
if self.refresh_interval > 0:
110+
try:
111+
from tornado.ioloop import PeriodicCallback, IOLoop
112+
# Initial loading NOW
113+
IOLoop.current().call_later(0, callback=self._update_env_data)
114+
# Later updates
115+
updater = PeriodicCallback(callback=self._update_env_data, callback_time=1000 * 60 * self.refresh_interval)
116+
updater.start()
117+
if not updater.is_running():
118+
raise Exception()
119+
self._periodic_updater = updater
120+
self.log.info("Started periodic updates of the kernel list (every %s minutes).", self.refresh_interval)
121+
except:
122+
self.log.exception("Error while trying to enable periodic updates of the kernel list.")
123+
else:
124+
self.log.info("Periodical updates the kernel list are DISABLED.")
105125

106126
def validate_env(self, envname):
107127
"""
@@ -121,6 +141,10 @@ def validate_env(self, envname):
121141
else:
122142
return True
123143

144+
def _update_env_data(self):
145+
self.log.info("Starting periodic scan of virtual environments...")
146+
self._get_env_data(reload=True)
147+
self.log.debug("done...")
124148

125149
def _get_env_data(self, reload=False):
126150
"""Get the data about the available environments.
@@ -129,7 +153,7 @@ def _get_env_data(self, reload=False):
129153
"""
130154

131155
# This is called much too often and finding-process is really expensive :-(
132-
if hasattr(self, "_env_data_cache"):
156+
if not reload and hasattr(self, "_env_data_cache"):
133157
return getattr(self, "_env_data_cache")
134158

135159
env_data = {}
@@ -138,12 +162,11 @@ def _get_env_data(self, reload=False):
138162

139163
env_data = {name: env_data[name] for name in env_data if self.validate_env(name)}
140164
self.log.info("Found the following kernels in environments: %s",
141-
", ".join(list(env_data)))
165+
", ".join(list(env_data)))
142166

143167
self._env_data_cache = env_data
144168
return env_data
145169

146-
147170
def find_kernel_specs_for_envs(self):
148171
"""Returns a dict mapping kernel names to resource directories."""
149172
data = self._get_env_data()

environment_kernels/env_kernelspec.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# coding: utf-8
2+
"""Common function to deal with virtual environments"""
3+
from __future__ import absolute_import
4+
5+
from jupyter_client.kernelspec import KernelSpec
6+
from traitlets import default
7+
8+
_nothing = object()
9+
10+
class EnvironmentLoadingKernelSpec(KernelSpec):
11+
"""A KernelSpec which loads `env` by activating the virtual environment"""
12+
13+
_loader = None
14+
_env = _nothing
15+
16+
@property
17+
def env(self):
18+
if self._env is _nothing:
19+
if self._loader:
20+
try:
21+
self._env = self._loader()
22+
except:
23+
self._env = {}
24+
return self._env
25+
26+
def __init__(self, loader, **kwargs):
27+
self._loader = loader
28+
super(EnvironmentLoadingKernelSpec, self).__init__(**kwargs)
29+
30+
31+
def to_dict(self):
32+
d = dict(argv=self.argv,
33+
# Do not trigger the loading
34+
#env=self.env,
35+
display_name=self.display_name,
36+
language=self.language,
37+
)
38+
39+
return d
40+

environment_kernels/envs_common.py

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
import os
77
import glob
88

9-
from jupyter_client.kernelspec import KernelSpec
9+
from .env_kernelspec import EnvironmentLoadingKernelSpec
1010

1111

12-
from .lazyobj import LazyProxyDict
13-
1412
def find_env_paths_in_basedirs(base_dirs):
1513
"""Returns all potential envs in a basedir"""
1614
# get potential env path in the base_dirs
@@ -22,6 +20,7 @@ def find_env_paths_in_basedirs(base_dirs):
2220

2321
return env_path
2422

23+
2524
def convert_to_env_data(mgr, env_paths, validator_func, activate_func,
2625
name_template, display_name_template, name_prefix):
2726
"""Converts a list of paths to environments to env_data.
@@ -51,15 +50,13 @@ def convert_to_env_data(mgr, env_paths, validator_func, activate_func,
5150

5251
# the default vars are needed to save the vars in the function context
5352
def loader(env_dir=venv_dir, activate_func=activate_func, mgr=mgr):
54-
return activate_func(mgr, env_dir)
55-
56-
# this constructs a proxy which only loads the real values when it is used.
57-
# The result is that the env vars are only loaded (= env is activated) when
58-
# the kernel is actually used, so we get a nice speedup on startup :-)
59-
kspec_dict["env"] = LazyProxyDict(loader)
53+
mgr.log.debug("Loading env data for %s" % env_dir)
54+
res = activate_func(mgr, env_dir)
55+
#mgr.log.info("PATH: %s" % res['PATH'])
56+
return res
6057

61-
# This should probably use self.kernel_spec_class instead of the direct class
62-
env_data.update({kernel_name: (resource_dir, KernelSpec(**kspec_dict))})
58+
kspec = EnvironmentLoadingKernelSpec(loader, **kspec_dict)
59+
env_data.update({kernel_name: (resource_dir, kspec)})
6360
return env_data
6461

6562

@@ -87,11 +84,11 @@ def validate_IPykernel(venv_dir):
8784
# check if this is really an ipython **kernel**
8885
import subprocess
8986
try:
90-
subprocess.check_call([python_exe_name, '-c', '"IPython.kernel"'])
87+
subprocess.check_call([python_exe_name, '-c', '"import ipykernel"'])
9188
except:
9289
# not installed? -> not useable in any case...
9390
return [], None, None
94-
argv = [python_exe_name, "-m", "IPython.kernel", "-f", "{connection_file}"]
91+
argv = [python_exe_name, "-m", "ipykernel", "-f", "{connection_file}"]
9592
resources_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logos", "python")
9693
return argv, "python", resources_dir
9794

@@ -134,5 +131,3 @@ def find_exe(env_dir, name):
134131
if not os.path.exists(exe_name):
135132
return None
136133
return exe_name
137-
138-

environment_kernels/envs_conda.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from .activate_helper import source_env_vars_from_command
66
from .envs_common import (find_env_paths_in_basedirs, convert_to_env_data,
77
validate_IPykernel, validate_IRkernel)
8-
from .utils import FileNotFoundError
9-
8+
from .utils import FileNotFoundError, ON_WINDOWS
109

1110
def get_conda_env_data(mgr):
1211
"""Finds kernel specs from conda environments
@@ -43,9 +42,15 @@ def get_conda_env_data(mgr):
4342

4443

4544
def _get_env_vars_for_conda_env(mgr, env_path):
46-
args = ["activate", env_path]
45+
if ON_WINDOWS:
46+
args = ['activate', env_path]
47+
else:
48+
args = ['source', 'activate', env_path]
49+
4750
try:
48-
return source_env_vars_from_command(args)
51+
envs = source_env_vars_from_command(args)
52+
#mgr.log.debug("PATH: %s", envs['PATH'])
53+
return envs
4954
except:
5055
# as a fallback, don't activate...
5156
mgr.log.exception(

environment_kernels/envs_virtualenv.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ def _get_env_vars_for_virtualenv_env(mgr, env_path):
3737
if ON_WINDOWS:
3838
args = [os.path.join(env_path, "Shell", "activate")]
3939
else:
40-
args = [os.path.join(env_path, "bin", "activate")]
40+
args = ['source', os.path.join(env_path, "bin", "activate")]
4141
try:
42-
return source_env_vars_from_command(args)
42+
envs = source_env_vars_from_command(args)
43+
#mgr.log.debug("Environment variables: %s", envs)
44+
return envs
4345
except:
4446
# as a fallback, don't activate...
4547
mgr.log.exception(

environment_kernels/lazyobj.py

Lines changed: 0 additions & 89 deletions
This file was deleted.

0 commit comments

Comments
 (0)