python: Clean up getattr, setattr, delattr calls with literal names.

These were useful as a transitional workaround to ignore type errors
that only show up with django-stubs, while avoiding errors about
unused type: ignore comments without django-stubs.  Now that the
django-stubs transition is complete, switch to type: ignore comments
so that mypy will tell us if they become unnecessary.  Many already
have.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-10-07 21:10:17 -07:00 committed by Tim Abbott
parent 5e1ebf2861
commit 1385a827c2
17 changed files with 50 additions and 55 deletions

View File

@ -368,7 +368,7 @@ def webhook_view(
# Store the event types registered for this webhook as an attribute, which can be access # Store the event types registered for this webhook as an attribute, which can be access
# later conveniently in zerver.lib.test_classes.WebhookTestCase. # later conveniently in zerver.lib.test_classes.WebhookTestCase.
setattr(_wrapped_func_arguments, "_all_event_types", all_event_types) _wrapped_func_arguments._all_event_types = all_event_types # type: ignore[attr-defined] # custom attribute
return _wrapped_func_arguments return _wrapped_func_arguments
return _wrapped_view_func return _wrapped_view_func
@ -793,7 +793,7 @@ def process_as_post(
if not request.POST: if not request.POST:
# Only take action if POST is empty. # Only take action if POST is empty.
if request.content_type == "multipart/form-data": if request.content_type == "multipart/form-data":
POST, _files = MultiPartParser( POST, files = MultiPartParser(
request.META, request.META,
BytesIO(request.body), BytesIO(request.body),
request.upload_handlers, request.upload_handlers,
@ -809,10 +809,7 @@ def process_as_post(
# See also: https://github.com/typeddjango/django-stubs/pull/925#issue-1206399444 # See also: https://github.com/typeddjango/django-stubs/pull/925#issue-1206399444
POST._mutable = False POST._mutable = False
request.POST = cast("_ImmutableQueryDict", POST) request.POST = cast("_ImmutableQueryDict", POST)
# Note that request._files is just the private attribute that backs the request.FILES.update(files)
# FILES property, so we are essentially setting request.FILES here. (In
# Django 3.2 FILES was still a read-only property.)
setattr(request, "_files", _files)
elif request.content_type == "application/x-www-form-urlencoded": elif request.content_type == "application/x-www-form-urlencoded":
request.POST = QueryDict(request.body, encoding=request.encoding) request.POST = QueryDict(request.body, encoding=request.encoding)

View File

@ -43,8 +43,8 @@ def copy_default_settings(
target_profile.save() target_profile.save()
return return
setattr(target_profile, "full_name", settings_source.full_name) target_profile.full_name = settings_source.full_name
setattr(target_profile, "timezone", canonicalize_timezone(settings_source.timezone)) target_profile.timezone = canonicalize_timezone(settings_source.timezone)
target_profile.save() target_profile.save()
if settings_source.avatar_source == UserProfile.AVATAR_FROM_USER: if settings_source.avatar_source == UserProfile.AVATAR_FROM_USER:

View File

@ -892,7 +892,7 @@ def import_uploads(
process_avatars(record) process_avatars(record)
else: else:
connection.close() connection.close()
_cache = getattr(cache, "_cache") _cache = cache._cache # type: ignore[attr-defined] # not in stubs
assert isinstance(_cache, bmemcached.Client) assert isinstance(_cache, bmemcached.Client)
_cache.disconnect_all() _cache.disconnect_all()
with ProcessPoolExecutor(max_workers=processes) as executor: with ProcessPoolExecutor(max_workers=processes) as executor:

View File

@ -197,12 +197,10 @@ class ZulipFormatter(logging.Formatter):
return " ".join(pieces) return " ".join(pieces)
def format(self, record: logging.LogRecord) -> str: def format(self, record: logging.LogRecord) -> str:
if not getattr(record, "zulip_decorated", False): if not hasattr(record, "zulip_decorated"):
# The `setattr` calls put this logic explicitly outside the bounds of the record.zulip_level_abbrev = abbrev_log_levelname(record.levelname)
# type system; otherwise mypy would complain LogRecord lacks these attributes. record.zulip_origin = find_log_origin(record)
setattr(record, "zulip_level_abbrev", abbrev_log_levelname(record.levelname)) record.zulip_decorated = True
setattr(record, "zulip_origin", find_log_origin(record))
setattr(record, "zulip_decorated", True)
return super().format(record) return super().format(record)
@ -227,12 +225,12 @@ class ZulipWebhookFormatter(ZulipFormatter):
request = get_current_request() request = get_current_request()
if not request: if not request:
setattr(record, "user", None) record.user = None
setattr(record, "client", None) record.client = None
setattr(record, "url", None) record.url = None
setattr(record, "content_type", None) record.content_type = None
setattr(record, "custom_headers", None) record.custom_headers = None
setattr(record, "payload", None) record.payload = None
return super().format(record) return super().format(record)
if request.content_type == "application/json": if request.content_type == "application/json":
@ -257,12 +255,12 @@ class ZulipWebhookFormatter(ZulipFormatter):
assert client is not None assert client is not None
assert request.user.is_authenticated assert request.user.is_authenticated
setattr(record, "user", f"{request.user.delivery_email} ({request.user.realm.string_id})") record.user = f"{request.user.delivery_email} ({request.user.realm.string_id})"
setattr(record, "client", client.name) record.client = client.name
setattr(record, "url", request.META.get("PATH_INFO", None)) record.url = request.META.get("PATH_INFO", None)
setattr(record, "content_type", request.content_type) record.content_type = request.content_type
setattr(record, "custom_headers", header_text or None) record.custom_headers = header_text or None
setattr(record, "payload", payload) record.payload = payload
return super().format(record) return super().format(record)

View File

@ -474,9 +474,9 @@ def get_current_request() -> Optional[HttpRequest]:
def set_request(req: HttpRequest) -> None: def set_request(req: HttpRequest) -> None:
setattr(local, "request", req) local.request = req
def unset_request() -> None: def unset_request() -> None:
if hasattr(local, "request"): if hasattr(local, "request"):
delattr(local, "request") del local.request

View File

@ -394,7 +394,7 @@ def instrument_url(f: UrlFuncT) -> UrlFuncT:
info = "<bytes>" info = "<bytes>"
elif isinstance(info, dict): elif isinstance(info, dict):
info = { info = {
k: "<file object>" if hasattr(v, "read") and callable(getattr(v, "read")) else v k: "<file object>" if hasattr(v, "read") and callable(v.read) else v
for k, v in info.items() for k, v in info.items()
} }

View File

@ -41,7 +41,7 @@ def transfer_avatars_to_s3(processes: int) -> None:
_transfer_avatar_to_s3(user) _transfer_avatar_to_s3(user)
else: # nocoverage else: # nocoverage
connection.close() connection.close()
_cache = getattr(cache, "_cache") _cache = cache._cache # type: ignore[attr-defined] # not in stubs
assert isinstance(_cache, bmemcached.Client) assert isinstance(_cache, bmemcached.Client)
_cache.disconnect_all() _cache.disconnect_all()
with ProcessPoolExecutor(max_workers=processes) as executor: with ProcessPoolExecutor(max_workers=processes) as executor:
@ -76,7 +76,7 @@ def transfer_message_files_to_s3(processes: int) -> None:
_transfer_message_files_to_s3(attachment) _transfer_message_files_to_s3(attachment)
else: # nocoverage else: # nocoverage
connection.close() connection.close()
_cache = getattr(cache, "_cache") _cache = cache._cache # type: ignore[attr-defined] # not in stubs
assert isinstance(_cache, bmemcached.Client) assert isinstance(_cache, bmemcached.Client)
_cache.disconnect_all() _cache.disconnect_all()
with ProcessPoolExecutor(max_workers=processes) as executor: with ProcessPoolExecutor(max_workers=processes) as executor:
@ -111,7 +111,7 @@ def transfer_emoji_to_s3(processes: int) -> None:
_transfer_emoji_to_s3(realm_emoji) _transfer_emoji_to_s3(realm_emoji)
else: # nocoverage else: # nocoverage
connection.close() connection.close()
_cache = getattr(cache, "_cache") _cache = cache._cache # type: ignore[attr-defined] # not in stubs
assert isinstance(_cache, bmemcached.Client) assert isinstance(_cache, bmemcached.Client)
_cache.disconnect_all() _cache.disconnect_all()
with ProcessPoolExecutor(max_workers=processes) as executor: with ProcessPoolExecutor(max_workers=processes) as executor:

View File

@ -192,7 +192,7 @@ def get_fixture_http_headers(integration_name: str, fixture_name: str) -> Dict["
# TODO: We may want to migrate to a more explicit registration # TODO: We may want to migrate to a more explicit registration
# strategy for this behavior rather than a try/except import. # strategy for this behavior rather than a try/except import.
view_module = importlib.import_module(view_module_name) view_module = importlib.import_module(view_module_name)
fixture_to_headers = getattr(view_module, "fixture_to_headers") fixture_to_headers = view_module.fixture_to_headers
except (ImportError, AttributeError): except (ImportError, AttributeError):
return {} return {}
return fixture_to_headers(fixture_name) return fixture_to_headers(fixture_name)

View File

@ -228,9 +228,8 @@ class Command(makemessages.Command):
process_all = self.frontend_all process_all = self.frontend_all
# After calling super().handle(), default_locale_path gets set on self # After calling super().handle(), default_locale_path gets set on self
# so that we can reuse it here. We have to use getattr it to access it without # so that we can reuse it here.
# getting an attribute error from mypy. default_locale_path = self.default_locale_path # type: ignore[attr-defined] # not in stubs
default_locale_path = getattr(self, "default_locale_path")
paths = glob.glob(f"{default_locale_path}/*") paths = glob.glob(f"{default_locale_path}/*")
all_locales = [os.path.basename(path) for path in paths if os.path.isdir(path)] all_locales = [os.path.basename(path) for path in paths if os.path.isdir(path)]

View File

@ -774,7 +774,7 @@ class ZulipSCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware):
# so we can assign the corresponding SCIMClient object to request.user - which # so we can assign the corresponding SCIMClient object to request.user - which
# will allow this request to pass request.user.is_authenticated checks from now on, # will allow this request to pass request.user.is_authenticated checks from now on,
# to be served by the relevant views implemented in django-scim2. # to be served by the relevant views implemented in django-scim2.
# Since request.user must be a UserProfile or AnonymousUser, setattr is a type-unsafe # Since request.user must be a UserProfile or AnonymousUser, this is a type-unsafe
# workaround to make this monkey-patching work. # workaround to make this monkey-patching work.
setattr(request, "user", scim_client) request.user = scim_client # type: ignore[assignment] # wrong type
return None return None

View File

@ -3245,22 +3245,20 @@ class AbstractUserMessage(models.Model):
@staticmethod @staticmethod
def where_unread() -> str: def where_unread() -> str:
return AbstractUserMessage.where_flag_is_absent(getattr(AbstractUserMessage.flags, "read")) return AbstractUserMessage.where_flag_is_absent(AbstractUserMessage.flags.read)
@staticmethod @staticmethod
def where_read() -> str: def where_read() -> str:
return AbstractUserMessage.where_flag_is_present(getattr(AbstractUserMessage.flags, "read")) return AbstractUserMessage.where_flag_is_present(AbstractUserMessage.flags.read)
@staticmethod @staticmethod
def where_starred() -> str: def where_starred() -> str:
return AbstractUserMessage.where_flag_is_present( return AbstractUserMessage.where_flag_is_present(AbstractUserMessage.flags.starred)
getattr(AbstractUserMessage.flags, "starred")
)
@staticmethod @staticmethod
def where_active_push_notification() -> str: def where_active_push_notification() -> str:
return AbstractUserMessage.where_flag_is_present( return AbstractUserMessage.where_flag_is_present(
getattr(AbstractUserMessage.flags, "active_mobile_push_notification") AbstractUserMessage.flags.active_mobile_push_notification
) )
def flags_list(self) -> List[str]: def flags_list(self) -> List[str]:

View File

@ -542,7 +542,7 @@ class PreviewTestCase(ZulipTestCase):
self.assertEqual(msg.rendered_content, with_preview) self.assertEqual(msg.rendered_content, with_preview)
realm = msg.get_realm() realm = msg.get_realm()
setattr(realm, "inline_url_embed_preview", False) realm.inline_url_embed_preview = False
realm.save() realm.save()
msg = self._send_message_with_test_org_url( msg = self._send_message_with_test_org_url(

View File

@ -240,7 +240,7 @@ class AdminNotifyHandlerTest(ZulipTestCase):
self.assertEqual(report["stack_trace"], "No stack trace available") self.assertEqual(report["stack_trace"], "No stack trace available")
# Test arbitrary exceptions from request.user # Test arbitrary exceptions from request.user
delattr(record.request, "user") del record.request.user
with patch("zerver.logging_handlers.traceback.print_exc"): with patch("zerver.logging_handlers.traceback.print_exc"):
report = self.run_handler(record) report = self.run_handler(record)
self.assertIn("host", report) self.assertIn("host", report)

View File

@ -623,7 +623,7 @@ class MarkdownTest(ZulipTestCase):
self.assertEqual(converted.rendered_content, with_preview) self.assertEqual(converted.rendered_content, with_preview)
realm = msg.get_realm() realm = msg.get_realm()
setattr(realm, "inline_image_preview", False) realm.inline_image_preview = False
realm.save() realm.save()
sender_user_profile = self.example_user("othello") sender_user_profile = self.example_user("othello")

View File

@ -422,8 +422,8 @@ do not match the types declared in the implementation of {function.__name__}.\n"
# Iterate through the decorators to find the original # Iterate through the decorators to find the original
# function, wrapped by has_request_variables, so we can parse # function, wrapped by has_request_variables, so we can parse
# its arguments. # its arguments.
while hasattr(function, "__wrapped__"): while (wrapped := getattr(function, "__wrapped__", None)) is not None:
function = getattr(function, "__wrapped__") function = wrapped
# Now, we do inference mapping each REQ parameter's # Now, we do inference mapping each REQ parameter's
# declaration details to the Python/mypy types for the # declaration details to the Python/mypy types for the

View File

@ -12,7 +12,8 @@ from django.contrib.auth.views import PasswordResetConfirmView
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail.message import EmailMultiAlternatives from django.core.mail.message import EmailMultiAlternatives
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse, HttpResponseBase
from django.template.response import TemplateResponse
from django.test import Client, override_settings from django.test import Client, override_settings
from django.urls import reverse from django.urls import reverse
from django.utils import translation from django.utils import translation
@ -198,7 +199,7 @@ class DeactivationNoticeTestCase(ZulipTestCase):
realm.save(update_fields=["deactivated"]) realm.save(update_fields=["deactivated"])
result = self.client_get("/login/", follow=True) result = self.client_get("/login/", follow=True)
self.assertEqual(getattr(result, "redirect_chain")[-1], ("/accounts/deactivated/", 302)) self.assertEqual(result.redirect_chain[-1], ("/accounts/deactivated/", 302))
self.assertIn("Zulip Dev, has been deactivated.", result.content.decode()) self.assertIn("Zulip Dev, has been deactivated.", result.content.decode())
self.assertNotIn("It has moved to", result.content.decode()) self.assertNotIn("It has moved to", result.content.decode())
@ -1080,10 +1081,12 @@ class LoginTest(ZulipTestCase):
description = "https://www.google.com/images/srpr/logo4w.png" description = "https://www.google.com/images/srpr/logo4w.png"
realm.description = description realm.description = description
realm.save(update_fields=["description"]) realm.save(update_fields=["description"])
response = self.client_get("/login/") response: HttpResponseBase = self.client_get("/login/")
expected_response = """<p><a href="https://www.google.com/images/srpr/logo4w.png">\ expected_response = """<p><a href="https://www.google.com/images/srpr/logo4w.png">\
https://www.google.com/images/srpr/logo4w.png</a></p>""" https://www.google.com/images/srpr/logo4w.png</a></p>"""
self.assertEqual(getattr(response, "context_data")["realm_description"], expected_response) assert isinstance(response, TemplateResponse)
assert response.context_data is not None
self.assertEqual(response.context_data["realm_description"], expected_response)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@ -15,6 +15,6 @@ class Command(BaseCommand):
def handle(self, *args: Any, **options: Any) -> None: def handle(self, *args: Any, **options: Any) -> None:
assert settings.DEVELOPMENT assert settings.DEVELOPMENT
UserMessage.objects.all().update(flags=F("flags").bitand(~UserMessage.flags.read)) UserMessage.objects.all().update(flags=F("flags").bitand(~UserMessage.flags.read))
_cache = getattr(cache, "_cache") _cache = cache._cache # type: ignore[attr-defined] # not in stubs
assert isinstance(_cache, bmemcached.Client) assert isinstance(_cache, bmemcached.Client)
_cache.flush_all() _cache.flush_all()