Here is my code:
from fastapi import (FastAPI, BackgroundTasks, UploadFile,
File, Form, Depends, HTTPException, status, Request)
from tortoise.contrib.fastapi import register_tortoise
from models import (User, Business, Product, user_pydantic, user_pydanticIn,
product_pydantic,product_pydanticIn, business_pydantic,
business_pydanticIn, user_pydanticOut)
# signals
from tortoise.signals import post_save
from typing import List, Optional, Type
from tortoise import BaseDBAsyncClient
from starlette.responses import JSONResponse
from starlette.requests import Request
#authentication and authorization
import jwt
from dotenv import dotenv_values
from fastapi.security import (
OAuth2PasswordBearer,
OAuth2PasswordRequestForm
)
# self packages
from emails import *
from authentication import *
from dotenv import dotenv_values
import math
# user image uploads
# pip install python-multipart
from fastapi import File, UploadFile
import secrets
# static files
from fastapi.staticfiles import StaticFiles
# pillow
from PIL import Image
# templates
from fastapi.templating import Jinja2Templates
# HTMLResponse
from fastapi.responses import HTMLResponse
config_credentials = dict(dotenv_values(".env"))
app = FastAPI()
# static files
# pip install aiofiles
app.mount("/static", StaticFiles(directory="static"), name="static")
# authorization configs
oath2_scheme = OAuth2PasswordBearer(tokenUrl = 'token')
# password helper functions
#app.post('/token')
async def generate_token(request_form: OAuth2PasswordRequestForm = Depends()):
token = await token_generator(request_form.username, request_form.password)
return {'access_token' : token, 'token_type' : 'bearer'}
# process signals here
#post_save(User)
async def create_business(
sender: "Type[User]",
instance: User,
created: bool,
using_db: "Optional[BaseDBAsyncClient]",
update_fields: List[str]) -> None:
if created:
business_obj = await Business.create(
business_name = instance.username, owner = instance)
await business_pydantic.from_tortoise_orm(business_obj)
# send email functionality
await send_email([instance.email], instance)
#app.post('/registration')
async def user_registration(user: user_pydanticIn):
user_info = user.dict(exclude_unset = True)
user_info['password'] = get_password_hash(user_info['password'])
user_obj = await User.create(**user_info)
new_user = await user_pydantic.from_tortoise_orm(user_obj)
return {"status" : "ok",
"data" :
f"Hello {new_user.username} thanks for choosing our services. Please check your email inbox and click on the link to confirm your registration."}
# template for email verification
templates = Jinja2Templates(directory="templates")
#app.get('/verification', response_class=HTMLResponse)
# make sure to import request from fastapi and HTMLResponse
async def email_verification(request: Request, token: str):
user = await verify_token(token)
if user and not user.is_verified:
user.is_verified = True
await user.save()
return templates.TemplateResponse("verification.html",
{"request": request, "username": user.username}
)
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
async def get_current_user(token: str = Depends(oath2_scheme)):
try:
payload = jwt.decode(token, config_credentials['SECRET'], algorithms = ['HS256'])
user = await User.get(id = payload.get("id"))
except:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Invalid username or password",
headers={"WWW-Authenticate": "Bearer"},
)
return await user
#app.post('/user/me')
async def user_login(user: user_pydantic = Depends(get_current_user)):
business = await Business.get(owner = user)
logo = business.logo
logo = "localhost:8000/static/images/"+logo
return {"status" : "ok",
"data" :
{
"username" : user.username,
"email" : user.email,
"verified" : user.is_verified,
"join_date" : user.join_date.strftime("%b %d %Y"),
"logo" : logo
}
}
#app.post("/products")
async def add_new_product(product: product_pydanticIn,
user: user_pydantic = Depends(get_current_user)):
product = product.dict(exclude_unset = True)
# to avoid division by zero error
if product['original_price'] > 0:
product["percentage_discount"] = ((product["original_price"] - product['new_price'] )/ product['original_price']) * 100
product_obj = await Product.create(**product, business = user)
product_obj = await product_pydantic.from_tortoise_orm(product_obj)
return {"status" : "ok", "data" : product_obj}
#app.get("/products")
async def get_products():
response = await product_pydantic.from_tortoise_orm(Product.all())
return {"status" : "ok", "data" : response}
#app.get("/products/{id}")
async def specific_product(id: int):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
response = await product_pydantic.from_queryset_single(Product.get(id = id))
print(type(response))
return {"status" : "ok",
"data" :
{
"product_details" : response,
"business_details" : {
"name" : business.business_name,
"city" : business.city,
"region" : business.region,
"description" : business.business_description,
"logo" : business.logo,
"owner_id" : owner.id,
"email" : owner.email,
"join_date" : owner.join_date.strftime("%b %d %Y")
}
}
}
#app.delete("/products/{id}")
async def delete_product(id: int, user: user_pydantic = Depends(get_current_user)):
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
if user == owner:
product.delete()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
return {"status" : "ok"}
# image upload
#app.post("/uploadfile/profile")
async def create_upload_file(file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
business = await Business.get(owner = user)
owner = await business.owner
# check if the user making the request is authenticated
print(user.id)
print(owner.id)
if owner == user:
business.logo = token_name
await business.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
#app.post("/uploadfile/product/{id}")
# check for product owner before making the changes.
async def create_upload_file(id: int, file: UploadFile = File(...),
user: user_pydantic = Depends(get_current_user)):
FILEPATH = "./static/images/"
filename = file.filename
extension = filename.split(".")[1]
if extension not in ["jpg", "png"]:
return {"status" : "error", "detail" : "file extension not allowed"}
token_name = secrets.token_hex(10)+"."+extension
generated_name = FILEPATH + token_name
file_content = await file.read()
with open(generated_name, "wb") as file:
file.write(file_content)
# pillow
img = Image.open(generated_name)
img = img.resize(size = (200,200))
img.save(generated_name)
file.close()
#get product details
product = await Product.get(id = id)
business = await product.business
owner = await business.owner
# check if the user making the request is authenticated
if owner == user:
product.product_image = token_name
await product.save()
else:
raise HTTPException(
status_code = status.HTTP_401_UNAUTHORIZED,
detail = "Not authenticated to perform this action",
headers={"WWW-Authenticate": "Bearer"},
)
file_url = "localhost:8000" + generated_name[1:]
return {"status": "ok", "filename": file_url}
register_tortoise(
app,
db_url='sqlite://database.sqlite3',
modules={'models': ['models']},
generate_schemas = True,
add_exception_handlers = True
)
When im opening up http://127.0.0.1:8000/docs and try the "/registration" request I get the error: Code: 422 Details: Error: Unprocessable Entity`
Response body:
{
"detail": [
{
"loc": [],
"msg": "UNIQUE constraint failed: user.username",
"type": "IntegrityError"
}
]
}
Response headers:
content-length: 95
content-type: application/json
date: Sun,04 Dec 2022 23:22:02 GMT
server: uvicorn
And in the console I get:
127.0.0.1:54566 - "POST /registration HTTP/1.1" 422 Unprocessable Entity
After some debugging I think that the line 86
user_obj = await User.create(**user_info)
Is causing problems, but I can't seem to be able to fix the issue. Feel free to ask for any additional information
UPD: I was followng Princekrampah's guide on youtube, the git link to the repo is: https://github.com/Princekrampah/learningFastAPI/tree/master/shoppingAPI
Related
authentication.py
from datetime import datetime, timedelta
from fastapi import Depends , HTTPException, status, Security
from fastapi.security import OAuth2PasswordBearer, SecurityScopes
from jose import JWTError, jwt
from auth.schemas import TokenData, UserBase
from sqlalchemy.orm import Session
from db.database import get_db
from pydantic import ValidationError
from db.models import UserModel
SECRET_KEY = '7f0ef2549adac85716db6ca433a44c79021e829c8c4dd8e7d9363eb9fc800e73'
ALGORITHM = 'HS256'
ACCESS_TOKEN_EXPIRE_MINUTES = 30
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login",
scopes={"admin":"create content", "user":"interact with content"}
)
def get_user(email: str,db:Session=Depends(get_db)):
user = db.query(UserModel).filter(UserModel.email == email).first()
if not user:
raise HTTPException(status=status.HTTP_404_NOT_FOUND,
details=f"User with email: {email} not found")
return user
async def get_current_user(
security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)
):
if security_scopes.scopes:
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
else:
authenticate_value = f"Bearer"
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": authenticate_value},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_scopes = payload.get("scopes", [])
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
raise credentials_exception
user = get_user(email=token_data.email)
if user is None:
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_data.scopes:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="not enough permission",
headers={"WWW-Authenticate": authenticate_value},
)
return user
def get_current_active_user(
current_user:UserBase = Security(get_current_user, scopes=["admin"])):
if not current_user:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
def create_access_token(data:dict):
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode,SECRET_KEY,algorithm=ALGORITHM)
return encoded_jwt
def verify_token(token:str,credentials_exception):
try:
payload = jwt.decode(token,SECRET_KEY,algorithms=ALGORITHM)
email:str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
return token_data
except JWTError:
raise credentials_exception
login
from fastapi import APIRouter, Depends,HTTPException,status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
from sqlalchemy.orm import Session
from db.models import UserModel
from db.database import get_db
from . import hashing
from auth.oauth import create_access_token
from .hashing import get_password_hash
from .schemas import UserBase
router = APIRouter(tags=['authentication'])
#router.post('/login')
async def login(request:OAuth2PasswordRequestForm=Depends(),db:Session=Depends(get_db)):
user = db.query(UserModel).filter(UserModel.email == request.username).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,detail="Invalid Credentials")
if not hashing.verify_password(request.password,user.password):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,detail="Invalid Password")
access_token = create_access_token(data={"sub":user.username,"scopes":request.scopes})
return {"access_token":access_token,"token_type":"bearer"}
#router.post('/register')
async def register(request:UserBase,db:Session=Depends(get_db)):
new_user = UserModel(
email = request.email,
username = request.username,
password = get_password_hash(request.password),
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
endpoints
from fastapi import APIRouter, Depends, Security
from auth.oauth import get_current_active_user
from routers.schemas import BikeBase
from sqlalchemy.orm import Session
from db.database import get_db
from db.models import BikeModel
from .crud import add_bike
from auth.schemas import UserBase
router = APIRouter(
prefix='/bike',
tags = ['bike']
)
#router.post('/add')
def add_bike(request:BikeBase,user:UserBase=Depends(get_current_active_user),db:Session=Depends(get_db)):
bike = db.query(BikeModel).filter(BikeModel.model==request.model).first()
if bike is None:
return add_bike(request,user)
if request.model in bike:
raise Exception("Bike Model Already present")
return add_bike(request,user)
#router.get('/check')
def check(user:UserBase=Security(get_current_active_user, scopes=["admin"])):
return {"data":"scope working"}
def add_bike(request:BikeBase,user=Security(get_current_active_user, scopes=["admin"]),db:Session=Depends(get_db)):
new_bike = BikeBase(
brand = request.brand.lower(),
model = request.model.lower(),
price = request.price,
mileage = request.mileage,
max_power = request.max_power,
rating = request.rating
)
db.add(new_bike)
db.commit()
db.refresh(new_bike)
return new_bike
On the #router.post('/add') getting the error:
File "/home/raj/code/bike-forum-fastapi/./auth/oauth.py", line 24, in get_user
user = db.query(UserModel).filter(UserModel.email == email).first()
AttributeError: 'Depends' object has no attribute 'query'
When i change some endpoints to async the error changes something with coroutine, i cant remember how to produce it
#router.check('/check') endpoint works fine with scope and permission, but produces error at #router.post('/add')
i new to fastapi, the auth code is directly copied from fastapi-docs, but theres a hardcoded fake database for get_user in docs, i cant figure how to do it with a real database
I have researched similar questions but can't find an answer that works for me.
I would like to get the username or user_id from a session when a user connects to a websocket.
This is what I have in consumers.py:
class PracticeConsumer(AsyncConsumer):
async def websocket_connect(self, event):
print('session data', self.scope['user'])
await self.send({"type": "websocket.accept", })
...
#database_sync_to_async
def get_user(self, user_id):
try:
return User.objects.get(username=user_id).pk
except User.DoesNotExist:
return AnonymousUser()
This is my asgi.py:
"""
ASGI config for restapi project.
It exposes the ASGI callable as a module-level variable named application.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'signup.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(websocket_urlpatterns)
)
)
})
and the user login function + token sent when user logs in:
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
authenticate_kwargs = {
self.username_field: attrs[self.username_field],
"password": attrs["password"],
}
try:
authenticate_kwargs["request"] = self.context["request"]
except KeyError:
pass
user = authenticate(**authenticate_kwargs)
if not user:
return {
'user': 'Username or password is incorrect',
}
token = RefreshToken.for_user(user)
# customizing token payload
token['username'] = user.username
token['first_name'] = user.first_name
token['last_name'] = user.last_name
token['country'] = user.profile.country
token['city'] = user.profile.city
token['bio'] = user.profile.bio
token['photo'] = json.dumps(str(user.profile.profile_pic))
user_logged_in.send(sender=user.__class__, request=self.context['request'], user=user)
if not api_settings.USER_AUTHENTICATION_RULE(user):
raise exceptions.AuthenticationFailed(
self.error_messages["no_active_account"],
"no_active_account",
)
return {
'refresh': str(token),
'access': str(token.access_token),
}
Whenever I print out self.scope['user'] upon connecting, I get AnonymousUser
UPDATE
I tried writing some custom middleware to handle simple JWT authentication:
#database_sync_to_async
def get_user(validated_token):
try:
user = get_user_model().objects.get(id=validated_token["user_id"])
print(f"{user}")
return user
except User.DoesNotExist:
return AnonymousUser()
class JwtAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
try:
UntypedToken(token)
except (InvalidToken, TokenError) as e:
print(e)
return None
else:
decoded_data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print(decoded_data)
scope["user"] = await get_user(validated_token=decoded_data)
return await super().__call__(scope, receive, send)
def JwtAuthMiddlewareStack(inner):
return JwtAuthMiddleware(AuthMiddlewareStack(inner))
However, this gives me this error:
File "C:\Users\15512\Desktop\django-project\peerplatform\signup\middleware.py", line 37, in __call__
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
KeyError: 'token'
I am trying to write a logout function in fastapi. For logging out from server side, I am setting the token expiry time to 0 and sending it to client, expecting that this would invalidate the token right at that movement. However, it is not working as expect and even after logout I am able to access the protected APIs. What is wrong here? It this a good approach to logout in jwt scheme? Please help.
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel
import uvicorn
import pytz
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_SECONDS = 200 # expire the token, even though user has not logged out. Change the time for your testing.
utc=pytz.UTC
fake_users_db = {
"johndoe": {
"username": "johndoe",
"full_name": "John Doe",
"email": "johndoe#example.com",
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
"disabled": False,
},
"ravi": {
"username": "ravi",
"full_name": "ravi singh",
"email": "ravi.singh#gmail.com",
"hashed_password": "$2b$12$ODf2vUEfanF3P1JykF0CgO7jafMA9RWCuqZxLUAqCcQJ1FYxxFROC",
"disabled": False,
},
"alice": {
"username": "alice",
"full_name": "Alice Wonderson",
"email": "alice#example.com",
"hashed_password": "fakehashedsecret2",
"disabled": True,
},
}
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None
expires: datetime
class User(BaseModel):
username: str
email: Optional[str] = None
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
app = FastAPI()
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def get_user(db, username: str):
if username in db:
user_dict = db[username]
return UserInDB(**user_dict)
def authenticate_user(fake_db, username: str, password: str):
user = get_user(fake_db, username)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# get the current user from auth token
async def get_current_user(token: str = Depends(oauth2_scheme)):
# define credential exception
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
# decode token and extract username and expires data
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
exps:int = payload.get("exp")
# validate username
if username is None:
raise credentials_exception
token_data = TokenData(username=username, expires=exps) # exps of int is converted to datetime type
except JWTError:
raise credentials_exception
user = get_user(fake_users_db, username=token_data.username)
if user is None:
raise credentials_exception
# check token expiration
if exps is None:
raise credentials_exception
if utc.localize(datetime.utcnow()) > token_data.expires:
print("Token is expired.\n")
raise credentials_exception
return user
async def get_current_active_user(current_user: User = Depends(get_current_user)):
if current_user.disabled:
raise HTTPException(status_code=400, detail="Inactive user")
return current_user
#app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()): # login function to get access token
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
)
return {"access_token": access_token, "token_type": "bearer"}
#app.post("/logout", response_model=Token)
async def logout(current_user: User = Depends(get_current_active_user)): # logout function to delete access token
token_data = TokenData(username=current_user.username, expires=0)
return token_data
#return "User logout sucessful."
#app.get("/users/me/", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_active_user)):
return current_user
#app.get("/users/me/items/")
async def read_own_items(current_user: User = Depends(get_current_active_user)):
return [{"item_id": "Foo", "owner": current_user.username}]
#app.get("/protected_hi")
async def protected_hi(current_user: User = Depends(get_current_active_user)):
return "Hi! How are you? You are in a protected Zone."
#app.get("/unprotected_hi")
async def unprotected_hi():
return "Hi! How are you? You are in an un-protected Zone."
if __name__ == "__main__":
import uvicorn
uvicorn.run('jwt_fourth:app', host="0.0.0.0", port=5000, reload=True)
Is it possible to add a custom field into the OAuth2PasswordRequestForm? I need the content of the field to be returned in the token as "rentalpoint". Now in the return "rentalpoint" holds a fixed string "rentalpoint": "cologne" but i want to replace it with something like this: "rentalpoint": request.rentalpoint. How can i do that?
#router.post('/')
def login(request: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(database.get_db)):
user = db.query(models.User).filter(models.User.email == request.username).first()
if not user:
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND, detail='Invalid credentials')
if not Hash.verify(user.password, request.password):
raise HTTPException(status_code = status.HTTP_404_NOT_FOUND, detail='Incorrect password')
access_token = JWTtoken.create_access_token(data={"sub": user.email})
return {"access_token": access_token, "token_type": "bearer", "rentalpoint": "cologne", "user_id": request.username}
I need to fulfill a request to two services.
The code looks like this:
async def post1(data):
response = await aiohttp.request('post', 'http://', json=data)
json_response = await response.json()
response.close()
return json_response
async def get2():
response = await aiohttp.request('get', 'http://')
json_response = await response.json()
response.close()
return json_response
async def asynchronous(parameters):
task1 = post1(parameters['data'])
task2 = get2()
result_list = []
for body in await asyncio.gather(task1, task2):
result_list.append(body)
return result_list
If I run the code locally, it's OK. The code looks like this:
if __name__ == "__main__":
ioloop = asyncio.get_event_loop()
parameters = {'data': data}
result = ioloop.run_until_complete(asynchronous(parameters))
ioloop.close()
print(result)
I get the right result. But if I try to execute code from the DRF method, an error occurs:
TypeError: object _SessionRequestContextManager can't be used in
'await' expression
example code that I run:
.....
class MyViewSet(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
......
ioloop = asyncio.get_event_loop()
result = ioloop.run_until_complete(asynchronous(serializer.data)) # <<<<< error here
ioloop.close()
......
return Response(serializer.data, status=status.HTTP_201_CREATED)
Please, tell me what the problem may be?
The object returned by aiohttp.request cannot be awaited, it must be used as an async context manager. This code:
response = await aiohttp.request('post', 'http://', json=data)
json_response = await response.json()
response.close()
needs to changed to something like:
async with aiohttp.request('post', 'http://', json=data) as response:
json_response = await response.json()
See the documentation for more usage examples.
Perhaps you have a different aiohttp version on the server where you run DRF, which is why it works locally and fails under DRF.
Try it https://github.com/Skorpyon/aiorestframework
Create some middleware for authentication:
import re
from xml.etree import ElementTree as etree
from json.decoder import JSONDecodeError
from multidict import MultiDict, MultiDictProxy
from aiohttp import web
from aiohttp.hdrs import (
METH_POST, METH_PUT, METH_PATCH, METH_DELETE
)
from aiorestframework import exceptions
from aiorestframework omport serializers
from aiorestframework import Response
from aiorestframework.views import BaseViewSet
from aiorestframework.permissions import set_permissions, AllowAny
from aiorestframework.app import APIApplication
from my_project.settings import Settings # Generated by aiohttp-devtools
TOKEN_RE = re.compile(r'^\s*BEARER\s{,3}(\S{64})\s*$')
async def token_authentication(app, handler):
"""
Authorization middleware
Catching Authorization: BEARER <token> from request headers
Found user in Tarantool by token and bind User or AnonymousUser to request
"""
async def middleware_handler(request):
# Check that `Authorization` header exists
if 'authorization' in request.headers:
authorization = request.headers['authorization']
# Check matches in header value
match = TOKEN_RE.match(authorization)
if not match:
setattr(request, 'user', AnonymousUser())
return await handler(request)
else:
token = match[1]
elif 'authorization_token' in request.query:
token = request.query['authorization_token']
else:
setattr(request, 'user', AnonymousUser())
return await handler(request)
# Try select user auth record from Tarantool by token index
res = await app['tnt'].select('auth', [token, ])
cached = res.body
if not cached:
raise exceptions.AuthenticationFailed()
# Build basic user data and bind it to User instance
record = cached[0]
user = User()
user.bind_cached_tarantool(record)
# Add User to request
setattr(request, 'user', user)
return await handler(request)
return middleware_handler
And for data extraction from request:
DATA_METHODS = [METH_POST, METH_PUT, METH_PATCH, METH_DELETE]
JSON_CONTENT = ['application/json', ]
XML_CONTENT = ['application/xml', ]
FORM_CONTENT = ['application/x-www-form-urlencoded', 'multipart/form-data']
async def request_data_handler(app, handler):
"""
Request .data middleware
Try extract POST data or application/json from request body
"""
async def middleware_handler(request):
data = None
if request.method in DATA_METHODS:
if request.has_body:
if request.content_type in JSON_CONTENT:
# If request has body - try to decode it to JSON
try:
data = await request.json()
except JSONDecodeError:
raise exceptions.ParseError()
elif request.content_type in XML_CONTENT:
if request.charset is not None:
encoding = request.charset
else:
encoding = api_settings.DEFAULT_CHARSET
parser = etree.XMLParser(encoding=encoding)
try:
text = await request.text()
tree = etree.XML(text, parser=parser)
except (etree.ParseError, ValueError) as exc:
raise exceptions.ParseError(
detail='XML parse error - %s' % str(exc))
data = tree
elif request.content_type in FORM_CONTENT:
data = await request.post()
if data is None:
# If not catch any data create empty MultiDictProxy
data = MultiDictProxy(MultiDict())
# Attach extracted data to request
setattr(request, 'data', data)
return await handler(request)
return middleware_handler
Create few serializers:
class UserRegisterSerializer(s.Serializer):
"""Register new user"""
email = s.EmailField(max_length=256)
password = s.CharField(min_length=8, max_length=64)
first_name = s.CharField(min_length=2, max_length=64)
middle_name = s.CharField(default='', min_length=2, max_length=64,
required=False, allow_blank=True)
last_name = s.CharField(min_length=2, max_length=64)
phone = s.CharField(max_length=32, required=False,
allow_blank=True, default='')
async def register_user(self, app):
user = User()
data = self.validated_data
try:
await user.register_user(data, app)
except Error as e:
resolve_db_exception(e, self)
return user
And few ViewSets. It may be nested in bindings['custom']
class UserViewSet(BaseViewSet):
name = 'user'
lookup_url_kwarg = '{user_id:[0-9a-f]{32}}'
permission_classes = [AllowAny, ]
bindings = {
'list': {
'retrieve': 'get',
'update': 'put'
},
'custom': {
'list': {
'set_status': 'post',
'create_new_sip_password': 'post',
'get_registration_domain': 'get',
'report': UserReportViewSet
}
}
}
#staticmethod
async def resolve_sip_host(data, user, app):
sip_host = await resolve_registration_switch(user, app)
data.update({'sip_host': sip_host})
async def retrieve(self, request):
user = User()
await user.load_from_db(request.match_info['user_id'], request.app)
serializer = user_ser.UserProfileSerializer(instance=user)
data = serializer.data
await self.resolve_sip_host(data, user, request.app)
return Response(data=data)
#atomic
#set_permissions([AuthenticatedOnly, IsCompanyMember, CompanyIsEnabled])
async def update(self, request):
serializer = user_ser.UserProfileSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = await serializer.update_user(user_id=request.user.id,
app=request.app)
serializer = user_ser.UserProfileSerializer(instance=user)
data = serializer.data
await self.resolve_sip_host(data, user, request.app)
return Response(data=data)
Register Viewsets and run Application:
def setup_routes(app: APIApplication):
"""Add app routes here"""
# Auth API
app.router.register_viewset('/auth', auth_vws.AuthViewSet())
# User API
app.router.register_viewset('/user', user_vws.UserViewSet())
# Root redirection to Swagger
redirect = app.router.add_resource('/', name='home_redirect')
redirect.add_route('*', swagger_redirect)
def create_api_app():
sentry = get_sentry_middleware(settings.SENTRY_CONNECT_STRING, settings.SENTRY_ENVIRONMENT)
middlewares = [sentry, token_authentication, request_data_handler]
api_app = APIApplication(name='api', middlewares=middlewares,
client_max_size=10*(1024**2))
api_app.on_startup.append(startup.startup_api)
api_app.on_shutdown.append(startup.shutdown_api)
api_app.on_cleanup.append(startup.cleanup_api)
setup_routes(api_app)
if __name__ == '__main__':
app = create_api_app()
web.run_app(app)