11"""
22Example demonstrating File parameter usage for handling file uploads.
3+ This showcases both the new UploadFile class for metadata access and
4+ backward-compatible bytes approach.
35"""
46
57from __future__ import annotations
68
79from typing import Annotated , Union
810
911from aws_lambda_powertools .event_handler import APIGatewayRestResolver
10- from aws_lambda_powertools .event_handler .openapi .params import File , Form
12+ from aws_lambda_powertools .event_handler .openapi .params import File , Form , UploadFile
1113
1214# Initialize resolver with OpenAPI validation enabled
1315app = APIGatewayRestResolver (enable_validation = True )
1416
1517
18+ # ========================================
19+ # NEW: UploadFile with Metadata Access
20+ # ========================================
21+
22+
23+ @app .post ("/upload-with-metadata" )
24+ def upload_file_with_metadata (file : Annotated [UploadFile , File (description = "File with metadata access" )]):
25+ """Upload a file with full metadata access - NEW UploadFile feature!"""
26+ return {
27+ "status" : "uploaded" ,
28+ "filename" : file .filename ,
29+ "content_type" : file .content_type ,
30+ "file_size" : file .size ,
31+ "headers" : file .headers ,
32+ "content_preview" : file .read (100 ).decode ("utf-8" , errors = "ignore" ),
33+ "can_reconstruct_file" : True ,
34+ "message" : "File uploaded with metadata access" ,
35+ }
36+
37+
38+ @app .post ("/upload-mixed-form" )
39+ def upload_file_with_form_data (
40+ file : Annotated [UploadFile , File (description = "File with metadata" )],
41+ description : Annotated [str , Form (description = "File description" )],
42+ category : Annotated [str | None , Form (description = "File category" )] = None ,
43+ ):
44+ """Upload file with UploadFile metadata + form data."""
45+ return {
46+ "status" : "uploaded" ,
47+ "filename" : file .filename ,
48+ "content_type" : file .content_type ,
49+ "file_size" : file .size ,
50+ "description" : description ,
51+ "category" : category ,
52+ "custom_headers" : {k : v for k , v in file .headers .items () if k .startswith ("X-" )},
53+ "message" : "File and form data uploaded with metadata" ,
54+ }
55+
56+
57+ # ========================================
58+ # BACKWARD COMPATIBLE: Bytes Approach
59+ # ========================================
60+
61+
1662@app .post ("/upload" )
1763def upload_single_file (file : Annotated [bytes , File (description = "File to upload" )]):
18- """Upload a single file."""
64+ """Upload a single file - LEGACY bytes approach (still works!) ."""
1965 return {"status" : "uploaded" , "file_size" : len (file ), "message" : "File uploaded successfully" }
2066
2167
22- @app .post ("/upload-with -metadata" )
23- def upload_file_with_metadata (
68+ @app .post ("/upload-legacy -metadata" )
69+ def upload_file_legacy_with_metadata (
2470 file : Annotated [bytes , File (description = "File to upload" )],
2571 description : Annotated [str , Form (description = "File description" )],
2672 tags : Annotated [Union [str , None ], Form (description = "Optional tags" )] = None , # noqa: UP007
2773):
28- """Upload a file with additional form metadata."""
74+ """Upload a file with additional form metadata - LEGACY bytes approach ."""
2975 return {
3076 "status" : "uploaded" ,
3177 "file_size" : len (file ),
@@ -37,22 +83,24 @@ def upload_file_with_metadata(
3783
3884@app .post ("/upload-multiple" )
3985def upload_multiple_files (
40- primary_file : Annotated [bytes , File (alias = "primary" , description = "Primary file" )],
41- secondary_file : Annotated [bytes , File (alias = "secondary" , description = "Secondary file" )],
86+ primary_file : Annotated [UploadFile , File (alias = "primary" , description = "Primary file with metadata " )],
87+ secondary_file : Annotated [bytes , File (alias = "secondary" , description = "Secondary file as bytes " )],
4288):
43- """Upload multiple files."""
89+ """Upload multiple files - showcasing BOTH UploadFile and bytes approaches ."""
4490 return {
4591 "status" : "uploaded" ,
46- "primary_size" : len (primary_file ),
92+ "primary_filename" : primary_file .filename ,
93+ "primary_content_type" : primary_file .content_type ,
94+ "primary_size" : primary_file .size ,
4795 "secondary_size" : len (secondary_file ),
48- "total_size" : len ( primary_file ) + len (secondary_file ),
49- "message" : "Multiple files uploaded successfully " ,
96+ "total_size" : primary_file . size + len (secondary_file ),
97+ "message" : "Multiple files uploaded with mixed approaches " ,
5098 }
5199
52100
53101@app .post ("/upload-with-constraints" )
54102def upload_small_file (file : Annotated [bytes , File (description = "Small file only" , max_length = 1024 )]):
55- """Upload a file with size constraints (max 1KB)."""
103+ """Upload a file with size constraints (max 1KB) - bytes approach ."""
56104 return {
57105 "status" : "uploaded" ,
58106 "file_size" : len (file ),
@@ -63,14 +111,16 @@ def upload_small_file(file: Annotated[bytes, File(description="Small file only",
63111@app .post ("/upload-optional" )
64112def upload_optional_file (
65113 message : Annotated [str , Form (description = "Required message" )],
66- file : Annotated [Union [ bytes , None ] , File (description = "Optional file" )] = None , # noqa: UP007
114+ file : Annotated [UploadFile | None , File (description = "Optional file with metadata " )] = None ,
67115):
68- """Upload with an optional file parameter. """
116+ """Upload with an optional UploadFile parameter - NEW approach! """
69117 return {
70118 "status" : "processed" ,
71119 "message" : message ,
72120 "has_file" : file is not None ,
73- "file_size" : len (file ) if file else 0 ,
121+ "filename" : file .filename if file else None ,
122+ "content_type" : file .content_type if file else None ,
123+ "file_size" : file .size if file else 0 ,
74124 }
75125
76126
@@ -80,13 +130,28 @@ def lambda_handler(event, context):
80130 return app .resolve (event , context )
81131
82132
83- # The File parameter provides:
84- # 1. Automatic multipart/form-data parsing
85- # 2. OpenAPI schema generation with proper file upload documentation
86- # 3. Request validation with meaningful error messages
87- # 4. Support for file constraints (max_length, etc.)
88- # 5. Compatibility with WebKit and other browser boundary formats
89- # 6. Base64-encoded request handling (common in AWS Lambda)
90- # 7. Mixed file and form data support
91- # 8. Multiple file upload support
92- # 9. Optional file parameters
133+ # The File parameter now provides TWO approaches:
134+ #
135+ # 1. NEW UploadFile Class (Recommended):
136+ # - filename property (e.g., "document.pdf")
137+ # - content_type property (e.g., "application/pdf")
138+ # - size property (file size in bytes)
139+ # - headers property (dict of all multipart headers)
140+ # - read() method (flexible content access)
141+ # - Perfect for file reconstruction in Lambda/S3 scenarios
142+ #
143+ # 2. LEGACY bytes approach (Backward Compatible):
144+ # - Direct bytes content access
145+ # - Existing code continues to work unchanged
146+ # - Automatic conversion from UploadFile to bytes when needed
147+ #
148+ # Both approaches provide:
149+ # - Automatic multipart/form-data parsing
150+ # - OpenAPI schema generation with proper file upload documentation
151+ # - Request validation with meaningful error messages
152+ # - Support for file constraints (max_length, etc.)
153+ # - Compatibility with WebKit and other browser boundary formats
154+ # - Base64-encoded request handling (common in AWS Lambda)
155+ # - Mixed file and form data support
156+ # - Multiple file upload support
157+ # - Optional file parameters
0 commit comments