← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:py3-pretty into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:py3-pretty into launchpad:master.

Commit message:
Adjust "pretty" doctest helper to use Python 3 text representation

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

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

Unfortunately we have to take some minor countermeasures against pprint.PrettyPrinter wrapping long strings in Python >= 3.5.  At some point it may become necessary to implement our own pretty-printer, but we aren't there yet.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:py3-pretty into launchpad:master.
diff --git a/lib/lp/bugs/browser/tests/person-bug-views.txt b/lib/lp/bugs/browser/tests/person-bug-views.txt
index 6a047b0..d4cd88a 100644
--- a/lib/lp/bugs/browser/tests/person-bug-views.txt
+++ b/lib/lp/bugs/browser/tests/person-bug-views.txt
@@ -411,7 +411,7 @@ search page allow selection of a milestone.
 
     >>> print(pretty(related_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 0.9',
+      'title': 'Coughing Bob 0.9',
       'value': ...}]
 
 
@@ -428,7 +428,7 @@ will already appear.
 
     >>> print(pretty(reported_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 0.9',
+      'title': 'Coughing Bob 0.9',
       'value': ...}]
 
 Filing a new bug and assigning a new milestone will make the new
@@ -440,10 +440,10 @@ milestone appear amongst the possible options.
 
     >>> print(pretty(reported_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 1.0',
+      'title': 'Coughing Bob 1.0',
       'value': ...},
      {'checked': False,
-      'title': u'Coughing Bob 0.9',
+      'title': 'Coughing Bob 0.9',
       'value': ...}]
 
 
@@ -465,7 +465,7 @@ Once a bug has been assigned, the milestone appears.
 
     >>> print(pretty(assigned_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 1.0',
+      'title': 'Coughing Bob 1.0',
       'value': ...}]
 
 
@@ -488,7 +488,7 @@ Once the user has commented, the related milestone does appear.
 
     >>> print(pretty(commented_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 1.0',
+      'title': 'Coughing Bob 1.0',
       'value': ...}]
 
 
@@ -512,5 +512,5 @@ Once new_user has subscribed, the related milestones appear.
 
     >>> print(pretty(subscribed_bugs_view.getMilestoneWidgetValues()))
     [{'checked': False,
-      'title': u'Coughing Bob 1.0',
+      'title': 'Coughing Bob 1.0',
       'value': ...}]
diff --git a/lib/lp/bugs/doc/bug-change.txt b/lib/lp/bugs/doc/bug-change.txt
index 881c389..49b15c8 100644
--- a/lib/lp/bugs/doc/bug-change.txt
+++ b/lib/lp/bugs/doc/bug-change.txt
@@ -239,9 +239,9 @@ method.
 
     >>> activity_data = simple_change.getBugActivity()
     >>> print(pretty(activity_data))
-    {'newvalue': u'Spam',
-     'oldvalue': u'Reality is on the blink again',
-     'whatchanged': u'title'}
+    {'newvalue': 'Spam',
+     'oldvalue': 'Reality is on the blink again',
+     'whatchanged': 'title'}
 
 
 === BugDescriptionChange ===
@@ -334,9 +334,9 @@ other attribute change. The list of tags is converted to a
 space-separated string for display.
 
     >>> print(pretty(tags_change.getBugActivity()))
-    {'newvalue': u'second-tag third-tag zillionth-tag',
-     'oldvalue': u'first-tag second-tag third-tag',
-     'whatchanged': u'tags'}
+    {'newvalue': 'second-tag third-tag zillionth-tag',
+     'oldvalue': 'first-tag second-tag third-tag',
+     'whatchanged': 'tags'}
 
 Addtions and removals are expressed separately in the notification.
 
@@ -362,7 +362,7 @@ when a CVE is linked to a bug.
     ...     when=nowish, person=example_person, cve=cve)
 
     >>> print(pretty(bug_cve_linked.getBugActivity()))
-    {'newvalue': u'1999-8979',
+    {'newvalue': '1999-8979',
      'whatchanged': 'cve linked'}
 
     >>> print(bug_cve_linked.getBugNotification()['text'])
@@ -374,7 +374,7 @@ And when a CVE is unlinked from a bug.
     ...     when=nowish, person=example_person, cve=cve)
 
     >>> print(pretty(bug_cve_unlinked.getBugActivity()))
-    {'oldvalue': u'1999-8979',
+    {'oldvalue': '1999-8979',
      'whatchanged': 'cve unlinked'}
 
     >>> print(bug_cve_unlinked.getBugNotification()['text'])
@@ -400,7 +400,7 @@ You can add an attachment...
 
     >>> print(pretty(attachment_change.getBugActivity()))
     {'newvalue':
-         u'sample-attachment http://bugs.launchpad.test/bugs/...+files/...',
+         'sample-attachment http://bugs.launchpad.test/bugs/...+files/...',
      'oldvalue': None,
      'whatchanged': 'attachment added'}
 
@@ -418,7 +418,7 @@ Or remove one.
     >>> print(pretty(attachment_change.getBugActivity()))
     {'newvalue': None,
      'oldvalue':
-         u'sample-attachment http://bugs.launchpad.test/bugs/...+files/...',
+         'sample-attachment http://bugs.launchpad.test/bugs/...+files/...',
      'whatchanged': 'attachment removed'}
 
     >>> print(attachment_change.getBugNotification()['text'])
@@ -482,7 +482,7 @@ the user what has changed.
     >>> print(pretty(status_change.getBugActivity()))
     {'newvalue': 'Fix Released',
      'oldvalue': 'New',
-     'whatchanged': u'heart-of-gold: status'}
+     'whatchanged': 'heart-of-gold: status'}
 
     >>> notification_text = status_change.getBugNotification()['text']
     >>> print(notification_text) #doctest: -NORMALIZE_WHITESPACE
@@ -506,7 +506,7 @@ describe to the user what has changed.
     >>> print(pretty(importance_change.getBugActivity()))
     {'newvalue': 'Critical',
      'oldvalue': 'Undecided',
-     'whatchanged': u'heart-of-gold: importance'}
+     'whatchanged': 'heart-of-gold: importance'}
 
     >>> notification_text = importance_change.getBugNotification()['text']
     >>> print(notification_text) #doctest: -NORMALIZE_WHITESPACE
@@ -531,9 +531,9 @@ the user what has changed.
     ...     person=example_person, what_changed='milestone',
     ...     old_value=None, new_value=milestone)
     >>> print(pretty(milestone_change.getBugActivity()))
-    {'newvalue': u'example-milestone',
+    {'newvalue': 'example-milestone',
      'oldvalue': None,
-     'whatchanged': u'heart-of-gold: milestone'}
+     'whatchanged': 'heart-of-gold: milestone'}
 
     >>> notification_text = milestone_change.getBugNotification()['text']
     >>> print(notification_text) #doctest: -NORMALIZE_WHITESPACE
@@ -560,9 +560,9 @@ user what has changed.
     ...     person=example_person, what_changed='bugwatch',
     ...     old_value=None, new_value=bug_watch)
     >>> print(pretty(bug_watch_change.getBugActivity()))
-    {'newvalue': u'bugs.example.com/ #1245',
+    {'newvalue': 'bugs.example.com/ #1245',
      'oldvalue': None,
-     'whatchanged': u'heart-of-gold: remote watch'}
+     'whatchanged': 'heart-of-gold: remote watch'}
 
     >>> notification_text = bug_watch_change.getBugNotification()['text']
     >>> print(notification_text) #doctest: -NORMALIZE_WHITESPACE
@@ -583,9 +583,9 @@ describe to the user what has changed.
     ...     person=example_person, what_changed='assignee',
     ...     old_value=None, new_value=example_person)
     >>> print(pretty(assignee_change.getBugActivity()))
-    {'newvalue': u'Ford Prefect (ford-prefect)',
+    {'newvalue': 'Ford Prefect (ford-prefect)',
      'oldvalue': None,
-     'whatchanged': u'heart-of-gold: assignee'}
+     'whatchanged': 'heart-of-gold: assignee'}
 
     >>> notification_text = assignee_change.getBugNotification()['text']
     >>> print(notification_text) #doctest: -NORMALIZE_WHITESPACE
@@ -610,8 +610,8 @@ attribute for the values to use in the activity log.
     ...     old_value=example_bug_task.target,
     ...     new_value=new_target)
     >>> print(pretty(target_change.getBugActivity()))
-    {'newvalue': u'magrathea',
-     'oldvalue': u'heart-of-gold',
+    {'newvalue': 'magrathea',
+     'oldvalue': 'heart-of-gold',
      'whatchanged': 'affects'}
 
     >>> notification_text = target_change.getBugNotification()['text']
diff --git a/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled b/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
index dab711c..4c9922a 100644
--- a/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
+++ b/lib/lp/registry/doc/launchpadlib/project-registry.txt.disabled
@@ -339,36 +339,36 @@ Read-only attributes cannot be changed.
 milestone and release.
 
     >>> print pretty(firefox.get_timeline())
-    [{u'is_development_focus': False,
-      u'landmarks': [{u'code_name': u'First Stable Release',
-                      u'date': u'2004-06-28',
-                      u'name': u'1.0.0',
-                      u'type': u'release',
-                      u'uri': u'/firefox/1.0/1.0.0'}],
-      u'name': u'1.0',
-      u'status': u'Active Development',
-      u'uri': u'/firefox/1.0'},
-     {u'is_development_focus': True,
-      u'landmarks': [{u'code_name': None,
-                      u'date': u'2056-10-16',
-                      u'name': u'1.0',
-                      u'type': u'milestone',
-                      u'uri': u'/firefox/+milestone/1.0'},
-                     {u'code_name': u'One (secure) Tree Hill',
-                      u'date': u'2004-10-15',
-                      u'name': u'0.9.2',
-                      u'type': u'release',
-                      u'uri': u'/firefox/trunk/0.9.2'},
-                     {u'code_name': u'One Tree Hill (v2)',
-                      u'date': u'2004-10-15',
-                      u'name': u'0.9.1',
-                      u'type': u'release',
-                      u'uri': u'/firefox/trunk/0.9.1'},
-                     {u'code_name': u'One Tree Hill',
-                      u'date': u'2004-10-15',
-                      u'name': u'0.9',
-                      u'type': u'release',
-                      u'uri': u'/firefox/trunk/0.9'}],
-      u'name': u'trunk',
-      u'status': u'Active Development',
-      u'uri': u'/firefox/trunk'}]
+    [{'is_development_focus': False,
+      'landmarks': [{'code_name': 'First Stable Release',
+                     'date': '2004-06-28',
+                     'name': '1.0.0',
+                     'type': 'release',
+                     'uri': '/firefox/1.0/1.0.0'}],
+      'name': '1.0',
+      'status': 'Active Development',
+      'uri': '/firefox/1.0'},
+     {'is_development_focus': True,
+      'landmarks': [{'code_name': None,
+                     'date': '2056-10-16',
+                     'name': '1.0',
+                     'type': 'milestone',
+                     'uri': '/firefox/+milestone/1.0'},
+                    {'code_name': 'One (secure) Tree Hill',
+                     'date': '2004-10-15',
+                     'name': '0.9.2',
+                     'type': 'release',
+                     'uri': '/firefox/trunk/0.9.2'},
+                    {'code_name': 'One Tree Hill (v2)',
+                     'date': '2004-10-15',
+                     'name': '0.9.1',
+                     'type': 'release',
+                     'uri': '/firefox/trunk/0.9.1'},
+                    {'code_name': 'One Tree Hill',
+                     'date': '2004-10-15',
+                     'name': '0.9',
+                     'type': 'release',
+                     'uri': '/firefox/trunk/0.9'}],
+      'name': 'trunk',
+      'status': 'Active Development',
+      'uri': '/firefox/trunk'}]
diff --git a/lib/lp/registry/stories/webservice/xx-distribution-source-package.txt b/lib/lp/registry/stories/webservice/xx-distribution-source-package.txt
index 418bed1..9711784 100644
--- a/lib/lp/registry/stories/webservice/xx-distribution-source-package.txt
+++ b/lib/lp/registry/stories/webservice/xx-distribution-source-package.txt
@@ -10,18 +10,18 @@ Source packages can be obtained from the context of a distribution.
     ...     name='mozilla-firefox').jsonBody()
 
     >>> print(pretty(mozilla_firefox))
-    {u'bug_reported_acknowledgement': None,
-     u'bug_reporting_guidelines': None,
-     u'display_name': u'mozilla-firefox in Debian',
-     u'distribution_link': u'http://.../debian',
-     u'http_etag': u'...',
-     u'name': u'mozilla-firefox',
-     u'official_bug_tags': [],
-     u'resource_type_link': u'http://.../#distribution_source_package',
-     u'self_link': u'http://.../debian/+source/mozilla-firefox',
-     u'title': u'mozilla-firefox package in Debian',
-     u'upstream_product_link': None,
-     u'web_link': u'http://launchpad.../debian/+source/mozilla-firefox'}
+    {'bug_reported_acknowledgement': None,
+     'bug_reporting_guidelines': None,
+     'display_name': 'mozilla-firefox in Debian',
+     'distribution_link': 'http://.../debian',
+     'http_etag': '...',
+     'name': 'mozilla-firefox',
+     'official_bug_tags': [],
+     'resource_type_link': 'http://.../#distribution_source_package',
+     'self_link': 'http://.../debian/+source/mozilla-firefox',
+     'title': 'mozilla-firefox package in Debian',
+     'upstream_product_link': None,
+     'web_link': 'http://launchpad.../debian/+source/mozilla-firefox'}
 
 It's also possible to search for tasks with the "searchTasks" method:
 
@@ -53,8 +53,8 @@ package.
 
     >>> print(pretty(upstream_product))
     {...
-     u'display_name': u'Mozilla Firefox'...
-     u'self_link': u'http://.../firefox'...}
+     'display_name': 'Mozilla Firefox'...
+     'self_link': 'http://.../firefox'...}
 
 If the package isn't linked to an upstream product its
 upstream_product_link will be None.
diff --git a/lib/lp/registry/stories/webservice/xx-project-registry.txt b/lib/lp/registry/stories/webservice/xx-project-registry.txt
index 06c53b7..c92b677 100644
--- a/lib/lp/registry/stories/webservice/xx-project-registry.txt
+++ b/lib/lp/registry/stories/webservice/xx-project-registry.txt
@@ -446,61 +446,61 @@ hierarchy of series, milestones, and releases.
     ...     "get_timeline",
     ...     include_inactive=True).jsonBody()
     >>> print(pretty(timeline))
-    {u'entries': [{u'http_etag': ...
-                   u'is_development_focus': True,
-                   u'landmarks': [{u'code_name': None,
-                                   u'date': u'2056-10-16',
-                                   u'name': u'1.0',
-                                   u'type': u'milestone',
-                                   u'uri': u'/firefox/+milestone/1.0'},
-                                  {u'code_name': u'One (secure) Tree Hill',
-                                   u'date': u'2004-10-15',
-                                   u'name': u'0.9.2',
-                                   u'type': u'release',
-                                   u'uri': u'/firefox/trunk/0.9.2'},
-                                  {u'code_name': u'One Tree Hill (v2)',
-                                   u'date': u'2004-10-15',
-                                   u'name': u'0.9.1',
-                                   u'type': u'release',
-                                   u'uri': u'/firefox/trunk/0.9.1'},
-                                  {u'code_name': u'One Tree Hill',
-                                   u'date': u'2004-10-15',
-                                   u'name': u'0.9',
-                                   u'type': u'release',
-                                   u'uri': u'/firefox/trunk/0.9'}],
-                   u'name': u'trunk',
-                   u'project_link': u'http://.../firefox',
-                   u'resource_type_link': u'.../#timeline_project_series',
-                   u'self_link': u'http://.../firefox/trunk',
-                   u'status': u'Obsolete',
-                   u'uri': u'/firefox/trunk',
-                   u'web_link': u'http://launchpad.../firefox/trunk'},
-                  {u'http_etag': ...
-                   u'is_development_focus': False,
-                   u'landmarks': [{u'code_name': u'First Stable Release',
-                                   u'date': u'2004-06-28',
-                                   u'name': u'1.0.0',
-                                   u'type': u'release',
-                                   u'uri': u'/firefox/1.0/1.0.0'}],
-                   u'name': u'1.0',
-                   u'project_link': u'http://.../firefox',
-                   u'resource_type_link': u'.../#timeline_project_series',
-                   u'self_link': u'http://.../firefox/1.0',
-                   u'status': u'Active Development',
-                   u'uri': u'/firefox/1.0',
-                   u'web_link': u'http://launchpad.../firefox/1.0'},
-                  {u'http_etag': ...
-                   u'is_development_focus': False,
-                   u'landmarks': [],
-                   u'name': u'experimental',
-                   u'project_link': u'http://.../firefox',
-                   u'resource_type_link': u'.../#timeline_project_series',
-                   u'self_link': u'http://.../firefox/experimental',
-                   u'status': u'Active Development',
-                   u'uri': u'/firefox/experimental',
-                   u'web_link': u'http://launchpad.../firefox/experimental'}],
-     u'start': 0,
-     u'total_size': 3}
+    {'entries': [{'http_etag': ...
+                  'is_development_focus': True,
+                  'landmarks': [{'code_name': None,
+                                 'date': '2056-10-16',
+                                 'name': '1.0',
+                                 'type': 'milestone',
+                                 'uri': '/firefox/+milestone/1.0'},
+                                {'code_name': 'One (secure) Tree Hill',
+                                 'date': '2004-10-15',
+                                 'name': '0.9.2',
+                                 'type': 'release',
+                                 'uri': '/firefox/trunk/0.9.2'},
+                                {'code_name': 'One Tree Hill (v2)',
+                                 'date': '2004-10-15',
+                                 'name': '0.9.1',
+                                 'type': 'release',
+                                 'uri': '/firefox/trunk/0.9.1'},
+                                {'code_name': 'One Tree Hill',
+                                 'date': '2004-10-15',
+                                 'name': '0.9',
+                                 'type': 'release',
+                                 'uri': '/firefox/trunk/0.9'}],
+                  'name': 'trunk',
+                  'project_link': 'http://.../firefox',
+                  'resource_type_link': '.../#timeline_project_series',
+                  'self_link': 'http://.../firefox/trunk',
+                  'status': 'Obsolete',
+                  'uri': '/firefox/trunk',
+                  'web_link': 'http://launchpad.../firefox/trunk'},
+                 {'http_etag': ...
+                  'is_development_focus': False,
+                  'landmarks': [{'code_name': 'First Stable Release',
+                                 'date': '2004-06-28',
+                                 'name': '1.0.0',
+                                 'type': 'release',
+                                 'uri': '/firefox/1.0/1.0.0'}],
+                  'name': '1.0',
+                  'project_link': 'http://.../firefox',
+                  'resource_type_link': '.../#timeline_project_series',
+                  'self_link': 'http://.../firefox/1.0',
+                  'status': 'Active Development',
+                  'uri': '/firefox/1.0',
+                  'web_link': 'http://launchpad.../firefox/1.0'},
+                 {'http_etag': ...
+                  'is_development_focus': False,
+                  'landmarks': [],
+                  'name': 'experimental',
+                  'project_link': 'http://.../firefox',
+                  'resource_type_link': '.../#timeline_project_series',
+                  'self_link': 'http://.../firefox/experimental',
+                  'status': 'Active Development',
+                  'uri': '/firefox/experimental',
+                  'web_link': 'http://launchpad.../firefox/experimental'}],
+     'start': 0,
+     'total_size': 3}
 
 
 Project collection
@@ -756,16 +756,16 @@ milestones and releases.
     >>> timeline = webservice.named_get(
     ...     babadoo_foobadoo['self_link'], "get_timeline").jsonBody()
     >>> print(pretty(timeline))
-    {u'http_etag': ...
-     u'is_development_focus': False,
-     u'landmarks': [],
-     u'name': u'foobadoo',
-     u'project_link': u'http://.../babadoo',
-     u'resource_type_link': u'http://.../#timeline_project_series',
-     u'self_link': u'http://.../babadoo/foobadoo',
-     u'status': u'Active Development',
-     u'uri': u'/babadoo/foobadoo',
-     u'web_link': u'http://launchpad.../babadoo/foobadoo'}
+    {'http_etag': ...
+     'is_development_focus': False,
+     'landmarks': [],
+     'name': 'foobadoo',
+     'project_link': 'http://.../babadoo',
+     'resource_type_link': 'http://.../#timeline_project_series',
+     'self_link': 'http://.../babadoo/foobadoo',
+     'status': 'Active Development',
+     'uri': '/babadoo/foobadoo',
+     'web_link': 'http://launchpad.../babadoo/foobadoo'}
 
 
 Creating a milestone on the product series
diff --git a/lib/lp/services/doc/sprites.txt b/lib/lp/services/doc/sprites.txt
index d1b85ac..c2483c4 100644
--- a/lib/lp/services/doc/sprites.txt
+++ b/lib/lp/services/doc/sprites.txt
@@ -116,9 +116,9 @@ changes without requiring the combined image file to be recreated.
 The positions attribute can be cleared and loaded from the file.
 
     >>> print pretty(sprite_util.positions)
-    {u'../images/add.png': (0, -114),
-     u'../images/blue-bar.png': (0, -342),
-     u'../images/edit.png': (0, -228)}
+    {'../images/add.png': (0, -114),
+     '../images/blue-bar.png': (0, -342),
+     '../images/edit.png': (0, -228)}
     >>> sprite_util.positions = None
     >>> sprite_util.loadPositioning(new_positioning_file.name)
     >>> print pretty(sprite_util.positions)
diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
index 2efabd0..e620147 100644
--- a/lib/lp/testing/pages.py
+++ b/lib/lp/testing/pages.py
@@ -13,7 +13,6 @@ import doctest
 from io import BytesIO
 from itertools import chain
 import os
-import pprint
 import re
 import unittest
 
@@ -85,6 +84,7 @@ from lp.testing.factory import LaunchpadObjectFactory
 from lp.testing.layers import PageTestLayer
 from lp.testing.systemdocs import (
     LayeredDocFileSuite,
+    PrettyPrinter,
     stop,
     )
 
@@ -876,7 +876,7 @@ def setUpGlobs(test, future=False):
     test.globs['logout'] = logout
     test.globs['parse_relationship_section'] = parse_relationship_section
     test.globs['permissive_security_policy'] = permissive_security_policy
-    test.globs['pretty'] = pprint.PrettyPrinter(width=1).pformat
+    test.globs['pretty'] = PrettyPrinter(width=1).pformat
     test.globs['print_action_links'] = print_action_links
     test.globs['print_errors'] = print_errors
     test.globs['print_location'] = print_location
diff --git a/lib/lp/testing/systemdocs.py b/lib/lp/testing/systemdocs.py
index 3fb72bf..706fa75 100644
--- a/lib/lp/testing/systemdocs.py
+++ b/lib/lp/testing/systemdocs.py
@@ -9,6 +9,7 @@ __metaclass__ = type
 __all__ = [
     'default_optionflags',
     'LayeredDocFileSuite',
+    'PrettyPrinter',
     'setUp',
     'setGlobs',
     'stop',
@@ -212,6 +213,33 @@ def stop():
         sys.stdout = old_stdout
 
 
+class PrettyPrinter(pprint.PrettyPrinter, object):
+    """A pretty-printer that formats text in the Python 3 style.
+
+    This should only be used when the resulting ambiguity between str and
+    unicode representation on Python 2 is not a problem.
+    """
+
+    def format(self, obj, contexts, maxlevels, level):
+        if isinstance(obj, six.text_type):
+            obj = obj.encode('unicode_escape').decode('ASCII')
+            if "'" in obj and '"' not in obj:
+                return '"%s"' % obj, True, False
+            else:
+                return "'%s'" % obj.replace("'", "\\'"), True, False
+        else:
+            return super(PrettyPrinter, self).format(
+                obj, contexts, maxlevels, level)
+
+    # Disable wrapping of long strings on Python >= 3.5, which is unhelpful
+    # in doctests.  There seems to be no reasonable public API for this.
+    if sys.version_info[:2] >= (3, 5):
+        _dispatch = dict(pprint.PrettyPrinter._dispatch)
+        del _dispatch[six.text_type.__repr__]
+        del _dispatch[bytes.__repr__]
+        del _dispatch[bytearray.__repr__]
+
+
 # XXX cjwatson 2018-05-13: Once all doctests are made safe for the standard
 # __future__ imports, the `future=True` behaviour should become
 # unconditional.
@@ -230,7 +258,7 @@ def setGlobs(test, future=False):
     test.globs['factory'] = LaunchpadObjectFactory()
     test.globs['ordered_dict_as_string'] = ordered_dict_as_string
     test.globs['verifyObject'] = verifyObject
-    test.globs['pretty'] = pprint.PrettyPrinter(width=1).pformat
+    test.globs['pretty'] = PrettyPrinter(width=1).pformat
     test.globs['stop'] = stop
     test.globs['launchpadlib_for'] = launchpadlib_for
     test.globs['launchpadlib_credentials_for'] = launchpadlib_credentials_for