Utils Module¶
The utils
module provides core utility functions used throughout FastAPI Shield for dependency resolution, request processing, and signature manipulation.
Overview¶
This module contains low-level utility functions that power the shield system's integration with FastAPI's dependency injection and request handling. While these functions are primarily for internal use, understanding them can be helpful for advanced use cases.
Module Reference¶
Utility functions for FastAPI Shield dependency resolution and request processing.
This module provides core utility functions used throughout the FastAPI Shield library for handling dependency injection, request body parsing, signature manipulation, and other internal operations.
generate_unique_id_for_fastapi_shield(dependant, path_format)
¶
Generate a unique identifier for FastAPI Shield dependants.
Creates a unique operation ID by combining the dependant's callable name with the path format, then sanitizing the result to ensure it's valid for use in OpenAPI schemas and internal operations.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
dependant
|
Dependant
|
The FastAPI Dependant object containing the callable. |
required |
path_format
|
str
|
The raw path format string (e.g., "/users/{user_id}"). |
required |
Returns:
Name | Type | Description |
---|---|---|
str |
A sanitized unique identifier string with non-word characters replaced by underscores. |
Examples:
get_body_field_from_dependant(dependant, path_format)
¶
Extract body field information from a FastAPI Dependant.
Analyzes a FastAPI Dependant to determine the appropriate body field configuration and whether body fields should be embedded. This is used for proper request body parsing and validation.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
dependant
|
Dependant
|
The FastAPI Dependant object to analyze. |
required |
path_format
|
str
|
The raw path format string for generating unique IDs. |
required |
Returns:
Type | Description |
---|---|
tuple[Optional[ModelField], bool]
|
tuple[Optional[ModelField], bool]: A tuple containing: - The body field (ModelField) if one exists, None otherwise - Boolean indicating whether body fields should be embedded |
Examples:
get_body_from_request(request, body_field=None)
async
¶
Extract and parse the request body based on content type and field configuration.
Handles various content types including JSON, form data, and raw bytes. Properly manages file uploads and form closures to prevent resource leaks. Provides comprehensive error handling for malformed requests.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
request
|
Request
|
The FastAPI Request object to extract body from. |
required |
body_field
|
Optional[ModelField]
|
Optional ModelField specifying expected body structure. If None, no body parsing is performed. |
None
|
Returns:
Name | Type | Description |
---|---|---|
Any |
The parsed request body. Type depends on content type: - dict/list for JSON content - FormData for form submissions - bytes for other content types - None if no body field specified |
Raises:
Type | Description |
---|---|
RequestValidationError
|
If JSON parsing fails or body is malformed. |
HTTPException
|
If there's an error parsing the request body. |
Examples:
get_path_format_from_request_for_endpoint(request)
¶
Extract the path format from a FastAPI request.
Attempts to retrieve the raw path format (with parameter placeholders)
from the request's route. Falls back to the actual URL path if the
route doesn't have a path_format
attribute.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
request
|
Request
|
The FastAPI |
required |
Returns:
Name | Type | Description |
---|---|---|
str |
str
|
The path format string (e.g., "/users/{user_id}") or the actual request path if format is unavailable. |
Examples:
get_solved_dependencies(request, path_format, endpoint, dependency_cache)
async
¶
Resolve all dependencies for a FastAPI endpoint.
Performs complete dependency resolution for an endpoint, including parsing the request body, resolving nested dependencies, and handling dependency caching. This is the core function used by shields to access resolved dependency values.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
request
|
Request
|
The FastAPI |
required |
path_format
|
str
|
The raw path format string for the endpoint. |
required |
endpoint
|
Callable
|
The endpoint callable to resolve dependencies for. |
required |
dependency_cache
|
dict
|
Dictionary for caching resolved dependencies to avoid duplicate resolution. |
required |
Returns:
Name | Type | Description |
---|---|---|
tuple |
A tuple containing: - Solved dependencies object with resolved values and any errors - The parsed request body (if any) |
Examples:
merge_dedup_seq_params(*seqs_of_params)
¶
Merge multiple sequences of Parameters while removing duplicates.
Combines multiple sequences of inspect.Parameter
objects, keeping only
the first occurrence of each parameter name. This is used when merging
parameters from wrapped functions to avoid duplicate parameters in
the final signature.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*seqs_of_params
|
Sequence[Parameter]
|
Variable number of |
()
|
Yields:
Name | Type | Description |
---|---|---|
Parameter |
Unique parameters in the order they first appear. |
Examples:
>>> from inspect import Parameter
>>> seq1 = [Parameter('a', Parameter.POSITIONAL_OR_KEYWORD)]
>>> seq2 = [Parameter('b', Parameter.POSITIONAL_OR_KEYWORD)]
>>> seq3 = [Parameter('a', Parameter.KEYWORD_ONLY)] # duplicate 'a'
>>> merged = list(merge_dedup_seq_params(seq1, seq2, seq3))
>>> [p.name for p in merged] # ['a', 'b'] - duplicate 'a' removed
prepend_request_to_signature_params_of_function(function)
¶
Prepend a Request
parameter to a function's signature parameters.
Creates a new parameter sequence that starts with a positional-only
Request
parameter, followed by all the original function's parameters.
This is used internally to ensure shield functions have access to the
Request
object.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
function
|
Callable
|
The callable to prepend the Request parameter to. |
required |
Yields:
Name | Type | Description |
---|---|---|
Parameter |
The new Request parameter followed by original parameters. |
Examples:
rearrange_params(iter_params)
¶
Rearrange function parameters according to Python's parameter ordering rules.
Sorts parameters to follow Python's required parameter order: 1. POSITIONAL_ONLY parameters 2. Required POSITIONAL_OR_KEYWORD parameters (no default) 3. Optional POSITIONAL_OR_KEYWORD parameters (with default) 4. VAR_POSITIONAL (args) 5. KEYWORD_ONLY parameters 6. VAR_KEYWORD (*kwargs)
This function is highly optimized using alternating buffers and minimal operations for performance when processing large parameter lists.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
iter_params
|
Iterator[Parameter]
|
Iterator of Parameter objects to rearrange. |
required |
Yields:
Name | Type | Description |
---|---|---|
Parameter |
Parameters in the correct order according to Python rules. |
Examples:
>>> from inspect import Parameter, signature
>>> def func(a, *args, b=1, c, **kwargs, d=2): pass
>>> params = signature(func).parameters.values()
>>> arranged = list(rearrange_params(iter(params)))
>>> [p.name for p in arranged] # ['a', 'c', 'd', 'b', 'args', 'kwargs']
Note
This function handles the special case where POSITIONAL_OR_KEYWORD parameters are split into required and optional categories for proper ordering.
Function Categories¶
Dependency Resolution¶
get_solved_dependencies¶
The core function for resolving FastAPI dependencies within the shield system.
Usage Example:
from fastapi_shield.utils import get_solved_dependencies
async def my_shield(request: Request) -> dict | None:
# Resolve dependencies manually if needed
solved_deps, body = await get_solved_dependencies(
request=request,
path_format="/users/{user_id}",
endpoint=my_endpoint_function,
dependency_cache={}
)
if solved_deps.errors:
return None # Block request on dependency errors
# Use resolved dependencies
user_id = solved_deps.values.get("user_id")
return {"resolved_user_id": user_id}
get_body_field_from_dependant¶
Extract body field configuration from FastAPI dependants for proper request parsing.
get_body_from_request¶
Parse request bodies with comprehensive error handling for various content types.
Request Processing¶
get_path_format_from_request_for_endpoint¶
Extract path format information from FastAPI requests for use in dependency resolution.
Usage Example:
from fastapi_shield.utils import get_path_format_from_request_for_endpoint
@shield
def path_aware_shield(request: Request) -> dict | None:
path_format = get_path_format_from_request_for_endpoint(request)
print(f"Endpoint path format: {path_format}") # e.g., "/users/{user_id}"
# Use path format for custom logic
if "/admin/" in path_format:
return validate_admin_access(request)
return validate_regular_access(request)
generate_unique_id_for_fastapi_shield¶
Generate unique identifiers for shield-related operations, particularly useful for OpenAPI schema generation.
Signature Manipulation¶
rearrange_params¶
Optimize parameter ordering according to Python's parameter rules for performance and compatibility.
Technical Details: - Handles POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY, and VAR_KEYWORD parameters - Uses optimized alternating buffer algorithm for performance - Properly handles required vs optional POSITIONAL_OR_KEYWORD parameters
merge_dedup_seq_params¶
Merge multiple parameter sequences while removing duplicates, essential for combining parameters from wrapped functions.
prepend_request_to_signature_params_of_function¶
Add Request parameter to function signatures for shield integration.
Advanced Usage Examples¶
Custom Dependency Resolution¶
from fastapi_shield.utils import get_solved_dependencies, get_body_from_request
from fastapi.dependencies.utils import get_dependant
async def advanced_shield_with_custom_resolution(request: Request) -> dict | None:
"""Shield that manually resolves specific dependencies."""
# Get dependant for a specific function
dependant = get_dependant(path="/api/users", call=some_function)
# Resolve dependencies manually
solved_deps, body = await get_solved_dependencies(
request=request,
path_format="/api/users",
endpoint=some_function,
dependency_cache={}
)
# Check for validation errors
if solved_deps.errors:
logger.error(f"Dependency resolution failed: {solved_deps.errors}")
return None
# Access resolved values
db_session = solved_deps.values.get("db")
current_user = solved_deps.values.get("current_user")
# Custom validation logic using resolved dependencies
if db_session and current_user:
user_permissions = check_permissions(db_session, current_user.id)
if "admin" in user_permissions:
return {"user": current_user, "permissions": user_permissions}
return None
Request Body Processing¶
from fastapi_shield.utils import get_body_from_request, get_body_field_from_dependant
from fastapi.dependencies.utils import get_dependant
async def body_validation_shield(request: Request) -> dict | None:
"""Shield that validates request body before endpoint processing."""
try:
# For endpoints that expect a request body
dependant = get_dependant(path="/api/data", call=endpoint_function)
body_field, embed_fields = get_body_field_from_dependant(dependant, "/api/data")
if body_field:
# Parse and validate body
body = await get_body_from_request(request, body_field)
# Custom body validation
if isinstance(body, dict):
required_fields = ["user_id", "action"]
if all(field in body for field in required_fields):
return {"validated_body": body}
return None
except Exception as e:
logger.error(f"Body validation failed: {e}")
return None
Signature Analysis¶
from fastapi_shield.utils import (
merge_dedup_seq_params,
rearrange_params,
prepend_request_to_signature_params_of_function
)
from inspect import signature, Parameter
def analyze_endpoint_signature(endpoint_func):
"""Analyze and optimize endpoint function signatures."""
# Get original parameters
original_params = list(signature(endpoint_func).parameters.values())
# Add Request parameter for shield compatibility
shield_params = list(prepend_request_to_signature_params_of_function(endpoint_func))
# Merge with additional parameters if needed
additional_params = [
Parameter("shield_data", Parameter.KEYWORD_ONLY, annotation=dict, default=None)
]
merged_params = list(merge_dedup_seq_params(shield_params, additional_params))
# Rearrange according to Python rules
optimized_params = list(rearrange_params(iter(merged_params)))
return {
"original_count": len(original_params),
"shield_count": len(shield_params),
"merged_count": len(merged_params),
"optimized_count": len(optimized_params),
"parameter_names": [p.name for p in optimized_params]
}
# Usage
result = analyze_endpoint_signature(my_endpoint)
print(f"Parameter analysis: {result}")
Performance Monitoring¶
from fastapi_shield.utils import get_solved_dependencies
import time
import asyncio
async def performance_monitoring_shield(request: Request) -> dict | None:
"""Shield that monitors dependency resolution performance."""
start_time = time.time()
try:
solved_deps, body = await get_solved_dependencies(
request=request,
path_format=request.url.path,
endpoint=request.scope.get("endpoint"),
dependency_cache={}
)
resolution_time = time.time() - start_time
# Log performance metrics
logger.info(f"Dependency resolution took {resolution_time:.3f}s")
if resolution_time > 0.1: # Slow dependency resolution
logger.warning(f"Slow dependency resolution: {resolution_time:.3f}s")
if solved_deps.errors:
return None
return {
"performance": {
"dependency_resolution_time": resolution_time,
"dependency_count": len(solved_deps.values)
}
}
except Exception as e:
logger.error(f"Performance monitoring failed: {e}")
return None
Integration with FastAPI Internals¶
Custom Dependant Creation¶
from fastapi.dependencies.utils import get_dependant
from fastapi_shield.utils import get_body_field_from_dependant
def create_custom_dependant(func, path: str):
"""Create a custom dependant with shield-specific configuration."""
# Create base dependant
dependant = get_dependant(path=path, call=func)
# Get body field configuration
body_field, embed_fields = get_body_field_from_dependant(dependant, path)
# Custom modifications
dependant.shield_enabled = True
dependant.custom_body_field = body_field
return dependant
Error Handling Utilities¶
from fastapi_shield.utils import get_body_from_request
from fastapi.exceptions import RequestValidationError
async def robust_request_parser(request: Request, body_field=None):
"""Robust request parsing with comprehensive error handling."""
try:
body = await get_body_from_request(request, body_field)
return {"success": True, "body": body}
except RequestValidationError as e:
return {
"success": False,
"error": "validation_error",
"details": e.errors()
}
except Exception as e:
return {
"success": False,
"error": "parsing_error",
"message": str(e)
}
Performance Considerations¶
- Caching: Many utility functions benefit from caching resolved dependencies
- Async Operations: Use async versions when dealing with I/O operations
- Parameter Optimization: The
rearrange_params
function is highly optimized for performance - Memory Management: Utility functions handle resource cleanup automatically
Internal Architecture¶
The utils module serves as the bridge between FastAPI Shield and FastAPI's internal systems:
- Dependency Resolution: Integrates with FastAPI's dependency injection system
- Request Processing: Handles various content types and parsing scenarios
- Signature Management: Optimizes function signatures for performance
- Error Handling: Provides comprehensive error handling and validation
See Also¶
- Shield Class - Main shield implementation
- ShieldDepends - Dependency injection integration
- OpenAPI Integration - Schema generation utilities