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.

25