Skip to content

Commit b258f3b

Browse files
authored
Temp fix and document 3.11+ fix for contexts (#2617)
* Add work-around for 3.11+ warnings from Sphinx breaking PIL context managers (Weird, right?) * Add some notes on Sphinx interference with streams
1 parent cdeed03 commit b258f3b

File tree

1 file changed

+71
-12
lines changed

1 file changed

+71
-12
lines changed

util/create_resources_listing.py

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,71 @@ def do_filetile(out, suffix: str | None = None, state: str = None):
649649
f" <img class=\"resource-thumb\" src=\"{src_kludge('/_static/filetiles/' + name)}\"/>\n\n"))
650650

651651

652+
# pending: a fix for Pillow / Sphinx interactions?
653+
def read_image_size(path: Path | str) -> tuple[int, int]:
654+
"""Get the size of a raster image and close the file.
655+
656+
This function ensures Sphinx does not break ``with``
657+
blocks using :py:func:`PIL.Image.open`:
658+
659+
Pillow makes assumptions about streams which Sphinx
660+
may interfere with:
661+
662+
#. Pillow assumes things about stream read / write
663+
#. Sphinx sometimes changes stream read / write global
664+
#. This makes :py:func:`PIL.Image.open` fail to close files
665+
#. Python 3.11+ reports unclosed files with warning
666+
667+
This is where the problem begins:
668+
669+
* When nitpicky mode is off, the logs are filled with noise
670+
* When it is on, build can break
671+
672+
The fix below is good-enough to get build running. To dive
673+
deper, start with these:
674+
675+
#. Pillow dislikes things which alter stream read/write
676+
(See https://github.com/python-pillow/Pillow/issues/2760)
677+
#. Sphinx overrides logging stream handling
678+
(See https://www.sphinx-doc.org/en/master/extdev/logging.html#sphinx.util.logging.getLogger)
679+
680+
Args:
681+
path: A path to an image file to read the size of.
682+
683+
Returns:
684+
A ``(width, height)`` tuple of the image size.
685+
"""
686+
# Isolating this in a function prevents Sphinx and other
687+
# "magic" stream things from breaking the context manager.
688+
# If you care to investigate, see the docstring's links.
689+
with PIL.Image.open(path) as im:
690+
return im.size
691+
692+
693+
def read_size_info(path: Path) -> str:
694+
"""Cleanliness wrapper for reading image sizes.
695+
696+
#. SVGs say they are SVGs
697+
#. Raster graphics report pixel size
698+
#. All else says it couldn't get size info.
699+
700+
Args:
701+
path: A path to an image file.
702+
703+
Returns:
704+
The formatted size info as either dimensions or
705+
another status string.
706+
"""
707+
if path.suffix == ".svg":
708+
return "Scalable Vector Graphic"
709+
710+
elif (pair := read_image_size(path)):
711+
width, height = pair
712+
return f"{width} px x {height} px"
713+
714+
return "Could not read size info"
715+
716+
652717
def process_resource_files(
653718
out,
654719
file_list: List[Path],
@@ -707,18 +772,12 @@ def start():
707772
#out.write(indent(" ", tile_rst_code))
708773

709774
size_info = None
710-
if suffix == ".svg":
711-
size_info = "Scalable Vector Graphic"
712-
else:
713-
try:
714-
im = PIL.Image.open(path)
715-
im_width, im_height = im.size
716-
size_info = f"{im_width}px x {im_height}px"
717-
except Exception as e:
718-
log.warning(f"FAILED to read size info for {path}:\n {e}")
719-
720-
if size_info is None:
721-
size_info = "Could not read size info"
775+
try:
776+
size_info = read_size_info(path)
777+
except Exception as e:
778+
log.warning(f"FAILED to read size info for {path}:\n {e}")
779+
780+
722781
parts.append(f"*({size_info})*\n")
723782
out.write(indent(" ", '\n'.join(parts)))
724783
out.write("\n\n")

0 commit comments

Comments
 (0)