Merge pull request #1527 from SylvainCorlay/traitlets_api

Update traitlets API
This commit is contained in:
Min RK 2016-06-13 09:17:08 +02:00 committed by GitHub
commit 869d5d9ffc
6 changed files with 165 additions and 98 deletions

View File

@ -78,7 +78,7 @@ from .services.sessions.sessionmanager import SessionManager
from .auth.login import LoginHandler
from .auth.logout import LogoutHandler
from .base.handlers import FileFindHandler, IPythonHandler
from .base.handlers import FileFindHandler
from traitlets.config import Config
from traitlets.config.application import catch_config_error, boolean_flag
@ -91,7 +91,7 @@ from jupyter_client.session import Session
from nbformat.sign import NotebookNotary
from traitlets import (
Dict, Unicode, Integer, List, Bool, Bytes, Instance,
TraitError, Type, Float
TraitError, Type, Float, observe, default, validate
)
from ipython_genutils import py3compat
from jupyter_core.paths import jupyter_runtime_dir, jupyter_path
@ -144,7 +144,7 @@ def load_handlers(name):
class NotebookWebApplication(web.Application):
def __init__(self, ipython_app, kernel_manager, contents_manager,
def __init__(self, jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager,
config_manager, log,
base_url, default_url, settings_overrides, jinja_env_options):
@ -157,14 +157,14 @@ class NotebookWebApplication(web.Application):
log.info(DEV_NOTE_NPM)
settings = self.init_settings(
ipython_app, kernel_manager, contents_manager,
jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager, config_manager, log, base_url,
default_url, settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
def init_settings(self, ipython_app, kernel_manager, contents_manager,
def init_settings(self, jupyter_app, kernel_manager, contents_manager,
session_manager, kernel_spec_manager,
config_manager,
log, base_url, default_url, settings_overrides,
@ -172,7 +172,7 @@ class NotebookWebApplication(web.Application):
_template_path = settings_overrides.get(
"template_path",
ipython_app.template_file_path,
jupyter_app.template_file_path,
)
if isinstance(_template_path, py3compat.string_types):
_template_path = (_template_path,)
@ -191,7 +191,7 @@ class NotebookWebApplication(web.Application):
# reset the cache on server restart
version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
if ipython_app.ignore_minified_js:
if jupyter_app.ignore_minified_js:
log.warn("""The `ignore_minified_js` flag is deprecated and no
longer works. Alternatively use `npm run build:watch` when
working on the notebook's Javascript and LESS""")
@ -203,8 +203,8 @@ class NotebookWebApplication(web.Application):
base_url=base_url,
default_url=default_url,
template_path=template_path,
static_path=ipython_app.static_file_path,
static_custom_path=ipython_app.static_custom_path,
static_path=jupyter_app.static_file_path,
static_custom_path=jupyter_app.static_custom_path,
static_handler_class = FileFindHandler,
static_url_prefix = url_path_join(base_url,'/static/'),
static_handler_args = {
@ -212,19 +212,19 @@ class NotebookWebApplication(web.Application):
'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
},
version_hash=version_hash,
ignore_minified_js=ipython_app.ignore_minified_js,
ignore_minified_js=jupyter_app.ignore_minified_js,
# rate limits
iopub_msg_rate_limit=ipython_app.iopub_msg_rate_limit,
iopub_data_rate_limit=ipython_app.iopub_data_rate_limit,
rate_limit_window=ipython_app.rate_limit_window,
iopub_msg_rate_limit=jupyter_app.iopub_msg_rate_limit,
iopub_data_rate_limit=jupyter_app.iopub_data_rate_limit,
rate_limit_window=jupyter_app.rate_limit_window,
# authentication
cookie_secret=ipython_app.cookie_secret,
cookie_secret=jupyter_app.cookie_secret,
login_url=url_path_join(base_url,'/login'),
login_handler_class=ipython_app.login_handler_class,
logout_handler_class=ipython_app.logout_handler_class,
password=ipython_app.password,
login_handler_class=jupyter_app.login_handler_class,
logout_handler_class=jupyter_app.logout_handler_class,
password=jupyter_app.password,
# managers
kernel_manager=kernel_manager,
@ -234,13 +234,13 @@ class NotebookWebApplication(web.Application):
config_manager=config_manager,
# IPython stuff
jinja_template_vars=ipython_app.jinja_template_vars,
nbextensions_path=ipython_app.nbextensions_path,
websocket_url=ipython_app.websocket_url,
mathjax_url=ipython_app.mathjax_url,
mathjax_config=ipython_app.mathjax_config,
config=ipython_app.config,
config_dir=ipython_app.config_dir,
jinja_template_vars=jupyter_app.jinja_template_vars,
nbextensions_path=jupyter_app.nbextensions_path,
websocket_url=jupyter_app.websocket_url,
mathjax_url=jupyter_app.mathjax_url,
mathjax_config=jupyter_app.mathjax_config,
config=jupyter_app.config,
config_dir=jupyter_app.config_dir,
jinja2_env=env,
terminals_available=False, # Set later if terminals are available
)
@ -426,14 +426,17 @@ class NotebookApp(JupyterApp):
_log_formatter_cls = LogFormatter
def _log_level_default(self):
@default('log_level')
def _default_log_level(self):
return logging.INFO
def _log_datefmt_default(self):
@default('log_datefmt')
def _default_log_datefmt(self):
"""Exclude date from default date format"""
return "%H:%M:%S"
def _log_format_default(self):
@default('log_format')
def _default_log_format(self):
"""override default log format to include time"""
return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
@ -484,7 +487,9 @@ class NotebookApp(JupyterApp):
ip = Unicode('localhost', config=True,
help="The IP address the notebook server will listen on."
)
def _ip_default(self):
@default('ip')
def _default_ip(self):
"""Return localhost if available, 127.0.0.1 otherwise.
On some (horribly broken) systems, localhost cannot be bound.
@ -499,12 +504,17 @@ class NotebookApp(JupyterApp):
s.close()
return 'localhost'
def _ip_changed(self, name, old, new):
if new == u'*': self.ip = u''
@validate('ip')
def _valdate_ip(self, proposal):
value = proposal['value']
if value == u'*':
value = u''
return value
port = Integer(8888, config=True,
help="The port the notebook server will listen on."
)
port_retries = Integer(50, config=True,
help="The number of additional ports to try if the specified port is not available."
)
@ -524,7 +534,9 @@ class NotebookApp(JupyterApp):
cookie_secret_file = Unicode(config=True,
help="""The file where the cookie secret is stored."""
)
def _cookie_secret_file_default(self):
@default('cookie_secret_file')
def _default_cookie_secret_file(self):
return os.path.join(self.runtime_dir, 'notebook_cookie_secret')
cookie_secret = Bytes(b'', config=True,
@ -536,7 +548,9 @@ class NotebookApp(JupyterApp):
cookie_secret stored in plaintext (you can read the value from a file).
"""
)
def _cookie_secret_default(self):
@default('cookie_secret')
def _default_cookie_secret(self):
if os.path.exists(self.cookie_secret_file):
with io.open(self.cookie_secret_file, 'rb') as f:
return f.read()
@ -599,9 +613,11 @@ class NotebookApp(JupyterApp):
webapp_settings = Dict(config=True,
help="DEPRECATED, use tornado_settings"
)
def _webapp_settings_changed(self, name, old, new):
@observe('webapp_settings')
def _update_webapp_settings(self, change):
self.log.warning("\n webapp_settings is deprecated, use tornado_settings.\n")
self.tornado_settings = new
self.tornado_settings = change['new']
tornado_settings = Dict(config=True,
help="Supply overrides for the tornado.web.Application that the "
@ -636,9 +652,11 @@ class NotebookApp(JupyterApp):
When disabled, equations etc. will appear as their untransformed TeX source.
"""
)
def _enable_mathjax_changed(self, name, old, new):
@observe('enable_mathjax')
def _update_enable_mathjax(self, change):
"""set mathjax url to empty if mathjax is disabled"""
if not new:
if not change['new']:
self.mathjax_url = u''
base_url = Unicode('/', config=True,
@ -647,16 +665,22 @@ class NotebookApp(JupyterApp):
Leading and trailing slashes can be omitted,
and will automatically be added.
''')
def _base_url_changed(self, name, old, new):
if not new.startswith('/'):
self.base_url = '/'+new
elif not new.endswith('/'):
self.base_url = new+'/'
@validate('base_url')
def _update_base_url(self, proposal):
value = proposal['value']
if not value.startswith('/'):
value = '/' + value
elif not value.endswith('/'):
value = value + '/'
return value
base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
def _base_project_url_changed(self, name, old, new):
@observe('base_project_url')
def _update_base_project_url(self, change):
self.log.warning("base_project_url is deprecated, use base_url")
self.base_url = new
self.base_url = change['new']
extra_static_paths = List(Unicode(), config=True,
help="""Extra paths to search for serving static files.
@ -673,7 +697,9 @@ class NotebookApp(JupyterApp):
static_custom_path = List(Unicode(),
help="""Path to search for custom.js, css"""
)
def _static_custom_path_default(self):
@default('static_custom_path')
def _default_static_custom_path(self):
return [
os.path.join(d, 'custom') for d in (
self.config_dir,
@ -715,16 +741,21 @@ class NotebookApp(JupyterApp):
Should be in the form of an HTTP origin: ws[s]://hostname[:port]
"""
)
mathjax_url = Unicode("", config=True,
help="""The url for MathJax.js."""
)
def _mathjax_url_default(self):
@default('mathjax_url')
def _default_mathjax_url(self):
if not self.enable_mathjax:
return u''
static_url_prefix = self.tornado_settings.get("static_url_prefix", "static")
return url_path_join(static_url_prefix, 'components', 'MathJax', 'MathJax.js')
def _mathjax_url_changed(self, name, old, new):
@observe('mathjax_url')
def _update_mathjax_url(self, change):
new = change['new']
if new and not self.enable_mathjax:
# enable_mathjax=False overrides mathjax_url
self.mathjax_url = u''
@ -735,8 +766,9 @@ class NotebookApp(JupyterApp):
help="""The MathJax.js configuration file that is to be used."""
)
def _mathjax_config_changed(self, name, old, new):
self.log.info("Using MathJax configuration file: %s", new)
@observe('mathjax_config')
def _update_mathjax_config(self, change):
self.log.info("Using MathJax configuration file: %s", change['new'])
contents_manager_class = Type(
default_value=FileContentsManager,
@ -744,11 +776,13 @@ class NotebookApp(JupyterApp):
config=True,
help='The notebook manager class to use.'
)
kernel_manager_class = Type(
default_value=MappingKernelManager,
config=True,
help='The kernel manager class to use.'
)
session_manager_class = Type(
default_value=SessionManager,
config=True,
@ -796,7 +830,8 @@ class NotebookApp(JupyterApp):
info_file = Unicode()
def _info_file_default(self):
@default('info_file')
def _default_info_file(self):
info_file = "nbserver-%s.json" % os.getpid()
return os.path.join(self.runtime_dir, info_file)
@ -805,10 +840,12 @@ class NotebookApp(JupyterApp):
DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
"""
)
def _pylab_changed(self, name, old, new):
@observe('pylab')
def _update_pylab(self, change):
"""when --pylab is specified, display a warning and exit"""
if new != 'warn':
backend = ' %s' % new
backend = ' %s' % change['new']
else:
backend = ''
self.log.error("Support for specifying --pylab on the command line has been removed.")
@ -821,21 +858,22 @@ class NotebookApp(JupyterApp):
help="The directory to use for notebooks and kernels."
)
def _notebook_dir_default(self):
@default('notebook_dir')
def _default_notebook_dir(self):
if self.file_to_run:
return os.path.dirname(os.path.abspath(self.file_to_run))
else:
return py3compat.getcwd()
def _notebook_dir_validate(self, value, trait):
@validate('notebook_dir')
def _notebook_dir_validate(self, proposal):
value = proposal['value']
# Strip any trailing slashes
# *except* if it's root
_, path = os.path.splitdrive(value)
if path == os.sep:
return value
value = value.rstrip(os.sep)
if not os.path.isabs(value):
# If we receive a non-absolute path, make it absolute.
value = os.path.abspath(value)
@ -843,9 +881,11 @@ class NotebookApp(JupyterApp):
raise TraitError("No such notebook dir: %r" % value)
return value
def _notebook_dir_changed(self, name, old, new):
@observe('notebook_dir')
def _update_notebook_dir(self, change):
"""Do a bit of validation of the notebook dir."""
# setting App.notebook_dir implies setting notebook and kernel dirs as well
new = change['new']
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new
@ -853,9 +893,11 @@ class NotebookApp(JupyterApp):
server_extensions = List(Unicode(), config=True,
help=("DEPRECATED use the nbserver_extensions dict instead")
)
def _server_extensions_changed(self, name, old, new):
@observe('server_extensions')
def _update_server_extensions(self, change):
self.log.warning("server_extensions is deprecated, use nbserver_extensions")
self.server_extensions = new
self.server_extensions = change['new']
nbserver_extensions = Dict({}, config=True,
help=("Dict of Python modules to load as notebook server extensions."

View File

@ -7,7 +7,7 @@ import os.path
from traitlets.config.manager import BaseJSONConfigManager, recursive_update
from jupyter_core.paths import jupyter_config_dir, jupyter_config_path
from traitlets import Unicode, Instance, List
from traitlets import Unicode, Instance, List, observe, default
from traitlets.config import LoggingConfigurable
@ -35,16 +35,23 @@ class ConfigManager(LoggingConfigurable):
# Private API
read_config_path = List(Unicode())
def _read_config_path_default(self):
@default('read_config_path')
def _default_read_config_path(self):
return [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
write_config_dir = Unicode()
def _write_config_dir_default(self):
@default('write_config_dir')
def _default_write_config_dir(self):
return os.path.join(jupyter_config_dir(), 'nbconfig')
write_config_manager = Instance(BaseJSONConfigManager)
def _write_config_manager_default(self):
@default('write_config_manager')
def _default_write_config_manager(self):
return BaseJSONConfigManager(config_dir=self.write_config_dir)
def _write_config_dir_changed(self, name, old, new):
@observe('write_config_dir')
def _update_write_config_dir(self, change):
self.write_config_manager = BaseJSONConfigManager(config_dir=self.write_config_dir)

View File

@ -17,9 +17,8 @@ from .filecheckpoints import FileCheckpoints
from .fileio import FileManagerMixin
from .manager import ContentsManager
from ipython_genutils.importstring import import_item
from traitlets import Any, Unicode, Bool, TraitError
from traitlets import Any, Unicode, Bool, TraitError, observe, default, validate
from ipython_genutils.py3compat import getcwd, string_types
from . import tz
from notebook.utils import (
@ -59,14 +58,17 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
root_dir = Unicode(config=True)
def _root_dir_default(self):
@default('root_dir')
def _default_root_dir(self):
try:
return self.parent.notebook_dir
except AttributeError:
return getcwd()
save_script = Bool(False, config=True, help='DEPRECATED, use post_save_hook. Will be removed in Notebook 5.0')
def _save_script_changed(self):
@observe('save_script')
def _update_save_script(self):
self.log.warning("""
`--script` is deprecated and will be removed in notebook 5.0.
@ -101,12 +103,15 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
- contents_manager: this ContentsManager instance
"""
)
def _post_save_hook_changed(self, name, old, new):
if new and isinstance(new, string_types):
self.post_save_hook = import_item(self.post_save_hook)
elif new:
if not callable(new):
raise TraitError("post_save_hook must be callable")
@validate('post_save_hook')
def _validate_post_save_hook(self, proposal):
value = proposal['value']
if isinstance(value, string_types):
value = import_item(value)
if not callable(value):
raise TraitError("post_save_hook must be callable")
return value
def run_post_save_hook(self, model, os_path):
"""Run the post-save hook if defined, and log errors"""
@ -117,14 +122,16 @@ class FileContentsManager(FileManagerMixin, ContentsManager):
except Exception:
self.log.error("Post-save hook failed on %s", os_path, exc_info=True)
def _root_dir_changed(self, name, old, new):
@validate('root_dir')
def _validate_root_dir(self, proposal):
"""Do a bit of validation of the root_dir."""
if not os.path.isabs(new):
value = proposal['value']
if not os.path.isabs(value):
# If we receive a non-absolute path, make it absolute.
self.root_dir = os.path.abspath(new)
return
if not os.path.isdir(new):
raise TraitError("%r is not a directory" % new)
value = os.path.abspath(value)
if not os.path.isdir(value):
raise TraitError("%r is not a directory" % value)
return value
def _checkpoints_class_default(self):
return FileCheckpoints

View File

@ -24,6 +24,8 @@ from traitlets import (
TraitError,
Type,
Unicode,
validate,
default,
)
from ipython_genutils.py3compat import string_types
@ -91,12 +93,15 @@ class ContentsManager(LoggingConfigurable):
- contents_manager: this ContentsManager instance
"""
)
def _pre_save_hook_changed(self, name, old, new):
if new and isinstance(new, string_types):
self.pre_save_hook = import_item(self.pre_save_hook)
elif new:
if not callable(new):
raise TraitError("pre_save_hook must be callable")
@validate('pre_save_hook')
def _validate_pre_save_hook(self, proposal):
value = proposal['value']
if isinstance(value, string_types):
value = import_item(self.pre_save_hook)
if not callable(value):
raise TraitError("pre_save_hook must be callable")
return value
def run_pre_save_hook(self, model, path, **kwargs):
"""Run the pre-save hook if defined, and log errors"""
@ -111,10 +116,12 @@ class ContentsManager(LoggingConfigurable):
checkpoints = Instance(Checkpoints, config=True)
checkpoints_kwargs = Dict(config=True)
def _checkpoints_default(self):
@default('checkpoints')
def _default_checkpoints(self):
return self.checkpoints_class(**self.checkpoints_kwargs)
def _checkpoints_kwargs_default(self):
@default('checkpoints_kwargs')
def _default_checkpoints_kwargs(self):
return dict(
parent=self,
log=self.log,

View File

@ -14,7 +14,7 @@ from tornado.concurrent import Future
from tornado.ioloop import IOLoop
from jupyter_client.multikernelmanager import MultiKernelManager
from traitlets import List, Unicode, TraitError
from traitlets import List, Unicode, TraitError, default, validate
from notebook.utils import to_os_path
from ipython_genutils.py3compat import getcwd
@ -23,27 +23,31 @@ from ipython_genutils.py3compat import getcwd
class MappingKernelManager(MultiKernelManager):
"""A KernelManager that handles notebook mapping and HTTP error handling"""
def _kernel_manager_class_default(self):
@default('kernel_manager_class')
def _default_kernel_manager_class(self):
return "jupyter_client.ioloop.IOLoopKernelManager"
kernel_argv = List(Unicode())
root_dir = Unicode(config=True)
def _root_dir_default(self):
@default('root_dir')
def _default_root_dir(self):
try:
return self.parent.notebook_dir
except AttributeError:
return getcwd()
def _root_dir_changed(self, name, old, new):
@validate('root_dir')
def _update_root_dir(self, proposal):
"""Do a bit of validation of the root dir."""
if not os.path.isabs(new):
value = proposal['value']
if not os.path.isabs(value):
# If we receive a non-absolute path, make it absolute.
self.root_dir = os.path.abspath(new)
return
if not os.path.exists(new) or not os.path.isdir(new):
raise TraitError("kernel root dir %r is not a directory" % new)
value = os.path.abspath(value)
if not os.path.exists(value) or not os.path.isdir(value):
raise TraitError("kernel root dir %r is not a directory" % value)
return value
#-------------------------------------------------------------------------
# Methods for managing kernels and sessions

View File

@ -78,7 +78,7 @@ for more information.
version = version,
scripts = glob(pjoin('scripts', '*')),
packages = find_packages(),
package_data = find_package_data(),
package_data = find_package_data(),
author = 'Jupyter Development Team',
author_email = 'jupyter@googlegroups.com',
url = 'http://jupyter.org',
@ -146,7 +146,7 @@ install_requires = [
'jinja2',
'tornado>=4',
'ipython_genutils',
'traitlets',
'traitlets>=4.2.1',
'jupyter_core',
'jupyter_client',
'nbformat',