fix errors, remove pep8 corrections

This commit is contained in:
Christian Barra
2017-01-03 14:10:46 +01:00
parent dbe8bf5428
commit 02090c953b
5 changed files with 118 additions and 145 deletions

View File

@@ -1095,7 +1095,9 @@ class JupyterHub(Application):
# if user.server is defined. # if user.server is defined.
log = self.log.warning if user.server else self.log.debug log = self.log.warning if user.server else self.log.debug
log("%s not running.", user.name) log("%s not running.", user.name)
user.server = None for server in user.servers:
db.delete(server)
db.commit()
user_summaries.append(_user_summary(user)) user_summaries.append(_user_summary(user))

View File

@@ -19,7 +19,7 @@ from sqlalchemy import (
from sqlalchemy.ext.declarative import declarative_base, declared_attr from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import sessionmaker, relationship, backref from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.pool import StaticPool from sqlalchemy.pool import StaticPool
from sqlalchemy.schema import Index from sqlalchemy.schema import Index, UniqueConstraint
from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.sql.expression import bindparam from sqlalchemy.sql.expression import bindparam
from sqlalchemy import create_engine, Table from sqlalchemy import create_engine, Table
@@ -32,11 +32,8 @@ from .utils import (
class JSONDict(TypeDecorator): class JSONDict(TypeDecorator):
"""Represents an immutable structure as a json-encoded string. """Represents an immutable structure as a json-encoded string.
Usage:: Usage::
JSONEncodedDict(255) JSONEncodedDict(255)
""" """
impl = TEXT impl = TEXT
@@ -59,7 +56,6 @@ Base.log = app_log
class Server(Base): class Server(Base):
"""The basic state of a server """The basic state of a server
connection and cookie info connection and cookie info
""" """
__tablename__ = 'servers' __tablename__ = 'servers'
@@ -69,6 +65,7 @@ class Server(Base):
port = Column(Integer, default=random_port) port = Column(Integer, default=random_port)
base_url = Column(Unicode(255), default='/') base_url = Column(Unicode(255), default='/')
cookie_name = Column(Unicode(255), default='cookie') cookie_name = Column(Unicode(255), default='cookie')
# added to handle multi-server feature # added to handle multi-server feature
last_activity = Column(DateTime, default=datetime.utcnow) last_activity = Column(DateTime, default=datetime.utcnow)
@@ -101,10 +98,8 @@ class Server(Base):
@property @property
def bind_url(self): def bind_url(self):
"""representation of URL used for binding """representation of URL used for binding
Never used in APIs, only logging, Never used in APIs, only logging,
since it can be non-connectable value, such as '', since it can be non-connectable value, such as '', meaning all interfaces.
meaning all interfaces.
""" """
if self.ip in {'', '0.0.0.0'}: if self.ip in {'', '0.0.0.0'}:
return self.url.replace('127.0.0.1', self.ip or '*', 1) return self.url.replace('127.0.0.1', self.ip or '*', 1)
@@ -116,8 +111,7 @@ class Server(Base):
if http: if http:
yield wait_for_http_server(self.url, timeout=timeout) yield wait_for_http_server(self.url, timeout=timeout)
else: else:
yield wait_for_server(self.ip or '127.0.0.1', self.port, yield wait_for_server(self.ip or '127.0.0.1', self.port, timeout=timeout)
timeout=timeout)
def is_up(self): def is_up(self):
"""Is the server accepting connections?""" """Is the server accepting connections?"""
@@ -126,7 +120,6 @@ class Server(Base):
class Proxy(Base): class Proxy(Base):
"""A configurable-http-proxy instance. """A configurable-http-proxy instance.
A proxy consists of the API server info and the public-facing server info, A proxy consists of the API server info and the public-facing server info,
plus an auth token for configuring the proxy table. plus an auth token for configuring the proxy table.
""" """
@@ -191,9 +184,9 @@ class Proxy(Base):
client=client, client=client,
) )
@gen.coroutine
# FIX-ME # FIX-ME
# we need to add a reference to a specific server # we need to add a reference to a specific server
@gen.coroutine
def add_user(self, user, client=None): def add_user(self, user, client=None):
"""Add a user's server to the proxy table.""" """Add a user's server to the proxy table."""
self.log.info("Adding user %s to proxy %s => %s", self.log.info("Adding user %s to proxy %s => %s",
@@ -225,7 +218,6 @@ class Proxy(Base):
@gen.coroutine @gen.coroutine
def add_all_services(self, service_dict): def add_all_services(self, service_dict):
"""Update the proxy table from the database. """Update the proxy table from the database.
Used when loading up a new proxy. Used when loading up a new proxy.
""" """
db = inspect(self).session db = inspect(self).session
@@ -241,7 +233,6 @@ class Proxy(Base):
@gen.coroutine @gen.coroutine
def add_all_users(self, user_dict): def add_all_users(self, user_dict):
"""Update the proxy table from the database. """Update the proxy table from the database.
Used when loading up a new proxy. Used when loading up a new proxy.
""" """
db = inspect(self).session db = inspect(self).session
@@ -299,11 +290,10 @@ class Proxy(Base):
yield f yield f
class Hub(Base): class Hub(Base):
"""Bring it all together at the hub. """Bring it all together at the hub.
The Hub is a server, plus its API path suffix The Hub is a server, plus its API path suffix
the api_url is the full URL plus the api_path suffix on the end the api_url is the full URL plus the api_path suffix on the end
of the server base_url. of the server base_url.
""" """
@@ -331,8 +321,7 @@ class Hub(Base):
user_group_map = Table('user_group_map', Base.metadata, user_group_map = Table('user_group_map', Base.metadata,
Column('user_id', ForeignKey('users.id'), primary_key=True), Column('user_id', ForeignKey('users.id'), primary_key=True),
Column('group_id', ForeignKey('groups.id'), primary_key=True), Column('group_id', ForeignKey('groups.id'), primary_key=True),
) )
class Group(Base): class Group(Base):
"""User Groups""" """User Groups"""
@@ -345,47 +334,41 @@ class Group(Base):
return "<%s %s (%i users)>" % ( return "<%s %s (%i users)>" % (
self.__class__.__name__, self.name, len(self.users) self.__class__.__name__, self.name, len(self.users)
) )
@classmethod @classmethod
def find(cls, db, name): def find(cls, db, name):
"""Find a group by name. """Find a group by name.
Returns None if not found. Returns None if not found.
""" """
return db.query(cls).filter(cls.name == name).first() return db.query(cls).filter(cls.name==name).first()
class User(Base): class User(Base):
"""The User table """The User table
Each user can have more than a single server,
Each user can have more than one server,
and multiple tokens used for authorization. and multiple tokens used for authorization.
API tokens grant access to the Hub's REST API. API tokens grant access to the Hub's REST API.
These are used by single-user servers to authenticate requests, These are used by single-user servers to authenticate requests,
and external services to manipulate the Hub. and external services to manipulate the Hub.
Cookies are set with a single ID. Cookies are set with a single ID.
Resetting the Cookie ID invalidates all cookies, forcing user to login again. Resetting the Cookie ID invalidates all cookies, forcing user to login again.
`server` returns the first entry for the users' servers.
A `state` column contains a JSON dict, A `state` column contains a JSON dict,
used for restoring state of a Spawner. used for restoring state of a Spawner.
`server` returns the first entry for the users' servers.
'servers' is a list that contains a reference to the ser's Servers.
""" """
__tablename__ = 'users' __tablename__ = 'users'
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Unicode(1023), unique=True) name = Column(Unicode(1023), unique=True)
admin = Column(Boolean, default=False)
last_activity = Column(DateTime, default=datetime.utcnow)
servers = association_proxy("user_to_servers", "server", creator=lambda server: UserServer(server=server)) servers = association_proxy("user_to_servers", "server", creator=lambda server: UserServer(server=server))
admin = Column(Boolean, default=False)
last_activity = Column(DateTime, default=datetime.utcnow)
api_tokens = relationship("APIToken", backref="user") api_tokens = relationship("APIToken", backref="user")
cookie_id = Column(Unicode(1023), default=new_token) cookie_id = Column(Unicode(1023), default=new_token)
# User.state is actually Spawner state # User.state is actually Spawner state
# We will need to figure something else # We will need to figure something else out if/when we have multiple spawners per user
# out if/when we have multiple spawners per user
state = Column(JSONDict) state = Column(JSONDict)
# Authenticators can store their state here: # Authenticators can store their state here:
auth_state = Column(JSONDict) auth_state = Column(JSONDict)
@@ -397,7 +380,6 @@ class User(Base):
@property @property
def server(self): def server(self):
"""Returns the first element of servers. """Returns the first element of servers.
Returns None if the list is empty. Returns None if the list is empty.
""" """
if len(self.servers) == 0: if len(self.servers) == 0:
@@ -429,24 +411,19 @@ class User(Base):
@classmethod @classmethod
def find(cls, db, name): def find(cls, db, name):
"""Find a user by name. """Find a user by name.
Returns None if not found. Returns None if not found.
""" """
return db.query(cls).filter(cls.name == name).first() return db.query(cls).filter(cls.name==name).first()
class UserServer(Base): class UserServer(Base):
"""The UserServer table """The UserServer table
Each user can have have more than one server, Each user can have have more than one server,
we use this table to mantain the Many-To-Many we use this table to mantain the Many-To-One
relationship between Users and Servers. relationship between Users and Servers tables.
Cookies are set with a single ID. Servers can have only 1 user, this condition is mantained
Resetting the Cookie ID invalidates all cookies, forcing user to login again. by UniqueConstraint
A `state` column contains a JSON dict,
used for restoring state of a Spawner.
""" """
__tablename__ = 'users_servers' __tablename__ = 'users_servers'
@@ -454,9 +431,12 @@ class UserServer(Base):
_server_id = Column(Integer, ForeignKey('servers.id'), primary_key=True) _server_id = Column(Integer, ForeignKey('servers.id'), primary_key=True)
user = relationship(User, backref=backref('user_to_servers', cascade='all, delete-orphan')) user = relationship(User, backref=backref('user_to_servers', cascade='all, delete-orphan'))
server = relationship(Server, backref=backref('server_to_users', cascade='all, delete-orphan')) server = relationship(Server, backref=backref('server_to_users', cascade='all, delete-orphan')
)
__table_args__ = (Index('server_user_index', '_server_id', '_user_id'),) __table_args__ = (
UniqueConstraint('_server_id'),
Index('server_user_index', '_server_id', '_user_id'),)
def __repr__(self): def __repr__(self):
return "<{cls}({name}@{ip}:{port})>".format( return "<{cls}({name}@{ip}:{port})>".format(
@@ -469,21 +449,15 @@ class UserServer(Base):
class Service(Base): class Service(Base):
"""A service run with JupyterHub """A service run with JupyterHub
A service is similar to a User without a Spawner. A service is similar to a User without a Spawner.
A service can have API tokens for accessing the Hub's API A service can have API tokens for accessing the Hub's API
It has: It has:
- name - name
- admin - admin
- api tokens - api tokens
- server (if proxied http endpoint) - server (if proxied http endpoint)
In addition to what it has in common with users, a Service has extra info: In addition to what it has in common with users, a Service has extra info:
- pid: the process id (if managed) - pid: the process id (if managed)
""" """
__tablename__ = 'services' __tablename__ = 'services'
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
@@ -501,7 +475,6 @@ class Service(Base):
def new_api_token(self, token=None): def new_api_token(self, token=None):
"""Create a new API token """Create a new API token
If `token` is given, load that token. If `token` is given, load that token.
""" """
return APIToken.new(token=token, service=self) return APIToken.new(token=token, service=self)
@@ -509,7 +482,6 @@ class Service(Base):
@classmethod @classmethod
def find(cls, db, name): def find(cls, db, name):
"""Find a service by name. """Find a service by name.
Returns None if not found. Returns None if not found.
""" """
return db.query(cls).filter(cls.name==name).first() return db.query(cls).filter(cls.name==name).first()
@@ -567,7 +539,6 @@ class APIToken(Base):
@classmethod @classmethod
def find(cls, db, token, *, kind=None): def find(cls, db, token, *, kind=None):
"""Find a token object by value. """Find a token object by value.
Returns None if not found. Returns None if not found.
`kind='user'` only returns API tokens for users `kind='user'` only returns API tokens for users

View File

@@ -30,8 +30,8 @@ def db():
_db = orm.new_session_factory('sqlite:///:memory:', echo=True)() _db = orm.new_session_factory('sqlite:///:memory:', echo=True)()
user = orm.User( user = orm.User(
name=getuser(), name=getuser(),
server=orm.Server(),
) )
user.servers.append(orm.Server())
hub = orm.Hub( hub = orm.Hub(
server=orm.Server(), server=orm.Server(),
) )

View File

@@ -198,12 +198,12 @@ class User(HasTraits):
def spawn(self, options=None): def spawn(self, options=None):
"""Start the user's spawner""" """Start the user's spawner"""
db = self.db db = self.db
server = orm.Server(
self.server = orm.Server(
cookie_name=self.cookie_name, cookie_name=self.cookie_name,
base_url=self.base_url, base_url=self.base_url,
) )
db.add(self.server) self.servers.append(server)
db.add(self)
db.commit() db.commit()
api_token = self.new_api_token() api_token = self.new_api_token()
@@ -316,7 +316,8 @@ class User(HasTraits):
orm_token = orm.APIToken.find(self.db, api_token) orm_token = orm.APIToken.find(self.db, api_token)
if orm_token: if orm_token:
self.db.delete(orm_token) self.db.delete(orm_token)
self.server = None for server in self.servers:
self.db.delete(server)
self.db.commit() self.db.commit()
finally: finally:
self.stop_pending = False self.stop_pending = False
@@ -326,4 +327,3 @@ class User(HasTraits):
yield gen.maybe_future( yield gen.maybe_future(
auth.post_spawn_stop(self, spawner) auth.post_spawn_stop(self, spawner)
) )