From 1734b75d47887e3d74c347523b51b67b10c13612 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 28 Jun 2023 15:32:50 +0200 Subject: [PATCH] avoid instantiating User object wrapper in user_model --- jupyterhub/apihandlers/base.py | 33 +++++++++++++++++++++++++++------ jupyterhub/apihandlers/users.py | 7 ++++--- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/jupyterhub/apihandlers/base.py b/jupyterhub/apihandlers/base.py index 6bdddc90..a35e9793 100644 --- a/jupyterhub/apihandlers/base.py +++ b/jupyterhub/apihandlers/base.py @@ -268,9 +268,30 @@ class APIHandler(BaseHandler): return self._include_stopped_servers def user_model(self, user): - """Get the JSON model for a User object""" + """Get the JSON model for a User object + + User may be either a high-level User wrapper, + or a low-level orm.User. + """ + is_orm = False if isinstance(user, orm.User): - user = self.users[user.id] + if user.id in self.users: + # if it's an 'active' user, it's in the users dict, + # get the wrapper so we can get 'pending' state, etc. + user = self.users[user.id] + else: + # don't create wrapper of low-level orm object + is_orm = True + + if is_orm: + # if it's not in the users dict, + # we know it has no running servers + running = False + spawners = {} + if not is_orm: + running = user.running + spawners = user.spawners + include_stopped_servers = self.include_stopped_servers # TODO: we shouldn't fetch fields we can't read and then filter them out, # which may be wasted database queries @@ -283,7 +304,7 @@ class APIHandler(BaseHandler): 'admin': user.admin, 'roles': [r.name for r in user.roles], 'groups': [g.name for g in user.groups], - 'server': user.url if user.running else None, + 'server': user.url if running else None, 'pending': None, 'created': isoformat(user.created), 'last_activity': isoformat(user.last_activity), @@ -313,12 +334,12 @@ class APIHandler(BaseHandler): model, access_map, user, kind='user', keys=allowed_keys ) if model: - if '' in user.spawners and 'pending' in allowed_keys: - model['pending'] = user.spawners[''].pending + if '' in spawners and 'pending' in allowed_keys: + model['pending'] = spawners[''].pending servers = {} scope_filter = self.get_scope_filter('read:servers') - for name, spawner in user.spawners.items(): + for name, spawner in spawners.items(): # include 'active' servers, not just ready # (this includes pending events) if (spawner.active or include_stopped_servers) and scope_filter( diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py index 979418d5..dc96c2a8 100644 --- a/jupyterhub/apihandlers/users.py +++ b/jupyterhub/apihandlers/users.py @@ -9,7 +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 sqlalchemy.orm import joinedload, raiseload, selectinload from tornado import web from tornado.iostream import StreamClosedError @@ -132,10 +132,11 @@ class UserListAPIHandler(APIHandler): orm.Server, orm.Spawner.server ) - # apply joinedload options + # apply eager load options query = query.options( + selectinload(orm.User.roles), selectinload(orm.User.groups), - selectinload(orm.User._orm_spawners), + joinedload(orm.User._orm_spawners), ) # if testing, add raiseload to prevent lazy loading of anything we didn't ask for if True: