Skip to content

avoid reading in whole blob during NamedBlobFile validation (slows TUS uploads) #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions plone/namedfile/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from zope.interface import Interface
from zope.schema import Object
from zope.schema import ValidationError

from zope.schema._bootstrapinterfaces import SchemaNotProvided

_ = MessageFactory("plone")

Expand Down Expand Up @@ -106,7 +106,12 @@ def __init__(self, **kw):
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
# we don't want default validation of super()._validate(value)
# as that will read the whole file into memory
# schema has to be provided by value
if not self.schema.providedBy(value):
raise SchemaNotProvided(self.schema, value).with_field_and_value(
self, value)
validate_file_field(self, value)


Expand All @@ -123,5 +128,10 @@ def __init__(self, **kw):
super().__init__(schema=self.schema, **kw)

def _validate(self, value):
super()._validate(value)
# we don't want default validation of super()._validate(value)
# as that will read the whole file into memory
# schema has to be provided by value
if not self.schema.providedBy(value):
raise SchemaNotProvided(self.schema, value).with_field_and_value(
self, value)
validate_image_field(self, value)
46 changes: 46 additions & 0 deletions plone/namedfile/tests/test_storable.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from OFS.Image import Pdata
from plone.namedfile.file import FileChunk
from plone.namedfile.file import NamedBlobImage
from plone.namedfile.file import NamedBlobFile
from plone.namedfile import field
from plone.namedfile.testing import PLONE_NAMEDFILE_FUNCTIONAL_TESTING
from plone.namedfile.tests import getFile

Expand Down Expand Up @@ -56,3 +58,47 @@ def test_opened_file_storable(self):
if os.path.exists(path):
os.remove(path)
self.assertEqual(303, fi.getSize())

def test_upload_no_read(self):
# ensure we don't read the whole file into memory

import ZODB.blob

old_open = ZODB.blob.Blob.open
blob_read = 0
blob_write = 0

def count_open(self, mode="r"):
nonlocal blob_read, blob_write
blob_read += 1 if "r" in mode else 0
blob_write += 1 if "w" in mode else 0
return old_open(self, mode)

ZODB.blob.Blob.open = count_open

data = getFile("image.gif")
f = tempfile.NamedTemporaryFile(delete=False)
try:
path = f.name
f.write(data)
f.close()
with open(path, "rb") as f:
fi = NamedBlobFile(f, filename="image.gif")
finally:
if os.path.exists(path):
os.remove(path)
self.assertEqual(303, fi.getSize())
self.assertEqual(blob_read, 1, "blob should have only been opened to get size")
self.assertEqual(
blob_write,
1,
"Slow write to blob instead of os rename. Should be only 1 on init",
)
blob_read = 0

blob_field = field.NamedBlobFile()
blob_field.validate(fi)

self.assertEqual(blob_read, 0, "Validation is reading the whole blob in memory")

ZODB.blob.Blob.open = old_open