How do deploy a backend that uses node.js to call python

65 Views Asked by At

So I was building a text summarizer that works perfectly fine on localhost and wanted to deploy it. I deployed the front end using Github pages and was trying to use Vercel to deploy the backend part.

The front end takes some text and uses Axios.post to send the text and a length value.

    //Adding headers
    const headers={'Content-Type': 'application/json;charset=utf-8', 'Access-Control-Allow-Origin': '*'}

    //Setting the data in a more searchable form
    const data = {text: sanitizeText}
    activeItem === 'p' ? data.length = length : data.length = 0

    //Sending the data to the backend
    let result = await Axios.post(url+"/summary", data, {headers: headers})
    .then((res)=>{
        console.log("Then 1", res.data)
        return res.data.summary
    })
    .then((result)=>{
        console.log("Then 2", result)
        document.getElementsByClassName("text-output")[0].value = result
        setloadingSubmit(false) 
        return result
    })
    .catch((err)=>{
        console.log("There was an error: ", err)
    setloadingSubmit(false)
    return "Error"
    })

Then it goes to the backend which looks like this

const express = require("express");
const cors = require("cors");
const spawn = require("child_process").spawn //This allows me to use Python

const app = express();

//Uses the python summary file
app.post("/summary", (req, res) => {

    console.log("Has gotten here")
    let child;
    let wordCounter = req.body.text.split(" ").length
    let sum_vals = {
        1 : [30, 130], 
        2 : [100, 300],
        3 : [150, 400],
        4 : [200, 500]
    }
    let len = parseInt(req.body.length)

    if (len === 0)
    {
        child = spawn('python', ['./pyAPI/bullet.py', req.body.text])
    }
    else
    {
        if(len === 1)
        {
            if (wordCounter >= sum_vals[1][0])
            {
                child = spawn('python', ['./pyAPI/summarizer.py', req.body.text, sum_vals[1][0], sum_vals[1][1]] )
            }
            else{
                console.log("This sentence is too short to summarize.")
            }
        }
        else if (wordCounter >= sum_vals[req.body.length][0])
        {
            child = spawn('python', ['./pyAPI/summarizer.py', req.body.text, sum_vals[len][0], sum_vals[len][1]] )
        }
        else
        {
            child = spawn('python', ['./pyAPI/summarizer.py', req.body.text, sum_vals[len-1][0], sum_vals[len-1][1]] )
        }
    }
    
    child.stdout.on('data', (data)=>{
        console.log("stdout: ", Buffer.from(data.toString(), "hex").toString())
        res.send({summary: Buffer.from(data.toString(), "hex").toString()})
    })
    child.stderr.on('data', (data)=>{
        console.error("stderr: ", data.toString())
    })
    child.on('close', (code)=>{
        console.log('child process exited with code', code.toString())
    })
})

const PORT = process.env.PORT || 3000;

app.listen(PORT,
    console.log(`Server started on port ${PORT}`)
);

And this is an example of one of the python scripts.

import sys
import math
from sumy.summarizers.luhn import LuhnSummarizer
from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from nltk.tokenize import sent_tokenize

# Get text
text = sys.argv[1]

# Tokenize the sentences
# I used to determine how many sentences there are
sentences = sent_tokenize(text)

# Create a parser based on the text
parser = PlaintextParser.from_string(text, Tokenizer("english"))

# Create a Luhn Summarizer var
summarizer_luhn = LuhnSummarizer()

# Use the Luhn summarizer to summarize the the document created by the parser
# and how long we want our summarized document to be being a 3rd of its origial length to the ceiling
summary = summarizer_luhn(parser.document, int(math.ceil(sentences.__len__()/3)))

# Changing the returned values to strings because they are of class sumy.models.dom
document_parser = []
for i in summary:
    line_parser = str(i)
    document_parser.append(line_parser)

# Setting up the sentences to have a bullet point before them and be on a seperate line
final_sentence = ""
for document in document_parser:
    final_sentence += "\u2022 "+document+"\n"

# Sending back sentence as a hex so no information is lost.
print(final_sentence.encode(encoding="utf-8").hex())

Vercel needs a .json file and this is how I ended up configuring it.

{
    "version": 2,
    "builds": [
        {
        "src": "app.js",
        "use": "@vercel/node",
        "config": { "includeFiles": ["dist/**"] }
        },
        {
            "src": "./pyAPI/*.py",
            "use": "@vercel/python"
        }
    ],
    "routes": [
        {
        "src": "/(.*)",
        "dest": "app.js",
        "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
        "headers": {
            "Access-Control-Allow-Origin": "*"
        }
        }
    ]
    }

Now, when I deployed the backend to Vercel and tried to run the script I get this error in the logs.

Has gotten here
Uncaught Exception {
    "errorType": "Error",
    "errorMessage": "spawn python ENOENT",
    "code": "ENOENT",
    "errno": -2,
    "syscall": "spawn python",
    "path": "python",
    "spawnargs": ["./pyAPI/bullet.py", "In here is where the text that I submitted should be the normal text passes the character test but I don't want this to be too long."],
    "stack": ["Error: spawn python ENOENT", "    at ChildProcess._handle.onexit (node:internal/child_process:284:19)", "    at onErrorNT (node:internal/child_process:477:16)", "    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)"]
}
Unknown application error occurred
Runtime.Unknown

Front end gives me "Request failed with status code 500", "ERR_BAD_RESPONSE" etc.

So I looked up a bunch of possible solutions.

  • I changed the spawn from child=spawn('python'... to child=spawn('python3'...
  • Changed the path a bunch ex. ['RootFolder/pyAPI/bullet.py'..., ['/pyAPI/bullet.py'..., ['bullet.py'..., [process.env.PATH+'./pyAPI/bullet.py'..., [process.env.PATH+'/pyAPI/bullet.py'...
  • Added shell child = spawn('python', ['./pyAPI/bullet.py', req.body.text], {shell: true})
  • Added more cors
app.use((req, res, next) => {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "POST, GET, PUT");
    res.header("Access-Control-Allow-Headers", "Content-Type");
    next();
})
app.options('*', cors())
  • Added another build for python
{
    "src": "./pyAPI/*.py", "src": "*.py" and "src": "/*.py",
    "use": "@vercel/python"
}
  • Pulled out one of the python files into the main project folder instead of the pyAPI folder.
  • Change from post to get (It doesn't really do what I want it to so I changed it back)

They all give me the same error with various changes depending on what I changed (there is no error for builds). "errorMessage": "spawn python3 ENOENT", "spawnargs": ["bullet.py", //etc.

Vercel looks like it should be able to handle what I'm trying to do so I'm not entirely sure why it doesn't seem to be working.

0

There are 0 best solutions below