Merge lp:~vila/bzr/106898-put-bytes-raises-TypeError into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 6613
Proposed branch: lp:~vila/bzr/106898-put-bytes-raises-TypeError
Merge into: lp:bzr
Diff against target: 289 lines (+51/-73)
9 files modified
bzrlib/tests/per_transport.py (+2/-23)
bzrlib/tests/test_smart_transport.py (+1/-1)
bzrlib/transport/__init__.py (+14/-14)
bzrlib/transport/local.py (+7/-5)
bzrlib/transport/memory.py (+4/-10)
bzrlib/transport/readonly.py (+1/-1)
bzrlib/transport/remote.py (+11/-16)
bzrlib/transport/sftp.py (+7/-3)
doc/en/release-notes/bzr-2.7.txt (+4/-0)
To merge this branch: bzr merge lp:~vila/bzr/106898-put-bytes-raises-TypeError
Reviewer Review Type Date Requested Status
Richard Wilbur Needs Fixing
Review via email: mp+283522@code.launchpad.net

Commit message

Make all transport put_bytes() raises TypeError when given unicode strings rather than bytes

Description of the change

While looking at https://bugs.launchpad.net/bzr/+bug/1524066 (which is a paramiko issue starting with version 1.16), some other failures were mentioned for per_transport.TransportTests.test_put_file_unicode.

These failures also occur on ubuntu wily and xenial.

From there I came across https://bugs.launchpad.net/bzr/+bug/106898 which was indeed proposing the right fix for test_put_bytes_unicode() and implementing it, I realize that test_put_file_unicode() was just bogus: it's the only place in the code base that try to force a file content to be unicode when we take care everywhere else to treat a file content as bytes.

To post a comment you must log in.
Revision history for this message
Richard Wilbur (richard-wilbur) wrote :

Looks like a valuable realignment of the code to be more consistent. This should also serve us well as we prepare to take on python 3 bytes.

I've suggested a few places where it looks like the parameter documentation could be updated to reflect the changes.

Other than that, +2.

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :

Good catches ! Thanks ! Fixed.

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Revision history for this message
Vincent Ladeuil (vila) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/tests/per_transport.py'
--- bzrlib/tests/per_transport.py 2015-10-23 09:05:09 +0000
+++ bzrlib/tests/per_transport.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2011, 2015 Canonical Ltd1# Copyright (C) 2005-2011, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -520,32 +520,11 @@
520 self.assertTransportMode(t, 'dir777', 0777)520 self.assertTransportMode(t, 'dir777', 0777)
521521
522 def test_put_bytes_unicode(self):522 def test_put_bytes_unicode(self):
523 # Expect put_bytes to raise AssertionError or UnicodeEncodeError if
524 # given unicode "bytes". UnicodeEncodeError doesn't really make sense
525 # (we don't want to encode unicode here at all, callers should be
526 # strictly passing bytes to put_bytes), but we allow it for backwards
527 # compatibility. At some point we should use a specific exception.
528 # See https://bugs.launchpad.net/bzr/+bug/106898.
529 t = self.get_transport()523 t = self.get_transport()
530 if t.is_readonly():524 if t.is_readonly():
531 return525 return
532 unicode_string = u'\u1234'526 unicode_string = u'\u1234'
533 self.assertRaises(527 self.assertRaises(TypeError, t.put_bytes, 'foo', unicode_string)
534 (AssertionError, UnicodeEncodeError),
535 t.put_bytes, 'foo', unicode_string)
536
537 def test_put_file_unicode(self):
538 # Like put_bytes, except with a StringIO.StringIO of a unicode string.
539 # This situation can happen (and has) if code is careless about the type
540 # of "string" they initialise/write to a StringIO with. We cannot use
541 # cStringIO, because it never returns unicode from read.
542 # Like put_bytes, UnicodeEncodeError isn't quite the right exception to
543 # raise, but we raise it for hysterical raisins.
544 t = self.get_transport()
545 if t.is_readonly():
546 return
547 unicode_file = pyStringIO(u'\u1234')
548 self.assertRaises(UnicodeEncodeError, t.put_file, 'foo', unicode_file)
549528
550 def test_mkdir(self):529 def test_mkdir(self):
551 t = self.get_transport()530 t = self.get_transport()
552531
=== modified file 'bzrlib/tests/test_smart_transport.py'
--- bzrlib/tests/test_smart_transport.py 2015-10-23 09:05:09 +0000
+++ bzrlib/tests/test_smart_transport.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006-2015 Canonical Ltd1# Copyright (C) 2006-2014, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
55
=== modified file 'bzrlib/transport/__init__.py'
--- bzrlib/transport/__init__.py 2012-03-13 17:25:29 +0000
+++ bzrlib/transport/__init__.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2011 Canonical Ltd1# Copyright (C) 2005-2012, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -873,21 +873,21 @@
873 yield self.get(relpath)873 yield self.get(relpath)
874 count += 1874 count += 1
875875
876 def put_bytes(self, relpath, bytes, mode=None):876 def put_bytes(self, relpath, raw_bytes, mode=None):
877 """Atomically put the supplied bytes into the given location.877 """Atomically put the supplied bytes into the given location.
878878
879 :param relpath: The location to put the contents, relative to the879 :param relpath: The location to put the contents, relative to the
880 transport base.880 transport base.
881 :param bytes: A bytestring of data.881 :param raw_bytes: A bytestring of data.
882 :param mode: Create the file with the given mode.882 :param mode: Create the file with the given mode.
883 :return: None883 :return: None
884 """884 """
885 if not isinstance(bytes, str):885 if not isinstance(raw_bytes, str):
886 raise AssertionError(886 raise TypeError(
887 'bytes must be a plain string, not %s' % type(bytes))887 'raw_bytes must be a plain string, not %s' % type(raw_bytes))
888 return self.put_file(relpath, StringIO(bytes), mode=mode)888 return self.put_file(relpath, StringIO(raw_bytes), mode=mode)
889889
890 def put_bytes_non_atomic(self, relpath, bytes, mode=None,890 def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
891 create_parent_dir=False,891 create_parent_dir=False,
892 dir_mode=None):892 dir_mode=None):
893 """Copy the string into the target location.893 """Copy the string into the target location.
@@ -896,8 +896,8 @@
896 Transport.put_bytes_non_atomic for more information.896 Transport.put_bytes_non_atomic for more information.
897897
898 :param relpath: The remote location to put the contents.898 :param relpath: The remote location to put the contents.
899 :param bytes: A string object containing the raw bytes to write into899 :param raw_bytes: A string object containing the raw bytes to write
900 the target file.900 into the target file.
901 :param mode: Possible access permissions for new file.901 :param mode: Possible access permissions for new file.
902 None means do not set remote permissions.902 None means do not set remote permissions.
903 :param create_parent_dir: If we cannot create the target file because903 :param create_parent_dir: If we cannot create the target file because
@@ -905,10 +905,10 @@
905 create it, and then try again.905 create it, and then try again.
906 :param dir_mode: Possible access permissions for new directories.906 :param dir_mode: Possible access permissions for new directories.
907 """907 """
908 if not isinstance(bytes, str):908 if not isinstance(raw_bytes, str):
909 raise AssertionError(909 raise TypeError(
910 'bytes must be a plain string, not %s' % type(bytes))910 'raw_bytes must be a plain string, not %s' % type(raw_bytes))
911 self.put_file_non_atomic(relpath, StringIO(bytes), mode=mode,911 self.put_file_non_atomic(relpath, StringIO(raw_bytes), mode=mode,
912 create_parent_dir=create_parent_dir,912 create_parent_dir=create_parent_dir,
913 dir_mode=dir_mode)913 dir_mode=dir_mode)
914914
915915
=== modified file 'bzrlib/transport/local.py'
--- bzrlib/transport/local.py 2012-03-13 17:25:29 +0000
+++ bzrlib/transport/local.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2011 Canonical Ltd1# Copyright (C) 2005-2012, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -184,13 +184,15 @@
184 fp.close()184 fp.close()
185 return length185 return length
186186
187 def put_bytes(self, relpath, bytes, mode=None):187 def put_bytes(self, relpath, raw_bytes, mode=None):
188 """Copy the string into the location.188 """Copy the string into the location.
189189
190 :param relpath: Location to put the contents, relative to base.190 :param relpath: Location to put the contents, relative to base.
191 :param bytes: String191 :param raw_bytes: String
192 """192 """
193193 if not isinstance(raw_bytes, str):
194 raise TypeError(
195 'raw_bytes must be a plain string, not %s' % type(raw_bytes))
194 path = relpath196 path = relpath
195 try:197 try:
196 path = self._abspath(relpath)198 path = self._abspath(relpath)
@@ -200,7 +202,7 @@
200 self._translate_error(e, path)202 self._translate_error(e, path)
201 try:203 try:
202 if bytes:204 if bytes:
203 fp.write(bytes)205 fp.write(raw_bytes)
204 fp.commit()206 fp.commit()
205 finally:207 finally:
206 fp.close()208 fp.close()
207209
=== modified file 'bzrlib/transport/memory.py'
--- bzrlib/transport/memory.py 2011-12-19 13:23:58 +0000
+++ bzrlib/transport/memory.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2010 Canonical Ltd1# Copyright (C) 2005-2011, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -148,15 +148,9 @@
148 """See Transport.put_file()."""148 """See Transport.put_file()."""
149 _abspath = self._abspath(relpath)149 _abspath = self._abspath(relpath)
150 self._check_parent(_abspath)150 self._check_parent(_abspath)
151 bytes = f.read()151 raw_bytes = f.read()
152 if type(bytes) is not str:152 self._files[_abspath] = (raw_bytes, mode)
153 # Although not strictly correct, we raise UnicodeEncodeError to be153 return len(raw_bytes)
154 # compatible with other transports.
155 raise UnicodeEncodeError(
156 'undefined', bytes, 0, 1,
157 'put_file must be given a file of bytes, not unicode.')
158 self._files[_abspath] = (bytes, mode)
159 return len(bytes)
160154
161 def mkdir(self, relpath, mode=None):155 def mkdir(self, relpath, mode=None):
162 """See Transport.mkdir()."""156 """See Transport.mkdir()."""
163157
=== modified file 'bzrlib/transport/readonly.py'
--- bzrlib/transport/readonly.py 2015-10-23 09:05:09 +0000
+++ bzrlib/transport/readonly.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006, 2007, 2009, 2010, 2011, 2015 Canonical Ltd1# Copyright (C) 2006, 2007, 2009, 2010, 2011, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
55
=== modified file 'bzrlib/transport/remote.py'
--- bzrlib/transport/remote.py 2012-03-13 17:25:29 +0000
+++ bzrlib/transport/remote.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006-2010 Canonical Ltd1# Copyright (C) 2006-2012, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -247,23 +247,18 @@
247 transport._file_streams[self.abspath(relpath)] = result247 transport._file_streams[self.abspath(relpath)] = result
248 return result248 return result
249249
250 def put_bytes(self, relpath, upload_contents, mode=None):250 def put_bytes(self, relpath, raw_bytes, mode=None):
251 # FIXME: upload_file is probably not safe for non-ascii characters -251 if not isinstance(raw_bytes, str):
252 # should probably just pass all parameters as length-delimited252 raise TypeError(
253 # strings?253 'raw_bytes must be a plain string, not %s' % type(raw_bytes))
254 if type(upload_contents) is unicode:254 resp = self._call_with_body_bytes(
255 # Although not strictly correct, we raise UnicodeEncodeError to be255 'put',
256 # compatible with other transports.
257 raise UnicodeEncodeError(
258 'undefined', upload_contents, 0, 1,
259 'put_bytes must be given bytes, not unicode.')
260 resp = self._call_with_body_bytes('put',
261 (self._remote_path(relpath), self._serialise_optional_mode(mode)),256 (self._remote_path(relpath), self._serialise_optional_mode(mode)),
262 upload_contents)257 raw_bytes)
263 self._ensure_ok(resp)258 self._ensure_ok(resp)
264 return len(upload_contents)259 return len(raw_bytes)
265260
266 def put_bytes_non_atomic(self, relpath, bytes, mode=None,261 def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
267 create_parent_dir=False,262 create_parent_dir=False,
268 dir_mode=None):263 dir_mode=None):
269 """See Transport.put_bytes_non_atomic."""264 """See Transport.put_bytes_non_atomic."""
@@ -276,7 +271,7 @@
276 'put_non_atomic',271 'put_non_atomic',
277 (self._remote_path(relpath), self._serialise_optional_mode(mode),272 (self._remote_path(relpath), self._serialise_optional_mode(mode),
278 create_parent_str, self._serialise_optional_mode(dir_mode)),273 create_parent_str, self._serialise_optional_mode(dir_mode)),
279 bytes)274 raw_bytes)
280 self._ensure_ok(resp)275 self._ensure_ok(resp)
281276
282 def put_file(self, relpath, upload_file, mode=None):277 def put_file(self, relpath, upload_file, mode=None):
283278
=== modified file 'bzrlib/transport/sftp.py'
--- bzrlib/transport/sftp.py 2011-12-19 13:23:58 +0000
+++ bzrlib/transport/sftp.py 2016-01-22 08:02:39 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2005-2010 Canonical Ltd1# Copyright (C) 2005-2011, 2016 Canonical Ltd
2#2#
3# This program is free software; you can redistribute it and/or modify3# This program is free software; you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by4# it under the terms of the GNU General Public License as published by
@@ -593,11 +593,15 @@
593 create_parent_dir=create_parent_dir,593 create_parent_dir=create_parent_dir,
594 dir_mode=dir_mode)594 dir_mode=dir_mode)
595595
596 def put_bytes_non_atomic(self, relpath, bytes, mode=None,596 def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
597 create_parent_dir=False,597 create_parent_dir=False,
598 dir_mode=None):598 dir_mode=None):
599 if not isinstance(raw_bytes, str):
600 raise TypeError(
601 'raw_bytes must be a plain string, not %s' % type(raw_bytes))
602
599 def writer(fout):603 def writer(fout):
600 fout.write(bytes)604 fout.write(raw_bytes)
601 self._put_non_atomic_helper(relpath, writer, mode=mode,605 self._put_non_atomic_helper(relpath, writer, mode=mode,
602 create_parent_dir=create_parent_dir,606 create_parent_dir=create_parent_dir,
603 dir_mode=dir_mode)607 dir_mode=dir_mode)
604608
=== modified file 'doc/en/release-notes/bzr-2.7.txt'
--- doc/en/release-notes/bzr-2.7.txt 2016-01-21 21:44:58 +0000
+++ doc/en/release-notes/bzr-2.7.txt 2016-01-22 08:02:39 +0000
@@ -66,6 +66,10 @@
66.. Major internal changes, unlikely to be visible to users or plugin 66.. Major internal changes, unlikely to be visible to users or plugin
67 developers, but interesting for bzr developers.67 developers, but interesting for bzr developers.
6868
69* Make all transport put_bytes() raises TypeError instead of AssertionError
70 or UnicodeEncodeError when given unicode strings rather than bytes.
71 (Vincent Ladeuil, #106898)
72
69Changed Behaviour73Changed Behaviour
70*****************74*****************
7175