Skip to content
This repository was archived by the owner on Feb 21, 2026. It is now read-only.

Commit b104430

Browse files
authored
Merge pull request #142 from mpacer/client_fix
Fixes client endpoints and adds docstrings
2 parents 73211a9 + 2e9ea82 commit b104430

File tree

2 files changed

+140
-45
lines changed

2 files changed

+140
-45
lines changed

bookstore/client/nb_client.py

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,4 @@
1-
"""Client to test bookstore endpoints from within a notebook.
2-
3-
4-
TODO: (Clarify) We want to test our bookstore endpoints, but it's no fun having
5-
to do this in an insecure fashion. Better would be to have some security in
6-
place.
7-
8-
Example
9-
-------
10-
11-
[{'base_url': '/',
12-
'hostname': 'localhost',
13-
'notebook_dir': '/Users/mpacer/jupyter/eg_notebooks',
14-
'password': False,
15-
'pid': 96033,
16-
'port': 8888,
17-
'secure': False,
18-
'token': '',
19-
'url': 'http://localhost:8888/'}]
1+
"""Client for accessing notebook server endpoints from within a notebook.
202
"""
213
import json
224
import os
@@ -42,6 +24,21 @@ class LiveNotebookRecord(NamedTuple):
4224
4325
This is a record of an object returned by
4426
`notebook.notebookapp.list_running_servers()`.
27+
28+
Example
29+
-------
30+
::
31+
32+
[{'base_url': '/',
33+
'hostname': 'localhost',
34+
'notebook_dir': '/Users/mpacer/jupyter/eg_notebooks',
35+
'password': False,
36+
'pid': 96033,
37+
'port': 8888,
38+
'secure': False,
39+
'token': '',
40+
'url': 'http://localhost:8888/'}]
41+
4542
"""
4643

4744
base_url: str
@@ -56,11 +53,27 @@ class LiveNotebookRecord(NamedTuple):
5653

5754

5855
class KernelInfo:
59-
# id: str # 'f92b7c8b-0858-4d10-903c-b0631540fb36',
60-
# name: str # 'dev',
61-
# last_activity: str #'2019-03-14T23:38:08.137987Z',
62-
# execution_state: str #'idle',
63-
# connections: int # 0
56+
"""Representation of kernel info returned by the notebook's /api/kernel endpoint.
57+
58+
Attributes
59+
----------
60+
id: str
61+
name: str
62+
last_activity: str
63+
execution_state: str
64+
connections: int
65+
66+
Example
67+
-------
68+
::
69+
70+
{id: 'f92b7c8b-0858-4d10-903c-b0631540fb36',
71+
name: 'dev',
72+
last_activity: '2019-03-14T23:38:08.137987Z',
73+
execution_state: 'idle',
74+
connections: 0}
75+
"""
76+
6477
def __init__(self, *args, id, name, last_activity, execution_state, connections):
6578
self.model = {
6679
"id": id,
@@ -92,13 +105,35 @@ def __eq__(self, other):
92105
return False
93106

94107

95-
class NotebookSession: # (NamedTuple):
96-
# id: str #'68d9c58f-c57d-4133-8b41-5ec2731b268d',
97-
# path: str #'Untitled38.ipynb',
98-
# name: str #'',
99-
# type: str #'notebook',
100-
# kernel: KernelInfo
101-
# notebook: dict # deprecated API {'path': 'Untitled38.ipynb', 'name': ''}
108+
class NotebookSession:
109+
"""Representation of session info returned by the notebook's /api/sessions/ endpoint.
110+
111+
Attributes
112+
----------
113+
id: str
114+
path: str
115+
name: str
116+
type: str
117+
kernel: KernelInfo
118+
notebook: dict
119+
model: dict
120+
Record of the raw response (without converting the KernelInfo).
121+
122+
Example
123+
-------
124+
::
125+
126+
{id: '68d9c58f-c57d-4133-8b41-5ec2731b268d',
127+
path: 'Untitled38.ipynb',
128+
name: '',
129+
type: 'notebook',
130+
kernel: KernelInfo(id='f92b7c8b-0858-4d10-903c-b0631540fb36',
131+
name='dev',
132+
last_activity='2019-03-14T23:38:08.137987Z',
133+
execution_state='idle',
134+
connections=0),
135+
notebook: {'path': 'Untitled38.ipynb', 'name': ''}}
136+
"""
102137

103138
def __init__(self, *args, path, name, type, kernel, notebook={}, **kwargs):
104139
self.model = {
@@ -133,7 +168,28 @@ def __eq__(self, other):
133168

134169

135170
class NotebookClient:
136-
"""Client used to interact with bookstore from within a running notebook UI"""
171+
"""EXPERIMENTAL SUPPORT: Client used to interact with a notebook server from within a notebook.
172+
173+
Parameters
174+
----------
175+
nb_config: dict
176+
Dictionary of info compatible with creating a LiveNotebookRecord.
177+
178+
Attributes
179+
----------
180+
nb_config: dict
181+
Dictionary of info compatible with creating a LiveNotebookRecord.
182+
nb_record: LiveNotebookRecord
183+
LiveNotebookRecord of info for this notebook
184+
url: str
185+
url from nb_record minus final /
186+
token: str
187+
token used for authenticating requests serverside
188+
xsrf_token: str
189+
xsrf_token used in cookie for authenticating requests
190+
req_session: requests.Session
191+
Session to be reused across methods
192+
"""
137193

138194
def __init__(self, nb_config):
139195
self.nb_config = nb_config
@@ -154,23 +210,20 @@ def setup_auth(self):
154210
self.xsrf_token = first.cookies.get("_xsrf", "")
155211

156212
def setup_request_sessions(self):
157-
""" Sets up a requests.Session object for sharing headers across API requests.
158-
"""
213+
""" Sets up a requests.Session object for sharing headers across API requests. """
159214
self.req_session = requests.Session()
160215
self.req_session.headers.update(self.headers)
161216

162217
@property
163218
def sessions(self):
164-
"""Current notebook sessions. Reissues request on each call.
165-
"""
219+
"""Current notebook sessions. Reissues request on each call. """
166220
return {
167221
session['kernel']['id']: NotebookSession(**session) for session in self.get_sessions()
168222
}
169223

170224
@property
171225
def headers(self):
172-
"""Default headers to be shared across requests.
173-
"""
226+
"""Default headers to be shared across requests. """
174227
headers = {
175228
'Authorization': f'token {self.token}',
176229
'X-XSRFToken': self.xsrf_token,
@@ -180,41 +233,48 @@ def headers(self):
180233

181234
@property
182235
def sessions_endpoint(self):
236+
"""Current server's kernels API endpoint."""
183237
api_endpoint = "/api/sessions/"
184238
return f"{self.url}{api_endpoint}"
185239

186240
def get_sessions(self):
241+
"""Requests info about current sessions from notebook server."""
187242
target_url = f"{self.sessions_endpoint}"
188243
resp = self.req_session.get(target_url)
189244
return resp.json()
190245

191246
@property
192247
def kernels_endpoint(self):
248+
"""Current server's kernels API endpoint."""
193249
api_endpoint = "/api/kernels/"
194250
return f"{self.url}{api_endpoint}"
195251

196252
def get_kernels(self):
253+
"""Requests info about current kernels from notebook server."""
197254
target_url = f"{self.kernels_endpoint}"
198255
resp = self.req_session.get(target_url)
199256
return resp.json()
200257

201258
@property
202259
def kernels(self):
260+
"""Current notebook kernels. Reissues request on each call."""
203261
return self.get_kernels()
204262

205263
@property
206264
def contents_endpoint(self):
265+
"""Current server's contents API endpoint."""
207266
api_endpoint = "/api/contents/"
208267
return f"{self.url}{api_endpoint}"
209268

210269
def get_contents(self, path):
270+
"""Requests info about current contents from notebook server."""
211271
target_url = f"{self.contents_endpoint}{path}"
212272
resp = self.req_session.get(target_url)
213273
return resp.json()
214274

215275

216276
class NotebookClientCollection:
217-
"""Representation of a collection of notebook clients"""
277+
"""EXPERIMENTAL SUPPORT: Representation of a collection of notebook clients"""
218278

219279
# TODO: refactor from lambda to a def
220280
nb_client_gen = lambda: (NotebookClient(x) for x in list_running_servers())
@@ -234,7 +294,7 @@ def current_server(cls):
234294

235295

236296
class CurrentNotebookClient(NotebookClient):
237-
"""Represents the currently active notebook client"""
297+
"""EXPERIMENTAL SUPPORT: Represents the currently active notebook client."""
238298

239299
def __init__(self):
240300
self.nb_client = NotebookClientCollection.current_server()
@@ -243,8 +303,10 @@ def __init__(self):
243303

244304
@property
245305
def connection_file(self):
306+
"""Connection file for connecting to current notebook's kernel."""
246307
return get_ipython().parent.parent.connection_file
247308

248309
@property
249310
def kernel_id(self):
311+
"""Kernel id for identifying which notebook is currently being used by this session."""
250312
return extract_kernel_id(self.connection_file)

bookstore/client/store_client.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
"""Client to interact with the notebook store"""
1+
"""Client to interact with a notebook's bookstore functionality."""
22
import requests
33

44
from .nb_client import CurrentNotebookClient
55

66

77
class BookstoreClient(CurrentNotebookClient):
8-
"""Represents a bookstore client that corresponds to the active nb client"""
8+
"""EXPERIMENTAL SUPPORT: A client that allows access to a Bookstore from within a notebook.
9+
10+
Parameters
11+
----------
12+
s3_bucket: str
13+
(optional) Provide a default bucket for this bookstore client to clone from.
14+
15+
16+
Attributes
17+
----------
18+
default_bucket : str
19+
The default bucket to be used for cloning.
20+
"""
921

1022
def __init__(self, s3_bucket=None):
1123
if s3_bucket:
@@ -14,11 +26,20 @@ def __init__(self, s3_bucket=None):
1426

1527
@property
1628
def publish_endpoint(self):
17-
api_endpoint = "/api/bookstore/published/"
29+
"""Helper to refer to construct the publish endpoint for this notebook server."""
30+
api_endpoint = "/api/bookstore/publish/"
1831
return f"{self.url}{api_endpoint}"
1932

2033
def publish(self, path=None):
21-
"""Publish notebook to bookstore"""
34+
"""Publish notebook to bookstore
35+
36+
Parameters
37+
----------
38+
path : str
39+
(optional) Path that you wish to publish; defaults to current notebook.
40+
s3_object_key: str
41+
The the path we wish to clone to.
42+
"""
2243
if path is None:
2344
path = self.session.path
2445
nb_json = self.get_contents(path)['content']
@@ -31,10 +52,22 @@ def publish(self, path=None):
3152

3253
@property
3354
def clone_endpoint(self):
34-
api_endpoint = "/api/bookstore/cloned/"
55+
"""Helper to refer to construct the clone endpoint for this notebook server."""
56+
api_endpoint = "/api/bookstore/clone/"
3557
return f"{self.url}{api_endpoint}"
3658

3759
def clone(self, s3_bucket="", s3_key="", target_path=""):
60+
"""Clone files via bookstore.
61+
62+
Parameters
63+
----------
64+
s3_bucket : str
65+
(optional) S3 bucket you wish to clone from; defaults to client's bucket.
66+
s3_object_key: str
67+
The object key describing the object you wish to clone from S3.
68+
target_path: str
69+
(optional) The location you wish to clone the object to; defaults to s3_object_key.
70+
"""
3871
s3_bucket = s3_bucket or self.default_bucket
3972
json_body = {"s3_bucket": s3_bucket, "s3_key": s3_key, "target_path": target_path}
4073
# TODO: Add a check for success

0 commit comments

Comments
 (0)