mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-15 05:53:00 +00:00
reduce repeat queries in GET /api/users
add eager loading of several relationships that are ~always used when the given objects are requested add specific eager loading of spawners to the users query - roles, groups (always needed to resolve permissions) - APIToken.user, service
This commit is contained in:
@@ -9,6 +9,7 @@ from datetime import datetime, timedelta, timezone
|
||||
from async_generator import aclosing
|
||||
from dateutil.parser import parse as parse_date
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import raiseload, selectinload
|
||||
from tornado import web
|
||||
from tornado.iostream import StreamClosedError
|
||||
|
||||
@@ -90,6 +91,10 @@ class UserListAPIHandler(APIHandler):
|
||||
# post_filter
|
||||
post_filter = None
|
||||
|
||||
# starting query
|
||||
# fetch users and groups, which will be used for filters
|
||||
query = self.db.query(orm.User).outerjoin(orm.Group, orm.User.groups)
|
||||
|
||||
if state_filter in {"active", "ready"}:
|
||||
# only get users with active servers
|
||||
# an 'active' Spawner has a server record in the database
|
||||
@@ -97,9 +102,9 @@ class UserListAPIHandler(APIHandler):
|
||||
# it may still be in a pending start/stop state.
|
||||
# join filters out users with no Spawners
|
||||
query = (
|
||||
self.db.query(orm.User)
|
||||
query
|
||||
# join filters out any Users with no Spawners
|
||||
.join(orm.Spawner)
|
||||
.join(orm.Spawner, orm.User._orm_spawners)
|
||||
# this implicitly gets Users with *any* active server
|
||||
.filter(orm.Spawner.server != None)
|
||||
)
|
||||
@@ -114,9 +119,8 @@ class UserListAPIHandler(APIHandler):
|
||||
# this is the complement to the above query.
|
||||
# how expensive is this with lots of servers?
|
||||
query = (
|
||||
self.db.query(orm.User)
|
||||
.outerjoin(orm.Spawner)
|
||||
.outerjoin(orm.Server)
|
||||
query.outerjoin(orm.Spawner, orm.User._orm_spawners)
|
||||
.outerjoin(orm.Server, orm.Spawner.server)
|
||||
.group_by(orm.User.id)
|
||||
.having(func.count(orm.Server.id) == 0)
|
||||
)
|
||||
@@ -124,7 +128,19 @@ class UserListAPIHandler(APIHandler):
|
||||
raise web.HTTPError(400, "Unrecognized state filter: %r" % state_filter)
|
||||
else:
|
||||
# no filter, return all users
|
||||
query = self.db.query(orm.User)
|
||||
query = query.outerjoin(orm.Spawner, orm.User._orm_spawners).outerjoin(
|
||||
orm.Server, orm.Spawner.server
|
||||
)
|
||||
|
||||
# apply joinedload options
|
||||
query = query.options(
|
||||
selectinload(orm.User.groups),
|
||||
selectinload(orm.User._orm_spawners),
|
||||
)
|
||||
# if testing, add raiseload to prevent lazy loading of anything we didn't ask for
|
||||
if True:
|
||||
# FIXME: detect tests
|
||||
query = query.options(raiseload("*"))
|
||||
|
||||
sub_scope = self.parsed_scopes['list:users']
|
||||
if sub_scope != scopes.Scope.ALL:
|
||||
|
@@ -263,7 +263,10 @@ class User(Base):
|
||||
name = Column(Unicode(255), unique=True)
|
||||
|
||||
roles = relationship(
|
||||
'Role', secondary='user_role_map', back_populates='users', lazy="selectin"
|
||||
'Role',
|
||||
secondary='user_role_map',
|
||||
back_populates='users',
|
||||
lazy="selectin",
|
||||
)
|
||||
|
||||
_orm_spawners = relationship(
|
||||
@@ -285,6 +288,7 @@ class User(Base):
|
||||
"Group",
|
||||
secondary='user_group_map',
|
||||
back_populates="users",
|
||||
lazy="selectin",
|
||||
)
|
||||
oauth_codes = relationship(
|
||||
"OAuthCode", back_populates="user", cascade="all, delete-orphan"
|
||||
|
Reference in New Issue
Block a user