Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
28
venv/Lib/site-packages/notebook/__init__.py
Normal file
28
venv/Lib/site-packages/notebook/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
"""The Jupyter HTML Notebook"""
|
||||
|
||||
import os
|
||||
# Packagers: modify this line if you store the notebook static files elsewhere
|
||||
DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static")
|
||||
|
||||
# Packagers: modify the next line if you store the notebook template files
|
||||
# elsewhere
|
||||
|
||||
# Include both notebook/ and notebook/templates/. This makes it
|
||||
# possible for users to override a template with a file that inherits from that
|
||||
# template.
|
||||
#
|
||||
# For example, if you want to override a specific block of notebook.html, you
|
||||
# can create a file called notebook.html that inherits from
|
||||
# templates/notebook.html, and the latter will resolve correctly to the base
|
||||
# implementation.
|
||||
DEFAULT_TEMPLATE_PATH_LIST = [
|
||||
os.path.dirname(__file__),
|
||||
os.path.join(os.path.dirname(__file__), "templates"),
|
||||
]
|
||||
|
||||
DEFAULT_NOTEBOOK_PORT = 8888
|
||||
|
||||
del os
|
||||
|
||||
from .nbextensions import install_nbextension
|
||||
from ._version import version_info, __version__
|
5
venv/Lib/site-packages/notebook/__main__.py
Normal file
5
venv/Lib/site-packages/notebook/__main__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
if __name__ == '__main__':
|
||||
from notebook import notebookapp as app
|
||||
app.launch_new_instance()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/notebook/__pycache__/_tz.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/notebook/__pycache__/_tz.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/notebook/__pycache__/log.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/notebook/__pycache__/log.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/notebook/__pycache__/utils.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/notebook/__pycache__/utils.cpython-36.pyc
Normal file
Binary file not shown.
99
venv/Lib/site-packages/notebook/_sysinfo.py
Normal file
99
venv/Lib/site-packages/notebook/_sysinfo.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
# encoding: utf-8
|
||||
"""
|
||||
Utilities for getting information about Jupyter and the system it's running in.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import platform
|
||||
import pprint
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from ipython_genutils import py3compat, encoding
|
||||
|
||||
import notebook
|
||||
|
||||
def pkg_commit_hash(pkg_path):
|
||||
"""Get short form of commit hash given directory `pkg_path`
|
||||
|
||||
We get the commit hash from git if it's a repo.
|
||||
|
||||
If this fail, we return a not-found placeholder tuple
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pkg_path : str
|
||||
directory containing package
|
||||
only used for getting commit from active repo
|
||||
|
||||
Returns
|
||||
-------
|
||||
hash_from : str
|
||||
Where we got the hash from - description
|
||||
hash_str : str
|
||||
short form of hash
|
||||
"""
|
||||
|
||||
# maybe we are in a repository, check for a .git folder
|
||||
p = os.path
|
||||
cur_path = None
|
||||
par_path = pkg_path
|
||||
while cur_path != par_path:
|
||||
cur_path = par_path
|
||||
if p.exists(p.join(cur_path, '.git')):
|
||||
try:
|
||||
proc = subprocess.Popen(['git', 'rev-parse', '--short', 'HEAD'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=pkg_path)
|
||||
repo_commit, _ = proc.communicate()
|
||||
except OSError:
|
||||
repo_commit = None
|
||||
|
||||
if repo_commit:
|
||||
return 'repository', repo_commit.strip().decode('ascii')
|
||||
else:
|
||||
return u'', u''
|
||||
par_path = p.dirname(par_path)
|
||||
|
||||
return u'', u''
|
||||
|
||||
|
||||
def pkg_info(pkg_path):
|
||||
"""Return dict describing the context of this package
|
||||
|
||||
Parameters
|
||||
----------
|
||||
pkg_path : str
|
||||
path containing __init__.py for package
|
||||
|
||||
Returns
|
||||
-------
|
||||
context : dict
|
||||
with named parameters of interest
|
||||
"""
|
||||
src, hsh = pkg_commit_hash(pkg_path)
|
||||
return dict(
|
||||
notebook_version=notebook.__version__,
|
||||
notebook_path=pkg_path,
|
||||
commit_source=src,
|
||||
commit_hash=hsh,
|
||||
sys_version=sys.version,
|
||||
sys_executable=sys.executable,
|
||||
sys_platform=sys.platform,
|
||||
platform=platform.platform(),
|
||||
os_name=os.name,
|
||||
default_encoding=encoding.DEFAULT_ENCODING,
|
||||
)
|
||||
|
||||
def get_sys_info():
|
||||
"""Return useful information about the system as a dict."""
|
||||
p = os.path
|
||||
path = p.realpath(p.dirname(p.abspath(p.join(notebook.__file__))))
|
||||
return pkg_info(path)
|
||||
|
42
venv/Lib/site-packages/notebook/_tz.py
Normal file
42
venv/Lib/site-packages/notebook/_tz.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# encoding: utf-8
|
||||
"""
|
||||
Timezone utilities
|
||||
|
||||
Just UTC-awareness right now
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from datetime import tzinfo, timedelta, datetime
|
||||
|
||||
# constant for zero offset
|
||||
ZERO = timedelta(0)
|
||||
|
||||
class tzUTC(tzinfo):
|
||||
"""tzinfo object for UTC (zero offset)"""
|
||||
|
||||
def utcoffset(self, d):
|
||||
return ZERO
|
||||
|
||||
def dst(self, d):
|
||||
return ZERO
|
||||
|
||||
UTC = tzUTC()
|
||||
|
||||
def utc_aware(unaware):
|
||||
"""decorator for adding UTC tzinfo to datetime's utcfoo methods"""
|
||||
def utc_method(*args, **kwargs):
|
||||
dt = unaware(*args, **kwargs)
|
||||
return dt.replace(tzinfo=UTC)
|
||||
return utc_method
|
||||
|
||||
utcfromtimestamp = utc_aware(datetime.utcfromtimestamp)
|
||||
utcnow = utc_aware(datetime.utcnow)
|
||||
|
||||
def isoformat(dt):
|
||||
"""Return iso-formatted timestamp
|
||||
|
||||
Like .isoformat(), but uses Z for UTC instead of +00:00
|
||||
"""
|
||||
return dt.isoformat().replace('+00:00', 'Z')
|
13
venv/Lib/site-packages/notebook/_version.py
Normal file
13
venv/Lib/site-packages/notebook/_version.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
"""
|
||||
store the current version info of the notebook.
|
||||
|
||||
"""
|
||||
|
||||
# Downstream maintainer, when running `python.setup.py jsversion`,
|
||||
# the version string is propagated to the JavaScript files, do not forget to
|
||||
# patch the JavaScript files in `.postN` release done by distributions.
|
||||
|
||||
# Next beta/alpha/rc release: The version number for beta is X.Y.ZbN **without dots**.
|
||||
|
||||
version_info = (6, 1, 5)
|
||||
__version__ = '.'.join(map(str, version_info[:3])) + ''.join(version_info[3:])
|
1
venv/Lib/site-packages/notebook/auth/__init__.py
Normal file
1
venv/Lib/site-packages/notebook/auth/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .security import passwd
|
42
venv/Lib/site-packages/notebook/auth/__main__.py
Normal file
42
venv/Lib/site-packages/notebook/auth/__main__.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from notebook.auth import passwd
|
||||
from getpass import getpass
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
from jupyter_core.paths import jupyter_config_dir
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
def set_password(args):
|
||||
password = args.password
|
||||
while not password :
|
||||
password1 = getpass("" if args.quiet else "Provide password: ")
|
||||
password_repeat = getpass("" if args.quiet else "Repeat password: ")
|
||||
if password1 != password_repeat:
|
||||
print("Passwords do not match, try again")
|
||||
elif len(password1) < 4:
|
||||
print("Please provide at least 4 characters")
|
||||
else:
|
||||
password = password1
|
||||
|
||||
password_hash = passwd(password)
|
||||
cfg = BaseJSONConfigManager(config_dir=jupyter_config_dir())
|
||||
cfg.update('jupyter_notebook_config', {
|
||||
'NotebookApp': {
|
||||
'password': password_hash,
|
||||
}
|
||||
})
|
||||
if not args.quiet:
|
||||
print("password stored in config dir: %s" % jupyter_config_dir())
|
||||
|
||||
def main(argv):
|
||||
parser = argparse.ArgumentParser(argv[0])
|
||||
subparsers = parser.add_subparsers()
|
||||
parser_password = subparsers.add_parser('password', help='sets a password for your notebook server')
|
||||
parser_password.add_argument("password", help="password to set, if not given, a password will be queried for (NOTE: this may not be safe)",
|
||||
nargs="?")
|
||||
parser_password.add_argument("--quiet", help="suppress messages", action="store_true")
|
||||
parser_password.set_defaults(function=set_password)
|
||||
args = parser.parse_args(argv[1:])
|
||||
args.function(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
253
venv/Lib/site-packages/notebook/auth/login.py
Normal file
253
venv/Lib/site-packages/notebook/auth/login.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
"""Tornado handlers for logging into the notebook."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import uuid
|
||||
|
||||
from tornado.escape import url_escape
|
||||
|
||||
from .security import passwd_check, set_password
|
||||
|
||||
from ..base.handlers import IPythonHandler
|
||||
|
||||
|
||||
class LoginHandler(IPythonHandler):
|
||||
"""The basic tornado login handler
|
||||
|
||||
authenticates with a hashed password from the configuration.
|
||||
"""
|
||||
def _render(self, message=None):
|
||||
self.write(self.render_template('login.html',
|
||||
next=url_escape(self.get_argument('next', default=self.base_url)),
|
||||
message=message,
|
||||
))
|
||||
|
||||
def _redirect_safe(self, url, default=None):
|
||||
"""Redirect if url is on our PATH
|
||||
|
||||
Full-domain redirects are allowed if they pass our CORS origin checks.
|
||||
|
||||
Otherwise use default (self.base_url if unspecified).
|
||||
"""
|
||||
if default is None:
|
||||
default = self.base_url
|
||||
# protect chrome users from mishandling unescaped backslashes.
|
||||
# \ is not valid in urls, but some browsers treat it as /
|
||||
# instead of %5C, causing `\\` to behave as `//`
|
||||
url = url.replace("\\", "%5C")
|
||||
parsed = urlparse(url)
|
||||
if parsed.netloc or not (parsed.path + '/').startswith(self.base_url):
|
||||
# require that next_url be absolute path within our path
|
||||
allow = False
|
||||
# OR pass our cross-origin check
|
||||
if parsed.netloc:
|
||||
# if full URL, run our cross-origin check:
|
||||
origin = '%s://%s' % (parsed.scheme, parsed.netloc)
|
||||
origin = origin.lower()
|
||||
if self.allow_origin:
|
||||
allow = self.allow_origin == origin
|
||||
elif self.allow_origin_pat:
|
||||
allow = bool(self.allow_origin_pat.match(origin))
|
||||
if not allow:
|
||||
# not allowed, use default
|
||||
self.log.warning("Not allowing login redirect to %r" % url)
|
||||
url = default
|
||||
self.redirect(url)
|
||||
|
||||
def get(self):
|
||||
if self.current_user:
|
||||
next_url = self.get_argument('next', default=self.base_url)
|
||||
self._redirect_safe(next_url)
|
||||
else:
|
||||
self._render()
|
||||
|
||||
@property
|
||||
def hashed_password(self):
|
||||
return self.password_from_settings(self.settings)
|
||||
|
||||
def passwd_check(self, a, b):
|
||||
return passwd_check(a, b)
|
||||
|
||||
def post(self):
|
||||
typed_password = self.get_argument('password', default=u'')
|
||||
new_password = self.get_argument('new_password', default=u'')
|
||||
|
||||
|
||||
|
||||
if self.get_login_available(self.settings):
|
||||
if self.passwd_check(self.hashed_password, typed_password) and not new_password:
|
||||
self.set_login_cookie(self, uuid.uuid4().hex)
|
||||
elif self.token and self.token == typed_password:
|
||||
self.set_login_cookie(self, uuid.uuid4().hex)
|
||||
if new_password and self.settings.get('allow_password_change'):
|
||||
config_dir = self.settings.get('config_dir')
|
||||
config_file = os.path.join(config_dir, 'jupyter_notebook_config.json')
|
||||
set_password(new_password, config_file=config_file)
|
||||
self.log.info("Wrote hashed password to %s" % config_file)
|
||||
else:
|
||||
self.set_status(401)
|
||||
self._render(message={'error': 'Invalid credentials'})
|
||||
return
|
||||
|
||||
|
||||
next_url = self.get_argument('next', default=self.base_url)
|
||||
self._redirect_safe(next_url)
|
||||
|
||||
@classmethod
|
||||
def set_login_cookie(cls, handler, user_id=None):
|
||||
"""Call this on handlers to set the login cookie for success"""
|
||||
cookie_options = handler.settings.get('cookie_options', {})
|
||||
cookie_options.setdefault('httponly', True)
|
||||
# tornado <4.2 has a bug that considers secure==True as soon as
|
||||
# 'secure' kwarg is passed to set_secure_cookie
|
||||
if handler.settings.get('secure_cookie', handler.request.protocol == 'https'):
|
||||
cookie_options.setdefault('secure', True)
|
||||
cookie_options.setdefault('path', handler.base_url)
|
||||
handler.set_secure_cookie(handler.cookie_name, user_id, **cookie_options)
|
||||
return user_id
|
||||
|
||||
auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE)
|
||||
|
||||
@classmethod
|
||||
def get_token(cls, handler):
|
||||
"""Get the user token from a request
|
||||
|
||||
Default:
|
||||
|
||||
- in URL parameters: ?token=<token>
|
||||
- in header: Authorization: token <token>
|
||||
"""
|
||||
|
||||
user_token = handler.get_argument('token', '')
|
||||
if not user_token:
|
||||
# get it from Authorization header
|
||||
m = cls.auth_header_pat.match(handler.request.headers.get('Authorization', ''))
|
||||
if m:
|
||||
user_token = m.group(1)
|
||||
return user_token
|
||||
|
||||
@classmethod
|
||||
def should_check_origin(cls, handler):
|
||||
"""Should the Handler check for CORS origin validation?
|
||||
|
||||
Origin check should be skipped for token-authenticated requests.
|
||||
|
||||
Returns:
|
||||
- True, if Handler must check for valid CORS origin.
|
||||
- False, if Handler should skip origin check since requests are token-authenticated.
|
||||
"""
|
||||
return not cls.is_token_authenticated(handler)
|
||||
|
||||
@classmethod
|
||||
def is_token_authenticated(cls, handler):
|
||||
"""Returns True if handler has been token authenticated. Otherwise, False.
|
||||
|
||||
Login with a token is used to signal certain things, such as:
|
||||
|
||||
- permit access to REST API
|
||||
- xsrf protection
|
||||
- skip origin-checks for scripts
|
||||
"""
|
||||
if getattr(handler, '_user_id', None) is None:
|
||||
# ensure get_user has been called, so we know if we're token-authenticated
|
||||
handler.get_current_user()
|
||||
return getattr(handler, '_token_authenticated', False)
|
||||
|
||||
@classmethod
|
||||
def get_user(cls, handler):
|
||||
"""Called by handlers.get_current_user for identifying the current user.
|
||||
|
||||
See tornado.web.RequestHandler.get_current_user for details.
|
||||
"""
|
||||
# Can't call this get_current_user because it will collide when
|
||||
# called on LoginHandler itself.
|
||||
if getattr(handler, '_user_id', None):
|
||||
return handler._user_id
|
||||
user_id = cls.get_user_token(handler)
|
||||
if user_id is None:
|
||||
get_secure_cookie_kwargs = handler.settings.get('get_secure_cookie_kwargs', {})
|
||||
user_id = handler.get_secure_cookie(handler.cookie_name, **get_secure_cookie_kwargs )
|
||||
else:
|
||||
cls.set_login_cookie(handler, user_id)
|
||||
# Record that the current request has been authenticated with a token.
|
||||
# Used in is_token_authenticated above.
|
||||
handler._token_authenticated = True
|
||||
if user_id is None:
|
||||
# If an invalid cookie was sent, clear it to prevent unnecessary
|
||||
# extra warnings. But don't do this on a request with *no* cookie,
|
||||
# because that can erroneously log you out (see gh-3365)
|
||||
if handler.get_cookie(handler.cookie_name) is not None:
|
||||
handler.log.warning("Clearing invalid/expired login cookie %s", handler.cookie_name)
|
||||
handler.clear_login_cookie()
|
||||
if not handler.login_available:
|
||||
# Completely insecure! No authentication at all.
|
||||
# No need to warn here, though; validate_security will have already done that.
|
||||
user_id = 'anonymous'
|
||||
|
||||
# cache value for future retrievals on the same request
|
||||
handler._user_id = user_id
|
||||
return user_id
|
||||
|
||||
@classmethod
|
||||
def get_user_token(cls, handler):
|
||||
"""Identify the user based on a token in the URL or Authorization header
|
||||
|
||||
Returns:
|
||||
- uuid if authenticated
|
||||
- None if not
|
||||
"""
|
||||
token = handler.token
|
||||
if not token:
|
||||
return
|
||||
# check login token from URL argument or Authorization header
|
||||
user_token = cls.get_token(handler)
|
||||
authenticated = False
|
||||
if user_token == token:
|
||||
# token-authenticated, set the login cookie
|
||||
handler.log.debug("Accepting token-authenticated connection from %s", handler.request.remote_ip)
|
||||
authenticated = True
|
||||
|
||||
if authenticated:
|
||||
return uuid.uuid4().hex
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@classmethod
|
||||
def validate_security(cls, app, ssl_options=None):
|
||||
"""Check the notebook application's security.
|
||||
|
||||
Show messages, or abort if necessary, based on the security configuration.
|
||||
"""
|
||||
if not app.ip:
|
||||
warning = "WARNING: The notebook server is listening on all IP addresses"
|
||||
if ssl_options is None:
|
||||
app.log.warning(warning + " and not using encryption. This "
|
||||
"is not recommended.")
|
||||
if not app.password and not app.token:
|
||||
app.log.warning(warning + " and not using authentication. "
|
||||
"This is highly insecure and not recommended.")
|
||||
else:
|
||||
if not app.password and not app.token:
|
||||
app.log.warning(
|
||||
"All authentication is disabled."
|
||||
" Anyone who can connect to this server will be able to run code.")
|
||||
|
||||
@classmethod
|
||||
def password_from_settings(cls, settings):
|
||||
"""Return the hashed password from the tornado settings.
|
||||
|
||||
If there is no configured password, an empty string will be returned.
|
||||
"""
|
||||
return settings.get('password', u'')
|
||||
|
||||
@classmethod
|
||||
def get_login_available(cls, settings):
|
||||
"""Whether this LoginHandler is needed - and therefore whether the login page should be displayed."""
|
||||
return bool(cls.password_from_settings(settings) or settings.get('token'))
|
23
venv/Lib/site-packages/notebook/auth/logout.py
Normal file
23
venv/Lib/site-packages/notebook/auth/logout.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
"""Tornado handlers for logging out of the notebook.
|
||||
"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from ..base.handlers import IPythonHandler
|
||||
|
||||
|
||||
class LogoutHandler(IPythonHandler):
|
||||
|
||||
def get(self):
|
||||
self.clear_login_cookie()
|
||||
if self.login_available:
|
||||
message = {'info': 'Successfully logged out.'}
|
||||
else:
|
||||
message = {'warning': 'Cannot log out. Notebook authentication '
|
||||
'is disabled.'}
|
||||
self.write(self.render_template('logout.html',
|
||||
message=message))
|
||||
|
||||
|
||||
default_handlers = [(r"/logout", LogoutHandler)]
|
172
venv/Lib/site-packages/notebook/auth/security.py
Normal file
172
venv/Lib/site-packages/notebook/auth/security.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
"""
|
||||
Password generation for the Notebook.
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
import getpass
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import traceback
|
||||
import warnings
|
||||
|
||||
from ipython_genutils.py3compat import cast_bytes, str_to_bytes, cast_unicode
|
||||
from traitlets.config import Config, ConfigFileNotFound, JSONFileConfigLoader
|
||||
from jupyter_core.paths import jupyter_config_dir
|
||||
|
||||
# Length of the salt in nr of hex chars, which implies salt_len * 4
|
||||
# bits of randomness.
|
||||
salt_len = 12
|
||||
|
||||
|
||||
def passwd(passphrase=None, algorithm='argon2'):
|
||||
"""Generate hashed password and salt for use in notebook configuration.
|
||||
|
||||
In the notebook configuration, set `c.NotebookApp.password` to
|
||||
the generated string.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
passphrase : str
|
||||
Password to hash. If unspecified, the user is asked to input
|
||||
and verify a password.
|
||||
algorithm : str
|
||||
Hashing algorithm to use (e.g, 'sha1' or any argument supported
|
||||
by :func:`hashlib.new`, or 'argon2').
|
||||
|
||||
Returns
|
||||
-------
|
||||
hashed_passphrase : str
|
||||
Hashed password, in the format 'hash_algorithm:salt:passphrase_hash'.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> passwd('mypassword')
|
||||
'sha1:7cf3:b7d6da294ea9592a9480c8f52e63cd42cfb9dd12'
|
||||
|
||||
"""
|
||||
if passphrase is None:
|
||||
for i in range(3):
|
||||
p0 = getpass.getpass('Enter password: ')
|
||||
p1 = getpass.getpass('Verify password: ')
|
||||
if p0 == p1:
|
||||
passphrase = p0
|
||||
break
|
||||
else:
|
||||
print('Passwords do not match.')
|
||||
else:
|
||||
raise ValueError('No matching passwords found. Giving up.')
|
||||
|
||||
if algorithm == 'argon2':
|
||||
from argon2 import PasswordHasher
|
||||
ph = PasswordHasher(
|
||||
memory_cost=10240,
|
||||
time_cost=10,
|
||||
parallelism=8,
|
||||
)
|
||||
h = ph.hash(passphrase)
|
||||
|
||||
return ':'.join((algorithm, cast_unicode(h, 'ascii')))
|
||||
else:
|
||||
h = hashlib.new(algorithm)
|
||||
salt = ('%0' + str(salt_len) + 'x') % random.getrandbits(4 * salt_len)
|
||||
h.update(cast_bytes(passphrase, 'utf-8') + str_to_bytes(salt, 'ascii'))
|
||||
|
||||
return ':'.join((algorithm, salt, h.hexdigest()))
|
||||
|
||||
|
||||
def passwd_check(hashed_passphrase, passphrase):
|
||||
"""Verify that a given passphrase matches its hashed version.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
hashed_passphrase : str
|
||||
Hashed password, in the format returned by `passwd`.
|
||||
passphrase : str
|
||||
Passphrase to validate.
|
||||
|
||||
Returns
|
||||
-------
|
||||
valid : bool
|
||||
True if the passphrase matches the hash.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from notebook.auth.security import passwd_check
|
||||
>>> passwd_check('argon2:...', 'mypassword')
|
||||
True
|
||||
|
||||
>>> passwd_check('argon2:...', 'otherpassword')
|
||||
False
|
||||
|
||||
>>> passwd_check('sha1:0e112c3ddfce:a68df677475c2b47b6e86d0467eec97ac5f4b85a',
|
||||
... 'mypassword')
|
||||
True
|
||||
"""
|
||||
if hashed_passphrase.startswith('argon2:'):
|
||||
import argon2
|
||||
import argon2.exceptions
|
||||
ph = argon2.PasswordHasher()
|
||||
try:
|
||||
return ph.verify(hashed_passphrase[7:], passphrase)
|
||||
except argon2.exceptions.VerificationError:
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
algorithm, salt, pw_digest = hashed_passphrase.split(':', 2)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
try:
|
||||
h = hashlib.new(algorithm)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
if len(pw_digest) == 0:
|
||||
return False
|
||||
|
||||
h.update(cast_bytes(passphrase, 'utf-8') + cast_bytes(salt, 'ascii'))
|
||||
|
||||
return h.hexdigest() == pw_digest
|
||||
|
||||
@contextmanager
|
||||
def persist_config(config_file=None, mode=0o600):
|
||||
"""Context manager that can be used to modify a config object
|
||||
|
||||
On exit of the context manager, the config will be written back to disk,
|
||||
by default with user-only (600) permissions.
|
||||
"""
|
||||
|
||||
if config_file is None:
|
||||
config_file = os.path.join(jupyter_config_dir(), 'jupyter_notebook_config.json')
|
||||
|
||||
os.makedirs(os.path.dirname(config_file), exist_ok=True)
|
||||
|
||||
loader = JSONFileConfigLoader(os.path.basename(config_file), os.path.dirname(config_file))
|
||||
try:
|
||||
config = loader.load_config()
|
||||
except ConfigFileNotFound:
|
||||
config = Config()
|
||||
|
||||
yield config
|
||||
|
||||
with io.open(config_file, 'w', encoding='utf8') as f:
|
||||
f.write(cast_unicode(json.dumps(config, indent=2)))
|
||||
|
||||
try:
|
||||
os.chmod(config_file, mode)
|
||||
except Exception as e:
|
||||
tb = traceback.format_exc()
|
||||
warnings.warn("Failed to set permissions on %s:\n%s" % (config_file, tb),
|
||||
RuntimeWarning)
|
||||
|
||||
|
||||
def set_password(password=None, config_file=None):
|
||||
"""Ask user for password, store it in notebook json configuration file"""
|
||||
|
||||
hashed_password = passwd(password)
|
||||
|
||||
with persist_config(config_file) as config:
|
||||
config.NotebookApp.password = hashed_password
|
0
venv/Lib/site-packages/notebook/auth/tests/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/auth/tests/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
48
venv/Lib/site-packages/notebook/auth/tests/test_login.py
Normal file
48
venv/Lib/site-packages/notebook/auth/tests/test_login.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""Tests for login redirects"""
|
||||
|
||||
import requests
|
||||
from tornado.httputil import url_concat
|
||||
|
||||
from notebook.tests.launchnotebook import NotebookTestBase
|
||||
|
||||
|
||||
class LoginTest(NotebookTestBase):
|
||||
def login(self, next):
|
||||
first = requests.get(self.base_url() + "login")
|
||||
first.raise_for_status()
|
||||
resp = requests.post(
|
||||
url_concat(
|
||||
self.base_url() + "login",
|
||||
{'next': next},
|
||||
),
|
||||
allow_redirects=False,
|
||||
data={
|
||||
"password": self.token,
|
||||
"_xsrf": first.cookies.get("_xsrf", ""),
|
||||
},
|
||||
cookies=first.cookies,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
return resp.headers['Location']
|
||||
|
||||
def test_next_bad(self):
|
||||
for bad_next in (
|
||||
"//some-host",
|
||||
"//host" + self.url_prefix + "tree",
|
||||
"https://google.com",
|
||||
"/absolute/not/base_url",
|
||||
):
|
||||
url = self.login(next=bad_next)
|
||||
self.assertEqual(url, self.url_prefix)
|
||||
assert url
|
||||
|
||||
def test_next_ok(self):
|
||||
for next_path in (
|
||||
"tree/",
|
||||
"//" + self.url_prefix + "tree",
|
||||
"notebooks/notebook.ipynb",
|
||||
"tree//something",
|
||||
):
|
||||
expected = self.url_prefix + next_path
|
||||
actual = self.login(next=expected)
|
||||
self.assertEqual(actual, expected)
|
27
venv/Lib/site-packages/notebook/auth/tests/test_security.py
Normal file
27
venv/Lib/site-packages/notebook/auth/tests/test_security.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# coding: utf-8
|
||||
from ..security import passwd, passwd_check
|
||||
import nose.tools as nt
|
||||
|
||||
def test_passwd_structure():
|
||||
p = passwd('passphrase')
|
||||
algorithm, hashed = p.split(':')
|
||||
nt.assert_equal(algorithm, 'argon2')
|
||||
nt.assert_true(hashed.startswith('$argon2id$'))
|
||||
|
||||
def test_roundtrip():
|
||||
p = passwd('passphrase')
|
||||
nt.assert_equal(passwd_check(p, 'passphrase'), True)
|
||||
|
||||
def test_bad():
|
||||
p = passwd('passphrase')
|
||||
nt.assert_equal(passwd_check(p, p), False)
|
||||
nt.assert_equal(passwd_check(p, 'a:b:c:d'), False)
|
||||
nt.assert_equal(passwd_check(p, 'a:b'), False)
|
||||
|
||||
def test_passwd_check_unicode():
|
||||
# GH issue #4524
|
||||
phash = u'sha1:23862bc21dd3:7a415a95ae4580582e314072143d9c382c491e4f'
|
||||
assert passwd_check(phash, u"łe¶ŧ←↓→")
|
||||
phash = (u'argon2:$argon2id$v=19$m=10240,t=10,p=8$'
|
||||
u'qjjDiZUofUVVnrVYxacnbA$l5pQq1bJ8zglGT2uXP6iOg')
|
||||
assert passwd_check(phash, u"łe¶ŧ←↓→")
|
0
venv/Lib/site-packages/notebook/base/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/base/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
947
venv/Lib/site-packages/notebook/base/handlers.py
Normal file
947
venv/Lib/site-packages/notebook/base/handlers.py
Normal file
|
@ -0,0 +1,947 @@
|
|||
"""Base Tornado handlers for the notebook server."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import datetime
|
||||
import functools
|
||||
import ipaddress
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
import warnings
|
||||
from http.client import responses
|
||||
from http.cookies import Morsel
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from jinja2 import TemplateNotFound
|
||||
from tornado import web, gen, escape, httputil
|
||||
from tornado.log import app_log
|
||||
import prometheus_client
|
||||
|
||||
from notebook._sysinfo import get_sys_info
|
||||
|
||||
from traitlets.config import Application
|
||||
from ipython_genutils.path import filefind
|
||||
from ipython_genutils.py3compat import string_types
|
||||
|
||||
import notebook
|
||||
from notebook._tz import utcnow
|
||||
from notebook.i18n import combine_translations
|
||||
from notebook.utils import is_hidden, url_path_join, url_is_absolute, url_escape, urldecode_unix_socket_path
|
||||
from notebook.services.security import csp_report_uri
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Top-level handlers
|
||||
#-----------------------------------------------------------------------------
|
||||
non_alphanum = re.compile(r'[^A-Za-z0-9]')
|
||||
|
||||
_sys_info_cache = None
|
||||
def json_sys_info():
|
||||
global _sys_info_cache
|
||||
if _sys_info_cache is None:
|
||||
_sys_info_cache = json.dumps(get_sys_info())
|
||||
return _sys_info_cache
|
||||
|
||||
def log():
|
||||
if Application.initialized():
|
||||
return Application.instance().log
|
||||
else:
|
||||
return app_log
|
||||
|
||||
class AuthenticatedHandler(web.RequestHandler):
|
||||
"""A RequestHandler with an authenticated user."""
|
||||
|
||||
@property
|
||||
def content_security_policy(self):
|
||||
"""The default Content-Security-Policy header
|
||||
|
||||
Can be overridden by defining Content-Security-Policy in settings['headers']
|
||||
"""
|
||||
if 'Content-Security-Policy' in self.settings.get('headers', {}):
|
||||
# user-specified, don't override
|
||||
return self.settings['headers']['Content-Security-Policy']
|
||||
|
||||
return '; '.join([
|
||||
"frame-ancestors 'self'",
|
||||
# Make sure the report-uri is relative to the base_url
|
||||
"report-uri " + self.settings.get('csp_report_uri', url_path_join(self.base_url, csp_report_uri)),
|
||||
])
|
||||
|
||||
def set_default_headers(self):
|
||||
headers = {}
|
||||
headers["X-Content-Type-Options"] = "nosniff"
|
||||
headers.update(self.settings.get('headers', {}))
|
||||
|
||||
headers["Content-Security-Policy"] = self.content_security_policy
|
||||
|
||||
# Allow for overriding headers
|
||||
for header_name, value in headers.items():
|
||||
try:
|
||||
self.set_header(header_name, value)
|
||||
except Exception as e:
|
||||
# tornado raise Exception (not a subclass)
|
||||
# if method is unsupported (websocket and Access-Control-Allow-Origin
|
||||
# for example, so just ignore)
|
||||
self.log.debug(e)
|
||||
|
||||
def force_clear_cookie(self, name, path="/", domain=None):
|
||||
"""Deletes the cookie with the given name.
|
||||
|
||||
Tornado's cookie handling currently (Jan 2018) stores cookies in a dict
|
||||
keyed by name, so it can only modify one cookie with a given name per
|
||||
response. The browser can store multiple cookies with the same name
|
||||
but different domains and/or paths. This method lets us clear multiple
|
||||
cookies with the same name.
|
||||
|
||||
Due to limitations of the cookie protocol, you must pass the same
|
||||
path and domain to clear a cookie as were used when that cookie
|
||||
was set (but there is no way to find out on the server side
|
||||
which values were used for a given cookie).
|
||||
"""
|
||||
name = escape.native_str(name)
|
||||
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
|
||||
|
||||
morsel = Morsel()
|
||||
morsel.set(name, '', '""')
|
||||
morsel['expires'] = httputil.format_timestamp(expires)
|
||||
morsel['path'] = path
|
||||
if domain:
|
||||
morsel['domain'] = domain
|
||||
self.add_header("Set-Cookie", morsel.OutputString())
|
||||
|
||||
def clear_login_cookie(self):
|
||||
cookie_options = self.settings.get('cookie_options', {})
|
||||
path = cookie_options.setdefault('path', self.base_url)
|
||||
self.clear_cookie(self.cookie_name, path=path)
|
||||
if path and path != '/':
|
||||
# also clear cookie on / to ensure old cookies are cleared
|
||||
# after the change in path behavior (changed in notebook 5.2.2).
|
||||
# N.B. This bypasses the normal cookie handling, which can't update
|
||||
# two cookies with the same name. See the method above.
|
||||
self.force_clear_cookie(self.cookie_name)
|
||||
|
||||
def get_current_user(self):
|
||||
if self.login_handler is None:
|
||||
return 'anonymous'
|
||||
return self.login_handler.get_user(self)
|
||||
|
||||
def skip_check_origin(self):
|
||||
"""Ask my login_handler if I should skip the origin_check
|
||||
|
||||
For example: in the default LoginHandler, if a request is token-authenticated,
|
||||
origin checking should be skipped.
|
||||
"""
|
||||
if self.request.method == 'OPTIONS':
|
||||
# no origin-check on options requests, which are used to check origins!
|
||||
return True
|
||||
if self.login_handler is None or not hasattr(self.login_handler, 'should_check_origin'):
|
||||
return False
|
||||
return not self.login_handler.should_check_origin(self)
|
||||
|
||||
@property
|
||||
def token_authenticated(self):
|
||||
"""Have I been authenticated with a token?"""
|
||||
if self.login_handler is None or not hasattr(self.login_handler, 'is_token_authenticated'):
|
||||
return False
|
||||
return self.login_handler.is_token_authenticated(self)
|
||||
|
||||
@property
|
||||
def cookie_name(self):
|
||||
default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
|
||||
self.request.host
|
||||
))
|
||||
return self.settings.get('cookie_name', default_cookie_name)
|
||||
|
||||
@property
|
||||
def logged_in(self):
|
||||
"""Is a user currently logged in?"""
|
||||
user = self.get_current_user()
|
||||
return (user and not user == 'anonymous')
|
||||
|
||||
@property
|
||||
def login_handler(self):
|
||||
"""Return the login handler for this application, if any."""
|
||||
return self.settings.get('login_handler_class', None)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
"""Return the login token for this application, if any."""
|
||||
return self.settings.get('token', None)
|
||||
|
||||
@property
|
||||
def login_available(self):
|
||||
"""May a user proceed to log in?
|
||||
|
||||
This returns True if login capability is available, irrespective of
|
||||
whether the user is already logged in or not.
|
||||
|
||||
"""
|
||||
if self.login_handler is None:
|
||||
return False
|
||||
return bool(self.login_handler.get_login_available(self.settings))
|
||||
|
||||
|
||||
class IPythonHandler(AuthenticatedHandler):
|
||||
"""IPython-specific extensions to authenticated handling
|
||||
|
||||
Mostly property shortcuts to IPython-specific settings.
|
||||
"""
|
||||
|
||||
@property
|
||||
def ignore_minified_js(self):
|
||||
"""Wether to user bundle in template. (*.min files)
|
||||
|
||||
Mainly use for development and avoid file recompilation
|
||||
"""
|
||||
return self.settings.get('ignore_minified_js', False)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
return self.settings.get('config', None)
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
"""use the IPython log by default, falling back on tornado's logger"""
|
||||
return log()
|
||||
|
||||
@property
|
||||
def jinja_template_vars(self):
|
||||
"""User-supplied values to supply to jinja templates."""
|
||||
return self.settings.get('jinja_template_vars', {})
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# URLs
|
||||
#---------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def version_hash(self):
|
||||
"""The version hash to use for cache hints for static files"""
|
||||
return self.settings.get('version_hash', '')
|
||||
|
||||
@property
|
||||
def mathjax_url(self):
|
||||
url = self.settings.get('mathjax_url', '')
|
||||
if not url or url_is_absolute(url):
|
||||
return url
|
||||
return url_path_join(self.base_url, url)
|
||||
|
||||
@property
|
||||
def mathjax_config(self):
|
||||
return self.settings.get('mathjax_config', 'TeX-AMS-MML_HTMLorMML-full,Safe')
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
return self.settings.get('base_url', '/')
|
||||
|
||||
@property
|
||||
def default_url(self):
|
||||
return self.settings.get('default_url', '')
|
||||
|
||||
@property
|
||||
def ws_url(self):
|
||||
return self.settings.get('websocket_url', '')
|
||||
|
||||
@property
|
||||
def contents_js_source(self):
|
||||
self.log.debug("Using contents: %s", self.settings.get('contents_js_source',
|
||||
'services/contents'))
|
||||
return self.settings.get('contents_js_source', 'services/contents')
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# Manager objects
|
||||
#---------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def kernel_manager(self):
|
||||
return self.settings['kernel_manager']
|
||||
|
||||
@property
|
||||
def contents_manager(self):
|
||||
return self.settings['contents_manager']
|
||||
|
||||
@property
|
||||
def session_manager(self):
|
||||
return self.settings['session_manager']
|
||||
|
||||
@property
|
||||
def terminal_manager(self):
|
||||
return self.settings['terminal_manager']
|
||||
|
||||
@property
|
||||
def kernel_spec_manager(self):
|
||||
return self.settings['kernel_spec_manager']
|
||||
|
||||
@property
|
||||
def config_manager(self):
|
||||
return self.settings['config_manager']
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# CORS
|
||||
#---------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def allow_origin(self):
|
||||
"""Normal Access-Control-Allow-Origin"""
|
||||
return self.settings.get('allow_origin', '')
|
||||
|
||||
@property
|
||||
def allow_origin_pat(self):
|
||||
"""Regular expression version of allow_origin"""
|
||||
return self.settings.get('allow_origin_pat', None)
|
||||
|
||||
@property
|
||||
def allow_credentials(self):
|
||||
"""Whether to set Access-Control-Allow-Credentials"""
|
||||
return self.settings.get('allow_credentials', False)
|
||||
|
||||
def set_default_headers(self):
|
||||
"""Add CORS headers, if defined"""
|
||||
super(IPythonHandler, self).set_default_headers()
|
||||
if self.allow_origin:
|
||||
self.set_header("Access-Control-Allow-Origin", self.allow_origin)
|
||||
elif self.allow_origin_pat:
|
||||
origin = self.get_origin()
|
||||
if origin and self.allow_origin_pat.match(origin):
|
||||
self.set_header("Access-Control-Allow-Origin", origin)
|
||||
elif (
|
||||
self.token_authenticated
|
||||
and "Access-Control-Allow-Origin" not in
|
||||
self.settings.get('headers', {})
|
||||
):
|
||||
# allow token-authenticated requests cross-origin by default.
|
||||
# only apply this exception if allow-origin has not been specified.
|
||||
self.set_header('Access-Control-Allow-Origin',
|
||||
self.request.headers.get('Origin', ''))
|
||||
|
||||
if self.allow_credentials:
|
||||
self.set_header("Access-Control-Allow-Credentials", 'true')
|
||||
|
||||
def set_attachment_header(self, filename):
|
||||
"""Set Content-Disposition: attachment header
|
||||
|
||||
As a method to ensure handling of filename encoding
|
||||
"""
|
||||
escaped_filename = url_escape(filename)
|
||||
self.set_header('Content-Disposition',
|
||||
'attachment;'
|
||||
" filename*=utf-8''{utf8}"
|
||||
.format(
|
||||
utf8=escaped_filename,
|
||||
)
|
||||
)
|
||||
|
||||
def get_origin(self):
|
||||
# Handle WebSocket Origin naming convention differences
|
||||
# The difference between version 8 and 13 is that in 8 the
|
||||
# client sends a "Sec-Websocket-Origin" header and in 13 it's
|
||||
# simply "Origin".
|
||||
if "Origin" in self.request.headers:
|
||||
origin = self.request.headers.get("Origin")
|
||||
else:
|
||||
origin = self.request.headers.get("Sec-Websocket-Origin", None)
|
||||
return origin
|
||||
|
||||
# origin_to_satisfy_tornado is present because tornado requires
|
||||
# check_origin to take an origin argument, but we don't use it
|
||||
def check_origin(self, origin_to_satisfy_tornado=""):
|
||||
"""Check Origin for cross-site API requests, including websockets
|
||||
|
||||
Copied from WebSocket with changes:
|
||||
|
||||
- allow unspecified host/origin (e.g. scripts)
|
||||
- allow token-authenticated requests
|
||||
"""
|
||||
if self.allow_origin == '*' or self.skip_check_origin():
|
||||
return True
|
||||
|
||||
host = self.request.headers.get("Host")
|
||||
origin = self.request.headers.get("Origin")
|
||||
|
||||
# If no header is provided, let the request through.
|
||||
# Origin can be None for:
|
||||
# - same-origin (IE, Firefox)
|
||||
# - Cross-site POST form (IE, Firefox)
|
||||
# - Scripts
|
||||
# The cross-site POST (XSRF) case is handled by tornado's xsrf_token
|
||||
if origin is None or host is None:
|
||||
return True
|
||||
|
||||
origin = origin.lower()
|
||||
origin_host = urlparse(origin).netloc
|
||||
|
||||
# OK if origin matches host
|
||||
if origin_host == host:
|
||||
return True
|
||||
|
||||
# Check CORS headers
|
||||
if self.allow_origin:
|
||||
allow = self.allow_origin == origin
|
||||
elif self.allow_origin_pat:
|
||||
allow = bool(self.allow_origin_pat.match(origin))
|
||||
else:
|
||||
# No CORS headers deny the request
|
||||
allow = False
|
||||
if not allow:
|
||||
self.log.warning("Blocking Cross Origin API request for %s. Origin: %s, Host: %s",
|
||||
self.request.path, origin, host,
|
||||
)
|
||||
return allow
|
||||
|
||||
def check_referer(self):
|
||||
"""Check Referer for cross-site requests.
|
||||
|
||||
Disables requests to certain endpoints with
|
||||
external or missing Referer.
|
||||
|
||||
If set, allow_origin settings are applied to the Referer
|
||||
to whitelist specific cross-origin sites.
|
||||
|
||||
Used on GET for api endpoints and /files/
|
||||
to block cross-site inclusion (XSSI).
|
||||
"""
|
||||
host = self.request.headers.get("Host")
|
||||
referer = self.request.headers.get("Referer")
|
||||
|
||||
if not host:
|
||||
self.log.warning("Blocking request with no host")
|
||||
return False
|
||||
if not referer:
|
||||
self.log.warning("Blocking request with no referer")
|
||||
return False
|
||||
|
||||
referer_url = urlparse(referer)
|
||||
referer_host = referer_url.netloc
|
||||
if referer_host == host:
|
||||
return True
|
||||
|
||||
# apply cross-origin checks to Referer:
|
||||
origin = "{}://{}".format(referer_url.scheme, referer_url.netloc)
|
||||
if self.allow_origin:
|
||||
allow = self.allow_origin == origin
|
||||
elif self.allow_origin_pat:
|
||||
allow = bool(self.allow_origin_pat.match(origin))
|
||||
else:
|
||||
# No CORS settings, deny the request
|
||||
allow = False
|
||||
|
||||
if not allow:
|
||||
self.log.warning("Blocking Cross Origin request for %s. Referer: %s, Host: %s",
|
||||
self.request.path, origin, host,
|
||||
)
|
||||
return allow
|
||||
|
||||
def check_xsrf_cookie(self):
|
||||
"""Bypass xsrf cookie checks when token-authenticated"""
|
||||
if self.token_authenticated or self.settings.get('disable_check_xsrf', False):
|
||||
# Token-authenticated requests do not need additional XSRF-check
|
||||
# Servers without authentication are vulnerable to XSRF
|
||||
return
|
||||
try:
|
||||
return super(IPythonHandler, self).check_xsrf_cookie()
|
||||
except web.HTTPError as e:
|
||||
if self.request.method in {'GET', 'HEAD'}:
|
||||
# Consider Referer a sufficient cross-origin check for GET requests
|
||||
if not self.check_referer():
|
||||
referer = self.request.headers.get('Referer')
|
||||
if referer:
|
||||
msg = "Blocking Cross Origin request from {}.".format(referer)
|
||||
else:
|
||||
msg = "Blocking request from unknown origin"
|
||||
raise web.HTTPError(403, msg) from e
|
||||
else:
|
||||
raise
|
||||
|
||||
def check_host(self):
|
||||
"""Check the host header if remote access disallowed.
|
||||
|
||||
Returns True if the request should continue, False otherwise.
|
||||
"""
|
||||
if self.settings.get('allow_remote_access', False):
|
||||
return True
|
||||
|
||||
# Remove port (e.g. ':8888') from host
|
||||
host = re.match(r'^(.*?)(:\d+)?$', self.request.host).group(1)
|
||||
|
||||
# Browsers format IPv6 addresses like [::1]; we need to remove the []
|
||||
if host.startswith('[') and host.endswith(']'):
|
||||
host = host[1:-1]
|
||||
|
||||
# UNIX socket handling
|
||||
check_host = urldecode_unix_socket_path(host)
|
||||
if check_host.startswith('/') and os.path.exists(check_host):
|
||||
allow = True
|
||||
else:
|
||||
try:
|
||||
addr = ipaddress.ip_address(host)
|
||||
except ValueError:
|
||||
# Not an IP address: check against hostnames
|
||||
allow = host in self.settings.get('local_hostnames', ['localhost'])
|
||||
else:
|
||||
allow = addr.is_loopback
|
||||
|
||||
if not allow:
|
||||
self.log.warning(
|
||||
("Blocking request with non-local 'Host' %s (%s). "
|
||||
"If the notebook should be accessible at that name, "
|
||||
"set NotebookApp.allow_remote_access to disable the check."),
|
||||
host, self.request.host
|
||||
)
|
||||
return allow
|
||||
|
||||
def prepare(self):
|
||||
if not self.check_host():
|
||||
raise web.HTTPError(403)
|
||||
return super(IPythonHandler, self).prepare()
|
||||
|
||||
#---------------------------------------------------------------
|
||||
# template rendering
|
||||
#---------------------------------------------------------------
|
||||
|
||||
def get_template(self, name):
|
||||
"""Return the jinja template object for a given name"""
|
||||
return self.settings['jinja2_env'].get_template(name)
|
||||
|
||||
def render_template(self, name, **ns):
|
||||
ns.update(self.template_namespace)
|
||||
template = self.get_template(name)
|
||||
return template.render(**ns)
|
||||
|
||||
@property
|
||||
def template_namespace(self):
|
||||
return dict(
|
||||
base_url=self.base_url,
|
||||
default_url=self.default_url,
|
||||
ws_url=self.ws_url,
|
||||
logged_in=self.logged_in,
|
||||
allow_password_change=self.settings.get('allow_password_change'),
|
||||
login_available=self.login_available,
|
||||
token_available=bool(self.token),
|
||||
static_url=self.static_url,
|
||||
sys_info=json_sys_info(),
|
||||
contents_js_source=self.contents_js_source,
|
||||
version_hash=self.version_hash,
|
||||
ignore_minified_js=self.ignore_minified_js,
|
||||
xsrf_form_html=self.xsrf_form_html,
|
||||
token=self.token,
|
||||
xsrf_token=self.xsrf_token.decode('utf8'),
|
||||
nbjs_translations=json.dumps(combine_translations(
|
||||
self.request.headers.get('Accept-Language', ''))),
|
||||
**self.jinja_template_vars
|
||||
)
|
||||
|
||||
def get_json_body(self):
|
||||
"""Return the body of the request as JSON data."""
|
||||
if not self.request.body:
|
||||
return None
|
||||
# Do we need to call body.decode('utf-8') here?
|
||||
body = self.request.body.strip().decode(u'utf-8')
|
||||
try:
|
||||
model = json.loads(body)
|
||||
except Exception as e:
|
||||
self.log.debug("Bad JSON: %r", body)
|
||||
self.log.error("Couldn't parse JSON", exc_info=True)
|
||||
raise web.HTTPError(400, u'Invalid JSON in body of request') from e
|
||||
return model
|
||||
|
||||
def write_error(self, status_code, **kwargs):
|
||||
"""render custom error pages"""
|
||||
exc_info = kwargs.get('exc_info')
|
||||
message = ''
|
||||
status_message = responses.get(status_code, 'Unknown HTTP Error')
|
||||
exception = '(unknown)'
|
||||
if exc_info:
|
||||
exception = exc_info[1]
|
||||
# get the custom message, if defined
|
||||
try:
|
||||
message = exception.log_message % exception.args
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# construct the custom reason, if defined
|
||||
reason = getattr(exception, 'reason', '')
|
||||
if reason:
|
||||
status_message = reason
|
||||
|
||||
# build template namespace
|
||||
ns = dict(
|
||||
status_code=status_code,
|
||||
status_message=status_message,
|
||||
message=message,
|
||||
exception=exception,
|
||||
)
|
||||
|
||||
self.set_header('Content-Type', 'text/html')
|
||||
# render the template
|
||||
try:
|
||||
html = self.render_template('%s.html' % status_code, **ns)
|
||||
except TemplateNotFound:
|
||||
html = self.render_template('error.html', **ns)
|
||||
|
||||
self.write(html)
|
||||
|
||||
|
||||
class APIHandler(IPythonHandler):
|
||||
"""Base class for API handlers"""
|
||||
|
||||
def prepare(self):
|
||||
if not self.check_origin():
|
||||
raise web.HTTPError(404)
|
||||
return super(APIHandler, self).prepare()
|
||||
|
||||
def write_error(self, status_code, **kwargs):
|
||||
"""APIHandler errors are JSON, not human pages"""
|
||||
self.set_header('Content-Type', 'application/json')
|
||||
message = responses.get(status_code, 'Unknown HTTP Error')
|
||||
reply = {
|
||||
'message': message,
|
||||
}
|
||||
exc_info = kwargs.get('exc_info')
|
||||
if exc_info:
|
||||
e = exc_info[1]
|
||||
if isinstance(e, HTTPError):
|
||||
reply['message'] = e.log_message or message
|
||||
reply['reason'] = e.reason
|
||||
else:
|
||||
reply['message'] = 'Unhandled error'
|
||||
reply['reason'] = None
|
||||
reply['traceback'] = ''.join(traceback.format_exception(*exc_info))
|
||||
self.log.warning(reply['message'])
|
||||
self.finish(json.dumps(reply))
|
||||
|
||||
def get_current_user(self):
|
||||
"""Raise 403 on API handlers instead of redirecting to human login page"""
|
||||
# preserve _user_cache so we don't raise more than once
|
||||
if hasattr(self, '_user_cache'):
|
||||
return self._user_cache
|
||||
self._user_cache = user = super(APIHandler, self).get_current_user()
|
||||
return user
|
||||
|
||||
def get_login_url(self):
|
||||
# if get_login_url is invoked in an API handler,
|
||||
# that means @web.authenticated is trying to trigger a redirect.
|
||||
# instead of redirecting, raise 403 instead.
|
||||
if not self.current_user:
|
||||
raise web.HTTPError(403)
|
||||
return super(APIHandler, self).get_login_url()
|
||||
|
||||
@property
|
||||
def content_security_policy(self):
|
||||
csp = '; '.join([
|
||||
super(APIHandler, self).content_security_policy,
|
||||
"default-src 'none'",
|
||||
])
|
||||
return csp
|
||||
|
||||
# set _track_activity = False on API handlers that shouldn't track activity
|
||||
_track_activity = True
|
||||
|
||||
def update_api_activity(self):
|
||||
"""Update last_activity of API requests"""
|
||||
# record activity of authenticated requests
|
||||
if (
|
||||
self._track_activity
|
||||
and getattr(self, '_user_cache', None)
|
||||
and self.get_argument('no_track_activity', None) is None
|
||||
):
|
||||
self.settings['api_last_activity'] = utcnow()
|
||||
|
||||
def finish(self, *args, **kwargs):
|
||||
self.update_api_activity()
|
||||
self.set_header('Content-Type', 'application/json')
|
||||
return super(APIHandler, self).finish(*args, **kwargs)
|
||||
|
||||
def options(self, *args, **kwargs):
|
||||
if 'Access-Control-Allow-Headers' in self.settings.get('headers', {}):
|
||||
self.set_header('Access-Control-Allow-Headers', self.settings['headers']['Access-Control-Allow-Headers'])
|
||||
else:
|
||||
self.set_header('Access-Control-Allow-Headers',
|
||||
'accept, content-type, authorization, x-xsrftoken')
|
||||
self.set_header('Access-Control-Allow-Methods',
|
||||
'GET, PUT, POST, PATCH, DELETE, OPTIONS')
|
||||
|
||||
# if authorization header is requested,
|
||||
# that means the request is token-authenticated.
|
||||
# avoid browser-side rejection of the preflight request.
|
||||
# only allow this exception if allow_origin has not been specified
|
||||
# and notebook authentication is enabled.
|
||||
# If the token is not valid, the 'real' request will still be rejected.
|
||||
requested_headers = self.request.headers.get('Access-Control-Request-Headers', '').split(',')
|
||||
if requested_headers and any(
|
||||
h.strip().lower() == 'authorization'
|
||||
for h in requested_headers
|
||||
) and (
|
||||
# FIXME: it would be even better to check specifically for token-auth,
|
||||
# but there is currently no API for this.
|
||||
self.login_available
|
||||
) and (
|
||||
self.allow_origin
|
||||
or self.allow_origin_pat
|
||||
or 'Access-Control-Allow-Origin' in self.settings.get('headers', {})
|
||||
):
|
||||
self.set_header('Access-Control-Allow-Origin',
|
||||
self.request.headers.get('Origin', ''))
|
||||
|
||||
|
||||
class Template404(IPythonHandler):
|
||||
"""Render our 404 template"""
|
||||
def prepare(self):
|
||||
raise web.HTTPError(404)
|
||||
|
||||
|
||||
class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
|
||||
"""static files should only be accessible when logged in"""
|
||||
|
||||
@property
|
||||
def content_security_policy(self):
|
||||
# In case we're serving HTML/SVG, confine any Javascript to a unique
|
||||
# origin so it can't interact with the notebook server.
|
||||
return super(AuthenticatedFileHandler, self).content_security_policy + \
|
||||
"; sandbox allow-scripts"
|
||||
|
||||
@web.authenticated
|
||||
def head(self, path):
|
||||
self.check_xsrf_cookie()
|
||||
return super(AuthenticatedFileHandler, self).head(path)
|
||||
|
||||
@web.authenticated
|
||||
def get(self, path):
|
||||
self.check_xsrf_cookie()
|
||||
|
||||
if os.path.splitext(path)[1] == '.ipynb' or self.get_argument("download", False):
|
||||
name = path.rsplit('/', 1)[-1]
|
||||
self.set_attachment_header(name)
|
||||
|
||||
return web.StaticFileHandler.get(self, path)
|
||||
|
||||
def get_content_type(self):
|
||||
path = self.absolute_path.strip('/')
|
||||
if '/' in path:
|
||||
_, name = path.rsplit('/', 1)
|
||||
else:
|
||||
name = path
|
||||
if name.endswith('.ipynb'):
|
||||
return 'application/x-ipynb+json'
|
||||
else:
|
||||
cur_mime = mimetypes.guess_type(name)[0]
|
||||
if cur_mime == 'text/plain':
|
||||
return 'text/plain; charset=UTF-8'
|
||||
else:
|
||||
return super(AuthenticatedFileHandler, self).get_content_type()
|
||||
|
||||
def set_headers(self):
|
||||
super(AuthenticatedFileHandler, self).set_headers()
|
||||
# disable browser caching, rely on 304 replies for savings
|
||||
if "v" not in self.request.arguments:
|
||||
self.add_header("Cache-Control", "no-cache")
|
||||
|
||||
def compute_etag(self):
|
||||
return None
|
||||
|
||||
def validate_absolute_path(self, root, absolute_path):
|
||||
"""Validate and return the absolute path.
|
||||
|
||||
Requires tornado 3.1
|
||||
|
||||
Adding to tornado's own handling, forbids the serving of hidden files.
|
||||
"""
|
||||
abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
|
||||
abs_root = os.path.abspath(root)
|
||||
if is_hidden(abs_path, abs_root) and not self.contents_manager.allow_hidden:
|
||||
self.log.info("Refusing to serve hidden file, via 404 Error, use flag 'ContentsManager.allow_hidden' to enable")
|
||||
raise web.HTTPError(404)
|
||||
return abs_path
|
||||
|
||||
|
||||
def json_errors(method):
|
||||
"""Decorate methods with this to return GitHub style JSON errors.
|
||||
|
||||
This should be used on any JSON API on any handler method that can raise HTTPErrors.
|
||||
|
||||
This will grab the latest HTTPError exception using sys.exc_info
|
||||
and then:
|
||||
|
||||
1. Set the HTTP status code based on the HTTPError
|
||||
2. Create and return a JSON body with a message field describing
|
||||
the error in a human readable form.
|
||||
"""
|
||||
warnings.warn('@json_errors is deprecated in notebook 5.2.0. Subclass APIHandler instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
@functools.wraps(method)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
self.write_error = types.MethodType(APIHandler.write_error, self)
|
||||
return method(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# File handler
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# to minimize subclass changes:
|
||||
HTTPError = web.HTTPError
|
||||
|
||||
class FileFindHandler(IPythonHandler, web.StaticFileHandler):
|
||||
"""subclass of StaticFileHandler for serving files from a search path"""
|
||||
|
||||
# cache search results, don't search for files more than once
|
||||
_static_paths = {}
|
||||
|
||||
def set_headers(self):
|
||||
super(FileFindHandler, self).set_headers()
|
||||
# disable browser caching, rely on 304 replies for savings
|
||||
if "v" not in self.request.arguments or \
|
||||
any(self.request.path.startswith(path) for path in self.no_cache_paths):
|
||||
self.set_header("Cache-Control", "no-cache")
|
||||
|
||||
def initialize(self, path, default_filename=None, no_cache_paths=None):
|
||||
self.no_cache_paths = no_cache_paths or []
|
||||
|
||||
if isinstance(path, string_types):
|
||||
path = [path]
|
||||
|
||||
self.root = tuple(
|
||||
os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
|
||||
)
|
||||
self.default_filename = default_filename
|
||||
|
||||
def compute_etag(self):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_absolute_path(cls, roots, path):
|
||||
"""locate a file to serve on our static file search path"""
|
||||
with cls._lock:
|
||||
if path in cls._static_paths:
|
||||
return cls._static_paths[path]
|
||||
try:
|
||||
abspath = os.path.abspath(filefind(path, roots))
|
||||
except IOError:
|
||||
# IOError means not found
|
||||
return ''
|
||||
|
||||
cls._static_paths[path] = abspath
|
||||
|
||||
|
||||
log().debug("Path %s served from %s"%(path, abspath))
|
||||
return abspath
|
||||
|
||||
def validate_absolute_path(self, root, absolute_path):
|
||||
"""check if the file should be served (raises 404, 403, etc.)"""
|
||||
if absolute_path == '':
|
||||
raise web.HTTPError(404)
|
||||
|
||||
for root in self.root:
|
||||
if (absolute_path + os.sep).startswith(root):
|
||||
break
|
||||
|
||||
return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
|
||||
|
||||
|
||||
class APIVersionHandler(APIHandler):
|
||||
|
||||
def get(self):
|
||||
# not authenticated, so give as few info as possible
|
||||
self.finish(json.dumps({"version":notebook.__version__}))
|
||||
|
||||
|
||||
class TrailingSlashHandler(web.RequestHandler):
|
||||
"""Simple redirect handler that strips trailing slashes
|
||||
|
||||
This should be the first, highest priority handler.
|
||||
"""
|
||||
|
||||
def get(self):
|
||||
path, *rest = self.request.uri.partition("?")
|
||||
# trim trailing *and* leading /
|
||||
# to avoid misinterpreting repeated '//'
|
||||
path = "/" + path.strip("/")
|
||||
new_uri = "".join([path, *rest])
|
||||
self.redirect(new_uri)
|
||||
|
||||
post = put = get
|
||||
|
||||
|
||||
class FilesRedirectHandler(IPythonHandler):
|
||||
"""Handler for redirecting relative URLs to the /files/ handler"""
|
||||
|
||||
@staticmethod
|
||||
def redirect_to_files(self, path):
|
||||
"""make redirect logic a reusable static method
|
||||
|
||||
so it can be called from other handlers.
|
||||
"""
|
||||
cm = self.contents_manager
|
||||
if cm.dir_exists(path):
|
||||
# it's a *directory*, redirect to /tree
|
||||
url = url_path_join(self.base_url, 'tree', url_escape(path))
|
||||
else:
|
||||
orig_path = path
|
||||
# otherwise, redirect to /files
|
||||
parts = path.split('/')
|
||||
|
||||
if not cm.file_exists(path=path) and 'files' in parts:
|
||||
# redirect without files/ iff it would 404
|
||||
# this preserves pre-2.0-style 'files/' links
|
||||
self.log.warning("Deprecated files/ URL: %s", orig_path)
|
||||
parts.remove('files')
|
||||
path = '/'.join(parts)
|
||||
|
||||
if not cm.file_exists(path=path):
|
||||
raise web.HTTPError(404)
|
||||
|
||||
url = url_path_join(self.base_url, 'files', url_escape(path))
|
||||
self.log.debug("Redirecting %s to %s", self.request.path, url)
|
||||
self.redirect(url)
|
||||
|
||||
def get(self, path=''):
|
||||
return self.redirect_to_files(self, path)
|
||||
|
||||
|
||||
class RedirectWithParams(web.RequestHandler):
|
||||
"""Sam as web.RedirectHandler, but preserves URL parameters"""
|
||||
def initialize(self, url, permanent=True):
|
||||
self._url = url
|
||||
self._permanent = permanent
|
||||
|
||||
def get(self):
|
||||
sep = '&' if '?' in self._url else '?'
|
||||
url = sep.join([self._url, self.request.query])
|
||||
self.redirect(url, permanent=self._permanent)
|
||||
|
||||
|
||||
class PrometheusMetricsHandler(IPythonHandler):
|
||||
"""
|
||||
Return prometheus metrics for this notebook server
|
||||
"""
|
||||
@web.authenticated
|
||||
def get(self):
|
||||
self.set_header('Content-Type', prometheus_client.CONTENT_TYPE_LATEST)
|
||||
self.write(prometheus_client.generate_latest(prometheus_client.REGISTRY))
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# URL pattern fragments for re-use
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
# path matches any number of `/foo[/bar...]` or just `/` or ''
|
||||
path_regex = r"(?P<path>(?:(?:/[^/]+)+|/?))"
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# URL to handler mappings
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
default_handlers = [
|
||||
(r".*/", TrailingSlashHandler),
|
||||
(r"api", APIVersionHandler),
|
||||
(r'/(robots\.txt|favicon\.ico)', web.StaticFileHandler),
|
||||
(r'/metrics', PrometheusMetricsHandler)
|
||||
]
|
300
venv/Lib/site-packages/notebook/base/zmqhandlers.py
Normal file
300
venv/Lib/site-packages/notebook/base/zmqhandlers.py
Normal file
|
@ -0,0 +1,300 @@
|
|||
# coding: utf-8
|
||||
"""Tornado handlers for WebSocket <-> ZMQ sockets."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import json
|
||||
import struct
|
||||
import sys
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import tornado
|
||||
from tornado import gen, ioloop, web
|
||||
from tornado.iostream import StreamClosedError
|
||||
from tornado.websocket import WebSocketHandler, WebSocketClosedError
|
||||
|
||||
from jupyter_client.session import Session
|
||||
from jupyter_client.jsonutil import date_default, extract_dates
|
||||
from ipython_genutils.py3compat import cast_unicode
|
||||
|
||||
from notebook.utils import maybe_future
|
||||
from .handlers import IPythonHandler
|
||||
|
||||
|
||||
def serialize_binary_message(msg):
|
||||
"""serialize a message as a binary blob
|
||||
|
||||
Header:
|
||||
|
||||
4 bytes: number of msg parts (nbufs) as 32b int
|
||||
4 * nbufs bytes: offset for each buffer as integer as 32b int
|
||||
|
||||
Offsets are from the start of the buffer, including the header.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
The message serialized to bytes.
|
||||
|
||||
"""
|
||||
# don't modify msg or buffer list in-place
|
||||
msg = msg.copy()
|
||||
buffers = list(msg.pop('buffers'))
|
||||
if sys.version_info < (3, 4):
|
||||
buffers = [x.tobytes() for x in buffers]
|
||||
bmsg = json.dumps(msg, default=date_default).encode('utf8')
|
||||
buffers.insert(0, bmsg)
|
||||
nbufs = len(buffers)
|
||||
offsets = [4 * (nbufs + 1)]
|
||||
for buf in buffers[:-1]:
|
||||
offsets.append(offsets[-1] + len(buf))
|
||||
offsets_buf = struct.pack('!' + 'I' * (nbufs + 1), nbufs, *offsets)
|
||||
buffers.insert(0, offsets_buf)
|
||||
return b''.join(buffers)
|
||||
|
||||
|
||||
def deserialize_binary_message(bmsg):
|
||||
"""deserialize a message from a binary blog
|
||||
|
||||
Header:
|
||||
|
||||
4 bytes: number of msg parts (nbufs) as 32b int
|
||||
4 * nbufs bytes: offset for each buffer as integer as 32b int
|
||||
|
||||
Offsets are from the start of the buffer, including the header.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
message dictionary
|
||||
"""
|
||||
nbufs = struct.unpack('!i', bmsg[:4])[0]
|
||||
offsets = list(struct.unpack('!' + 'I' * nbufs, bmsg[4:4*(nbufs+1)]))
|
||||
offsets.append(None)
|
||||
bufs = []
|
||||
for start, stop in zip(offsets[:-1], offsets[1:]):
|
||||
bufs.append(bmsg[start:stop])
|
||||
msg = json.loads(bufs[0].decode('utf8'))
|
||||
msg['header'] = extract_dates(msg['header'])
|
||||
msg['parent_header'] = extract_dates(msg['parent_header'])
|
||||
msg['buffers'] = bufs[1:]
|
||||
return msg
|
||||
|
||||
# ping interval for keeping websockets alive (30 seconds)
|
||||
WS_PING_INTERVAL = 30000
|
||||
|
||||
|
||||
class WebSocketMixin(object):
|
||||
"""Mixin for common websocket options"""
|
||||
ping_callback = None
|
||||
last_ping = 0
|
||||
last_pong = 0
|
||||
stream = None
|
||||
|
||||
@property
|
||||
def ping_interval(self):
|
||||
"""The interval for websocket keep-alive pings.
|
||||
|
||||
Set ws_ping_interval = 0 to disable pings.
|
||||
"""
|
||||
return self.settings.get('ws_ping_interval', WS_PING_INTERVAL)
|
||||
|
||||
@property
|
||||
def ping_timeout(self):
|
||||
"""If no ping is received in this many milliseconds,
|
||||
close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
|
||||
Default is max of 3 pings or 30 seconds.
|
||||
"""
|
||||
return self.settings.get('ws_ping_timeout',
|
||||
max(3 * self.ping_interval, WS_PING_INTERVAL)
|
||||
)
|
||||
|
||||
def check_origin(self, origin=None):
|
||||
"""Check Origin == Host or Access-Control-Allow-Origin.
|
||||
|
||||
Tornado >= 4 calls this method automatically, raising 403 if it returns False.
|
||||
"""
|
||||
|
||||
if self.allow_origin == '*' or (
|
||||
hasattr(self, 'skip_check_origin') and self.skip_check_origin()):
|
||||
return True
|
||||
|
||||
host = self.request.headers.get("Host")
|
||||
if origin is None:
|
||||
origin = self.get_origin()
|
||||
|
||||
# If no origin or host header is provided, assume from script
|
||||
if origin is None or host is None:
|
||||
return True
|
||||
|
||||
origin = origin.lower()
|
||||
origin_host = urlparse(origin).netloc
|
||||
|
||||
# OK if origin matches host
|
||||
if origin_host == host:
|
||||
return True
|
||||
|
||||
# Check CORS headers
|
||||
if self.allow_origin:
|
||||
allow = self.allow_origin == origin
|
||||
elif self.allow_origin_pat:
|
||||
allow = bool(self.allow_origin_pat.match(origin))
|
||||
else:
|
||||
# No CORS headers deny the request
|
||||
allow = False
|
||||
if not allow:
|
||||
self.log.warning("Blocking Cross Origin WebSocket Attempt. Origin: %s, Host: %s",
|
||||
origin, host,
|
||||
)
|
||||
return allow
|
||||
|
||||
def clear_cookie(self, *args, **kwargs):
|
||||
"""meaningless for websockets"""
|
||||
pass
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
self.log.debug("Opening websocket %s", self.request.path)
|
||||
|
||||
# start the pinging
|
||||
if self.ping_interval > 0:
|
||||
loop = ioloop.IOLoop.current()
|
||||
self.last_ping = loop.time() # Remember time of last ping
|
||||
self.last_pong = self.last_ping
|
||||
self.ping_callback = ioloop.PeriodicCallback(
|
||||
self.send_ping, self.ping_interval,
|
||||
)
|
||||
self.ping_callback.start()
|
||||
return super(WebSocketMixin, self).open(*args, **kwargs)
|
||||
|
||||
def send_ping(self):
|
||||
"""send a ping to keep the websocket alive"""
|
||||
if self.ws_connection is None and self.ping_callback is not None:
|
||||
self.ping_callback.stop()
|
||||
return
|
||||
|
||||
# check for timeout on pong. Make sure that we really have sent a recent ping in
|
||||
# case the machine with both server and client has been suspended since the last ping.
|
||||
now = ioloop.IOLoop.current().time()
|
||||
since_last_pong = 1e3 * (now - self.last_pong)
|
||||
since_last_ping = 1e3 * (now - self.last_ping)
|
||||
if since_last_ping < 2*self.ping_interval and since_last_pong > self.ping_timeout:
|
||||
self.log.warning("WebSocket ping timeout after %i ms.", since_last_pong)
|
||||
self.close()
|
||||
return
|
||||
try:
|
||||
self.ping(b'')
|
||||
except (StreamClosedError, WebSocketClosedError):
|
||||
# websocket has been closed, stop pinging
|
||||
self.ping_callback.stop()
|
||||
return
|
||||
|
||||
self.last_ping = now
|
||||
|
||||
def on_pong(self, data):
|
||||
self.last_pong = ioloop.IOLoop.current().time()
|
||||
|
||||
|
||||
class ZMQStreamHandler(WebSocketMixin, WebSocketHandler):
|
||||
|
||||
if tornado.version_info < (4,1):
|
||||
"""Backport send_error from tornado 4.1 to 4.0"""
|
||||
def send_error(self, *args, **kwargs):
|
||||
if self.stream is None:
|
||||
super(WebSocketHandler, self).send_error(*args, **kwargs)
|
||||
else:
|
||||
# If we get an uncaught exception during the handshake,
|
||||
# we have no choice but to abruptly close the connection.
|
||||
# TODO: for uncaught exceptions after the handshake,
|
||||
# we can close the connection more gracefully.
|
||||
self.stream.close()
|
||||
|
||||
|
||||
def _reserialize_reply(self, msg_or_list, channel=None):
|
||||
"""Reserialize a reply message using JSON.
|
||||
|
||||
msg_or_list can be an already-deserialized msg dict or the zmq buffer list.
|
||||
If it is the zmq list, it will be deserialized with self.session.
|
||||
|
||||
This takes the msg list from the ZMQ socket and serializes the result for the websocket.
|
||||
This method should be used by self._on_zmq_reply to build messages that can
|
||||
be sent back to the browser.
|
||||
|
||||
"""
|
||||
if isinstance(msg_or_list, dict):
|
||||
# already unpacked
|
||||
msg = msg_or_list
|
||||
else:
|
||||
idents, msg_list = self.session.feed_identities(msg_or_list)
|
||||
msg = self.session.deserialize(msg_list)
|
||||
if channel:
|
||||
msg['channel'] = channel
|
||||
if msg['buffers']:
|
||||
buf = serialize_binary_message(msg)
|
||||
return buf
|
||||
else:
|
||||
smsg = json.dumps(msg, default=date_default)
|
||||
return cast_unicode(smsg)
|
||||
|
||||
def _on_zmq_reply(self, stream, msg_list):
|
||||
# Sometimes this gets triggered when the on_close method is scheduled in the
|
||||
# eventloop but hasn't been called.
|
||||
if self.ws_connection is None or stream.closed():
|
||||
self.log.warning("zmq message arrived on closed channel")
|
||||
self.close()
|
||||
return
|
||||
channel = getattr(stream, 'channel', None)
|
||||
try:
|
||||
msg = self._reserialize_reply(msg_list, channel=channel)
|
||||
except Exception:
|
||||
self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
|
||||
return
|
||||
|
||||
try:
|
||||
self.write_message(msg, binary=isinstance(msg, bytes))
|
||||
except (StreamClosedError, WebSocketClosedError):
|
||||
self.log.warning("zmq message arrived on closed channel")
|
||||
self.close()
|
||||
return
|
||||
|
||||
|
||||
class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
|
||||
|
||||
def set_default_headers(self):
|
||||
"""Undo the set_default_headers in IPythonHandler
|
||||
|
||||
which doesn't make sense for websockets
|
||||
"""
|
||||
pass
|
||||
|
||||
def pre_get(self):
|
||||
"""Run before finishing the GET request
|
||||
|
||||
Extend this method to add logic that should fire before
|
||||
the websocket finishes completing.
|
||||
"""
|
||||
# authenticate the request before opening the websocket
|
||||
if self.get_current_user() is None:
|
||||
self.log.warning("Couldn't authenticate WebSocket connection")
|
||||
raise web.HTTPError(403)
|
||||
|
||||
if self.get_argument('session_id', False):
|
||||
self.session.session = cast_unicode(self.get_argument('session_id'))
|
||||
else:
|
||||
self.log.warning("No session ID specified")
|
||||
|
||||
@gen.coroutine
|
||||
def get(self, *args, **kwargs):
|
||||
# pre_get can be a coroutine in subclasses
|
||||
# assign and yield in two step to avoid tornado 3 issues
|
||||
res = self.pre_get()
|
||||
yield maybe_future(res)
|
||||
res = super(AuthenticatedZMQStreamHandler, self).get(*args, **kwargs)
|
||||
yield maybe_future(res)
|
||||
|
||||
def initialize(self):
|
||||
self.log.debug("Initializing websocket connection %s", self.request.path)
|
||||
self.session = Session(config=self.config)
|
||||
|
||||
def get_compression_options(self):
|
||||
return self.settings.get('websocket_compression_options', None)
|
0
venv/Lib/site-packages/notebook/bundler/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/bundler/__init__.py
Normal file
7
venv/Lib/site-packages/notebook/bundler/__main__.py
Normal file
7
venv/Lib/site-packages/notebook/bundler/__main__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from .bundlerextensions import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
307
venv/Lib/site-packages/notebook/bundler/bundlerextensions.py
Normal file
307
venv/Lib/site-packages/notebook/bundler/bundlerextensions.py
Normal file
|
@ -0,0 +1,307 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import sys
|
||||
import os
|
||||
|
||||
from ..extensions import BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED
|
||||
from .._version import __version__
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
|
||||
from jupyter_core.paths import jupyter_config_path
|
||||
|
||||
from traitlets.utils.importstring import import_item
|
||||
from traitlets import Bool
|
||||
|
||||
BUNDLER_SECTION = "notebook"
|
||||
BUNDLER_SUBSECTION = "bundlerextensions"
|
||||
|
||||
def _get_bundler_metadata(module):
|
||||
"""Gets the list of bundlers associated with a Python package.
|
||||
|
||||
Returns a tuple of (the module, [{
|
||||
'name': 'unique name of the bundler',
|
||||
'label': 'file menu item label for the bundler',
|
||||
'module_name': 'dotted package/module name containing the bundler',
|
||||
'group': 'download or deploy parent menu item'
|
||||
}])
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
"""
|
||||
m = import_item(module)
|
||||
if not hasattr(m, '_jupyter_bundlerextension_paths'):
|
||||
raise KeyError('The Python module {} does not contain a valid bundlerextension'.format(module))
|
||||
bundlers = m._jupyter_bundlerextension_paths()
|
||||
return m, bundlers
|
||||
|
||||
def _set_bundler_state(name, label, module_name, group, state,
|
||||
user=True, sys_prefix=False, logger=None):
|
||||
"""Set whether a bundler is enabled or disabled.
|
||||
|
||||
Returns True if the final state is the one requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : string
|
||||
Unique name of the bundler
|
||||
label : string
|
||||
Human-readable label for the bundler menu item in the notebook UI
|
||||
module_name : string
|
||||
Dotted module/package name containing the bundler
|
||||
group : string
|
||||
'download' or 'deploy' indicating the parent menu containing the label
|
||||
state : bool
|
||||
The state in which to leave the extension
|
||||
user : bool [default: True]
|
||||
Whether to update the user's .jupyter/nbconfig directory
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to update the sys.prefix, i.e. environment. Will override
|
||||
`user`.
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
user = False if sys_prefix else user
|
||||
config_dir = os.path.join(
|
||||
_get_config_dir(user=user, sys_prefix=sys_prefix), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
|
||||
if logger:
|
||||
logger.info("{} {} bundler {}...".format(
|
||||
"Enabling" if state else "Disabling",
|
||||
name,
|
||||
module_name
|
||||
))
|
||||
|
||||
if state:
|
||||
cm.update(BUNDLER_SECTION, {
|
||||
BUNDLER_SUBSECTION: {
|
||||
name: {
|
||||
"label": label,
|
||||
"module_name": module_name,
|
||||
"group" : group
|
||||
}
|
||||
}
|
||||
})
|
||||
else:
|
||||
cm.update(BUNDLER_SECTION, {
|
||||
BUNDLER_SUBSECTION: {
|
||||
name: None
|
||||
}
|
||||
})
|
||||
|
||||
return (cm.get(BUNDLER_SECTION)
|
||||
.get(BUNDLER_SUBSECTION, {})
|
||||
.get(name) is not None) == state
|
||||
|
||||
|
||||
def _set_bundler_state_python(state, module, user, sys_prefix, logger=None):
|
||||
"""Enables or disables bundlers defined in a Python package.
|
||||
|
||||
Returns a list of whether the state was achieved for each bundler.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
state : Bool
|
||||
Whether the extensions should be enabled
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool
|
||||
Enable/disable in the sys.prefix, i.e. environment
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
m, bundlers = _get_bundler_metadata(module)
|
||||
return [_set_bundler_state(name=bundler["name"],
|
||||
label=bundler["label"],
|
||||
module_name=bundler["module_name"],
|
||||
group=bundler["group"],
|
||||
state=state,
|
||||
user=user, sys_prefix=sys_prefix,
|
||||
logger=logger)
|
||||
for bundler in bundlers]
|
||||
|
||||
def enable_bundler_python(module, user=True, sys_prefix=False, logger=None):
|
||||
"""Enables bundlers defined in a Python package.
|
||||
|
||||
Returns whether each bundle defined in the packaged was enabled or not.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool [default: True]
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to enable in the sys.prefix, i.e. environment. Will override
|
||||
`user`
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
return _set_bundler_state_python(True, module, user, sys_prefix,
|
||||
logger=logger)
|
||||
|
||||
def disable_bundler_python(module, user=True, sys_prefix=False, logger=None):
|
||||
"""Disables bundlers defined in a Python package.
|
||||
|
||||
Returns whether each bundle defined in the packaged was enabled or not.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
user : bool [default: True]
|
||||
Whether to enable in the user's nbconfig directory.
|
||||
sys_prefix : bool [default: False]
|
||||
Whether to enable in the sys.prefix, i.e. environment. Will override
|
||||
`user`
|
||||
logger : Jupyter logger [optional]
|
||||
Logger instance to use
|
||||
"""
|
||||
return _set_bundler_state_python(False, module, user, sys_prefix,
|
||||
logger=logger)
|
||||
|
||||
class ToggleBundlerExtensionApp(BaseExtensionApp):
|
||||
"""A base class for apps that enable/disable bundlerextensions"""
|
||||
name = "jupyter bundlerextension enable/disable"
|
||||
version = __version__
|
||||
description = "Enable/disable a bundlerextension in configuration."
|
||||
|
||||
user = Bool(True, config=True, help="Apply the configuration only for the current user (default)")
|
||||
|
||||
_toggle_value = None
|
||||
|
||||
def _config_file_name_default(self):
|
||||
"""The default config file name."""
|
||||
return 'jupyter_notebook_config'
|
||||
|
||||
def toggle_bundler_python(self, module):
|
||||
"""Toggle some extensions in an importable Python module.
|
||||
|
||||
Returns a list of booleans indicating whether the state was changed as
|
||||
requested.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
module : str
|
||||
Importable Python module exposing the
|
||||
magic-named `_jupyter_bundlerextension_paths` function
|
||||
"""
|
||||
toggle = (enable_bundler_python if self._toggle_value
|
||||
else disable_bundler_python)
|
||||
return toggle(module,
|
||||
user=self.user,
|
||||
sys_prefix=self.sys_prefix,
|
||||
logger=self.log)
|
||||
|
||||
def start(self):
|
||||
if not self.extra_args:
|
||||
sys.exit('Please specify an bundlerextension/package to enable or disable')
|
||||
elif len(self.extra_args) > 1:
|
||||
sys.exit('Please specify one bundlerextension/package at a time')
|
||||
if self.python:
|
||||
self.toggle_bundler_python(self.extra_args[0])
|
||||
else:
|
||||
raise NotImplementedError('Cannot install bundlers from non-Python packages')
|
||||
|
||||
class EnableBundlerExtensionApp(ToggleBundlerExtensionApp):
|
||||
"""An App that enables bundlerextensions"""
|
||||
name = "jupyter bundlerextension enable"
|
||||
description = """
|
||||
Enable a bundlerextension in frontend configuration.
|
||||
|
||||
Usage
|
||||
jupyter bundlerextension enable [--system|--sys-prefix]
|
||||
"""
|
||||
_toggle_value = True
|
||||
|
||||
class DisableBundlerExtensionApp(ToggleBundlerExtensionApp):
|
||||
"""An App that disables bundlerextensions"""
|
||||
name = "jupyter bundlerextension disable"
|
||||
description = """
|
||||
Disable a bundlerextension in frontend configuration.
|
||||
|
||||
Usage
|
||||
jupyter bundlerextension disable [--system|--sys-prefix]
|
||||
"""
|
||||
_toggle_value = None
|
||||
|
||||
|
||||
class ListBundlerExtensionApp(BaseExtensionApp):
|
||||
"""An App that lists and validates nbextensions"""
|
||||
name = "jupyter nbextension list"
|
||||
version = __version__
|
||||
description = "List all nbextensions known by the configuration system"
|
||||
|
||||
def list_nbextensions(self):
|
||||
"""List all the nbextensions"""
|
||||
config_dirs = [os.path.join(p, 'nbconfig') for p in jupyter_config_path()]
|
||||
|
||||
print("Known bundlerextensions:")
|
||||
|
||||
for config_dir in config_dirs:
|
||||
head = u' config dir: {}'.format(config_dir)
|
||||
head_shown = False
|
||||
|
||||
cm = BaseJSONConfigManager(parent=self, config_dir=config_dir)
|
||||
data = cm.get('notebook')
|
||||
if 'bundlerextensions' in data:
|
||||
if not head_shown:
|
||||
# only show heading if there is an nbextension here
|
||||
print(head)
|
||||
head_shown = True
|
||||
|
||||
for bundler_id, info in data['bundlerextensions'].items():
|
||||
label = info.get('label')
|
||||
module = info.get('module_name')
|
||||
if label is None or module is None:
|
||||
msg = u' {} {}'.format(bundler_id, RED_DISABLED)
|
||||
else:
|
||||
msg = u' "{}" from {} {}'.format(
|
||||
label, module, GREEN_ENABLED
|
||||
)
|
||||
print(msg)
|
||||
|
||||
def start(self):
|
||||
"""Perform the App's functions as configured"""
|
||||
self.list_nbextensions()
|
||||
|
||||
|
||||
class BundlerExtensionApp(BaseExtensionApp):
|
||||
"""Base jupyter bundlerextension command entry point"""
|
||||
name = "jupyter bundlerextension"
|
||||
version = __version__
|
||||
description = "Work with Jupyter bundler extensions"
|
||||
examples = """
|
||||
jupyter bundlerextension list # list all configured bundlers
|
||||
jupyter bundlerextension enable --py <packagename> # enable all bundlers in a Python package
|
||||
jupyter bundlerextension disable --py <packagename> # disable all bundlers in a Python package
|
||||
"""
|
||||
|
||||
subcommands = dict(
|
||||
enable=(EnableBundlerExtensionApp, "Enable a bundler extension"),
|
||||
disable=(DisableBundlerExtensionApp, "Disable a bundler extension"),
|
||||
list=(ListBundlerExtensionApp, "List bundler extensions")
|
||||
)
|
||||
|
||||
def start(self):
|
||||
"""Perform the App's functions as configured"""
|
||||
super(BundlerExtensionApp, self).start()
|
||||
|
||||
# The above should have called a subcommand and raised NoStart; if we
|
||||
# get here, it didn't, so we should self.log.info a message.
|
||||
subcmds = ", ".join(sorted(self.subcommands))
|
||||
sys.exit("Please supply at least one subcommand: %s" % subcmds)
|
||||
|
||||
main = BundlerExtensionApp.launch_instance
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
88
venv/Lib/site-packages/notebook/bundler/handlers.py
Normal file
88
venv/Lib/site-packages/notebook/bundler/handlers.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
"""Tornado handler for bundling notebooks."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from ipython_genutils.importstring import import_item
|
||||
from tornado import web, gen
|
||||
|
||||
from notebook.utils import maybe_future, url2path
|
||||
from notebook.base.handlers import IPythonHandler
|
||||
from notebook.services.config import ConfigManager
|
||||
|
||||
from . import tools
|
||||
|
||||
|
||||
class BundlerHandler(IPythonHandler):
|
||||
def initialize(self):
|
||||
"""Make tools module available on the handler instance for compatibility
|
||||
with existing bundler API and ease of reference."""
|
||||
self.tools = tools
|
||||
|
||||
def get_bundler(self, bundler_id):
|
||||
"""
|
||||
Get bundler metadata from config given a bundler ID.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
bundler_id: str
|
||||
Unique bundler ID within the notebook/bundlerextensions config section
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Bundler metadata with label, group, and module_name attributes
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
KeyError
|
||||
If the bundler ID is unknown
|
||||
"""
|
||||
cm = ConfigManager()
|
||||
return cm.get('notebook').get('bundlerextensions', {})[bundler_id]
|
||||
|
||||
@web.authenticated
|
||||
@gen.coroutine
|
||||
def get(self, path):
|
||||
"""Bundle the given notebook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path: str
|
||||
Path to the notebook (path parameter)
|
||||
bundler: str
|
||||
Bundler ID to use (query parameter)
|
||||
"""
|
||||
bundler_id = self.get_query_argument('bundler')
|
||||
model = self.contents_manager.get(path=url2path(path))
|
||||
|
||||
try:
|
||||
bundler = self.get_bundler(bundler_id)
|
||||
except KeyError as e:
|
||||
raise web.HTTPError(400, 'Bundler %s not enabled' %
|
||||
bundler_id) from e
|
||||
|
||||
module_name = bundler['module_name']
|
||||
try:
|
||||
# no-op in python3, decode error in python2
|
||||
module_name = str(module_name)
|
||||
except UnicodeEncodeError:
|
||||
# Encode unicode as utf-8 in python2 else import_item fails
|
||||
module_name = module_name.encode('utf-8')
|
||||
|
||||
try:
|
||||
bundler_mod = import_item(module_name)
|
||||
except ImportError as e:
|
||||
raise web.HTTPError(500, 'Could not import bundler %s ' %
|
||||
bundler_id) from e
|
||||
|
||||
# Let the bundler respond in any way it sees fit and assume it will
|
||||
# finish the request
|
||||
yield maybe_future(bundler_mod.bundle(self, model))
|
||||
|
||||
_bundler_id_regex = r'(?P<bundler_id>[A-Za-z0-9_]+)'
|
||||
|
||||
default_handlers = [
|
||||
(r"/bundle/(.*)", BundlerHandler)
|
||||
]
|
47
venv/Lib/site-packages/notebook/bundler/tarball_bundler.py
Normal file
47
venv/Lib/site-packages/notebook/bundler/tarball_bundler.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import io
|
||||
import tarfile
|
||||
import nbformat
|
||||
|
||||
def _jupyter_bundlerextension_paths():
|
||||
"""Metadata for notebook bundlerextension"""
|
||||
return [{
|
||||
# unique bundler name
|
||||
"name": "tarball_bundler",
|
||||
# module containing bundle function
|
||||
"module_name": "notebook.bundler.tarball_bundler",
|
||||
# human-readable menu item label
|
||||
"label" : "Notebook Tarball (tar.gz)",
|
||||
# group under 'deploy' or 'download' menu
|
||||
"group" : "download",
|
||||
}]
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Create a compressed tarball containing the notebook document.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
handler : tornado.web.RequestHandler
|
||||
Handler that serviced the bundle request
|
||||
model : dict
|
||||
Notebook model from the configured ContentManager
|
||||
"""
|
||||
notebook_filename = model['name']
|
||||
notebook_content = nbformat.writes(model['content']).encode('utf-8')
|
||||
notebook_name = os.path.splitext(notebook_filename)[0]
|
||||
tar_filename = '{}.tar.gz'.format(notebook_name)
|
||||
|
||||
info = tarfile.TarInfo(notebook_filename)
|
||||
info.size = len(notebook_content)
|
||||
|
||||
with io.BytesIO() as tar_buffer:
|
||||
with tarfile.open(tar_filename, "w:gz", fileobj=tar_buffer) as tar:
|
||||
tar.addfile(info, io.BytesIO(notebook_content))
|
||||
|
||||
handler.set_attachment_header(tar_filename)
|
||||
handler.set_header('Content-Type', 'application/gzip')
|
||||
|
||||
# Return the buffer value as the response
|
||||
handler.finish(tar_buffer.getvalue())
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
Used to test globbing.
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"nbformat_minor": 0,
|
||||
"cells": [],
|
||||
"nbformat": 4,
|
||||
"metadata": {}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
Used to test globbing.
|
|
@ -0,0 +1,80 @@
|
|||
"""Test the bundlers API."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import io
|
||||
from os.path import join as pjoin
|
||||
|
||||
from notebook.tests.launchnotebook import NotebookTestBase
|
||||
from nbformat import write
|
||||
from nbformat.v4 import (
|
||||
new_notebook, new_markdown_cell, new_code_cell, new_output,
|
||||
)
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Bundler test stub. Echo the notebook path."""
|
||||
handler.finish(model['path'])
|
||||
|
||||
class BundleAPITest(NotebookTestBase):
|
||||
"""Test the bundlers web service API"""
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
"""Make a test notebook. Borrowed from nbconvert test. Assumes the class
|
||||
teardown will clean it up in the end."""
|
||||
super(BundleAPITest, cls).setup_class()
|
||||
nbdir = cls.notebook_dir
|
||||
|
||||
nb = new_notebook()
|
||||
|
||||
nb.cells.append(new_markdown_cell(u'Created by test'))
|
||||
cc1 = new_code_cell(source=u'print(2*6)')
|
||||
cc1.outputs.append(new_output(output_type="stream", text=u'12'))
|
||||
nb.cells.append(cc1)
|
||||
|
||||
with io.open(pjoin(nbdir, 'testnb.ipynb'), 'w',
|
||||
encoding='utf-8') as f:
|
||||
write(nb, f, version=4)
|
||||
|
||||
def test_missing_bundler_arg(self):
|
||||
"""Should respond with 400 error about missing bundler arg"""
|
||||
resp = self.request('GET', 'bundle/fake.ipynb')
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn('Missing argument bundler', resp.text)
|
||||
|
||||
def test_notebook_not_found(self):
|
||||
"""Shoudl respond with 404 error about missing notebook"""
|
||||
resp = self.request('GET', 'bundle/fake.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
self.assertIn('Not Found', resp.text)
|
||||
|
||||
def test_bundler_not_enabled(self):
|
||||
"""Should respond with 400 error about disabled bundler"""
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn('Bundler fake_bundler not enabled', resp.text)
|
||||
|
||||
def test_bundler_import_error(self):
|
||||
"""Should respond with 500 error about failure to load bundler module"""
|
||||
with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock:
|
||||
mock.return_value = {'module_name': 'fake_module'}
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'fake_bundler'})
|
||||
mock.assert_called_with('fake_bundler')
|
||||
self.assertEqual(resp.status_code, 500)
|
||||
self.assertIn('Could not import bundler fake_bundler', resp.text)
|
||||
|
||||
def test_bundler_invoke(self):
|
||||
"""Should respond with 200 and output from test bundler stub"""
|
||||
with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock:
|
||||
mock.return_value = {'module_name': 'notebook.bundler.tests.test_bundler_api'}
|
||||
resp = self.request('GET', 'bundle/testnb.ipynb',
|
||||
params={'bundler': 'stub_bundler'})
|
||||
mock.assert_called_with('stub_bundler')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertIn('testnb.ipynb', resp.text)
|
|
@ -0,0 +1,124 @@
|
|||
"""Test the bundler tools."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import notebook.bundler.tools as tools
|
||||
|
||||
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
class TestBundlerTools(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
def test_get_no_cell_references(self):
|
||||
'''Should find no references in a regular HTML comment.'''
|
||||
no_references = tools.get_cell_reference_patterns({'source':'''!<--
|
||||
a
|
||||
b
|
||||
c
|
||||
-->''', 'cell_type':'markdown'})
|
||||
self.assertEqual(len(no_references), 0)
|
||||
|
||||
def test_get_cell_reference_patterns_comment_multiline(self):
|
||||
'''Should find two references and ignore a comment within an HTML comment.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''<!--associate:
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
-->'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references, str(references))
|
||||
self.assertEqual(len(references), 2, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_comment_trailing_filename(self):
|
||||
'''Should find three references within an HTML comment.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''<!--associate:c
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
-->'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references and 'c' in references, str(references))
|
||||
self.assertEqual(len(references), 3, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_precode(self):
|
||||
'''Should find no references in a fenced code block in a *code* cell.'''
|
||||
self.assertTrue(tools.get_cell_reference_patterns)
|
||||
no_references = tools.get_cell_reference_patterns({'source':'''```
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
```
|
||||
''', 'cell_type':'code'})
|
||||
self.assertEqual(len(no_references), 0)
|
||||
|
||||
def test_get_cell_reference_patterns_precode_mdcomment(self):
|
||||
'''Should find two references and ignore a comment in a fenced code block.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''```
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
```'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references, str(references))
|
||||
self.assertEqual(len(references), 2, str(references))
|
||||
|
||||
def test_get_cell_reference_patterns_precode_backticks(self):
|
||||
'''Should find three references in a fenced code block.'''
|
||||
cell = {'cell_type':'markdown', 'source':'''```c
|
||||
a
|
||||
b/
|
||||
#comment
|
||||
```'''}
|
||||
references = tools.get_cell_reference_patterns(cell)
|
||||
self.assertTrue('a' in references and 'b/' in references and 'c' in references, str(references))
|
||||
self.assertEqual(len(references), 3, str(references))
|
||||
|
||||
def test_glob_dir(self):
|
||||
'''Should expand to single file in the resources/ subfolder.'''
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'),
|
||||
tools.expand_references(HERE, ['resources/empty.ipynb']))
|
||||
|
||||
def test_glob_subdir(self):
|
||||
'''Should expand to all files in the resources/ subfolder.'''
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'),
|
||||
tools.expand_references(HERE, ['resources/']))
|
||||
|
||||
def test_glob_splat(self):
|
||||
'''Should expand to all contents under this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['*'])
|
||||
self.assertIn('test_bundler_tools.py', globs, globs)
|
||||
self.assertIn('resources', globs, globs)
|
||||
|
||||
def test_glob_splatsplat_in_middle(self):
|
||||
'''Should expand to test_file.txt deep under this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['resources/**/test_file.txt'])
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_glob_splatsplat_trailing(self):
|
||||
'''Should expand to all descendants of this test/ directory.'''
|
||||
globs = tools.expand_references(HERE, ['resources/**'])
|
||||
self.assertIn(os.path.join('resources', 'empty.ipynb'), globs, globs)
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_glob_splatsplat_leading(self):
|
||||
'''Should expand to test_file.txt under any path.'''
|
||||
globs = tools.expand_references(HERE, ['**/test_file.txt'])
|
||||
self.assertIn(os.path.join('resources', 'subdir', 'test_file.txt'), globs, globs)
|
||||
self.assertIn(os.path.join('resources', 'another_subdir', 'test_file.txt'), globs, globs)
|
||||
|
||||
def test_copy_filelist(self):
|
||||
'''Should copy select files from source to destination'''
|
||||
globs = tools.expand_references(HERE, ['**/test_file.txt'])
|
||||
tools.copy_filelist(HERE, self.tmp, globs)
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.tmp, 'resources', 'subdir', 'test_file.txt')))
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.tmp, 'resources', 'another_subdir', 'test_file.txt')))
|
||||
self.assertFalse(os.path.isfile(os.path.join(self.tmp, 'resources', 'empty.ipynb')))
|
|
@ -0,0 +1,72 @@
|
|||
"""Test the bundlerextension CLI."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from unittest.mock import patch
|
||||
from ipython_genutils.tempdir import TemporaryDirectory
|
||||
from ipython_genutils import py3compat
|
||||
|
||||
from traitlets.tests.utils import check_help_all_output
|
||||
|
||||
import notebook.nbextensions as nbextensions
|
||||
from notebook.config_manager import BaseJSONConfigManager
|
||||
from ..bundlerextensions import (_get_config_dir, enable_bundler_python,
|
||||
disable_bundler_python)
|
||||
|
||||
def test_help_output():
|
||||
check_help_all_output('notebook.bundler.bundlerextensions')
|
||||
check_help_all_output('notebook.bundler.bundlerextensions', ['enable'])
|
||||
check_help_all_output('notebook.bundler.bundlerextensions', ['disable'])
|
||||
|
||||
class TestBundlerExtensionCLI(unittest.TestCase):
|
||||
"""Tests the bundlerextension CLI against the example zip_bundler."""
|
||||
def setUp(self):
|
||||
"""Build an isolated config environment."""
|
||||
td = TemporaryDirectory()
|
||||
|
||||
self.test_dir = py3compat.cast_unicode(td.name)
|
||||
self.data_dir = os.path.join(self.test_dir, 'data')
|
||||
self.config_dir = os.path.join(self.test_dir, 'config')
|
||||
self.system_data_dir = os.path.join(self.test_dir, 'system_data')
|
||||
self.system_path = [self.system_data_dir]
|
||||
|
||||
# Use temp directory, not real user or system config paths
|
||||
self.patch_env = patch.dict('os.environ', {
|
||||
'JUPYTER_CONFIG_DIR': self.config_dir,
|
||||
'JUPYTER_DATA_DIR': self.data_dir,
|
||||
})
|
||||
self.patch_env.start()
|
||||
self.patch_system_path = patch.object(nbextensions,
|
||||
'SYSTEM_JUPYTER_PATH', self.system_path)
|
||||
self.patch_system_path.start()
|
||||
|
||||
def tearDown(self):
|
||||
"""Remove the test config environment."""
|
||||
shutil.rmtree(self.test_dir, ignore_errors=True)
|
||||
self.patch_env.stop()
|
||||
self.patch_system_path.stop()
|
||||
|
||||
def test_enable(self):
|
||||
"""Should add the bundler to the notebook configuration."""
|
||||
enable_bundler_python('notebook.bundler.zip_bundler')
|
||||
|
||||
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
bundlers = cm.get('notebook').get('bundlerextensions', {})
|
||||
self.assertEqual(len(bundlers), 1)
|
||||
self.assertIn('notebook_zip_download', bundlers)
|
||||
|
||||
def test_disable(self):
|
||||
"""Should remove the bundler from the notebook configuration."""
|
||||
self.test_enable()
|
||||
disable_bundler_python('notebook.bundler.zip_bundler')
|
||||
|
||||
config_dir = os.path.join(_get_config_dir(user=True), 'nbconfig')
|
||||
cm = BaseJSONConfigManager(config_dir=config_dir)
|
||||
bundlers = cm.get('notebook').get('bundlerextensions', {})
|
||||
self.assertEqual(len(bundlers), 0)
|
230
venv/Lib/site-packages/notebook/bundler/tools.py
Normal file
230
venv/Lib/site-packages/notebook/bundler/tools.py
Normal file
|
@ -0,0 +1,230 @@
|
|||
"""Set of common tools to aid bundler implementations."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import shutil
|
||||
import errno
|
||||
import nbformat
|
||||
import fnmatch
|
||||
import glob
|
||||
|
||||
def get_file_references(abs_nb_path, version):
|
||||
"""Gets a list of files referenced either in Markdown fenced code blocks
|
||||
or in HTML comments from the notebook. Expands patterns expressed in
|
||||
gitignore syntax (https://git-scm.com/docs/gitignore). Returns the
|
||||
fully expanded list of filenames relative to the notebook dirname.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
abs_nb_path: str
|
||||
Absolute path of the notebook on disk
|
||||
version: int
|
||||
Version of the notebook document format to use
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Filename strings relative to the notebook path
|
||||
"""
|
||||
ref_patterns = get_reference_patterns(abs_nb_path, version)
|
||||
expanded = expand_references(os.path.dirname(abs_nb_path), ref_patterns)
|
||||
return expanded
|
||||
|
||||
def get_reference_patterns(abs_nb_path, version):
|
||||
"""Gets a list of reference patterns either in Markdown fenced code blocks
|
||||
or in HTML comments from the notebook.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
abs_nb_path: str
|
||||
Absolute path of the notebook on disk
|
||||
version: int
|
||||
Version of the notebook document format to use
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Pattern strings from the notebook
|
||||
"""
|
||||
notebook = nbformat.read(abs_nb_path, version)
|
||||
referenced_list = []
|
||||
for cell in notebook.cells:
|
||||
references = get_cell_reference_patterns(cell)
|
||||
if references:
|
||||
referenced_list = referenced_list + references
|
||||
return referenced_list
|
||||
|
||||
def get_cell_reference_patterns(cell):
|
||||
'''
|
||||
Retrieves the list of references from a single notebook cell. Looks for
|
||||
fenced code blocks or HTML comments in Markdown cells, e.g.,
|
||||
|
||||
```
|
||||
some.csv
|
||||
foo/
|
||||
!foo/bar
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
<!--associate:
|
||||
some.csv
|
||||
foo/
|
||||
!foo/bar
|
||||
-->
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cell: dict
|
||||
Notebook cell object
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Reference patterns found in the cell
|
||||
'''
|
||||
referenced = []
|
||||
# invisible after execution: unrendered HTML comment
|
||||
if cell.get('cell_type').startswith('markdown') and cell.get('source').startswith('<!--associate:'):
|
||||
lines = cell.get('source')[len('<!--associate:'):].splitlines()
|
||||
for line in lines:
|
||||
if line.startswith('-->'):
|
||||
break
|
||||
# Trying to go out of the current directory leads to
|
||||
# trouble when deploying
|
||||
if line.find('../') < 0 and not line.startswith('#'):
|
||||
referenced.append(line)
|
||||
# visible after execution: rendered as a code element within a pre element
|
||||
elif cell.get('cell_type').startswith('markdown') and cell.get('source').find('```') >= 0:
|
||||
source = cell.get('source')
|
||||
offset = source.find('```')
|
||||
lines = source[offset + len('```'):].splitlines()
|
||||
for line in lines:
|
||||
if line.startswith('```'):
|
||||
break
|
||||
# Trying to go out of the current directory leads to
|
||||
# trouble when deploying
|
||||
if line.find('../') < 0 and not line.startswith('#'):
|
||||
referenced.append(line)
|
||||
|
||||
# Clean out blank references
|
||||
return [ref for ref in referenced if ref.strip()]
|
||||
|
||||
def expand_references(root_path, references):
|
||||
"""Expands a set of reference patterns by evaluating them against the
|
||||
given root directory. Expansions are performed against patterns
|
||||
expressed in the same manner as in gitignore
|
||||
(https://git-scm.com/docs/gitignore).
|
||||
|
||||
NOTE: Temporarily changes the current working directory when called.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
root_path: str
|
||||
Assumed root directory for the patterns
|
||||
references: list
|
||||
Reference patterns from get_reference_patterns expressed with
|
||||
forward-slash directory separators
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
Filename strings relative to the root path
|
||||
"""
|
||||
# Use normpath to convert to platform specific slashes, but be sure
|
||||
# to retain a trailing slash which normpath pulls off
|
||||
normalized_references = []
|
||||
for ref in references:
|
||||
normalized_ref = os.path.normpath(ref)
|
||||
# un-normalized separator
|
||||
if ref.endswith('/'):
|
||||
normalized_ref += os.sep
|
||||
normalized_references.append(normalized_ref)
|
||||
references = normalized_references
|
||||
|
||||
globbed = []
|
||||
negations = []
|
||||
must_walk = []
|
||||
for pattern in references:
|
||||
if pattern and pattern.find(os.sep) < 0:
|
||||
# simple shell glob
|
||||
cwd = os.getcwd()
|
||||
os.chdir(root_path)
|
||||
if pattern.startswith('!'):
|
||||
negations = negations + glob.glob(pattern[1:])
|
||||
else:
|
||||
globbed = globbed + glob.glob(pattern)
|
||||
os.chdir(cwd)
|
||||
elif pattern:
|
||||
must_walk.append(pattern)
|
||||
|
||||
for pattern in must_walk:
|
||||
pattern_is_negation = pattern.startswith('!')
|
||||
if pattern_is_negation:
|
||||
testpattern = pattern[1:]
|
||||
else:
|
||||
testpattern = pattern
|
||||
for root, _, filenames in os.walk(root_path):
|
||||
for filename in filenames:
|
||||
joined = os.path.join(root[len(root_path) + 1:], filename)
|
||||
if testpattern.endswith(os.sep):
|
||||
if joined.startswith(testpattern):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
elif testpattern.find('**') >= 0:
|
||||
# path wildcard
|
||||
ends = testpattern.split('**')
|
||||
if len(ends) == 2:
|
||||
if joined.startswith(ends[0]) and joined.endswith(ends[1]):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
else:
|
||||
# segments should be respected
|
||||
if fnmatch.fnmatch(joined, testpattern):
|
||||
if pattern_is_negation:
|
||||
negations.append(joined)
|
||||
else:
|
||||
globbed.append(joined)
|
||||
|
||||
for negated in negations:
|
||||
try:
|
||||
globbed.remove(negated)
|
||||
except ValueError as err:
|
||||
pass
|
||||
return set(globbed)
|
||||
|
||||
def copy_filelist(src, dst, src_relative_filenames):
|
||||
"""Copies the given list of files, relative to src, into dst, creating
|
||||
directories along the way as needed and ignore existence errors.
|
||||
Skips any files that do not exist. Does not create empty directories
|
||||
from src in dst.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
src: str
|
||||
Root of the source directory
|
||||
dst: str
|
||||
Root of the destination directory
|
||||
src_relative_filenames: list
|
||||
Filenames relative to src
|
||||
"""
|
||||
for filename in src_relative_filenames:
|
||||
# Only consider the file if it exists in src
|
||||
if os.path.isfile(os.path.join(src, filename)):
|
||||
parent_relative = os.path.dirname(filename)
|
||||
if parent_relative:
|
||||
# Make sure the parent directory exists
|
||||
parent_dst = os.path.join(dst, parent_relative)
|
||||
try:
|
||||
os.makedirs(parent_dst)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.EEXIST:
|
||||
pass
|
||||
else:
|
||||
raise exc
|
||||
shutil.copy2(os.path.join(src, filename), os.path.join(dst, filename))
|
59
venv/Lib/site-packages/notebook/bundler/zip_bundler.py
Normal file
59
venv/Lib/site-packages/notebook/bundler/zip_bundler.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import os
|
||||
import io
|
||||
import zipfile
|
||||
import notebook.bundler.tools as tools
|
||||
|
||||
def _jupyter_bundlerextension_paths():
|
||||
"""Metadata for notebook bundlerextension"""
|
||||
return [{
|
||||
'name': 'notebook_zip_download',
|
||||
'label': 'IPython Notebook bundle (.zip)',
|
||||
'module_name': 'notebook.bundler.zip_bundler',
|
||||
'group': 'download'
|
||||
}]
|
||||
|
||||
def bundle(handler, model):
|
||||
"""Create a zip file containing the original notebook and files referenced
|
||||
from it. Retain the referenced files in paths relative to the notebook.
|
||||
Return the zip as a file download.
|
||||
|
||||
Assumes the notebook and other files are all on local disk.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
handler : tornado.web.RequestHandler
|
||||
Handler that serviced the bundle request
|
||||
model : dict
|
||||
Notebook model from the configured ContentManager
|
||||
"""
|
||||
abs_nb_path = os.path.join(handler.settings['contents_manager'].root_dir,
|
||||
model['path'])
|
||||
notebook_filename = model['name']
|
||||
notebook_name = os.path.splitext(notebook_filename)[0]
|
||||
|
||||
# Headers
|
||||
zip_filename = os.path.splitext(notebook_name)[0] + '.zip'
|
||||
handler.set_attachment_header(zip_filename)
|
||||
handler.set_header('Content-Type', 'application/zip')
|
||||
|
||||
# Get associated files
|
||||
ref_filenames = tools.get_file_references(abs_nb_path, 4)
|
||||
|
||||
# Prepare the zip file
|
||||
zip_buffer = io.BytesIO()
|
||||
zipf = zipfile.ZipFile(zip_buffer, mode='w', compression=zipfile.ZIP_DEFLATED)
|
||||
zipf.write(abs_nb_path, notebook_filename)
|
||||
|
||||
notebook_dir = os.path.dirname(abs_nb_path)
|
||||
for nb_relative_filename in ref_filenames:
|
||||
# Build absolute path to file on disk
|
||||
abs_fn = os.path.join(notebook_dir, nb_relative_filename)
|
||||
# Store file under path relative to notebook
|
||||
zipf.write(abs_fn, nb_relative_filename)
|
||||
|
||||
zipf.close()
|
||||
|
||||
# Return the buffer value as the response
|
||||
handler.finish(zip_buffer.getvalue())
|
133
venv/Lib/site-packages/notebook/config_manager.py
Normal file
133
venv/Lib/site-packages/notebook/config_manager.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
# coding: utf-8
|
||||
"""Manager to read and modify config data in JSON files."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import errno
|
||||
import glob
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import copy
|
||||
|
||||
from traitlets.config import LoggingConfigurable
|
||||
from traitlets.traitlets import Unicode, Bool
|
||||
|
||||
|
||||
def recursive_update(target, new):
|
||||
"""Recursively update one dictionary using another.
|
||||
|
||||
None values will delete their keys.
|
||||
"""
|
||||
for k, v in new.items():
|
||||
if isinstance(v, dict):
|
||||
if k not in target:
|
||||
target[k] = {}
|
||||
recursive_update(target[k], v)
|
||||
if not target[k]:
|
||||
# Prune empty subdicts
|
||||
del target[k]
|
||||
|
||||
elif v is None:
|
||||
target.pop(k, None)
|
||||
|
||||
else:
|
||||
target[k] = v
|
||||
|
||||
|
||||
def remove_defaults(data, defaults):
|
||||
"""Recursively remove items from dict that are already in defaults"""
|
||||
# copy the iterator, since data will be modified
|
||||
for key, value in list(data.items()):
|
||||
if key in defaults:
|
||||
if isinstance(value, dict):
|
||||
remove_defaults(data[key], defaults[key])
|
||||
if not data[key]: # prune empty subdicts
|
||||
del data[key]
|
||||
else:
|
||||
if value == defaults[key]:
|
||||
del data[key]
|
||||
|
||||
|
||||
class BaseJSONConfigManager(LoggingConfigurable):
|
||||
"""General JSON config manager
|
||||
|
||||
Deals with persisting/storing config in a json file with optionally
|
||||
default values in a {section_name}.d directory.
|
||||
"""
|
||||
|
||||
config_dir = Unicode('.')
|
||||
read_directory = Bool(True)
|
||||
|
||||
def ensure_config_dir_exists(self):
|
||||
"""Will try to create the config_dir directory."""
|
||||
try:
|
||||
os.makedirs(self.config_dir, 0o755)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def file_name(self, section_name):
|
||||
"""Returns the json filename for the section_name: {config_dir}/{section_name}.json"""
|
||||
return os.path.join(self.config_dir, section_name+'.json')
|
||||
|
||||
def directory(self, section_name):
|
||||
"""Returns the directory name for the section name: {config_dir}/{section_name}.d"""
|
||||
return os.path.join(self.config_dir, section_name+'.d')
|
||||
|
||||
def get(self, section_name, include_root=True):
|
||||
"""Retrieve the config data for the specified section.
|
||||
|
||||
Returns the data as a dictionary, or an empty dictionary if the file
|
||||
doesn't exist.
|
||||
|
||||
When include_root is False, it will not read the root .json file,
|
||||
effectively returning the default values.
|
||||
"""
|
||||
paths = [self.file_name(section_name)] if include_root else []
|
||||
if self.read_directory:
|
||||
pattern = os.path.join(self.directory(section_name), '*.json')
|
||||
# These json files should be processed first so that the
|
||||
# {section_name}.json take precedence.
|
||||
# The idea behind this is that installing a Python package may
|
||||
# put a json file somewhere in the a .d directory, while the
|
||||
# .json file is probably a user configuration.
|
||||
paths = sorted(glob.glob(pattern)) + paths
|
||||
self.log.debug('Paths used for configuration of %s: \n\t%s', section_name, '\n\t'.join(paths))
|
||||
data = {}
|
||||
for path in paths:
|
||||
if os.path.isfile(path):
|
||||
with io.open(path, encoding='utf-8') as f:
|
||||
recursive_update(data, json.load(f))
|
||||
return data
|
||||
|
||||
def set(self, section_name, data):
|
||||
"""Store the given config data.
|
||||
"""
|
||||
filename = self.file_name(section_name)
|
||||
self.ensure_config_dir_exists()
|
||||
|
||||
if self.read_directory:
|
||||
# we will modify data in place, so make a copy
|
||||
data = copy.deepcopy(data)
|
||||
defaults = self.get(section_name, include_root=False)
|
||||
remove_defaults(data, defaults)
|
||||
|
||||
# Generate the JSON up front, since it could raise an exception,
|
||||
# in order to avoid writing half-finished corrupted data to disk.
|
||||
json_content = json.dumps(data, indent=2)
|
||||
|
||||
f = io.open(filename, 'w', encoding='utf-8')
|
||||
with f:
|
||||
f.write(json_content)
|
||||
|
||||
def update(self, section_name, new_data):
|
||||
"""Modify the config section by recursively updating it with new_data.
|
||||
|
||||
Returns the modified config data as a dictionary.
|
||||
"""
|
||||
data = self.get(section_name)
|
||||
recursive_update(data, new_data)
|
||||
self.set(section_name, data)
|
||||
return data
|
0
venv/Lib/site-packages/notebook/edit/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/edit/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
29
venv/Lib/site-packages/notebook/edit/handlers.py
Normal file
29
venv/Lib/site-packages/notebook/edit/handlers.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
#encoding: utf-8
|
||||
"""Tornado handlers for the terminal emulator."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from tornado import web
|
||||
from ..base.handlers import IPythonHandler, path_regex
|
||||
from ..utils import url_escape
|
||||
|
||||
class EditorHandler(IPythonHandler):
|
||||
"""Render the text editor interface."""
|
||||
@web.authenticated
|
||||
def get(self, path):
|
||||
path = path.strip('/')
|
||||
if not self.contents_manager.file_exists(path):
|
||||
raise web.HTTPError(404, u'File does not exist: %s' % path)
|
||||
|
||||
basename = path.rsplit('/', 1)[-1]
|
||||
self.write(self.render_template('edit.html',
|
||||
file_path=url_escape(path),
|
||||
basename=basename,
|
||||
page_title=basename + " (editing)",
|
||||
)
|
||||
)
|
||||
|
||||
default_handlers = [
|
||||
(r"/edit%s" % path_regex, EditorHandler),
|
||||
]
|
104
venv/Lib/site-packages/notebook/extensions.py
Normal file
104
venv/Lib/site-packages/notebook/extensions.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
# coding: utf-8
|
||||
"""Utilities for installing extensions"""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
from tornado.log import LogFormatter
|
||||
from traitlets import Bool, Any
|
||||
from jupyter_core.application import JupyterApp
|
||||
from jupyter_core.paths import (
|
||||
jupyter_config_dir, ENV_CONFIG_PATH, SYSTEM_CONFIG_PATH
|
||||
)
|
||||
from ._version import __version__
|
||||
|
||||
class ArgumentConflict(ValueError):
|
||||
pass
|
||||
|
||||
_base_flags = {}
|
||||
_base_flags.update(JupyterApp.flags)
|
||||
_base_flags.pop("y", None)
|
||||
_base_flags.pop("generate-config", None)
|
||||
_base_flags.update({
|
||||
"user" : ({
|
||||
"BaseExtensionApp" : {
|
||||
"user" : True,
|
||||
}}, "Apply the operation only for the given user"
|
||||
),
|
||||
"system" : ({
|
||||
"BaseExtensionApp" : {
|
||||
"user" : False,
|
||||
"sys_prefix": False,
|
||||
}}, "Apply the operation system-wide"
|
||||
),
|
||||
"sys-prefix" : ({
|
||||
"BaseExtensionApp" : {
|
||||
"sys_prefix" : True,
|
||||
}}, "Use sys.prefix as the prefix for installing nbextensions (for environments, packaging)"
|
||||
),
|
||||
"py" : ({
|
||||
"BaseExtensionApp" : {
|
||||
"python" : True,
|
||||
}}, "Install from a Python package"
|
||||
)
|
||||
})
|
||||
_base_flags['python'] = _base_flags['py']
|
||||
|
||||
_base_aliases = {}
|
||||
_base_aliases.update(JupyterApp.aliases)
|
||||
|
||||
|
||||
class BaseExtensionApp(JupyterApp):
|
||||
"""Base nbextension installer app"""
|
||||
_log_formatter_cls = LogFormatter
|
||||
flags = _base_flags
|
||||
aliases = _base_aliases
|
||||
version = __version__
|
||||
|
||||
user = Bool(False, config=True, help="Whether to do a user install")
|
||||
sys_prefix = Bool(False, config=True, help="Use the sys.prefix as the prefix")
|
||||
python = Bool(False, config=True, help="Install from a Python package")
|
||||
|
||||
# Remove for 5.0...
|
||||
verbose = Any(None, config=True, help="DEPRECATED: Verbosity level")
|
||||
|
||||
def _verbose_changed(self):
|
||||
"""Warn about verbosity changes"""
|
||||
import warnings
|
||||
warnings.warn("`verbose` traits of `{}` has been deprecated, has no effects and will be removed in notebook 5.0.".format(type(self).__name__), DeprecationWarning)
|
||||
|
||||
def _log_format_default(self):
|
||||
"""A default format for messages"""
|
||||
return "%(message)s"
|
||||
|
||||
def _get_config_dir(user=False, sys_prefix=False):
|
||||
"""Get the location of config files for the current context
|
||||
|
||||
Returns the string to the environment
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
user : bool [default: False]
|
||||
Get the user's .jupyter config directory
|
||||
sys_prefix : bool [default: False]
|
||||
Get sys.prefix, i.e. ~/.envs/my-env/etc/jupyter
|
||||
"""
|
||||
user = False if sys_prefix else user
|
||||
if user and sys_prefix:
|
||||
raise ArgumentConflict("Cannot specify more than one of user or sys_prefix")
|
||||
if user:
|
||||
nbext = jupyter_config_dir()
|
||||
elif sys_prefix:
|
||||
nbext = ENV_CONFIG_PATH[0]
|
||||
else:
|
||||
nbext = SYSTEM_CONFIG_PATH[0]
|
||||
return nbext
|
||||
|
||||
# Constants for pretty print extension listing function.
|
||||
# Window doesn't support coloring in the commandline
|
||||
GREEN_ENABLED = '\033[32m enabled \033[0m' if os.name != 'nt' else 'enabled '
|
||||
RED_DISABLED = '\033[31mdisabled\033[0m' if os.name != 'nt' else 'disabled'
|
||||
GREEN_OK = '\033[32mOK\033[0m' if os.name != 'nt' else 'ok'
|
||||
RED_X = '\033[31m X\033[0m' if os.name != 'nt' else ' X'
|
0
venv/Lib/site-packages/notebook/files/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/files/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
85
venv/Lib/site-packages/notebook/files/handlers.py
Normal file
85
venv/Lib/site-packages/notebook/files/handlers.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
"""Serve files directly from the ContentsManager."""
|
||||
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import mimetypes
|
||||
import json
|
||||
from base64 import decodebytes
|
||||
|
||||
from tornado import gen, web
|
||||
|
||||
from notebook.base.handlers import IPythonHandler
|
||||
from notebook.utils import maybe_future
|
||||
|
||||
|
||||
class FilesHandler(IPythonHandler):
|
||||
"""serve files via ContentsManager
|
||||
|
||||
Normally used when ContentsManager is not a FileContentsManager.
|
||||
|
||||
FileContentsManager subclasses use AuthenticatedFilesHandler by default,
|
||||
a subclass of StaticFileHandler.
|
||||
"""
|
||||
|
||||
@property
|
||||
def content_security_policy(self):
|
||||
# In case we're serving HTML/SVG, confine any Javascript to a unique
|
||||
# origin so it can't interact with the notebook server.
|
||||
return super(FilesHandler, self).content_security_policy + \
|
||||
"; sandbox allow-scripts"
|
||||
|
||||
@web.authenticated
|
||||
def head(self, path):
|
||||
self.check_xsrf_cookie()
|
||||
return self.get(path, include_body=False)
|
||||
|
||||
@web.authenticated
|
||||
@gen.coroutine
|
||||
def get(self, path, include_body=True):
|
||||
# /files/ requests must originate from the same site
|
||||
self.check_xsrf_cookie()
|
||||
cm = self.contents_manager
|
||||
|
||||
if cm.is_hidden(path) and not cm.allow_hidden:
|
||||
self.log.info("Refusing to serve hidden file, via 404 Error")
|
||||
raise web.HTTPError(404)
|
||||
|
||||
path = path.strip('/')
|
||||
if '/' in path:
|
||||
_, name = path.rsplit('/', 1)
|
||||
else:
|
||||
name = path
|
||||
|
||||
model = yield maybe_future(cm.get(path, type='file', content=include_body))
|
||||
|
||||
if self.get_argument("download", False):
|
||||
self.set_attachment_header(name)
|
||||
|
||||
# get mimetype from filename
|
||||
if name.lower().endswith('.ipynb'):
|
||||
self.set_header('Content-Type', 'application/x-ipynb+json')
|
||||
else:
|
||||
cur_mime = mimetypes.guess_type(name)[0]
|
||||
if cur_mime == 'text/plain':
|
||||
self.set_header('Content-Type', 'text/plain; charset=UTF-8')
|
||||
elif cur_mime is not None:
|
||||
self.set_header('Content-Type', cur_mime)
|
||||
else:
|
||||
if model['format'] == 'base64':
|
||||
self.set_header('Content-Type', 'application/octet-stream')
|
||||
else:
|
||||
self.set_header('Content-Type', 'text/plain; charset=UTF-8')
|
||||
|
||||
if include_body:
|
||||
if model['format'] == 'base64':
|
||||
b64_bytes = model['content'].encode('ascii')
|
||||
self.write(decodebytes(b64_bytes))
|
||||
elif model['format'] == 'json':
|
||||
self.write(json.dumps(model['content']))
|
||||
else:
|
||||
self.write(model['content'])
|
||||
self.flush()
|
||||
|
||||
|
||||
default_handlers = []
|
0
venv/Lib/site-packages/notebook/gateway/__init__.py
Normal file
0
venv/Lib/site-packages/notebook/gateway/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
253
venv/Lib/site-packages/notebook/gateway/handlers.py
Normal file
253
venv/Lib/site-packages/notebook/gateway/handlers.py
Normal file
|
@ -0,0 +1,253 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import mimetypes
|
||||
|
||||
from ..base.handlers import APIHandler, IPythonHandler
|
||||
from ..utils import url_path_join
|
||||
|
||||
from tornado import gen, web
|
||||
from tornado.concurrent import Future
|
||||
from tornado.ioloop import IOLoop, PeriodicCallback
|
||||
from tornado.websocket import WebSocketHandler, websocket_connect
|
||||
from tornado.httpclient import HTTPRequest
|
||||
from tornado.escape import url_escape, json_decode, utf8
|
||||
|
||||
from ipython_genutils.py3compat import cast_unicode
|
||||
from jupyter_client.session import Session
|
||||
from traitlets.config.configurable import LoggingConfigurable
|
||||
|
||||
from .managers import GatewayClient
|
||||
|
||||
# Keepalive ping interval (default: 30 seconds)
|
||||
GATEWAY_WS_PING_INTERVAL_SECS = int(os.getenv('GATEWAY_WS_PING_INTERVAL_SECS', 30))
|
||||
|
||||
|
||||
class WebSocketChannelsHandler(WebSocketHandler, IPythonHandler):
|
||||
|
||||
session = None
|
||||
gateway = None
|
||||
kernel_id = None
|
||||
ping_callback = None
|
||||
|
||||
def check_origin(self, origin=None):
|
||||
return IPythonHandler.check_origin(self, origin)
|
||||
|
||||
def set_default_headers(self):
|
||||
"""Undo the set_default_headers in IPythonHandler which doesn't make sense for websockets"""
|
||||
pass
|
||||
|
||||
def get_compression_options(self):
|
||||
# use deflate compress websocket
|
||||
return {}
|
||||
|
||||
def authenticate(self):
|
||||
"""Run before finishing the GET request
|
||||
|
||||
Extend this method to add logic that should fire before
|
||||
the websocket finishes completing.
|
||||
"""
|
||||
# authenticate the request before opening the websocket
|
||||
if self.get_current_user() is None:
|
||||
self.log.warning("Couldn't authenticate WebSocket connection")
|
||||
raise web.HTTPError(403)
|
||||
|
||||
if self.get_argument('session_id', False):
|
||||
self.session.session = cast_unicode(self.get_argument('session_id'))
|
||||
else:
|
||||
self.log.warning("No session ID specified")
|
||||
|
||||
def initialize(self):
|
||||
self.log.debug("Initializing websocket connection %s", self.request.path)
|
||||
self.session = Session(config=self.config)
|
||||
self.gateway = GatewayWebSocketClient(gateway_url=GatewayClient.instance().url)
|
||||
|
||||
@gen.coroutine
|
||||
def get(self, kernel_id, *args, **kwargs):
|
||||
self.authenticate()
|
||||
self.kernel_id = cast_unicode(kernel_id, 'ascii')
|
||||
yield super(WebSocketChannelsHandler, self).get(kernel_id=kernel_id, *args, **kwargs)
|
||||
|
||||
def send_ping(self):
|
||||
if self.ws_connection is None and self.ping_callback is not None:
|
||||
self.ping_callback.stop()
|
||||
return
|
||||
|
||||
self.ping(b'')
|
||||
|
||||
def open(self, kernel_id, *args, **kwargs):
|
||||
"""Handle web socket connection open to notebook server and delegate to gateway web socket handler """
|
||||
self.ping_callback = PeriodicCallback(self.send_ping, GATEWAY_WS_PING_INTERVAL_SECS * 1000)
|
||||
self.ping_callback.start()
|
||||
|
||||
self.gateway.on_open(
|
||||
kernel_id=kernel_id,
|
||||
message_callback=self.write_message,
|
||||
compression_options=self.get_compression_options()
|
||||
)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Forward message to gateway web socket handler."""
|
||||
self.gateway.on_message(message)
|
||||
|
||||
def write_message(self, message, binary=False):
|
||||
"""Send message back to notebook client. This is called via callback from self.gateway._read_messages."""
|
||||
if self.ws_connection: # prevent WebSocketClosedError
|
||||
if isinstance(message, bytes):
|
||||
binary = True
|
||||
super(WebSocketChannelsHandler, self).write_message(message, binary=binary)
|
||||
elif self.log.isEnabledFor(logging.DEBUG):
|
||||
msg_summary = WebSocketChannelsHandler._get_message_summary(json_decode(utf8(message)))
|
||||
self.log.debug("Notebook client closed websocket connection - message dropped: {}".format(msg_summary))
|
||||
|
||||
def on_close(self):
|
||||
self.log.debug("Closing websocket connection %s", self.request.path)
|
||||
self.gateway.on_close()
|
||||
super(WebSocketChannelsHandler, self).on_close()
|
||||
|
||||
@staticmethod
|
||||
def _get_message_summary(message):
|
||||
summary = []
|
||||
message_type = message['msg_type']
|
||||
summary.append('type: {}'.format(message_type))
|
||||
|
||||
if message_type == 'status':
|
||||
summary.append(', state: {}'.format(message['content']['execution_state']))
|
||||
elif message_type == 'error':
|
||||
summary.append(', {}:{}:{}'.format(message['content']['ename'],
|
||||
message['content']['evalue'],
|
||||
message['content']['traceback']))
|
||||
else:
|
||||
summary.append(', ...') # don't display potentially sensitive data
|
||||
|
||||
return ''.join(summary)
|
||||
|
||||
|
||||
class GatewayWebSocketClient(LoggingConfigurable):
|
||||
"""Proxy web socket connection to a kernel/enterprise gateway."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GatewayWebSocketClient, self).__init__(**kwargs)
|
||||
self.kernel_id = None
|
||||
self.ws = None
|
||||
self.ws_future = Future()
|
||||
self.disconnected = False
|
||||
|
||||
@gen.coroutine
|
||||
def _connect(self, kernel_id):
|
||||
# websocket is initialized before connection
|
||||
self.ws = None
|
||||
self.kernel_id = kernel_id
|
||||
ws_url = url_path_join(
|
||||
GatewayClient.instance().ws_url,
|
||||
GatewayClient.instance().kernels_endpoint, url_escape(kernel_id), 'channels'
|
||||
)
|
||||
self.log.info('Connecting to {}'.format(ws_url))
|
||||
kwargs = {}
|
||||
kwargs = GatewayClient.instance().load_connection_args(**kwargs)
|
||||
|
||||
request = HTTPRequest(ws_url, **kwargs)
|
||||
self.ws_future = websocket_connect(request)
|
||||
self.ws_future.add_done_callback(self._connection_done)
|
||||
|
||||
def _connection_done(self, fut):
|
||||
if not self.disconnected and fut.exception() is None: # prevent concurrent.futures._base.CancelledError
|
||||
self.ws = fut.result()
|
||||
self.log.debug("Connection is ready: ws: {}".format(self.ws))
|
||||
else:
|
||||
self.log.warning("Websocket connection has been closed via client disconnect or due to error. "
|
||||
"Kernel with ID '{}' may not be terminated on GatewayClient: {}".
|
||||
format(self.kernel_id, GatewayClient.instance().url))
|
||||
|
||||
def _disconnect(self):
|
||||
self.disconnected = True
|
||||
if self.ws is not None:
|
||||
# Close connection
|
||||
self.ws.close()
|
||||
elif not self.ws_future.done():
|
||||
# Cancel pending connection. Since future.cancel() is a noop on tornado, we'll track cancellation locally
|
||||
self.ws_future.cancel()
|
||||
self.log.debug("_disconnect: future cancelled, disconnected: {}".format(self.disconnected))
|
||||
|
||||
@gen.coroutine
|
||||
def _read_messages(self, callback):
|
||||
"""Read messages from gateway server."""
|
||||
while self.ws is not None:
|
||||
message = None
|
||||
if not self.disconnected:
|
||||
try:
|
||||
message = yield self.ws.read_message()
|
||||
except Exception as e:
|
||||
self.log.error("Exception reading message from websocket: {}".format(e)) # , exc_info=True)
|
||||
if message is None:
|
||||
if not self.disconnected:
|
||||
self.log.warning("Lost connection to Gateway: {}".format(self.kernel_id))
|
||||
break
|
||||
callback(message) # pass back to notebook client (see self.on_open and WebSocketChannelsHandler.open)
|
||||
else: # ws cancelled - stop reading
|
||||
break
|
||||
|
||||
if not self.disconnected: # if websocket is not disconnected by client, attept to reconnect to Gateway
|
||||
self.log.info("Attempting to re-establish the connection to Gateway: {}".format(self.kernel_id))
|
||||
self._connect(self.kernel_id)
|
||||
loop = IOLoop.current()
|
||||
loop.add_future(self.ws_future, lambda future: self._read_messages(callback))
|
||||
|
||||
def on_open(self, kernel_id, message_callback, **kwargs):
|
||||
"""Web socket connection open against gateway server."""
|
||||
self._connect(kernel_id)
|
||||
loop = IOLoop.current()
|
||||
loop.add_future(
|
||||
self.ws_future,
|
||||
lambda future: self._read_messages(message_callback)
|
||||
)
|
||||
|
||||
def on_message(self, message):
|
||||
"""Send message to gateway server."""
|
||||
if self.ws is None:
|
||||
loop = IOLoop.current()
|
||||
loop.add_future(
|
||||
self.ws_future,
|
||||
lambda future: self._write_message(message)
|
||||
)
|
||||
else:
|
||||
self._write_message(message)
|
||||
|
||||
def _write_message(self, message):
|
||||
"""Send message to gateway server."""
|
||||
try:
|
||||
if not self.disconnected and self.ws is not None:
|
||||
self.ws.write_message(message)
|
||||
except Exception as e:
|
||||
self.log.error("Exception writing message to websocket: {}".format(e)) # , exc_info=True)
|
||||
|
||||
def on_close(self):
|
||||
"""Web socket closed event."""
|
||||
self._disconnect()
|
||||
|
||||
|
||||
class GatewayResourceHandler(APIHandler):
|
||||
"""Retrieves resources for specific kernelspec definitions from kernel/enterprise gateway."""
|
||||
|
||||
@web.authenticated
|
||||
@gen.coroutine
|
||||
def get(self, kernel_name, path, include_body=True):
|
||||
ksm = self.kernel_spec_manager
|
||||
kernel_spec_res = yield ksm.get_kernel_spec_resource(kernel_name, path)
|
||||
if kernel_spec_res is None:
|
||||
self.log.warning("Kernelspec resource '{}' for '{}' not found. Gateway may not support"
|
||||
" resource serving.".format(path, kernel_name))
|
||||
else:
|
||||
self.set_header("Content-Type", mimetypes.guess_type(path)[0])
|
||||
self.finish(kernel_spec_res)
|
||||
|
||||
|
||||
from ..services.kernels.handlers import _kernel_id_regex
|
||||
from ..services.kernelspecs.handlers import kernel_name_regex
|
||||
|
||||
default_handlers = [
|
||||
(r"/api/kernels/%s/channels" % _kernel_id_regex, WebSocketChannelsHandler),
|
||||
(r"/kernelspecs/%s/(?P<path>.*)" % kernel_name_regex, GatewayResourceHandler),
|
||||
]
|
626
venv/Lib/site-packages/notebook/gateway/managers.py
Normal file
626
venv/Lib/site-packages/notebook/gateway/managers.py
Normal file
|
@ -0,0 +1,626 @@
|
|||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from socket import gaierror
|
||||
from tornado import gen, web
|
||||
from tornado.escape import json_encode, json_decode, url_escape
|
||||
from tornado.httpclient import HTTPClient, AsyncHTTPClient, HTTPError
|
||||
|
||||
from ..services.kernels.kernelmanager import MappingKernelManager
|
||||
from ..services.sessions.sessionmanager import SessionManager
|
||||
|
||||
from jupyter_client.kernelspec import KernelSpecManager
|
||||
from ..utils import url_path_join
|
||||
|
||||
from traitlets import Instance, Unicode, Float, Bool, default, validate, TraitError
|
||||
from traitlets.config import SingletonConfigurable
|
||||
|
||||
|
||||
class GatewayClient(SingletonConfigurable):
|
||||
"""This class manages the configuration. It's its own singleton class so that we
|
||||
can share these values across all objects. It also contains some helper methods
|
||||
to build request arguments out of the various config options.
|
||||
|
||||
"""
|
||||
|
||||
url = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The url of the Kernel or Enterprise Gateway server where
|
||||
kernel specifications are defined and kernel management takes place.
|
||||
If defined, this Notebook server acts as a proxy for all kernel
|
||||
management and kernel specification retrieval. (JUPYTER_GATEWAY_URL env var)
|
||||
"""
|
||||
)
|
||||
|
||||
url_env = 'JUPYTER_GATEWAY_URL'
|
||||
|
||||
@default('url')
|
||||
def _url_default(self):
|
||||
return os.environ.get(self.url_env)
|
||||
|
||||
@validate('url')
|
||||
def _url_validate(self, proposal):
|
||||
value = proposal['value']
|
||||
# Ensure value, if present, starts with 'http'
|
||||
if value is not None and len(value) > 0:
|
||||
if not str(value).lower().startswith('http'):
|
||||
raise TraitError("GatewayClient url must start with 'http': '%r'" % value)
|
||||
return value
|
||||
|
||||
ws_url = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The websocket url of the Kernel or Enterprise Gateway server. If not provided, this value
|
||||
will correspond to the value of the Gateway url with 'ws' in place of 'http'. (JUPYTER_GATEWAY_WS_URL env var)
|
||||
"""
|
||||
)
|
||||
|
||||
ws_url_env = 'JUPYTER_GATEWAY_WS_URL'
|
||||
|
||||
@default('ws_url')
|
||||
def _ws_url_default(self):
|
||||
default_value = os.environ.get(self.ws_url_env)
|
||||
if default_value is None:
|
||||
if self.gateway_enabled:
|
||||
default_value = self.url.lower().replace('http', 'ws')
|
||||
return default_value
|
||||
|
||||
@validate('ws_url')
|
||||
def _ws_url_validate(self, proposal):
|
||||
value = proposal['value']
|
||||
# Ensure value, if present, starts with 'ws'
|
||||
if value is not None and len(value) > 0:
|
||||
if not str(value).lower().startswith('ws'):
|
||||
raise TraitError("GatewayClient ws_url must start with 'ws': '%r'" % value)
|
||||
return value
|
||||
|
||||
kernels_endpoint_default_value = '/api/kernels'
|
||||
kernels_endpoint_env = 'JUPYTER_GATEWAY_KERNELS_ENDPOINT'
|
||||
kernels_endpoint = Unicode(default_value=kernels_endpoint_default_value, config=True,
|
||||
help="""The gateway API endpoint for accessing kernel resources (JUPYTER_GATEWAY_KERNELS_ENDPOINT env var)""")
|
||||
|
||||
@default('kernels_endpoint')
|
||||
def _kernels_endpoint_default(self):
|
||||
return os.environ.get(self.kernels_endpoint_env, self.kernels_endpoint_default_value)
|
||||
|
||||
kernelspecs_endpoint_default_value = '/api/kernelspecs'
|
||||
kernelspecs_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT'
|
||||
kernelspecs_endpoint = Unicode(default_value=kernelspecs_endpoint_default_value, config=True,
|
||||
help="""The gateway API endpoint for accessing kernelspecs (JUPYTER_GATEWAY_KERNELSPECS_ENDPOINT env var)""")
|
||||
|
||||
@default('kernelspecs_endpoint')
|
||||
def _kernelspecs_endpoint_default(self):
|
||||
return os.environ.get(self.kernelspecs_endpoint_env, self.kernelspecs_endpoint_default_value)
|
||||
|
||||
kernelspecs_resource_endpoint_default_value = '/kernelspecs'
|
||||
kernelspecs_resource_endpoint_env = 'JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT'
|
||||
kernelspecs_resource_endpoint = Unicode(default_value=kernelspecs_resource_endpoint_default_value, config=True,
|
||||
help="""The gateway endpoint for accessing kernelspecs resources
|
||||
(JUPYTER_GATEWAY_KERNELSPECS_RESOURCE_ENDPOINT env var)""")
|
||||
|
||||
@default('kernelspecs_resource_endpoint')
|
||||
def _kernelspecs_resource_endpoint_default(self):
|
||||
return os.environ.get(self.kernelspecs_resource_endpoint_env, self.kernelspecs_resource_endpoint_default_value)
|
||||
|
||||
connect_timeout_default_value = 40.0
|
||||
connect_timeout_env = 'JUPYTER_GATEWAY_CONNECT_TIMEOUT'
|
||||
connect_timeout = Float(default_value=connect_timeout_default_value, config=True,
|
||||
help="""The time allowed for HTTP connection establishment with the Gateway server.
|
||||
(JUPYTER_GATEWAY_CONNECT_TIMEOUT env var)""")
|
||||
|
||||
@default('connect_timeout')
|
||||
def connect_timeout_default(self):
|
||||
return float(os.environ.get('JUPYTER_GATEWAY_CONNECT_TIMEOUT', self.connect_timeout_default_value))
|
||||
|
||||
request_timeout_default_value = 40.0
|
||||
request_timeout_env = 'JUPYTER_GATEWAY_REQUEST_TIMEOUT'
|
||||
request_timeout = Float(default_value=request_timeout_default_value, config=True,
|
||||
help="""The time allowed for HTTP request completion. (JUPYTER_GATEWAY_REQUEST_TIMEOUT env var)""")
|
||||
|
||||
@default('request_timeout')
|
||||
def request_timeout_default(self):
|
||||
return float(os.environ.get('JUPYTER_GATEWAY_REQUEST_TIMEOUT', self.request_timeout_default_value))
|
||||
|
||||
client_key = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The filename for client SSL key, if any. (JUPYTER_GATEWAY_CLIENT_KEY env var)
|
||||
"""
|
||||
)
|
||||
client_key_env = 'JUPYTER_GATEWAY_CLIENT_KEY'
|
||||
|
||||
@default('client_key')
|
||||
def _client_key_default(self):
|
||||
return os.environ.get(self.client_key_env)
|
||||
|
||||
client_cert = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The filename for client SSL certificate, if any. (JUPYTER_GATEWAY_CLIENT_CERT env var)
|
||||
"""
|
||||
)
|
||||
client_cert_env = 'JUPYTER_GATEWAY_CLIENT_CERT'
|
||||
|
||||
@default('client_cert')
|
||||
def _client_cert_default(self):
|
||||
return os.environ.get(self.client_cert_env)
|
||||
|
||||
ca_certs = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The filename of CA certificates or None to use defaults. (JUPYTER_GATEWAY_CA_CERTS env var)
|
||||
"""
|
||||
)
|
||||
ca_certs_env = 'JUPYTER_GATEWAY_CA_CERTS'
|
||||
|
||||
@default('ca_certs')
|
||||
def _ca_certs_default(self):
|
||||
return os.environ.get(self.ca_certs_env)
|
||||
|
||||
http_user = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The username for HTTP authentication. (JUPYTER_GATEWAY_HTTP_USER env var)
|
||||
"""
|
||||
)
|
||||
http_user_env = 'JUPYTER_GATEWAY_HTTP_USER'
|
||||
|
||||
@default('http_user')
|
||||
def _http_user_default(self):
|
||||
return os.environ.get(self.http_user_env)
|
||||
|
||||
http_pwd = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The password for HTTP authentication. (JUPYTER_GATEWAY_HTTP_PWD env var)
|
||||
"""
|
||||
)
|
||||
http_pwd_env = 'JUPYTER_GATEWAY_HTTP_PWD'
|
||||
|
||||
@default('http_pwd')
|
||||
def _http_pwd_default(self):
|
||||
return os.environ.get(self.http_pwd_env)
|
||||
|
||||
headers_default_value = '{}'
|
||||
headers_env = 'JUPYTER_GATEWAY_HEADERS'
|
||||
headers = Unicode(default_value=headers_default_value, allow_none=True, config=True,
|
||||
help="""Additional HTTP headers to pass on the request. This value will be converted to a dict.
|
||||
(JUPYTER_GATEWAY_HEADERS env var)
|
||||
"""
|
||||
)
|
||||
|
||||
@default('headers')
|
||||
def _headers_default(self):
|
||||
return os.environ.get(self.headers_env, self.headers_default_value)
|
||||
|
||||
auth_token = Unicode(default_value=None, allow_none=True, config=True,
|
||||
help="""The authorization token used in the HTTP headers. (JUPYTER_GATEWAY_AUTH_TOKEN env var)
|
||||
"""
|
||||
)
|
||||
auth_token_env = 'JUPYTER_GATEWAY_AUTH_TOKEN'
|
||||
|
||||
@default('auth_token')
|
||||
def _auth_token_default(self):
|
||||
return os.environ.get(self.auth_token_env, '')
|
||||
|
||||
validate_cert_default_value = True
|
||||
validate_cert_env = 'JUPYTER_GATEWAY_VALIDATE_CERT'
|
||||
validate_cert = Bool(default_value=validate_cert_default_value, config=True,
|
||||
help="""For HTTPS requests, determines if server's certificate should be validated or not.
|
||||
(JUPYTER_GATEWAY_VALIDATE_CERT env var)"""
|
||||
)
|
||||
|
||||
@default('validate_cert')
|
||||
def validate_cert_default(self):
|
||||
return bool(os.environ.get(self.validate_cert_env, str(self.validate_cert_default_value)) not in ['no', 'false'])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GatewayClient, self).__init__(**kwargs)
|
||||
self._static_args = {} # initialized on first use
|
||||
|
||||
env_whitelist_default_value = ''
|
||||
env_whitelist_env = 'JUPYTER_GATEWAY_ENV_WHITELIST'
|
||||
env_whitelist = Unicode(default_value=env_whitelist_default_value, config=True,
|
||||
help="""A comma-separated list of environment variable names that will be included, along with
|
||||
their values, in the kernel startup request. The corresponding `env_whitelist` configuration
|
||||
value must also be set on the Gateway server - since that configuration value indicates which
|
||||
environmental values to make available to the kernel. (JUPYTER_GATEWAY_ENV_WHITELIST env var)""")
|
||||
|
||||
@default('env_whitelist')
|
||||
def _env_whitelist_default(self):
|
||||
return os.environ.get(self.env_whitelist_env, self.env_whitelist_default_value)
|
||||
|
||||
@property
|
||||
def gateway_enabled(self):
|
||||
return bool(self.url is not None and len(self.url) > 0)
|
||||
|
||||
# Ensure KERNEL_LAUNCH_TIMEOUT has a default value.
|
||||
KERNEL_LAUNCH_TIMEOUT = int(os.environ.get('KERNEL_LAUNCH_TIMEOUT', 40))
|
||||
|
||||
def init_static_args(self):
|
||||
"""Initialize arguments used on every request. Since these are static values, we'll
|
||||
perform this operation once.
|
||||
|
||||
"""
|
||||
# Ensure that request timeout and KERNEL_LAUNCH_TIMEOUT are the same, taking the
|
||||
# greater value of the two.
|
||||
if self.request_timeout < float(GatewayClient.KERNEL_LAUNCH_TIMEOUT):
|
||||
self.request_timeout = float(GatewayClient.KERNEL_LAUNCH_TIMEOUT)
|
||||
elif self.request_timeout > float(GatewayClient.KERNEL_LAUNCH_TIMEOUT):
|
||||
GatewayClient.KERNEL_LAUNCH_TIMEOUT = int(self.request_timeout)
|
||||
# Ensure any adjustments are reflected in env.
|
||||
os.environ['KERNEL_LAUNCH_TIMEOUT'] = str(GatewayClient.KERNEL_LAUNCH_TIMEOUT)
|
||||
|
||||
self._static_args['headers'] = json.loads(self.headers)
|
||||
if 'Authorization' not in self._static_args['headers'].keys():
|
||||
self._static_args['headers'].update({
|
||||
'Authorization': 'token {}'.format(self.auth_token)
|
||||
})
|
||||
self._static_args['connect_timeout'] = self.connect_timeout
|
||||
self._static_args['request_timeout'] = self.request_timeout
|
||||
self._static_args['validate_cert'] = self.validate_cert
|
||||
if self.client_cert:
|
||||
self._static_args['client_cert'] = self.client_cert
|
||||
self._static_args['client_key'] = self.client_key
|
||||
if self.ca_certs:
|
||||
self._static_args['ca_certs'] = self.ca_certs
|
||||
if self.http_user:
|
||||
self._static_args['auth_username'] = self.http_user
|
||||
if self.http_pwd:
|
||||
self._static_args['auth_password'] = self.http_pwd
|
||||
|
||||
def load_connection_args(self, **kwargs):
|
||||
"""Merges the static args relative to the connection, with the given keyword arguments. If statics
|
||||
have yet to be initialized, we'll do that here.
|
||||
|
||||
"""
|
||||
if len(self._static_args) == 0:
|
||||
self.init_static_args()
|
||||
|
||||
kwargs.update(self._static_args)
|
||||
return kwargs
|
||||
|
||||
|
||||
@gen.coroutine
|
||||
def gateway_request(endpoint, **kwargs):
|
||||
"""Make an async request to kernel gateway endpoint, returns a response """
|
||||
client = AsyncHTTPClient()
|
||||
kwargs = GatewayClient.instance().load_connection_args(**kwargs)
|
||||
try:
|
||||
response = yield client.fetch(endpoint, **kwargs)
|
||||
# Trap a set of common exceptions so that we can inform the user that their Gateway url is incorrect
|
||||
# or the server is not running.
|
||||
# NOTE: We do this here since this handler is called during the Notebook's startup and subsequent refreshes
|
||||
# of the tree view.
|
||||
except ConnectionRefusedError as e:
|
||||
raise web.HTTPError(
|
||||
503,
|
||||
"Connection refused from Gateway server url '{}'. Check to be sure the"
|
||||
" Gateway instance is running.".format(GatewayClient.instance().url)
|
||||
) from e
|
||||
except HTTPError as e:
|
||||
# This can occur if the host is valid (e.g., foo.com) but there's nothing there.
|
||||
raise web.HTTPError(e.code, "Error attempting to connect to Gateway server url '{}'. "
|
||||
"Ensure gateway url is valid and the Gateway instance is running.".
|
||||
format(GatewayClient.instance().url)) from e
|
||||
except gaierror as e:
|
||||
raise web.HTTPError(
|
||||
404,
|
||||
"The Gateway server specified in the gateway_url '{}' doesn't appear to be valid. Ensure gateway "
|
||||
"url is valid and the Gateway instance is running.".format(GatewayClient.instance().url)
|
||||
) from e
|
||||
|
||||
raise gen.Return(response)
|
||||
|
||||
|
||||
class GatewayKernelManager(MappingKernelManager):
|
||||
"""Kernel manager that supports remote kernels hosted by Jupyter Kernel or Enterprise Gateway."""
|
||||
|
||||
# We'll maintain our own set of kernel ids
|
||||
_kernels = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GatewayKernelManager, self).__init__(**kwargs)
|
||||
self.base_endpoint = url_path_join(GatewayClient.instance().url, GatewayClient.instance().kernels_endpoint)
|
||||
|
||||
def __contains__(self, kernel_id):
|
||||
return kernel_id in self._kernels
|
||||
|
||||
def remove_kernel(self, kernel_id):
|
||||
"""Complete override since we want to be more tolerant of missing keys """
|
||||
try:
|
||||
return self._kernels.pop(kernel_id)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def _get_kernel_endpoint_url(self, kernel_id=None):
|
||||
"""Builds a url for the kernels endpoint
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_id: kernel UUID (optional)
|
||||
"""
|
||||
if kernel_id:
|
||||
return url_path_join(self.base_endpoint, url_escape(str(kernel_id)))
|
||||
|
||||
return self.base_endpoint
|
||||
|
||||
@gen.coroutine
|
||||
def start_kernel(self, kernel_id=None, path=None, **kwargs):
|
||||
"""Start a kernel for a session and return its kernel_id.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_id : uuid
|
||||
The uuid to associate the new kernel with. If this
|
||||
is not None, this kernel will be persistent whenever it is
|
||||
requested.
|
||||
path : API path
|
||||
The API path (unicode, '/' delimited) for the cwd.
|
||||
Will be transformed to an OS path relative to root_dir.
|
||||
"""
|
||||
self.log.info('Request start kernel: kernel_id=%s, path="%s"', kernel_id, path)
|
||||
|
||||
if kernel_id is None:
|
||||
if path is not None:
|
||||
kwargs['cwd'] = self.cwd_for_path(path)
|
||||
kernel_name = kwargs.get('kernel_name', 'python3')
|
||||
kernel_url = self._get_kernel_endpoint_url()
|
||||
self.log.debug("Request new kernel at: %s" % kernel_url)
|
||||
|
||||
# Let KERNEL_USERNAME take precedent over http_user config option.
|
||||
if os.environ.get('KERNEL_USERNAME') is None and GatewayClient.instance().http_user:
|
||||
os.environ['KERNEL_USERNAME'] = GatewayClient.instance().http_user
|
||||
|
||||
kernel_env = {k: v for (k, v) in dict(os.environ).items() if k.startswith('KERNEL_')
|
||||
or k in GatewayClient.instance().env_whitelist.split(",")}
|
||||
|
||||
# Convey the full path to where this notebook file is located.
|
||||
if path is not None and kernel_env.get('KERNEL_WORKING_DIR') is None:
|
||||
kernel_env['KERNEL_WORKING_DIR'] = kwargs['cwd']
|
||||
|
||||
json_body = json_encode({'name': kernel_name, 'env': kernel_env})
|
||||
|
||||
response = yield gateway_request(kernel_url, method='POST', body=json_body)
|
||||
kernel = json_decode(response.body)
|
||||
kernel_id = kernel['id']
|
||||
self.log.info("Kernel started: %s" % kernel_id)
|
||||
self.log.debug("Kernel args: %r" % kwargs)
|
||||
else:
|
||||
kernel = yield self.get_kernel(kernel_id)
|
||||
kernel_id = kernel['id']
|
||||
self.log.info("Using existing kernel: %s" % kernel_id)
|
||||
|
||||
self._kernels[kernel_id] = kernel
|
||||
raise gen.Return(kernel_id)
|
||||
|
||||
@gen.coroutine
|
||||
def get_kernel(self, kernel_id=None, **kwargs):
|
||||
"""Get kernel for kernel_id.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_id : uuid
|
||||
The uuid of the kernel.
|
||||
"""
|
||||
kernel_url = self._get_kernel_endpoint_url(kernel_id)
|
||||
self.log.debug("Request kernel at: %s" % kernel_url)
|
||||
try:
|
||||
response = yield gateway_request(kernel_url, method='GET')
|
||||
except web.HTTPError as error:
|
||||
if error.status_code == 404:
|
||||
self.log.warn("Kernel not found at: %s" % kernel_url)
|
||||
self.remove_kernel(kernel_id)
|
||||
kernel = None
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
kernel = json_decode(response.body)
|
||||
self._kernels[kernel_id] = kernel
|
||||
self.log.debug("Kernel retrieved: %s" % kernel)
|
||||
raise gen.Return(kernel)
|
||||
|
||||
@gen.coroutine
|
||||
def kernel_model(self, kernel_id):
|
||||
"""Return a dictionary of kernel information described in the
|
||||
JSON standard model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_id : uuid
|
||||
The uuid of the kernel.
|
||||
"""
|
||||
self.log.debug("RemoteKernelManager.kernel_model: %s", kernel_id)
|
||||
model = yield self.get_kernel(kernel_id)
|
||||
raise gen.Return(model)
|
||||
|
||||
@gen.coroutine
|
||||
def list_kernels(self, **kwargs):
|
||||
"""Get a list of kernels."""
|
||||
kernel_url = self._get_kernel_endpoint_url()
|
||||
self.log.debug("Request list kernels: %s", kernel_url)
|
||||
response = yield gateway_request(kernel_url, method='GET')
|
||||
kernels = json_decode(response.body)
|
||||
self._kernels = {x['id']: x for x in kernels}
|
||||
raise gen.Return(kernels)
|
||||
|
||||
@gen.coroutine
|
||||
def shutdown_kernel(self, kernel_id, now=False, restart=False):
|
||||
"""Shutdown a kernel by its kernel uuid.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
kernel_id : uuid
|
||||
The id of the kernel to shutdown.
|
||||
now : bool
|
||||
Shutdown the kernel immediately (True) or gracefully (False)
|
||||
restart : bool
|
||||
The purpose of this shutdown is to restart the kernel (True)
|
||||
"""
|
||||
kernel_url = self._get_kernel_endpoint_url(kernel_id)
|
||||
self.log.debug("Request shutdown kernel at: %s", kernel_url)
|
||||
response = yield gateway_request(kernel_url, method='DELETE')
|
||||
self.log.debug("Shutdown kernel response: %d %s", response.code, response.reason)
|
||||
self.remove_kernel(kernel_id)
|
||||
|
||||
@gen.coroutine
|
||||
def restart_kernel(self, kernel_id, now=False, **kwargs):
|
||||
"""Restart a kernel by its kernel uuid.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
kernel_id : uuid
|
||||
The id of the kernel to restart.
|
||||
"""
|
||||
kernel_url = self._get_kernel_endpoint_url(kernel_id) + '/restart'
|
||||
self.log.debug("Request restart kernel at: %s", kernel_url)
|
||||
response = yield gateway_request(kernel_url, method='POST', body=json_encode({}))
|
||||
self.log.debug("Restart kernel response: %d %s", response.code, response.reason)
|
||||
|
||||
@gen.coroutine
|
||||
def interrupt_kernel(self, kernel_id, **kwargs):
|
||||
"""Interrupt a kernel by its kernel uuid.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
kernel_id : uuid
|
||||
The id of the kernel to interrupt.
|
||||
"""
|
||||
kernel_url = self._get_kernel_endpoint_url(kernel_id) + '/interrupt'
|
||||
self.log.debug("Request interrupt kernel at: %s", kernel_url)
|
||||
response = yield gateway_request(kernel_url, method='POST', body=json_encode({}))
|
||||
self.log.debug("Interrupt kernel response: %d %s", response.code, response.reason)
|
||||
|
||||
def shutdown_all(self, now=False):
|
||||
"""Shutdown all kernels."""
|
||||
# Note: We have to make this sync because the NotebookApp does not wait for async.
|
||||
shutdown_kernels = []
|
||||
kwargs = {'method': 'DELETE'}
|
||||
kwargs = GatewayClient.instance().load_connection_args(**kwargs)
|
||||
client = HTTPClient()
|
||||
for kernel_id in self._kernels.keys():
|
||||
kernel_url = self._get_kernel_endpoint_url(kernel_id)
|
||||
self.log.debug("Request delete kernel at: %s", kernel_url)
|
||||
try:
|
||||
response = client.fetch(kernel_url, **kwargs)
|
||||
except HTTPError:
|
||||
pass
|
||||
else:
|
||||
self.log.debug("Delete kernel response: %d %s", response.code, response.reason)
|
||||
shutdown_kernels.append(kernel_id) # avoid changing dict size during iteration
|
||||
client.close()
|
||||
for kernel_id in shutdown_kernels:
|
||||
self.remove_kernel(kernel_id)
|
||||
|
||||
|
||||
|
||||
class GatewayKernelSpecManager(KernelSpecManager):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(GatewayKernelSpecManager, self).__init__(**kwargs)
|
||||
base_endpoint = url_path_join(GatewayClient.instance().url,
|
||||
GatewayClient.instance().kernelspecs_endpoint)
|
||||
|
||||
self.base_endpoint = GatewayKernelSpecManager._get_endpoint_for_user_filter(base_endpoint)
|
||||
self.base_resource_endpoint = url_path_join(GatewayClient.instance().url,
|
||||
GatewayClient.instance().kernelspecs_resource_endpoint)
|
||||
|
||||
@staticmethod
|
||||
def _get_endpoint_for_user_filter(default_endpoint):
|
||||
kernel_user = os.environ.get('KERNEL_USERNAME')
|
||||
if kernel_user:
|
||||
return '?user='.join([default_endpoint, kernel_user])
|
||||
return default_endpoint
|
||||
|
||||
def _get_kernelspecs_endpoint_url(self, kernel_name=None):
|
||||
"""Builds a url for the kernels endpoint
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_name: kernel name (optional)
|
||||
"""
|
||||
if kernel_name:
|
||||
return url_path_join(self.base_endpoint, url_escape(kernel_name))
|
||||
|
||||
return self.base_endpoint
|
||||
|
||||
@gen.coroutine
|
||||
def get_all_specs(self):
|
||||
fetched_kspecs = yield self.list_kernel_specs()
|
||||
|
||||
# get the default kernel name and compare to that of this server.
|
||||
# If different log a warning and reset the default. However, the
|
||||
# caller of this method will still return this server's value until
|
||||
# the next fetch of kernelspecs - at which time they'll match.
|
||||
km = self.parent.kernel_manager
|
||||
remote_default_kernel_name = fetched_kspecs.get('default')
|
||||
if remote_default_kernel_name != km.default_kernel_name:
|
||||
self.log.info("Default kernel name on Gateway server ({gateway_default}) differs from "
|
||||
"Notebook server ({notebook_default}). Updating to Gateway server's value.".
|
||||
format(gateway_default=remote_default_kernel_name,
|
||||
notebook_default=km.default_kernel_name))
|
||||
km.default_kernel_name = remote_default_kernel_name
|
||||
|
||||
remote_kspecs = fetched_kspecs.get('kernelspecs')
|
||||
raise gen.Return(remote_kspecs)
|
||||
|
||||
@gen.coroutine
|
||||
def list_kernel_specs(self):
|
||||
"""Get a list of kernel specs."""
|
||||
kernel_spec_url = self._get_kernelspecs_endpoint_url()
|
||||
self.log.debug("Request list kernel specs at: %s", kernel_spec_url)
|
||||
response = yield gateway_request(kernel_spec_url, method='GET')
|
||||
kernel_specs = json_decode(response.body)
|
||||
raise gen.Return(kernel_specs)
|
||||
|
||||
@gen.coroutine
|
||||
def get_kernel_spec(self, kernel_name, **kwargs):
|
||||
"""Get kernel spec for kernel_name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_name : str
|
||||
The name of the kernel.
|
||||
"""
|
||||
kernel_spec_url = self._get_kernelspecs_endpoint_url(kernel_name=str(kernel_name))
|
||||
self.log.debug("Request kernel spec at: %s" % kernel_spec_url)
|
||||
try:
|
||||
response = yield gateway_request(kernel_spec_url, method='GET')
|
||||
except web.HTTPError as error:
|
||||
if error.status_code == 404:
|
||||
# Convert not found to KeyError since that's what the Notebook handler expects
|
||||
# message is not used, but might as well make it useful for troubleshooting
|
||||
raise KeyError(
|
||||
'kernelspec {kernel_name} not found on Gateway server at: {gateway_url}'.
|
||||
format(kernel_name=kernel_name, gateway_url=GatewayClient.instance().url)
|
||||
) from error
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
kernel_spec = json_decode(response.body)
|
||||
|
||||
raise gen.Return(kernel_spec)
|
||||
|
||||
@gen.coroutine
|
||||
def get_kernel_spec_resource(self, kernel_name, path):
|
||||
"""Get kernel spec for kernel_name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
kernel_name : str
|
||||
The name of the kernel.
|
||||
path : str
|
||||
The name of the desired resource
|
||||
"""
|
||||
kernel_spec_resource_url = url_path_join(self.base_resource_endpoint, str(kernel_name), str(path))
|
||||
self.log.debug("Request kernel spec resource '{}' at: {}".format(path, kernel_spec_resource_url))
|
||||
try:
|
||||
response = yield gateway_request(kernel_spec_resource_url, method='GET')
|
||||
except web.HTTPError as error:
|
||||
if error.status_code == 404:
|
||||
kernel_spec_resource = None
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
kernel_spec_resource = response.body
|
||||
raise gen.Return(kernel_spec_resource)
|
||||
|
||||
|
||||
class GatewaySessionManager(SessionManager):
|
||||
kernel_manager = Instance('notebook.gateway.managers.GatewayKernelManager')
|
||||
|
||||
@gen.coroutine
|
||||
def kernel_culled(self, kernel_id):
|
||||
"""Checks if the kernel is still considered alive and returns true if its not found. """
|
||||
kernel = yield self.kernel_manager.get_kernel(kernel_id)
|
||||
raise gen.Return(kernel is None)
|
103
venv/Lib/site-packages/notebook/i18n/__init__.py
Normal file
103
venv/Lib/site-packages/notebook/i18n/__init__.py
Normal file
|
@ -0,0 +1,103 @@
|
|||
"""Server functions for loading translations
|
||||
"""
|
||||
from collections import defaultdict
|
||||
import errno
|
||||
import io
|
||||
import json
|
||||
from os.path import dirname, join as pjoin
|
||||
import re
|
||||
|
||||
I18N_DIR = dirname(__file__)
|
||||
# Cache structure:
|
||||
# {'nbjs': { # Domain
|
||||
# 'zh-CN': { # Language code
|
||||
# <english string>: <translated string>
|
||||
# ...
|
||||
# }
|
||||
# }}
|
||||
TRANSLATIONS_CACHE = {'nbjs': {}}
|
||||
|
||||
|
||||
_accept_lang_re = re.compile(r'''
|
||||
(?P<lang>[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?)
|
||||
(\s*;\s*q\s*=\s*
|
||||
(?P<qvalue>[01](.\d+)?)
|
||||
)?''', re.VERBOSE)
|
||||
|
||||
def parse_accept_lang_header(accept_lang):
|
||||
"""Parses the 'Accept-Language' HTTP header.
|
||||
|
||||
Returns a list of language codes in *ascending* order of preference
|
||||
(with the most preferred language last).
|
||||
"""
|
||||
by_q = defaultdict(list)
|
||||
for part in accept_lang.split(','):
|
||||
m = _accept_lang_re.match(part.strip())
|
||||
if not m:
|
||||
continue
|
||||
lang, qvalue = m.group('lang', 'qvalue')
|
||||
# Browser header format is zh-CN, gettext uses zh_CN
|
||||
lang = lang.replace('-', '_')
|
||||
if qvalue is None:
|
||||
qvalue = 1.
|
||||
else:
|
||||
qvalue = float(qvalue)
|
||||
if qvalue == 0:
|
||||
continue # 0 means not accepted
|
||||
by_q[qvalue].append(lang)
|
||||
if '_' in lang:
|
||||
short = lang.split('_')[0]
|
||||
if short != 'en':
|
||||
by_q[qvalue].append(short)
|
||||
|
||||
res = []
|
||||
for qvalue, langs in sorted(by_q.items()):
|
||||
res.extend(sorted(langs))
|
||||
return res
|
||||
|
||||
def load(language, domain='nbjs'):
|
||||
"""Load translations from an nbjs.json file"""
|
||||
try:
|
||||
f = io.open(pjoin(I18N_DIR, language, 'LC_MESSAGES', 'nbjs.json'),
|
||||
encoding='utf-8')
|
||||
except IOError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
return {}
|
||||
|
||||
with f:
|
||||
data = json.load(f)
|
||||
return data["locale_data"][domain]
|
||||
|
||||
def cached_load(language, domain='nbjs'):
|
||||
"""Load translations for one language, using in-memory cache if available"""
|
||||
domain_cache = TRANSLATIONS_CACHE[domain]
|
||||
try:
|
||||
return domain_cache[language]
|
||||
except KeyError:
|
||||
data = load(language, domain)
|
||||
domain_cache[language] = data
|
||||
return data
|
||||
|
||||
def combine_translations(accept_language, domain='nbjs'):
|
||||
"""Combine translations for multiple accepted languages.
|
||||
|
||||
Returns data re-packaged in jed1.x format.
|
||||
"""
|
||||
lang_codes = parse_accept_lang_header(accept_language)
|
||||
combined = {}
|
||||
for language in lang_codes:
|
||||
if language == 'en':
|
||||
# en is default, all translations are in frontend.
|
||||
combined.clear()
|
||||
else:
|
||||
combined.update(cached_load(language, domain))
|
||||
|
||||
combined[''] = {"domain":"nbjs"}
|
||||
|
||||
return {
|
||||
"domain": domain,
|
||||
"locale_data": {
|
||||
domain: combined
|
||||
}
|
||||
}
|
Binary file not shown.
1355
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.json
Normal file
1355
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.json
Normal file
File diff suppressed because it is too large
Load diff
2131
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.po
Normal file
2131
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbjs.po
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbui.mo
Normal file
BIN
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbui.mo
Normal file
Binary file not shown.
745
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbui.po
Normal file
745
venv/Lib/site-packages/notebook/i18n/fr_FR/LC_MESSAGES/nbui.po
Normal file
|
@ -0,0 +1,745 @@
|
|||
# Translations template for Jupyter.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the Jupyter project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Jupyter VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2018-08-29 17:49+0200\n"
|
||||
"PO-Revision-Date: 2018-09-15 17:55+0200\n"
|
||||
"Last-Translator: Jocelyn Delalande <jocelyn@delalande.fr>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
"X-Generator: Poedit 1.8.11\n"
|
||||
|
||||
#: notebook/templates/404.html:3
|
||||
msgid "You are requesting a page that does not exist!"
|
||||
msgstr "Vous demandez une page qui n'existe pas !"
|
||||
|
||||
#: notebook/templates/edit.html:37
|
||||
msgid "current mode"
|
||||
msgstr "mode actuel"
|
||||
|
||||
#: notebook/templates/edit.html:48 notebook/templates/notebook.html:78
|
||||
msgid "File"
|
||||
msgstr "Fichier"
|
||||
|
||||
#: notebook/templates/edit.html:50 notebook/templates/tree.html:57
|
||||
msgid "New"
|
||||
msgstr "Nouveau"
|
||||
|
||||
#: notebook/templates/edit.html:51
|
||||
msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
#: notebook/templates/edit.html:52 notebook/templates/tree.html:36
|
||||
msgid "Rename"
|
||||
msgstr "Renommer"
|
||||
|
||||
#: notebook/templates/edit.html:53 notebook/templates/tree.html:38
|
||||
msgid "Download"
|
||||
msgstr "Télécharger"
|
||||
|
||||
#: notebook/templates/edit.html:56 notebook/templates/notebook.html:131
|
||||
#: notebook/templates/tree.html:41
|
||||
msgid "Edit"
|
||||
msgstr "Édition"
|
||||
|
||||
#: notebook/templates/edit.html:58
|
||||
msgid "Find"
|
||||
msgstr "Rechercher"
|
||||
|
||||
#: notebook/templates/edit.html:59
|
||||
msgid "Find & Replace"
|
||||
msgstr "Rechercher & Remplacer"
|
||||
|
||||
#: notebook/templates/edit.html:61
|
||||
msgid "Key Map"
|
||||
msgstr "Raccourcis clavier"
|
||||
|
||||
#: notebook/templates/edit.html:62
|
||||
msgid "Default"
|
||||
msgstr "Par Défaut"
|
||||
|
||||
#: notebook/templates/edit.html:63
|
||||
msgid "Sublime Text"
|
||||
msgstr "Sublime Text"
|
||||
|
||||
#: notebook/templates/edit.html:68 notebook/templates/notebook.html:159
|
||||
#: notebook/templates/tree.html:40
|
||||
msgid "View"
|
||||
msgstr "Affichage"
|
||||
|
||||
#: notebook/templates/edit.html:70 notebook/templates/notebook.html:162
|
||||
msgid "Show/Hide the logo and notebook title (above menu bar)"
|
||||
msgstr "Afficher/Masquer le logo et le titre du notebook (au-dessus de la "
|
||||
"barre de menu)"
|
||||
|
||||
#: notebook/templates/edit.html:71 notebook/templates/notebook.html:163
|
||||
msgid "Toggle Header"
|
||||
msgstr "Afficher/Masquer l'en-tête"
|
||||
|
||||
#: notebook/templates/edit.html:72 notebook/templates/notebook.html:171
|
||||
msgid "Toggle Line Numbers"
|
||||
msgstr "Afficher/Masquer les numéros de ligne"
|
||||
|
||||
#: notebook/templates/edit.html:75
|
||||
msgid "Language"
|
||||
msgstr "Langage"
|
||||
|
||||
#: notebook/templates/error.html:23
|
||||
msgid "The error was:"
|
||||
msgstr "L'erreur était :"
|
||||
|
||||
#: notebook/templates/login.html:24
|
||||
msgid "Password or token:"
|
||||
msgstr "Mot de passe ou jeton:"
|
||||
|
||||
#: notebook/templates/login.html:26
|
||||
msgid "Password:"
|
||||
msgstr "Mot de passe :"
|
||||
|
||||
#: notebook/templates/login.html:31
|
||||
msgid "Log in"
|
||||
msgstr "Se connecter"
|
||||
|
||||
#: notebook/templates/login.html:39
|
||||
msgid "No login available, you shouldn't be seeing this page."
|
||||
msgstr "Connexion non disponible, vous ne devriez pas voir cette page."
|
||||
|
||||
#: notebook/templates/logout.html:24
|
||||
#, python-format
|
||||
msgid "Proceed to the <a href=\"%(base_url)s\">dashboard"
|
||||
msgstr "Continuer vers le <a href=\"%(base_url)s\">tableau de bord"
|
||||
|
||||
#: notebook/templates/logout.html:26
|
||||
#, python-format
|
||||
msgid "Proceed to the <a href=\"%(base_url)slogin\">login page"
|
||||
msgstr "Continuer vers la <a href=\"%(base_url)slogin\">page de connexion"
|
||||
|
||||
#: notebook/templates/notebook.html:62
|
||||
msgid "Menu"
|
||||
msgstr "Menu"
|
||||
|
||||
#: notebook/templates/notebook.html:65 notebook/templates/notebook.html:254
|
||||
msgid "Kernel"
|
||||
msgstr "Noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:68
|
||||
msgid "This notebook is read-only"
|
||||
msgstr "Ce notebook est en lecture seule"
|
||||
|
||||
#: notebook/templates/notebook.html:81
|
||||
msgid "New Notebook"
|
||||
msgstr "Nouveau Notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:85
|
||||
msgid "Opens a new window with the Dashboard view"
|
||||
msgstr "Ouvre une nouvelle fenêtre de tableau de bord"
|
||||
|
||||
#: notebook/templates/notebook.html:86
|
||||
msgid "Open..."
|
||||
msgstr "Ouvrir..."
|
||||
|
||||
#: notebook/templates/notebook.html:90
|
||||
msgid "Open a copy of this notebook's contents and start a new kernel"
|
||||
msgstr "Ouvrir une copie du contenu de ce notebook et démarrer un nouveau noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:91
|
||||
msgid "Make a Copy..."
|
||||
msgstr "Faire une copie..."
|
||||
|
||||
#: notebook/templates/notebook.html:92
|
||||
msgid "Rename..."
|
||||
msgstr "Renommer..."
|
||||
|
||||
#: notebook/templates/notebook.html:93
|
||||
msgid "Save and Checkpoint"
|
||||
msgstr "Créer une nouvelle sauvegarde"
|
||||
|
||||
#: notebook/templates/notebook.html:96
|
||||
msgid "Revert to Checkpoint"
|
||||
msgstr "Restaurer la sauvegarde"
|
||||
|
||||
#: notebook/templates/notebook.html:106
|
||||
msgid "Print Preview"
|
||||
msgstr "Imprimer l'aperçu"
|
||||
|
||||
#: notebook/templates/notebook.html:107
|
||||
msgid "Download as"
|
||||
msgstr "Télécharger au format"
|
||||
|
||||
#: notebook/templates/notebook.html:109
|
||||
msgid "Notebook (.ipynb)"
|
||||
msgstr "Notebook (.ipynb)"
|
||||
|
||||
#: notebook/templates/notebook.html:110
|
||||
msgid "Script"
|
||||
msgstr "Script"
|
||||
|
||||
#: notebook/templates/notebook.html:111
|
||||
msgid "HTML (.html)"
|
||||
msgstr "HTML (.html)"
|
||||
|
||||
#: notebook/templates/notebook.html:112
|
||||
msgid "Markdown (.md)"
|
||||
msgstr "Markdown (.md)"
|
||||
|
||||
#: notebook/templates/notebook.html:113
|
||||
msgid "reST (.rst)"
|
||||
msgstr "reST (.rst)"
|
||||
|
||||
#: notebook/templates/notebook.html:114
|
||||
msgid "LaTeX (.tex)"
|
||||
msgstr "LaTeX (.tex)"
|
||||
|
||||
#: notebook/templates/notebook.html:115
|
||||
msgid "PDF via LaTeX (.pdf)"
|
||||
msgstr "PDF via LaTeX (.pdf)"
|
||||
|
||||
#: notebook/templates/notebook.html:118
|
||||
msgid "Deploy as"
|
||||
msgstr "Déployer en tant que"
|
||||
|
||||
#: notebook/templates/notebook.html:123
|
||||
msgid "Trust the output of this notebook"
|
||||
msgstr "Faire confiance à la sortie de ce notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:124
|
||||
msgid "Trust Notebook"
|
||||
msgstr "Faire confiance au notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:127
|
||||
msgid "Shutdown this notebook's kernel, and close this window"
|
||||
msgstr "Arrêter le noyau de ce notebook et fermer cette fenêtre"
|
||||
|
||||
#: notebook/templates/notebook.html:128
|
||||
msgid "Close and Halt"
|
||||
msgstr "Fermer et arrêter"
|
||||
|
||||
#: notebook/templates/notebook.html:133
|
||||
msgid "Cut Cells"
|
||||
msgstr "Couper les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:134
|
||||
msgid "Copy Cells"
|
||||
msgstr "Copier les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:135
|
||||
msgid "Paste Cells Above"
|
||||
msgstr "Coller les cellules avant"
|
||||
|
||||
#: notebook/templates/notebook.html:136
|
||||
msgid "Paste Cells Below"
|
||||
msgstr "Coller les cellules après"
|
||||
|
||||
#: notebook/templates/notebook.html:137
|
||||
msgid "Paste Cells & Replace"
|
||||
msgstr "Coller les cellules & remplacer"
|
||||
|
||||
#: notebook/templates/notebook.html:138
|
||||
msgid "Delete Cells"
|
||||
msgstr "Supprimer les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:139
|
||||
msgid "Undo Delete Cells"
|
||||
msgstr "Annuler la suppression des cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:141
|
||||
msgid "Split Cell"
|
||||
msgstr "Diviser la cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:142
|
||||
msgid "Merge Cell Above"
|
||||
msgstr "Fusionner avec la cellule précédente"
|
||||
|
||||
#: notebook/templates/notebook.html:143
|
||||
msgid "Merge Cell Below"
|
||||
msgstr "Fusionner avec la cellule suivante"
|
||||
|
||||
#: notebook/templates/notebook.html:145
|
||||
msgid "Move Cell Up"
|
||||
msgstr "Déplacer la cellule vers le haut"
|
||||
|
||||
#: notebook/templates/notebook.html:146
|
||||
msgid "Move Cell Down"
|
||||
msgstr "Déplacer la cellule vers le bas"
|
||||
|
||||
#: notebook/templates/notebook.html:148
|
||||
msgid "Edit Notebook Metadata"
|
||||
msgstr "Éditer les méta-données du notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:150
|
||||
msgid "Find and Replace"
|
||||
msgstr "Rechercher et remplacer"
|
||||
|
||||
#: notebook/templates/notebook.html:152
|
||||
msgid "Cut Cell Attachments"
|
||||
msgstr "Couper les pièces-Jointes de la cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:153
|
||||
msgid "Copy Cell Attachments"
|
||||
msgstr "Copier les pièces-jointes de la cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:154
|
||||
msgid "Paste Cell Attachments"
|
||||
msgstr "Coller les pièces-jointes de la cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:156
|
||||
msgid "Insert Image"
|
||||
msgstr "Insérer une image"
|
||||
|
||||
#: notebook/templates/notebook.html:166
|
||||
msgid "Show/Hide the action icons (below menu bar)"
|
||||
msgstr "Afficher/Masquer les icônes d'action (en-dessous de la barre de menu)"
|
||||
|
||||
#: notebook/templates/notebook.html:167
|
||||
msgid "Toggle Toolbar"
|
||||
msgstr "Afficher/Masquer la barre d'outils"
|
||||
|
||||
#: notebook/templates/notebook.html:170
|
||||
msgid "Show/Hide line numbers in cells"
|
||||
msgstr "Afficher/Masquer les numéros de ligne dans les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:174
|
||||
msgid "Cell Toolbar"
|
||||
msgstr "Barre d'outil de cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:179
|
||||
msgid "Insert"
|
||||
msgstr "Insérer"
|
||||
|
||||
#: notebook/templates/notebook.html:182
|
||||
msgid "Insert an empty Code cell above the currently active cell"
|
||||
msgstr "Insérer une cellule de code vide avant de la cellule active"
|
||||
|
||||
#: notebook/templates/notebook.html:183
|
||||
msgid "Insert Cell Above"
|
||||
msgstr "Insérer une cellule avant"
|
||||
|
||||
#: notebook/templates/notebook.html:185
|
||||
msgid "Insert an empty Code cell below the currently active cell"
|
||||
msgstr "Insérer une cellule de code vide après la cellule active"
|
||||
|
||||
#: notebook/templates/notebook.html:186
|
||||
msgid "Insert Cell Below"
|
||||
msgstr "Insérer une cellule après"
|
||||
|
||||
#: notebook/templates/notebook.html:189
|
||||
msgid "Cell"
|
||||
msgstr "Cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:191
|
||||
msgid "Run this cell, and move cursor to the next one"
|
||||
msgstr "Exécuter cette cellule, et déplacer le curseur à la suivante"
|
||||
|
||||
#: notebook/templates/notebook.html:192
|
||||
msgid "Run Cells"
|
||||
msgstr "Exécuter les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:193
|
||||
msgid "Run this cell, select below"
|
||||
msgstr "Exécuter cette cellule, sélectionner la suivante"
|
||||
|
||||
#: notebook/templates/notebook.html:194
|
||||
msgid "Run Cells and Select Below"
|
||||
msgstr "Exécuter les cellules et sélectionner la suivante"
|
||||
|
||||
#: notebook/templates/notebook.html:195
|
||||
msgid "Run this cell, insert below"
|
||||
msgstr "Exécuter la cellule et insérer à la suite"
|
||||
|
||||
#: notebook/templates/notebook.html:196
|
||||
msgid "Run Cells and Insert Below"
|
||||
msgstr "Exécuter les cellules et insérer après"
|
||||
|
||||
#: notebook/templates/notebook.html:197
|
||||
msgid "Run all cells in the notebook"
|
||||
msgstr "Exécuter toutes les cellules du notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:198
|
||||
msgid "Run All"
|
||||
msgstr "Exécuter tout"
|
||||
|
||||
#: notebook/templates/notebook.html:199
|
||||
msgid "Run all cells above (but not including) this cell"
|
||||
msgstr "Exécuter toutes les cellules avant celle-ci (non incluse)"
|
||||
|
||||
#: notebook/templates/notebook.html:200
|
||||
msgid "Run All Above"
|
||||
msgstr "Exécuter toutes les précédentes"
|
||||
|
||||
#: notebook/templates/notebook.html:201
|
||||
msgid "Run this cell and all cells below it"
|
||||
msgstr "Exécuter cette cellule et toutes les suivantes"
|
||||
|
||||
#: notebook/templates/notebook.html:202
|
||||
msgid "Run All Below"
|
||||
msgstr "Exécuter toutes les suivantes"
|
||||
|
||||
#: notebook/templates/notebook.html:205
|
||||
msgid ""
|
||||
"All cells in the notebook have a cell type. By default, new cells are "
|
||||
"created as 'Code' cells"
|
||||
msgstr ""
|
||||
"Toutes les cellules dans le notebook ont un type de "
|
||||
"cellule. Par défaut, les nouvelles cellules sont de type 'Code'"
|
||||
|
||||
#: notebook/templates/notebook.html:206
|
||||
msgid "Cell Type"
|
||||
msgstr "Type de cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:209
|
||||
msgid ""
|
||||
"Contents will be sent to the kernel for execution, and output will display "
|
||||
"in the footer of cell"
|
||||
msgstr ""
|
||||
"Le contenu sera envoyé au noyau pour exécution, et la sortie sera affichée "
|
||||
"dans le pied de cellule"
|
||||
|
||||
#: notebook/templates/notebook.html:212
|
||||
msgid "Contents will be rendered as HTML and serve as explanatory text"
|
||||
msgstr ""
|
||||
"Le contenu sera rendu en tant que HTML afin de servir de texte explicatif"
|
||||
|
||||
#: notebook/templates/notebook.html:213 notebook/templates/notebook.html:298
|
||||
msgid "Markdown"
|
||||
msgstr "Markdown"
|
||||
|
||||
#: notebook/templates/notebook.html:215
|
||||
msgid "Contents will pass through nbconvert unmodified"
|
||||
msgstr "Le contenu passera par nbconvert qui ne l'altèrera pas"
|
||||
|
||||
#: notebook/templates/notebook.html:216
|
||||
msgid "Raw NBConvert"
|
||||
msgstr "Texte Brut (pour NBConvert)"
|
||||
|
||||
#: notebook/templates/notebook.html:220
|
||||
msgid "Current Outputs"
|
||||
msgstr "Sorties actuelles"
|
||||
|
||||
#: notebook/templates/notebook.html:223
|
||||
msgid "Hide/Show the output of the current cell"
|
||||
msgstr "Masquer/Afficher la sortie de la cellule actuelle"
|
||||
|
||||
#: notebook/templates/notebook.html:224 notebook/templates/notebook.html:240
|
||||
msgid "Toggle"
|
||||
msgstr "Afficher/Masquer"
|
||||
|
||||
#: notebook/templates/notebook.html:227
|
||||
msgid "Scroll the output of the current cell"
|
||||
msgstr "Faire défiler la sortie de la cellule actuelle"
|
||||
|
||||
#: notebook/templates/notebook.html:228 notebook/templates/notebook.html:244
|
||||
msgid "Toggle Scrolling"
|
||||
msgstr "Activer/Désactiver le défilement"
|
||||
|
||||
#: notebook/templates/notebook.html:231
|
||||
msgid "Clear the output of the current cell"
|
||||
msgstr "Effacer la sortie de la cellule actuelle"
|
||||
|
||||
#: notebook/templates/notebook.html:232 notebook/templates/notebook.html:248
|
||||
msgid "Clear"
|
||||
msgstr "Effacer"
|
||||
|
||||
#: notebook/templates/notebook.html:236
|
||||
msgid "All Output"
|
||||
msgstr "Toute la sortie"
|
||||
|
||||
#: notebook/templates/notebook.html:239
|
||||
msgid "Hide/Show the output of all cells"
|
||||
msgstr "Afficher/Masquer la sortie de toutes les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:243
|
||||
msgid "Scroll the output of all cells"
|
||||
msgstr "Faire défiler la sortie de toutes les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:247
|
||||
msgid "Clear the output of all cells"
|
||||
msgstr "Effacer la sortie de toutes les cellules"
|
||||
|
||||
#: notebook/templates/notebook.html:257
|
||||
msgid "Send Keyboard Interrupt (CTRL-C) to the Kernel"
|
||||
msgstr "Envoyer l'interruption clavier (CTRL-C) au noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:258
|
||||
msgid "Interrupt"
|
||||
msgstr "Interrompre"
|
||||
|
||||
#: notebook/templates/notebook.html:261
|
||||
msgid "Restart the Kernel"
|
||||
msgstr "Redémarrer le noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:262
|
||||
msgid "Restart"
|
||||
msgstr "Redémarrer"
|
||||
|
||||
#: notebook/templates/notebook.html:265
|
||||
msgid "Restart the Kernel and clear all output"
|
||||
msgstr "Redémarrer le noyau et effacer toutes les sorties"
|
||||
|
||||
#: notebook/templates/notebook.html:266
|
||||
msgid "Restart & Clear Output"
|
||||
msgstr "Redémarrer & effacer les sorties"
|
||||
|
||||
#: notebook/templates/notebook.html:269
|
||||
msgid "Restart the Kernel and re-run the notebook"
|
||||
msgstr "Redémarrer le noyau et ré-exécuter le noteboook"
|
||||
|
||||
#: notebook/templates/notebook.html:270
|
||||
msgid "Restart & Run All"
|
||||
msgstr "Redémarrer & tout exécuter"
|
||||
|
||||
#: notebook/templates/notebook.html:273
|
||||
msgid "Reconnect to the Kernel"
|
||||
msgstr "Reconnecter au noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:274
|
||||
msgid "Reconnect"
|
||||
msgstr "Reconnecter"
|
||||
|
||||
#: notebook/templates/notebook.html:282
|
||||
msgid "Change kernel"
|
||||
msgstr "Changer de noyau"
|
||||
|
||||
#: notebook/templates/notebook.html:287
|
||||
msgid "Help"
|
||||
msgstr "Aide"
|
||||
|
||||
#: notebook/templates/notebook.html:290
|
||||
msgid "A quick tour of the notebook user interface"
|
||||
msgstr "Une rapide visite de l'interface utilisateur du notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:290
|
||||
msgid "User Interface Tour"
|
||||
msgstr "Visite de l'interface utilisateur"
|
||||
|
||||
#: notebook/templates/notebook.html:291
|
||||
msgid "Opens a tooltip with all keyboard shortcuts"
|
||||
msgstr "Ouvre une infobulle listant tous les raccourcis clavier"
|
||||
|
||||
#: notebook/templates/notebook.html:291
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "Raccourcis clavier"
|
||||
|
||||
#: notebook/templates/notebook.html:292
|
||||
msgid "Opens a dialog allowing you to edit Keyboard shortcuts"
|
||||
msgstr "Ouvre une boîte de dialogue permettant de modifier les raccourcis clavier"
|
||||
|
||||
#: notebook/templates/notebook.html:292
|
||||
msgid "Edit Keyboard Shortcuts"
|
||||
msgstr "Editer les raccourcis clavier"
|
||||
|
||||
#: notebook/templates/notebook.html:297
|
||||
msgid "Notebook Help"
|
||||
msgstr "Aide notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:303
|
||||
msgid "Opens in a new window"
|
||||
msgstr "S'ouvre dans une nouvelle fenêtre"
|
||||
|
||||
#: notebook/templates/notebook.html:319
|
||||
msgid "About Jupyter Notebook"
|
||||
msgstr "À propos de Jupyter Notebook"
|
||||
|
||||
#: notebook/templates/notebook.html:319
|
||||
msgid "About"
|
||||
msgstr "À propos"
|
||||
|
||||
#: notebook/templates/page.html:114
|
||||
msgid "Jupyter Notebook requires JavaScript."
|
||||
msgstr "Jupyter Notebook nécessite JavaScript"
|
||||
|
||||
#: notebook/templates/page.html:115
|
||||
msgid "Please enable it to proceed. "
|
||||
msgstr "Merci de l'activer pour continuer."
|
||||
|
||||
#: notebook/templates/page.html:121
|
||||
msgid "dashboard"
|
||||
msgstr "tableau de bord"
|
||||
|
||||
#: notebook/templates/page.html:132
|
||||
msgid "Logout"
|
||||
msgstr "Se déconnecter"
|
||||
|
||||
#: notebook/templates/page.html:134
|
||||
msgid "Login"
|
||||
msgstr "Se connecter"
|
||||
|
||||
#: notebook/templates/tree.html:23
|
||||
msgid "Files"
|
||||
msgstr "Fichiers"
|
||||
|
||||
#: notebook/templates/tree.html:24
|
||||
msgid "Running"
|
||||
msgstr "Actifs"
|
||||
|
||||
#: notebook/templates/tree.html:25
|
||||
msgid "Clusters"
|
||||
msgstr "Grappes"
|
||||
|
||||
#: notebook/templates/tree.html:32
|
||||
msgid "Select items to perform actions on them."
|
||||
msgstr "Sélectionner des éléments pour leur appliquer des actions."
|
||||
|
||||
#: notebook/templates/tree.html:35
|
||||
msgid "Duplicate selected"
|
||||
msgstr "Dupliquer la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:35
|
||||
msgid "Duplicate"
|
||||
msgstr "Dupliquer"
|
||||
|
||||
#: notebook/templates/tree.html:36
|
||||
msgid "Rename selected"
|
||||
msgstr "Renommer la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:37
|
||||
msgid "Move selected"
|
||||
msgstr "Déplacer la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:37
|
||||
msgid "Move"
|
||||
msgstr "Déplacer"
|
||||
|
||||
#: notebook/templates/tree.html:38
|
||||
msgid "Download selected"
|
||||
msgstr "Télécharger la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:39
|
||||
msgid "Shutdown selected notebook(s)"
|
||||
msgstr "Arrêter le(s) notebook(s) sélectionné(s)"
|
||||
|
||||
#: notebook/templates/notebook.html:278 notebook/templates/tree.html:39
|
||||
msgid "Shutdown"
|
||||
msgstr "Arrêter"
|
||||
|
||||
#: notebook/templates/tree.html:40
|
||||
msgid "View selected"
|
||||
msgstr "Voir la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:41
|
||||
msgid "Edit selected"
|
||||
msgstr "Éditer la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:42
|
||||
msgid "Delete selected"
|
||||
msgstr "Supprimer la sélection"
|
||||
|
||||
#: notebook/templates/tree.html:50
|
||||
msgid "Click to browse for a file to upload."
|
||||
msgstr "Cliquer pour choisir un fichier à téléverser"
|
||||
|
||||
#: notebook/templates/tree.html:51
|
||||
msgid "Upload"
|
||||
msgstr "Téléverser"
|
||||
|
||||
#: notebook/templates/tree.html:65
|
||||
msgid "Text File"
|
||||
msgstr "Fichier texte"
|
||||
|
||||
#: notebook/templates/tree.html:68
|
||||
msgid "Folder"
|
||||
msgstr "Répertoire"
|
||||
|
||||
#: notebook/templates/tree.html:72
|
||||
msgid "Terminal"
|
||||
msgstr "Terminal"
|
||||
|
||||
#: notebook/templates/tree.html:76
|
||||
msgid "Terminals Unavailable"
|
||||
msgstr "Terminaux non disponibles"
|
||||
|
||||
#: notebook/templates/tree.html:82
|
||||
msgid "Refresh notebook list"
|
||||
msgstr "Rafraîchir la liste des notebooks"
|
||||
|
||||
#: notebook/templates/tree.html:90
|
||||
msgid "Select All / None"
|
||||
msgstr "Sélectionner tout / aucun"
|
||||
|
||||
#: notebook/templates/tree.html:93
|
||||
msgid "Select..."
|
||||
msgstr "Sélectionner..."
|
||||
|
||||
#: notebook/templates/tree.html:98
|
||||
msgid "Select All Folders"
|
||||
msgstr "Sélectionner tous les répertoires"
|
||||
|
||||
#: notebook/templates/tree.html:98
|
||||
msgid "Folders"
|
||||
msgstr "Répertoires"
|
||||
|
||||
#: notebook/templates/tree.html:99
|
||||
msgid "Select All Notebooks"
|
||||
msgstr "Sélectionner tous les notebooks"
|
||||
|
||||
#: notebook/templates/tree.html:99
|
||||
msgid "All Notebooks"
|
||||
msgstr "Tous les notebooks"
|
||||
|
||||
#: notebook/templates/tree.html:100
|
||||
msgid "Select Running Notebooks"
|
||||
msgstr "Sélectionner les notebooks en cours d'exécution"
|
||||
|
||||
#: notebook/templates/tree.html:100
|
||||
msgid "Running"
|
||||
msgstr "Actifs"
|
||||
|
||||
#: notebook/templates/tree.html:101
|
||||
msgid "Select All Files"
|
||||
msgstr "Sélectionner tous les fichiers"
|
||||
|
||||
#: notebook/templates/tree.html:101
|
||||
msgid "Files"
|
||||
msgstr "Fichiers"
|
||||
|
||||
#: notebook/templates/tree.html:114
|
||||
msgid "Last Modified"
|
||||
msgstr "Dernière modification"
|
||||
|
||||
#: notebook/templates/tree.html:120
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: notebook/templates/tree.html:130
|
||||
msgid "Currently running Jupyter processes"
|
||||
msgstr "Processus Jupyter en cours d'exécution"
|
||||
|
||||
#: notebook/templates/tree.html:134
|
||||
msgid "Refresh running list"
|
||||
msgstr "Rafraîchir la liste des actifs"
|
||||
|
||||
#: notebook/templates/tree.html:150
|
||||
msgid "There are no terminals running."
|
||||
msgstr "Il n'y a aucun terminal en cours d'exécution."
|
||||
|
||||
#: notebook/templates/tree.html:152
|
||||
msgid "Terminals are unavailable."
|
||||
msgstr "Les terminaux sont indisponibles."
|
||||
|
||||
#: notebook/templates/tree.html:162
|
||||
msgid "Notebooks"
|
||||
msgstr "Notebooks"
|
||||
|
||||
#: notebook/templates/tree.html:169
|
||||
msgid "There are no notebooks running."
|
||||
msgstr "Il n'y a aucun notebook en cours d'exécution."
|
||||
|
||||
#: notebook/templates/tree.html:178
|
||||
msgid "Clusters tab is now provided by IPython parallel."
|
||||
msgstr "L'onglet des grappes est désormais fourni par IPython parallel."
|
||||
|
||||
#: notebook/templates/tree.html:179
|
||||
msgid ""
|
||||
"See '<a href=\"https://github.com/ipython/ipyparallel\">IPython parallel</"
|
||||
"a>' for installation details."
|
||||
msgstr ""
|
||||
"Voir '<a href=\"https://github.com/ipython/ipyparallel\">IPython parallel</"
|
||||
"a>' pour les détails d'installation."
|
Binary file not shown.
|
@ -0,0 +1,480 @@
|
|||
# Translations template for Jupyter.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the Jupyter project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Jupyter VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-07-08 21:52-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: notebook/notebookapp.py:53
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:57
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:59
|
||||
#, python-format
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:209
|
||||
msgid "The `ignore_minified_js` flag is deprecated and no longer works."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:210
|
||||
#, python-format
|
||||
msgid "Alternatively use `%s` when working on the notebook's Javascript and LESS"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:211
|
||||
msgid "The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:389
|
||||
msgid "List currently running notebook servers."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:393
|
||||
msgid "Produce machine-readable JSON output."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:397
|
||||
msgid "If True, each line of output will be a JSON object with the details from the server info file."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:402
|
||||
msgid "Currently running servers:"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:419
|
||||
msgid "Don't open the notebook in a browser after startup."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:423
|
||||
msgid "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:439
|
||||
msgid "Allow the notebook to be run from root user."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:470
|
||||
msgid ""
|
||||
"The Jupyter HTML Notebook.\n"
|
||||
" \n"
|
||||
" This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:509
|
||||
msgid "Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:540
|
||||
msgid "Set the Access-Control-Allow-Credentials: true header"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:544
|
||||
msgid "Whether to allow the user to run the notebook as root."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:548
|
||||
msgid "The default URL to redirect to from `/`"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:552
|
||||
msgid "The IP address the notebook server will listen on."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:565
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot bind to localhost, using 127.0.0.1 as default ip\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:579
|
||||
msgid "The port the notebook server will listen on."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:583
|
||||
msgid "The number of additional ports to try if the specified port is not available."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:587
|
||||
msgid "The full path to an SSL/TLS certificate file."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:591
|
||||
msgid "The full path to a private key file for usage with SSL/TLS."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:595
|
||||
msgid "The full path to a certificate authority certificate for SSL/TLS client authentication."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:599
|
||||
msgid "The file where the cookie secret is stored."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:628
|
||||
#, python-format
|
||||
msgid "Writing notebook server cookie secret to %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:635
|
||||
#, python-format
|
||||
msgid "Could not set permissions on %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:640
|
||||
msgid ""
|
||||
"Token used for authenticating first-time connections to the server.\n"
|
||||
"\n"
|
||||
" When no password is enabled,\n"
|
||||
" the default is to generate a new, random token.\n"
|
||||
"\n"
|
||||
" Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:650
|
||||
msgid ""
|
||||
"One-time token used for opening a browser.\n"
|
||||
" Once used, this token cannot be used again.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:726
|
||||
msgid ""
|
||||
"Specify Where to open the notebook on startup. This is the\n"
|
||||
" `new` argument passed to the standard library method `webbrowser.open`.\n"
|
||||
" The behaviour is not guaranteed, but depends on browser support. Valid\n"
|
||||
" values are:\n"
|
||||
" 2 opens a new tab,\n"
|
||||
" 1 opens a new window,\n"
|
||||
" 0 opens in an existing window.\n"
|
||||
" See the `webbrowser.open` documentation for details.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:737
|
||||
msgid "DEPRECATED, use tornado_settings"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:742
|
||||
msgid ""
|
||||
"\n"
|
||||
" webapp_settings is deprecated, use tornado_settings.\n"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:746
|
||||
msgid "Supply overrides for the tornado.web.Application that the Jupyter notebook uses."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:750
|
||||
msgid ""
|
||||
"\n"
|
||||
" Set the tornado compression options for websocket connections.\n"
|
||||
"\n"
|
||||
" This value will be returned from :meth:`WebSocketHandler.get_compression_options`.\n"
|
||||
" None (default) will disable compression.\n"
|
||||
" A dict (even an empty one) will enable compression.\n"
|
||||
"\n"
|
||||
" See the tornado docs for WebSocketHandler.get_compression_options for details.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:761
|
||||
msgid "Supply overrides for terminado. Currently only supports \"shell_command\"."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:764
|
||||
msgid "Extra keyword arguments to pass to `set_secure_cookie`. See tornado's set_secure_cookie docs for details."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:768
|
||||
msgid ""
|
||||
"Supply SSL options for the tornado HTTPServer.\n"
|
||||
" See the tornado docs for details."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:772
|
||||
msgid "Supply extra arguments that will be passed to Jinja environment."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:776
|
||||
msgid "Extra variables to supply to jinja templates when rendering."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:812
|
||||
msgid "DEPRECATED use base_url"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:816
|
||||
msgid "base_project_url is deprecated, use base_url"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:832
|
||||
msgid "Path to search for custom.js, css"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:844
|
||||
msgid ""
|
||||
"Extra paths to search for serving jinja templates.\n"
|
||||
"\n"
|
||||
" Can be used to override templates from notebook.templates."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:855
|
||||
msgid "extra paths to look for Javascript notebook extensions"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:900
|
||||
#, python-format
|
||||
msgid "Using MathJax: %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:903
|
||||
msgid "The MathJax.js configuration file that is to be used."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:908
|
||||
#, python-format
|
||||
msgid "Using MathJax configuration file: %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:914
|
||||
msgid "The notebook manager class to use."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:920
|
||||
msgid "The kernel manager class to use."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:926
|
||||
msgid "The session manager class to use."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:932
|
||||
msgid "The config manager class to use"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:953
|
||||
msgid "The login handler class to use."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:960
|
||||
msgid "The logout handler class to use."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:964
|
||||
msgid "Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headerssent by the upstream reverse proxy. Necessary if the proxy handles SSL"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:976
|
||||
msgid ""
|
||||
"\n"
|
||||
" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:988
|
||||
msgid "Support for specifying --pylab on the command line has been removed."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:990
|
||||
msgid "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:995
|
||||
msgid "The directory to use for notebooks and kernels."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1018
|
||||
#, python-format
|
||||
msgid "No such notebook dir: '%r'"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1031
|
||||
msgid "DEPRECATED use the nbserver_extensions dict instead"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1036
|
||||
msgid "server_extensions is deprecated, use nbserver_extensions"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1040
|
||||
msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1049
|
||||
msgid "Reraise exceptions encountered loading server extensions?"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1052
|
||||
msgid ""
|
||||
"(msgs/sec)\n"
|
||||
" Maximum rate at which messages can be sent on iopub before they are\n"
|
||||
" limited."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1056
|
||||
msgid ""
|
||||
"(bytes/sec)\n"
|
||||
" Maximum rate at which stream output can be sent on iopub before they are\n"
|
||||
" limited."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1060
|
||||
msgid ""
|
||||
"(sec) Time window used to \n"
|
||||
" check the message and data rate limits."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1071
|
||||
#, python-format
|
||||
msgid "No such file or directory: %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1141
|
||||
msgid "Notebook servers are configured to only be run with a password."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1142
|
||||
msgid "Hint: run the following command to set a password"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1143
|
||||
msgid "\t$ python -m notebook.auth password"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1181
|
||||
#, python-format
|
||||
msgid "The port %i is already in use, trying another port."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1184
|
||||
#, python-format
|
||||
msgid "Permission to listen on port %i denied"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1193
|
||||
msgid "ERROR: the notebook server could not be started because no available port could be found."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1199
|
||||
msgid "[all ip addresses on your system]"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1223
|
||||
#, python-format
|
||||
msgid "Terminals not available (error was %s)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1259
|
||||
msgid "interrupted"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1261
|
||||
msgid "y"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1262
|
||||
msgid "n"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1263
|
||||
#, python-format
|
||||
msgid "Shutdown this notebook server (%s/[%s])? "
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1269
|
||||
msgid "Shutdown confirmed"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1273
|
||||
msgid "No answer for 5s:"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1274
|
||||
msgid "resuming operation..."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1282
|
||||
#, python-format
|
||||
msgid "received signal %s, stopping"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1338
|
||||
#, python-format
|
||||
msgid "Error loading server extension %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1369
|
||||
#, python-format
|
||||
msgid "Shutting down %d kernels"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1375
|
||||
#, python-format
|
||||
msgid "%d active kernel"
|
||||
msgid_plural "%d active kernels"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: notebook/notebookapp.py:1379
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The Jupyter Notebook is running at:\n"
|
||||
"\r"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1426
|
||||
msgid "Running as root is not recommended. Use --allow-root to bypass."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1432
|
||||
msgid "Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1434
|
||||
msgid "Welcome to Project Jupyter! Explore the various tools available and their corresponding documentation. If you are interested in contributing to the platform, please visit the communityresources section at http://jupyter.org/community.html."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1445
|
||||
#, python-format
|
||||
msgid "No web browser found: %s."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1450
|
||||
#, python-format
|
||||
msgid "%s does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1484
|
||||
msgid "Interrupted..."
|
||||
msgstr ""
|
||||
|
||||
#: notebook/services/contents/filemanager.py:506
|
||||
#, python-format
|
||||
msgid "Serving notebooks from local directory: %s"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/services/contents/manager.py:68
|
||||
msgid "Untitled"
|
||||
msgstr ""
|
||||
|
1353
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.json
Normal file
1353
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.json
Normal file
File diff suppressed because it is too large
Load diff
1934
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po
Normal file
1934
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbjs.po
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbui.mo
Normal file
BIN
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbui.mo
Normal file
Binary file not shown.
740
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbui.po
Normal file
740
venv/Lib/site-packages/notebook/i18n/ja_JP/LC_MESSAGES/nbui.po
Normal file
|
@ -0,0 +1,740 @@
|
|||
# Translations template for Jupyter.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the Jupyter project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Jupyter VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-07-07 12:48-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: notebook/templates/404.html:3
|
||||
msgid "You are requesting a page that does not exist!"
|
||||
msgstr "要求したページは存在しません!"
|
||||
|
||||
#: notebook/templates/edit.html:37
|
||||
msgid "current mode"
|
||||
msgstr "現在のモード"
|
||||
|
||||
#: notebook/templates/edit.html:48 notebook/templates/notebook.html:78
|
||||
msgid "File"
|
||||
msgstr "ファイル"
|
||||
|
||||
#: notebook/templates/edit.html:50 notebook/templates/tree.html:57
|
||||
msgid "New"
|
||||
msgstr "新規"
|
||||
|
||||
#: notebook/templates/edit.html:51
|
||||
msgid "Save"
|
||||
msgstr "保存"
|
||||
|
||||
#: notebook/templates/edit.html:52 notebook/templates/tree.html:36
|
||||
msgid "Rename"
|
||||
msgstr "リネーム"
|
||||
|
||||
#: notebook/templates/edit.html:53 notebook/templates/tree.html:38
|
||||
msgid "Download"
|
||||
msgstr "ダウンロード"
|
||||
|
||||
#: notebook/templates/edit.html:56 notebook/templates/notebook.html:131
|
||||
#: notebook/templates/tree.html:41
|
||||
msgid "Edit"
|
||||
msgstr "編集"
|
||||
|
||||
#: notebook/templates/edit.html:58
|
||||
msgid "Find"
|
||||
msgstr "検索"
|
||||
|
||||
#: notebook/templates/edit.html:59
|
||||
msgid "Find & Replace"
|
||||
msgstr "検索と置換"
|
||||
|
||||
#: notebook/templates/edit.html:61
|
||||
msgid "Key Map"
|
||||
msgstr "キーマッピング"
|
||||
|
||||
#: notebook/templates/edit.html:62
|
||||
msgid "Default"
|
||||
msgstr "デフォルト"
|
||||
|
||||
#: notebook/templates/edit.html:63
|
||||
msgid "Sublime Text"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/edit.html:68 notebook/templates/notebook.html:159
|
||||
#: notebook/templates/tree.html:40
|
||||
msgid "View"
|
||||
msgstr "表示"
|
||||
|
||||
#: notebook/templates/edit.html:70 notebook/templates/notebook.html:162
|
||||
msgid "Show/Hide the logo and notebook title (above menu bar)"
|
||||
msgstr "ロゴとノートブックのタイトルを表示/非表示 (メニューバーの上)"
|
||||
|
||||
#: notebook/templates/edit.html:71 notebook/templates/notebook.html:163
|
||||
msgid "Toggle Header"
|
||||
msgstr "ヘッダをトグル"
|
||||
|
||||
#: notebook/templates/edit.html:72 notebook/templates/notebook.html:171
|
||||
msgid "Toggle Line Numbers"
|
||||
msgstr "行番号をトグル"
|
||||
|
||||
#: notebook/templates/edit.html:75
|
||||
msgid "Language"
|
||||
msgstr "言語"
|
||||
|
||||
#: notebook/templates/error.html:23
|
||||
msgid "The error was:"
|
||||
msgstr "エラー内容:"
|
||||
|
||||
#: notebook/templates/login.html:24
|
||||
msgid "Password or token:"
|
||||
msgstr "パスワードまたはトークン:"
|
||||
|
||||
#: notebook/templates/login.html:26
|
||||
msgid "Password:"
|
||||
msgstr "パスワード:"
|
||||
|
||||
#: notebook/templates/login.html:31
|
||||
msgid "Log in"
|
||||
msgstr "ログイン"
|
||||
|
||||
#: notebook/templates/login.html:39
|
||||
msgid "No login available, you shouldn't be seeing this page."
|
||||
msgstr "ログインしていないのでこのページを見る事はできません。"
|
||||
|
||||
#: notebook/templates/logout.html:24
|
||||
#, python-format
|
||||
msgid "Proceed to the <a href=\"%(base_url)s\">dashboard"
|
||||
msgstr "<a href=\"%(base_url)s\">ダッシュボード"
|
||||
|
||||
#: notebook/templates/logout.html:26
|
||||
#, python-format
|
||||
msgid "Proceed to the <a href=\"%(base_url)slogin\">login page"
|
||||
msgstr "<a href=\"%(base_url)slogin\">ログインページ"
|
||||
|
||||
#: notebook/templates/notebook.html:62
|
||||
msgid "Menu"
|
||||
msgstr "メニュー"
|
||||
|
||||
#: notebook/templates/notebook.html:65 notebook/templates/notebook.html:254
|
||||
msgid "Kernel"
|
||||
msgstr "カーネル"
|
||||
|
||||
#: notebook/templates/notebook.html:68
|
||||
msgid "This notebook is read-only"
|
||||
msgstr "このノートブックは読み取り専用です"
|
||||
|
||||
#: notebook/templates/notebook.html:81
|
||||
msgid "New Notebook"
|
||||
msgstr "新しいノートブック"
|
||||
|
||||
#: notebook/templates/notebook.html:85
|
||||
msgid "Opens a new window with the Dashboard view"
|
||||
msgstr "ダッシュボードで新しいウィンドウを開く"
|
||||
|
||||
#: notebook/templates/notebook.html:86
|
||||
msgid "Open..."
|
||||
msgstr "開く..."
|
||||
|
||||
#: notebook/templates/notebook.html:90
|
||||
msgid "Open a copy of this notebook's contents and start a new kernel"
|
||||
msgstr "このノートブックの内容の複製を開き新しいカーネルを起動する"
|
||||
|
||||
#: notebook/templates/notebook.html:91
|
||||
msgid "Make a Copy..."
|
||||
msgstr "コピーを作る..."
|
||||
|
||||
#: notebook/templates/notebook.html:92
|
||||
msgid "Rename..."
|
||||
msgstr "リネーム..."
|
||||
|
||||
msgid "Save as..."
|
||||
msgstr "名前を付けて保存..."
|
||||
|
||||
msgid "Quit"
|
||||
msgstr "終了"
|
||||
|
||||
#: notebook/templates/notebook.html:93
|
||||
msgid "Save and Checkpoint"
|
||||
msgstr "保存とチェックポイント"
|
||||
|
||||
#: notebook/templates/notebook.html:96
|
||||
msgid "Revert to Checkpoint"
|
||||
msgstr "チェックポイントを元に戻す"
|
||||
|
||||
#: notebook/templates/notebook.html:106
|
||||
msgid "Print Preview"
|
||||
msgstr "印刷プレビュー"
|
||||
|
||||
#: notebook/templates/notebook.html:107
|
||||
msgid "Download as"
|
||||
msgstr "名前を付けてダウンロード"
|
||||
|
||||
#: notebook/templates/notebook.html:109
|
||||
msgid "Notebook (.ipynb)"
|
||||
msgstr "ノートブック (.ipynb)"
|
||||
|
||||
#: notebook/templates/notebook.html:110
|
||||
msgid "Script"
|
||||
msgstr "スクリプト"
|
||||
|
||||
#: notebook/templates/notebook.html:111
|
||||
msgid "HTML (.html)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:112
|
||||
msgid "Markdown (.md)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:113
|
||||
msgid "reST (.rst)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:114
|
||||
msgid "LaTeX (.tex)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:115
|
||||
msgid "PDF via LaTeX (.pdf)"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:118
|
||||
msgid "Deploy as"
|
||||
msgstr "名前を付けてデプロイ"
|
||||
|
||||
#: notebook/templates/notebook.html:123
|
||||
msgid "Trust the output of this notebook"
|
||||
msgstr "このノートブックの出力を信頼する"
|
||||
|
||||
#: notebook/templates/notebook.html:124
|
||||
msgid "Trust Notebook"
|
||||
msgstr "ノートブックを信頼する"
|
||||
|
||||
#: notebook/templates/notebook.html:127
|
||||
msgid "Shutdown this notebook's kernel, and close this window"
|
||||
msgstr "このノートブックのカーネルをシャットダウンし、このウィンドウを閉じる"
|
||||
|
||||
#: notebook/templates/notebook.html:128
|
||||
msgid "Close and Halt"
|
||||
msgstr "閉じて終了"
|
||||
|
||||
#: notebook/templates/notebook.html:133
|
||||
msgid "Cut Cells"
|
||||
msgstr "セルを切り取り"
|
||||
|
||||
#: notebook/templates/notebook.html:134
|
||||
msgid "Copy Cells"
|
||||
msgstr "セルをコピー"
|
||||
|
||||
#: notebook/templates/notebook.html:135
|
||||
msgid "Paste Cells Above"
|
||||
msgstr "上にセルをペースト"
|
||||
|
||||
#: notebook/templates/notebook.html:136
|
||||
msgid "Paste Cells Below"
|
||||
msgstr "下にセルをペースト"
|
||||
|
||||
#: notebook/templates/notebook.html:137
|
||||
msgid "Paste Cells & Replace"
|
||||
msgstr "セルをペーストして入れ替え(&A)"
|
||||
|
||||
#: notebook/templates/notebook.html:138
|
||||
msgid "Delete Cells"
|
||||
msgstr "セルを削除"
|
||||
|
||||
#: notebook/templates/notebook.html:139
|
||||
msgid "Undo Delete Cells"
|
||||
msgstr "セルの削除を取り消し"
|
||||
|
||||
#: notebook/templates/notebook.html:141
|
||||
msgid "Split Cell"
|
||||
msgstr "セルを分割"
|
||||
|
||||
#: notebook/templates/notebook.html:142
|
||||
msgid "Merge Cell Above"
|
||||
msgstr "上のセルをマージ"
|
||||
|
||||
#: notebook/templates/notebook.html:143
|
||||
msgid "Merge Cell Below"
|
||||
msgstr "下のセルをマージ"
|
||||
|
||||
#: notebook/templates/notebook.html:145
|
||||
msgid "Move Cell Up"
|
||||
msgstr "セルを上に移動"
|
||||
|
||||
#: notebook/templates/notebook.html:146
|
||||
msgid "Move Cell Down"
|
||||
msgstr "セルを下に移動"
|
||||
|
||||
#: notebook/templates/notebook.html:148
|
||||
msgid "Edit Notebook Metadata"
|
||||
msgstr "ノートブックのメタデータを編集"
|
||||
|
||||
#: notebook/templates/notebook.html:150
|
||||
msgid "Find and Replace"
|
||||
msgstr "検索と置換"
|
||||
|
||||
#: notebook/templates/notebook.html:152
|
||||
msgid "Cut Cell Attachments"
|
||||
msgstr "セルのアタッチメントを切り取り"
|
||||
|
||||
#: notebook/templates/notebook.html:153
|
||||
msgid "Copy Cell Attachments"
|
||||
msgstr "セルのアタッチメントをコピー"
|
||||
|
||||
#: notebook/templates/notebook.html:154
|
||||
msgid "Paste Cell Attachments"
|
||||
msgstr "セルのアタッチメントをペースト"
|
||||
|
||||
#: notebook/templates/notebook.html:156
|
||||
msgid "Insert Image"
|
||||
msgstr "画像を挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:166
|
||||
msgid "Show/Hide the action icons (below menu bar)"
|
||||
msgstr "アクションアイコンを表示/非表示 (メニューバーの下)"
|
||||
|
||||
#: notebook/templates/notebook.html:167
|
||||
msgid "Toggle Toolbar"
|
||||
msgstr "ツールバーをトグル"
|
||||
|
||||
#: notebook/templates/notebook.html:170
|
||||
msgid "Show/Hide line numbers in cells"
|
||||
msgstr "セル内の行番号を表示/非表示"
|
||||
|
||||
#: notebook/templates/notebook.html:174
|
||||
msgid "Cell Toolbar"
|
||||
msgstr "セルツールバー"
|
||||
|
||||
#: notebook/templates/notebook.html:179
|
||||
msgid "Insert"
|
||||
msgstr "挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:182
|
||||
msgid "Insert an empty Code cell above the currently active cell"
|
||||
msgstr "現在アクティブなセルの上に空のコードセルを挿入する"
|
||||
|
||||
#: notebook/templates/notebook.html:183
|
||||
msgid "Insert Cell Above"
|
||||
msgstr "上にセルを挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:185
|
||||
msgid "Insert an empty Code cell below the currently active cell"
|
||||
msgstr "現在アクティブなセルの下に空のコードセルを挿入する"
|
||||
|
||||
#: notebook/templates/notebook.html:186
|
||||
msgid "Insert Cell Below"
|
||||
msgstr "下にセルを挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:189
|
||||
msgid "Cell"
|
||||
msgstr "セル"
|
||||
|
||||
#: notebook/templates/notebook.html:191
|
||||
msgid "Run this cell, and move cursor to the next one"
|
||||
msgstr "このセルを実行しカーソルを一つ次に移動する"
|
||||
|
||||
#: notebook/templates/notebook.html:192
|
||||
msgid "Run Cells"
|
||||
msgstr "セルを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:193
|
||||
msgid "Run this cell, select below"
|
||||
msgstr "このセルを実行し下を選択する"
|
||||
|
||||
#: notebook/templates/notebook.html:194
|
||||
msgid "Run Cells and Select Below"
|
||||
msgstr "ここまでのセルを実行し下を選択する"
|
||||
|
||||
#: notebook/templates/notebook.html:195
|
||||
msgid "Run this cell, insert below"
|
||||
msgstr "このセルを実行し下に挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:196
|
||||
msgid "Run Cells and Insert Below"
|
||||
msgstr "ここまでのセルを実行し下に挿入"
|
||||
|
||||
#: notebook/templates/notebook.html:197
|
||||
msgid "Run all cells in the notebook"
|
||||
msgstr "ノートブックの全てのセルを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:198
|
||||
msgid "Run All"
|
||||
msgstr "全てを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:199
|
||||
msgid "Run all cells above (but not including) this cell"
|
||||
msgstr "このセルの上にある (このセルは含まない) すべてのセルを実行する"
|
||||
|
||||
#: notebook/templates/notebook.html:200
|
||||
msgid "Run All Above"
|
||||
msgstr "ここまでのセルの全てを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:201
|
||||
msgid "Run this cell and all cells below it"
|
||||
msgstr "このセルと以下のすべてのセルを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:202
|
||||
msgid "Run All Below"
|
||||
msgstr "以下を全て実行"
|
||||
|
||||
#: notebook/templates/notebook.html:205
|
||||
msgid "All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells"
|
||||
msgstr "ノートブック上のすべてのセルには種別があります。デフォルトでは新しいセルは 'コード' セルとして作成されます"
|
||||
|
||||
#: notebook/templates/notebook.html:206
|
||||
msgid "Cell Type"
|
||||
msgstr "セルの種別"
|
||||
|
||||
#: notebook/templates/notebook.html:209
|
||||
msgid "Contents will be sent to the kernel for execution, and output will display in the footer of cell"
|
||||
msgstr "実行のために内容がカーネルに送られ、セルのフッターに出力が表示されます"
|
||||
|
||||
#: notebook/templates/notebook.html:212
|
||||
msgid "Contents will be rendered as HTML and serve as explanatory text"
|
||||
msgstr "内容は HTML としてレンダリングされ説明のテキストとしてサーブされます"
|
||||
|
||||
#: notebook/templates/notebook.html:213 notebook/templates/notebook.html:298
|
||||
msgid "Markdown"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:215
|
||||
msgid "Contents will pass through nbconvert unmodified"
|
||||
msgstr "内容は変更されずに nbconvert に渡されます"
|
||||
|
||||
#: notebook/templates/notebook.html:216
|
||||
msgid "Raw NBConvert"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/templates/notebook.html:220
|
||||
msgid "Current Outputs"
|
||||
msgstr "現在の出力"
|
||||
|
||||
#: notebook/templates/notebook.html:223
|
||||
msgid "Hide/Show the output of the current cell"
|
||||
msgstr "現在のセルの出力を表示/非表示"
|
||||
|
||||
#: notebook/templates/notebook.html:224 notebook/templates/notebook.html:240
|
||||
msgid "Toggle"
|
||||
msgstr "トグル"
|
||||
|
||||
#: notebook/templates/notebook.html:227
|
||||
msgid "Scroll the output of the current cell"
|
||||
msgstr "現在のセルの出力をスクロール"
|
||||
|
||||
#: notebook/templates/notebook.html:228 notebook/templates/notebook.html:244
|
||||
msgid "Toggle Scrolling"
|
||||
msgstr "スクロールをトグル"
|
||||
|
||||
#: notebook/templates/notebook.html:231
|
||||
msgid "Clear the output of the current cell"
|
||||
msgstr "現在のセルの出力をクリア"
|
||||
|
||||
#: notebook/templates/notebook.html:232 notebook/templates/notebook.html:248
|
||||
msgid "Clear"
|
||||
msgstr "クリア"
|
||||
|
||||
#: notebook/templates/notebook.html:236
|
||||
msgid "All Output"
|
||||
msgstr "全ての出力"
|
||||
|
||||
#: notebook/templates/notebook.html:239
|
||||
msgid "Hide/Show the output of all cells"
|
||||
msgstr "全てのセルの出力を表示/非表示"
|
||||
|
||||
#: notebook/templates/notebook.html:243
|
||||
msgid "Scroll the output of all cells"
|
||||
msgstr "全てのセルの出力をスクロール"
|
||||
|
||||
#: notebook/templates/notebook.html:247
|
||||
msgid "Clear the output of all cells"
|
||||
msgstr "全てのセルの出力をクリア"
|
||||
|
||||
#: notebook/templates/notebook.html:257
|
||||
msgid "Send Keyboard Interrupt (CTRL-C) to the Kernel"
|
||||
msgstr "キーボードの中断(CTRL-C)をカーネルに送る"
|
||||
|
||||
#: notebook/templates/notebook.html:258
|
||||
msgid "Interrupt"
|
||||
msgstr "中断"
|
||||
|
||||
#: notebook/templates/notebook.html:261
|
||||
msgid "Restart the Kernel"
|
||||
msgstr "カーネルを再起動"
|
||||
|
||||
#: notebook/templates/notebook.html:262
|
||||
msgid "Restart"
|
||||
msgstr "再起動"
|
||||
|
||||
#: notebook/templates/notebook.html:265
|
||||
msgid "Restart the Kernel and clear all output"
|
||||
msgstr "カーネルを再起動し全ての出力をクリアする"
|
||||
|
||||
#: notebook/templates/notebook.html:266
|
||||
msgid "Restart & Clear Output"
|
||||
msgstr "再起動し出力をクリアする"
|
||||
|
||||
#: notebook/templates/notebook.html:269
|
||||
msgid "Restart the Kernel and re-run the notebook"
|
||||
msgstr "カーネルを再起動しノートブックを再実行する"
|
||||
|
||||
#: notebook/templates/notebook.html:270
|
||||
msgid "Restart & Run All"
|
||||
msgstr "再起動し全てを実行"
|
||||
|
||||
#: notebook/templates/notebook.html:273
|
||||
msgid "Reconnect to the Kernel"
|
||||
msgstr "カーネルに再接続する"
|
||||
|
||||
#: notebook/templates/notebook.html:274
|
||||
msgid "Reconnect"
|
||||
msgstr "再接続"
|
||||
|
||||
#: notebook/templates/notebook.html:282
|
||||
msgid "Change kernel"
|
||||
msgstr "カーネルの変更"
|
||||
|
||||
#: notebook/templates/notebook.html:287
|
||||
msgid "Help"
|
||||
msgstr "ヘルプ"
|
||||
|
||||
#: notebook/templates/notebook.html:290
|
||||
msgid "A quick tour of the notebook user interface"
|
||||
msgstr "ノートブックユーザーインターフェースのクイックツアー"
|
||||
|
||||
#: notebook/templates/notebook.html:290
|
||||
msgid "User Interface Tour"
|
||||
msgstr "ユーザーインタフェースツアー"
|
||||
|
||||
#: notebook/templates/notebook.html:291
|
||||
msgid "Opens a tooltip with all keyboard shortcuts"
|
||||
msgstr "全てのキーボードショートカットのツールチップを表示する"
|
||||
|
||||
#: notebook/templates/notebook.html:291
|
||||
msgid "Keyboard Shortcuts"
|
||||
msgstr "キーボードショートカット"
|
||||
|
||||
#: notebook/templates/notebook.html:292
|
||||
msgid "Opens a dialog allowing you to edit Keyboard shortcuts"
|
||||
msgstr "キーボードショートカットの編集ダイアログを開く"
|
||||
|
||||
#: notebook/templates/notebook.html:292
|
||||
msgid "Edit Keyboard Shortcuts"
|
||||
msgstr "キーボードショートカットの編集"
|
||||
|
||||
#: notebook/templates/notebook.html:297
|
||||
msgid "Notebook Help"
|
||||
msgstr "ノートブックのヘルプ"
|
||||
|
||||
#: notebook/templates/notebook.html:303
|
||||
msgid "Opens in a new window"
|
||||
msgstr "新しいウィンドウで開く"
|
||||
|
||||
#: notebook/templates/notebook.html:319
|
||||
msgid "About Jupyter Notebook"
|
||||
msgstr "Jupyter Notebook について"
|
||||
|
||||
#: notebook/templates/notebook.html:319
|
||||
msgid "About"
|
||||
msgstr "詳細"
|
||||
|
||||
#: notebook/templates/page.html:114
|
||||
msgid "Jupyter Notebook requires JavaScript."
|
||||
msgstr "Jupyter Notebook には JavaScript が必要です。"
|
||||
|
||||
#: notebook/templates/page.html:115
|
||||
msgid "Please enable it to proceed. "
|
||||
msgstr "続行するには有効にして下さい。 "
|
||||
|
||||
#: notebook/templates/page.html:121
|
||||
msgid "dashboard"
|
||||
msgstr "ダッシュボード"
|
||||
|
||||
#: notebook/templates/page.html:132
|
||||
msgid "Logout"
|
||||
msgstr "ログアウト"
|
||||
|
||||
#: notebook/templates/page.html:134
|
||||
msgid "Login"
|
||||
msgstr "ログイン"
|
||||
|
||||
#: notebook/templates/tree.html:23
|
||||
msgid "Files"
|
||||
msgstr "ファイル"
|
||||
|
||||
#: notebook/templates/tree.html:24
|
||||
msgid "Running"
|
||||
msgstr "実行中"
|
||||
|
||||
#: notebook/templates/tree.html:25
|
||||
msgid "Clusters"
|
||||
msgstr "クラスタ"
|
||||
|
||||
#: notebook/templates/tree.html:32
|
||||
msgid "Select items to perform actions on them."
|
||||
msgstr "アクションを実行する為のアイテムを選択して下さい。"
|
||||
|
||||
#: notebook/templates/tree.html:35
|
||||
msgid "Duplicate selected"
|
||||
msgstr "選択アイテムを複製する"
|
||||
|
||||
#: notebook/templates/tree.html:35
|
||||
msgid "Duplicate"
|
||||
msgstr "複製"
|
||||
|
||||
#: notebook/templates/tree.html:36
|
||||
msgid "Rename selected"
|
||||
msgstr "選択アイテムをリネームする"
|
||||
|
||||
#: notebook/templates/tree.html:37
|
||||
msgid "Move selected"
|
||||
msgstr "選択アイテムを移動する"
|
||||
|
||||
#: notebook/templates/tree.html:37
|
||||
msgid "Move"
|
||||
msgstr "移動"
|
||||
|
||||
#: notebook/templates/tree.html:38
|
||||
msgid "Download selected"
|
||||
msgstr "選択アイテムをダウンロードする"
|
||||
|
||||
#: notebook/templates/tree.html:39
|
||||
msgid "Shutdown selected notebook(s)"
|
||||
msgstr "選択されているノートブックをシャットダウンする"
|
||||
|
||||
#: notebook/templates/notebook.html:278
|
||||
#: notebook/templates/tree.html:39
|
||||
msgid "Shutdown"
|
||||
msgstr "シャットダウン"
|
||||
|
||||
#: notebook/templates/tree.html:40
|
||||
msgid "View selected"
|
||||
msgstr "選択されているアイテムを表示する"
|
||||
|
||||
#: notebook/templates/tree.html:41
|
||||
msgid "Edit selected"
|
||||
msgstr "選択されているアイテムを編集する"
|
||||
|
||||
#: notebook/templates/tree.html:42
|
||||
msgid "Delete selected"
|
||||
msgstr "選択されているアイテムを削除する"
|
||||
|
||||
#: notebook/templates/tree.html:50
|
||||
msgid "Click to browse for a file to upload."
|
||||
msgstr "クリックしてアップロードするファイルを選択して下さい。"
|
||||
|
||||
#: notebook/templates/tree.html:51
|
||||
msgid "Upload"
|
||||
msgstr "アップロード"
|
||||
|
||||
#: notebook/templates/tree.html:65
|
||||
msgid "Text File"
|
||||
msgstr "テキストファイル"
|
||||
|
||||
#: notebook/templates/tree.html:68
|
||||
msgid "Folder"
|
||||
msgstr "フォルダ"
|
||||
|
||||
#: notebook/templates/tree.html:72
|
||||
msgid "Terminal"
|
||||
msgstr "端末"
|
||||
|
||||
#: notebook/templates/tree.html:76
|
||||
msgid "Terminals Unavailable"
|
||||
msgstr "端末が存在しません"
|
||||
|
||||
#: notebook/templates/tree.html:82
|
||||
msgid "Refresh notebook list"
|
||||
msgstr "ノートブックの一覧を再読み込み"
|
||||
|
||||
#: notebook/templates/tree.html:90
|
||||
msgid "Select All / None"
|
||||
msgstr "全てを選択 / 解除"
|
||||
|
||||
#: notebook/templates/tree.html:93
|
||||
msgid "Select..."
|
||||
msgstr "選択..."
|
||||
|
||||
#: notebook/templates/tree.html:98
|
||||
msgid "Select All Folders"
|
||||
msgstr "全てのフォルダを選択..."
|
||||
|
||||
#: notebook/templates/tree.html:98
|
||||
msgid "Folders"
|
||||
msgstr "フォルダ"
|
||||
|
||||
#: notebook/templates/tree.html:99
|
||||
msgid "Select All Notebooks"
|
||||
msgstr "全てのノートブックを選択"
|
||||
|
||||
#: notebook/templates/tree.html:99
|
||||
msgid "All Notebooks"
|
||||
msgstr "全てのノートブック"
|
||||
|
||||
#: notebook/templates/tree.html:100
|
||||
msgid "Select Running Notebooks"
|
||||
msgstr "実行中のノートブックを選択"
|
||||
|
||||
#: notebook/templates/tree.html:100
|
||||
msgid "Running"
|
||||
msgstr "実行中"
|
||||
|
||||
#: notebook/templates/tree.html:101
|
||||
msgid "Select All Files"
|
||||
msgstr "全てのファイルを選択"
|
||||
|
||||
#: notebook/templates/tree.html:101
|
||||
msgid "Files"
|
||||
msgstr "ファイル"
|
||||
|
||||
#: notebook/templates/tree.html:114
|
||||
msgid "Last Modified"
|
||||
msgstr "最終変更時刻"
|
||||
|
||||
#: notebook/templates/tree.html:120
|
||||
msgid "Name"
|
||||
msgstr "名前"
|
||||
|
||||
msgid "File size"
|
||||
msgstr "ファイルサイズ"
|
||||
|
||||
#: notebook/templates/tree.html:130
|
||||
msgid "Currently running Jupyter processes"
|
||||
msgstr "現在実行中の Jupyter プロセス一覧"
|
||||
|
||||
#: notebook/templates/tree.html:134
|
||||
msgid "Refresh running list"
|
||||
msgstr "実行中の一覧を再読み込み"
|
||||
|
||||
#: notebook/templates/tree.html:150
|
||||
msgid "There are no terminals running."
|
||||
msgstr "実行中の端末はありません。"
|
||||
|
||||
#: notebook/templates/tree.html:152
|
||||
msgid "Terminals are unavailable."
|
||||
msgstr "端末はありません。"
|
||||
|
||||
#: notebook/templates/tree.html:162
|
||||
msgid "Notebooks"
|
||||
msgstr "ノートブック"
|
||||
|
||||
#: notebook/templates/tree.html:169
|
||||
msgid "There are no notebooks running."
|
||||
msgstr "実行中のノートブックはありません。"
|
||||
|
||||
#: notebook/templates/tree.html:178
|
||||
msgid "Clusters tab is now provided by IPython parallel."
|
||||
msgstr "Clousters タブが IPython parallel によって提供される様になりました。"
|
||||
|
||||
#: notebook/templates/tree.html:179
|
||||
msgid "See '<a href=\"https://github.com/ipython/ipyparallel\">IPython parallel</a>' for installation details."
|
||||
msgstr "詳しいインストール方法は '<a href=\"https://github.com/ipython/ipyparallel\">IPython parallel</a>' を参照"
|
Binary file not shown.
|
@ -0,0 +1,527 @@
|
|||
# Translations template for Jupyter.
|
||||
# Copyright (C) 2017 ORGANIZATION
|
||||
# This file is distributed under the same license as the Jupyter project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Jupyter VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-07-08 21:52-0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: notebook/notebookapp.py:53
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0"
|
||||
msgstr "Jupyter Notebook は tornade 4.0 以上が必要です"
|
||||
|
||||
#: notebook/notebookapp.py:57
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have < 1.1.0"
|
||||
msgstr "Jupyter Notebook は tornade 4.0 以上が必要ですが 1.1.0 以下です"
|
||||
|
||||
#: notebook/notebookapp.py:59
|
||||
#, python-format
|
||||
msgid "The Jupyter Notebook requires tornado >= 4.0, but you have %s"
|
||||
msgstr "Jupyter Notebook は tornade 4.0 以上が必要ですが %s です"
|
||||
|
||||
#: notebook/notebookapp.py:209
|
||||
msgid "The `ignore_minified_js` flag is deprecated and no longer works."
|
||||
msgstr "`ignore_minified_js` フラグは非推奨であり既に動作していません。"
|
||||
|
||||
#: notebook/notebookapp.py:210
|
||||
#, python-format
|
||||
msgid "Alternatively use `%s` when working on the notebook's Javascript and LESS"
|
||||
msgstr "ノートブックの Javascript と LESS で動作する場合には代わりに `%s` を使用してください。"
|
||||
|
||||
#: notebook/notebookapp.py:211
|
||||
msgid "The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
|
||||
msgstr "`ignore_minified_js` フラグは非推奨でありノートブック 6.0 では削除されます"
|
||||
|
||||
#: notebook/notebookapp.py:389
|
||||
msgid "List currently running notebook servers."
|
||||
msgstr "現在起動中のノートブックサーバの一覧"
|
||||
|
||||
#: notebook/notebookapp.py:393
|
||||
msgid "Produce machine-readable JSON output."
|
||||
msgstr "機械で読み込み可能な JSON 出力。"
|
||||
|
||||
#: notebook/notebookapp.py:397
|
||||
msgid "If True, each line of output will be a JSON object with the details from the server info file."
|
||||
msgstr "True の場合、出力の各行はサーバ情報ファイルからの詳細情報を含む JSON オブジェクトになります。"
|
||||
|
||||
#: notebook/notebookapp.py:402
|
||||
msgid "Currently running servers:"
|
||||
msgstr "現在実行中のサーバ:"
|
||||
|
||||
#: notebook/notebookapp.py:419
|
||||
msgid "Don't open the notebook in a browser after startup."
|
||||
msgstr "起動後にブラウザでノートブックを開かない。"
|
||||
|
||||
#: notebook/notebookapp.py:423
|
||||
msgid "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
|
||||
msgstr "無効: matplotlib を有効にするにはノートブックで %pylab または %matplotlib を使用して下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:439
|
||||
msgid "Allow the notebook to be run from root user."
|
||||
msgstr "ノートブックをrootユーザーから実行できるようにする。"
|
||||
|
||||
#: notebook/notebookapp.py:470
|
||||
msgid ""
|
||||
"The Jupyter HTML Notebook.\n"
|
||||
" \n"
|
||||
" This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client."
|
||||
msgstr ""
|
||||
"The Jupyter HTML Notebook.\n"
|
||||
" \n"
|
||||
" HTML5/Javascript Notebook クライアントを提供する Tornado ベースの HTML Notebook サーバを起動します。"
|
||||
|
||||
#: notebook/notebookapp.py:509
|
||||
msgid "Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation"
|
||||
msgstr "非推奨: 圧縮された JS ファイルを使用するかどうか。主に開発中に JS が再コンパイルされるのを回避するために使用します。"
|
||||
|
||||
#: notebook/notebookapp.py:540
|
||||
msgid "Set the Access-Control-Allow-Credentials: true header"
|
||||
msgstr "Access-Control-Allow-Credentials: true ヘッダーを設定します。"
|
||||
|
||||
#: notebook/notebookapp.py:544
|
||||
msgid "Whether to allow the user to run the notebook as root."
|
||||
msgstr "ユーザーがノートブックを root として実行できるようにするかどうか。"
|
||||
|
||||
#: notebook/notebookapp.py:548
|
||||
msgid "The default URL to redirect to from `/`"
|
||||
msgstr "`/` からリダイレクトされるデフォルトの URL"
|
||||
|
||||
#: notebook/notebookapp.py:552
|
||||
msgid "The IP address the notebook server will listen on."
|
||||
msgstr "ノートブックサーバが待ち受ける IP アドレス。"
|
||||
|
||||
#: notebook/notebookapp.py:565
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot bind to localhost, using 127.0.0.1 as default ip\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"localhost でバインドできません。デフォルト IP アドレスとして 127.0.0.1 を使用します\n"
|
||||
"%s"
|
||||
|
||||
#: notebook/notebookapp.py:579
|
||||
msgid "The port the notebook server will listen on."
|
||||
msgstr "ノートブックサーバが待ち受けするポート番号。"
|
||||
|
||||
#: notebook/notebookapp.py:583
|
||||
msgid "The number of additional ports to try if the specified port is not available."
|
||||
msgstr "指定されたポートが利用できない場合に試す追加のポートの数。"
|
||||
|
||||
#: notebook/notebookapp.py:587
|
||||
msgid "The full path to an SSL/TLS certificate file."
|
||||
msgstr "SSL/TLS 証明書ファイルへの完全なパス。"
|
||||
|
||||
#: notebook/notebookapp.py:591
|
||||
msgid "The full path to a private key file for usage with SSL/TLS."
|
||||
msgstr "SSL/TLS で使用する秘密鍵ファイルへの完全なパス。"
|
||||
|
||||
#: notebook/notebookapp.py:595
|
||||
msgid "The full path to a certificate authority certificate for SSL/TLS client authentication."
|
||||
msgstr "SSL/TLS クライアント認証用の認証局証明書への完全なパス。"
|
||||
|
||||
#: notebook/notebookapp.py:599
|
||||
msgid "The file where the cookie secret is stored."
|
||||
msgstr "cookie secret を保存するファイル。"
|
||||
|
||||
#: notebook/notebookapp.py:628
|
||||
#, python-format
|
||||
msgid "Writing notebook server cookie secret to %s"
|
||||
msgstr "ノートブックサーバは cookie secret を %s に書き込みます"
|
||||
|
||||
#: notebook/notebookapp.py:635
|
||||
#, python-format
|
||||
msgid "Could not set permissions on %s"
|
||||
msgstr "%s の権限を設定出来ませんでした"
|
||||
|
||||
#: notebook/notebookapp.py:640
|
||||
msgid ""
|
||||
"Token used for authenticating first-time connections to the server.\n"
|
||||
"\n"
|
||||
" When no password is enabled,\n"
|
||||
" the default is to generate a new, random token.\n"
|
||||
"\n"
|
||||
" Setting to an empty string disables authentication altogether, which is NOT RECOMMENDED.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"サーバに接続する際に初回の認証に使われるトークン。\n"
|
||||
"\n"
|
||||
" パスワード無しが有効になっている場合\n"
|
||||
" デフォルト値はランダムなトークンが新しく生成されます。\n"
|
||||
"\n"
|
||||
" 空の文字列に設定すると認証が完全に無効になります。これは推奨されていません。\n"
|
||||
" "
|
||||
|
||||
#: notebook/notebookapp.py:650
|
||||
msgid ""
|
||||
"One-time token used for opening a browser.\n"
|
||||
" Once used, this token cannot be used again.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"開いたブラウザが仕様するワンタイムトークン。\n"
|
||||
" 1度使用されると再度使用する事が出来ません。\n"
|
||||
" "
|
||||
|
||||
#: notebook/notebookapp.py:726
|
||||
msgid ""
|
||||
"Specify Where to open the notebook on startup. This is the\n"
|
||||
" `new` argument passed to the standard library method `webbrowser.open`.\n"
|
||||
" The behaviour is not guaranteed, but depends on browser support. Valid\n"
|
||||
" values are:\n"
|
||||
" 2 opens a new tab,\n"
|
||||
" 1 opens a new window,\n"
|
||||
" 0 opens in an existing window.\n"
|
||||
" See the `webbrowser.open` documentation for details.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"起動時にどこでノートブックを開くかを指定します。これは\n"
|
||||
" 標準ライブラリのメソッド `webbrowser.open` の引数 `new` に渡されます。\n"
|
||||
" 動作は保証されていませんがブラウザのサポートによって異なります。\n"
|
||||
" 有効な値:\n"
|
||||
" 2 新しいタブで開く\n"
|
||||
" 1 新しいウィンドウで開く\n"
|
||||
" 0 既にあるウィンドウで開く\n"
|
||||
" 詳細は `webbrowser.open` のドキュメントを参照。\n"
|
||||
" "
|
||||
|
||||
#: notebook/notebookapp.py:737
|
||||
msgid "DEPRECATED, use tornado_settings"
|
||||
msgstr "非推奨 tornado_settings の使用"
|
||||
|
||||
#: notebook/notebookapp.py:742
|
||||
msgid ""
|
||||
"\n"
|
||||
" webapp_settings is deprecated, use tornado_settings.\n"
|
||||
msgstr ""
|
||||
"\n"
|
||||
" webapp_settings は非推奨です。tornado_settings を使って下さい。\n"
|
||||
|
||||
#: notebook/notebookapp.py:746
|
||||
msgid "Supply overrides for the tornado.web.Application that the Jupyter notebook uses."
|
||||
msgstr "Jupyterノートブックが使用する tornado.web.Application のオーバーライドを指定します。"
|
||||
|
||||
#: notebook/notebookapp.py:750
|
||||
msgid ""
|
||||
"\n"
|
||||
" Set the tornado compression options for websocket connections.\n"
|
||||
"\n"
|
||||
" This value will be returned from :meth:`WebSocketHandler.get_compression_options`.\n"
|
||||
" None (default) will disable compression.\n"
|
||||
" A dict (even an empty one) will enable compression.\n"
|
||||
"\n"
|
||||
" See the tornado docs for WebSocketHandler.get_compression_options for details.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" tornado の websocket 接続の圧縮オプションを指定します。\n"
|
||||
"\n"
|
||||
" この値は :meth:`WebSocketHandler.get_compression_options` から返されます。\n"
|
||||
" None (default) の場合は圧縮は無効になります。\n"
|
||||
" 辞書 (空でも良い) の場合は圧縮が有効になります。\n"
|
||||
"\n"
|
||||
" 詳細は tornado の WebSocketHandler.get_compression_options のドキュメントを参照。\n"
|
||||
" "
|
||||
|
||||
#: notebook/notebookapp.py:761
|
||||
msgid "Supply overrides for terminado. Currently only supports \"shell_command\"."
|
||||
msgstr "terminado のオーバーライドを指定します。現時は \"shell_command \" のみをサポートしています。"
|
||||
|
||||
#: notebook/notebookapp.py:764
|
||||
msgid "Extra keyword arguments to pass to `set_secure_cookie`. See tornado's set_secure_cookie docs for details."
|
||||
msgstr "`set_secure_cookie` に渡す追加のキーワード引数。詳細は tornado の set_secure_cookie のドキュメントを参照。"
|
||||
|
||||
#: notebook/notebookapp.py:768
|
||||
msgid ""
|
||||
"Supply SSL options for the tornado HTTPServer.\n"
|
||||
" See the tornado docs for details."
|
||||
msgstr ""
|
||||
"tornado HTTPServer の SSL オプションを指定します。\n"
|
||||
" 詳しくは tornado のドキュメントを参照。"
|
||||
|
||||
#: notebook/notebookapp.py:772
|
||||
msgid "Supply extra arguments that will be passed to Jinja environment."
|
||||
msgstr "Jinja environment に渡される追加の引数を指定します。"
|
||||
|
||||
#: notebook/notebookapp.py:776
|
||||
msgid "Extra variables to supply to jinja templates when rendering."
|
||||
msgstr "jinja テンプレートがレンダリングする際に渡される追加の変数。"
|
||||
|
||||
#: notebook/notebookapp.py:812
|
||||
msgid "DEPRECATED use base_url"
|
||||
msgstr "非推奨 base_url の使用"
|
||||
|
||||
#: notebook/notebookapp.py:816
|
||||
msgid "base_project_url is deprecated, use base_url"
|
||||
msgstr "base_project_url は非推奨です。base_url を使用して下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:832
|
||||
msgid "Path to search for custom.js, css"
|
||||
msgstr "custom.js、CSS を検索するためのパス"
|
||||
|
||||
#: notebook/notebookapp.py:844
|
||||
msgid ""
|
||||
"Extra paths to search for serving jinja templates.\n"
|
||||
"\n"
|
||||
" Can be used to override templates from notebook.templates."
|
||||
msgstr ""
|
||||
"Jinja テンプレートを探す為の追加パス。\n"
|
||||
"\n"
|
||||
" notebook.templates を上書きする為に使う事が出来ます。"
|
||||
|
||||
#: notebook/notebookapp.py:855
|
||||
msgid "extra paths to look for Javascript notebook extensions"
|
||||
msgstr "Javascript ノートブック拡張への追加パス"
|
||||
|
||||
#: notebook/notebookapp.py:900
|
||||
#, python-format
|
||||
msgid "Using MathJax: %s"
|
||||
msgstr "使用している MathJax: %s"
|
||||
|
||||
#: notebook/notebookapp.py:903
|
||||
msgid "The MathJax.js configuration file that is to be used."
|
||||
msgstr "使用される MathJax.js 設定ファイル。"
|
||||
|
||||
#: notebook/notebookapp.py:908
|
||||
#, python-format
|
||||
msgid "Using MathJax configuration file: %s"
|
||||
msgstr "使用する MathJax 設定ファイル: %s"
|
||||
|
||||
#: notebook/notebookapp.py:914
|
||||
msgid "The notebook manager class to use."
|
||||
msgstr "ノートブックマネージャのクラス"
|
||||
|
||||
#: notebook/notebookapp.py:920
|
||||
msgid "The kernel manager class to use."
|
||||
msgstr "カーネルマネージャのクラス"
|
||||
|
||||
#: notebook/notebookapp.py:926
|
||||
msgid "The session manager class to use."
|
||||
msgstr "セッションマネージャのクラス"
|
||||
|
||||
#: notebook/notebookapp.py:932
|
||||
msgid "The config manager class to use"
|
||||
msgstr "設定マネージャのクラス"
|
||||
|
||||
#: notebook/notebookapp.py:953
|
||||
msgid "The login handler class to use."
|
||||
msgstr "ログインのハンドラクラス"
|
||||
|
||||
#: notebook/notebookapp.py:960
|
||||
msgid "The logout handler class to use."
|
||||
msgstr "ログアウトのハンドラクラス"
|
||||
|
||||
#: notebook/notebookapp.py:964
|
||||
msgid "Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headerssent by the upstream reverse proxy. Necessary if the proxy handles SSL"
|
||||
msgstr "X-Scheme/X-Forwarded-Proto および X-Real-Ip/X-Forwarded-For ヘッダーがアップストリームのリバースプロキシによって送信されたことを信頼するかどうか。プロキシが SSL を処理する場合に必要となります。"
|
||||
|
||||
#: notebook/notebookapp.py:976
|
||||
msgid ""
|
||||
"\n"
|
||||
" DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
" 非推奨: matplotlib を有効にするにはノートブックで %pylab または %matplotlib\n"
|
||||
" を実行して下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:988
|
||||
msgid "Support for specifying --pylab on the command line has been removed."
|
||||
msgstr "コマンドラインでの --pylab 指定はサポートされなくなりました。"
|
||||
|
||||
#: notebook/notebookapp.py:990
|
||||
msgid "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself."
|
||||
msgstr "ノートブックの中で `%pylab{0}` または `%matplotlib{0}` を使ってください。"
|
||||
|
||||
#: notebook/notebookapp.py:995
|
||||
msgid "The directory to use for notebooks and kernels."
|
||||
msgstr "ノートブックとカーネルが使うディレクトリ。"
|
||||
|
||||
#: notebook/notebookapp.py:1018
|
||||
#, python-format
|
||||
msgid "No such notebook dir: '%r'"
|
||||
msgstr "ノートブックディレクトリが見つかりません: '%r'"
|
||||
|
||||
#: notebook/notebookapp.py:1031
|
||||
msgid "DEPRECATED use the nbserver_extensions dict instead"
|
||||
msgstr "非推奨 nbserver_extensions 辞書を代わりに使用して下さい "
|
||||
|
||||
#: notebook/notebookapp.py:1036
|
||||
msgid "server_extensions is deprecated, use nbserver_extensions"
|
||||
msgstr "server_extensions が非推奨です。 nbserver_extensions を使用して下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:1040
|
||||
msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order."
|
||||
msgstr "ノートブックサーバ拡張としてロードする Python モジュールの辞書。エントリー値を使用して拡張のロードを有効または無効にすることができます。 拡張子はアルファベット順にロードされます。"
|
||||
|
||||
#: notebook/notebookapp.py:1049
|
||||
msgid "Reraise exceptions encountered loading server extensions?"
|
||||
msgstr "サーバ拡張の読み込み中に例外が発生しましたか?"
|
||||
|
||||
#: notebook/notebookapp.py:1052
|
||||
msgid ""
|
||||
"(msgs/sec)\n"
|
||||
" Maximum rate at which messages can be sent on iopub before they are\n"
|
||||
" limited."
|
||||
msgstr "メッセージが送信される前に iopub で送信可能な最大レート。"
|
||||
|
||||
#: notebook/notebookapp.py:1056
|
||||
msgid ""
|
||||
"(bytes/sec)\n"
|
||||
" Maximum rate at which stream output can be sent on iopub before they are\n"
|
||||
" limited."
|
||||
msgstr ""
|
||||
"(bytes/sec)\n"
|
||||
" ストリーム出力が送信制限される前に iopub で送信可能な最大レート。"
|
||||
|
||||
#: notebook/notebookapp.py:1060
|
||||
msgid ""
|
||||
"(sec) Time window used to \n"
|
||||
" check the message and data rate limits."
|
||||
msgstr ""
|
||||
"(sec) このウィンドウはメッセージとデータの帯域リミット\n"
|
||||
" をチェックする為に使用されます。"
|
||||
|
||||
#: notebook/notebookapp.py:1071
|
||||
#, python-format
|
||||
msgid "No such file or directory: %s"
|
||||
msgstr "その様なファイルまたはディレクトリは存在しません: %s"
|
||||
|
||||
#: notebook/notebookapp.py:1141
|
||||
msgid "Notebook servers are configured to only be run with a password."
|
||||
msgstr "ノートブックサーバはパスワードが設定された場合にだけ動作するよう設定されています。"
|
||||
|
||||
#: notebook/notebookapp.py:1142
|
||||
msgid "Hint: run the following command to set a password"
|
||||
msgstr "ヒント: パスワードを設定するには以下のコマンドを実行します"
|
||||
|
||||
#: notebook/notebookapp.py:1143
|
||||
msgid "\t$ python -m notebook.auth password"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1181
|
||||
#, python-format
|
||||
msgid "The port %i is already in use, trying another port."
|
||||
msgstr "ポート %i は既に使用されています。他のポートで試して下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:1184
|
||||
#, python-format
|
||||
msgid "Permission to listen on port %i denied"
|
||||
msgstr "ポート %i で待機する権限がありません"
|
||||
|
||||
#: notebook/notebookapp.py:1193
|
||||
msgid "ERROR: the notebook server could not be started because no available port could be found."
|
||||
msgstr "エラー: 有効なポートが見付からなかったためノートブックサーバを起動できませんでした。"
|
||||
|
||||
#: notebook/notebookapp.py:1199
|
||||
msgid "[all ip addresses on your system]"
|
||||
msgstr "[システム上の全ての IP アドレス]"
|
||||
|
||||
#: notebook/notebookapp.py:1223
|
||||
#, python-format
|
||||
msgid "Terminals not available (error was %s)"
|
||||
msgstr "端末は存在しません (%s でエラー発生)"
|
||||
|
||||
#: notebook/notebookapp.py:1259
|
||||
msgid "interrupted"
|
||||
msgstr "中断しました"
|
||||
|
||||
#: notebook/notebookapp.py:1261
|
||||
msgid "y"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1262
|
||||
msgid "n"
|
||||
msgstr ""
|
||||
|
||||
#: notebook/notebookapp.py:1263
|
||||
#, python-format
|
||||
msgid "Shutdown this notebook server (%s/[%s])? "
|
||||
msgstr "このノートブックサーバをシャットダウンしますか? (%s/[%s])"
|
||||
|
||||
#: notebook/notebookapp.py:1269
|
||||
msgid "Shutdown confirmed"
|
||||
msgstr "シャットダウンの確認"
|
||||
|
||||
#: notebook/notebookapp.py:1273
|
||||
msgid "No answer for 5s:"
|
||||
msgstr "5秒間に応答がありません:"
|
||||
|
||||
#: notebook/notebookapp.py:1274
|
||||
msgid "resuming operation..."
|
||||
msgstr "操作を再開中..."
|
||||
|
||||
#: notebook/notebookapp.py:1282
|
||||
#, python-format
|
||||
msgid "received signal %s, stopping"
|
||||
msgstr "シグナル %s を受信。停止します"
|
||||
|
||||
#: notebook/notebookapp.py:1338
|
||||
#, python-format
|
||||
msgid "Error loading server extension %s"
|
||||
msgstr "サーバ拡張 %s の読み込みエラー"
|
||||
|
||||
#: notebook/notebookapp.py:1369
|
||||
#, python-format
|
||||
msgid "Shutting down %d kernels"
|
||||
msgstr "%d 個のカーネルをシャットダウンしています"
|
||||
|
||||
#: notebook/notebookapp.py:1375
|
||||
#, python-format
|
||||
msgid "%d active kernel"
|
||||
msgid_plural "%d active kernels"
|
||||
msgstr[0] "%d 個のアクティブなカーネル"
|
||||
msgstr[1] "%d 個のアクティブなカーネル"
|
||||
|
||||
#: notebook/notebookapp.py:1379
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The Jupyter Notebook is running at:\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
"Jupyter Notebook は以下の URL 起動しています:\n"
|
||||
"%s"
|
||||
|
||||
#: notebook/notebookapp.py:1426
|
||||
msgid "Running as root is not recommended. Use --allow-root to bypass."
|
||||
msgstr "root ユーザでの実行は推奨されません。バイパスするには --allow-root を使って下さい。"
|
||||
|
||||
#: notebook/notebookapp.py:1432
|
||||
msgid "Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)."
|
||||
msgstr "サーバを停止し全てのカーネルをシャットダウンするには Control-C を使って下さい(確認をスキップするには2回)。"
|
||||
|
||||
#: notebook/notebookapp.py:1434
|
||||
msgid "Welcome to Project Jupyter! Explore the various tools available and their corresponding documentation. If you are interested in contributing to the platform, please visit the communityresources section at http://jupyter.org/community.html."
|
||||
msgstr "Project Jupyter へようこそ! 利用可能な色々なツールとそれに対応するドキュメントを探索して下さい。プラットフォームへの貢献に興味がある場合は http://jupyter.org/community.html の communityresources セクションにアクセスしてください。"
|
||||
|
||||
#: notebook/notebookapp.py:1445
|
||||
#, python-format
|
||||
msgid "No web browser found: %s."
|
||||
msgstr "ウェブブラウザが見つかりません: %s"
|
||||
|
||||
#: notebook/notebookapp.py:1450
|
||||
#, python-format
|
||||
msgid "%s does not exist"
|
||||
msgstr "%s は存在しません"
|
||||
|
||||
#: notebook/notebookapp.py:1484
|
||||
msgid "Interrupted..."
|
||||
msgstr "中断..."
|
||||
|
||||
#: notebook/services/contents/filemanager.py:506
|
||||
#, python-format
|
||||
msgid "Serving notebooks from local directory: %s"
|
||||
msgstr "ローカルディレクトリからノートブックをサーブ: %s"
|
||||
|
||||
#: notebook/services/contents/manager.py:68
|
||||
msgid "Untitled"
|
||||
msgstr ""
|
1353
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbjs.json
Normal file
1353
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbjs.json
Normal file
File diff suppressed because it is too large
Load diff
2116
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbjs.po
Normal file
2116
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbjs.po
Normal file
File diff suppressed because it is too large
Load diff
BIN
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbui.mo
Normal file
BIN
venv/Lib/site-packages/notebook/i18n/nl/LC_MESSAGES/nbui.mo
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue