Python FastAPI "Post Unprocessable Entity" error

Viewed 3637

Getting error when to submit button

main.py

from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from googletrans import Translator

import uvicorn

#from googletrans import Translator
#init
app = FastAPI(debug=True)

templates = Jinja2Templates(directory="template")


#route
@app.get('/')
async def home(request: Request):
        data = {'request': request}
        return templates.TemplateResponse('translates.html', data)


class variable(BaseModel):
    Input_text: str
    trans: str

#def translator(request):
@app.post('/',response_model=variable)
async def trans(request: Request,  form: variable ):

    text = request.get('Input_text')
    lang = request.get('trans')
    # print('text:',text,'lang:',lang)

    # connect the translator
    translator = Translator(service_urls=['translate.googleapis.com'])

    # detect langguage
    dt = translator.detect(text)
    dt2 = dt.lang

    # translate the text
    translated = translator.translate(text, lang)
    tr = translated.text
    data = {
        'request': request,
        'translated': tr
        , 'u_lang': dt2
        , 't_lang': lang}

    return templates.TemplateResponse(data, "translates.html",)


if __name__=="__main__":
   uvicorn.run(app,host="127.0.0.1",port=8000)

Then the HTML code

translate.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Translate</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
    <script src="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.js"></script>

</head>
<body>
<div class="ui container">
    <h2>Translation</h2>

<form action="" method="post">
    <br>

<div class="form-input">
    <center><label for="TextareaInput">Enter Text </label></center>
    <center><textarea class="form-control" name="Input_text" id="TextareaInput" rows="3"></textarea></center>
</div>
<div class="ui divider"></div>

<div class="selection list">
  <center><label for="languages">Choose Langguage:</label></center>
  <center><select name="trans" id="languages">
    <option value="en">English</option>
    <option value="ms">Malay</option>
    <option value="zh-cn">Mandarin</option>
    <option value="ko">Korean</option>
      <option value="ja">Japanese</option>
      <option value="vi">Vietnamese</option>
      <option value="th">Thailand</option>
  </select></center>
</div>
<div class="ui divider"></div>

<div>
   <center> <button type="Submit" class="button">Translate</button></center>
</div>
<div class="ui divider"></div>

<div class="form-output">
    <!---<center><textarea class="form-control" id="TextareaOutput" rows="3" value={{translated}} placeholder="Translate..."></textarea></center>-->

    <div class="Output Translation">

        <br><br>
        <h1>Text translated {{u_lang}} from {{t_lang}}</h1>
        <center>
            <h1>{{translated}}</h1>
        </center>
    </div>
</div>
</form>

</div>

</body>
</html>

When I try to button submit these happen ->

{
  "detail": [
    {
      "loc": [
        "body",
        0
      ],
      "msg": "Expecting value: line 1 column 1 (char 0)",
      "type": "value_error.jsondecode",
      "ctx": {
        "msg": "Expecting value",
        "doc": "Input_text=hi&trans=zh-cn",
        "pos": 0,
        "lineno": 1,
        "colno": 1
      }
    }
  ]
}

Pretty sure the error occurs on my BaseModel parameter but I seem can't get it corrected, already search on the internet tho.

2 Answers

When you use pydantic models for a request body parameters, FastAPI interprets them as json objects:

With just that Python type declaration, FastAPI will:

  • Read the body of the request as JSON.
  • Convert the corresponding types (if needed).
  • Validate the data...

But HTML forms are sent to the server not in the form of json, but in a special x-www-form-urlencoded format (more info here), so to read the form values, you need to use the Form parameters, like so:

@app.post("/")
async def path_operation(Input_text: str = Form(...), trans: str = Form(...)):
    return {"Input_text": Input_text, "trans": trans}

You can use Pydantic models, but you need to create a workaround like this, it expects a Body by default, so we are changing the signature of the Fields by wrapping the Pydantic Model.

from fastapi import Form, Depends


def form_body(cls):
    cls.__signature__ = cls.__signature__.replace(
        parameters=[
            arg.replace(default=Form(...))
            for arg in cls.__signature__.parameters.values()
        ]
    )
    return cls


@form_body
class variable(BaseModel):
    Input_text: str
    trans: str

Then you can start using it like this

from fastapi import FastAPI, Form, Depends
from pydantic import BaseModel


app = FastAPI(debug=True)


def form_body(cls):
    cls.__signature__ = cls.__signature__.replace(
        parameters=[
            arg.replace(default=Form(...))
            for arg in cls.__signature__.parameters.values()
        ]
    )
    return cls


@form_body
class variable(BaseModel):
    Input_text: str
    trans: str


@app.post("/", response_model=variable)
async def trans(form: variable = Depends(variable)):

    return form
Related