1
1
"""
2
2
Example 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.
3
5
"""
4
6
5
7
from __future__ import annotations
6
8
7
9
from typing import Annotated , Union
8
10
9
11
from 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
11
13
12
14
# Initialize resolver with OpenAPI validation enabled
13
15
app = APIGatewayRestResolver (enable_validation = True )
14
16
15
17
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
+
16
62
@app .post ("/upload" )
17
63
def 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!) ."""
19
65
return {"status" : "uploaded" , "file_size" : len (file ), "message" : "File uploaded successfully" }
20
66
21
67
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 (
24
70
file : Annotated [bytes , File (description = "File to upload" )],
25
71
description : Annotated [str , Form (description = "File description" )],
26
72
tags : Annotated [Union [str , None ], Form (description = "Optional tags" )] = None , # noqa: UP007
27
73
):
28
- """Upload a file with additional form metadata."""
74
+ """Upload a file with additional form metadata - LEGACY bytes approach ."""
29
75
return {
30
76
"status" : "uploaded" ,
31
77
"file_size" : len (file ),
@@ -37,22 +83,24 @@ def upload_file_with_metadata(
37
83
38
84
@app .post ("/upload-multiple" )
39
85
def 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 " )],
42
88
):
43
- """Upload multiple files."""
89
+ """Upload multiple files - showcasing BOTH UploadFile and bytes approaches ."""
44
90
return {
45
91
"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 ,
47
95
"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 " ,
50
98
}
51
99
52
100
53
101
@app .post ("/upload-with-constraints" )
54
102
def 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 ."""
56
104
return {
57
105
"status" : "uploaded" ,
58
106
"file_size" : len (file ),
@@ -63,14 +111,16 @@ def upload_small_file(file: Annotated[bytes, File(description="Small file only",
63
111
@app .post ("/upload-optional" )
64
112
def upload_optional_file (
65
113
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 ,
67
115
):
68
- """Upload with an optional file parameter. """
116
+ """Upload with an optional UploadFile parameter - NEW approach! """
69
117
return {
70
118
"status" : "processed" ,
71
119
"message" : message ,
72
120
"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 ,
74
124
}
75
125
76
126
@@ -80,13 +130,28 @@ def lambda_handler(event, context):
80
130
return app .resolve (event , context )
81
131
82
132
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