From 77acf963d7624d1eb01c00037f94a06910fac61c Mon Sep 17 00:00:00 2001 From: Igor Okulist Date: Thu, 14 Mar 2024 21:55:47 -0500 Subject: [PATCH] add form parsing test --- awscurl/awscurl.py | 54 ++++++++++++++++++-------------------- tests/form_parsing_test.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 tests/form_parsing_test.py diff --git a/awscurl/awscurl.py b/awscurl/awscurl.py index def58fb..adcc3bd 100755 --- a/awscurl/awscurl.py +++ b/awscurl/awscurl.py @@ -12,7 +12,7 @@ import sys import re -from typing import IO, List, Optional, Tuple, Union +from typing import IO, Dict, List, Optional, Tuple, Union import urllib from urllib.parse import quote @@ -434,53 +434,51 @@ def load_aws_config(access_key, secret_key, security_token, credentials_path, pr return access_key, secret_key, security_token -def __process_form_data(form_data: List[str]) -> Optional[List[Tuple[str, Union[str, Tuple[str, Union[IO, Tuple[IO, str]]]]]]]: +def process_form_data(form_data: List[str]) -> Dict[str, Union[Tuple[str, IO[bytes]], Tuple[str, IO[bytes], str], Tuple[str, IO[bytes], str, Dict[str, str]]]]: """ - Process form data to prepare files for uploading with optional MIME types, - supporting specific form field names for each file. + Process form data to prepare files for uploading, supporting specific form field names for each file + and accommodating the `requests` library files parameter format. This function is intended for internal + use within this module. Args: - form_data (list of str): Each string in the list should be formatted as - 'fieldname=@filepath[;type=mimetype]'. + form_data: Each string in the list should be formatted as 'fieldname=@filepath[;type=mimetype]'. Examples: - - Simple file upload with field name: - 'profile=@portrait.jpg' - This will upload a file located at 'portrait.jpg' with the field name 'profile'. + - Simple file upload with field name: 'profile=@portrait.jpg' + This will upload a file located at 'portrait.jpg' under the field name 'profile'. - - File upload with field name and MIME type: - 'document=@report.pdf;type=application/pdf' + - File upload with field name and MIME type: 'document=@report.pdf;type=application/pdf' This uploads 'report.pdf' as 'document', with an explicit MIME type 'application/pdf'. - - Multiple file uploads: - ['photo=@vacation.jpg', 'resume=@resume.docx;type=application/msword'] + - Multiple file uploads: ['photo=@vacation.jpg', 'resume=@resume.docx;type=application/msword'] This example shows how to upload multiple files with different field names and specifying MIME type for one of the files. Returns: - list of tuple: A list where each tuple represents a file to be uploaded. Each tuple is - (fieldname, filetuple), where 'filetuple' is either (filepath, fileobject) - or (filepath, (fileobject, mimetype)) if a MIME type is specified. + A dictionary where keys are the form field names and the values are file-tuples compatible + with the `requests` library. The file-tuple can be a 2-tuple ('filename', fileobj), + 3-tuple ('filename', fileobj, 'content_type'), or a 4-tuple + ('filename', fileobj, 'content_type', custom_headers) if additional headers are needed. """ - if form_data is None: - return None - - files = [] + files: Dict[str, Union[Tuple[str, IO[bytes]], Tuple[str, IO[bytes], str], Tuple[str, IO[bytes], str, Dict[str, str]]]] = {} for file_arg in form_data: - # Splitting the argument into its components: field name, file path, and optional MIME type parts = file_arg.split(';') field_name, file_path = parts[0].split('=', 1) file_path = file_path[1:] # Remove the '@' at the beginning mime_type = None - # Look for an optional MIME type specification - for part in parts[1:]: + for part in parts: if part.startswith('type='): - mime_type = part[5:] + mime_type = part.split('=', 1)[1] + break # Assuming only one MIME type specification, break after finding + + file_obj: IO[bytes] = open(file_path, 'rb') + file_tuple: Union[Tuple[str, IO[bytes]], Tuple[str, IO[bytes], str]] = (file_path.split('/')[-1], file_obj) + if mime_type: + file_tuple = (file_path.split('/')[-1], file_obj, mime_type) + + files[field_name] = file_tuple - # Adding the file to the list, including the MIME type if specified - file_tuple = (file_path, (open(file_path, 'rb'), mime_type)) if mime_type else (file_path, open(file_path, 'rb')) - files.append((field_name, file_tuple)) return files @@ -546,7 +544,7 @@ def inner_main(argv): with open(filename, read_mode) as post_data_file: data = post_data_file.read() - files = __process_form_data(args.form) + files = process_form_data(args.form) if args.header is None: args.header = default_headers diff --git a/tests/form_parsing_test.py b/tests/form_parsing_test.py new file mode 100644 index 0000000..f112af9 --- /dev/null +++ b/tests/form_parsing_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python + + +from unittest import TestCase + +from awscurl import awscurl + +__author__ = 'iokulist' + +import unittest +from typing import Dict, Tuple, Union, IO +from awscurl.awscurl import process_form_data + +class TestProcessFormData(unittest.TestCase): + def test_single_file_upload(self): + form_data = ['profile=@README.md'] + result = process_form_data(form_data) + self.assertIn('profile', result) + self.assertTrue(isinstance(result['profile'], Tuple)) + self.assertEqual(result['profile'][0], 'README.md') + + def test_file_upload_with_mime(self): + form_data = ['document=@README.md;type=application/pdf'] + result = process_form_data(form_data) + self.assertIn('document', result) + self.assertTrue(isinstance(result['document'], Tuple)) + self.assertEqual(result['document'][0], 'README.md') + self.assertEqual(result['document'][2], 'application/pdf') + + def test_multiple_file_uploads(self): + form_data = [ + 'photo=@README.md', + 'resume=@LICENSE;type=application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ] + result = process_form_data(form_data) + self.assertIn('photo', result) + self.assertIn('resume', result) + self.assertTrue(isinstance(result['photo'], Tuple)) + self.assertTrue(isinstance(result['resume'], Tuple)) + self.assertEqual(result['resume'][2], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') + +if __name__ == '__main__': + unittest.main() +