Commit 54f28d80 authored by Jérome Perrin's avatar Jérome Perrin

WIP: patches/python: override py3 builtin round to keep py2 behavior.

And workaround safeimage impacted by this round2 patch.
parent db6fdb11
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
############################################################################## ##############################################################################
import six import six
import contextlib
import os, sys, shutil, tempfile import os, sys, shutil, tempfile
import io import io
from zLOG import LOG,ERROR,INFO,WARNING from zLOG import LOG,ERROR,INFO,WARNING
...@@ -33,6 +34,27 @@ import base64 ...@@ -33,6 +34,27 @@ import base64
from OFS.Folder import Folder from OFS.Folder import Folder
from Products.ERP5Type.Utils import str2bytes from Products.ERP5Type.Utils import str2bytes
# XXX zope4py3, we patch builtin round to keep python2 behavior
# but this interfers with PIL, because python2 round sometimes
# returns an int where python3 round is supposed to return a float.
# This context manager swaps the implementation with the native
# float on python3, which is NOT THREAD SAFE. Don't use it in
# production, for now this makes the test pass 🤐.
import Products.ERP5Type.patches.python
@contextlib.contextmanager
def native_round():
if six.PY3:
round_original = __builtins__['round']
try:
__builtins__['round'] = Products.ERP5Type.patches.python.round_native
yield
finally:
__builtins__['round'] = round_original
else:
# on python2 do nothing
yield
class ZoomifyBase: class ZoomifyBase:
_v_imageFilename = '' _v_imageFilename = ''
...@@ -180,9 +202,10 @@ class ZoomifyBase: ...@@ -180,9 +202,10 @@ class ZoomifyBase:
saveFilename = root + str(tier) + '-' + str(row) + ext saveFilename = root + str(tier) + '-' + str(row) + ext
if imageRow.mode != 'RGB': if imageRow.mode != 'RGB':
imageRow = imageRow.convert('RGB') imageRow = imageRow.convert('RGB')
imageRow.save(os.path.join(tempfile.gettempdir(), saveFilename), with native_round():
# see https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg-saving imageRow.save(os.path.join(tempfile.gettempdir(), saveFilename),
# for quality, Values above 95 should be avoided; # see https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg-saving
# for quality, Values above 95 should be avoided;
'JPEG', quality=95) 'JPEG', quality=95)
if os.path.exists(os.path.join(tempfile.gettempdir(), saveFilename)): if os.path.exists(os.path.join(tempfile.gettempdir(), saveFilename)):
self.processRowImage(tier=tier, row=row) self.processRowImage(tier=tier, row=row)
...@@ -263,7 +286,8 @@ class ZoomifyBase: ...@@ -263,7 +286,8 @@ class ZoomifyBase:
(imageWidth//2, imageHeight//2), (imageWidth//2, imageHeight//2),
PIL_Image.LANCZOS if six.PY3 else PIL_Image.ANTIALIAS, PIL_Image.LANCZOS if six.PY3 else PIL_Image.ANTIALIAS,
) )
tempImage.save(os.path.join(tempfile.gettempdir(), root + str(tier) with native_round():
tempImage.save(os.path.join(tempfile.gettempdir(), root + str(tier)
+ '-' + str(row) + ext)) + '-' + str(row) + ext))
tempImage = None tempImage = None
rowImage = None rowImage = None
...@@ -542,7 +566,8 @@ class ERP5ZoomifyZopeProcessor(ZoomifyZopeProcessor): ...@@ -542,7 +566,8 @@ class ERP5ZoomifyZopeProcessor(ZoomifyZopeProcessor):
tile_group_id = self.getAssignedTileContainerName() tile_group_id = self.getAssignedTileContainerName()
tile_group=self.document[tile_group_id] tile_group=self.document[tile_group_id]
tileImageData= io.BytesIO() tileImageData= io.BytesIO()
image.save(tileImageData, 'JPEG', quality=self.qualitySetting) with native_round():
image.save(tileImageData, 'JPEG', quality=self.qualitySetting)
tileImageData.seek(0) tileImageData.seek(0)
if tile_group is None: if tile_group is None:
raise AttributeError('unable to fine tile group %r' % tile_group_id) raise AttributeError('unable to fine tile group %r' % tile_group_id)
......
...@@ -178,3 +178,43 @@ def patch_linecache(): ...@@ -178,3 +178,43 @@ def patch_linecache():
if sys.version_info[:3] < (3, ): if sys.version_info[:3] < (3, ):
patch_linecache() patch_linecache()
import decimal as _decimal
def round2(number, ndigits=None):
"""
See Python 2 documentation.
Rounds a number to a given precision in decimal digits (default
0 digits). The result is a floating point number. Values are rounded
to the closest multiple of 10 to the power minus ndigits; if two
multiples are equally close, rounding is done away from 0.
ndigits may be negative.
"""
if ndigits is None:
ndigits = 0
elif hasattr(ndigits, '__index__'):
# any type with an __index__ method should be permitted as
# a second argument
ndigits = ndigits.__index__()
if ndigits < 0:
exponent = 10 ** (-ndigits)
quotient, remainder = divmod(number, exponent)
if remainder >= exponent//2 and number >= 0:
quotient += 1
return float(quotient * exponent)
else:
exponent = _decimal.Decimal('10') ** (-ndigits)
d = _decimal.Decimal.from_float(number).quantize(
exponent, rounding=_decimal.ROUND_HALF_UP)
return float(d)
round_native = round
if sys.version_info >= (3, ):
__builtins__['round'] = round2
from AccessControl.ZopeGuards import safe_builtins
safe_builtins['round'] = round2
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment