From 2d9b2a2a05ee259f3ffca28ee4d941d618876f03 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Wed, 8 Mar 2023 13:18:59 -0800 Subject: [PATCH] models: Remove type prefixes from __str__ values. The Django convention is for __repr__ to include the type and __str__ to omit it. In fact its default __repr__ implementation for models automatically adds a type prefix to __str__, which has resulted in the type being duplicated: >>> UserProfile.objects.first() >> Signed-off-by: Anders Kaseorg --- analytics/lib/counts.py | 2 +- analytics/models.py | 12 ++++---- confirmation/models.py | 2 +- corporate/models.py | 2 +- tools/coveragerc | 2 +- zerver/models.py | 48 +++++++++++++++---------------- zerver/tests/test_audit_log.py | 4 +-- zerver/tests/test_external.py | 2 +- zerver/tests/test_markdown.py | 44 ++++++++++++++-------------- zerver/tests/test_message_dict.py | 4 +-- zerver/tests/test_message_send.py | 4 +-- zerver/tests/test_presence.py | 2 +- zerver/tests/test_realm_emoji.py | 4 +-- zerver/tests/test_signup.py | 6 ++-- zerver/tests/test_subs.py | 8 +++--- zerver/tests/test_upload.py | 2 +- zilencer/auth.py | 2 +- zilencer/models.py | 12 ++++---- 18 files changed, 80 insertions(+), 82 deletions(-) diff --git a/analytics/lib/counts.py b/analytics/lib/counts.py index f901c65b50..f4b28668e3 100644 --- a/analytics/lib/counts.py +++ b/analytics/lib/counts.py @@ -62,7 +62,7 @@ class CountStat: else: self.interval = self.time_increment - def __str__(self) -> str: + def __repr__(self) -> str: return f"" def last_successful_fill(self) -> Optional[datetime]: diff --git a/analytics/models.py b/analytics/models.py index 76f7285178..46998c63bc 100644 --- a/analytics/models.py +++ b/analytics/models.py @@ -17,7 +17,7 @@ class FillState(models.Model): state = models.PositiveSmallIntegerField() def __str__(self) -> str: - return f"" + return f"{self.property} {self.end_time} {self.state}" # The earliest/starting end_time in FillState @@ -59,7 +59,7 @@ class InstallationCount(BaseCount): ] def __str__(self) -> str: - return f"" + return f"{self.property} {self.subgroup} {self.value}" class RealmCount(BaseCount): @@ -82,7 +82,7 @@ class RealmCount(BaseCount): index_together = ["property", "end_time"] def __str__(self) -> str: - return f"" + return f"{self.realm!r} {self.property} {self.subgroup} {self.value}" class UserCount(BaseCount): @@ -108,7 +108,7 @@ class UserCount(BaseCount): index_together = ["property", "realm", "end_time"] def __str__(self) -> str: - return f"" + return f"{self.user!r} {self.property} {self.subgroup} {self.value}" class StreamCount(BaseCount): @@ -134,6 +134,4 @@ class StreamCount(BaseCount): index_together = ["property", "realm", "end_time"] def __str__(self) -> str: - return ( - f"" - ) + return f"{self.stream!r} {self.property} {self.subgroup} {self.value} {self.id}" diff --git a/confirmation/models.py b/confirmation/models.py index 5d26039328..d1a43b70b9 100644 --- a/confirmation/models.py +++ b/confirmation/models.py @@ -180,7 +180,7 @@ class Confirmation(models.Model): type = models.PositiveSmallIntegerField() def __str__(self) -> str: - return f"" + return f"{self.content_object!r}" class Meta: unique_together = ("type", "confirmation_key") diff --git a/corporate/models.py b/corporate/models.py index c2c4df6430..5bab42b7eb 100644 --- a/corporate/models.py +++ b/corporate/models.py @@ -43,7 +43,7 @@ class Customer(models.Model): return is_cloud def __str__(self) -> str: - return f"" + return f"{self.realm!r} {self.stripe_customer_id}" def get_customer_by_realm(realm: Realm) -> Optional[Customer]: diff --git a/tools/coveragerc b/tools/coveragerc index 3ed6968a54..a31c7929d7 100644 --- a/tools/coveragerc +++ b/tools/coveragerc @@ -10,7 +10,7 @@ exclude_lines = # Don't require coverage for test suite AssertionError -- they're usually for clarity raise AssertionError # Don't require coverage for __str__ statements just used for printing - def __str__[(]self[)] -> .*: + def __(repr|str)__[(]self[)] -> .*: # Don't require coverage for errors about unsupported webhook event types raise UnsupportedWebhookEventTypeError # Don't require coverage for blocks only run when type-checking diff --git a/zerver/models.py b/zerver/models.py index d1c51c2a09..76d9a3fec5 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -801,7 +801,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub return ret def __str__(self) -> str: - return f"" + return f"{self.string_id} {self.id}" # `realm` instead of `self` here to make sure the parameters of the cache key # function matches the original method. @@ -1113,7 +1113,7 @@ class RealmEmoji(models.Model): STILL_PATH_ID_TEMPLATE = "{realm_id}/emoji/images/still/{emoji_filename_without_extension}.png" def __str__(self) -> str: - return f"" + return f"{self.realm.string_id}: {self.id} {self.name} {self.deactivated} {self.file_name}" class Meta: constraints = [ @@ -1316,7 +1316,7 @@ class RealmFilter(models.Model): ) def __str__(self) -> str: - return f"" + return f"{self.realm.string_id}: {self.pattern} {self.url_format_string}" def get_linkifiers_cache_key(realm_id: int) -> str: @@ -1413,7 +1413,7 @@ class RealmPlayground(models.Model): unique_together = (("realm", "pygments_language", "name"),) def __str__(self) -> str: - return f"" + return f"{self.realm.string_id}: {self.pygments_language} {self.name}" def get_realm_playgrounds(realm: Realm) -> List[RealmPlaygroundDict]: @@ -1479,7 +1479,7 @@ class Recipient(models.Model): def __str__(self) -> str: display_recipient = get_display_recipient(self) - return f"" + return f"{display_recipient} ({self.type_id}, {self.type})" class UserBaseSettings(models.Model): @@ -1977,7 +1977,7 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings): # type return False def __str__(self) -> str: - return f"" + return f"{self.email} {self.realm!r}" @property def is_provisional_member(self) -> bool: @@ -2562,7 +2562,7 @@ class Stream(models.Model): } def __str__(self) -> str: - return f"" + return self.name def is_public(self) -> bool: # All streams are private in Zephyr mirroring realms. @@ -2695,7 +2695,7 @@ class UserTopic(models.Model): ] def __str__(self) -> str: - return f"" + return f"({self.user_profile.email}, {self.stream.name}, {self.topic_name}, {self.last_updated})" class MutedUser(models.Model): @@ -2707,7 +2707,7 @@ class MutedUser(models.Model): unique_together = ("user_profile", "muted_user") def __str__(self) -> str: - return f" {self.muted_user.email}>" + return f"{self.user_profile.email} -> {self.muted_user.email}" post_save.connect(flush_muting_users_cache, sender=MutedUser) @@ -2719,7 +2719,7 @@ class Client(models.Model): name = models.CharField(max_length=MAX_NAME_LENGTH, db_index=True, unique=True) def __str__(self) -> str: - return f"" + return self.name get_client_cache: Dict[str, Client] = {} @@ -2905,7 +2905,7 @@ class AbstractMessage(models.Model): def __str__(self) -> str: display_recipient = get_display_recipient(self.recipient) - return f"<{type(self).__name__}: {display_recipient} / {self.subject} / {self.sender}>" + return f"{display_recipient} / {self.subject} / {self.sender!r}" class ArchiveTransaction(models.Model): @@ -2923,7 +2923,7 @@ class ArchiveTransaction(models.Model): realm = models.ForeignKey(Realm, null=True, on_delete=CASCADE) def __str__(self) -> str: - return "ArchiveTransaction id: {id}, type: {type}, realm: {realm}, timestamp: {timestamp}".format( + return "id: {id}, type: {type}, realm: {realm}, timestamp: {timestamp}".format( id=self.id, type="MANUAL" if self.type == self.MANUAL else "RETENTION_POLICY_BASED", realm=self.realm.string_id if self.realm else None, @@ -3094,7 +3094,7 @@ class Draft(models.Model): last_edit_time = models.DateTimeField(db_index=True) def __str__(self) -> str: - return f"<{type(self).__name__}: {self.user_profile.email} / {self.id} / {self.last_edit_time}>" + return f"{self.user_profile.email} / {self.id} / {self.last_edit_time}" def to_dict(self) -> Dict[str, Any]: if self.recipient is None: @@ -3391,7 +3391,7 @@ class UserMessage(AbstractUserMessage): def __str__(self) -> str: display_recipient = get_display_recipient(self.message.recipient) - return f"<{type(self).__name__}: {display_recipient} / {self.user_profile.email} ({self.flags_list()})>" + return f"{display_recipient} / {self.user_profile.email} ({self.flags_list()})" @staticmethod def select_for_update_query() -> QuerySet["UserMessage"]: @@ -3429,7 +3429,7 @@ class ArchivedUserMessage(AbstractUserMessage): def __str__(self) -> str: display_recipient = get_display_recipient(self.message.recipient) - return f"<{type(self).__name__}: {display_recipient} / {self.user_profile.email} ({self.flags_list()})>" + return f"{display_recipient} / {self.user_profile.email} ({self.flags_list()})" class AbstractAttachment(models.Model): @@ -3471,7 +3471,7 @@ class AbstractAttachment(models.Model): abstract = True def __str__(self) -> str: - return f"<{type(self).__name__}: {self.file_name}>" + return self.file_name class ArchivedAttachment(AbstractAttachment): @@ -3713,7 +3713,7 @@ class Subscription(models.Model): ] def __str__(self) -> str: - return f" {self.recipient}>" + return f"{self.user_profile!r} -> {self.recipient!r}" # Subscription fields included whenever a Subscription object is provided to # Zulip clients via the API. A few details worth noting: @@ -4208,7 +4208,7 @@ class ScheduledEmail(AbstractScheduledJob): type = models.PositiveSmallIntegerField() def __str__(self) -> str: - return f"" + return f"{self.type} {self.address or list(self.users.all())} {self.scheduled_timestamp}" class MissedMessageEmailAddress(models.Model): @@ -4295,7 +4295,7 @@ class ScheduledMessage(models.Model): def __str__(self) -> str: display_recipient = get_display_recipient(self.recipient) - return f"" + return f"{display_recipient} {self.subject} {self.sender!r} {self.scheduled_timestamp}" EMAIL_TYPES = { @@ -4471,10 +4471,10 @@ class RealmAuditLog(AbstractRealmAuditLog): def __str__(self) -> str: if self.modified_user is not None: - return f"" + return f"{self.modified_user!r} {self.event_type} {self.event_time} {self.id}" if self.modified_stream is not None: - return f"" - return f"" + return f"{self.modified_stream!r} {self.event_type} {self.event_time} {self.id}" + return f"{self.realm!r} {self.event_type} {self.event_time} {self.id}" class UserHotspot(models.Model): @@ -4623,7 +4623,7 @@ class CustomProfileField(models.Model): return False def __str__(self) -> str: - return f"" + return f"{self.realm!r} {self.name} {self.field_type} {self.order}" def custom_profile_fields_for_realm(realm_id: int) -> QuerySet[CustomProfileField]: @@ -4640,7 +4640,7 @@ class CustomProfileFieldValue(models.Model): unique_together = ("user_profile", "field") def __str__(self) -> str: - return f"" + return f"{self.user_profile!r} {self.field!r} {self.value}" # Interfaces for services diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index 0e327291ae..4ea6c8ed69 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -211,8 +211,8 @@ class TestRealmAuditLog(ZulipTestCase): event_type=RealmAuditLog.USER_EMAIL_CHANGED, event_time__gte=now ) self.assertTrue( - str(audit_entry).startswith( - f" {RealmAuditLog.USER_EMAIL_CHANGED} " + repr(audit_entry).startswith( + f" {RealmAuditLog.USER_EMAIL_CHANGED} " ) ) diff --git a/zerver/tests/test_external.py b/zerver/tests/test_external.py index 4ab4c14b69..8986564f32 100644 --- a/zerver/tests/test_external.py +++ b/zerver/tests/test_external.py @@ -435,7 +435,7 @@ class RateLimitTests(ZulipTestCase): self.assertEqual( m.output, [ - f"WARNING:zilencer.auth:Remote server exceeded rate limits on domain api_by_remote_server" + f"WARNING:zilencer.auth:Remote server exceeded rate limits on domain api_by_remote_server" ], ) finally: diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index fda73aab66..23637eeab0 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -1334,13 +1334,13 @@ class MarkdownTest(ZulipTestCase): ) def check_add_linkifiers( - self, linkifiers: List[RealmFilter], expected_linkifier_strs: List[str] + self, linkifiers: List[RealmFilter], expected_linkifier_reprs: List[str] ) -> None: - self.assert_length(linkifiers, len(expected_linkifier_strs)) - for linkifier, expected_linkifier_str in zip(linkifiers, expected_linkifier_strs): + self.assert_length(linkifiers, len(expected_linkifier_reprs)) + for linkifier, expected_linkifier_repr in zip(linkifiers, expected_linkifier_reprs): linkifier.clean() linkifier.save() - self.assertEqual(str(linkifier), expected_linkifier_str) + self.assertEqual(repr(linkifier), expected_linkifier_repr) def test_realm_patterns(self) -> None: realm = get_realm("zulip") @@ -1352,7 +1352,7 @@ class MarkdownTest(ZulipTestCase): url_format_string=r"https://trac.example.com/ticket/%(id)s", ) ], - ["[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>"], + ["[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>"], ) msg = Message(sender=self.example_user("othello")) @@ -1519,9 +1519,9 @@ class MarkdownTest(ZulipTestCase): ), ], [ - "ABC-[0-9]+) https://trac.example.com/ticket/%(id)s>", - "[A-Z][A-Z0-9]*-[0-9]+) https://other-trac.example.com/ticket/%(id)s>", - "[A-Z][A-Z0-9]+) https://yet-another-trac.example.com/ticket/%(id)s>", + "ABC-[0-9]+) https://trac.example.com/ticket/%(id)s>", + "[A-Z][A-Z0-9]*-[0-9]+) https://other-trac.example.com/ticket/%(id)s>", + "[A-Z][A-Z0-9]+) https://yet-another-trac.example.com/ticket/%(id)s>", ], ) @@ -1581,8 +1581,8 @@ class MarkdownTest(ZulipTestCase): ), ], [ - "", - r"ABC\-[0-9]+) https://trac.example.com/ticket/%(id)s>", + "", + r"ABC\-[0-9]+) https://trac.example.com/ticket/%(id)s>", ], ) @@ -1640,10 +1640,10 @@ class MarkdownTest(ZulipTestCase): ), ], [ - "", - "[a-z]+) http://example.com/b/%(id)s>", - "[a-z]+) b#(?P[a-z]+) http://example.com/a/%(aid)s/b/%(bid)s>", - "[a-z]+) http://example.com/a/%(id)s>", + "", + "[a-z]+) http://example.com/b/%(id)s>", + "[a-z]+) b#(?P[a-z]+) http://example.com/a/%(aid)s/b/%(bid)s>", + "[a-z]+) http://example.com/a/%(id)s>", ], ) # There should be 5 link matches in the topic, if ordered from the most priortized to the least: @@ -2431,8 +2431,8 @@ class MarkdownTest(ZulipTestCase): ) linkifier.save() self.assertEqual( - str(linkifier), - "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", + repr(linkifier), + "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", ) # Create a user that potentially interferes with the pattern. test_user = create_user( @@ -2519,8 +2519,8 @@ class MarkdownTest(ZulipTestCase): ) linkifier.save() self.assertEqual( - str(linkifier), - "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", + repr(linkifier), + "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", ) # Create a user-group that potentially interferes with the pattern. user_id = user_profile.id @@ -2777,8 +2777,8 @@ class MarkdownTest(ZulipTestCase): ) linkifier.save() self.assertEqual( - str(linkifier), - "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", + repr(linkifier), + "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", ) # Create a topic link that potentially interferes with the pattern. denmark = get_stream("Denmark", realm) @@ -2846,8 +2846,8 @@ class MarkdownTest(ZulipTestCase): ) linkifier.save() self.assertEqual( - str(linkifier), - "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", + repr(linkifier), + "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", ) # Create a stream that potentially interferes with the pattern. stream = self.make_stream(stream_name="Stream #1234", realm=realm) diff --git a/zerver/tests/test_message_dict.py b/zerver/tests/test_message_dict.py index 8bbc447bbf..d14f876f54 100644 --- a/zerver/tests/test_message_dict.py +++ b/zerver/tests/test_message_dict.py @@ -257,8 +257,8 @@ class MessageDictTest(ZulipTestCase): realm=zulip_realm, pattern=r"#(?P[0-9]{2,8})", url_format_string=url_format_string ) self.assertEqual( - str(linkifier), - "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", + repr(linkifier), + "[0-9]{2,8}) https://trac.example.com/ticket/%(id)s>", ) def get_message(sender: UserProfile, realm: Realm) -> Message: diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index fb86f97288..fef92c07b9 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -1645,9 +1645,9 @@ class StreamMessagesTest(ZulipTestCase): self.send_stream_message(sender, "Denmark", content="whatever", topic_name="my topic") message = most_recent_message(receiving_user_profile) self.assertEqual( - str(message), + repr(message), ">".format(sender.email, sender.realm), + ">".format(sender.email, sender.realm), ) def test_message_mentions(self) -> None: diff --git a/zerver/tests/test_presence.py b/zerver/tests/test_presence.py index 3d1d72768d..73089c0c2d 100644 --- a/zerver/tests/test_presence.py +++ b/zerver/tests/test_presence.py @@ -25,7 +25,7 @@ class TestClientModel(ZulipTestCase): This test is designed to cover __str__ method for Client. """ client = make_client("some_client") - self.assertEqual(str(client), "") + self.assertEqual(repr(client), "") class UserPresenceModelTests(ZulipTestCase): diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index 2c3b24fd0b..403dd22933 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -109,8 +109,8 @@ class RealmEmojiTest(ZulipTestCase): realm_emoji = RealmEmoji.objects.get(name="green_tick") file_name = str(realm_emoji.id) + ".png" self.assertEqual( - str(realm_emoji), - f"", + repr(realm_emoji), + f"", ) def test_upload_exception(self) -> None: diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index f80553a357..7b9cdffd36 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -356,14 +356,14 @@ class AddNewUserHistoryTest(ZulipTestCase): with patch("zerver.models.get_display_recipient", return_value="recip"): self.assertEqual( - str(message), + repr(message), ">".format(user_profile.email, user_profile.realm), + ">".format(user_profile.email, user_profile.realm), ) user_message = most_recent_usermessage(user_profile) self.assertEqual( - str(user_message), + repr(user_message), f"", ) diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index e2455f2fa3..3f97f860a3 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -552,7 +552,7 @@ class RecipientTest(ZulipTestCase): type_id=stream.id, type=Recipient.STREAM, ) - self.assertEqual(str(recipient), f"") + self.assertEqual(repr(recipient), f"") class StreamAdminTest(ZulipTestCase): @@ -5111,11 +5111,11 @@ class SubscriptionAPITest(ZulipTestCase): ) subscription = self.get_subscription(user_profile, invite_streams[0]) - with mock.patch("zerver.models.Recipient.__str__", return_value="recip"): + with mock.patch("zerver.models.Recipient.__repr__", return_value="recip"): self.assertEqual( - str(subscription), + repr(subscription), " -> recip>", + f" -> recip>", ) self.assertIsNone(subscription.desktop_notifications) diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index 2575d9438b..22216739ec 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -390,7 +390,7 @@ class FileUploadTest(UploadSerializeMixin, ZulipTestCase): d1_attachment = Attachment.objects.get(path_id=d1_path_id) d1_attachment.create_time = two_week_ago d1_attachment.save() - self.assertEqual(str(d1_attachment), "") + self.assertEqual(repr(d1_attachment), "") # This Attachment won't have any messages. d2_attachment = Attachment.objects.get(path_id=d2_path_id) d2_attachment.create_time = two_week_ago diff --git a/zilencer/auth.py b/zilencer/auth.py index 34786987f7..2d7f093694 100644 --- a/zilencer/auth.py +++ b/zilencer/auth.py @@ -61,7 +61,7 @@ def rate_limit_remote_server( try: RateLimitedRemoteZulipServer(remote_server, domain=domain).rate_limit_request(request) except RateLimitedError as e: - logger.warning("Remote server %s exceeded rate limits on domain %s", remote_server, domain) + logger.warning("Remote server %r exceeded rate limits on domain %s", remote_server, domain) raise e diff --git a/zilencer/models.py b/zilencer/models.py index 910ac0c287..611a2f073b 100644 --- a/zilencer/models.py +++ b/zilencer/models.py @@ -50,7 +50,7 @@ class RemoteZulipServer(models.Model): plan_type = models.PositiveSmallIntegerField(default=PLAN_TYPE_SELF_HOSTED) def __str__(self) -> str: - return f"" + return f"{self.hostname} {str(self.uuid)[0:12]}" def format_requestor_for_logs(self) -> str: return "zulip-server:" + str(self.uuid) @@ -75,7 +75,7 @@ class RemotePushDeviceToken(AbstractPushDeviceToken): ] def __str__(self) -> str: - return f"" + return f"{self.server!r} {self.user_id}" class RemoteZulipServerAuditLog(AbstractRealmAuditLog): @@ -91,7 +91,7 @@ class RemoteZulipServerAuditLog(AbstractRealmAuditLog): server = models.ForeignKey(RemoteZulipServer, on_delete=models.CASCADE) def __str__(self) -> str: - return f"" + return f"{self.server!r} {self.event_type} {self.event_time} {self.id}" class RemoteRealmAuditLog(AbstractRealmAuditLog): @@ -105,7 +105,7 @@ class RemoteRealmAuditLog(AbstractRealmAuditLog): remote_id = models.IntegerField(db_index=True) def __str__(self) -> str: - return f"" + return f"{self.server!r} {self.event_type} {self.event_time} {self.id}" class RemoteInstallationCount(BaseCount): @@ -120,7 +120,7 @@ class RemoteInstallationCount(BaseCount): ] def __str__(self) -> str: - return f"" + return f"{self.property} {self.subgroup} {self.value}" # We can't subclass RealmCount because we only have a realm_id here, not a foreign key. @@ -138,7 +138,7 @@ class RemoteRealmCount(BaseCount): ] def __str__(self) -> str: - return f"{self.server} {self.realm_id} {self.property} {self.subgroup} {self.value}" + return f"{self.server!r} {self.realm_id} {self.property} {self.subgroup} {self.value}" class RateLimitedRemoteZulipServer(RateLimitedObject):