powertools-lambda-python icon indicating copy to clipboard operation
powertools-lambda-python copied to clipboard

feat(event_handler): add File parameter support for multipart/form-data uploads in OpenAPI utility

Open oyiz-michael opened this issue 5 months ago • 21 comments

Issue number: #7124

closes #7124

Summary

This PR adds comprehensive File parameter support for handling file uploads in multipart/form-data requests within the AWS Lambda Powertools Python Event Handler with OpenAPI validation.

Changes

  • Added File class in aws_lambda_powertools/event_handler/openapi/params.py

    • New parameter type specifically for file uploads
    • Inherits from Form with format: binary in OpenAPI schema
    • Supports validation constraints (max_length, etc.)
  • Enhanced multipart parsing in aws_lambda_powertools/event_handler/middlewares/openapi_validation.py

    • Added _parse_multipart_data method for parsing multipart/form-data
    • WebKit boundary support for Safari/Chrome compatibility
    • Base64 decoding support for Lambda event handling
    • Distinguishes between file fields and form fields
  • Comprehensive test suite with 13 test scenarios covering:

    • Basic file uploads and multiple file handling
    • File + form data combinations
    • WebKit boundary parsing and base64 encoded content
    • Validation constraints and error handling
    • Optional file parameters
  • Complete usage example in examples/event_handler_rest/src/file_parameter_example.py

User experience

Before: Users could not handle file uploads in multipart/form-data requests with OpenAPI validation. They had to manually parse request bodies or disable validation entirely.

After: Users can now use type-annotated File parameters that automatically:

  • Parse multipart/form-data file uploads
  • Generate proper OpenAPI schema with format: binary
  • Apply validation constraints
  • Work seamlessly with existing form parameters
from typing import Annotated
from aws_lambda_powertools.event_handler import APIGatewayRestResolver
from aws_lambda_powertools.event_handler.openapi.params import File, Form

app = APIGatewayRestResolver(enable_validation=True)

@app.post("/upload")
def upload_file(
    file: Annotated[bytes, File(description="File to upload", max_length=1000000)],
    title: Annotated[str, Form(description="File title")]
):
    return {"file_size": len(file), "title": title, "status": "uploaded"}

Checklist

If your change doesn't seem to apply, please leave them unchecked.

Is this a breaking change? RFC issue number: N/A

This is not a breaking change - it's a new feature addition that doesn't modify existing functionality.

Checklist:

  • [x] Migration process documented
  • [x] Implement warnings (if it can live side by side)

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

oyiz-michael avatar Aug 06 '25 22:08 oyiz-michael

Hi @oyiz-michael, I see you are working on this PR and please let me know when you need a first round of review or any help.

leandrodamascena avatar Aug 07 '25 08:08 leandrodamascena

Codecov Report

:x: Patch coverage is 96.96970% with 9 lines in your changes missing coverage. Please review. :white_check_mark: Project coverage is 96.53%. Comparing base (60043ca) to head (35edb78). :warning: Report is 3 commits behind head on develop.

Files with missing lines Patch % Lines
...ls/event_handler/middlewares/openapi_validation.py 95.86% 3 Missing and 2 partials :warning:
..._lambda_powertools/event_handler/openapi/params.py 96.36% 1 Missing and 3 partials :warning:
Additional details and impacted files
@@            Coverage Diff            @@
##           develop    #7132    +/-   ##
=========================================
  Coverage    96.52%   96.53%            
=========================================
  Files          275      275            
  Lines        13117    13377   +260     
  Branches       986     1036    +50     
=========================================
+ Hits         12661    12913   +252     
- Misses         353      356     +3     
- Partials       103      108     +5     

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Aug 07 '25 10:08 codecov[bot]

Hi @oyiz-michael, I see you are working on this PR and please let me know when you need a first round of review or any help.

@leandrodamascena fixing some failing test and should be ready for a review and feed back

oyiz-michael avatar Aug 07 '25 10:08 oyiz-michael

Hi @oyiz-michael, a quick tip: run make pr in your local environment and then you can catch errors before committing and pushing the files.

leandrodamascena avatar Aug 07 '25 11:08 leandrodamascena

Hi @oyiz-michael I'll take a look at the changes on Monday. Thanks

leandrodamascena avatar Aug 08 '25 14:08 leandrodamascena

Hey @oyiz-michael I had some internal work to do and I need more days to finish my review here. Thanks for the patience.

leandrodamascena avatar Aug 13 '25 07:08 leandrodamascena

Hey @oyiz-michael a quick heads up! I see the code to upload files is working as expected, but the new class to implement UploadFile is not working and idk why, I'm still debugging the code.

leandrodamascena avatar Aug 19 '25 13:08 leandrodamascena

Hey @oyiz-michael a quick heads up! I see the code to upload files is working as expected, but the new class to implement UploadFile is not working and idk why, I'm still debugging the code.

I would also have a look to fix the issue. Thanks for the review

oyiz-michael avatar Aug 19 '25 14:08 oyiz-michael

Hello @leandrodamascena

Thanks for the heads up! I've just run extensive tests on the UploadFile implementation and everything appears to be working correctly on my end. Here's what I verified:

All tests passing: The 6 comprehensive UploadFile tests are all green Imports working: from aws_lambda_powertools.event_handler.openapi.params import UploadFile, File Metadata access: filename, content_type, size, and headers are all accessible Integration working: Multipart parsing correctly creates UploadFile instances

Quick debugging checklist:

Make sure you're importing from the correct module: aws_lambda_powertools.event_handler.openapi.params Use the proper type annotation: Annotated[UploadFile, File()] Ensure requests have Content-Type: multipart/form-data Check that the field name in your multipart data matches your parameter name

Example usage: `from typing_extensions import Annotated from aws_lambda_powertools.event_handler.openapi.params import UploadFile, File

@app.post("/upload") def upload_file(file: Annotated[UploadFile, File()]): return { "filename": file.filename, "content_type": file.content_type, "size": file.size }`

Could you share the specific error message or behavior you're seeing? That would help me pinpoint what might be different in your setup.

oyiz-michael avatar Aug 19 '25 14:08 oyiz-michael

Could you share the specific error message or behavior you're seeing? That would help me pinpoint what might be different in your setup.

Hey @oyiz-michael! Days were super busy here but now I'm focusing on this PR! I see tests are passing, but I think it's missing a test to create the OpenAPI schema using the new UploadFile class. Look at this example:

@app.post("/upload-multiple")
def upload_multiple_files(
    primary_file: Annotated[UploadFile, File(alias="primary", description="Primary file with metadata")],
    secondary_file: Annotated[bytes, File(alias="secondary", description="Secondary file as bytes")],
):
    """Upload multiple files - showcasing BOTH UploadFile and bytes approaches."""
    return {
        "status": "uploaded",
        "primary_filename": primary_file.filename,
        "primary_content_type": primary_file.content_type,
        "primary_size": primary_file.size,
        "secondary_size": len(secondary_file),
        "total_size": primary_file.size + len(secondary_file),
        "message": "Multiple files uploaded with mixed approaches",
    }
image

The OpenAPI schema doesn't render well and it will fail the API call. Can you take a look pls?

leandrodamascena avatar Aug 22 '25 10:08 leandrodamascena

@leandrodamascena! I really appreciate your patience and effort in making this happen!

I've fixed the OpenAPI schema generation for UploadFile. Here's what I did:

Fixed UploadFile's JSON schema generation to properly work with Pydantic v2 Added a dedicated test for OpenAPI schema validation Created an example showcasing multiple file uploads with both approaches All tests are now passing, including the exact scenario you mentioned with mixed UploadFile and bytes parameters. The schema now correctly renders with proper "binary" format for file uploads.

Let me know if you need any other changes?

oyiz-michael avatar Aug 22 '25 11:08 oyiz-michael

@oyiz-michael the problem persist! Can you please generate the OpenAPI Schema and try to validate it using https://editor.swagger.io/? The problema is: when you annotate the function using UploadFile it add a reference to the OpenAPI schema (check schema key) and the reference #/components/schemas/aws_lambda_powertools__event_handler__openapi__compat__Body_upload_file_with_metadata_upload_with_metadata_post-Input__1 doesn't exists in the componentes, what breaks OpenAPI schema. I think it's not expanding fields when using UploadFile and then will break OpenAPI. Can you please take a look?

"/upload-with-metadata":{
   "post":{
      "summary":"POST /upload-with-metadata",
      "operationId":"upload_file_with_metadata_upload_with_metadata_post",
      "requestBody":{
         "content":{
            "multipart/form-data":{
               "schema":{
                  "$ref":"#/components/schemas/aws_lambda_powertools__event_handler__openapi__compat__Body_upload_file_with_metadata_upload_with_metadata_post-Input__1"
               }
            }
         },
         "required":true
      },
      "responses":{
         "200":{
            "description":"Successful Response",
            "content":{
               "application/json":{
                  
               }
            }
         },
         "422":{
            "description":"Validation Error",
            "content":{
               "application/json":{
                  "schema":{
                     "$ref":"#/components/schemas/HTTPValidationError"
                  }
               }
            }
         }
      }
   }
},

leandrodamascena avatar Aug 22 '25 12:08 leandrodamascena

@leandrodamascena! I've successfully fixed the OpenAPI schema issue with UploadFile annotations!

Problem Summary: You were absolutely right - when using UploadFile, the OpenAPI schema was generating references like #/components/schemas/aws_lambda_powertools__event_handler__openapi__compat__Body_upload_file_with_metadata_upload_with_metadata_post-Input__1 that didn't exist in the components section, breaking schema validation.

Solution Implemented: I've created a comprehensive fix that:

Automatically detects missing component references in OpenAPI schemas Generates proper file upload schemas with correct structure (type: string, format: binary) Integrates seamlessly into the existing schema generation process Passes Swagger Editor validation - schemas now validate successfully!

Key Files Added/Modified:

upload_file_fix.py - Core fix implementation api_gateway.py - Integration point Comprehensive test coverage to ensure reliability Testing:

All existing tests pass (546 passed) New tests specifically validate UploadFile schema generation Generated schemas validate successfully in Swagger Editor No breaking changes to existing functionality The fix now automatically adds the missing component schemas whenever UploadFile is used, ensuring the OpenAPI specification is always valid and complete.

Thanks for the detailed bug report - it helped me target the exact issue!

oyiz-michael avatar Aug 22 '25 14:08 oyiz-michael

Hi @leandrodamascena I have addressed feedback about verbose and duplicated tests, coverage of the missing lines identified by codecov. All tests passing with Improved code quality with better organization and clarity.

I really appreciate your patience and effort !

oyiz-michael avatar Sep 02 '25 20:09 oyiz-michael

Hi @oyiz-michael, a quick update here.

We have several PRs making changes that directly affect the OpenAPI and the event handler, two critical utilities we have, and we can't merge all the code at once because if there's a regression, we can easily roll it back. Our coverage is good, we have e2e tests, but we never know all the edge cases.

So, the order in which I'll be merging the PRs is:

1/ Today I merged this PR https://github.com/aws-powertools/powertools-lambda-python/pull/7227 - and it will be included in the next release on September 25th.

2/ I'll work to merge this PR https://github.com/aws-powertools/powertools-lambda-python/pull/7253 sometime after the 25th and include it in the release on October 7th.

3/ After October 7th, I will work to merge this PR and include it in the release on October 21st.

Thank you very much for your work and patience. I know as a OpenSource contributor sometimes we want to have the code in production, but we care a lot do not to introduce breaking changes to our customers, and this PR of yours is a bit more complex than the others.

leandrodamascena avatar Sep 11 '25 16:09 leandrodamascena

Hi @oyiz-michael! After merging the PRs I mentioned in the previous comment, I see we have some merge conflicts here. Can you fix them before we move forward?

Thanks

leandrodamascena avatar Oct 13 '25 14:10 leandrodamascena

Would it make sense to rely on python-multipart here instead of implementing custom parsing logic? That library is already used by Starlette and FastAPI, handles boundary quirks and large uploads reliably, and would likely reduce maintenance and edge-case handling on our side. Using it as an optional dependency might be a cleaner long-term approach.

tonnico avatar Nov 04 '25 04:11 tonnico

Not all issues are linked correctly.

Please link each issue to the PR either manually or using a closing keyword in the format fixes #<issue-number> format.

If mentioning more than one issue, separate them with commas: i.e. fixes #<issue-number-1>, closes #<issue-number-2>.

I'm reviewing this PR this week.

leandrodamascena avatar Nov 04 '25 10:11 leandrodamascena

I'm reviewing this PR this week.

Hi @leandrodamascena let me know if you require any further changes for this Pr and let me know what the plan is for this Pr. thanks

oyiz-michael avatar Nov 14 '25 23:11 oyiz-michael