|
8 | 8 | BaseRequestDataExtractor, |
9 | 9 | ) |
10 | 10 |
|
| 11 | +_MULTIPART_CACHE_ATTR = "_fastopenapi_multipart_cache" |
| 12 | + |
11 | 13 |
|
12 | 14 | class FalconRequestDataExtractor(BaseRequestDataExtractor): |
13 | 15 | @classmethod |
@@ -51,49 +53,70 @@ def _get_body(cls, request: Any) -> dict | list | None: |
51 | 53 | return {} |
52 | 54 |
|
53 | 55 | @classmethod |
54 | | - def _get_form_data(cls, request: Any) -> dict: |
55 | | - """Extract form data""" |
56 | | - form_data = {} |
57 | | - ct = (request.content_type or "").lower() |
| 56 | + def _parse_multipart( |
| 57 | + cls, request: Any |
| 58 | + ) -> tuple[dict, dict[str, FileUpload | list[FileUpload]]]: |
| 59 | + """Parse multipart stream once and cache the result on the request.""" |
| 60 | + cached = getattr(request, _MULTIPART_CACHE_ATTR, None) |
| 61 | + if isinstance(cached, tuple): |
| 62 | + return cached |
58 | 63 |
|
59 | | - if ct == "application/x-www-form-urlencoded": |
60 | | - form_data = request.media or {} |
61 | | - |
62 | | - return form_data |
63 | | - |
64 | | - @classmethod |
65 | | - def _get_files(cls, request: Any) -> dict[str, FileUpload | list[FileUpload]]: |
66 | | - """Extract files from Falcon request (sync)""" |
67 | | - files = {} |
68 | | - ct = (request.content_type or "").lower() |
| 64 | + form_data = {} |
| 65 | + files: dict[str, FileUpload | list[FileUpload]] = {} |
69 | 66 |
|
70 | | - if "multipart/form-data" in ct and hasattr(request, "get_media"): |
71 | | - form = request.get_media() |
72 | | - for part in form: |
73 | | - filename = getattr(part, "secure_filename", None) or getattr( |
74 | | - part, "filename", None |
75 | | - ) |
76 | | - if not filename: |
77 | | - continue |
| 67 | + form = request.get_media() |
| 68 | + for part in form: |
| 69 | + filename = getattr(part, "secure_filename", None) or getattr( |
| 70 | + part, "filename", None |
| 71 | + ) |
| 72 | + field_name = getattr(part, "name", filename) |
78 | 73 |
|
| 74 | + if filename: |
79 | 75 | content = part.stream.read() |
80 | 76 | file_upload = FileUpload( |
81 | 77 | filename=filename, |
82 | 78 | content_type=getattr(part, "content_type", None), |
83 | 79 | size=len(content), |
84 | 80 | file=content, |
85 | 81 | ) |
86 | | - |
87 | | - field_name = getattr(part, "name", filename) |
88 | 82 | if field_name in files: |
89 | 83 | if isinstance(files[field_name], list): |
90 | 84 | files[field_name].append(file_upload) |
91 | 85 | else: |
92 | 86 | files[field_name] = [files[field_name], file_upload] |
93 | 87 | else: |
94 | 88 | files[field_name] = file_upload |
| 89 | + else: |
| 90 | + form_data[field_name] = part.text |
| 91 | + |
| 92 | + result = (form_data, files) |
| 93 | + setattr(request, _MULTIPART_CACHE_ATTR, result) |
| 94 | + return result |
95 | 95 |
|
96 | | - return files |
| 96 | + @classmethod |
| 97 | + def _get_form_data(cls, request: Any) -> dict: |
| 98 | + """Extract form data""" |
| 99 | + ct = str(request.content_type or "").lower() |
| 100 | + |
| 101 | + if ct == "application/x-www-form-urlencoded": |
| 102 | + return request.media or {} |
| 103 | + |
| 104 | + if "multipart/form-data" in ct and hasattr(request, "get_media"): |
| 105 | + form_data, _ = cls._parse_multipart(request) |
| 106 | + return form_data |
| 107 | + |
| 108 | + return {} |
| 109 | + |
| 110 | + @classmethod |
| 111 | + def _get_files(cls, request: Any) -> dict[str, FileUpload | list[FileUpload]]: |
| 112 | + """Extract files from Falcon request (sync)""" |
| 113 | + ct = str(request.content_type or "").lower() |
| 114 | + |
| 115 | + if "multipart/form-data" in ct and hasattr(request, "get_media"): |
| 116 | + _, files = cls._parse_multipart(request) |
| 117 | + return files |
| 118 | + |
| 119 | + return {} |
97 | 120 |
|
98 | 121 |
|
99 | 122 | class FalconAsyncRequestDataExtractor( |
|
0 commit comments