Skip to content

Commit 63e9e90

Browse files
authored
Merge pull request #8229 from 4teamwork/es/TI-3678-fix-tus-upload
Avoid loading file content into RAM during TUS upload deserialization
2 parents 3e12164 + 7fd2de9 commit 63e9e90

3 files changed

Lines changed: 38 additions & 0 deletions

File tree

changes/TI-3678.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix huge memory counsumption if uploading a large file [elioschmutz]

opengever/api/configure.zcml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
<adapter factory=".field_deserializers.DateFieldDeserializer" />
8080
<adapter factory=".field_deserializers.DatetimeFieldDeserializer" />
8181
<adapter factory=".field_deserializers.UTCDatetimeFieldDeserializer" />
82+
<adapter factory=".field_deserializers.GeverNamedFieldDeserializer" />
8283
<adapter factory=".todo.SerializeToDoToJson" />
8384

8485
<configure zcml:condition="installed z3c.relationfield">

opengever/api/field_deserializers.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@
55
from opengever.base.schema import IUTCDatetime
66
from persistent.interfaces import IPersistent
77
from plone.dexterity.interfaces import IDexterityContent
8+
from plone.namedfile.interfaces import INamedField
89
from plone.restapi.deserializer.dxfields import DatetimeFieldDeserializer
910
from plone.restapi.deserializer.dxfields import DefaultFieldDeserializer
11+
from plone.restapi.deserializer.dxfields import NamedFieldDeserializer
1012
from plone.restapi.interfaces import IFieldDeserializer
13+
from plone.restapi.services.content.tus import TUSUpload
1114
from pytz import utc
1215
from zope.component import adapter
1316
from zope.interface import implementer
1417
from zope.publisher.interfaces.browser import IBrowserRequest
1518
from zope.schema.interfaces import IDate
1619
from zope.schema.interfaces import IDatetime
1720
from zope.schema.interfaces import IField
21+
from zope.schema.interfaces import RequiredMissing
1822
import dateutil
1923

2024

@@ -84,3 +88,35 @@ def reject_year_before_1900(value):
8488
if isinstance(value, (date, datetime)) and value.year < 1900:
8589
raise ValueError(
8690
'year=%s is invalid. Year must be >= 1900.' % value.year)
91+
92+
93+
class TUSUploadNamedField(object):
94+
"""Lightweight named file field used during TUS uploads instead of the
95+
original INamedField, to avoid loading the entire file content into RAM
96+
during field level validations.
97+
"""
98+
99+
def __init__(self, original_field):
100+
self.required = original_field.required
101+
self.missing_value = original_field.missing_value
102+
self.__name__ = original_field.__name__
103+
self._type = original_field._type
104+
105+
def validate(self, value):
106+
if value == self.missing_value and self.required:
107+
raise RequiredMissing(self.__name__)
108+
109+
110+
@implementer(IFieldDeserializer)
111+
@adapter(INamedField, IDexterityContent, IOpengeverBaseLayer)
112+
class GeverNamedFieldDeserializer(NamedFieldDeserializer):
113+
"""For TUS uploads, replaces self.field with a TUSUploadNamedField before
114+
delegating to the parent. The parent's self.field.validate(value) call then
115+
hits TUSUploadNamedField.validate(), which only checks the required
116+
constraint without loading the blob content into RAM.
117+
"""
118+
119+
def __call__(self, value):
120+
if isinstance(value, TUSUpload):
121+
self.field = TUSUploadNamedField(self.field)
122+
return super(GeverNamedFieldDeserializer, self).__call__(value)

0 commit comments

Comments
 (0)