Bug 50989

Summary: cairo.SVGSurface(sys.stdout, foo, bar) doesn't work with python3
Product: pycairo Reporter: Shawn Landen <shawn>
Component: generalAssignee: Steve Chaplin <d74n5pohf9>
Status: RESOLVED FIXED QA Contact:
Severity: normal    
Priority: medium CC: simon.sapin
Version: unspecified   
Hardware: Other   
OS: All   
Whiteboard:
i915 platform: i915 features:
Attachments: Revert SVGSurface to bytes-mode

Description Shawn Landen 2012-06-11 22:31:18 UTC
If you open a svgsurface with sys.stdout 

when you call .finish(), then you get

 TypeError: must be str, not bytes

The docs says "fobj (None, str, file or file-like object) – a filename or writable file object. None may be used to specify no output. "

which sys.stdout is
Comment 1 Shawn Landen 2012-07-10 06:35:54 UTC
The bug in is _write_func at src/surface.c:127
Comment 2 Steve Chaplin 2012-07-12 12:37:07 UTC
sys.stdout is a file-like object, but in Python 3 there are now 2 types of file objects - text-mode and bytes-mode objects. The Python 2 sys.stdout was equivalent to the Python 3 bytes-mode objects and you could write binary data to sys.stdout. The Python 3 sys.stdout is a text-mode object and you can no longer write binary data to it. So the old method no longer works.

SVG files use XML format and are text-mode files. So I changed the SVG write_func to use text-mode. So writing
to sys.stdout is working again.

Fixed in the git repo.
Comment 3 Shawn Landen 2012-08-04 20:54:54 UTC
note to self:

fixed in commit 9576b3d77034d91f456a5b199a9fc9cc2fde3c08
Comment 4 Simon Sapin 2012-12-19 08:52:28 UTC
I think this is the wrong way to fix this bug: it helps with SVGSurface(sys.stdout, foo, bar) but not with PDFSurface(sys.stdout, foo, bar) and others.

The generated SVG files have a `<?xml version="1.0" encoding="UTF-8"?>` header, which mean they are bytes, not unicode. For that reason and for consistency with other surface types, I think that SVGSurface should always emit bytes. The documentation clarification are good, but the changes to surface.c in commit 9576b3d7703 should be reverted. This shouldn’t cause much compatibility problem since the change never was in a release.

The fix for the original issue is easy: to write bytes to the standard output in Python 3, use sys.stdout.buffer.
See http://docs.python.org/3/library/sys.html#sys.stdout
For code that runs on both Python 2 and 3, use .buffer if it exists:

bytes_stdout = sys.stdout.buffer if hasattr(sys.stdout, 'buffer') else sys.stdout
# Or shorter:
bytes_stdout = getattr(sys.stdout, 'buffer', sys.stdout)

cairo.SVGSurface(stdout foo, bar)
cairo.PDFSurface(stdout foo, bar)

For an in-memory file object, always use io.BytesIO(). It’s available since Python 2.6.
Comment 5 Simon Sapin 2012-12-19 09:07:23 UTC
Created attachment 71790 [details] [review]
Revert SVGSurface to bytes-mode

This reverts some changes in 9576b3d77034d91f456a5b199a9fc9cc2fde3c08 as described in comment 4, but keeps the documentation clarifications.

Generated with 'git format-patch', should be ready for 'git am'. Also available here:
https://github.com/SimonSapin/pycairo/commit/c682c4df83a29bd9c2fd7f7c92c19f1c42de8b61
Comment 6 Christoph Reiter 2017-07-05 15:24:38 UTC
The mentioned commit was never part of pycairo 1.11, which is based on the Python 2 version, so closing.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.