Electron Adventures: Episode 59: Notebook Python Engine
In previous episodes we created:
- HTTP-based Ruby language server
- HTTP-based Python language server
- process-based Ruby language server
So now it's time to create a process-based Python language server as well.
We'll be reusing the whole frontend from the previous episode - other than changing name of the script we run from ruby_language_server
to python_language_server
, and replacing Ruby code examples by Python examples.
All the new code will be Python.
python_language_server
#!/usr/bin/env python3
from io import StringIO
import sys
import json
class Capturing(list):
def __enter__(self):
self._stdout = sys.stdout
self._stderr = sys.stderr
self._stringio = StringIO()
sys.stdout = self._stringio
sys.stderr = self._stringio
return self
def __exit__(self, *args):
output = self._stringio.getvalue()
self.append(output)
sys.stdout = self._stdout
sys.stderr = self._stderr
sessions = {}
for line in sys.stdin:
body = json.loads(line)
session_id = body["session_id"]
code = body["code"]
sessions.setdefault(session_id, {})
error = None
with Capturing() as output:
try:
exec(code, sessions[session_id])
except Exception as e:
error = str(e)
result = {"output": output[0], "error": error}
print(json.dumps(result), flush=True)
There's very little new. We already had all the code for executing code and capturing the output in the Flask version.
We just need to:
- read the input with
for line in sys.stdin
- parse it with
body = json.loads(line)
- print the result with
print(json.dumps(result), flush=True)
The flush=True
is important, as communication between processes is normally buffered, so it won't actually be sent until the 4kB buffer is full. This buffering does not happen if you print to the terminal, and normally if you send things to files, you don't care about exact timing of when each line gets there. But when talking to processes, we need to do this.
We don't need to do any tricks on input, as only the sending process can potentially have such a buffer.
Result
Here's the result if we press "Run All" button:
It was all very easy as we basically just merged what we did in previous two episodes.
In the next episode we'll do something a little more complicated and try to do the same for a language you might not have heard of in a while. And we'll also move session control into Electron side.
As usual, all the code for the episode is here.