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 3. Validation rules still apply when the parameter is not None
The existing documentation shows either: The existing documentation shows either:
- Optional parameters: `param: str | None = None` - Optional parameters: `param: str | None = None`
- Required parameters: `param: str` - Required parameters: `param: str`
But doesn't clearly demonstrate the middle ground: **required but nullable**. But doesn't clearly demonstrate the middle ground: **required but nullable**.
@ -32,7 +32,7 @@ async def read_items(
# Handle explicit None case # Handle explicit None case
if q == "null": if q == "null":
q = None q = None
if q is not None: if q is not None:
# Filter logic here # Filter logic here
return {"items": [...], "filtered_by": q} return {"items": [...], "filtered_by": q}
@ -44,14 +44,14 @@ async def read_items(
### Key Points ### Key Points
1. **No default value** - Makes the parameter required 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 3. **Handle "null" string** - Convert string "null" to Python None
4. **Validation applies** - min_length=3 still enforced for non-None values 4. **Validation applies** - min_length=3 still enforced for non-None values
### Usage Examples ### Usage Examples
```bash ```bash
# ✅ Valid - searches for "python" # ✅ Valid - searches for "python"
GET /items/?q=python GET /items/?q=python
# ✅ Valid - explicitly no filtering (null converted to None) # ✅ 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_py310.py` - Simple demonstration
- `tutorial016_required_can_be_none_an.py` - Compatible with older Python versions - `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: - `tutorial017_comprehensive_required_none_py310.py` - Advanced patterns including:
- Multiple required parameters with different types - Multiple required parameters with different types
- Custom validation and error handling - Custom validation and error handling
@ -80,7 +80,7 @@ GET /items/?q=ab
## Why This Enhancement Matters ## Why This Enhancement Matters
1. **Addresses Real User Need** - Issue #12419 shows this is a genuine pain point 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 3. **Provides Working Code** - Complete, tested examples ready to use
4. **Shows Best Practices** - Error handling, validation, and API design patterns 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: This enhancement connects to existing FastAPI documentation:
- Query Parameters tutorial - Query Parameters tutorial
- Parameter validation - Parameter validation
- Request validation and error handling - Request validation and error handling
- Type hints and annotations - Type hints and annotations
The examples follow FastAPI's established patterns while addressing the specific "required but nullable" use case. 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 - Developers migrating from other frameworks with different parameter semantics
- API designers needing explicit null/empty state handling - 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/") @app.get("/items/")
async def read_items( async def read_items(q: Annotated[Union[str, None], Query(min_length=3)] = ...):
q: Annotated[Union[str, None], Query(min_length=3)] = ...
):
""" """
Example of a required query parameter that can be None. Example of a required query parameter that can be None.
The parameter 'q' is required (must be provided by the client) The parameter 'q' is required (must be provided by the client)
but can explicitly be set to None by passing "null" as string. 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 provided and valid: returns items filtered by q
- If q is "null": treats it as None and returns all items - 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 missing: returns 422 validation error (required parameter)
- If q is too short: returns 422 validation error (min_length=3) - If q is too short: returns 422 validation error (min_length=3)
""" """
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case # Handle explicit None case
if q == "null": if q == "null":
q = None q = None
if q is not None: if q is not None:
results.update({"q": q, "filtered": True}) results.update({"q": q, "filtered": True})
else: else:
results.update({"q": None, "filtered": False}) 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/") @app.get("/items/")
async def read_items( async def read_items(q: Annotated[Union[str, None], Query(min_length=3)]):
q: Annotated[Union[str, None], Query(min_length=3)]
):
""" """
Example of a required query parameter that can be None. Example of a required query parameter that can be None.
The parameter 'q' is required (must be provided by the client) The parameter 'q' is required (must be provided by the client)
but can explicitly be set to None by passing "null" as string. 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 provided and valid: returns items filtered by q
- If q is "null": treats it as None and returns all items - 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 missing: returns 422 validation error (required parameter)
- If q is too short: returns 422 validation error (min_length=3) - If q is too short: returns 422 validation error (min_length=3)
""" """
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case # Handle explicit None case
if q == "null": if q == "null":
q = None q = None
if q is not None: if q is not None:
results.update({"q": q, "filtered": True}) results.update({"q": q, "filtered": True})
else: else:
results.update({"q": None, "filtered": False}) 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 typing import Annotated, Union
from fastapi import FastAPI, Query, HTTPException from fastapi import FastAPI, HTTPException, Query
app = FastAPI() app = FastAPI()
@app.get("/items/basic/") @app.get("/items/basic/")
async def read_items_basic( async def read_items_basic(q: Annotated[Union[str, None], Query(min_length=3)]):
q: Annotated[Union[str, None], Query(min_length=3)]
):
""" """
Basic example: Required query parameter that can be None. Basic example: Required query parameter that can be None.
Usage examples: Usage examples:
- GET /items/basic/?q=test -> filters by "test" - GET /items/basic/?q=test -> filters by "test"
- GET /items/basic/?q=null -> returns all items (q treated as None) - 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) - GET /items/basic/?q=ab -> 422 error (too short)
""" """
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
# Handle explicit None case # Handle explicit None case
if q == "null": if q == "null":
q = None q = None
if q is not None: if q is not None:
results.update({"q": q, "filtered": True}) results.update({"q": q, "filtered": True})
else: else:
results.update({"q": None, "filtered": False}) results.update({"q": None, "filtered": False})
return results return results
@app.get("/items/advanced/") @app.get("/items/advanced/")
async def read_items_advanced( async def read_items_advanced(
q: Annotated[Union[str, None], Query(min_length=3)], 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. Advanced example: Multiple required parameters with None handling.
This shows how to handle multiple query parameters where some This shows how to handle multiple query parameters where some
can be None and others have different types. can be None and others have different types.
Usage examples: Usage examples:
- GET /items/advanced/?q=search&include_metadata=true - 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) - GET /items/advanced/?q=search (include_metadata defaults to false)
""" """
# Convert "null" string to actual None # Convert "null" string to actual None
if q == "null": if q == "null":
q = None q = None
# Simulate database items # Simulate database items
all_items = [ all_items = [
{"item_id": "Foo", "tags": ["tag1", "tag2"], "active": True}, {"item_id": "Foo", "tags": ["tag1", "tag2"], "active": True},
{"item_id": "Bar", "tags": ["tag3"], "active": False}, {"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 # Filter items if query is provided
if q is not None: if q is not None:
filtered_items = [ filtered_items = [
item for item in all_items item
if q.lower() in item["item_id"].lower() or for item in all_items
any(q.lower() in tag.lower() for tag in item["tags"]) if q.lower() in item["item_id"].lower()
or any(q.lower() in tag.lower() for tag in item["tags"])
] ]
else: else:
filtered_items = all_items filtered_items = all_items
# Remove metadata if not requested # Remove metadata if not requested
if not include_metadata: if not include_metadata:
filtered_items = [ filtered_items = [{"item_id": item["item_id"]} for item in filtered_items]
{"item_id": item["item_id"]}
for item in filtered_items
]
return { return {
"items": filtered_items, "items": filtered_items,
"query_used": q, "query_used": q,
"total_found": len(filtered_items), "total_found": len(filtered_items),
"metadata_included": include_metadata "metadata_included": include_metadata,
} }
@app.get("/items/validation/") @app.get("/items/validation/")
async def read_items_with_custom_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. Example with custom validation and error handling.
This shows how to add custom business logic validation This shows how to add custom business logic validation
while still using FastAPI's automatic validation. while still using FastAPI's automatic validation.
""" """
# Handle explicit None case # Handle explicit None case
if q == "null": if q == "null":
q = None q = None
# Custom validation (after FastAPI's automatic validation) # Custom validation (after FastAPI's automatic validation)
if q is not None: if q is not None:
# Example: Reject certain reserved words # Example: Reject certain reserved words
@ -105,25 +101,29 @@ async def read_items_with_custom_validation(
if q.lower() in reserved_words: if q.lower() in reserved_words:
raise HTTPException( raise HTTPException(
status_code=400, 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 # Example: Convert to proper search format
q = q.strip().lower() q = q.strip().lower()
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]} results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q is not None: if q is not None:
results.update({ results.update(
"q": q, {
"search_performed": True, "q": q,
"note": "Query has been normalized to lowercase" "search_performed": True,
}) "note": "Query has been normalized to lowercase",
}
)
else: else:
results.update({ results.update(
"q": None, {
"search_performed": False, "q": None,
"note": "No search query provided (null value received)" "search_performed": False,
}) "note": "No search query provided (null value received)",
}
return results )
return results

Loading…
Cancel
Save