From 91ade25ba3cedbc7d2e8dea1f64fe499ffe3ba16 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Tue, 3 Sep 2024 10:42:14 -0700 Subject: [PATCH] python: Simplify with str.removeprefix, str.removesuffix. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These are available in Python ≥ 3.9. https://docs.python.org/3/library/stdtypes.html#str.removeprefix Signed-off-by: Anders Kaseorg --- analytics/views/stats.py | 2 +- corporate/lib/decorator.py | 5 +---- corporate/tests/test_stripe.py | 11 ++++++----- scripts/lib/sharding.py | 2 +- scripts/lib/supervisor.py | 3 ++- scripts/lib/upgrade-zulip-from-git | 2 +- scripts/lib/zulip_tools.py | 2 +- scripts/log-search | 4 ++-- .../spiders/common/spiders.py | 8 ++++---- tools/lib/provision_inner.py | 2 +- tools/lib/template_parser.py | 4 +--- .../generate-integration-docs-screenshot | 3 +-- tools/upload-release | 2 +- zerver/lib/cache.py | 4 ++-- zerver/lib/email_mirror.py | 2 +- zerver/lib/github.py | 3 +-- zerver/lib/markdown/__init__.py | 10 +++++----- .../lib/markdown/api_arguments_table_generator.py | 4 +--- zerver/lib/markdown/static.py | 2 +- zerver/lib/name_restrictions.py | 4 ++-- zerver/lib/test_classes.py | 2 +- zerver/lib/test_helpers.py | 14 +++++--------- zerver/lib/test_runner.py | 2 +- zerver/lib/thumbnail.py | 2 +- zerver/lib/topic.py | 2 +- zerver/lib/upload/local.py | 2 +- zerver/lib/upload/s3.py | 6 +++--- zerver/lib/widget.py | 2 +- zerver/lib/zcommand.py | 2 +- zerver/lib/zephyr.py | 2 +- ...move_invalid_characters_from_user_group_name.py | 4 ++-- zerver/openapi/javascript_examples.py | 2 +- zerver/openapi/markdown_extension.py | 2 +- zerver/openapi/openapi.py | 8 ++++---- zerver/tests/test_realm_export.py | 2 +- zerver/tests/test_thumbnail.py | 10 +++++----- zerver/tests/test_upload_s3.py | 10 +++++----- zerver/views/development/integrations.py | 4 +--- zerver/views/documentation.py | 3 ++- zerver/views/message_send.py | 3 +-- zerver/webhooks/bitbucket3/view.py | 6 +++--- zerver/webhooks/greenhouse/view.py | 2 +- 42 files changed, 79 insertions(+), 92 deletions(-) diff --git a/analytics/views/stats.py b/analytics/views/stats.py index 89cf588eb2..0bd6690a9b 100644 --- a/analytics/views/stats.py +++ b/analytics/views/stats.py @@ -610,7 +610,7 @@ def client_label_map(name: str) -> str: if name in ["ZulipPython", "API: Python"]: return "Python API" if name.startswith("Zulip") and name.endswith("Webhook"): - return name[len("Zulip") : -len("Webhook")] + " webhook" + return name.removeprefix("Zulip").removesuffix("Webhook") + " webhook" return name diff --git a/corporate/lib/decorator.py b/corporate/lib/decorator.py index 3f4dc53b89..3620a45ec3 100644 --- a/corporate/lib/decorator.py +++ b/corporate/lib/decorator.py @@ -135,10 +135,7 @@ def get_next_page_param_from_request_path(request: HttpRequest) -> str | None: # Therefore we can use this nice property to figure out easily what # kind of page the user is trying to access and find the right value # for the `next` query parameter. - path = request.path - if path.endswith("/"): - path = path[:-1] - + path = request.path.removesuffix("/") page_type = path.split("/")[-1] from corporate.views.remote_billing_page import ( diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index 98c0752647..73f8801f88 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -120,15 +120,16 @@ def stripe_fixture_path( ) -> str: # Make the eventual filename a bit shorter, and also we conventionally # use test_* for the python test files - if decorated_function_name[:5] == "test_": - decorated_function_name = decorated_function_name[5:] - return f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name[7:]}.{call_count}.json" + decorated_function_name = decorated_function_name.removeprefix("test_") + mocked_function_name = mocked_function_name.removeprefix("stripe.") + return ( + f"{STRIPE_FIXTURES_DIR}/{decorated_function_name}--{mocked_function_name}.{call_count}.json" + ) def fixture_files_for_function(decorated_function: CallableT) -> list[str]: # nocoverage decorated_function_name = decorated_function.__name__ - if decorated_function_name[:5] == "test_": - decorated_function_name = decorated_function_name[5:] + decorated_function_name = decorated_function_name.removeprefix("test_") return sorted( f"{STRIPE_FIXTURES_DIR}/{f}" for f in os.listdir(STRIPE_FIXTURES_DIR) diff --git a/scripts/lib/sharding.py b/scripts/lib/sharding.py index 43df3c03a3..2b4c9ab8e9 100755 --- a/scripts/lib/sharding.py +++ b/scripts/lib/sharding.py @@ -54,7 +54,7 @@ def write_updated_configs() -> None: ).strip() for key, shards in config_file["tornado_sharding"].items(): if key.endswith("_regex"): - ports = [int(port) for port in key[: -len("_regex")].split("_")] + ports = [int(port) for port in key.removesuffix("_regex").split("_")] shard_regexes.append((shards, ports[0] if len(ports) == 1 else ports)) nginx_sharding_conf_f.write( f" {nginx_quote('~*' + shards)} http://tornado{'_'.join(map(str, ports))};\n" diff --git a/scripts/lib/supervisor.py b/scripts/lib/supervisor.py index f6f3dd3b1b..fab85931c1 100644 --- a/scripts/lib/supervisor.py +++ b/scripts/lib/supervisor.py @@ -58,7 +58,8 @@ def list_supervisor_processes( for filter_name in filter_names: # zulip-tornado:* matches zulip-tornado:9800 and zulip-tornado if filter_name.endswith(":*") and ( - name.startswith(filter_name[:-1]) or name == filter_name[:-2] + name.startswith(filter_name.removesuffix("*")) + or name == filter_name.removesuffix(":*") ): match = True break diff --git a/scripts/lib/upgrade-zulip-from-git b/scripts/lib/upgrade-zulip-from-git index 86b9c51997..31d9169398 100755 --- a/scripts/lib/upgrade-zulip-from-git +++ b/scripts/lib/upgrade-zulip-from-git @@ -122,7 +122,7 @@ try: keep_refs = set() for worktree_line in worktree_data: if worktree_line.startswith("branch "): - keep_refs.add(worktree_line[len("branch ") :]) + keep_refs.add(worktree_line.removeprefix("branch ")) delete_input = "".join( f"delete {refname}\n" for refname in matching_refs if refname not in keep_refs diff --git a/scripts/lib/zulip_tools.py b/scripts/lib/zulip_tools.py index 4fe1a50390..c4ab236031 100755 --- a/scripts/lib/zulip_tools.py +++ b/scripts/lib/zulip_tools.py @@ -639,7 +639,7 @@ def get_tornado_ports(config_file: configparser.RawConfigParser) -> list[int]: { int(port) for key in config_file.options("tornado_sharding") - for port in (key[: -len("_regex")] if key.endswith("_regex") else key).split("_") + for port in key.removesuffix("_regex").split("_") } ) if not ports: diff --git a/scripts/log-search b/scripts/log-search index 0624a6698b..f176c7fba5 100755 --- a/scripts/log-search +++ b/scripts/log-search @@ -454,9 +454,9 @@ def print_line( ts = match["time"] if match["duration"].endswith("ms"): - duration_ms = int(match["duration"][:-2]) + duration_ms = int(match["duration"].removesuffix("ms")) else: - duration_ms = int(float(match["duration"][:-1]) * 1000) + duration_ms = int(float(match["duration"].removesuffix("s")) * 1000) code = int(match["code"]) indicator = " " diff --git a/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py b/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py index 2654e0acf5..5b9bf1ff10 100644 --- a/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py +++ b/tools/documentation_crawler/documentation_crawler/spiders/common/spiders.py @@ -165,8 +165,8 @@ class BaseDocumentationSpider(scrapy.Spider): if split_url.hostname == "github.com" and f"{split_url.path}/".startswith( f"{ZULIP_SERVER_GITHUB_FILE_PATH_PREFIX}/" ): - file_path = ( - DEPLOY_ROOT + split_url.path[len(ZULIP_SERVER_GITHUB_FILE_PATH_PREFIX) :] + file_path = DEPLOY_ROOT + split_url.path.removeprefix( + ZULIP_SERVER_GITHUB_FILE_PATH_PREFIX ) if not os.path.isfile(file_path): self.logger.error( @@ -176,8 +176,8 @@ class BaseDocumentationSpider(scrapy.Spider): elif split_url.hostname == "github.com" and f"{split_url.path}/".startswith( f"{ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX}/" ): - dir_path = ( - DEPLOY_ROOT + split_url.path[len(ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX) :] + dir_path = DEPLOY_ROOT + split_url.path.removeprefix( + ZULIP_SERVER_GITHUB_DIRECTORY_PATH_PREFIX ) if not os.path.isdir(dir_path): self.logger.error( diff --git a/tools/lib/provision_inner.py b/tools/lib/provision_inner.py index 2fe44d3527..f095fcc15b 100755 --- a/tools/lib/provision_inner.py +++ b/tools/lib/provision_inner.py @@ -37,7 +37,7 @@ UUID_VAR_PATH = get_dev_uuid_var_path() with get_tzdata_zi() as f: line = f.readline() assert line.startswith("# version ") - timezones_version = line[len("# version ") :] + timezones_version = line.removeprefix("# version ") def create_var_directories() -> None: diff --git a/tools/lib/template_parser.py b/tools/lib/template_parser.py index 4a93165d0f..c819cafa94 100644 --- a/tools/lib/template_parser.py +++ b/tools/lib/template_parser.py @@ -186,9 +186,7 @@ def tokenize(text: str, template_format: str | None = None) -> list[Token]: kind = "handlebars_else" elif looking_at_handlebars_start(): s = get_handlebars_tag(text, state.i) - tag = s[3:-2].split()[0].strip("#") - if tag.startswith("*"): - tag = tag[1:] + tag = s[3:-2].split()[0].strip("#").removeprefix("*") kind = "handlebars_start" elif looking_at_handlebars_end(): s = get_handlebars_tag(text, state.i) diff --git a/tools/screenshots/generate-integration-docs-screenshot b/tools/screenshots/generate-integration-docs-screenshot index 2d34a98f6f..c8b1629397 100755 --- a/tools/screenshots/generate-integration-docs-screenshot +++ b/tools/screenshots/generate-integration-docs-screenshot @@ -120,8 +120,7 @@ def get_requests_headers(integration_name: str, fixture_name: str) -> dict[str, headers = get_fixture_http_headers(integration_name, fixture_name) def fix_name(header: str) -> str: - header = header if not header.startswith("HTTP_") else header[len("HTTP_") :] - return header.replace("_", "-") + return header.removeprefix("HTTP_").replace("_", "-") return {fix_name(k): v for k, v in headers.items()} diff --git a/tools/upload-release b/tools/upload-release index 8e70ca3ac5..da43db7449 100755 --- a/tools/upload-release +++ b/tools/upload-release @@ -50,7 +50,7 @@ print("Fetching existing hashes..") for obj_summary in bucket.objects.filter(Prefix="server/zulip-server-"): head = client.head_object(Bucket=bucket.name, Key=obj_summary.key) assert obj_summary.key.startswith("server/") - filename = obj_summary.key[len("server/") :] + filename = obj_summary.key.removeprefix("server/") if filename in file_hashes: print(f" {filename} was already uploaded, skipping existing hash") continue diff --git a/zerver/lib/cache.py b/zerver/lib/cache.py index 85aeb8c088..86db52f917 100644 --- a/zerver/lib/cache.py +++ b/zerver/lib/cache.py @@ -78,7 +78,7 @@ def get_or_create_key_prefix() -> str: tries = 1 while tries < 10: with open(filename) as f: - prefix = f.readline()[:-1] + prefix = f.readline().removesuffix("\n") if len(prefix) == 33: break tries += 1 @@ -214,7 +214,7 @@ def cache_get_many(keys: list[str], cache_name: str | None = None) -> dict[str, remote_cache_stats_start() ret = get_cache_backend(cache_name).get_many(keys) remote_cache_stats_finish() - return {key[len(KEY_PREFIX) :]: value for key, value in ret.items()} + return {key.removeprefix(KEY_PREFIX): value for key, value in ret.items()} def safe_cache_get_many(keys: list[str], cache_name: str | None = None) -> dict[str, Any]: diff --git a/zerver/lib/email_mirror.py b/zerver/lib/email_mirror.py index fadbd09c6d..f28f735740 100644 --- a/zerver/lib/email_mirror.py +++ b/zerver/lib/email_mirror.py @@ -44,7 +44,7 @@ def redact_email_address(error_message: str) -> str: domain = Address(addr_spec=settings.EMAIL_GATEWAY_PATTERN).domain else: # EMAIL_GATEWAY_EXTRA_PATTERN_HACK is of the form '@example.com' - domain = settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK[1:] + domain = settings.EMAIL_GATEWAY_EXTRA_PATTERN_HACK.removeprefix("@") def redact(address_match: Match[str]) -> str: email_address = address_match[0] diff --git a/zerver/lib/github.py b/zerver/lib/github.py index 1847f57e3b..6839827119 100644 --- a/zerver/lib/github.py +++ b/zerver/lib/github.py @@ -54,8 +54,7 @@ def get_latest_github_release_download_link_for_platform(platform: str) -> str: latest_version = get_latest_github_release_version_for_repo("zulip-desktop") if latest_version: - if latest_version[0] in ["v", "V"]: - latest_version = latest_version[1:] + latest_version = latest_version.removeprefix("v") setup_file = PLATFORM_TO_SETUP_FILE[platform].format(version=latest_version) link = f"https://desktop-download.zulip.com/v{latest_version}/{setup_file}" if verify_release_download_link(link): diff --git a/zerver/lib/markdown/__init__.py b/zerver/lib/markdown/__init__.py index 3dbe0def96..6ecf9963a3 100644 --- a/zerver/lib/markdown/__init__.py +++ b/zerver/lib/markdown/__init__.py @@ -269,7 +269,7 @@ def rewrite_local_links_to_relative(db_data: DbData | None, link: str) -> str: if db_data: realm_url_prefix = db_data.realm_url + "/" if link.startswith((realm_url_prefix + "#", realm_url_prefix + "user_uploads/")): - return link[len(realm_url_prefix) :] + return link.removeprefix(realm_url_prefix) return link @@ -622,7 +622,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): a.set("data-id", data_id) img = SubElement(a, "img") if image_url.startswith("/user_uploads/") and self.zmd.zulip_db_data: - path_id = image_url[len("/user_uploads/") :] + path_id = image_url.removeprefix("/user_uploads/") # We should have pulled the preview data for this image # (even if that's "no preview yet") from the database @@ -737,7 +737,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): # a row for the ImageAttachment, then its header didn't parse # as a valid image type which libvips handles. if url.startswith("/user_uploads/") and self.zmd.zulip_db_data: - path_id = url[len("/user_uploads/") :] + path_id = url.removeprefix("/user_uploads/") return path_id in self.zmd.zulip_db_data.user_upload_previews return any(parsed_url.path.lower().endswith(ext) for ext in IMAGE_EXTENSIONS) @@ -825,7 +825,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): elif split_url.path.startswith(("/embed/", "/shorts/", "/v/")): id = split_url.path.split("/", 3)[2] elif split_url.hostname == "youtu.be" and split_url.path.startswith("/"): - id = split_url.path[len("/") :] + id = split_url.path.removeprefix("/") if id is not None and re.fullmatch(r"[0-9A-Za-z_-]+", id): return id @@ -1274,7 +1274,7 @@ class InlineInterestingLinkProcessor(markdown.treeprocessors.Treeprocessor): if not parsed_url.path.startswith("/user_uploads/"): continue - path_id = parsed_url.path[len("/user_uploads/") :] + path_id = parsed_url.path.removeprefix("/user_uploads/") self.zmd.zulip_rendering_result.potential_attachment_path_ids.append(path_id) if len(found_urls) == 0: diff --git a/zerver/lib/markdown/api_arguments_table_generator.py b/zerver/lib/markdown/api_arguments_table_generator.py index 675402e428..b1f7e5e517 100644 --- a/zerver/lib/markdown/api_arguments_table_generator.py +++ b/zerver/lib/markdown/api_arguments_table_generator.py @@ -299,9 +299,7 @@ def makeExtension(*args: Any, **kwargs: str) -> MarkdownArgumentsTableGenerator: def generate_data_type(schema: Mapping[str, Any]) -> str: data_type = "" if "oneOf" in schema: - for item in schema["oneOf"]: - data_type = data_type + generate_data_type(item) + " | " - data_type = data_type[:-3] + data_type = " | ".join(generate_data_type(item) for item in schema["oneOf"]) elif "items" in schema: data_type = "(" + generate_data_type(schema["items"]) + ")[]" else: diff --git a/zerver/lib/markdown/static.py b/zerver/lib/markdown/static.py index 804d16a557..8827a21377 100644 --- a/zerver/lib/markdown/static.py +++ b/zerver/lib/markdown/static.py @@ -29,7 +29,7 @@ class StaticImageProcessor(markdown.treeprocessors.Treeprocessor): for img in root.iter("img"): url = img.get("src") if url is not None and url.startswith("/static/"): - img.set("src", staticfiles_storage.url(url[len("/static/") :])) + img.set("src", staticfiles_storage.url(url.removeprefix("/static/"))) def makeExtension(*args: Any, **kwargs: str) -> MarkdownStaticImagesGenerator: diff --git a/zerver/lib/name_restrictions.py b/zerver/lib/name_restrictions.py index c83bc47b89..b570c4d4bc 100644 --- a/zerver/lib/name_restrictions.py +++ b/zerver/lib/name_restrictions.py @@ -9,11 +9,11 @@ def is_reserved_subdomain(subdomain: str) -> bool: return True if subdomain in ZULIP_RESERVED_SUBDOMAINS: return True - if subdomain[-1] == "s" and subdomain[:-1] in ZULIP_RESERVED_SUBDOMAINS: + if subdomain.endswith("s") and subdomain.removesuffix("s") in ZULIP_RESERVED_SUBDOMAINS: return True if subdomain in GENERIC_RESERVED_SUBDOMAINS: return True - if subdomain[-1] == "s" and subdomain[:-1] in GENERIC_RESERVED_SUBDOMAINS: + if subdomain.endswith("s") and subdomain.removesuffix("s") in GENERIC_RESERVED_SUBDOMAINS: return True if settings.CORPORATE_ENABLED and ("zulip" in subdomain or "kandra" in subdomain): return True diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 3566aae659..e1591ce750 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -1647,7 +1647,7 @@ Output: # as the actual value of the attribute in LDAP. for attr, value in attrs.items(): if isinstance(value, str) and value.startswith("file:"): - with open(value[5:], "rb") as f: + with open(value.removeprefix("file:"), "rb") as f: attrs[attr] = [f.read()] ldap_patcher = mock.patch("django_auth_ldap.config.ldap.initialize") diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index 7cf5a90bc4..f2243183d1 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -489,14 +489,10 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N find_pattern(pattern, prefixes) def cleanup_url(url: str) -> str: - if url.startswith("/"): - url = url[1:] - if url.startswith("http://testserver/"): - url = url[len("http://testserver/") :] - if url.startswith("http://zulip.testserver/"): - url = url[len("http://zulip.testserver/") :] - if url.startswith("http://testserver:9080/"): - url = url[len("http://testserver:9080/") :] + url = url.removeprefix("/") + url = url.removeprefix("http://testserver/") + url = url.removeprefix("http://zulip.testserver/") + url = url.removeprefix("http://testserver:9080/") return url def find_pattern(pattern: Any, prefixes: list[str]) -> None: @@ -516,7 +512,7 @@ def write_instrumentation_reports(full_suite: bool, include_webhooks: bool) -> N for prefix in prefixes: if url.startswith(prefix): - match_url = url[len(prefix) :] + match_url = url.removeprefix(prefix) if pattern.resolve(match_url): if call["status_code"] in [200, 204, 301, 302]: cnt += 1 diff --git a/zerver/lib/test_runner.py b/zerver/lib/test_runner.py index 17435ecb18..2a5092239f 100644 --- a/zerver/lib/test_runner.py +++ b/zerver/lib/test_runner.py @@ -346,7 +346,7 @@ class Runner(DiscoverRunner): prefix = "unittest.loader._FailedTest." for test_name in get_test_names(suite): if test_name.startswith(prefix): - test_name = test_name[len(prefix) :] + test_name = test_name.removeprefix(prefix) for label in test_labels: # This code block is for when a test label is # directly provided, for example: diff --git a/zerver/lib/thumbnail.py b/zerver/lib/thumbnail.py index 9e16a47d4f..b9af74f8ac 100644 --- a/zerver/lib/thumbnail.py +++ b/zerver/lib/thumbnail.py @@ -426,7 +426,7 @@ def rewrite_thumbnailed_images( # second image, the first image will have no placeholder. continue - path_id = image_link["href"][len("/user_uploads/") :] + path_id = image_link["href"].removeprefix("/user_uploads/") if to_delete and path_id in to_delete: # This was not a valid thumbnail target, for some reason. # Trim out the whole "message_inline_image" element, since diff --git a/zerver/lib/topic.py b/zerver/lib/topic.py index 673f83936e..7e3c7d4b89 100644 --- a/zerver/lib/topic.py +++ b/zerver/lib/topic.py @@ -294,7 +294,7 @@ def get_topic_resolution_and_bare_name(stored_name: str) -> tuple[bool, str]: - The topic name with the resolution prefix, if present in stored_name, removed """ if stored_name.startswith(RESOLVED_TOPIC_PREFIX): - return (True, stored_name[len(RESOLVED_TOPIC_PREFIX) :]) + return (True, stored_name.removeprefix(RESOLVED_TOPIC_PREFIX)) return (False, stored_name) diff --git a/zerver/lib/upload/local.py b/zerver/lib/upload/local.py index efcc86caaa..7c1f494e4c 100644 --- a/zerver/lib/upload/local.py +++ b/zerver/lib/upload/local.py @@ -239,7 +239,7 @@ class LocalUploadBackend(ZulipUploadBackend): def delete_export_tarball(self, export_path: str) -> str | None: # Get the last element of a list in the form ['user_avatars', ''] assert export_path.startswith("/") - file_path = export_path[1:].split("/", 1)[-1] + file_path = export_path.removeprefix("/").split("/", 1)[-1] if delete_local_file("avatars", file_path): return export_path return None diff --git a/zerver/lib/upload/s3.py b/zerver/lib/upload/s3.py index e212deebc6..8a23c498b3 100644 --- a/zerver/lib/upload/s3.py +++ b/zerver/lib/upload/s3.py @@ -183,7 +183,7 @@ class S3UploadBackend(ZulipUploadBackend): assert split_url.path.endswith(f"/{DUMMY_KEY}") return urlunsplit( - (split_url.scheme, split_url.netloc, split_url.path[: -len(DUMMY_KEY)], "", "") + (split_url.scheme, split_url.netloc, split_url.path.removesuffix(DUMMY_KEY), "", "") ) @override @@ -395,7 +395,7 @@ class S3UploadBackend(ZulipUploadBackend): @override def get_export_tarball_url(self, realm: Realm, export_path: str) -> str: # export_path has a leading / - return self.get_public_upload_url(export_path[1:]) + return self.get_public_upload_url(export_path.removeprefix("/")) @override def upload_export_tarball( @@ -420,7 +420,7 @@ class S3UploadBackend(ZulipUploadBackend): @override def delete_export_tarball(self, export_path: str) -> str | None: assert export_path.startswith("/") - path_id = export_path[1:] + path_id = export_path.removeprefix("/") if self.delete_file_from_s3(path_id, self.avatar_bucket): return export_path return None diff --git a/zerver/lib/widget.py b/zerver/lib/widget.py index ea9363b978..059c904857 100644 --- a/zerver/lib/widget.py +++ b/zerver/lib/widget.py @@ -12,7 +12,7 @@ def get_widget_data(content: str) -> tuple[str | None, Any]: # tokens[0] will always exist if tokens[0].startswith("/"): - widget_type = tokens[0][1:] + widget_type = tokens[0].removeprefix("/") if widget_type in valid_widget_types: remaining_content = content.replace(tokens[0], "", 1) extra_data = get_extra_data_from_widget_type(remaining_content, widget_type) diff --git a/zerver/lib/zcommand.py b/zerver/lib/zcommand.py index 0a7be1ca3c..fe51666248 100644 --- a/zerver/lib/zcommand.py +++ b/zerver/lib/zcommand.py @@ -22,7 +22,7 @@ def process_zcommands(content: str, user_profile: UserProfile) -> dict[str, Any] if not content.startswith("/"): raise JsonableError(_("There should be a leading slash in the zcommand.")) - command = content[1:] + command = content.removeprefix("/") if command == "ping": return {} diff --git a/zerver/lib/zephyr.py b/zerver/lib/zephyr.py index ab2970ed37..20a73a4857 100644 --- a/zerver/lib/zephyr.py +++ b/zerver/lib/zephyr.py @@ -14,7 +14,7 @@ def compute_mit_user_fullname(email: str) -> str: if hesiod_name != "": return hesiod_name elif match_user: - return match_user.group(1).lower() + "@" + match_user.group(2).upper()[1:] + return match_user.group(1).lower() + "@" + match_user.group(2).upper().removeprefix("|") except DNS.Base.ServerError: pass except Exception: diff --git a/zerver/migrations/0459_remove_invalid_characters_from_user_group_name.py b/zerver/migrations/0459_remove_invalid_characters_from_user_group_name.py index 12d185b27e..fc33e08604 100644 --- a/zerver/migrations/0459_remove_invalid_characters_from_user_group_name.py +++ b/zerver/migrations/0459_remove_invalid_characters_from_user_group_name.py @@ -32,7 +32,7 @@ def remove_invalid_characters_from_user_group_name( continue old_group_name = group.name - group.name = old_group_name[1:] + group.name = old_group_name.removeprefix("@") groups_to_update.append(group) # Fix the name of non-system groups as well. @@ -53,7 +53,7 @@ def remove_invalid_characters_from_user_group_name( ) if len(matching_invalid_prefix) == 0: break - group_name = group_name[len(matching_invalid_prefix) :] + group_name = group_name.removeprefix(matching_invalid_prefix) if len(group_name) > 0 and group_name not in existing_group_names_set: group.name = group_name diff --git a/zerver/openapi/javascript_examples.py b/zerver/openapi/javascript_examples.py index 5305163f65..107963ee49 100644 --- a/zerver/openapi/javascript_examples.py +++ b/zerver/openapi/javascript_examples.py @@ -16,7 +16,7 @@ from zerver.openapi.openapi import validate_against_openapi_schema def test_js_bindings(client: Client) -> None: os.environ["ZULIP_USERNAME"] = client.email os.environ["ZULIP_API_KEY"] = client.api_key - os.environ["ZULIP_REALM"] = client.base_url[:-5] + os.environ["ZULIP_REALM"] = client.base_url.removesuffix("/api/") output = subprocess.check_output( args=["node", "--unhandled-rejections=strict", "zerver/openapi/javascript_examples.js"], diff --git a/zerver/openapi/markdown_extension.py b/zerver/openapi/markdown_extension.py index 32fed6d99b..71b0d134a4 100644 --- a/zerver/openapi/markdown_extension.py +++ b/zerver/openapi/markdown_extension.py @@ -160,7 +160,7 @@ def render_python_code_example( "```python", *config, # Remove one level of indentation and strip newlines - *(line[4:].rstrip() for snippet in snippets for line in snippet), + *(line.removeprefix(" ").rstrip() for snippet in snippets for line in snippet), "print(result)", "\n", "```", diff --git a/zerver/openapi/openapi.py b/zerver/openapi/openapi.py index b6de7c5de7..2415978536 100644 --- a/zerver/openapi/openapi.py +++ b/zerver/openapi/openapi.py @@ -443,9 +443,9 @@ def validate_test_response(request: Request, response: Response) -> bool: """ if request.path.startswith("/json/"): - path = request.path[len("/json") :] + path = request.path.removeprefix("/json") elif request.path.startswith("/api/v1/"): - path = request.path[len("/api/v1") :] + path = request.path.removeprefix("/api/v1") else: return False assert request.method is not None @@ -571,14 +571,14 @@ def validate_test_request( assert request.method is not None method = request.method.lower() if request.path.startswith("/json/"): - url = request.path[len("/json") :] + url = request.path.removeprefix("/json") # Some JSON endpoints have different parameters compared to # their `/api/v1` counterparts. if (url, method) in SKIP_JSON: return else: assert request.path.startswith("/api/v1/") - url = request.path[len("/api/v1") :] + url = request.path.removeprefix("/api/v1") # TODO: Add support for file upload endpoints that lack the /json/ # or /api/v1/ prefix. diff --git a/zerver/tests/test_realm_export.py b/zerver/tests/test_realm_export.py index f7ac043e32..340e8406eb 100644 --- a/zerver/tests/test_realm_export.py +++ b/zerver/tests/test_realm_export.py @@ -71,7 +71,7 @@ class RealmExportTest(ZulipTestCase): # Test that the file is hosted, and the contents are as expected. export_path = audit_log_entry.extra_data["export_path"] assert export_path.startswith("/") - path_id = export_path[1:] + path_id = export_path.removeprefix("/") self.assertEqual(bucket.Object(path_id).get()["Body"].read(), b"zulip!") result = self.client_get("/json/export/realm") diff --git a/zerver/tests/test_thumbnail.py b/zerver/tests/test_thumbnail.py index 8543035cb6..b972381ace 100644 --- a/zerver/tests/test_thumbnail.py +++ b/zerver/tests/test_thumbnail.py @@ -50,12 +50,12 @@ class ThumbnailRedirectEndpointTest(ZulipTestCase): base = "/user_uploads/" self.assertEqual(base, url[: len(base)]) - result = self.client_get("/thumbnail", {"url": url[1:], "size": "full"}) + result = self.client_get("/thumbnail", {"url": url.removeprefix("/"), "size": "full"}) self.assertEqual(result.status_code, 200) self.assertEqual(result.getvalue(), b"zulip!") self.login("iago") - result = self.client_get("/thumbnail", {"url": url[1:], "size": "full"}) + result = self.client_get("/thumbnail", {"url": url.removeprefix("/"), "size": "full"}) self.assertEqual(result.status_code, 403, result) self.assert_in_response("You are not authorized to view this file.", result) @@ -92,7 +92,7 @@ class ThumbnailRedirectEndpointTest(ZulipTestCase): self.send_stream_message(self.example_user("hamlet"), "Denmark", body, "test") self.logout() - response = self.client_get("/thumbnail", {"url": url[1:], "size": "full"}) + response = self.client_get("/thumbnail", {"url": url.removeprefix("/"), "size": "full"}) self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/accounts/login/?next=")) @@ -104,12 +104,12 @@ class ThumbnailRedirectEndpointTest(ZulipTestCase): self.send_stream_message(self.example_user("hamlet"), "web-public-stream", body, "test") self.logout() - response = self.client_get("/thumbnail", {"url": url[1:], "size": "full"}) + response = self.client_get("/thumbnail", {"url": url.removeprefix("/"), "size": "full"}) self.assertEqual(response.status_code, 200) # Deny file access since rate limited with ratelimit_rule(86400, 0, domain="spectator_attachment_access_by_file"): - response = self.client_get("/thumbnail", {"url": url[1:], "size": "full"}) + response = self.client_get("/thumbnail", {"url": url.removeprefix("/"), "size": "full"}) self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith("/accounts/login/?next=")) diff --git a/zerver/tests/test_upload_s3.py b/zerver/tests/test_upload_s3.py index 7498b75089..e79df66f63 100644 --- a/zerver/tests/test_upload_s3.py +++ b/zerver/tests/test_upload_s3.py @@ -193,7 +193,7 @@ class S3Test(ZulipTestCase): redirect_url = response["Location"] path = urlsplit(redirect_url).path assert path.startswith("/") - key = path[len("/") :] + key = path.removeprefix("/") self.assertEqual(b"zulip!", bucket.Object(key).get()["Body"].read()) prefix = f"/internal/s3/{settings.S3_AUTH_UPLOADS_BUCKET}.s3.amazonaws.com/" @@ -202,7 +202,7 @@ class S3Test(ZulipTestCase): redirect_url = response["X-Accel-Redirect"] path = urlsplit(redirect_url).path assert path.startswith(prefix) - key = path[len(prefix) :] + key = path.removeprefix(prefix) self.assertEqual(b"zulip!", bucket.Object(key).get()["Body"].read()) # Check the download endpoint @@ -212,7 +212,7 @@ class S3Test(ZulipTestCase): redirect_url = response["X-Accel-Redirect"] path = urlsplit(redirect_url).path assert path.startswith(prefix) - key = path[len(prefix) :] + key = path.removeprefix(prefix) self.assertEqual(b"zulip!", bucket.Object(key).get()["Body"].read()) # Now try the endpoint that's supposed to return a temporary URL for access @@ -232,7 +232,7 @@ class S3Test(ZulipTestCase): redirect_url = response["X-Accel-Redirect"] path = urlsplit(redirect_url).path assert path.startswith(prefix) - key = path[len(prefix) :] + key = path.removeprefix(prefix) self.assertEqual(b"zulip!", bucket.Object(key).get()["Body"].read()) # The original url shouldn't work when logged out: @@ -279,7 +279,7 @@ class S3Test(ZulipTestCase): self.assertEqual(base, url[: len(base)]) # Try hitting the equivalent `/user_avatars` endpoint - wrong_url = "/user_avatars/" + url[len(base) :] + wrong_url = "/user_avatars/" + url.removeprefix(base) result = self.client_get(wrong_url) self.assertEqual(result.status_code, 301) self.assertEqual(result["Location"], url) diff --git a/zerver/views/development/integrations.py b/zerver/views/development/integrations.py index aed1b279d7..29b78bae8d 100644 --- a/zerver/views/development/integrations.py +++ b/zerver/views/development/integrations.py @@ -94,9 +94,7 @@ def get_fixtures(request: HttpRequest, *, integration_name: PathOnly[str]) -> Ht ) def fix_name(header: str) -> str: # nocoverage - if header.startswith("HTTP_"): # HTTP_ is a prefix intended for Django. - return header[len("HTTP_") :] - return header + return header.removeprefix("HTTP_") # HTTP_ is a prefix intended for Django. headers = {fix_name(k): v for k, v in headers_raw.items()} fixtures[fixture] = {"body": body, "headers": headers} diff --git a/zerver/views/documentation.py b/zerver/views/documentation.py index 3617d69538..c9a657b26e 100644 --- a/zerver/views/documentation.py +++ b/zerver/views/documentation.py @@ -382,7 +382,8 @@ def integration_doc(request: HttpRequest, *, integration_name: PathOnly[str]) -> context["integration_display_name"] = integration.display_name context["recommended_channel_name"] = integration.stream_name if isinstance(integration, WebhookIntegration): - context["integration_url"] = integration.url[3:] + assert integration.url.startswith("api/") + context["integration_url"] = integration.url.removeprefix("api") all_event_types = get_all_event_types_for_integration(integration) if all_event_types is not None: context["all_event_types"] = all_event_types diff --git a/zerver/views/message_send.py b/zerver/views/message_send.py index d5fcbbf7b5..072fc958e3 100644 --- a/zerver/views/message_send.py +++ b/zerver/views/message_send.py @@ -105,8 +105,7 @@ def same_realm_irc_user(user_profile: UserProfile, email: str) -> bool: return False domain = Address(addr_spec=email).domain.lower() - if domain.startswith("irc."): - domain = domain[len("irc.") :] + domain = domain.removeprefix("irc.") # Assumes allow_subdomains=False for all RealmDomain's corresponding to # these realms. diff --git a/zerver/webhooks/bitbucket3/view.py b/zerver/webhooks/bitbucket3/view.py index 62f8b731fe..b3a4c284e3 100644 --- a/zerver/webhooks/bitbucket3/view.py +++ b/zerver/webhooks/bitbucket3/view.py @@ -92,9 +92,9 @@ def repo_comment_handler( repo_name = payload["repository"]["name"].tame(check_string) topic_name = BITBUCKET_TOPIC_TEMPLATE.format(repository_name=repo_name) sha = payload["commit"].tame(check_string) - commit_url = payload["repository"]["links"]["self"][0]["href"].tame(check_string)[ - : -len("browse") - ] + commit_url = ( + payload["repository"]["links"]["self"][0]["href"].tame(check_string).removesuffix("browse") + ) commit_url += f"commits/{sha}" message = payload["comment"]["text"].tame(check_string) if action == "deleted their comment": diff --git a/zerver/webhooks/greenhouse/view.py b/zerver/webhooks/greenhouse/view.py index eaf650a27b..5759bc0cb6 100644 --- a/zerver/webhooks/greenhouse/view.py +++ b/zerver/webhooks/greenhouse/view.py @@ -26,7 +26,7 @@ def dict_list_to_string(some_list: WildValue) -> str: elif item_type and item_url: internal_template += f"[{item_type}]({item_url}), " - internal_template = internal_template[:-2] + internal_template = internal_template.removesuffix(", ") return internal_template