Skip to content

Commit cf80175

Browse files
authored
Subscriptions (#2)
* get deps up-to-date, add graphql-ws * install subscriptions * did someone say subscriptions? * blacken * sure, let's call it a version * subscribe to something useful
1 parent 1df8647 commit cf80175

16 files changed

+480
-529
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,9 @@ _scripts/
55
*_files/
66
*.bundle.*
77
*.egg-info/
8-
*.html
98
*.log
109
*.tar.gz
1110
envs/
1211
lib/
1312
Untitled*.ipynb
1413
static/
15-
anaconda-project.yml

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# CHANGELOG
2+
3+
## Unreleased
4+
5+
### 0.2.0
6+
- Subscriptions (in server and GraphiQL)
7+
8+
### 0.1.0
9+
- Basic Capability with contents manager

anaconda-project.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: jupyter-graphql-dev
2+
3+
commands:
4+
lab:
5+
unix: jupyter lab --no-browser --debug
6+
setup:
7+
unix: pip install -e . --ignore-installed --no-deps
8+
black:
9+
unix: black src/py setup.py
10+
atom:
11+
unix: atom .
12+
static:
13+
unix: python -m jupyter_graphql.fetch_static
14+
15+
env_specs:
16+
default:
17+
platforms:
18+
- linux-64
19+
- osx-64
20+
- win-64
21+
inherit_from:
22+
- jupyter-graphql-dev
23+
packages:
24+
- black
25+
- flake8
26+
- beautysh
27+
jupyter-graphql-dev:
28+
packages:
29+
- gql
30+
- graphene
31+
- iso8601
32+
- jupyterlab >=0.35,<0.36
33+
- python >=3.6,<3.7
34+
- requests
35+
- werkzeug
36+
- pip:
37+
- graphql-ws
38+
- graphene-tornado
39+
channels:
40+
- conda-forge
41+
- defaults

environment.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,13 @@ channels:
55
- defaults
66

77
dependencies:
8-
- aniso8601
8+
- gql
9+
- graphene
10+
- iso8601
911
- jupyterlab >=0.35,<0.36
10-
- pip
11-
- promise
1212
- python >=3.6,<3.7
1313
- requests
14-
- rx
1514
- werkzeug
1615
- pip:
17-
- gql
18-
- graphene
19-
- graphql-core
20-
- graphql-relay
16+
- graphql-ws
17+
- graphene-tornado

notebooks/gql.ipynb

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@
1616
"metadata": {},
1717
"outputs": [],
1818
"source": [
19-
"import requests # should investigate writing a tornado transport\n",
19+
"from pprint import pprint\n",
2020
"from getpass import getpass\n",
2121
"from gql import Client, gql\n",
2222
"from gql.transport.requests import RequestsHTTPTransport\n",
23-
"from IPython.display import JSON\n",
24-
"from pprint import pprint"
23+
"from IPython.display import JSON, IFrame\n",
24+
"import requests # should investigate writing a tornado transport"
2525
]
2626
},
2727
{
2828
"cell_type": "markdown",
2929
"metadata": {},
3030
"source": [
31-
"You'll need your `jupyter notebook` or `jupyter lab` token (view source, look for `\"token\"`)"
31+
"## Client!"
32+
]
33+
},
34+
{
35+
"cell_type": "markdown",
36+
"metadata": {},
37+
"source": [
38+
"This has to know where you are. For example for `http://localhost:8888/lab`:"
3239
]
3340
},
3441
{
@@ -37,14 +44,14 @@
3744
"metadata": {},
3845
"outputs": [],
3946
"source": [
40-
"token = getpass()"
47+
"URL = \"http://localhost:8888/graphql\""
4148
]
4249
},
4350
{
4451
"cell_type": "markdown",
4552
"metadata": {},
4653
"source": [
47-
"Update to suit!"
54+
"Since this is a _kernel_ talking back to the notebook _server_, you'll need your `jupyter notebook` or `jupyter lab` token (view source, look for `\"token\"`)"
4855
]
4956
},
5057
{
@@ -53,14 +60,7 @@
5360
"metadata": {},
5461
"outputs": [],
5562
"source": [
56-
"URL = \"http://localhost:8888/graphql\""
57-
]
58-
},
59-
{
60-
"cell_type": "markdown",
61-
"metadata": {},
62-
"source": [
63-
"Make a client!"
63+
"token = getpass()"
6464
]
6565
},
6666
{
@@ -79,7 +79,7 @@
7979
"cell_type": "markdown",
8080
"metadata": {},
8181
"source": [
82-
"Make a query!"
82+
"## Query!"
8383
]
8484
},
8585
{
@@ -88,11 +88,11 @@
8888
"metadata": {},
8989
"outputs": [],
9090
"source": [
91-
"query = gql('''\n",
92-
"query {\n",
91+
"query = \"\"\"{\n",
9392
" contents(path: \"notebooks/gql.ipynb\") {\n",
93+
" path\n",
94+
" last_modified\n",
9495
" ... on NotebookContents {\n",
95-
" path\n",
9696
" content {\n",
9797
" nbformat\n",
9898
" nbformat_minor\n",
@@ -107,8 +107,9 @@
107107
" }\n",
108108
" }\n",
109109
"}\n",
110-
"''')\n",
111-
"query"
110+
"\"\"\"\n",
111+
"query_gql = gql(query)\n",
112+
"query_gql"
112113
]
113114
},
114115
{
@@ -124,15 +125,50 @@
124125
"metadata": {},
125126
"outputs": [],
126127
"source": [
127-
"result = client.execute(query)\n",
128+
"result = client.execute(query_gql)\n",
128129
"JSON(result)"
129130
]
130131
},
131132
{
132133
"cell_type": "markdown",
133134
"metadata": {},
134135
"source": [
135-
"Where can you go from here?"
136+
"Where can you go from here? "
137+
]
138+
},
139+
{
140+
"cell_type": "markdown",
141+
"metadata": {},
142+
"source": [
143+
"## Subscribe!\n",
144+
"With a little work up-front and even less work at query time, the same types from above can be used to power live _subscriptions_. Right now, only contents are available, but many things in the notebook server and broader ecosystem could become \"live\"."
145+
]
146+
},
147+
{
148+
"cell_type": "code",
149+
"execution_count": null,
150+
"metadata": {},
151+
"outputs": [],
152+
"source": [
153+
"subscription = f\"subscription {query}\"\n",
154+
"print(subscription)"
155+
]
156+
},
157+
{
158+
"cell_type": "markdown",
159+
"metadata": {},
160+
"source": [
161+
"Go ahead and paste that in the iframe below and hit (▷)!\n",
162+
"> TODO: fix query param parsing!"
163+
]
164+
},
165+
{
166+
"cell_type": "code",
167+
"execution_count": null,
168+
"metadata": {},
169+
"outputs": [],
170+
"source": [
171+
"IFrame(URL, width=\"100%\", height=\"400px\")"
136172
]
137173
}
138174
],
@@ -152,7 +188,7 @@
152188
"name": "python",
153189
"nbconvert_exporter": "python",
154190
"pygments_lexer": "ipython3",
155-
"version": "3.6.6"
191+
"version": "3.6.7"
156192
}
157193
},
158194
"nbformat": 4,

postBuild

100644100755
File mode changed.

setup.cfg

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ classifiers =
2525

2626
[options]
2727
install_requires =
28-
jupyterlab
2928
graphene-tornado
29+
graphql-ws
30+
iso8601
31+
notebook
32+
werkzeug
33+
3034
package_dir =
3135
= src/py
3236
packages = find:

src/py/jupyter_graphql/__init__.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from pathlib import Path
22
from notebook.base.handlers import FileFindHandler
33

4-
54
from notebook.utils import url_path_join as ujoin
65

7-
from .handlers import GraphQLHandler
8-
6+
from .subscriptions import TornadoSubscriptionServer
7+
from .handlers import GraphQLHandler, SubscriptionHandler
98
from .schema import schema
109

1110

@@ -18,6 +17,8 @@ def load_jupyter_server_extension(app):
1817
app.log.info("[graphql] initializing")
1918
web_app = app.web_app
2019

20+
subscription_server = TornadoSubscriptionServer(schema)
21+
2122
# add our templates
2223
web_app.settings["jinja2_env"].loader.searchpath += [TEMPLATES]
2324

@@ -34,12 +35,12 @@ def app_middleware(next, root, info, **args):
3435
(
3536
base(),
3637
GraphQLHandler,
37-
dict(
38-
schema=schema,
39-
graphiql=True,
40-
nb_app=app,
41-
middleware=[app_middleware],
42-
),
38+
dict(schema=schema, graphiql=True, middleware=[app_middleware]),
39+
),
40+
(
41+
base("subscriptions"),
42+
SubscriptionHandler,
43+
dict(subscription_server=subscription_server, app=app),
4344
),
4445
# serve the graphiql assets
4546
(base("static", "(.*)"), FileFindHandler, dict(path=[STATIC])),

src/py/jupyter_graphql/executor.py

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,44 @@
11
from urllib.request import urlretrieve
22
from urllib.parse import urlparse
3+
import sys
34

45
from . import STATIC
56

67
# Download the file from `url` and save it locally under `file_name`:
78

8-
ASSETS = [
9+
JSDELIVR_ASSETS = [
910
"https://cdn.jsdelivr.net/npm/[email protected]/graphiql.css",
1011
"https://cdn.jsdelivr.net/npm/[email protected]/fetch.min.js",
1112
"https://cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js",
1213
"https://cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js",
1314
"https://cdn.jsdelivr.net/npm/[email protected]/graphiql.min.js",
15+
# "https://cdn.jsdelivr.net/npm/[email protected]/browser/client.js",
16+
# "https://cdn.jsdelivr.net/npm/[email protected]/dist/fetcher.js",
17+
]
18+
19+
UNPKG_ASSETS = [
20+
"https://unpkg.com/[email protected]/browser/client.js",
21+
"https://unpkg.com/[email protected]/browser/client.js",
1422
]
1523

1624

17-
def fetch_static():
18-
for url in ASSETS:
19-
out = (STATIC / urlparse(url).path[1:]).resolve()
20-
if not out.exists():
25+
def fetch_assets(assets, prefix=None, force=False):
26+
for url in assets:
27+
out = STATIC
28+
if prefix:
29+
out = STATIC / prefix
30+
out = (out / urlparse(url).path[1:]).resolve()
31+
if force or not out.exists():
2132
out.parent.mkdir(parents=True, exist_ok=True)
2233
out.write_text("")
23-
print("fetching", url, "to", out)
34+
print(f"fetching\n\t- {url}\n\t> {out.relative_to(STATIC)}")
2435
urlretrieve(url, out)
2536

2637

38+
def fetch_static(force=False):
39+
fetch_assets(JSDELIVR_ASSETS, force=force)
40+
fetch_assets(UNPKG_ASSETS, "npm", force=force)
41+
42+
2743
if __name__ == "__main__":
28-
fetch_static()
44+
fetch_static(force="--force" in sys.argv)

0 commit comments

Comments
 (0)