So, to register a user, I needed some additional fields that were not in the standard form:
@router.post("/sign-up/", response_model=TokensScheme)
async def sign_up(form_data: OAuth2PasswordRequestForm = Depends(),
email: str = Body(),
first_name: str | None = Body(default=None),
second_name: str | None = Body(default=None),
session: Session = Depends(get_db)):
repository = UsersRepository(session)
repository.create(
User(
email=email,
username=form_data.username,
password=get_password_hash(form_data.password),
first_name=first_name,
second_name=second_name,
is_active=False,
)
)
return sign_in(form_data, session)
This route successfully fulfills its task, however, I did not really like adding fields in this way and I decided to extend the standard form as follows:
class UserBaseScheme(BaseModel):
email: str
username: str
first_name: str | None
second_name: str | None
class UserCreateScheme(UserBaseScheme):
password: str
class OAuth2ExtendedForm(OAuth2PasswordRequestForm,
UserCreateScheme):
pass
@router.post("/sign-up/", response_model=TokensScheme)
async def sign_up(form_data: OAuth2ExtendedForm = Depends(),
session: Session = Depends(get_db)):
repository = UsersRepository(session)
repository.create(
User(
email=form_data.email,
username=form_data.username,
password=get_password_hash(form_data.password),
first_name=form_data.first_name,
second_name=form_data.second_name,
is_active=False,
)
)
return sign_in(form_data, session)
I don't know if it's a good idea to mix Pydantic model and form, but it doesn't work:
ValueError: "OAuth2ExtendedForm" object has no field "grant_type"
I tried to tried to remove inheritance from UserCreateScheme and enter the fields I need directly into the new form, as in this question:
class OAuth2ExtendedForm(OAuth2PasswordRequestForm):
email: str
first_name: str | None
second_name: str | None
It works, but it absolutely ignores the new attributes, they just don't exist in the form.
The question is: is this some mistake of mine, FastAPI's internal security? Is it possible to expand this form at all? Thanks in advance.
EDIT:
I tried different combinations, maybe I really made a mistake somewhere.
So, as Daniil Fajnberg said, it would be nice to provide some example:
from fastapi import FastAPI, Depends
from fastapi.security import OAuth2PasswordRequestForm
from pydantic import BaseModel
app = FastAPI()
class UserBaseScheme(BaseModel):
email: str
username: str
first_name: str | None
second_name: str | None
class UserCreateScheme(UserBaseScheme):
password: str
class OAuth2ExtendedFormManual(OAuth2PasswordRequestForm):
email: str
class OAuth2ExtendedFormGrantType(OAuth2PasswordRequestForm):
email: str
grant_type: str
class OAuth2ExtendedFormInherit(OAuth2PasswordRequestForm,
UserCreateScheme):
pass
"""
Trying to send this as a form-data to all routes:
1. {'username': 'boo', 'password': 'boo', 'email': 'boo@gmail.com}
2. {'username': 'boo', 'password': 'boo', 'email': 'boo@gmail.com, 'grant_type': 'password'}
In both cases I can't get the expected result.
"""
@app.post("/sign-up-standard/")
async def sign_up(form_data: OAuth2PasswordRequestForm = Depends()):
print(form_data.__dict__)
# 1, 2 -> {
# 'grant_type': None | 'password',
# 'username': 'boo',
# 'password': 'boo',
# 'scopes': [],
# 'client_id': None,
# 'client_secret': None
# }
# Where is an email field?
@app.post("/sign-up-extended-manual/")
async def sign_up(form_data: OAuth2ExtendedFormManual = Depends()):
print(form_data.__dict__)
# 1, 2 -> {
# 'grant_type': None | 'password',
# 'username': 'boo',
# 'password': 'boo',
# 'scopes': [],
# 'client_id': None,
# 'client_secret': None
# }
# Where is an email field?
@app.post("/sign-up-extended-manual-grant-type/")
async def sign_up(form_data: OAuth2ExtendedFormGrantType = Depends()):
print(form_data.__dict__)
# 1, 2 -> {
# 'grant_type': None | 'password',
# 'username': 'boo',
# 'password': 'boo',
# 'scopes': [],
# 'client_id': None,
# 'client_secret': None
# }
# Where is an email field?
@app.post("/sign-up-extended-inherit/")
async def sign_up(form_data: OAuth2ExtendedFormInherit = Depends()):
print(form_data.__dict__)
# 1, 2 -> File "pydantic\main.py", line 358, in pydantic.main.BaseModel.__setattr__
# ValueError: "OAuth2ExtendedFormInherit" object has no field "grant_type"
EDIT 2:
Ok, it works like a standard:
class OAuth2ExtendedForm(OAuth2PasswordRequestForm):
pass
This doesn't work:
class OAuth2ExtendedForm(OAuth2PasswordRequestForm):
def __init__(self,
email: str = Form(),
first_name: str | None = Form(None),
second_name: str | None = Form(None)):
super().__init__()
self.email = email
self.first_name = first_name
self.second_name = second_name
# It does not process fields
self.scopes = scope.split() # in constructor
AttributeError: 'Form' object has no attribute 'split'
It's terrible, but it works exactly as I need it:
class OAuth2ExtendedForm(OAuth2PasswordRequestForm):
def __init__(self,
email: str = Form(),
first_name: str | None = Form(None),
second_name: str | None = Form(None),
grant_type: str = Form(default=None, regex="password"),
username: str = Form(),
password: str = Form(),
scope: str = Form(default=""),
client_id: Optional[str] = Form(default=None),
client_secret: Optional[str] = Form(default=None)
):
super().__init__(grant_type=grant_type,
username=username,
password=password,
scope=scope,
client_id=client_id,
client_secret=client_secret)
self.email = email
self.first_name = first_name
self.second_name = second_name
I also try use kwargs, but Pydantic said this:
{
"detail": [
{
"loc": [
"query",
"kwargs"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
source https://stackoverflow.com/questions/75188072/extended-fastapi-oauth2passwordrequestform
Comments
Post a Comment