ForgeTotal.js 5
nl

Total.js 5

Roep een Python-programma dat blijft draaien aan met PYPELINE()

Total.js 5 · total5@0.0.19 pythonpypelineintegrationsai Uitgevoerd met Total.js 5

PYPELINE(name, [options]) start pypelines/name.py met python3 en houdt een verbinding ermee, zodat de applicatie Python-bibliotheken (data, modellen, beeldverwerking) kan gebruiken zonder voor elk verzoek een nieuw proces te starten. Het geeft een object terug met send(data) om een bericht te sturen, on('data', fn) om de antwoorden te ontvangen en close() om het script te stoppen. Een naam die met ~ begint is in plaats daarvan het pad van een bestand (~/opt/job.py), en een tekst die code is (met regeleinden of die eruitziet als Python) wordt uitgevoerd als inline programma met python3 -c.

  • Het protocol. Het script wordt gestart als python3 -u script.py <socket>: het laatste argument is het pad van een Unix-socket waarop de applicatie luistert, en het script verbindt ermee. Elk bericht, in beide richtingen, is een frame: 4 bytes met de lengte (big endian) gevolgd door de body, die standaard JSON is.
  • Opties: type ('json' standaard, 'text', of iets anders voor ruwe Buffers), autorestart (false om het script niet te herstarten als het eindigt; standaard wordt het opnieuw gestart), threads en socket: false (geen socket, alleen het programma starten).
  • Berichten die worden verstuurd voordat het script is verbonden, wachten en worden bezorgd zodra het verbindt.
  • Gebeurtenissen: open (het script verbond), data (een bericht), restart en close.
  • Het pad van de socket heeft een limiet. De socket is een bestand in de map tmp van de applicatie en het systeem beperkt het hele pad (ongeveer 104 tekens op macOS en 108 op Linux). In een project dat diep op de schijf staat, mislukt de server met listen EINVAL, verbindt het script nooit en wachten de berichten eindeloos. Het pad is de eigenschap pypeline.socket: stel die direct na PYPELINE() in (het bestand wordt gecontroleerd en de server daarna gestart) op een kort pad, zoals het voorbeeld doet.
  • Het Python-programma is een gewoon programma dat met de rechten van de applicatie draait: het is zo veilig als de code van het bestand, en Python 3 moet geïnstalleerd zijn (python3 in het PATH).
pypelines/shout.py
import json
import socket
import struct
import sys

# The last argument is the socket that the application listens on
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(sys.argv[-1])

def read(count):
    data = b''
    while len(data) < count:
        chunk = sock.recv(count - len(data))
        if not chunk:
            raise SystemExit(0)
        data += chunk
    return data

while True:
    size = struct.unpack('>I', read(4))[0]
    message = json.loads(read(size).decode('utf-8'))
    answer = json.dumps({ 'id': message['id'], 'upper': message['text'].upper(), 'words': len(message['text'].split()) }).encode('utf-8')
    sock.sendall(struct.pack('>I', len(answer)) + answer)
controllers/py.js
exports.install = function() {
	ROUTE('POST /shout/ @json', shout);
};

const pypeline = PYPELINE('shout');

// A short path for the socket: the folder of a project can be too deep for a Unix socket
pypeline.socket = require('os').tmpdir() + '/forge-shout-' + process.pid + '.socket';
const pending = {};
let counter = 0;

// Every answer says which message it is for
pypeline.on('data', function(answer) {
	const callback = pending[answer.id];
	if (callback) {
		delete pending[answer.id];
		callback(answer);
	}
});

function shout($) {
	const id = ++counter;
	const timer = setTimeout(() => $.invalid('The script did not answer'), 5000);
	pending[id] = function(answer) {
		clearTimeout(timer);
		$.json(answer);
	};
	pypeline.send({ id: id, text: $.body.text });
}