Skip to content

Commit 8bbba8f

Browse files
authored
Merge branch 'master' into hooks
2 parents 4928e6f + 0a32ff6 commit 8bbba8f

33 files changed

+667
-185
lines changed

CHANGELOG.md

+26-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
# Change Log
22

3+
## 2.3.0 [Unreleased](https://github.com/nteract/bookstore/compare/2.2.1...HEAD)
34

4-
[Unreleased](https://github.com/nteract/bookstore/compare/0.2.1...HEAD)
5+
[2.3.0 on Github](https://github.com/nteract/bookstore/releases/tag/2.3.0)
56

6-
[2.1.0](https://github.com/nteract/bookstore/releases/tag/2.1.0)
7+
### Significant changes
78

8-
[2.0.0](https://github.com/nteract/bookstore/releases/tag/2.0.0)
9+
Validation information is now exposed as a dict at the /api/bookstore endpoint.
910

10-
[0.1](https://github.com/nteract/bookstore/releases/tag/0.1)
11+
This allows us to distinguish whether different features have been enabled on bookstore.
12+
13+
The structure now is
14+
15+
```python
16+
validation_checks = {
17+
"bookstore_valid": all(general_settings),
18+
"archive_valid": all(archive_settings),
19+
"publish_valid": all(published_settings),
20+
}
21+
```
22+
## Releases prior to 2.3.0
23+
24+
[2.2.1 (2019-02-03)](https://github.com/nteract/bookstore/releases/tag/2.2.1)
25+
26+
[2.2.0 (2019-01-29)](https://github.com/nteract/bookstore/releases/tag/2.2.0)
27+
28+
[2.1.0 (2018-11-20)](https://github.com/nteract/bookstore/releases/tag/2.1.0)
29+
30+
[2.0.0 (2018-11-13)](https://github.com/nteract/bookstore/releases/tag/2.0.0)
31+
32+
[0.1 (2018=10-16)](https://github.com/nteract/bookstore/releases/tag/0.1)

CONTRIBUTING.md

+3-6
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,9 @@ in the nteract world? Here's a quick rundown!
3030

3131
5. Make the appropriate changes for the issue you are trying to address or the
3232
feature that you want to add.
33-
6. Confirm that unit tests and linting still pass successfully with:
34-
35-
yarn test
36-
37-
If tests fail, don't hesitate to ask for help.
38-
33+
6. You can run python unit tests using `pytest`. Running integration tests
34+
locally requires a more complicated setup. This setup is described in
35+
[running_ci_locally.md](./running_ci_locally.md)
3936
7. Add and commit the changed files using `git add` and `git commit`.
4037
8. Push the changes to the remote repository using:
4138

MANIFEST.in

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ include *.md
1515
include *.toml
1616

1717
include bookstore/_version.py
18+
include bookstore/clone.html
1819
include .coveragerc
1920

2021
# Documentation
@@ -24,4 +25,4 @@ prune docs/_build
2425
# exclude sample notebooks for binder
2526
prune binder/
2627
# Scripts
27-
graft scripts
28+
graft scripts

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,11 @@ c.BookstoreSettings.s3_bucket = "<bucket-name>"
9090
c.BookstoreSettings.s3_access_key_id = <AWS Access Key ID / IAM Access Key ID>
9191
c.BookstoreSettings.s3_secret_access_key = <AWS Secret Access Key / IAM Secret Access Key>
9292
```
93+
94+
## Developing
95+
96+
If you are developing on bookstore you will want to run the ci tests locally and to make releases.
97+
98+
Use [CONTRIBUTING.md](./CONTRIBUTING.md) to learn more about contributing.
99+
Use [running_ci_locally.md](./running_ci_locally.md) to learn more about running ci tests locally.
100+
Use [RELEASING.md](./RELEASING.md) to learn more about releasing bookstore.

bookstore/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import os
2+
3+
PACKAGE_DIR: str = os.path.realpath(os.path.dirname(__file__))
4+
5+
del os
6+
17
from .archive import BookstoreContentsArchiver
28
from .bookstore_config import BookstoreSettings
39
from .handlers import load_jupyter_server_extension

bookstore/client/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .bookstore import BookstoreClient

bookstore/client/bookstore.py

-21
This file was deleted.

bookstore/client/notebook.py bookstore/client/nb_client.py

+68-40
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
1-
# # Building a Notebook client
2-
#
3-
# We want to test our bookstore endpoints, but it's no fun having to do this in an insecure fashion. Better would be to have some security in place.
4-
#
5-
#
6-
# ## Example notebook config
7-
#
8-
#
9-
# ```
10-
# [{'base_url': '/',
11-
# 'hostname': 'localhost',
12-
# 'notebook_dir': '/Users/mpacer/jupyter/eg_notebooks',
13-
# 'password': False,
14-
# 'pid': 96033,
15-
# 'port': 8888,
16-
# 'secure': False,
17-
# 'token': '',
18-
# 'url': 'http://localhost:8888/'}]
19-
# ```
20-
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/'}]
20+
"""
2121
import os
22-
23-
import requests
24-
22+
import re
23+
import json
2524
from copy import deepcopy
2625
from typing import NamedTuple
2726

28-
27+
import requests
28+
from IPython import get_ipython
2929
from notebook.notebookapp import list_running_servers
3030

3131

3232
def extract_kernel_id(connection_file):
33-
return os.path.basename(connection_file).lstrip('kernel-').rstrip('.json')
33+
connection_filename = os.path.basename(connection_file)
34+
kernel_id = re.sub(r"kernel-(.*)\.json", r"\1", connection_filename)
35+
return kernel_id
3436

3537

3638
class LiveNotebookRecord(NamedTuple):
@@ -58,30 +60,52 @@ class KernelInfo:
5860
# execution_state: str #'idle',
5961
# connections: int # 0
6062
def __init__(self, *args, id, name, last_activity, execution_state, connections):
63+
self.model = {
64+
"id": id,
65+
"name": name,
66+
"last_activity": last_activity,
67+
"execution_state": execution_state,
68+
"connections": connections,
69+
}
6170
self.id = id
6271
self.name = name
6372
self.last_activity = last_activity
6473
self.execution_state = execution_state
6574
self.connections = connections
6675

76+
def __repr__(self):
77+
return json.dumps(self.model, indent=2)
78+
6779

6880
class NotebookSession: # (NamedTuple):
6981
# id: str #'68d9c58f-c57d-4133-8b41-5ec2731b268d',
7082
# path: str #'Untitled38.ipynb',
7183
# name: str #'',
7284
# type: str #'notebook',
7385
# kernel: KernelInfo
74-
# notebook: dict # {'path': 'Untitled38.ipynb', 'name': ''}}}
86+
# notebook: dict # deprecated API {'path': 'Untitled38.ipynb', 'name': ''}}}
7587

7688
def __init__(self, *args, path, name, type, kernel, notebook, **kwargs):
89+
self.model = {
90+
"path": path,
91+
"name": name,
92+
"type": type,
93+
"kernel": kernel,
94+
"notebook": notebook,
95+
}
7796
self.path = path
7897
self.name = name
7998
self.type = type
8099
self.kernel = KernelInfo(**kernel)
81100
self.notebook = notebook
82101

102+
def __repr__(self):
103+
return json.dumps(self.model, indent=2)
104+
83105

84106
class NotebookClient:
107+
"""Client used to interact with bookstore from within a running notebook UI"""
108+
85109
def __init__(self, nb_config):
86110
self.nb_config = nb_config
87111
self.nb_record = LiveNotebookRecord(**self.nb_config)
@@ -110,7 +134,9 @@ def setup_request_sessions(self):
110134
def sessions(self):
111135
"""Current notebook sessions. Reissues request on each call.
112136
"""
113-
return {session['kernel']['id']: session for session in self.get_sessions()}
137+
return {
138+
session['kernel']['id']: NotebookSession(**session) for session in self.get_sessions()
139+
}
114140

115141
@property
116142
def headers(self):
@@ -139,10 +165,14 @@ def kernels_endpoint(self):
139165
return f"{self.url}{api_endpoint}"
140166

141167
def get_kernels(self):
142-
target_url = f"{self.sessions_endpoint}"
168+
target_url = f"{self.kernels_endpoint}"
143169
resp = self.req_session.get(target_url)
144170
return resp.json()
145171

172+
@property
173+
def kernels(self):
174+
return self.get_kernels()
175+
146176
@property
147177
def contents_endpoint(self):
148178
api_endpoint = "/api/contents/"
@@ -154,40 +184,38 @@ def get_contents(self, path):
154184
return resp.json()
155185

156186

157-
def python_compat_session(session):
158-
deepcopy(session)
159-
160-
161187
class NotebookClientCollection:
188+
"""Representation of a collection of notebook clients"""
189+
190+
# TODO: refactor from lambda to a def
162191
nb_client_gen = lambda: (NotebookClient(x) for x in list_running_servers())
163192
sessions = {x.url: x.sessions for x in nb_client_gen()}
164193

165194
@classmethod
166195
def current_server(cls):
196+
"""class method for current notebook server"""
197+
198+
current_kernel_id = extract_kernel_id(get_ipython().parent.parent.connection_file)
167199
for server_url, session_dict in cls.sessions.items():
168200
for session_id, session in session_dict.items():
169-
python_compat_session(session)
170-
if NotebookSession(**session).kernel.id == extract_kernel_id(
171-
get_ipython().parent.parent.connection_file
172-
):
173-
# if session['kernel']['id'] == extract_kernel_id(get_ipython().parent.parent.connection_file):
174-
201+
if session.kernel.id == current_kernel_id:
175202
return next(
176203
client for client in cls.nb_client_gen() if client.url == server_url
177204
)
178205

179206

180207
class CurrentNotebookClient(NotebookClient):
208+
"""Represents the currently active notebook client"""
209+
181210
def __init__(self):
182211
self.nb_client = NotebookClientCollection.current_server()
183212
super().__init__(self.nb_client.nb_config)
184213
self.session = self.sessions[self.kernel_id]
185-
self.notebook = NotebookSession(**self.session).notebook
186214

187215
@property
188216
def connection_file(self):
189217
return get_ipython().parent.parent.connection_file
190218

191219
@property
192220
def kernel_id(self):
193-
return os.path.basename(self.connection_file).lstrip('kernel-').rstrip('.json')
221+
return extract_kernel_id(self.connection_file)

bookstore/client/store_client.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""Client to interact with the notebook store"""
2+
import requests
3+
4+
from .nb_client import CurrentNotebookClient
5+
6+
7+
class BookstoreClient(CurrentNotebookClient):
8+
"""Represents a bookstore client that corresponds to the active nb client"""
9+
10+
def __init__(self, s3_bucket=""):
11+
if s3_bucket:
12+
self.default_bucket = s3_bucket
13+
super().__init__()
14+
15+
@property
16+
def publish_endpoint(self):
17+
api_endpoint = "/api/bookstore/published/"
18+
return f"{self.url}{api_endpoint}"
19+
20+
def publish(self, path=None):
21+
"""Publish notebook to bookstore"""
22+
if path is None:
23+
path = self.session.path
24+
nb_json = self.get_contents(self.session.path)['content']
25+
json_body = {"type": "notebook", "content": nb_json}
26+
27+
target_url = f"{self.publish_endpoint}{self.session.path}"
28+
29+
resp = self.req_session.put(target_url, json=json_body)
30+
return resp
31+
32+
@property
33+
def clone_endpoint(self):
34+
api_endpoint = "/api/bookstore/cloned/"
35+
return f"{self.url}{api_endpoint}"
36+
37+
def clone(self, s3_bucket="", s3_key="", target_path=""):
38+
s3_bucket = s3_bucket or self.default_bucket
39+
json_body = {"s3_bucket": s3_bucket, "s3_key": s3_key, "target_path": target_path}
40+
target_url = f"{self.clone_endpoint}"
41+
# TODO: Add a check for success
42+
resp = self.req_session.post(target_url, json=json_body)
43+
return resp

0 commit comments

Comments
 (0)