How to pass unencoded URL in FastAPI/Swagger UI via GET method? - python

I would like to write a FastAPI endpoint, with a Swagger page (or something similar) that will accept a non-encoded URL as input. It should preferably use GET, not POST method.
Here's an example of a GET endpoint that does require double-URL encoding.
#app.get("/get_from_dub_encoded/{double_encoded_url}")
async def get_from_dub_encoded(double_encoded_url: str):
"""
Try https%253A%252F%252Fworld.openfoodfacts.org%252Fapi%252Fv0%252Fproduct%252F7622300489434.json
"""
original_url = urllib.parse.unquote(urllib.parse.unquote(double_encoded_url))
response = requests.get(original_url)
return response.json()
Which generates a Swagger interface as below.
The following PUT request does solve my problem, but the simplicity of a GET request with a form is better for my co-workers.
class InputModel(BaseModel):
unencoded_url: AnyUrl = Field(description="An unencoded URL for an external resource", format="url")
#app.post("/unencoded-url")
def unencoded_url(inputs: InputModel):
response = requests.get(inputs.unencoded_url)
return response.json()
How can I deploy a convenient interface like that without requiring users to write the payload for a PUT request or to perform double URL encoding?
This post has some helpful related discussion, but doesn't explicitly address the FORM solution: How to pass URL as a path parameter to a FastAPI route?

You can use Form instead of query parameters as payload.
from fastapi import FastAPI, Form
import requests
app = FastAPI()
#app.post("/")
def get_url(url: str = Form()):
"""
Try https://world.openfoodfacts.org/api/v0/product/7622300489434.json
"""
response = requests.get(url)
return response.json()
Swagger interface would look like:
You'll need to install python-multipart.
Tip: Don't use async endpoint if you are using requests or any non-async library.

Related

How to read the request body using orjson library in FastAPI?

I am writing code to receive a JSON payload in FastAPI.
Here is my code:
from fastapi import FastAPI, status, Request
from fastapi.responses import ORJSONResponse
import uvicorn
import asyncio
import orjson
app = FastAPI()
#app.post("/", status_code = status.HTTP_200_OK)
async def get_data(request: Request):
param = await request.json()
return param
However, what I want is request.json() to be used with orjson instead of the default json library of Python.
Any idea how to address this problem? Please help me, thanks.
Reading request data using orjson
When calling await request.json(), FastAPI (actually Starlette) first reads the body (using the .body() method of the Request object), and then calls json.loads() (using the standard json library of Python) to return a dict/list object to you inside the endpoint (see the implementation here)—it doesn't use .dumps(), as you mentioned in the comments section, as that method is used to serialise a Python object into JSON.
Hence, to read/convert the request body using orjson instead, you can use the below (if you would like to retrieve the raw body within a def instead of async def endpoint, please have a look at this answer):
from fastapi import FastAPI, Request
import orjson
app = FastAPI()
#app.post('/')
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return 'success'
Returning response data using orjson
When returning data such as dict, list, etc, FastAPI will automatically convert that return value into JSON, using the Python standard json.dumps(), after inspecting every item inside and making sure it is serializable with JSON, using the JSON Compatible Encoder (see this answer for more details). Hence, if you would like to use the orjson library instead, you would need to send a custom Response directly, as described in this answer. Example:
from fastapi import FastAPI, Request
import orjson
app = FastAPI()
#app.post('/')
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return Response(orjson.dumps(data), media_type='application/json')
Alternatively, you can use the ORJSONResponse provided by FastAPI (still make sure you have the orjson libray installed, as well as the content that you are returning is serializable with JSON). Have a look at futher documentation here and here on how to customise and/or set ORJSONResponse as the default response class (the implementation of ORJSONResponse can be found here). Example:
from fastapi import FastAPI, Request
from fastapi.responses import ORJSONResponse
import orjson
app = FastAPI()
#app.post('/', response_class=ORJSONResponse)
async def submit(request: Request):
body = await request.body()
data = orjson.loads(body)
return ORJSONResponse(data)
Please make sure to have a look here, here, as well as here and here to learn about the various approaches of sending JSON data to a FastAPI backend, and how to define an endpoint to expect and validate JSON data, instead of relying on using await request.json() (which is useful when the app requires passing arbitrary JSON data, but does not perform any validation on the data).

How To Pass Parameters From A User Into An Endpoint To Process Logic From User Input

So I have a Python script that sends a get request to an API and returns information. For educational purposes, I wanted to create an API that I could pass parameters to which would return results from that script. I couldn't really find a similar example on stackoverflow or on the official flask documentation for an example that meets what I'm looking for. Everything I'm seeing is for returning data that you already have existing in a database or in a json file.
Here's an example of the script that I have right now.
script.py
api_url = www.statetaxes.com
parameters = {"taxinfo": "state:Texas county:soaker_county zip_code:78331"}
token = "abcd123456"
response= request.get(api_url, params=parameters, headers={"authentication":token})
print(response.json()) >----- [{tax rate: 1.5%, education_quality:great, crime_rate:0}]
The user would set the parameters in a config file somewhere like so:
[taxinfo]
county: some_county
zip_code: 12345
I'd want to read that text file, grab those parameters and push it to an API endpoint. I can figure out the part on how to read those parameters and send it to the API, but I'm not sure how I'm supposed to be configuring the rest of my FLASK app. I'd assume that I'd have to take those parameters which I'm reading from the text file as an argument. Am I on the right path here?
#app.route('/taxes/<taxinfo>', methods = ['GET', 'POST', 'PUT' ])
def state_taxes(taxinfo):
def api_call():
api_url = www.statetaxes.com
parameters=taxinfo
token = "abcd123456"
response= request.get(api_url, params=parameters, headers={"authentication":token})
return api_call
if __name__== "__main__":
app.run(debug=True)

hosting an image with the flask and then processing the same using another view function in the same code

so I am hosting an image using flask and then I want to do a post request to an API using the url all in the same code:
#app.route('/host')
def host():
return send_from_directory("C:/images", "image1.png")
#app.route('/post')
def post():
response = requests.post(url, data={'input':'<url for host>', headers=headers)
return jsonify(response.json())
I believe as both these view functions are in the same python file, post() gets blocked.
Is there a workaround this problem ?
PS: if I host images on a different machine, it works, but that's not what I desire.
Thanks!
I think there are some problems with your code.
First, I don't believe there is an #app.post() decorator in Flask. My guess is that you were trying to specify that that route should be POSTed to by your users. The way to do that would be #app.route('/post', methods=['POST']).
Next, it seems like you want the /post endpoint to send a POST request to a user-specified(?) URL when the user sends an HTTP request to this endpoint. The way you would do that for a user-specified / user-POSTed URL is something like this (I haven't run this code to test it):
#app.route('/send_post_request', methods=['POST'])
def send_post_request():
user_posted_data = json.loads(request.data)
user_specified_url = user_posted_data['url']
dict_to_post= { 'input': url_for('hosts') }
headers = {} # Fill these in
response = requests.post(user_specified_url , json=dict_to_post, headers=headers)
return jsonify(response.json())
If the URL to send the POST request to is known by the server, you could have your user simply send a GET request:
#app.route('/send_post_request', methods=['GET'])
def send_post_request():
dict_to_post = { 'input': url_for('hosts') }
headers = {} # Fill these in
server_specified_url = '' # Fill this in
response = requests.post(server_specified_url, json=dict_to_post, headers=headers)
return jsonify(response.json())

Redirect to external url while sending a JSON object or String from flask app

I have a flask application where I need to redirect to an URL outside flask root path (www.externalurl.com). This URL, on the client side, requires consuming some information (either a JSON object or a String). How to send this information along while redirecting?
Consider below example,
from flask import Flask,redirect
app = Flask(__name__)
#app.route('/<some_path>')
def some_method():
# some_info = {'key1' : 'value1'}
response = redirect("http://www.externalurl.com", code=302)
return response
I have come across setting headers & cookies. But, I came to know that the cookie size should be less than 4k and I don't want this limitation. Are there any standard ways of doing this?
You mentioned that you have control over externalurl.com
You can associate some key or hash with the JSON object and store them to be retrieved by a script on externalurl.com later.
So, store it in a database at example.com and setup an endpoint for it to be requested later.
#app.route('/<some_path>')
def some_method():
some_info = {'key1' : 'value1'}
db_entry = JsonInfo(some_info)
db.add(db_entry)
db.commit()
hash = db_entry.hash
response = redirect("http://www.externalurl.com?hash={}".format(hash), code=302)
return response
#app.route('/get-json')
def get_json():
hash = request.args.get("hash")
json_info = JsonInfo.query.filter_by(hash=hash).first()
return jsonify(json_info)
Then on the side of externalurl.com parse the hash from the get request, then use that to make a request to https://example.com/get-json?hash=abc123 to retreive the info.

Flask Restful: change representation based on URL parameter

I am building an API using Flask and Flask-Restful. The API might be accessed by different sort of tools (web apps, automated tools, etc.) and one of the requirement is to provide different representations (let's say json and csv for the sake of the example)
As explained in the restful doc, it's easy to change the serialization based on the content type, so for my CSV serialization I've added this:
#api.representation('text/csv')
def output_csv(data, code, headers=None):
#some CSV serialized data
data = 'some,csv,fields'
resp = app.make_response(data)
return resp
And it's working when using curl and passing the correct -H "Accept: text/csv" parameter.
The issue is that since some browsers might be routed to a url directly to download a csv file, I would like to be able to force my serialization via a url parameter for example http://my.domain.net/api/resource?format=csv where the format=csvwould have the same effect as -H "Accept: text/csv".
I've gone through both Flask and Flask-Restful documentation and I don't see how to correctly handle this.
Simply create a sub-class of Api and override the mediatypes method:
from werkzeug.exceptions import NotAcceptable
class CustomApi(Api):
FORMAT_MIMETYPE_MAP = {
"csv": "text/csv",
"json": "application/json"
# Add other mimetypes as desired here
}
def mediatypes(self):
"""Allow all resources to have their representation
overriden by the `format` URL argument"""
preferred_response_type = []
format = request.args.get("format")
if format:
mimetype = FORMAT_MIMETYPE_MAP.get(format)
preferred_response_type.append(mimetype)
if not mimetype:
raise NotAcceptable()
return preferred_response_type + super(CustomApi, self).mediatypes()
Basically you want to retrieve parameters from GET method. Please refer to:
How do I get the url parameter in a Flask view

Categories