← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:bug-addattachment-bytes into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:bug-addattachment-bytes into launchpad:master.

Commit message:
Make Bug.addAttachment take data as bytes

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/379648

More explicit, and needed for Python 3.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:bug-addattachment-bytes into launchpad:master.
diff --git a/lib/lp/bugs/browser/bugmessage.py b/lib/lp/bugs/browser/bugmessage.py
index e8128cf..7bf7612 100644
--- a/lib/lp/bugs/browser/bugmessage.py
+++ b/lib/lp/bugs/browser/bugmessage.py
@@ -8,7 +8,7 @@ __all__ = [
     'BugMessageAddFormView',
     ]
 
-from StringIO import StringIO
+from io import BytesIO
 
 from zope.component import getUtility
 from zope.formlib.widget import CustomWidgetFactory
@@ -125,7 +125,7 @@ class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck):
             else:
                 is_patch = data['patch']
             attachment = bug.addAttachment(
-                owner=self.user, data=StringIO(data['filecontent']),
+                owner=self.user, data=BytesIO(data['filecontent']),
                 filename=filename, description=file_description,
                 comment=message, is_patch=is_patch)
 
diff --git a/lib/lp/bugs/browser/bugtarget.py b/lib/lp/bugs/browser/bugtarget.py
index 13e45a1..5755c1b 100644
--- a/lib/lp/bugs/browser/bugtarget.py
+++ b/lib/lp/bugs/browser/bugtarget.py
@@ -19,9 +19,9 @@ __all__ = [
     "product_to_productbugconfiguration",
     ]
 
-from cStringIO import StringIO
 from datetime import datetime
 from functools import partial
+from io import BytesIO
 from operator import itemgetter
 
 from lazr.restful.interface import copy_field
@@ -617,7 +617,7 @@ class FileBugViewBase(LaunchpadFormView):
                     file_description = filename
 
                 bug.addAttachment(
-                    owner=self.user, data=StringIO(data['filecontent']),
+                    owner=self.user, data=BytesIO(data['filecontent']),
                     filename=filename, description=file_description,
                     comment=attachment_comment, is_patch=data['patch'])
 
diff --git a/lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py b/lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py
index 8c90b34..2710d05 100644
--- a/lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py
+++ b/lib/lp/bugs/browser/tests/test_bugattachment_edit_view.py
@@ -33,7 +33,7 @@ class TestBugAttachmentEditView(TestCaseWithFactory):
         login_person(self.bug_owner)
         self.bug = self.factory.makeBug(owner=self.bug_owner)
         self.bugattachment = self.factory.makeBugAttachment(
-            bug=self.bug, filename='foo.diff', data='file content',
+            bug=self.bug, filename='foo.diff', data=b'file content',
             description='attachment description', content_type='text/plain',
             is_patch=False)
         # The Librarian server should know about the new file before
diff --git a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
index 30e6cca..2f68b38 100644
--- a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
+++ b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
@@ -53,7 +53,7 @@ class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
         login_person(self.bug_owner)
         self.bug = self.factory.makeBug(owner=self.bug_owner)
         self.bugattachment = self.factory.makeBugAttachment(
-            bug=self.bug, filename='foo.txt', data='file content')
+            bug=self.bug, filename='foo.txt', data=b'file content')
 
     def test_traversal_to_lfa_of_bug_attachment(self):
         # Traversing to the URL provided by a ProxiedLibraryFileAlias of a
@@ -132,7 +132,7 @@ class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
         login_person(self.bug_owner)
         self.bug = self.factory.makeBug(owner=self.bug_owner)
         self.factory.makeBugAttachment(
-            bug=self.bug, filename='foo.txt', data='file content')
+            bug=self.bug, filename='foo.txt', data=b'file content')
         self.bug_url = api_url(self.bug)
 
     def test_anon_access_to_public_bug_attachment(self):
diff --git a/lib/lp/bugs/doc/bug-export.txt b/lib/lp/bugs/doc/bug-export.txt
index ecb3076..3df8a3b 100644
--- a/lib/lp/bugs/doc/bug-export.txt
+++ b/lib/lp/bugs/doc/bug-export.txt
@@ -131,11 +131,13 @@ Attachments are included in the XML dump.  First add an attachment to
 bug #1.  We need to commit here so that the librarian can later serve
 the file when we later serialise the bug:
 
+    >>> from io import BytesIO
+
     >>> login('test@xxxxxxxxxxxxx')
     >>> bug4 = getUtility(IBugSet).get(4)
     >>> sampleperson = getUtility(IPersonSet).getByEmail('test@xxxxxxxxxxxxx')
     >>> bug4.addAttachment(
-    ...     sampleperson, StringIO('Hello World'), 'Added attachment',
+    ...     sampleperson, BytesIO(b'Hello World'), 'Added attachment',
     ...     'hello.txt', description='"Hello World" attachment')
     <BugAttachment ...>
 
diff --git a/lib/lp/bugs/doc/bugattachments.txt b/lib/lp/bugs/doc/bugattachments.txt
index bbd23bd..0bac3b7 100644
--- a/lib/lp/bugs/doc/bugattachments.txt
+++ b/lib/lp/bugs/doc/bugattachments.txt
@@ -66,7 +66,7 @@ ObjectCreatedEvent in order to trigger email notifications:
     'Unspecified'
 
 IBug.addAttachment's comment parameter can also be a string. The data
-passed in is often a file-like object, but can be a string too.
+passed in is often a file-like object, but can be bytes too.
 
     >>> data = filecontent
     >>> attachment_from_strings = bug_four.addAttachment(
diff --git a/lib/lp/bugs/doc/bugcomment.txt b/lib/lp/bugs/doc/bugcomment.txt
index 8aa5ae0..f98907b 100644
--- a/lib/lp/bugs/doc/bugcomment.txt
+++ b/lib/lp/bugs/doc/bugcomment.txt
@@ -64,10 +64,10 @@ first comment.
 If we add an attachment to the first comment, this comment is included
 in activity_and_comments...
 
-    >>> import StringIO
+    >>> from io import BytesIO
     >>> login("test@xxxxxxxxxxxxx")
     >>> attachment = bug_11.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug_11.initial_message, filename='test.txt',
     ...     is_patch=False, content_type='text/plain',
     ...     description='sample data')
@@ -194,7 +194,6 @@ attachments to a bug to see this in action:
 
     >>> from lp.services.webapp.interfaces import ILaunchBag
     >>> from lp.registry.interfaces.person import IPersonSet
-    >>> import StringIO
     >>> user = getUtility(ILaunchBag).user
     >>> different_user = getUtility(IPersonSet).getByName('name16')
 
@@ -208,7 +207,7 @@ attachments to a bug to see this in action:
     ...     owner=user, subject="Ho", content="Hello there")
     >>> m4 = bug_three.newMessage(
     ...     owner=user, subject="Ho", content="Hello there")
-    >>> file_ = StringIO.StringIO("Bogus content makes the world go round")
+    >>> file_ = BytesIO(b"Bogus content makes the world go round")
     >>> a1 = bug_three.addAttachment(
     ...     owner=user, data=file_, description="Ho",
     ...     filename="munchy", comment="Hello there")
@@ -386,22 +385,22 @@ not included in BugComment.patches.
     >>> bug_task = factory.makeBugTask(owner=user)
     >>> bug = bug_task.bug
     >>> attachment_1 = bug.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug.initial_message, filename='file1',
     ...     is_patch=False, content_type='text/plain',
     ...     description='sample data 1')
     >>> attachment_2 = bug.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug.initial_message, filename='file2',
     ...     is_patch=False, content_type='text/plain',
     ...     description='sample data 2')
     >>> patch_1 = bug.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug.initial_message, filename='patch1',
     ...     is_patch=True, content_type='text/plain',
     ...     description='patch 1')
     >>> patch_2 = bug.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug.initial_message, filename='patch2',
     ...     is_patch=True, content_type='text/plain',
     ...     description='patch 2')
diff --git a/lib/lp/bugs/interfaces/bug.py b/lib/lp/bugs/interfaces/bug.py
index 2d9aa49..79397ef 100644
--- a/lib/lp/bugs/interfaces/bug.py
+++ b/lib/lp/bugs/interfaces/bug.py
@@ -698,7 +698,7 @@ class IBugAppend(Interface):
         """Attach a file to this bug.
 
         :owner: An IPerson.
-        :data: A file-like object, or a `str`.
+        :data: A file-like object, or a `bytes`.
         :description: A brief description of the attachment.
         :comment: An IMessage or string.
         :filename: A string.
diff --git a/lib/lp/bugs/model/bug.py b/lib/lp/bugs/model/bug.py
index 4339892..be4bb07 100644
--- a/lib/lp/bugs/model/bug.py
+++ b/lib/lp/bugs/model/bug.py
@@ -19,9 +19,9 @@ __all__ = [
     ]
 
 
-from cStringIO import StringIO
 from email.utils import make_msgid
 from functools import wraps
+from io import BytesIO
 from itertools import chain
 import operator
 import re
@@ -1299,7 +1299,7 @@ class Bug(SQLBase, InformationTypeMixin):
         # wrongly encoded.
         if from_api:
             data = get_raw_form_value_from_current_request(data, 'data')
-        if isinstance(data, str):
+        if isinstance(data, bytes):
             filecontent = data
         else:
             filecontent = data.read()
@@ -1313,7 +1313,7 @@ class Bug(SQLBase, InformationTypeMixin):
 
         filealias = getUtility(ILibraryFileAliasSet).create(
             name=filename, size=len(filecontent),
-            file=StringIO(filecontent), contentType=content_type,
+            file=BytesIO(filecontent), contentType=content_type,
             restricted=self.private)
 
         return self.linkAttachment(
diff --git a/lib/lp/bugs/model/tests/test_bugtasksearch.py b/lib/lp/bugs/model/tests/test_bugtasksearch.py
index fddce7f..60524b8 100644
--- a/lib/lp/bugs/model/tests/test_bugtasksearch.py
+++ b/lib/lp/bugs/model/tests/test_bugtasksearch.py
@@ -280,10 +280,10 @@ class OnceTests:
         # a given type.
         with person_logged_in(self.owner):
             self.bugtasks[0].bug.addAttachment(
-                owner=self.owner, data='filedata', comment='a comment',
+                owner=self.owner, data=b'filedata', comment='a comment',
                 filename='file1.txt', is_patch=False)
             self.bugtasks[1].bug.addAttachment(
-                owner=self.owner, data='filedata', comment='a comment',
+                owner=self.owner, data=b'filedata', comment='a comment',
                 filename='file1.txt', is_patch=True)
         # We can search for bugs with non-patch attachments...
         params = self.getBugTaskSearchParams(
diff --git a/lib/lp/bugs/scripts/tests/test_bugnotification.py b/lib/lp/bugs/scripts/tests/test_bugnotification.py
index 41b7ba4..1b3f4cb 100644
--- a/lib/lp/bugs/scripts/tests/test_bugnotification.py
+++ b/lib/lp/bugs/scripts/tests/test_bugnotification.py
@@ -958,7 +958,7 @@ class TestEmailNotificationsAttachments(
             # another five minutes.  Therefore, we send out separate
             # change notifications.
             return self.bug.addAttachment(
-                self.person, 'content', 'a comment', 'stuff.txt')
+                self.person, b'content', 'a comment', 'stuff.txt')
 
     old = cachedproperty('old')(_attachment)
     new = cachedproperty('new')(_attachment)
diff --git a/lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt b/lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
index eaf7ac8..2bf6a34 100644
--- a/lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
+++ b/lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
@@ -20,7 +20,7 @@ of bug #11 is:
 
 If we add an attachment to the bug report...
 
-    >>> import StringIO
+    >>> from io import BytesIO
     >>> from zope.component import getUtility
     >>> from lp.testing import login, logout
     >>> from lp.services.webapp.interfaces import ILaunchBag
@@ -30,7 +30,7 @@ If we add an attachment to the bug report...
     >>> launchbag = getUtility(ILaunchBag)
     >>> launchbag.clear()
     >>> attachment = bug_11.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('whatever'),
+    ...     owner=None, data=BytesIO(b'whatever'),
     ...     comment=bug_11.initial_message, filename='test.txt',
     ...     is_patch=False, content_type='text/plain',
     ...     description='sample data')
@@ -64,7 +64,7 @@ Patch attachments are shown before non-patch attachments
     >>> login("test@xxxxxxxxxxxxx")
     >>> launchbag.clear()
     >>> patch_attachment = bug_11.addAttachment(
-    ...     owner=None, data=StringIO.StringIO('patchy patch patch'),
+    ...     owner=None, data=BytesIO(b'patchy patch patch'),
     ...     comment=bug_11.initial_message, filename='patch.txt',
     ...     is_patch=True, content_type='text/plain',
     ...     description='a patch')
diff --git a/lib/lp/bugs/stories/bugs/xx-bug-text-pages.txt b/lib/lp/bugs/stories/bugs/xx-bug-text-pages.txt
index 3f418d1..5a53e48 100644
--- a/lib/lp/bugs/stories/bugs/xx-bug-text-pages.txt
+++ b/lib/lp/bugs/stories/bugs/xx-bug-text-pages.txt
@@ -7,7 +7,7 @@ To demonstrate this feature, we'll use bug 1.
 
 We'll start by adding some attachments to the bug:
 
-    >>> import StringIO
+    >>> from io import BytesIO
     >>> from lp.services.database.sqlbase import flush_database_updates
     >>> from lp.testing import login, logout
     >>> from lp.bugs.model.bug import Bug
@@ -16,14 +16,14 @@ We'll start by adding some attachments to the bug:
     >>> mark = Person.selectOneBy(name="mark")
     >>> mark.display_name = u"M\xe1rk Sh\xfattlew\xf2rth"
     >>> bug = Bug.get(1)
-    >>> content = StringIO.StringIO("<html><body>bogus</body></html>")
+    >>> content = BytesIO(b"<html><body>bogus</body></html>")
     >>> a1 = bug.addAttachment(mark, content, "comment for file a",
     ...     "file_a.txt", content_type="text/html")
-    >>> content = StringIO.StringIO("do we need to")
+    >>> content = BytesIO(b"do we need to")
     >>> a2 = bug.addAttachment(mark, content, "comment for file with space",
     ...     "file with space.txt",
     ...     content_type='text/plain;\n  name="file with space.txt"')
-    >>> content = StringIO.StringIO("Yes we can!")
+    >>> content = BytesIO(b"Yes we can!")
     >>> a3 = bug.addAttachment(mark, content, "comment for patch",
     ...      "bug-patch.diff", is_patch=True, content_type='text/plain',
     ...      description="a patch")
diff --git a/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt b/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
index 38dacdb..7300d25 100644
--- a/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
+++ b/lib/lp/bugs/stories/bugtask-searches/xx-listing-basics.txt
@@ -232,9 +232,9 @@ relevant listings:
 
 Patches also appear as badges in bug listings.
 
+    >>> from io import BytesIO
     >>> from lp.testing import login, logout
     >>> from zope.component import getUtility
-    >>> from StringIO import StringIO
     >>> from lp.services.messages.interfaces.message import IMessageSet
     >>> from lp.bugs.interfaces.bug import IBugSet
     >>> from lp.registry.interfaces.person import IPersonSet
@@ -249,7 +249,7 @@ Patches also appear as badges in bug listings.
     >>> bug_one = bugset.get(1)
     >>> bug_one.addAttachment(
     ...     owner=foobar,
-    ...     data=StringIO("file data"),
+    ...     data=BytesIO(b"file data"),
     ...     filename="foo.bar",
     ...     description="this fixes the bug",
     ...     comment=message,
diff --git a/lib/lp/bugs/stories/webservice/xx-bug.txt b/lib/lp/bugs/stories/webservice/xx-bug.txt
index 9154525..119fb43 100644
--- a/lib/lp/bugs/stories/webservice/xx-bug.txt
+++ b/lib/lp/bugs/stories/webservice/xx-bug.txt
@@ -1244,7 +1244,7 @@ An attachment can be added to the bug:
 
     >>> response = webservice.named_post(
     ...     bug_one['self_link'], 'addAttachment',
-    ...     data="12345", filename="numbers.txt", content_type='foo/bar',
+    ...     data=b"12345", filename="numbers.txt", content_type='foo/bar',
     ...     comment="The numbers you asked for.")
     >>> print(response)
     HTTP/1.1 201 Created...
diff --git a/lib/lp/bugs/tests/test_bugs_webservice.py b/lib/lp/bugs/tests/test_bugs_webservice.py
index b795785..2de90c3 100644
--- a/lib/lp/bugs/tests/test_bugs_webservice.py
+++ b/lib/lp/bugs/tests/test_bugs_webservice.py
@@ -392,7 +392,7 @@ class TestErrorHandling(TestCaseWithFactory):
         launchpad = launchpadlib_for('test', owner)
         lp_bug = launchpad.load(api_url(bug))
         self.assertRaises(
-            BadRequest, lp_bug.addAttachment, comment='foo', data='foo',
+            BadRequest, lp_bug.addAttachment, comment='foo', data=b'foo',
             filename='/home/foo/bar.txt')
 
 
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index bac9b17..ed3e163 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -485,6 +485,10 @@ class ObjectFactory:
         string = "%s-%s" % (prefix, self.getUniqueInteger())
         return string
 
+    # XXX cjwatson 2020-02-20: We should disentangle this; most uses of
+    # getUniqueString should probably use getUniqueUnicode instead.
+    getUniqueBytes = getUniqueString
+
     def getUniqueUnicode(self, prefix=None):
         return self.getUniqueString(prefix=prefix).decode('latin-1')
 
@@ -2157,7 +2161,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
         if owner is None:
             owner = self.makePerson()
         if data is None:
-            data = self.getUniqueString()
+            data = self.getUniqueBytes()
         if description is None:
             description = self.getUniqueString()
         if comment is None: