This example shows how to do request validation using FastAPI's Form.
@router.post("/create", response_class=RedirectResponse)
def create_organization(
name: Annotated[str, Form(
min_length=1,
strip_whitespace=True,
pattern=r"\S+",
description="Organization name cannot be empty or contain only whitespace",
title="Organization name"
)],
Where else in the repository should we be leveraging this pattern to better effect?
Okay, let's analyze the codebase to find other places where leveraging Annotated[..., Form(...)] for declarative validation would be beneficial.
The pattern is most useful for POST routes that receive form data, particularly simple string inputs where constraints like non-emptiness, length, whitespace stripping, or basic patterns are desired directly in the signature.
Here's a breakdown by router file:
-
routers/account.py
delete_account:
email: EmailStr = Form(...): EmailStr provides good validation already. Adding Form for description/title is minor.
password: str = Form(...): Candidate. Could add min_length=8 (as a basic check, though complex validation is elsewhere), description, title.
password: Annotated[str, Form(
min_length=8, # Or match the required minimum
description="Account password for verification",
title="Password"
)],
register:
name: str = Form(...): Strong Candidate. Similar to the example, needs non-empty, strip whitespace.
name: Annotated[str, Form(
min_length=1,
strip_whitespace=True,
pattern=r"\S+",
description="User's full name",
title="Name"
)],
email: EmailStr = Form(...): EmailStr is sufficient.
password: str = Form(...), confirm_password: str = Form(...): Candidates. Similar to delete_account, could add min_length, description, title. The complex pattern matching is handled by the validate_password_strength_and_match dependency, but basic checks can be added here.
password: Annotated[str, Form(
min_length=8, # Match required minimum
description="Choose a strong password",
title="Password"
)],
confirm_password: Annotated[str, Form(
min_length=8, # Match required minimum
description="Confirm your password",
title="Confirm Password"
)],
# Note: The Depends(validate_password_strength_and_match) still handles the comparison and complex pattern.
login:
email: EmailStr = Form(...): EmailStr is sufficient.
password: str = Form(...): Candidate. Needs min_length, description, title.
password: Annotated[str, Form(
min_length=1, # Basic non-empty check
description="Your account password",
title="Password"
)],
forgot_password:
email: EmailStr = Form(...): EmailStr is sufficient.
reset_password:
email: EmailStr = Form(...): EmailStr is sufficient.
token: str = Form(...): Candidate. Should not be empty.
token: Annotated[str, Form(
min_length=1,
strip_whitespace=True,
description="Password reset token from email",
title="Reset Token"
)],
new_password: str = Depends(...), confirm_password: str = Form(...): Handled by dependency, but confirm_password could benefit like in register.
confirm_password: Annotated[str, Form(
min_length=8, # Match required minimum
description="Confirm your new password",
title="Confirm New Password"
)],
request_email_update:
email: EmailStr = Form(...), new_email: EmailStr = Form(...): Both EmailStr are sufficient.
-
routers/organization.py
create_organization, update_organization: Already use the pattern for name.
-
routers/role.py
create_role:
name: str = Form(...): Strong Candidate. Needs non-empty, strip whitespace.
name: Annotated[str, Form(
min_length=1,
strip_whitespace=True,
pattern=r"\S+",
description="Name for the new role",
title="Role Name"
)],
organization_id: int = Form(...): int validation is implicit. Form could add gt=0, description, title.
permissions: List[ValidPermissions] = Form(...): List validation handled by FastAPI. Form could add description.
update_role:
id: int = Form(...), organization_id: int = Form(...), permissions: List[ValidPermissions] = Form(...): Similar to create_role.
name: str = Form(...): Strong Candidate. Same as in create_role.
name: Annotated[str, Form(
min_length=1,
strip_whitespace=True,
pattern=r"\S+",
description="Updated name for the role",
title="Role Name"
)],
delete_role:
id: int = Form(...), organization_id: int = Form(...): int validation is implicit.
-
routers/user.py
update_profile:
name: Optional[str] = Form(None): Candidate. Since it's optional, the rules apply if a value is provided. strip_whitespace is useful. A min_length if not None could also be added, but is harder to express declaratively. strip_whitespace is the main benefit here.
name: Annotated[Optional[str], Form(
None, # Keep default as None for optionality
strip_whitespace=True,
# min_length=1, # Can't easily apply conditionally here
description="User's updated full name (optional)",
title="Name"
)],
avatar_file: Optional[UploadFile] = File(None): Not applicable for Form.
Summary of Recommendations:
Apply the Annotated[..., Form(...)] pattern primarily to:
routers/account.py:
name in register (non-empty, strip whitespace).
password/confirm_password fields in register, reset_password, login, delete_account (at least min_length=1 or min_length=8, description, title).
token in reset_password (non-empty, strip whitespace).
routers/role.py:
name in create_role and update_role (non-empty, strip whitespace).
routers/user.py:
name in update_profile (strip_whitespace=True).
This makes the validation constraints clearer directly in the function signature and leverages FastAPI's built-in mechanisms more effectively for basic form field validation, reserving more complex or cross-field validation for dependencies or the route body.
This example shows how to do request validation using FastAPI's
Form.Where else in the repository should we be leveraging this pattern to better effect?
Okay, let's analyze the codebase to find other places where leveraging
Annotated[..., Form(...)]for declarative validation would be beneficial.The pattern is most useful for POST routes that receive form data, particularly simple string inputs where constraints like non-emptiness, length, whitespace stripping, or basic patterns are desired directly in the signature.
Here's a breakdown by router file:
routers/account.pydelete_account:email: EmailStr = Form(...):EmailStrprovides good validation already. AddingFormfordescription/titleis minor.password: str = Form(...): Candidate. Could addmin_length=8(as a basic check, though complex validation is elsewhere),description,title.register:name: str = Form(...): Strong Candidate. Similar to the example, needs non-empty, strip whitespace.email: EmailStr = Form(...):EmailStris sufficient.password: str = Form(...),confirm_password: str = Form(...): Candidates. Similar todelete_account, could addmin_length,description,title. The complex pattern matching is handled by thevalidate_password_strength_and_matchdependency, but basic checks can be added here.login:email: EmailStr = Form(...):EmailStris sufficient.password: str = Form(...): Candidate. Needsmin_length,description,title.forgot_password:email: EmailStr = Form(...):EmailStris sufficient.reset_password:email: EmailStr = Form(...):EmailStris sufficient.token: str = Form(...): Candidate. Should not be empty.new_password: str = Depends(...),confirm_password: str = Form(...): Handled by dependency, butconfirm_passwordcould benefit like inregister.request_email_update:email: EmailStr = Form(...),new_email: EmailStr = Form(...): BothEmailStrare sufficient.routers/organization.pycreate_organization,update_organization: Already use the pattern forname.routers/role.pycreate_role:name: str = Form(...): Strong Candidate. Needs non-empty, strip whitespace.organization_id: int = Form(...):intvalidation is implicit.Formcould addgt=0,description,title.permissions: List[ValidPermissions] = Form(...): List validation handled by FastAPI.Formcould adddescription.update_role:id: int = Form(...),organization_id: int = Form(...),permissions: List[ValidPermissions] = Form(...): Similar tocreate_role.name: str = Form(...): Strong Candidate. Same as increate_role.delete_role:id: int = Form(...),organization_id: int = Form(...):intvalidation is implicit.routers/user.pyupdate_profile:name: Optional[str] = Form(None): Candidate. Since it's optional, the rules apply if a value is provided.strip_whitespaceis useful. Amin_lengthif notNonecould also be added, but is harder to express declaratively.strip_whitespaceis the main benefit here.avatar_file: Optional[UploadFile] = File(None): Not applicable forForm.Summary of Recommendations:
Apply the
Annotated[..., Form(...)]pattern primarily to:routers/account.py:nameinregister(non-empty, strip whitespace).password/confirm_passwordfields inregister,reset_password,login,delete_account(at leastmin_length=1ormin_length=8,description,title).tokeninreset_password(non-empty, strip whitespace).routers/role.py:nameincreate_roleandupdate_role(non-empty, strip whitespace).routers/user.py:nameinupdate_profile(strip_whitespace=True).This makes the validation constraints clearer directly in the function signature and leverages FastAPI's built-in mechanisms more effectively for basic form field validation, reserving more complex or cross-field validation for dependencies or the route body.