FastAPI modify request model before parsing based on header - fastapi

I have an immutable Pydantic model (Item) that requires a field shape_mode that is schematic or realistic. The default value of the shape_mode should depend on the response class:
for application/json default is schematic
for image/png default is realistic
The question is: can I modify the field shape_mode based on the accept header, before the body is parsed to an Item object?
from enum import Enum
from fastapi import FastAPI, Request
from pydantic import BaseModel
class ShapeModeEnum(str, Enum):
schematic = "schematic"
realistic = "realistic"
class Item(BaseModel):
name: str
shape_mode: ShapeModeEnum
class Config:
allow_mutation = False
extra = "forbid"
strict = True
validate_assignment = True
app = FastAPI()
RESPONSE_CLASSES = {
"application/json": "schematic",
"image/png": "realistic",
}
#app.post("/item")
async def create_item(
request: Request,
item: Item,
):
accept = request.headers["accept"]
return [item, accept]

You could do this by combining a couple of concepts; inheriting BaseModels and dependencies. Below is a working example (although not really robust; it will throw HTTP500 if you try to post a request with a non-valid accept header.
from enum import Enum
from fastapi import Depends, FastAPI, Request
from pydantic import BaseModel
app = FastAPI()
class ShapeModeEnum(str, Enum):
schematic = "schematic"
realistic = "realistic"
class ItemBase(BaseModel):
name: str
class Item(ItemBase):
shape_mode: ShapeModeEnum
class Config:
# allow_mutation = False
extra = "forbid"
strict = True
validate_assignment = True
def get_real_item(request: Request, item: ItemBase) -> Item:
RESPONSE_CLASSES = {"application/json": "schematic", "image/png": "realistic"}
return Item(
name=item.name, shape_mode=RESPONSE_CLASSES[request.headers.get("Accept", None)]
)
#app.post("/item")
async def get_item(item: Item = Depends(get_real_item)):
return item
#app.get("/")
async def root():
return {"hello": "world"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Calling with curl:
% curl -X 'POST' \
'http://localhost:8000/item' \
-H 'accept: image/png' \
-H 'Content-Type: application/json' \
-d '{
"name": "string"
}'
{"name":"string","shape_mode":"realistic"}%
% curl -X 'POST' \
'http://localhost:8000/item' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"name": "string"
}'
{"name":"string","shape_mode":"schematic"}%

Related

This Starlette Code works but only with GET, and I need POST

I was able to build from a sample Starlette example a piece of code that gets Basic Auth username and password, reads a header, and grabs the json body. But it only does so if I use "GET" instead of post, and I have not been able to figure out how to change the accepted method to POST. (The application I am trying to host for only uses POST. Is it a simple thing to get the POST method to work, or is this a rewrite?
from starlette.applications import Starlette
from starlette.authentication import requires
from starlette.authentication import (
AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import (PlainTextResponse, JSONResponse)
from starlette.routing import Route
import base64
import binascii
class BasicAuthBackend(AuthenticationBackend):
async def authenticate(self, conn):
if "Authorization" not in conn.headers:
return
auth = conn.headers["Authorization"]
try:
scheme, credentials = auth.split()
if scheme.lower() != 'basic':
return
decoded = base64.b64decode(credentials).decode("ascii")
except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
raise AuthenticationError('Invalid basic auth credentials')
username, _, password = decoded.partition(":")
global my_user
global my_pass
my_user = username
my_pass = password
# TODO: You'd want to verify the username and password here.
return AuthCredentials(["authenticated"]), SimpleUser(username)
async def homepage(request):
if request.user.is_authenticated:
body = await request.json()
return JSONResponse({"user": my_user, "password": my_pass, "header": request.headers['client_id']}, body )
return PlainTextResponse('Hello, you')
routes = [
Route("/testpath", endpoint=homepage)
]
middleware = [
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]
app = Starlette(debug=True, routes=routes, middleware=middleware)
You need mention that your route accepts POST method.
async def homepage(request):
if request.user.is_authenticated:
body = await request.json()
return JSONResponse({"user": my_user, "password": my_pass, "header": request.headers['client_id']})
return PlainTextResponse('Hello, you')
routes = [
Route("/testpath", endpoint=homepage, methods=["POST"])
]

pendingToken in response payload of signInWithIdp

What does the pendingToken parameter mean, in the response payload of a successful POST to signInWithIdp in the Firebase Authentication REST API (Sign in with OAuth credential endpoint) ?
curl -X POST \
'https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key={{key}}' \
-H 'Accept: */*' \
.....
-H 'Content-Type: application/json' \
-H 'Host: identitytoolkit.googleapis.com' \
-d '{
"postBody": "id_token=eyJraWQiO....JWCJHHrxeg&providerId=apple.com",
"requestUri": "https://myapp.firebaseapp.com/__/auth/handler",
"returnIdpCredential": true,
"returnSecureToken": true
}'
{
"federatedId": "apple.com/ABCDE.abcde1234567895ab21ab098234.1234",
"providerId": "apple.com",
"email": "user#privaterelay.appleid.com",
"emailVerified": true,
"localId": "12345678abcdef",
"idToken": "eyJhbGciOiJSUzI1N...RiFQ",
"refreshToken": "AEu4I...N0DuQ",
"expiresIn": "3600",
"oauthIdToken": "eyJraWQiOiJB...Hrxeg",
"rawUserInfo": "{{...user info...}}",
"isNewUser": true,
"kind": "identitytoolkit#VerifyAssertionResponse",
"pendingToken": "AMzJoSn....jNlcw" <-------
}
pendingToken is a private property accidentally left in public header.
The team at Firebase are working to remove it, as they say
"It's a field of no use case at all".
You can read more about it here

Angular 5 to CherryPy POST

I am trying to send data to a cherrypy backend from an Angular 5 app. I am able to call the correct cherrypy function and get a 200 response, but none of my parameters are getting through.
One thing that caught my eye was that my payload, when I click view source in the chrome debugger looks like this {"username":"admin","password":"admin"}. Is this supposed to be formatted differently?
Here's my post:
getUserDetails(username, password) {
const _headers = new HttpHeaders();
const headers = _headers.append('Content-Type', 'application/json');
const options = {headers: headers };
this.data = JSON.stringify({ username, password });
return this.http.post(this.url1 + 'login', this.data, options )
.subscribe(data => {
console.log(data);
});
}
Again, this gets to the proper endpoint, just none of the data gets through.
Here's cherryPy:
Login Function:
class authServer(object):
#cherrypy.expose
def login(self,*args, **kwargs):
print(type(args),args, kwargs)
I've tried various args, if I have username and password I get the error saying missing parameters.
Here's the cherrypy configuration.
def CORS():
"""Allow AngularJS apps not on the same server to use our API
"""
cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
cherrypy.response.headers["Access-Control-Allow-Headers"] = \
"content-type, Authorization, X-Requested-With"
cherrypy.response.headers["Access-Control-Allow-Methods"] = 'GET, POST'
if __name__ == '__main__':
cherrypy.tools.CORS = cherrypy.Tool('before_handler', CORS)
cherrypy.log.error_log.propagate = False
cherrypy.log.access_log.propagate = False
cherrypy.config.update({'server.thread_pool': 30,
'tools.CORS.on': True,
'server.socket_host': '0.0.0.0',
'server.socket_port': 8081})
cherrypy.quickstart(authServer(), '/')
I've made countless posts to cherrypy before. The only difference here is the frontend I'm using. Any help would be greatly appreciated.
Thanks,
Turns out it was a CORS issue. I changed my CORS function from this:
def CORS():
cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
cherrypy.response.headers["Access-Control-Allow-Headers"] = \
"content-type, Authorization, X-Requested-With"
cherrypy.response.headers["Access-Control-Allow-Methods"] = 'GET, POST'
to this:
def CORS():
if cherrypy.request.method == 'OPTIONS':
cherrypy.response.headers['Access-Control-Allow-Methods'] = 'POST'
cherrypy.response.headers['Access-Control-Allow-Headers'] = 'content-type'
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
return True
else:
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
cherrypy.tools.CORS = cherrypy._cptools.HandlerTool(CORS)
Above my main function I put this:
#cherrypy.expose
#cherrypy.config(**{'tools.CORS.on': True})
#cherrypy.tools.json_in()
#cherrypy.tools.json_out()

How to pass X-Auth-Token with Meteor's HTTP.call?

How to pass X-Auth-Token with Meteor's HTTP.call?
E.g. to do something like this:
curl -X GET \
--header "X-Auth-Token: 1234567890abcdeff978d137bc01a2" \
https://example.com/api/call
I found an answer on Meteor's forum:
options = {
headers: { 'X-Auth-Token' : '1234567890abcdeff978d137bc01a2' }
}
A bit more elaborated, in CoffeeScript:
res = HTTP.call 'GET', 'https://example.com/api/call',
headers:
'X-Auth-Token': auth_token
params:
a: 1
b: 2

How to specify constraint in Meteor Http call?

I need to pass a where constraint(where UserName = "User1") in Meteor http call for Parse Rest APIs. Currently, the result that I get after the below API call includes all the entries not just those where UserName is User1.
var authUrl = "https://api.parse.com/1/classes/ImageData";
Meteor.http.call("GET", authUrl, {
headers: {
"X-Parse-Application-Id": "2CMX1b4JY5xCOPrYEbSc69ucNDDh9pl5yFeqv3A3",
"X-Parse-REST-API-Key": "9UWpw6H7UuBaOEQgT7R3ANQ3rE67yxZxcMHJJaBu",
"content-type": "application/json"
},
params: {
"UserName": "User1",
}
}, function(error, result) {
console.log(JSON.parse(result.content));
}
);
The parse documentation for such an HTTP call in curl representation is:
curl -X GET \
-H "X-Parse-Application-Id: 2CMX1b4JY5xCOPrYEbSc69ucNDDh9pl5yFeqv3A3" \
-H "X-Parse-REST-API-Key: 9UWpw6H7UuBaOEQgT7R3ANQ3rE67yxZxcMHJJaBu" \
-G \
--data-urlencode 'where={"UserName":"User1"}' \
https://api.parse.com/1/classes/ImageData
How can I correctly write this in Meteor?
This seems like it works:
var util = Npm.require('util');
var url = 'https://api.parse.com/1/classes/ImageData';
var result = HTTP.get(url, {
headers: {
'X-Parse-Application-Id': '2CMX1b4JY5xCOPrYEbSc69ucNDDh9pl5yFeqv3A3',
'X-Parse-REST-API-Key': '9UWpw6H7UuBaOEQgT7R3ANQ3rE67yxZxcMHJJaBu',
'content-type': 'application/json'
},
query: 'where={"UserName":"User1"}'
});
console.log(util.inspect(result.data, {depth: null}));
Notes
Meteor.http.call is deprecated. Use the HTTP API instead. Note you will need to $ meteor add http.
Because you need a string and not a key/value pair, use query instead of params. For a GET, both are placed into the query string but your original code made the query ?Username=User1 rather than ?where....

Resources