How to include JSON and File data together in FastAPI endpoint? - python

I would like to POST JSON and File data together, as shown in the code below:
fastapi.py
#router.post('/rate')
def users(user_review:schemas.Rate, image123: UploadFile = File(...), db: Session=Depends(get_db)):
print(image123)
schemas.py
class Rate(BaseModel):
id1:int
id2:int
message:Optional[str] = None
rate:conint(ge=1, le=5)
However, when I execute it, it throws the following 422 error:
{
"detail": [
{
"loc": [
"body",
"user_review"
],
"msg": "field required",
"type": "value_error.missing"
},
{
"loc": [
"body",
"image123"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}

You can't declare an endpoint that expects both JSON and File/Form data together, as this is not supported by the HTTP protocol, as explained in FastAPI's documentation. When a request includes Form data, it will have the body encoded using application/x-www-form-urlencoded instead of application/json; if File data are included as well, it will have the body encoded using multipart/form-data.
As explained in this answer, there are various methods to send additional data together with uploading Files. The below demonstrates an approach based on Method 4 of the above answer.
Note: You shouldn't name your python script file fastapi.py (as shown in your question), as this would interfere with the library (when using, for example, from fastapi import FastAPI), but rather use some neutral name, such as app.py.
app.py
from fastapi import FastAPI, File, UploadFile, Body
from pydantic import BaseModel, conint
from typing import Optional
import json
app = FastAPI()
class Rate(BaseModel):
id1: int
id2: int
message: Optional[str] = None
rate: conint(ge=1, le=5)
#classmethod
def __get_validators__(cls):
yield cls.validate_to_json
#classmethod
def validate_to_json(cls, value):
if isinstance(value, str):
return cls(**json.loads(value))
return value
#app.post("/rate")
def submit(user_review: Rate = Body(...), image: UploadFile = File(...)):
return {"JSON Payload ": user_review, "Image": image.filename}
test.py
import requests
url = 'http://127.0.0.1:8000/rate'
file = {'image': open('image.png','rb')}
data = {'user_review': '{"id1": 1, "id2": 2, "message": "foo", "rate": 5}'}
resp = requests.post(url=url, data=data, files=file)
print(resp.json())
Test with Fetch API (using Jinja2Templates or HTMLResponse)
<html>
<head>
<script type="text/javascript">
function submitData() {
var fileInput = document.getElementById('imageFile');
if (fileInput.files[0]) {
var data = new FormData();
data.append("user_review", JSON.stringify({id1: 1, id2: 2, message: "foo", rate: 5}));
data.append("image", fileInput.files[0]);
fetch('/rate', {
method: 'POST',
headers: {
'Accept': 'application/json'
},
body: data
})
.then(resp => resp.text()) // or, resp.json(), etc.
.then(data => {
document.getElementById("responseArea").innerHTML = data;
})
.catch(error => {
console.error(error);
});
}
}
</script>
</head>
<body>
<input type="file" id="imageFile" name="file"></br></br>
<input type="button" value="Submit" onclick="submitData()">
<div id="responseArea"></div>
</body>
</html>

Related

Can not deserialize instance of io.vavr.collection.Seq out of VALUE_STRING

I was trying to build a python request module passing API tokens, however I am running into below issue every time. Sharing my entire code :
import requests
import json
def bitbucketFunction():
variables = {"test":"1234"}
callBitBucketAPI(variables)
def callBitBucketAPI(variables):
try:
headers = {
'Content-Type': 'application/json'
}
url = "https:xyz/pipelines/"
data = {
"target": {
"type": "pipeline_ref_target",
"ref_type": "branch",
"ref_name": "master",
"selector": {
"type": "custom",
"pattern": "create-aws-account"
}
},"variables":json.dumps(variables, indent=2)}
response = requests.request("POST",url, auth=('token-key', 'token-value'), data=json.dumps(data),headers=headers)
print(response.text)
except Exception as e:
print("Exception occured in callBitBucketAPI method: ", e)
bitbucketFunction()
Error:
{"error": {"message": "An invalid field was found in the JSON payload.", "fields": ["variables"], "detail": "Can not deserialize instance of io.vavr.collection.Seq out of VALUE_STRING token\n at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 167] (through reference chain: com.atlassian.pipelines.rest.model.v1.pipeline.ImmutablePipelineModel$Builder[\"variables\"])", "data": {"key": "rest-service.request.invalid-json"}}}

Microsoft Graph API how to send an attachment in a chat

I want to use the Microsoft Graph API to send messages with attachments to chats or channels.
https://learn.microsoft.com/en-us/graph/api/chatmessage-post?view=graph-rest-1.0&tabs=http#example-4-send-a-message-with-file-attachment-in-it
I can send normal messages already just fine like this:
def post_message(chat_id: str, subject: str = "", content_type: str = "text", content: str = "") -> None:
url = f"https://graph.microsoft.com/v1.0/chats/{chat_id}/messages"
json = {
"subject": subject,
"body": {
"contentType": content_type,
"content": content
}
}
res = requests.post(url, headers=header, json=json)
I try to copy the body from the example in the link above, substitute for my values and swap json variable for this one:
attachment_id = '7QW90B10D7-B5AK-420A-AC78-1156324A54F2' # not real, only to show how it looks like
json = {
"body": {
"contentType": 'html',
"content": f'i dunno what i\'m doing. <attachment id="{attachment_id}"></attachment>'
},
'attachments': [
{
'id': attachment_id,
'contentType': 'reference',
'contentUrl': 'https://foo.sharepoint.com/sites/bar/User%20documentation/Databricks/Databricks%20guide.pptx',
'name': 'Databricks guide.pptx'
}
]
}
I get requests.exceptions.HTTPError: 400 Client Error: Bad Request for url
What's wrong with the code? How to get attachment id from a file correctly because I am not sure I got the right value?
I was able to get it working with your code, (using both formats <attachment id=\"\"> and <attachment id="">), so it seems the error is probably with your attachment_id.
I retrieved the driveItem ID by following this answer, where the driveItem ID is the GUID value in the eTag property in the response.
You could get the file by the path:
https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/{item-path}
For example:
https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/test.docx
If the file is in a folder, it would be like this:
https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/folder1/test.docx
Sample request after obtaining the ID
access_token = ""
attachment_name = "file.txt"
attachment_path = "https://domain.sharepoint.com/Shared%20Documents"
attachment_id = "12345678-1234-1234-1234-123456789123"
attachment_url = f"{attachment_path}/{attachment_name}"
chat_id = ""
req_url = f"https://graph.microsoft.com/v1.0/chats/{chat_id}/messages"
req_headers = {
"Authorization": "Bearer " + access_token,
"Content-Type": "application/json"
}
json = {
"body": {
"contentType": "html",
"content": f"Test message <attachment id=\"{attachment_id}\"></attachment>"
},
"attachments": [
{
"id": attachment_id,
"contentType": "reference",
"contentUrl": attachment_url,
"name": attachment_name
}
]
}
result = requests.post(url = req_url, headers = req_headers, json = json)

Flask + DataTables: how do you read the AJAX request that DataTables sends as JSON?

I've created a Flask application which:
Reads events from different event sources;
Persists those events to database; and
Allows users to search through those events using DataTables 1.10.20.
I'm trying to:
Read the parameters that DataTables is passing to the Flask backend via an AJAX request whenever a user attempts to search through the table; and
Translate those parameters into a dictionary so they can be used when performing server-side filtering.
Here is the code for the table I created in DataTables:
$(document).ready(function() {
events_table_template = {
"scrollX": true,
"pageLength": 10,
"processing": true,
"serverSide": true,
"ajax": {
"url": "/ajax/events",
"type": "POST",
"dataType": "json",
"dataSrc": "data",
"contentType": "application/json"
},
"columns": [
{"data": "event_id"},
{"data": "event_type"},
{"data": "event_timestamp"},
{"data": "event_name"},
]
};
var table = $('#events-table').DataTable(events_table_template);
});
I've tried explicitly returning JSON within the DataTables configuration:
$(document).ready(function() {
events_table_template = {
...
"ajax": {
...
"data": function(args){
return {"args": JSON.stringify(args)};
}
};
var table = $('#events-table').DataTable(events_table_template);
});
Here is the AJAX endpoint on the Flask server:
from mycode import EventService
from flask import Blueprint
import flask
blueprint = Blueprint('events-ajax', __name__, url_prefix='/ajax/')
#blueprint.route('/events/', methods=["POST"])
def get_events():
print("Request data", flask.request.data)
print("Request form", flask.request.form)
api = EventService()
events = api.get_events_via_database()
rows = []
for event in api.get_events_via_database():
rows.append({
'event_id': event.event_id,
'event_type': event.type,
'event_timestamp': event.timestamp,
'event_name': event.name,
})
response = {
'data': rows,
'recordsTotal': len(events),
'recordsFiltered': len(events),
'draw': 1,
}
return flask.jsonify(response)
I've noticed that:
flask.request.data returns a bunch of URL encoded text: Request data b'draw=1&columns%5B0%5D%5Bdata%5D=event_id&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=host_id&columns%5B1%5D%5B...
flask.request.form returns an ImmutableMultiDict object: ImmutableMultiDict([])
I've tried to:
Read the content of the AJAX request using these functions:
flask.request.data
flask.request.form
flask.request.json
flask.request.get_json(force=True)
Change the content-type from the DataTables side of things from application/json to application/json; charset=utf-8
How do you read the AJAX request as JSON?
Remove "contentType": "application/json" from the "ajax" configuration block; and
Use flask.request.form to read the map of parameters present within the AJAX request.
DataTables:
$(document).ready(function() {
events_table_template = {
"scrollX": true,
"pageLength": 10,
"processing": true,
"serverSide": true,
"ajax": {
"url": "/ajax/events",
"type": "POST",
"dataType": "json",
"dataSrc": "data",
},
"columns": [
{"data": "event_id"},
{"data": "event_type"},
{"data": "event_timestamp"},
{"data": "event_name"},
]
};
var table = $('#events-table').DataTable(events_table_template);
});
Flask:
#blueprint.route('/events/', methods=["POST"])
def get_events():
parameters = dict(flask.request.form)

Bottle POST method - getting query parameters

I am trying to send a POST AJAX request to a Bottle server and read query_string parameters.
This works with GET method, but with POST, bottle.request.query_string is empty.
This is with python 3.6.8. Bottle version in 0.12.17
I'm stuck, please advise.
Bottle server:
#!/usr/bin/env python3
import bottle
print(bottle.__version__)
class EnableCors(object):
name = "enable_cors"
api = 2
def apply(self, fn, context):
def _enable_cors(*args, **kwargs):
bottle.response.headers["Access-Control-Allow-Origin"] = "*"
bottle.response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
bottle.response.headers["Access-Control-Allow-Headers"] = "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
if bottle.request.method != "OPTIONS":
return fn(*args, **kwargs)
return _enable_cors
application = bottle.app()
application.install(EnableCors())
#application.route("/api/params", method=['OPTIONS', 'POST'])
def Api_Params():
print('bottle.request.query_string:', bottle.request.query_string)
bottle.run(host='0.0.0.0', port=8080, debug=True, reloader=True)
Test javscript client:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<script>
function test_post_param() {
var data = {'e': 'E', 'f': 'F', 'g': {'aa':'AA', 'bb':'BB'}};
$.ajax({
url: 'http://127.0.0.1:8080/api/params',
method: "POST",
data: "key=a",
// contentType: "text/plain",
success: function (response, textStatus) {
console.debug("test_post_param OK");
console.debug(textStatus);
console.debug(response);
},
error: function (response, textStatus) {
console.debug("test_post_param ERR");
console.debug(textStatus);
console.debug(response);
},
})
}
window.onload = test_post_param;
</script>
</body>
</html>
I put this on all my API endpoints. I am combining the POST form and query encoding into a single dict.
def merge_dicts(*args):
result = {}
for dictionary in args:
result.update(dictionary)
return result
payload = merge_dicts(dict(request.forms), dict(request.query.decode()))
So your code would look like this:
#application.route("/api/params", method=['OPTIONS', 'POST'])
def Api_Params():
payload = merge_dicts(dict(request.forms), dict(request.query.decode()))
print('bottle.request.query_string: {}'.format(payload))
Here is an example sending the data as JSON to a POST route which I have used successfully.
The JQuery AJAX call:
function test_post_param() {
var data = {'e': 'E', 'f': 'F', 'g': {'aa':'AA', 'bb':'BB'}};
$.ajax({
url: 'http://127.0.0.1:8080/api/params',
method: "POST",
data: JSON.stringify({
"key": "a"
}),
cache: false,
contentType: "application/json",
dataType: "json",
success: function(data, status, xhr){
// Your success code
},
error: function(xhr, status, error) {
// Your error code
}
})
};
The Bottle route:
#application.route("/api/params", method=['POST'])
def Api_Params():
key = bottle.request.forms.get("key")
print(key) # This should print 'a'
I prefer from bottle import route, get, post, template, static_file, request as the import statement. This allows the route to be written more simply (in my opinion).
#post("/api/params")
def Api_Params():
key = request.forms.get("key")
print(key) # This should print 'a'

Passing date from Angular to Python

I have Flask back-end and Angular front-end. My backend working well when I test it via Postman. I send the this kind of data
{
"date": "2018-01-27"
}
#app.route("/schedule", methods=['GET', 'POST'])
def schedule():
cursor = mysql.connection.cursor()
get_date = request.get_json(force = True)
day_name = datetime.datetime.strptime(get_date['date'], '%Y-%m-%d').strftime('%A')
week_number = datetime.datetime.strptime(get_date['date'], '%Y-%m-%d').strftime('%V')
...
But I have problems when I'm trying to send it from Angular.
My service :
#Injectable()
export class RequestService {
constructor(private http:Http) { }
getData(model): Observable<any>{
return this.http.post("http://127.0.0.1:5000/schedule",model)
.map((response:Response)=>{return response.json() });
}
}
My component:
export class CalendarComponent implements OnInit {
public myDatePickerOptions: IMyDpOptions = {
dateFormat: 'yyyy-mm-dd',
openSelectorOnInputClick: true,
satHighlight: true
};
data:any;
public model:any = { jsdate: new Date()};
constructor(private http:RequestService) { }
ngOnInit() {
}
getData(){
this.http.getData(this.model.formatted).subscribe(result => {
this.data = result;
});
console.log(this.model.formatted)
}
the error:
zone.js:2933 POST http://127.0.0.1:5000/schedule 400 (BAD REQUEST)
ERROR Response {_body: "<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final/…object: Extra data: line 1 column 5 (char 4)</p>↵", status: 400, ok: false, statusText: "BAD REQUEST", headers: Headers, …}
And this is structure of this.model:
{date: {…}, jsdate: Sat Jan 27 2018 00:00:00 GMT+0300 (RTZ 2 (зима)), formatted: "2018-01-27", epoc: 1517000400}
What should I send to get a normal reponse?
Use this
this.http.getData({date: this.model.formatted})
And modify your post methot like that
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post("http://127.0.0.1:5000/schedule", JSON.stringify(model), {headers: headers})

Categories