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
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp