mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-14 21:43:01 +00:00
drop support for old Python, IPython < 3
Require IPython >= 3.0, Python >= 3.3
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
language: python
|
||||
python:
|
||||
- 3.4
|
||||
- 2.7
|
||||
- 3.3
|
||||
before_install:
|
||||
# workaround for https://github.com/travis-ci/travis-cookbooks/issues/155
|
||||
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
|
||||
|
@@ -18,6 +18,8 @@ Basic principles:
|
||||
|
||||
## Dependencies
|
||||
|
||||
JupyterHub requires IPython >= 3.0 (current master) and Python >= 3.3.
|
||||
|
||||
You will need nodejs/npm, which you can get from your package manager:
|
||||
|
||||
sudo apt-get install npm nodejs-legacy
|
||||
|
@@ -1,2 +1,2 @@
|
||||
mock
|
||||
-r requirements.txt
|
||||
pytest
|
||||
|
@@ -1 +1,2 @@
|
||||
from .version import *
|
||||
|
||||
|
@@ -4,11 +4,7 @@
|
||||
|
||||
import json
|
||||
|
||||
try:
|
||||
# py3
|
||||
from http.client import responses
|
||||
except ImportError:
|
||||
from httplib import responses
|
||||
from http.client import responses
|
||||
|
||||
from tornado import web
|
||||
|
||||
@@ -19,7 +15,7 @@ class APIHandler(BaseHandler):
|
||||
"""Return the body of the request as JSON data."""
|
||||
if not self.request.body:
|
||||
return None
|
||||
body = self.request.body.strip().decode(u'utf-8')
|
||||
body = self.request.body.strip().decode('utf-8')
|
||||
try:
|
||||
model = json.loads(body)
|
||||
except Exception:
|
||||
|
@@ -11,11 +11,6 @@ from .. import orm
|
||||
from ..utils import admin_only, authenticated_403
|
||||
from .base import APIHandler
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
basestring = str # py3
|
||||
|
||||
class BaseUserHandler(APIHandler):
|
||||
|
||||
def user_model(self, user):
|
||||
@@ -26,7 +21,7 @@ class BaseUserHandler(APIHandler):
|
||||
}
|
||||
|
||||
_model_types = {
|
||||
'name': basestring,
|
||||
'name': str,
|
||||
'admin': bool,
|
||||
}
|
||||
|
||||
|
@@ -5,20 +5,18 @@
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import binascii
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from distutils.version import LooseVersion as V
|
||||
from getpass import getuser
|
||||
from subprocess import Popen
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
# py3
|
||||
raw_input = input
|
||||
if sys.version_info[:2] < (3,3):
|
||||
raise ValueError("Python < 3.3 not supported: %s" % sys.version)
|
||||
|
||||
from six import text_type
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from sqlalchemy.exc import OperationalError
|
||||
@@ -30,6 +28,10 @@ from tornado.ioloop import IOLoop, PeriodicCallback
|
||||
from tornado.log import LogFormatter, app_log, access_log, gen_log
|
||||
from tornado import gen, web
|
||||
|
||||
import IPython
|
||||
if V(IPython.__version__) < V('3.0'):
|
||||
raise ImportError("JupyterHub Requires IPython >= 3.0, found %s" % IPython.__version__)
|
||||
|
||||
from IPython.utils.traitlets import (
|
||||
Unicode, Integer, Dict, TraitError, List, Bool, Any,
|
||||
Type, Set, Instance, Bytes,
|
||||
@@ -43,8 +45,8 @@ from . import handlers, apihandlers
|
||||
from . import orm
|
||||
from ._data import DATA_FILES_PATH
|
||||
from .utils import (
|
||||
url_path_join, TimeoutError,
|
||||
ISO8601_ms, ISO8601_s, getuser_unicode,
|
||||
url_path_join,
|
||||
ISO8601_ms, ISO8601_s,
|
||||
)
|
||||
# classes for config
|
||||
from .auth import Authenticator, PAMAuthenticator
|
||||
@@ -302,14 +304,7 @@ class JupyterHubApp(Application):
|
||||
|
||||
def _log_format_default(self):
|
||||
"""override default log format to include time"""
|
||||
return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
|
||||
|
||||
def _log_format_changed(self, name, old, new):
|
||||
"""Change the log formatter when log_format is set."""
|
||||
# FIXME: IPython < 3 compat
|
||||
_log_handler = self.log.handlers[0]
|
||||
_log_formatter = self._log_formatter_cls(fmt=new, datefmt=self.log_datefmt)
|
||||
_log_handler.setFormatter(_log_formatter)
|
||||
return "%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
|
||||
|
||||
def init_logging(self):
|
||||
# This prevents double log messages because tornado use a root logger that
|
||||
@@ -325,8 +320,6 @@ class JupyterHubApp(Application):
|
||||
logger.propagate = True
|
||||
logger.parent = self.log
|
||||
logger.setLevel(self.log.level)
|
||||
# FIXME: IPython < 3 compat
|
||||
self._log_format_changed('', '', self.log_format)
|
||||
|
||||
def init_ports(self):
|
||||
if self.hub_port == self.port:
|
||||
@@ -370,7 +363,7 @@ class JupyterHubApp(Application):
|
||||
"""More informative log messages for failed filesystem access"""
|
||||
path = os.path.abspath(path)
|
||||
parent, fname = os.path.split(path)
|
||||
user = getuser_unicode()
|
||||
user = getuser()
|
||||
if not os.path.isdir(parent):
|
||||
self.log.error("Directory %s does not exist", parent)
|
||||
if os.path.exists(parent) and not os.access(parent, os.W_OK):
|
||||
@@ -399,7 +392,7 @@ class JupyterHubApp(Application):
|
||||
self.log.error("Bad permissions on %s", secret_file)
|
||||
else:
|
||||
self.log.info("Loading %s from %s", trait_name, secret_file)
|
||||
with io.open(secret_file) as f:
|
||||
with open(secret_file) as f:
|
||||
b64_secret = f.read()
|
||||
try:
|
||||
secret = binascii.a2b_base64(b64_secret)
|
||||
@@ -413,8 +406,8 @@ class JupyterHubApp(Application):
|
||||
if secret_file and secret_from == 'new':
|
||||
# if we generated a new secret, store it in the secret_file
|
||||
self.log.info("Writing %s to %s", trait_name, secret_file)
|
||||
b64_secret = text_type(binascii.b2a_base64(secret))
|
||||
with io.open(secret_file, 'w', encoding='utf8') as f:
|
||||
b64_secret = binascii.b2a_base64(secret).decode('ascii')
|
||||
with open(secret_file, 'w') as f:
|
||||
f.write(b64_secret)
|
||||
try:
|
||||
os.chmod(secret_file, 0o600)
|
||||
@@ -450,7 +443,7 @@ class JupyterHubApp(Application):
|
||||
ip=self.hub_ip,
|
||||
port=self.hub_port,
|
||||
base_url=self.hub_prefix,
|
||||
cookie_name=u'jupyter-hub-token',
|
||||
cookie_name='jupyter-hub-token',
|
||||
)
|
||||
)
|
||||
self.db.add(self.hub)
|
||||
@@ -470,7 +463,7 @@ class JupyterHubApp(Application):
|
||||
# add current user as admin if there aren't any others
|
||||
admins = db.query(orm.User).filter(orm.User.admin==True)
|
||||
if admins.first() is None:
|
||||
self.admin_users.add(getuser_unicode())
|
||||
self.admin_users.add(getuser())
|
||||
|
||||
for name in self.admin_users:
|
||||
# ensure anyone specified as admin in config is admin in db
|
||||
@@ -576,7 +569,7 @@ class JupyterHubApp(Application):
|
||||
self.proxy.public_server.port = self.port
|
||||
self.proxy.api_server.ip = self.proxy_api_ip
|
||||
self.proxy.api_server.port = self.proxy_api_port
|
||||
self.proxy.api_server.base_url = u'/api/routes/'
|
||||
self.proxy.api_server.base_url = '/api/routes/'
|
||||
self.db.commit()
|
||||
|
||||
@gen.coroutine
|
||||
@@ -694,8 +687,8 @@ class JupyterHubApp(Application):
|
||||
pid = os.getpid()
|
||||
if self.pid_file:
|
||||
self.log.debug("Writing PID %i to %s", pid, self.pid_file)
|
||||
with io.open(self.pid_file, 'w') as f:
|
||||
f.write(u'%i' % pid)
|
||||
with open(self.pid_file, 'w') as f:
|
||||
f.write('%i' % pid)
|
||||
|
||||
@catch_config_error
|
||||
def initialize(self, *args, **kwargs):
|
||||
@@ -756,7 +749,7 @@ class JupyterHubApp(Application):
|
||||
def ask():
|
||||
prompt = "Overwrite %s with default config? [y/N]" % self.config_file
|
||||
try:
|
||||
return raw_input(prompt).lower() or 'n'
|
||||
return input(prompt).lower() or 'n'
|
||||
except KeyboardInterrupt:
|
||||
print('') # empty line
|
||||
return 'n'
|
||||
@@ -771,7 +764,7 @@ class JupyterHubApp(Application):
|
||||
if isinstance(config_text, bytes):
|
||||
config_text = config_text.decode('utf8')
|
||||
print("Writing default config to: %s" % self.config_file)
|
||||
with io.open(self.config_file, encoding='utf8', mode='w') as f:
|
||||
with open(self.config_file, mode='w') as f:
|
||||
f.write(config_text)
|
||||
|
||||
@gen.coroutine
|
||||
|
@@ -153,5 +153,5 @@ class PAMAuthenticator(LocalAuthenticator):
|
||||
busername = username.encode(self.encoding)
|
||||
bpassword = data['password'].encode(self.encoding)
|
||||
if simplepam.authenticate(busername, bpassword, service=self.service):
|
||||
raise gen.Return(username)
|
||||
return username
|
||||
|
@@ -5,11 +5,7 @@
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
try:
|
||||
# py3
|
||||
from http.client import responses
|
||||
except ImportError:
|
||||
from httplib import responses
|
||||
from http.client import responses
|
||||
|
||||
from jinja2 import TemplateNotFound
|
||||
|
||||
@@ -156,7 +152,7 @@ class BaseHandler(RequestHandler):
|
||||
auth = self.authenticator
|
||||
if auth is not None:
|
||||
result = yield auth.authenticate(self, data)
|
||||
raise gen.Return(result)
|
||||
return result
|
||||
else:
|
||||
self.log.error("No authentication function, login is impossible!")
|
||||
|
||||
@@ -179,7 +175,7 @@ class BaseHandler(RequestHandler):
|
||||
)
|
||||
yield self.proxy.add_user(user)
|
||||
user.spawner.add_poll_callback(self.user_stopped, user)
|
||||
raise gen.Return(user)
|
||||
return user
|
||||
|
||||
@gen.coroutine
|
||||
def user_stopped(self, user):
|
||||
|
@@ -63,11 +63,11 @@ class Server(Base):
|
||||
"""
|
||||
__tablename__ = 'servers'
|
||||
id = Column(Integer, primary_key=True)
|
||||
proto = Column(Unicode, default=u'http')
|
||||
ip = Column(Unicode, default=u'localhost')
|
||||
proto = Column(Unicode, default='http')
|
||||
ip = Column(Unicode, default='localhost')
|
||||
port = Column(Integer, default=random_port)
|
||||
base_url = Column(Unicode, default=u'/')
|
||||
cookie_name = Column(Unicode, default=u'cookie')
|
||||
base_url = Column(Unicode, default='/')
|
||||
cookie_name = Column(Unicode, default='cookie')
|
||||
|
||||
def __repr__(self):
|
||||
return "<Server(%s:%s)>" % (self.ip, self.port)
|
||||
|
@@ -9,7 +9,6 @@ import os
|
||||
import requests
|
||||
|
||||
from tornado import ioloop
|
||||
from tornado import web
|
||||
|
||||
from IPython.utils.traitlets import Unicode
|
||||
|
||||
@@ -21,8 +20,8 @@ from IPython.html.utils import url_path_join
|
||||
from distutils.version import LooseVersion as V
|
||||
|
||||
import IPython
|
||||
if V(IPython.__version__) < V('2.2'):
|
||||
raise ImportError("JupyterHub Requires IPython >= 2.2, found %s" % IPython.__version__)
|
||||
if V(IPython.__version__) < V('3.0'):
|
||||
raise ImportError("JupyterHub Requires IPython >= 3.0, found %s" % IPython.__version__)
|
||||
|
||||
# Define two methods to attach to AuthenticatedHandler,
|
||||
# which authenticate via the central auth server.
|
||||
@@ -97,11 +96,6 @@ class SingleUserNotebookApp(NotebookApp):
|
||||
# disable the exit confirmation for background notebook processes
|
||||
ioloop.IOLoop.instance().stop()
|
||||
|
||||
def init_kernel_argv(self):
|
||||
"""construct the kernel arguments"""
|
||||
# FIXME: This is 2.x-compat, remove when 3.x is requred
|
||||
self.kernel_argv = ["--profile-dir", self.profile_dir.location]
|
||||
|
||||
def init_webapp(self):
|
||||
# monkeypatch authentication to use the hub
|
||||
from IPython.html.base.handlers import AuthenticatedHandler
|
||||
@@ -120,10 +114,7 @@ class SingleUserNotebookApp(NotebookApp):
|
||||
|
||||
# load the hub related settings into the tornado settings dict
|
||||
env = os.environ
|
||||
# FIXME: IPython < 3 compat
|
||||
s = getattr(self, 'tornado_settings',
|
||||
getattr(self, 'webapp_settings')
|
||||
)
|
||||
s = self.webapp_settings
|
||||
s['cookie_cache'] = {}
|
||||
s['user'] = self.user
|
||||
s['hub_api_key'] = env.pop('JPY_API_TOKEN')
|
||||
|
@@ -114,7 +114,7 @@ class Spawner(LoggingConfigurable):
|
||||
|
||||
Subclasses should call super, to ensure that state is properly cleared.
|
||||
"""
|
||||
self.api_token = u''
|
||||
self.api_token = ''
|
||||
|
||||
def get_args(self):
|
||||
"""Return the arguments to be passed after self.cmd"""
|
||||
@@ -339,13 +339,13 @@ class LocalProcessSpawner(Spawner):
|
||||
break
|
||||
pids = self._get_pg_pids(ppid)
|
||||
if pids:
|
||||
raise gen.Return(pids[0])
|
||||
return pids[0]
|
||||
else:
|
||||
yield gen.Task(loop.add_timeout, loop.time() + 0.1)
|
||||
self.log.error("Failed to get single-user PID")
|
||||
# return sudo pid if we can't get the real PID
|
||||
# this shouldn't happen
|
||||
raise gen.Return(ppid)
|
||||
return ppid
|
||||
|
||||
@gen.coroutine
|
||||
def start(self):
|
||||
@@ -378,7 +378,7 @@ class LocalProcessSpawner(Spawner):
|
||||
if status is not None:
|
||||
# clear state if the process is done
|
||||
self.clear_state()
|
||||
raise gen.Return(status)
|
||||
return status
|
||||
|
||||
# if we resumed from stored state,
|
||||
# we don't have the Popen handle anymore, so rely on self.pid
|
||||
@@ -386,16 +386,16 @@ class LocalProcessSpawner(Spawner):
|
||||
if not self.pid:
|
||||
# no pid, not running
|
||||
self.clear_state()
|
||||
raise gen.Return(0)
|
||||
return 0
|
||||
|
||||
# send signal 0 to check if PID exists
|
||||
# this doesn't work on Windows, but that's okay because we don't support Windows.
|
||||
alive = yield self._signal(0)
|
||||
if not alive:
|
||||
self.clear_state()
|
||||
raise gen.Return(0)
|
||||
return 0
|
||||
else:
|
||||
raise gen.Return(None)
|
||||
return None
|
||||
|
||||
@gen.coroutine
|
||||
def _signal(self, sig):
|
||||
@@ -405,9 +405,9 @@ class LocalProcessSpawner(Spawner):
|
||||
"""
|
||||
if self.set_user == 'sudo':
|
||||
rc = yield self._signal_sudo(sig)
|
||||
raise gen.Return(rc)
|
||||
return rc
|
||||
else:
|
||||
raise gen.Return(self._signal_setuid(sig))
|
||||
return self._signal_setuid(sig)
|
||||
|
||||
def _signal_setuid(self, sig):
|
||||
"""simple implementation of signal, which we can use when we are using setuid (we are root)"""
|
||||
@@ -427,10 +427,10 @@ class LocalProcessSpawner(Spawner):
|
||||
try:
|
||||
check_output(['ps', '-p', str(self.pid)], stderr=PIPE)
|
||||
except CalledProcessError:
|
||||
raise gen.Return(False) # process is gone
|
||||
return False # process is gone
|
||||
else:
|
||||
if sig == 0:
|
||||
raise gen.Return(True) # process exists
|
||||
return True # process exists
|
||||
|
||||
# build sudo -u user kill -SIG PID
|
||||
cmd = self.sudo_cmd(self.user)
|
||||
@@ -441,7 +441,7 @@ class LocalProcessSpawner(Spawner):
|
||||
check_output(cmd,
|
||||
preexec_fn=self.make_preexec_fn(self.user.name),
|
||||
)
|
||||
raise gen.Return(True) # process exists
|
||||
return True # process exists
|
||||
|
||||
@gen.coroutine
|
||||
def stop(self, now=False):
|
||||
|
@@ -4,12 +4,12 @@
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import logging
|
||||
from getpass import getuser
|
||||
|
||||
from pytest import fixture
|
||||
from tornado import ioloop
|
||||
|
||||
from .. import orm
|
||||
from ..utils import getuser_unicode
|
||||
|
||||
from .mocking import MockHubApp
|
||||
|
||||
@@ -24,7 +24,7 @@ def db():
|
||||
if _db is None:
|
||||
_db = orm.new_session_factory('sqlite:///:memory:', echo=True)()
|
||||
user = orm.User(
|
||||
name=getuser_unicode(),
|
||||
name=getuser(),
|
||||
server=orm.Server(),
|
||||
)
|
||||
hub = orm.Hub(
|
||||
|
@@ -1,29 +1,23 @@
|
||||
"""mock utilities for testing"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from tempfile import NamedTemporaryFile
|
||||
import threading
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
import mock
|
||||
from unittest import mock
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
from six import text_type
|
||||
|
||||
from ..spawner import LocalProcessSpawner
|
||||
from ..app import JupyterHubApp
|
||||
from ..auth import PAMAuthenticator, Authenticator
|
||||
from ..auth import PAMAuthenticator
|
||||
from .. import orm
|
||||
|
||||
def mock_authenticate(username, password, service='login'):
|
||||
# mimic simplepam's failure to handle unicode
|
||||
if isinstance(username, text_type):
|
||||
if isinstance(username, str):
|
||||
return False
|
||||
if isinstance(password, text_type):
|
||||
if isinstance(password, str):
|
||||
return False
|
||||
|
||||
# just use equality for testing
|
||||
|
@@ -1,6 +1,5 @@
|
||||
"""Test the JupyterHubApp entry point"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from subprocess import check_output
|
||||
@@ -8,8 +7,8 @@ from tempfile import NamedTemporaryFile
|
||||
|
||||
def test_help_all():
|
||||
out = check_output([sys.executable, '-m', 'jupyterhub', '--help-all']).decode('utf8', 'replace')
|
||||
assert u'--ip' in out
|
||||
assert u'--JupyterHubApp.ip' in out
|
||||
assert '--ip' in out
|
||||
assert '--JupyterHubApp.ip' in out
|
||||
|
||||
def test_generate_config():
|
||||
with NamedTemporaryFile(prefix='jupyter_hub_config', suffix='.py') as tf:
|
||||
@@ -19,7 +18,7 @@ def test_generate_config():
|
||||
'--generate-config', '-f', cfg_file]
|
||||
).decode('utf8', 'replace')
|
||||
assert os.path.exists(cfg_file)
|
||||
with io.open(cfg_file) as f:
|
||||
with open(cfg_file) as f:
|
||||
cfg_text = f.read()
|
||||
os.remove(cfg_file)
|
||||
assert cfg_file in out
|
||||
|
@@ -9,33 +9,33 @@ from .mocking import MockPAMAuthenticator
|
||||
def test_pam_auth(io_loop):
|
||||
authenticator = MockPAMAuthenticator()
|
||||
authorized = io_loop.run_sync(lambda : authenticator.authenticate(None, {
|
||||
u'username': u'match',
|
||||
u'password': u'match',
|
||||
'username': 'match',
|
||||
'password': 'match',
|
||||
}))
|
||||
assert authorized == u'match'
|
||||
assert authorized == 'match'
|
||||
|
||||
authorized = io_loop.run_sync(lambda : authenticator.authenticate(None, {
|
||||
u'username': u'match',
|
||||
u'password': u'nomatch',
|
||||
'username': 'match',
|
||||
'password': 'nomatch',
|
||||
}))
|
||||
assert authorized is None
|
||||
|
||||
def test_pam_auth_whitelist(io_loop):
|
||||
authenticator = MockPAMAuthenticator(whitelist={'wash', 'kaylee'})
|
||||
authorized = io_loop.run_sync(lambda : authenticator.authenticate(None, {
|
||||
u'username': u'kaylee',
|
||||
u'password': u'kaylee',
|
||||
'username': 'kaylee',
|
||||
'password': 'kaylee',
|
||||
}))
|
||||
assert authorized == u'kaylee'
|
||||
assert authorized == 'kaylee'
|
||||
|
||||
authorized = io_loop.run_sync(lambda : authenticator.authenticate(None, {
|
||||
u'username': u'wash',
|
||||
u'password': u'nomatch',
|
||||
'username': 'wash',
|
||||
'password': 'nomatch',
|
||||
}))
|
||||
assert authorized is None
|
||||
|
||||
authorized = io_loop.run_sync(lambda : authenticator.authenticate(None, {
|
||||
u'username': u'mal',
|
||||
u'password': u'mal',
|
||||
'username': 'mal',
|
||||
'password': 'mal',
|
||||
}))
|
||||
assert authorized is None
|
||||
|
@@ -5,48 +5,42 @@
|
||||
|
||||
from .. import orm
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
# py3
|
||||
unicode = str
|
||||
|
||||
|
||||
def test_server(db):
|
||||
server = orm.Server()
|
||||
db.add(server)
|
||||
db.commit()
|
||||
assert server.ip == u'localhost'
|
||||
assert server.ip == 'localhost'
|
||||
assert server.base_url == '/'
|
||||
assert server.proto == 'http'
|
||||
assert isinstance(server.port, int)
|
||||
assert isinstance(server.cookie_name, unicode)
|
||||
assert isinstance(server.cookie_name, str)
|
||||
assert server.url == 'http://localhost:%i/' % server.port
|
||||
|
||||
|
||||
def test_proxy(db):
|
||||
proxy = orm.Proxy(
|
||||
auth_token=u'abc-123',
|
||||
auth_token='abc-123',
|
||||
public_server=orm.Server(
|
||||
ip=u'192.168.1.1',
|
||||
ip='192.168.1.1',
|
||||
port=8000,
|
||||
),
|
||||
api_server=orm.Server(
|
||||
ip=u'127.0.0.1',
|
||||
ip='127.0.0.1',
|
||||
port=8001,
|
||||
),
|
||||
)
|
||||
db.add(proxy)
|
||||
db.commit()
|
||||
assert proxy.public_server.ip == u'192.168.1.1'
|
||||
assert proxy.api_server.ip == u'127.0.0.1'
|
||||
assert proxy.auth_token == u'abc-123'
|
||||
assert proxy.public_server.ip == '192.168.1.1'
|
||||
assert proxy.api_server.ip == '127.0.0.1'
|
||||
assert proxy.auth_token == 'abc-123'
|
||||
|
||||
|
||||
def test_hub(db):
|
||||
hub = orm.Hub(
|
||||
server=orm.Server(
|
||||
ip = u'1.2.3.4',
|
||||
ip = '1.2.3.4',
|
||||
port = 1234,
|
||||
base_url='/hubtest/',
|
||||
),
|
||||
@@ -54,30 +48,30 @@ def test_hub(db):
|
||||
)
|
||||
db.add(hub)
|
||||
db.commit()
|
||||
assert hub.server.ip == u'1.2.3.4'
|
||||
assert hub.server.ip == '1.2.3.4'
|
||||
hub.server.port == 1234
|
||||
assert hub.api_url == u'http://1.2.3.4:1234/hubtest/api'
|
||||
assert hub.api_url == 'http://1.2.3.4:1234/hubtest/api'
|
||||
|
||||
|
||||
def test_user(db):
|
||||
user = orm.User(name=u'kaylee',
|
||||
user = orm.User(name='kaylee',
|
||||
server=orm.Server(),
|
||||
state={'pid': 4234},
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
assert user.name == u'kaylee'
|
||||
assert user.server.ip == u'localhost'
|
||||
assert user.name == 'kaylee'
|
||||
assert user.server.ip == 'localhost'
|
||||
assert user.state == {'pid': 4234}
|
||||
|
||||
found = orm.User.find(db, u'kaylee')
|
||||
found = orm.User.find(db, 'kaylee')
|
||||
assert found.name == user.name
|
||||
found = orm.User.find(db, u'badger')
|
||||
found = orm.User.find(db, 'badger')
|
||||
assert found is None
|
||||
|
||||
|
||||
def test_tokens(db):
|
||||
user = orm.User(name=u'inara')
|
||||
user = orm.User(name='inara')
|
||||
db.add(user)
|
||||
db.commit()
|
||||
token = user.new_api_token()
|
||||
|
@@ -5,34 +5,17 @@
|
||||
|
||||
from binascii import b2a_hex
|
||||
import errno
|
||||
import getpass
|
||||
import hashlib
|
||||
import os
|
||||
import socket
|
||||
import uuid
|
||||
|
||||
from six import text_type
|
||||
from tornado import web, gen, ioloop
|
||||
from tornado.httpclient import AsyncHTTPClient, HTTPError
|
||||
from tornado.log import app_log
|
||||
|
||||
from IPython.html.utils import url_path_join
|
||||
|
||||
try:
|
||||
# make TimeoutError importable on Python >= 3.3
|
||||
TimeoutError = TimeoutError
|
||||
except NameError:
|
||||
# python < 3.3
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def getuser_unicode():
|
||||
"""
|
||||
Call getpass.getuser, ensuring that the output is returned as unicode.
|
||||
"""
|
||||
return text_type(getpass.getuser())
|
||||
|
||||
|
||||
def random_port():
|
||||
"""get a single random port"""
|
||||
@@ -147,7 +130,7 @@ def new_token(*args, **kwargs):
|
||||
|
||||
For now, just UUIDs.
|
||||
"""
|
||||
return text_type(uuid.uuid4().hex)
|
||||
return uuid.uuid4().hex
|
||||
|
||||
|
||||
def hash_token(token, salt=8, rounds=16384, algorithm='sha512'):
|
||||
@@ -169,7 +152,7 @@ def hash_token(token, salt=8, rounds=16384, algorithm='sha512'):
|
||||
h.update(btoken)
|
||||
digest = h.hexdigest()
|
||||
|
||||
return u"{algorithm}:{rounds}:{salt}:{digest}".format(**locals())
|
||||
return "{algorithm}:{rounds}:{salt}:{digest}".format(**locals())
|
||||
|
||||
|
||||
def compare_token(compare, token):
|
||||
|
@@ -1,7 +1,6 @@
|
||||
ipython[notebook]>=2.2
|
||||
tornado>=3.2
|
||||
-e git+https://github.com/ipython/ipython.git#egg=ipython[notebook]
|
||||
tornado>=4
|
||||
jinja2
|
||||
simplepam
|
||||
sqlalchemy
|
||||
requests
|
||||
six
|
||||
|
30
setup.py
30
setup.py
@@ -14,12 +14,11 @@ import os
|
||||
import sys
|
||||
|
||||
v = sys.version_info
|
||||
if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
|
||||
error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
|
||||
if v[:2] < (3,3):
|
||||
error = "ERROR: Jupyter Hub requires Python version 3.3 or above."
|
||||
print(error, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
if os.name in ('nt', 'dos'):
|
||||
error = "ERROR: Windows is not supported"
|
||||
@@ -34,14 +33,6 @@ from glob import glob
|
||||
from distutils.core import setup
|
||||
from subprocess import check_call
|
||||
|
||||
try:
|
||||
execfile
|
||||
except NameError:
|
||||
# py3
|
||||
def execfile(fname, globs, locs=None):
|
||||
locs = locs or globs
|
||||
exec(compile(open(fname).read(), fname, "exec"), globs, locs)
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
@@ -67,7 +58,9 @@ def get_data_files():
|
||||
|
||||
|
||||
ns = {}
|
||||
execfile(pjoin(here, 'jupyterhub', 'version.py'), ns)
|
||||
with open(pjoin(here, 'jupyterhub', 'version.py')) as f:
|
||||
exec(f.read(), {}, ns)
|
||||
|
||||
|
||||
packages = []
|
||||
for d, _, _ in os.walk('jupyterhub'):
|
||||
@@ -92,13 +85,11 @@ setup_args = dict(
|
||||
keywords = ['Interactive', 'Interpreter', 'Shell', 'Web'],
|
||||
classifiers = [
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
'Intended Audience :: Science/Research',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: System :: Shells',
|
||||
],
|
||||
)
|
||||
|
||||
@@ -194,11 +185,14 @@ if 'setuptools' in sys.modules:
|
||||
self.distribution.run_command('css')
|
||||
develop.run(self)
|
||||
setup_args['cmdclass']['develop'] = develop_js_css
|
||||
|
||||
setup_args['install_requires'] = install_requires = []
|
||||
|
||||
with open('requirements.txt') as f:
|
||||
install_requires = [ line.strip() for line in f.readlines() ]
|
||||
setup_args['install_requires'] = install_requires
|
||||
for line in f.readlines():
|
||||
req = line.strip()
|
||||
if req.startswith('-e'):
|
||||
req = line.split('#egg=', 1)[1]
|
||||
install_requires.append(req)
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
# setup
|
||||
|
Reference in New Issue
Block a user