Browse Source

🎨 [pre-commit.ci] Auto format from pre-commit.com hooks

pull/14304/head
pre-commit-ci[bot] 9 months ago
parent
commit
d25290c345
  1. 16
      REQUIRED_NONE_ENHANCEMENT.md
  2. 16
      docs_src/query_params_str_validations/tutorial016_required_can_be_none_an.py
  3. 16
      docs_src/query_params_str_validations/tutorial016_required_can_be_none_py310.py
  4. 88
      docs_src/query_params_str_validations/tutorial017_comprehensive_required_none_py310.py

16
REQUIRED_NONE_ENHANCEMENT.md

@ -12,7 +12,7 @@ Users often want to create APIs where:
3. Validation rules still apply when the parameter is not None
The existing documentation shows either:
- Optional parameters: `param: str | None = None`
- Optional parameters: `param: str | None = None`
- Required parameters: `param: str`
But doesn't clearly demonstrate the middle ground: **required but nullable**.
@ -32,7 +32,7 @@ async def read_items(
# Handle explicit None case
if q == "null":
q = None
if q is not None:
# Filter logic here
return {"items": [...], "filtered_by": q}
@ -44,14 +44,14 @@ async def read_items(
### Key Points
1. **No default value** - Makes the parameter required
2. **Union[str, None]** - Allows both string and None values
2. **Union[str, None]** - Allows both string and None values
3. **Handle "null" string** - Convert string "null" to Python None
4. **Validation applies** - min_length=3 still enforced for non-None values
### Usage Examples
```bash
# ✅ Valid - searches for "python"
# ✅ Valid - searches for "python"
GET /items/?q=python
# ✅ Valid - explicitly no filtering (null converted to None)
@ -70,7 +70,7 @@ GET /items/?q=ab
- `tutorial016_required_can_be_none_py310.py` - Simple demonstration
- `tutorial016_required_can_be_none_an.py` - Compatible with older Python versions
### Comprehensive Example
### Comprehensive Example
- `tutorial017_comprehensive_required_none_py310.py` - Advanced patterns including:
- Multiple required parameters with different types
- Custom validation and error handling
@ -80,7 +80,7 @@ GET /items/?q=ab
## Why This Enhancement Matters
1. **Addresses Real User Need** - Issue #12419 shows this is a genuine pain point
2. **Fills Documentation Gap** - Current docs don't cover this specific pattern
2. **Fills Documentation Gap** - Current docs don't cover this specific pattern
3. **Provides Working Code** - Complete, tested examples ready to use
4. **Shows Best Practices** - Error handling, validation, and API design patterns
@ -97,7 +97,7 @@ All examples include comprehensive test coverage showing:
This enhancement connects to existing FastAPI documentation:
- Query Parameters tutorial
- Parameter validation
- Request validation and error handling
- Request validation and error handling
- Type hints and annotations
The examples follow FastAPI's established patterns while addressing the specific "required but nullable" use case.
@ -109,4 +109,4 @@ Expected to help:
- Developers migrating from other frameworks with different parameter semantics
- API designers needing explicit null/empty state handling
This addresses a frequently asked question and provides clear, working solutions with comprehensive examples.
This addresses a frequently asked question and provides clear, working solutions with comprehensive examples.

16
docs_src/query_params_str_validations/tutorial016_required_can_be_none_an.py

@ -7,29 +7,27 @@ app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[Union[str, None], Query(min_length=3)] = ...
):
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
"""
Example of a required query parameter that can be None.
The parameter 'q' is required (must be provided by the client)
but can explicitly be set to None by passing "null" as string.
- If q is provided and valid: returns items filtered by q
- If q is "null": treats it as None and returns all items
- If q is missing: returns 422 validation error (required parameter)
- If q is too short: returns 422 validation error (min_length=3)
"""
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case
if q == "null":
q = None
if q is not None:
results.update({"q": q, "filtered": True})
else:
results.update({"q": None, "filtered": False})
return results
return results

16
docs_src/query_params_str_validations/tutorial016_required_can_be_none_py310.py

@ -6,29 +6,27 @@ app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[Union[str, None], Query(min_length=3)]
):
async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
"""
Example of a required query parameter that can be None.
The parameter 'q' is required (must be provided by the client)
but can explicitly be set to None by passing "null" as string.
- If q is provided and valid: returns items filtered by q
- If q is "null": treats it as None and returns all items
- If q is missing: returns 422 validation error (required parameter)
- If q is too short: returns 422 validation error (min_length=3)
"""
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case
if q == "null":
q = None
if q is not None:
results.update({"q": q, "filtered": True})
else:
results.update({"q": None, "filtered": False})
return results
return results

88
docs_src/query_params_str_validations/tutorial017_comprehensive_required_none_py310.py

@ -1,17 +1,15 @@
from typing import Annotated, Union
from fastapi import FastAPI, Query, HTTPException
from fastapi import FastAPI, HTTPException, Query
app = FastAPI()
@app.get("/items/basic/")
async def read_items_basic(
q: Annotated[Union[str, None], Query(min_length=3)]
):
async def read_items_basic(q: Annotated[Union[str, None], Query(min_length=3)]):
"""
Basic example: Required query parameter that can be None.
Usage examples:
- GET /items/basic/?q=test -> filters by "test"
- GET /items/basic/?q=null -> returns all items (q treated as None)
@ -19,85 +17,83 @@ async def read_items_basic(
- GET /items/basic/?q=ab -> 422 error (too short)
"""
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case
if q == "null":
q = None
if q is not None:
results.update({"q": q, "filtered": True})
else:
results.update({"q": None, "filtered": False})
return results
@app.get("/items/advanced/")
async def read_items_advanced(
q: Annotated[Union[str, None], Query(min_length=3)],
include_metadata: Annotated[bool, Query()] = False
include_metadata: Annotated[bool, Query()] = False,
):
"""
Advanced example: Multiple required parameters with None handling.
This shows how to handle multiple query parameters where some
can be None and others have different types.
Usage examples:
- GET /items/advanced/?q=search&include_metadata=true
- GET /items/advanced/?q=null&include_metadata=false
- GET /items/advanced/?q=null&include_metadata=false
- GET /items/advanced/?q=search (include_metadata defaults to false)
"""
# Convert "null" string to actual None
if q == "null":
q = None
# Simulate database items
all_items = [
{"item_id": "Foo", "tags": ["tag1", "tag2"], "active": True},
{"item_id": "Bar", "tags": ["tag3"], "active": False},
{"item_id": "Baz", "tags": ["tag1"], "active": True}
{"item_id": "Baz", "tags": ["tag1"], "active": True},
]
# Filter items if query is provided
if q is not None:
filtered_items = [
item for item in all_items
if q.lower() in item["item_id"].lower() or
any(q.lower() in tag.lower() for tag in item["tags"])
item
for item in all_items
if q.lower() in item["item_id"].lower()
or any(q.lower() in tag.lower() for tag in item["tags"])
]
else:
filtered_items = all_items
# Remove metadata if not requested
if not include_metadata:
filtered_items = [
{"item_id": item["item_id"]}
for item in filtered_items
]
filtered_items = [{"item_id": item["item_id"]} for item in filtered_items]
return {
"items": filtered_items,
"query_used": q,
"total_found": len(filtered_items),
"metadata_included": include_metadata
"metadata_included": include_metadata,
}
@app.get("/items/validation/")
async def read_items_with_custom_validation(
q: Annotated[Union[str, None], Query(min_length=3)]
q: Annotated[Union[str, None], Query(min_length=3)],
):
"""
Example with custom validation and error handling.
This shows how to add custom business logic validation
while still using FastAPI's automatic validation.
"""
# Handle explicit None case
if q == "null":
q = None
# Custom validation (after FastAPI's automatic validation)
if q is not None:
# Example: Reject certain reserved words
@ -105,25 +101,29 @@ async def read_items_with_custom_validation(
if q.lower() in reserved_words:
raise HTTPException(
status_code=400,
detail=f"Query '{q}' is a reserved word and cannot be used for searching"
detail=f"Query '{q}' is a reserved word and cannot be used for searching",
)
# Example: Convert to proper search format
q = q.strip().lower()
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q is not None:
results.update({
"q": q,
"search_performed": True,
"note": "Query has been normalized to lowercase"
})
results.update(
{
"q": q,
"search_performed": True,
"note": "Query has been normalized to lowercase",
}
)
else:
results.update({
"q": None,
"search_performed": False,
"note": "No search query provided (null value received)"
})
return results
results.update(
{
"q": None,
"search_performed": False,
"note": "No search query provided (null value received)",
}
)
return results

Loading…
Cancel
Save