Question about request of fastapi, request post - python-requests

I use fastapi to build my web-server.
I need to know the ip address of client so I follow this guildline https://fastapi.tiangolo.com/advanced/using-request-directly/
This is my router function
from fastapi import Request
...
#router.post("/login")
async def login(id: str, password: str, request: Request):
My client do like this
import requests
...
response = requests.post(my_url, json={"id": id, "password": password})
But client gets this message:
{"detail":[{"loc":["query","dn"],"msg":"field required","type":"value_error.missing"},{"loc":["query","password"],"msg":"field required","type":"value_error.missing"}]}
Server gives this message:
"POST /jobs/login HTTP/1.1" 422 Unprocessable Entity
So I think the Request field is needed or the transmitted data format is wrong..
Is there any solution for this?

You'll have to explicitly tell FastAPI that you want to retrieve each parameter from the JSON body if you haven't defined a Pydantic input schema.
from fastapi import Request, FastAPI, APIRouter, Body
app = FastAPI()
router = APIRouter()
#router.post("/login")
async def login(request: Request, id: str = Body(...), password: str = Body(...)):
print(id, password)
app.include_router(router)
To define a schema for a login request, create a Pydantic class that inherits from BaseModel:
from fastapi import Request, FastAPI, APIRouter, Body
from pydantic import BaseModel
class LoginSchema(BaseModel):
id: str
password: str
app = FastAPI()
router = APIRouter()
#router.post("/login-schema")
async def login(request: Request, login: LoginSchema):
print(login.id, login.password)
app.include_router(router)

I think I found out what is the issue and why you get 422 Unprocessable Entity Error.
async def login(id: str, password: str, request: Request):
id and password are query parameters but you are trying to pass JSON data instead of query parameters.
response = requests.post(my_url, json={"id": id, "password": password})
If you want this code to work you should pass id and password as query parameters
import requests
my_url = "http://127.0.0.1:8000/login"
id = "1"
password = "hello"
params = {'id': id, 'password': password}
response = requests.post(my_url, params=params)
here is the whole code.
main.py
from fastapi import Request, FastAPI
app = FastAPI()
#app.post("/login")
async def login(id: str, password: str, request: Request):
ip = request.client.host
return {
"id":id,
"password": password,
"host": ip
}
test.py
import requests
my_url = "http://127.0.0.1:8000/login"
id = "1"
password = "hello"
params = {'id': id, 'password': password}
response = requests.post(my_url, params=params)

Related

Can't access my router in fastapi with postman

If I try to access my router(/users/signup)
with postman(send to http://127.0.0.1:8000/users/signup), it keeps the response "detail": "Not Found"
and fastapi is showing
api | INFO: 172.19.0.1:43736 - "GET /users/signup HTTP/1.1" 404 Not Found
Originally I tried to use post instead of get but it doesn't work anyway
Here is the code in my main.py in the app folder and user.py in the routers folder
main.py:
import os
import sys
from .database import init_db
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
baseurl = os.path.dirname(os.path.abspath(__file__))
from fastapi import FastAPI, APIRouter
from .routers.user import router as user_router
from .routers.article import router as article_router
from fastapi.middleware.cors import CORSMiddleware
router = APIRouter()
router.include_router(user_router, prefix="/users",tags=["users"])
router.include_router(article_router, prefix="/articles",tags=["articles"])
app = FastAPI()
origins = ["http://localhost:3000"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
#app.on_event("startup")
async def on_startup():
await init_db()
#app.get("/")
async def root():
return {"message ": " Welcome Fastapi"}
#app.get("/hello/{name}")
async def say_hello(name: str):
return {"message": f"Hello {name}"}
user.py
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
import app.repository.user as dao
from app.admin.utils import currentTime
from app.database import get_db
from app.schemas.user import UserDTO
from app.admin.utils import currentTime
router = APIRouter()
#router.get("/signup")
async def signup(user: UserDTO, db: Session = Depends(get_db)):
print(f" 회원가입에 진입한 시간: {currentTime()} ")
print(f"SignUp Inform : {user}")
result = dao.signup(user, db)
if result == "":
result = "failure"
return {"data": result}
#router.post("/login")
async def login(user: UserDTO, db: Session = Depends(get_db)):
return_user = dao.login(user, db)
print(f"로그인 정보 : {return_user}")
return {"data": return_user}
#router.put("/modify/{id}")
async def update(id: str, item: UserDTO, db: Session = Depends(get_db)):
dao.update(id, item, db)
return {"data": "success"}
#router.delete("/delete/{id}", tags=['age'])
async def delete(id: str, item: UserDTO, db: Session = Depends(get_db)):
dao.delete(id, item, db)
return {"data": "success"}
#router.get("/page/{page}")
async def get_users(page: int, db: Session = Depends(get_db)):
ls = dao.find_users(page, db)
return {"data": ls}
#router.get("/email/{id}")
async def get_user(id: str, db: Session = Depends(get_db)):
dao.find_user(id, db)
return {"data": "success"}
#router.get("/point/{search}/{page}")
async def get_users_by_point(search: int, page: int, db: Session = Depends(get_db)):
dao.find_users_by_point(search, page, db)
return {"data": "success"}
What I was expecting is send {"user_email": "hong#naver.com", "id": "hong1234", "username": "홍길동", "password": "hong1234", "cpassword": "hong1234"} by using postman and recieve data:success and enrollment the data to DB
I tried the post method and get method and change router address but it didn't work anyway
You need to include router in your app too:
router = APIRouter()
router.include_router(user_router, prefix="/users",tags=["users"])
router.include_router(article_router, prefix="/articles",tags=["articles"])
app = FastAPI()
app.include_router(router) # Add this.
Alternatively remove main router:
# Remove router = APIRouter()
app = FastAPI()
app.include_router(user_router, prefix="/users",tags=["users"])
app.include_router(article_router, prefix="/articles",tags=["articles"])

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"])
]

how to route from one api to other fastapi

I am trying to redirect request from protected_api() to login() function in fastapi. but it fails with messages
Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be "http" or "https" for CORS request.
what could be the issue and how can be redirect from one api to other
#app.get("/protected_api")
async def protected_api():
resp = RedirectResponse("https://localhost:5000/token")
return resp
#app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()): # login function to get access token
print('In login fun value of form_data dict.....%s' % form_data.__dict__)
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(seconds=ACCESS_TOKEN_EXPIRE_SECONDS)
access_token = create_access_token(
data={"sub": user.username}, expires_delta=access_token_expires
)
print('Value of access_token in login fun ......%s\n' % access_token)
return {"access_token": access_token, "token_type": "bearer"}
You're having clients ping A (/protected_api) but sending a response from B (/token). They are not the same endpoint, so this involves CORS. Try using CORSMiddleware with FastAPI to resolve this issue.

pytest with httpx.AsyncClient cannot find newly created database records

I am trying to setup pytest with httpx.AsyncClient and sqlalchemy AsyncSession with FastAPI. Everything practically mimics the tests in FastAPI Fullstack repo, except for async stuff.
No issues with CRUD unit tests. The issue arises when running API tests using AsyncClient from httpx lib.
The issue is, any request made by client only has access to the users (in my case) created before initializing (setting up) the client fixture.
My pytest conftest.py setup is like this:
from typing import Dict, Generator, Callable
import asyncio
from fastapi import FastAPI
import pytest
# from sqlalchemy.orm import Session
from sqlalchemy.ext.asyncio import AsyncSession
from httpx import AsyncClient
import os
import warnings
import sqlalchemy as sa
from alembic.config import Config
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import sessionmaker
async def get_test_session() -> Generator:
test_engine = create_async_engine(
settings.SQLALCHEMY_DATABASE_URI + '_test',
echo=False,
)
# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_sess = sessionmaker(
test_engine, expire_on_commit=False, class_=AsyncSession
)
async with async_sess() as sess, sess.begin():
yield sess
#pytest.fixture(scope="session")
async def async_session() -> Generator:
test_engine = create_async_engine(
settings.SQLALCHEMY_DATABASE_URI + '_test',
echo=False,
pool_size=20, max_overflow=0
)
# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_sess = sessionmaker(
test_engine, expire_on_commit=False, class_=AsyncSession
)
yield async_sess
#pytest.fixture(scope="session")
async def insert_initial_data(async_session:Callable):
async with async_session() as session, session.begin():
# insert first superuser - basic CRUD ops to insert data in test db
await insert_first_superuser(session)
# insert test.superuser#example.com
await insert_first_test_user(session)
# inserts test.user#example.com
#pytest.fixture(scope='session')
def app(insert_initial_data) -> FastAPI:
return FastAPI()
#pytest.fixture(scope='session')
async def client(app: FastAPI) -> Generator:
from app.api.deps import get_session
app.dependency_overrides[get_session] = get_test_session
async with AsyncClient(
app=app, base_url="http://test",
) as ac:
yield ac
# reset dependencies
app.dependency_overrides = {}
So in this case, only the superuser test.superuser#example.com and normal user test.user#example.com are available during running API tests. e.g., code below is able to fetch the access token just fine:
async def authentication_token_from_email(
client: AsyncClient, session: AsyncSession,
) -> Dict[str, str]:
"""
Return a valid token for the user with given email.
"""
email = 'test.user#example.com'
password = 'test.user.password'
user = await crud.user.get_by_email(session, email=email)
assert user is not None
data = {"username": email, "password": password}
response = await client.post(f"{settings.API_V1_STR}/auth/access-token",
data=data)
auth_token = response.cookies.get('access_token')
assert auth_token is not None
return auth_token
but, the modified code below doesn't - here I try to insert new user, and then log in to get access token.
async def authentication_token_from_email(
client: AsyncClient, session: AsyncSession,
) -> Dict[str, str]:
"""
Return a valid token for the user with given email.
If the user doesn't exist it is created first.
"""
email = random_email()
password = random_lower_string()
user = await crud.user.get_by_email(session, email=email)
if not user:
user_in_create = UserCreate(email=email,
password=password)
user = await crud.user.create(session, obj_in=user_in_create)
else:
user_in_update = UserUpdate(password=password)
user = await crud.user.update(session, db_obj=user, obj_in=user_in_update)
assert user is not None
# works fine up to this point, user inserted successfully
# now try to send http request to fetch token, and user is not found in the db
data = {"username": email, "password": password}
response = await client.post(f"{settings.API_V1_STR}/auth/access-token",
data=data)
auth_token = response.cookies.get('access_token')
# returns None.
return auth_token
What is going on here ? Appreciate any help!
Turns out all I needed to do is, for reason I do not understand, is to define the FastAPI dependency override function inside the client fixture:
before
async def get_test_session() -> Generator:
test_engine = create_async_engine(
settings.SQLALCHEMY_DATABASE_URI + '_test',
echo=False,
)
# expire_on_commit=False will prevent attributes from being expired
# after commit.
async_sess = sessionmaker(
test_engine, expire_on_commit=False, class_=AsyncSession
)
async with async_sess() as sess, sess.begin():
yield sess
#pytest.fixture(scope='session')
async def client(app: FastAPI) -> Generator:
from app.api.deps import get_session
app.dependency_overrides[get_session] = get_test_session
async with AsyncClient(
app=app, base_url="http://test",
) as ac:
yield ac
# reset dependencies
app.dependency_overrides = {}
after
#pytest.fixture(scope="function")
async def session(async_session) -> Generator:
async with async_session() as sess, sess.begin():
yield sess
#pytest.fixture
async def client(app: FastAPI, session:AsyncSession) -> Generator:
from app.api.deps import get_session
# this needs to be defined inside this fixture
# this is generate that yields session retrieved from `session` fixture
def get_sess():
yield session
app.dependency_overrides[get_session] = get_sess
async with AsyncClient(
app=app, base_url="http://test",
) as ac:
yield ac
app.dependency_overrides = {}
I'd appreciate any explanation of this behavior. Thanks!

How to get graphiql ide while dependancy Injection in graphql fastapi?

Here is my code so far, I want to inject AuthJWT as dependancy:
from starlette.graphql import GraphQLApp
from starlette.requests import Request as Rq
from fastapi_jwt_auth import AuthJWT
graphql_app = GraphQLApp(schema=graphene.Schema(query=Query, mutation=Mutation))
#router.post("/gql")
async def graph(request: Rq, Authorize: AuthJWT = Depends() ):
request.state.authorize = Authorize
return await graphql_app.handle_graphql(request=request)
app.include_router(router)
Its working fine with post request on insomnia ide but I am unable to see the graphiql ide in the browser in "localhost:8000/gql" url
It gives error: "GET /gql HTTP/1.1" 405 Method Not Allowed
How can I get the graphiql ide?
Is it possible to get the ide by Custom Request and APIRoute class? https://fastapi.tiangolo.com/advanced/custom-request-and-route/
Answer by #IndominusByte
import graphene
from fastapi import FastAPI, Request, Depends
from fastapi_jwt_auth import AuthJWT
from starlette.graphql import GraphQLApp
from starlette.datastructures import URL
from pydantic import BaseModel
class Settings(BaseModel):
authjwt_secret_key: str = "secret"
#AuthJWT.load_config
def get_config():
return Settings()
class Query(graphene.ObjectType):
hello = graphene.String(name=graphene.String(default_value="stranger"))
def resolve_hello(self, info, name):
authorize = info.context['request'].state.authorize
access_token = authorize.create_access_token(subject=name)
return "Hello " + name + "access_token" + access_token
app = FastAPI()
graphql_app = GraphQLApp(schema=graphene.Schema(query=Query))
# graphiql ide path here
#app.get('/')
async def graphiql(request: Request):
request._url = URL('/gql')
return await graphql_app.handle_graphiql(request=request)
# use the path for frontend request
#app.post('/gql')
async def graphql(request: Request, authorize: AuthJWT = Depends()):
request.state.authorize = authorize
return await graphql_app.handle_graphql(request=request)
https://github.com/IndominusByte/fastapi-jwt-auth/issues/28

Resources