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 async_generator import aclosing
|
||||||
from dateutil.parser import parse as parse_date
|
from dateutil.parser import parse as parse_date
|
||||||
from sqlalchemy import func, or_
|
from sqlalchemy import func, or_
|
||||||
|
from sqlalchemy.orm import raiseload, selectinload
|
||||||
from tornado import web
|
from tornado import web
|
||||||
from tornado.iostream import StreamClosedError
|
from tornado.iostream import StreamClosedError
|
||||||
|
|
||||||
@@ -90,6 +91,10 @@ class UserListAPIHandler(APIHandler):
|
|||||||
# post_filter
|
# post_filter
|
||||||
post_filter = None
|
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"}:
|
if state_filter in {"active", "ready"}:
|
||||||
# only get users with active servers
|
# only get users with active servers
|
||||||
# an 'active' Spawner has a server record in the database
|
# 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.
|
# it may still be in a pending start/stop state.
|
||||||
# join filters out users with no Spawners
|
# join filters out users with no Spawners
|
||||||
query = (
|
query = (
|
||||||
self.db.query(orm.User)
|
query
|
||||||
# join filters out any Users with no Spawners
|
# 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
|
# this implicitly gets Users with *any* active server
|
||||||
.filter(orm.Spawner.server != None)
|
.filter(orm.Spawner.server != None)
|
||||||
)
|
)
|
||||||
@@ -114,9 +119,8 @@ class UserListAPIHandler(APIHandler):
|
|||||||
# this is the complement to the above query.
|
# this is the complement to the above query.
|
||||||
# how expensive is this with lots of servers?
|
# how expensive is this with lots of servers?
|
||||||
query = (
|
query = (
|
||||||
self.db.query(orm.User)
|
query.outerjoin(orm.Spawner, orm.User._orm_spawners)
|
||||||
.outerjoin(orm.Spawner)
|
.outerjoin(orm.Server, orm.Spawner.server)
|
||||||
.outerjoin(orm.Server)
|
|
||||||
.group_by(orm.User.id)
|
.group_by(orm.User.id)
|
||||||
.having(func.count(orm.Server.id) == 0)
|
.having(func.count(orm.Server.id) == 0)
|
||||||
)
|
)
|
||||||
@@ -124,7 +128,19 @@ class UserListAPIHandler(APIHandler):
|
|||||||
raise web.HTTPError(400, "Unrecognized state filter: %r" % state_filter)
|
raise web.HTTPError(400, "Unrecognized state filter: %r" % state_filter)
|
||||||
else:
|
else:
|
||||||
# no filter, return all users
|
# 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']
|
sub_scope = self.parsed_scopes['list:users']
|
||||||
if sub_scope != scopes.Scope.ALL:
|
if sub_scope != scopes.Scope.ALL:
|
||||||
|
@@ -263,7 +263,10 @@ class User(Base):
|
|||||||
name = Column(Unicode(255), unique=True)
|
name = Column(Unicode(255), unique=True)
|
||||||
|
|
||||||
roles = relationship(
|
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(
|
_orm_spawners = relationship(
|
||||||
@@ -285,6 +288,7 @@ class User(Base):
|
|||||||
"Group",
|
"Group",
|
||||||
secondary='user_group_map',
|
secondary='user_group_map',
|
||||||
back_populates="users",
|
back_populates="users",
|
||||||
|
lazy="selectin",
|
||||||
)
|
)
|
||||||
oauth_codes = relationship(
|
oauth_codes = relationship(
|
||||||
"OAuthCode", back_populates="user", cascade="all, delete-orphan"
|
"OAuthCode", back_populates="user", cascade="all, delete-orphan"
|
||||||
|
Reference in New Issue
Block a user