Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save federicoemartinez/4fb5b033684802e0bc9a73e53ab1897f to your computer and use it in GitHub Desktop.

Select an option

Save federicoemartinez/4fb5b033684802e0bc9a73e53ab1897f to your computer and use it in GitHub Desktop.
import io
from django.conf import settings
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.http import QueryDict
from rest_framework.exceptions import ParseError
from rest_framework.parsers import BaseParser
class DefnullPushMultipartParser(BaseParser):
"""
High-performance multipart parser using defnull/multipart's PushMultipartParser.
This is the FASTEST parser in our benchmarks - approximately 2x faster than
DRF's default MultiPartParser. It works in both sync and async Django views.
The PushMultipartParser is a streaming, non-blocking parser that processes
data incrementally without buffering the entire request in memory.
Install: pip install multipart
Usage:
from parsers import DefnullPushMultipartParser
class MyUploadView(APIView):
parser_classes = [DefnullPushMultipartParser]
def post(self, request):
data = request.data['data']
files = request.data['files']
# ...
"""
media_type = 'multipart/form-data'
def parse(self, stream, media_type=None, parser_context=None):
import multipart as defnull_multipart
request = parser_context.get('request')
encoding = parser_context.get('encoding', settings.DEFAULT_CHARSET)
content_type = request.content_type
# Parse options header to get boundary
_, options = defnull_multipart.parse_options_header(content_type)
boundary = options.get('boundary')
if not boundary:
raise ParseError('Missing boundary in multipart content-type header')
fields = QueryDict(mutable=True)
from django.utils.datastructures import MultiValueDict
files_dict = MultiValueDict()
current_segment = None
current_data = []
with defnull_multipart.PushMultipartParser(boundary, header_charset=encoding) as parser:
# Use blocking parse for sync context
for event in parser.parse_blocking(stream.read):
if isinstance(event, defnull_multipart.MultipartSegment):
# New part started
if current_segment is not None:
# Finalize previous part
self._finalize_part(
current_segment, current_data,
fields, files_dict, encoding
)
current_segment = event
current_data = []
elif event: # bytes data
current_data.append(event)
else: # None = end of part
if current_segment is not None:
self._finalize_part(
current_segment, current_data,
fields, files_dict, encoding
)
current_segment = None
current_data = []
return {'data': fields, 'files': files_dict}
def _finalize_part(self, segment, data_chunks, fields, files_dict, encoding):
"""Finalize a multipart segment and add to fields or files."""
name = segment.name
data = b''.join(data_chunks)
if segment.filename:
uploaded_file = InMemoryUploadedFile(
file=io.BytesIO(data),
field_name=name,
name=segment.filename,
content_type=segment.content_type or 'application/octet-stream',
size=len(data),
charset=encoding,
)
files_dict.appendlist(name, uploaded_file)
else:
fields.appendlist(name, data.decode(encoding))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment