As asked on the Jupyter forum:
Using the Rest api is there a way to check on whether or not a user has a non named notebook server running
import requests
# an admin service token added via config
# or a token owned by the user
api_token = "fb605a38c7ed4056b2280ee9bdb25258"
s = requests.Session()
s.headers["Authorization"] = f"token {api_token}"
username = "test-1"
hub_host = "http://localhost:8765"
hub_api = f"{hub_host}/hub/api"
user_url = f"{hub_api}/users/{username}"
server_url = f"{hub_api}/users/{username}/server"
The user model has a servers
dict of 'active' servers.
'active' means it is not stopped, i.e. it can be 'ready' or pending a transition from stopped to running or running to stopped.
The first state is: If a server name is not present in the dict, it is definitely not running.
Here we get the user model for a user who has no running servers.
The servers
dict is empty.
r = s.get(user_url)
r.json()
{'kind': 'user', 'name': 'test-1', 'admin': False, 'groups': [], 'server': None, 'pending': None, 'created': '2021-02-17T08:41:53.146076Z', 'last_activity': '2021-02-17T09:25:25.506361Z', 'servers': {}}
If we start the server and check again immediately, the server will be in a "pending spawn" state, which is in transition from stopped to running.
Note the following state:
servers
dict has a server record with key ''
. The empty string is the default server name,
i.e. the server at /user/{name}
.'pending': 'spawn'
indicating that it is pending a spawn action'ready': False
indicating that it is not readyr = s.post(server_url)
r.raise_for_status()
r = s.get(user_url)
r.json()
{'kind': 'user', 'name': 'test-1', 'admin': False, 'groups': [], 'server': None, 'pending': 'spawn', 'created': '2021-02-17T08:41:53.146076Z', 'last_activity': '2021-02-17T09:25:25.568339Z', 'servers': {'': {'name': '', 'last_activity': '2021-02-17T09:25:25.568339Z', 'started': '2021-02-17T09:25:25.568339Z', 'pending': 'spawn', 'ready': False, 'state': None, 'url': '/user/test-1/', 'user_options': {}, 'progress_url': '/hub/api/users/test-1/server/progress'}}}
We can wait for the server to be ready by following the progress event stream. When this finishes, the server will be ready.
import json
from pprint import pprint
progress_url = hub_host + r.json()["servers"][""]["progress_url"]
for line in s.get(progress_url, stream=True).iter_lines():
line = line.decode("utf8")
if line.startswith("data:"):
rest = line.split(":", 1)[1]
event = json.loads(rest)
pprint(event)
{'message': 'Server requested', 'progress': 0} {'message': 'Spawning server...', 'progress': 50} {'html_message': 'Server ready at <a href="/user/test-1/">/user/test-1/</a>', 'message': 'Server ready at /user/test-1/', 'progress': 100, 'ready': True, 'url': '/user/test-1/'}
Now that the server is running and ready, we can check the user model again.
r = s.get(user_url)
r.json()
{'kind': 'user', 'name': 'test-1', 'admin': False, 'groups': [], 'server': '/user/test-1/', 'pending': None, 'created': '2021-02-17T08:41:53.146076Z', 'last_activity': '2021-02-17T09:25:26.775787Z', 'servers': {'': {'name': '', 'last_activity': '2021-02-17T09:25:26.775787Z', 'started': '2021-02-17T09:25:25.568339Z', 'pending': None, 'ready': True, 'state': None, 'url': '/user/test-1/', 'user_options': {}, 'progress_url': '/hub/api/users/test-1/server/progress'}}}
We can do the same process to stop the server
s.delete(server_url)
r = s.get(user_url)
r.json()
{'kind': 'user', 'name': 'test-1', 'admin': False, 'groups': [], 'server': None, 'pending': None, 'created': '2021-02-17T08:41:53.146076Z', 'last_activity': '2021-02-17T09:25:29.302282Z', 'servers': {}}
To summarize:
""
is the key for the default server (no name)ready
boolean field that is True if the server is running and ready to handle requests, False otherwisepending
field that is None if no transitions are pending, or a string describing which transition is pending (i.e. 'spawn' or 'stop').What you do with pending
will depend on why you are asking.
Most API requests on a server will raise 400 if you try to act on a server that's pending some transition, so you may need to wait if you are asking because you want to do something like start a server if it's not already running.
If a server is ready or pending == 'spawn'
, it's safe to redirect users to the server's URL
because JupyterHub will send them to the spawn-pending page and ultimately redirect when it's ready.
If you have a reason to wait for a server to finish a pending spawn via the API, the progress event stream API works.
A server pending 'stop' can't be visited and can't be restarted until the stop event is complete.
To retrieve the current status of the user's default server or any named server:
username = "test-1"
servername = "" # empty for default, or pick any name
user_model = s.get(f"{hub_api}/users/{username}").json()
server = user_model["servers"].get(servername, {})
if server:
pending = server["pending"]
ready = server["ready"]
# server.url is a *path*, does not include https://host unless user-subdomains are used
url = server["url"]
else:
# no server field, not running, nothing pending
pending = None
ready = False
url = None
if ready:
print(f"{username}/{servername} running and ready at {url}")
elif pending:
print(f"{username}/{servername} pending {pending}")
else:
print(f"{username}/{servername} not running")
test-1/ not running