api: add server for easy Observable integration (#1537)
Summary: This commit adds a simple Python server for connecting the output of `yarn api` (or `yarn api --watch`) to an observable notebook. We need a custom server rather than just `python3 -m http.server` to send CORS headers properly. This server enables a very tight loop from editing SourceCred core code on your local filesystem to seeing live updates in an Observable notebook, with latency on the order of one second. Test Plan: Run `yarn api --watch` in the background. Launch the new API server. Navigate to <https://observablehq.com/demo>. Copy the two paragraphs of Observable code from `scripts/serve_api.py` into _separate_ Observable cells, and execute them. Note that `myGraph` becomes a valid SourceCred graph. Modify `src/core/graph.js` to add `this._aaa = 123;` to the top of the `Graph` constructor. Re-execute the first Observable cell (the one that loads the SourceCred module), and note that `myGraph` updates to include the new `_aaa` attribute: ![Screenshot of Observable notebook after test plan][ss] [ss]: https://user-images.githubusercontent.com/4317806/71958748-dddf8680-31a5-11ea-9016-5df76ceeea46.png wchargin-branch: api-server
This commit is contained in:
parent
8ed0585bb6
commit
4d77516bf4
|
@ -0,0 +1,71 @@
|
|||
"""Serve SourceCred API, for consumption by Observable notebooks.
|
||||
|
||||
Usage:
|
||||
|
||||
python3 ./scripts/serve_api.py [PORT]
|
||||
|
||||
from the SourceCred repository root. Omit the port to try a default, or
|
||||
specify port 0 to pick a free port automatically.
|
||||
|
||||
This server binds to `localhost`, which is a loopback address only
|
||||
accessible from your machine, not the local network.
|
||||
|
||||
Corresponding Observable code:
|
||||
|
||||
sourcecred = {
|
||||
const server = "http://localhost:9009";
|
||||
const blob = await fetch(server).then((r) => r.blob());
|
||||
const esModule = await require(URL.createObjectURL(blob));
|
||||
return esModule.default;
|
||||
}
|
||||
|
||||
myGraph = new sourcecred.core.graph.Graph() // e.g.
|
||||
|
||||
This server serves the same response for all requests.
|
||||
|
||||
For best results, run concurrently with `yarn api --watch`. After
|
||||
changing SourceCred code, just re-execute the definition of the
|
||||
`sourcecred` module in the Observable notebook to live-load the new code
|
||||
and update your notebook.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
from wsgiref import simple_server
|
||||
|
||||
|
||||
_API_FILE = os.path.join("dist", "api.js")
|
||||
_DEFAULT_PORT = 9009
|
||||
|
||||
|
||||
def _app(environ, start_response):
|
||||
# This will 500 if the file can't be read, which is fine.
|
||||
with open(_API_FILE, "rb") as infile:
|
||||
# Re-read the file every time to account for updates.
|
||||
contents = infile.read()
|
||||
headers = [
|
||||
("Access-Control-Allow-Origin", "*"),
|
||||
("Content-Type", "application/javascript; charset=UTF-8"),
|
||||
("X-Content-Type-Options", "nosniff"),
|
||||
]
|
||||
start_response("200 OK", headers)
|
||||
return (contents,)
|
||||
|
||||
|
||||
def _main():
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else _DEFAULT_PORT
|
||||
server = simple_server.make_server("localhost", port, _app)
|
||||
print("Serving on port %d" % server.server_port)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_main()
|
Loading…
Reference in New Issue