launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #30979
[Merge] ~cjwatson/launchpad:drop-py35 into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:drop-py35 into launchpad:master.
Commit message:
Drop various bits of code to handle Python <= 3.5
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/462554
Hi! I wanted to do a real-world performance test of my new laptop, so I thought of doing a full Launchpad test run on it (about 2h, if you're curious - it was usually more like 9h on my old laptop by the time I left Canonical), and I used a random half-finished refactoring branch I had lying around. Since it passes, you might as well have the results.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:drop-py35 into launchpad:master.
diff --git a/lib/lp/app/browser/tales.py b/lib/lp/app/browser/tales.py
index d6b2760..fc1c2fc 100644
--- a/lib/lp/app/browser/tales.py
+++ b/lib/lp/app/browser/tales.py
@@ -56,7 +56,6 @@ from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.registry.interfaces.socialaccount import SOCIAL_PLATFORM_TYPES_MAP
-from lp.services.compat import tzname
from lp.services.utils import round_half_up
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.canonicalurl import nearest_adapter
@@ -1301,8 +1300,7 @@ class PersonFormatterAPI(ObjectFormatterAPI):
def local_time(self):
"""Return the local time for this person."""
time_zone = self._context.time_zone
- dt = datetime.now(tz.gettz(time_zone))
- return "%s %s" % (dt.strftime("%T"), tzname(dt))
+ return datetime.now(tz.gettz(time_zone)).strftime("%T %Z")
def url(self, view_name=None, rootsite="mainsite"):
"""See `ObjectFormatterAPI`.
@@ -2390,7 +2388,7 @@ class DateTimeFormatterAPI:
def time(self):
if self._datetime.tzinfo:
value = self._datetime.astimezone(getUtility(ILaunchBag).time_zone)
- return "%s %s" % (value.strftime("%T"), tzname(value))
+ return value.strftime("%T %Z")
else:
return self._datetime.strftime("%T")
diff --git a/lib/lp/app/widgets/date.py b/lib/lp/app/widgets/date.py
index 99d8f3f..885518d 100644
--- a/lib/lp/app/widgets/date.py
+++ b/lib/lp/app/widgets/date.py
@@ -33,7 +33,6 @@ from zope.formlib.textwidgets import TextWidget
from zope.formlib.widget import DisplayWidget
from lp.app.validators import LaunchpadValidationError
-from lp.services.compat import tzname
from lp.services.utils import round_half_up
from lp.services.webapp.escaping import html_escape
from lp.services.webapp.interfaces import ILaunchBag
@@ -638,6 +637,4 @@ class DatetimeDisplayWidget(DisplayWidget):
if value == self.context.missing_value:
return ""
value = value.astimezone(time_zone)
- return html_escape(
- "%s %s" % (value.strftime("%Y-%m-%d %H:%M:%S", tzname(value)))
- )
+ return html_escape(value.strftime("%Y-%m-%d %H:%M:%S %Z"))
diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py
index a22ebaa..91ee23b 100644
--- a/lib/lp/blueprints/browser/sprint.py
+++ b/lib/lp/blueprints/browser/sprint.py
@@ -61,7 +61,6 @@ from lp.registry.browser.menu import (
RegistryCollectionActionMenuBase,
)
from lp.registry.interfaces.person import IPersonSet
-from lp.services.compat import tzname
from lp.services.database.bulk import load_referencing
from lp.services.helpers import shortlist
from lp.services.propertycache import cachedproperty
@@ -225,35 +224,28 @@ class SprintView(HasSpecificationsView):
def formatDateTime(self, dt):
"""Format a datetime value according to the sprint's time zone"""
dt = dt.astimezone(self.tzinfo)
- return "%s %s" % (dt.strftime("%Y-%m-%d %H:%M"), tzname(dt))
+ return dt.strftime("%Y-%m-%d %H:%M %Z")
def formatDate(self, dt):
"""Format a date value according to the sprint's time zone"""
dt = dt.astimezone(self.tzinfo)
return dt.strftime("%Y-%m-%d")
- def _formatLocal(self, dt):
- return "%s %s on %s" % (
- dt.strftime("%H:%M"),
- tzname(dt),
- dt.strftime("%A, %Y-%m-%d"),
- )
+ _local_timeformat = "%H:%M %Z on %A, %Y-%m-%d"
@property
def local_start(self):
"""The sprint start time, in the local time zone, as text."""
- return self._formatLocal(
- self.context.time_starts.astimezone(
- tz.gettz(self.context.time_zone)
- )
- )
+ return self.context.time_starts.astimezone(
+ tz.gettz(self.context.time_zone)
+ ).strftime(self._local_timeformat)
@property
def local_end(self):
"""The sprint end time, in the local time zone, as text."""
- return self._formatLocal(
- self.context.time_ends.astimezone(tz.gettz(self.context.time_zone))
- )
+ return self.context.time_ends.astimezone(
+ tz.gettz(self.context.time_zone)
+ ).strftime(self._local_timeformat)
class SprintAddView(LaunchpadFormView):
diff --git a/lib/lp/bugs/scripts/uct/models.py b/lib/lp/bugs/scripts/uct/models.py
index e11ce8c..418f074 100644
--- a/lib/lp/bugs/scripts/uct/models.py
+++ b/lib/lp/bugs/scripts/uct/models.py
@@ -42,7 +42,6 @@ from lp.registry.model.person import Person
from lp.registry.model.product import Product
from lp.registry.model.sourcepackage import SourcePackage
from lp.registry.model.sourcepackagename import SourcePackageName
-from lp.services.compat import tzname
from lp.services.propertycache import cachedproperty
__all__ = [
@@ -389,7 +388,7 @@ class UCTRecord:
@classmethod
def _format_datetime(cls, dt: datetime) -> str:
- return "%s %s" % (dt.strftime("%Y-%m-%d %H:%M:%S"), tzname(dt))
+ return dt.strftime("%Y-%m-%d %H:%M:%S %Z")
@classmethod
def _format_notes(cls, notes: List[Tuple[str, str]]) -> str:
diff --git a/lib/lp/bugs/tests/bug.py b/lib/lp/bugs/tests/bug.py
index 9798b9b..97dc8b5 100644
--- a/lib/lp/bugs/tests/bug.py
+++ b/lib/lp/bugs/tests/bug.py
@@ -40,7 +40,7 @@ def print_also_notified(bug_page):
def print_subscribers(bug_page, subscription_level=None, reverse=False):
"""Print the subscribers listed in the subscribers JSON portlet."""
- details = json.loads(bug_page.decode())
+ details = json.loads(bug_page)
if details is None:
# No subscribers at all.
diff --git a/lib/lp/buildmaster/tests/builderproxy.py b/lib/lp/buildmaster/tests/builderproxy.py
index a891b6c..fece785 100644
--- a/lib/lp/buildmaster/tests/builderproxy.py
+++ b/lib/lp/buildmaster/tests/builderproxy.py
@@ -28,7 +28,7 @@ class ProxyAuthAPITokensResource(resource.Resource):
self.requests = []
def render_POST(self, request):
- content = json.loads(request.content.read().decode("UTF-8"))
+ content = json.loads(request.content.read())
self.requests.append(
{
"method": request.method,
diff --git a/lib/lp/buildmaster/tests/fetchservice.py b/lib/lp/buildmaster/tests/fetchservice.py
index dd21e27..3fd879c 100644
--- a/lib/lp/buildmaster/tests/fetchservice.py
+++ b/lib/lp/buildmaster/tests/fetchservice.py
@@ -27,7 +27,7 @@ class FetchServiceAuthAPITokensResource(resource.Resource):
self.requests = []
def render_POST(self, request):
- content = json.loads(request.content.read().decode("UTF-8"))
+ content = json.loads(request.content.read())
self.requests.append(
{
"method": request.method,
diff --git a/lib/lp/charms/browser/tests/test_charmrecipe.py b/lib/lp/charms/browser/tests/test_charmrecipe.py
index d3a27b7..08313ab 100644
--- a/lib/lp/charms/browser/tests/test_charmrecipe.py
+++ b/lib/lp/charms/browser/tests/test_charmrecipe.py
@@ -423,7 +423,7 @@ class TestCharmRecipeAddView(BaseTestCharmRecipeView):
url=Equals("http://charmhub.example/v1/tokens"),
method=Equals("POST"),
body=AfterPreprocessing(
- lambda b: json.loads(b.decode()),
+ json.loads,
Equals(
{
"description": ("charmhub-name for launchpad.test"),
@@ -1094,7 +1094,7 @@ class TestCharmRecipeAuthorizeView(BaseTestCharmRecipeView):
url=Equals("http://charmhub.example/v1/tokens"),
method=Equals("POST"),
body=AfterPreprocessing(
- lambda b: json.loads(b.decode()),
+ json.loads,
Equals(
{
"description": (f"{store_name} for launchpad.test"),
@@ -1303,9 +1303,7 @@ class TestCharmRecipeAuthorizeView(BaseTestCharmRecipeView):
),
}
),
- body=AfterPreprocessing(
- lambda b: json.loads(b.decode()), Equals({})
- ),
+ body=AfterPreprocessing(json.loads, Equals({})),
)
self.assertThat(
responses.calls,
diff --git a/lib/lp/charms/tests/test_charmhubclient.py b/lib/lp/charms/tests/test_charmhubclient.py
index 7ca8734..34cd783 100644
--- a/lib/lp/charms/tests/test_charmhubclient.py
+++ b/lib/lp/charms/tests/test_charmhubclient.py
@@ -119,9 +119,7 @@ class RequestMatches(MatchesAll):
if json_data is not None:
matchers.append(
MatchesStructure(
- body=AfterPreprocessing(
- lambda b: json.loads(b.decode()), Equals(json_data)
- )
+ body=AfterPreprocessing(json.loads, Equals(json_data))
)
)
elif file_data is not None:
diff --git a/lib/lp/charms/tests/test_charmrecipe.py b/lib/lp/charms/tests/test_charmrecipe.py
index 0b41a97..1493f6f 100644
--- a/lib/lp/charms/tests/test_charmrecipe.py
+++ b/lib/lp/charms/tests/test_charmrecipe.py
@@ -1043,7 +1043,7 @@ class TestCharmRecipeAuthorization(TestCaseWithFactory):
url=Equals("http://charmhub.example/v1/tokens"),
method=Equals("POST"),
body=AfterPreprocessing(
- lambda b: json.loads(b.decode()),
+ json.loads,
Equals(
{
"description": (
@@ -1158,9 +1158,7 @@ class TestCharmRecipeAuthorization(TestCaseWithFactory):
),
}
),
- body=AfterPreprocessing(
- lambda b: json.loads(b.decode()), Equals({})
- ),
+ body=AfterPreprocessing(json.loads, Equals({})),
)
self.assertThat(
responses.calls,
@@ -2164,9 +2162,7 @@ class TestCharmRecipeWebservice(TestCaseWithFactory):
"package-view-revisions",
],
}
- self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8"))
- )
+ self.assertEqual(expected_body, json.loads(call.request.body))
self.assertEqual({"root": root_macaroon_raw}, recipe.store_secrets)
return response, root_macaroon_raw
@@ -2276,9 +2272,7 @@ class TestCharmRecipeWebservice(TestCaseWithFactory):
),
}
),
- body=AfterPreprocessing(
- lambda b: json.loads(b.decode()), Equals({})
- ),
+ body=AfterPreprocessing(json.loads, Equals({})),
)
self.assertThat(
responses.calls,
diff --git a/lib/lp/code/browser/gitrepository.py b/lib/lp/code/browser/gitrepository.py
index d6da0d3..089aee6 100644
--- a/lib/lp/code/browser/gitrepository.py
+++ b/lib/lp/code/browser/gitrepository.py
@@ -1101,9 +1101,7 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
field_type = field_bits[0]
try:
ref_pattern = decode_form_field_id(field_bits[1])
- # base64.b32decode raises TypeError for decoding errors on Python 2,
- # but binascii.Error on Python 3.
- except (TypeError, binascii.Error):
+ except binascii.Error:
raise UnexpectedFormData(
"Cannot parse field name: %s" % field_name
)
diff --git a/lib/lp/code/model/tests/test_githosting.py b/lib/lp/code/model/tests/test_githosting.py
index 0f5cf62..c876123 100644
--- a/lib/lp/code/model/tests/test_githosting.py
+++ b/lib/lp/code/model/tests/test_githosting.py
@@ -106,9 +106,7 @@ class TestGitHostingClient(TestCase):
),
)
if json_data is not None:
- self.assertEqual(
- json_data, json.loads(request.body.decode("UTF-8"))
- )
+ self.assertEqual(json_data, json.loads(request.body))
timeline = get_request_timeline(get_current_browser_request())
action = timeline.actions[-1]
self.assertEqual("git-hosting-%s" % method.lower(), action.category)
diff --git a/lib/lp/oci/model/ociregistryclient.py b/lib/lp/oci/model/ociregistryclient.py
index 04061a7..0a1211d 100644
--- a/lib/lp/oci/model/ociregistryclient.py
+++ b/lib/lp/oci/model/ociregistryclient.py
@@ -69,7 +69,7 @@ class OCIRegistryClient:
"""Read JSON out of a `LibraryFileAlias`."""
try:
reference.open()
- return json.loads(reference.read().decode("UTF-8"))
+ return json.loads(reference.read())
finally:
reference.close()
diff --git a/lib/lp/oci/tests/test_ociregistryclient.py b/lib/lp/oci/tests/test_ociregistryclient.py
index c3dec1c..6d01ea9 100644
--- a/lib/lp/oci/tests/test_ociregistryclient.py
+++ b/lib/lp/oci/tests/test_ociregistryclient.py
@@ -220,7 +220,7 @@ class TestOCIRegistryClient(
# We should have uploaded to the digest, not the tag
self.assertIn("sha256:", responses.calls[1].request.url)
self.assertNotIn("edge", responses.calls[1].request.url)
- request = json.loads(responses.calls[1].request.body.decode("UTF-8"))
+ request = json.loads(responses.calls[1].request.body)
layer_matchers = [
MatchesDict(
@@ -327,7 +327,7 @@ class TestOCIRegistryClient(
self.client.upload(self.build)
- request = json.loads(responses.calls[1].request.body.decode("UTF-8"))
+ request = json.loads(responses.calls[1].request.body)
layer_matchers = [
MatchesDict(
@@ -1078,7 +1078,7 @@ class TestOCIRegistryClient(
},
],
},
- json.loads(send_manifest_call.request.body.decode("UTF-8")),
+ json.loads(send_manifest_call.request.body),
)
@responses.activate
@@ -1195,7 +1195,7 @@ class TestOCIRegistryClient(
},
],
},
- json.loads(send_manifest_call.request.body.decode("UTF-8")),
+ json.loads(send_manifest_call.request.body),
)
@responses.activate
@@ -1267,7 +1267,7 @@ class TestOCIRegistryClient(
}
],
},
- json.loads(send_manifest_call.request.body.decode("UTF-8")),
+ json.loads(send_manifest_call.request.body),
)
@responses.activate
@@ -1441,7 +1441,7 @@ class TestOCIRegistryClient(
},
],
},
- json.loads(responses.calls[2].request.body.decode("UTF-8")),
+ json.loads(responses.calls[2].request.body),
)
diff --git a/lib/lp/registry/interfaces/ssh.py b/lib/lp/registry/interfaces/ssh.py
index 5559526..e99a000 100644
--- a/lib/lp/registry/interfaces/ssh.py
+++ b/lib/lp/registry/interfaces/ssh.py
@@ -176,15 +176,5 @@ class SSHKeyAdditionError(Exception):
)
if "exception" in kwargs:
exception = kwargs.pop("exception")
- try:
- exception_text = str(exception)
- except UnicodeDecodeError:
- # On Python 2, Key.fromString can raise exceptions with
- # non-UTF-8 messages.
- exception_text = (
- bytes(exception)
- .decode("unicode_escape")
- .encode("unicode_escape")
- )
- msg = "%s (%s)" % (msg, exception_text)
+ msg = "%s (%s)" % (msg, exception)
super().__init__(msg, *args, **kwargs)
diff --git a/lib/lp/services/auth/utils.py b/lib/lp/services/auth/utils.py
index f4c997b..c1a780c 100644
--- a/lib/lp/services/auth/utils.py
+++ b/lib/lp/services/auth/utils.py
@@ -7,12 +7,9 @@ __all__ = [
"create_access_token_secret",
]
-import binascii
-import os
+import secrets
-# XXX cjwatson 2021-09-30: Replace this with secrets.token_hex(32) once we
-# can rely on Python 3.6 everywhere.
def create_access_token_secret():
"""Create a secret suitable for use in a personal access token."""
- return binascii.hexlify(os.urandom(32)).decode("ASCII")
+ return secrets.token_hex(32)
diff --git a/lib/lp/services/compat.py b/lib/lp/services/compat.py
index 86d833a..ee2c2fe 100644
--- a/lib/lp/services/compat.py
+++ b/lib/lp/services/compat.py
@@ -8,12 +8,9 @@ Use this for things that six doesn't provide.
__all__ = [
"message_as_bytes",
- "tzname",
]
import io
-from datetime import datetime, time, timezone
-from typing import Union
def message_as_bytes(message):
@@ -24,18 +21,3 @@ def message_as_bytes(message):
g = BytesGenerator(fp, mangle_from_=False, maxheaderlen=0, policy=compat32)
g.flatten(message)
return fp.getvalue()
-
-
-def tzname(obj: Union[datetime, time]) -> str:
- """Return this (date)time object's time zone name as a string.
-
- Python 3.5's `timezone.utc.tzname` returns "UTC+00:00", rather than
- "UTC" which is what we prefer. Paper over this until we can rely on
- Python >= 3.6 everywhere.
- """
- if obj.tzinfo is None:
- return ""
- elif obj.tzinfo is timezone.utc:
- return "UTC"
- else:
- return obj.tzname()
diff --git a/lib/lp/services/librarian/tests/test_client.py b/lib/lp/services/librarian/tests/test_client.py
index 012d1e1..f258bab 100644
--- a/lib/lp/services/librarian/tests/test_client.py
+++ b/lib/lp/services/librarian/tests/test_client.py
@@ -154,10 +154,7 @@ class LibrarianFileWrapperTestCase(TestCase):
def test_unbounded_read_incorrect_length(self):
file = self.makeFile(extra_content_length=1)
with ExpectedException(http.client.IncompleteRead):
- # Python 3 notices the short response on the first read.
self.assertEqual(b"abcdef", file.read())
- # Python 2 only notices the short response on the next read.
- file.read()
def test_bounded_read_correct_length(self):
file = self.makeFile()
diff --git a/lib/lp/services/oauth/stories/authorize-token.rst b/lib/lp/services/oauth/stories/authorize-token.rst
index 61f3cc5..a3c30d1 100644
--- a/lib/lp/services/oauth/stories/authorize-token.rst
+++ b/lib/lp/services/oauth/stories/authorize-token.rst
@@ -169,7 +169,7 @@ the list of authentication levels.
>>> json_browser.open(
... "http://launchpad.test/+authorize-token?%s" % urlencode(params)
... )
- >>> json_token = json.loads(json_browser.contents.decode())
+ >>> json_token = json.loads(json_browser.contents)
>>> sorted(json_token.keys())
['access_levels', 'oauth_token', 'oauth_token_consumer']
@@ -190,7 +190,7 @@ the list of authentication levels.
... )
... % urlencode(params)
... )
- >>> json_token = json.loads(json_browser.contents.decode())
+ >>> json_token = json.loads(json_browser.contents)
>>> sorted(
... (level["value"], level["title"])
... for level in json_token["access_levels"]
diff --git a/lib/lp/services/oauth/stories/request-token.rst b/lib/lp/services/oauth/stories/request-token.rst
index 2bc110a..799fcc0 100644
--- a/lib/lp/services/oauth/stories/request-token.rst
+++ b/lib/lp/services/oauth/stories/request-token.rst
@@ -30,7 +30,7 @@ levels.
>>> json_browser.open(
... "http://launchpad.test/+request-token", data=urlencode(data)
... )
- >>> token = json.loads(json_browser.contents.decode())
+ >>> token = json.loads(json_browser.contents)
>>> sorted(token.keys())
['access_levels', 'oauth_token', 'oauth_token_consumer',
'oauth_token_secret']
diff --git a/lib/lp/services/signing/testing/fakesigning.py b/lib/lp/services/signing/testing/fakesigning.py
index eb7b27f..243408b 100644
--- a/lib/lp/services/signing/testing/fakesigning.py
+++ b/lib/lp/services/signing/testing/fakesigning.py
@@ -89,7 +89,7 @@ class GenerateResource(BoxedAuthenticationResource):
self.requests = []
def render_POST(self, request):
- payload = json.loads(self._decrypt(request).decode("UTF-8"))
+ payload = json.loads(self._decrypt(request))
self.requests.append(payload)
# We don't need to bother with generating a real key here. Just
# make up some random data.
@@ -117,7 +117,7 @@ class SignResource(BoxedAuthenticationResource):
self.requests = []
def render_POST(self, request):
- payload = json.loads(self._decrypt(request).decode("UTF-8"))
+ payload = json.loads(self._decrypt(request))
self.requests.append(payload)
_, public_key = self.keys[payload["fingerprint"]]
# We don't need to bother with generating a real signature here.
@@ -143,7 +143,7 @@ class InjectResource(BoxedAuthenticationResource):
self.requests = []
def render_POST(self, request):
- payload = json.loads(self._decrypt(request).decode("UTF-8"))
+ payload = json.loads(self._decrypt(request))
self.requests.append(payload)
private_key = base64.b64decode(payload["private-key"].encode("UTF-8"))
public_key = base64.b64decode(payload["public-key"].encode("UTF-8"))
diff --git a/lib/lp/services/signing/tests/test_proxy.py b/lib/lp/services/signing/tests/test_proxy.py
index 1db9530..6bb14ff 100644
--- a/lib/lp/services/signing/tests/test_proxy.py
+++ b/lib/lp/services/signing/tests/test_proxy.py
@@ -93,7 +93,7 @@ class SigningServiceResponseFactory:
"""
box = Box(self.service_private_key, self.client_public_key)
decrypted = box.decrypt(value, self.nonce, encoder=Base64Encoder)
- return json.loads(decrypted.decode("UTF-8"))
+ return json.loads(decrypted)
def getAPISignedContent(self, call_index=0):
"""Returns the signed message returned by the API.
diff --git a/lib/lp/services/twistedsupport/xmlrpc.py b/lib/lp/services/twistedsupport/xmlrpc.py
index 7cdf389..8e75dfc 100644
--- a/lib/lp/services/twistedsupport/xmlrpc.py
+++ b/lib/lp/services/twistedsupport/xmlrpc.py
@@ -56,9 +56,7 @@ def trap_fault(failure, *fault_classes):
:param failure: A Twisted L{Failure}.
:param *fault_codes: `LaunchpadFault` subclasses.
:raise Exception: if 'failure' is not a Fault failure, or if the fault
- code does not match the given codes. In line with L{Failure.trap},
- the exception is the L{Failure} itself on Python 2 and the
- underlying exception on Python 3.
+ code does not match the given codes.
:return: The Fault if it matches one of the codes.
"""
failure.trap(xmlrpc.Fault)
diff --git a/lib/lp/services/webapp/tests/test_candid.py b/lib/lp/services/webapp/tests/test_candid.py
index ea1898b..dfb4a8a 100644
--- a/lib/lp/services/webapp/tests/test_candid.py
+++ b/lib/lp/services/webapp/tests/test_candid.py
@@ -499,8 +499,7 @@ class TestCandidCallbackView(TestCaseWithFactory):
}
),
body=AfterPreprocessing(
- lambda b: json.loads(b.decode()),
- MatchesDict({"code": Equals("test code")}),
+ json.loads, MatchesDict({"code": Equals("test code")})
),
)
discharge_matcher = MatchesStructure(
diff --git a/lib/lp/services/webapp/tests/test_view_model.py b/lib/lp/services/webapp/tests/test_view_model.py
index 329fa64..f4b6ed6 100644
--- a/lib/lp/services/webapp/tests/test_view_model.py
+++ b/lib/lp/services/webapp/tests/test_view_model.py
@@ -123,7 +123,7 @@ class TestJsonModelView(BrowserTestCase):
lp.services.webapp.tests.ProductModelTestView = ProductModelTestView
self.configZCML()
browser = self.getUserBrowser(self.url)
- cache = json.loads(browser.contents.decode())
+ cache = json.loads(browser.contents)
self.assertThat(cache, KeysEqual("related_features", "context"))
def test_JsonModel_custom_cache(self):
@@ -140,7 +140,7 @@ class TestJsonModelView(BrowserTestCase):
lp.services.webapp.tests.ProductModelTestView = ProductModelTestView
self.configZCML()
browser = self.getUserBrowser(self.url)
- cache = json.loads(browser.contents.decode())
+ cache = json.loads(browser.contents)
self.assertThat(
cache, KeysEqual("related_features", "context", "target_info")
)
@@ -165,7 +165,7 @@ class TestJsonModelView(BrowserTestCase):
lp.services.webapp.tests.ProductModelTestView = ProductModelTestView
self.configZCML()
browser = self.getUserBrowser(self.url)
- cache = json.loads(browser.contents.decode())
+ cache = json.loads(browser.contents)
self.assertThat(
cache, KeysEqual("related_features", "context", "target_info")
)
diff --git a/lib/lp/services/webapp/url.py b/lib/lp/services/webapp/url.py
index ba3df8a..59c68dc 100644
--- a/lib/lp/services/webapp/url.py
+++ b/lib/lp/services/webapp/url.py
@@ -90,7 +90,7 @@ def urlparse(url, scheme="", allow_fragments=True):
The url parameter should contain ASCII characters only. This
function ensures that the original urlparse is called always with a
- str object, and never unicode (Python 2) or bytes (Python 3).
+ str object, and never bytes.
>>> tuple(urlparse("http://foo.com/bar"))
('http', 'foo.com', '/bar', '', '', '')
@@ -120,7 +120,7 @@ def urlsplit(url, scheme="", allow_fragments=True):
The url parameter should contain ASCII characters only. This
function ensures that the original urlsplit is called always with a
- str object, and never unicode (Python 2) or bytes (Python 3).
+ str object, and never bytes.
>>> tuple(urlsplit("http://foo.com/baz"))
('http', 'foo.com', '/baz', '', '')
diff --git a/lib/lp/snappy/browser/tests/test_snap.py b/lib/lp/snappy/browser/tests/test_snap.py
index 8d2a48a..819d770 100644
--- a/lib/lp/snappy/browser/tests/test_snap.py
+++ b/lib/lp/snappy/browser/tests/test_snap.py
@@ -653,9 +653,7 @@ class TestSnapAddView(BaseTestSnapView):
],
"permissions": ["package_upload"],
}
- self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8"))
- )
+ self.assertEqual(expected_body, json.loads(call.request.body))
self.assertEqual(303, browser.responseStatusCode)
parsed_location = urlsplit(browser.headers["Location"])
self.assertEqual(
@@ -1737,9 +1735,7 @@ class TestSnapEditView(BaseTestSnapView):
"packages": [{"name": "two", "series": self.snappyseries.name}],
"permissions": ["package_upload"],
}
- self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8"))
- )
+ self.assertEqual(expected_body, json.loads(call.request.body))
self.assertEqual(303, browser.responseStatusCode)
parsed_location = urlsplit(browser.headers["Location"])
self.assertEqual(
@@ -1820,9 +1816,7 @@ class TestSnapAuthorizeView(BaseTestSnapView):
],
"permissions": ["package_upload"],
}
- self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8"))
- )
+ self.assertEqual(expected_body, json.loads(call.request.body))
self.assertEqual(
{"root": root_macaroon_raw}, self.snap.store_secrets
)
diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
index 32a2d5c..9f671b8 100644
--- a/lib/lp/snappy/tests/test_snap.py
+++ b/lib/lp/snappy/tests/test_snap.py
@@ -4892,9 +4892,7 @@ class TestSnapWebservice(TestCaseWithFactory):
],
"permissions": ["package_upload"],
}
- self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8"))
- )
+ self.assertEqual(expected_body, json.loads(call.request.body))
self.assertEqual({"root": root_macaroon_raw}, snap.store_secrets)
return response, root_macaroon.third_party_caveats()[0]
diff --git a/lib/lp/snappy/tests/test_snapstoreclient.py b/lib/lp/snappy/tests/test_snapstoreclient.py
index d6f75bd..ddbf457 100644
--- a/lib/lp/snappy/tests/test_snapstoreclient.py
+++ b/lib/lp/snappy/tests/test_snapstoreclient.py
@@ -227,9 +227,7 @@ class RequestMatches(Matcher):
if mismatch is not None:
return mismatch
if self.json_data is not None:
- mismatch = Equals(self.json_data).match(
- json.loads(request.body.decode("UTF-8"))
- )
+ mismatch = Equals(self.json_data).match(json.loads(request.body))
if mismatch is not None:
return mismatch
if self.form_data is not None:
diff --git a/lib/lp/soyuz/wsgi/archiveauth.py b/lib/lp/soyuz/wsgi/archiveauth.py
index 006143d..17440e8 100644
--- a/lib/lp/soyuz/wsgi/archiveauth.py
+++ b/lib/lp/soyuz/wsgi/archiveauth.py
@@ -11,10 +11,8 @@ __all__ = [
]
import crypt
-import string
import sys
import time
-from random import SystemRandom
from xmlrpc.client import Fault, ServerProxy
import six
@@ -49,16 +47,6 @@ def _get_archive_reference(environ):
_log(environ, "No archive reference found in URL '%s'.", path)
-_sr = SystemRandom()
-
-
-def _crypt_sha256(word):
- """crypt.crypt(word, crypt.METHOD_SHA256), backported from Python 3.5."""
- saltchars = string.ascii_letters + string.digits + "./"
- salt = "$5$" + "".join(_sr.choice(saltchars) for _ in range(16))
- return crypt.crypt(word, salt)
-
-
_memcache_client = memcache_client_factory(timeline=False)
@@ -91,7 +79,9 @@ def check_password(environ, user, password):
proxy.checkArchiveAuthToken(archive_reference, user, password)
# Cache positive responses for a minute to reduce database load.
_memcache_client.set(
- memcache_key, _crypt_sha256(password), int(time.time()) + 60
+ memcache_key,
+ crypt.crypt(password, crypt.METHOD_SHA256),
+ int(time.time()) + 60,
)
_log(environ, "%s@%s: Authorized.", user, archive_reference)
return True
diff --git a/lib/lp/soyuz/wsgi/tests/test_archiveauth.py b/lib/lp/soyuz/wsgi/tests/test_archiveauth.py
index 3ac5ff9..16e8a2b 100644
--- a/lib/lp/soyuz/wsgi/tests/test_archiveauth.py
+++ b/lib/lp/soyuz/wsgi/tests/test_archiveauth.py
@@ -106,12 +106,6 @@ class TestWSGIArchiveAuth(TestCaseWithFactory):
self.assertEqual({}, self.memcache_fixture._cache)
self.assertLogs("No archive found for '~nonexistent/unknown/bad'.")
- def test_crypt_sha256(self):
- crypted_password = archiveauth._crypt_sha256("secret")
- self.assertEqual(
- crypted_password, crypt.crypt("secret", crypted_password)
- )
-
def makeArchiveAndToken(self):
archive = self.factory.makeArchive(private=True)
archive_path = "/%s/%s/ubuntu" % (archive.owner.name, archive.name)
diff --git a/lib/lp/testing/swift/fakeswift.py b/lib/lp/testing/swift/fakeswift.py
index 325c390..68126cb 100644
--- a/lib/lp/testing/swift/fakeswift.py
+++ b/lib/lp/testing/swift/fakeswift.py
@@ -107,9 +107,7 @@ class FakeKeystone(resource.Resource):
if "application/json" not in request.getHeader("content-type"):
request.setResponseCode(http.BAD_REQUEST)
return b""
- # XXX cjwatson 2020-06-15: Python 3.5 doesn't allow this to be a
- # binary file; 3.6 does.
- credentials = json.loads(request.content.read().decode("UTF-8"))
+ credentials = json.loads(request.content.read())
if "auth" not in credentials:
request.setResponseCode(http.FORBIDDEN)
return b""
diff --git a/lib/lp/translations/vocabularies.py b/lib/lp/translations/vocabularies.py
index a564444..d9c03dc 100644
--- a/lib/lp/translations/vocabularies.py
+++ b/lib/lp/translations/vocabularies.py
@@ -18,7 +18,6 @@ from storm.locals import Desc, Not, Or
from zope.schema.vocabulary import SimpleTerm
from lp.registry.interfaces.distroseries import IDistroSeries
-from lp.services.compat import tzname
from lp.services.webapp.vocabulary import (
NamedStormVocabulary,
StormVocabularyBase,
@@ -102,10 +101,7 @@ class FilteredLanguagePackVocabularyBase(StormVocabularyBase):
def toTerm(self, obj):
return SimpleTerm(
- obj,
- obj.id,
- "%s %s"
- % (obj.date_exported.strftime("%F %T"), tzname(obj.date_exported)),
+ obj, obj.id, "%s" % obj.date_exported.strftime("%F %T %Z")
)
@property
@@ -143,12 +139,8 @@ class FilteredLanguagePackVocabulary(FilteredLanguagePackVocabularyBase):
return SimpleTerm(
obj,
obj.id,
- "%s %s (%s)"
- % (
- obj.date_exported.strftime("%F %T"),
- tzname(obj.date_exported),
- obj.type.title,
- ),
+ "%s (%s)"
+ % (obj.date_exported.strftime("%F %T %Z"), obj.type.title),
)
@property
diff --git a/requirements/launchpad.txt b/requirements/launchpad.txt
index cc28c81..b7b7957 100644
--- a/requirements/launchpad.txt
+++ b/requirements/launchpad.txt
@@ -121,9 +121,6 @@ patiencediff==0.2.2
pexpect==4.8.0
pgbouncer==0.0.9
pickleshare==0.7.5
-# pkginfo 1.7.0 dropped Python 3.5 support, but we need the features of the
-# newer version, and both our and the package's test suite show no
-# incompatibilities
pkginfo==1.8.2
prettytable==0.7.2
prompt-toolkit==2.0.10
Follow ups