diff --git a/pyproject.toml b/pyproject.toml index 6c77dfd1e7..50b2beae5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,6 @@ module = [ "bitfield.*", "bmemcached.*", "circuitbreaker.*", - "defusedxml.*", # https://github.com/tiran/defusedxml/issues/46 "digitalocean.*", "django_auth_ldap.*", "django_bmemcached.*", diff --git a/requirements/dev.txt b/requirements/dev.txt index 979b2a0173..9f70872a8f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3236,6 +3236,10 @@ types-decorator==5.1.8.20240310 \ --hash=sha256:3af75dc38f5baf65b9b53ea6661ce2056c5ca7d70d620d0b1f620285c1242757 \ --hash=sha256:52e316b03783886a8a2abdc228f7071680ba65894545cd2085ebe3cf88684a0e # via -r requirements/mypy.in +types-defusedxml==0.7.0.20240218 \ + --hash=sha256:05688a7724dc66ea74c4af5ca0efc554a150c329cb28c13a64902cab878d06ed \ + --hash=sha256:2b7f3c5ca14fdbe728fab0b846f5f7eb98c4bd4fd2b83d25f79e923caa790ced + # via -r requirements/mypy.in types-docutils==0.21.0.20241005 \ --hash=sha256:48f804a2b50da3a1b1681c4ca1b6184416a6e4129e302d15c44e9d97c59b3365 \ --hash=sha256:4d9021422f2f3fca8b0726fb8949395f66a06c0d951479eb3b1387d75b134430 diff --git a/requirements/mypy.in b/requirements/mypy.in index 85499a7362..ee9f6e4c34 100644 --- a/requirements/mypy.in +++ b/requirements/mypy.in @@ -11,6 +11,7 @@ types-beautifulsoup4 types-boto types-chardet types-decorator +types-defusedxml types-jsonschema types-Markdown types-oauthlib diff --git a/version.py b/version.py index ae6e33ed87..f8a8060479 100644 --- a/version.py +++ b/version.py @@ -51,4 +51,4 @@ API_FEATURE_LEVEL = ( # historical commits sharing the same major version, in which case a # minor version bump suffices. -PROVISION_VERSION = (302, 0) # bumped 2024-11-18 to upgrade Python requirements +PROVISION_VERSION = (303, 0) # bumped 2024-11-18 for types-defusedxml diff --git a/zerver/views/video_calls.py b/zerver/views/video_calls.py index 1e231af4bd..07799819a8 100644 --- a/zerver/views/video_calls.py +++ b/zerver/views/video_calls.py @@ -32,6 +32,7 @@ from zerver.lib.response import json_success from zerver.lib.subdomains import get_subdomain from zerver.lib.typed_endpoint import typed_endpoint, typed_endpoint_without_parameters from zerver.lib.url_encoding import append_url_query_string +from zerver.lib.utils import assert_is_not_none from zerver.models import UserProfile from zerver.models.realms import get_realm @@ -276,10 +277,10 @@ def join_bigbluebutton(request: HttpRequest, *, bigbluebutton: str) -> HttpRespo raise JsonableError(_("Error connecting to the BigBlueButton server.")) payload = ElementTree.fromstring(response.text) - if payload.find("messageKey").text == "checksumError": + if assert_is_not_none(payload.find("messageKey")).text == "checksumError": raise JsonableError(_("Error authenticating to the BigBlueButton server.")) - if payload.find("returncode").text != "SUCCESS": + if assert_is_not_none(payload.find("returncode")).text != "SUCCESS": raise JsonableError(_("BigBlueButton server returned an unexpected error.")) join_params = urlencode( @@ -297,7 +298,7 @@ def join_bigbluebutton(request: HttpRequest, *, bigbluebutton: str) -> HttpRespo # createTime parameter will be created, as the meeting on # the BigBlueButton server has to be recreated. (after a # few minutes) - "createTime": payload.find("createTime").text, + "createTime": assert_is_not_none(payload.find("createTime")).text, }, quote_via=quote, ) diff --git a/zerver/webhooks/pivotal/view.py b/zerver/webhooks/pivotal/view.py index 8af5edcc2b..414fadb80b 100644 --- a/zerver/webhooks/pivotal/view.py +++ b/zerver/webhooks/pivotal/view.py @@ -12,6 +12,7 @@ from zerver.decorator import webhook_view from zerver.lib.exceptions import JsonableError, UnsupportedWebhookEventTypeError from zerver.lib.response import json_success from zerver.lib.typed_endpoint import typed_endpoint_without_parameters +from zerver.lib.utils import assert_is_not_none from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import UserProfile @@ -21,16 +22,18 @@ def api_pivotal_webhook_v3(request: HttpRequest, user_profile: UserProfile) -> t def get_text(attrs: list[str]) -> str: start = payload - try: - for attr in attrs: - start = start.find(attr) - return start.text - except AttributeError: - return "" + for attr in attrs: + child = start.find(attr) + if child is None: + return "" + start = child + assert start.text is not None + return start.text - event_type = payload.find("event_type").text - description = payload.find("description").text - project_id = payload.find("project_id").text + event_type = assert_is_not_none(payload.find("event_type")).text + description = assert_is_not_none(payload.find("description")).text + assert description is not None + project_id = assert_is_not_none(payload.find("project_id")).text story_id = get_text(["stories", "story", "id"]) # Ugh, the URL in the XML data is not a clickable URL that works for the user # so we try to build one that the user can actually click on