From 4d77516bf492091f48bc566547a2f4158c727e3d Mon Sep 17 00:00:00 2001 From: William Chargin Date: Thu, 9 Jan 2020 21:33:09 -0800 Subject: [PATCH] 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 . 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 --- scripts/serve_api.py | 71 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 scripts/serve_api.py diff --git a/scripts/serve_api.py b/scripts/serve_api.py new file mode 100644 index 0000000..3ace6d9 --- /dev/null +++ b/scripts/serve_api.py @@ -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()