diff --git a/pyproject.toml b/pyproject.toml index aa51dd53f4..50b154762f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,7 +159,7 @@ prod = [ "pyuca", # Handle connection retries with exponential backoff - "backoff", + "tenacity", # Needed for reading bson files in rocketchat import tool "pymongo", diff --git a/uv.lock b/uv.lock index 16cd4c9f47..22717a8d42 100644 --- a/uv.lock +++ b/uv.lock @@ -370,15 +370,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - [[package]] name = "backports-datetime-fromisoformat" version = "2.0.3" @@ -5350,6 +5341,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/be/5d2d47b1fb58943194fb59dcf222f7c4e35122ec0ffe8c36e18b5d728f0b/tblib-3.2.2-py3-none-any.whl", hash = "sha256:26bdccf339bcce6a88b2b5432c988b266ebbe63a4e593f6b578b1d2e723d2b76", size = 12893, upload-time = "2025-11-12T12:21:14.407Z" }, ] +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + [[package]] name = "tiktoken" version = "0.12.0" @@ -6523,7 +6523,6 @@ dev = [ { name = "altcha" }, { name = "annotated-types" }, { name = "asgiref" }, - { name = "backoff" }, { name = "backports-datetime-fromisoformat", marker = "python_full_version < '3.11'" }, { name = "beautifulsoup4" }, { name = "black" }, @@ -6621,6 +6620,7 @@ dev = [ { name = "stripe" }, { name = "talon-core" }, { name = "tblib" }, + { name = "tenacity" }, { name = "time-machine" }, { name = "tlds" }, { name = "tornado" }, @@ -6671,7 +6671,6 @@ prod = [ { name = "altcha" }, { name = "annotated-types" }, { name = "asgiref" }, - { name = "backoff" }, { name = "backports-datetime-fromisoformat", marker = "python_full_version < '3.11'" }, { name = "beautifulsoup4" }, { name = "boto3" }, @@ -6739,6 +6738,7 @@ prod = [ { name = "sqlalchemy" }, { name = "stripe" }, { name = "talon-core" }, + { name = "tenacity" }, { name = "tlds" }, { name = "tornado" }, { name = "typing-extensions" }, @@ -6767,7 +6767,6 @@ dev = [ { name = "altcha" }, { name = "annotated-types" }, { name = "asgiref" }, - { name = "backoff" }, { name = "backports-datetime-fromisoformat", marker = "python_full_version < '3.11'" }, { name = "beautifulsoup4" }, { name = "black" }, @@ -6867,6 +6866,7 @@ dev = [ { name = "stripe" }, { name = "talon-core", git = "https://github.com/zulip/talon.git?subdirectory=talon-core&rev=e87a64dccc3c5ee1b8ea157d4b6e15ecd46f2bed" }, { name = "tblib" }, + { name = "tenacity" }, { name = "time-machine" }, { name = "tlds" }, { name = "tornado" }, @@ -6917,7 +6917,6 @@ prod = [ { name = "altcha" }, { name = "annotated-types" }, { name = "asgiref" }, - { name = "backoff" }, { name = "backports-datetime-fromisoformat", marker = "python_full_version < '3.11'" }, { name = "beautifulsoup4" }, { name = "boto3" }, @@ -6985,6 +6984,7 @@ prod = [ { name = "sqlalchemy", specifier = "==1.4.*" }, { name = "stripe" }, { name = "talon-core", git = "https://github.com/zulip/talon.git?subdirectory=talon-core&rev=e87a64dccc3c5ee1b8ea157d4b6e15ecd46f2bed" }, + { name = "tenacity" }, { name = "tlds" }, { name = "tornado" }, { name = "typing-extensions" }, diff --git a/version.py b/version.py index 84d4dcf82f..7e1018d934 100644 --- a/version.py +++ b/version.py @@ -48,4 +48,4 @@ API_FEATURE_LEVEL = 493 # historical commits sharing the same major version, in which case a # minor version bump suffices. -PROVISION_VERSION = (376, 2) # bumped 2026-04-22 to add @types/postcss-import +PROVISION_VERSION = (377, 0) # bumped 2026-04-22 to remove backoff diff --git a/zproject/email_backends.py b/zproject/email_backends.py index 66393b2a5d..1bcc440cf2 100644 --- a/zproject/email_backends.py +++ b/zproject/email_backends.py @@ -6,7 +6,7 @@ from collections.abc import Sequence from email.message import Message from typing import Any -import backoff +import tenacity from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.core.mail.backends.smtp import EmailBackend @@ -21,14 +21,19 @@ MAX_CONNECTION_TRIES = 3 # errors (ConnectionError, TimeoutError) and SMTPServerDisconnected # (dropped connection), but not on SMTP protocol errors like # SMTPAuthenticationError, SMTPRecipientsRefused, or SMTPDataError. -smtp_connection_backoff = backoff.on_exception( - backoff.expo, - OSError, - max_tries=MAX_CONNECTION_TRIES, - logger=None, - giveup=lambda e: ( - isinstance(e, smtplib.SMTPException) and not isinstance(e, smtplib.SMTPServerDisconnected) +smtp_connection_backoff = tenacity.retry( + wait=tenacity.wait_exponential_jitter(), + retry=tenacity.retry_if_exception( + lambda exc: ( + isinstance(exc, OSError) + and ( + not isinstance(exc, smtplib.SMTPException) + or isinstance(exc, smtplib.SMTPServerDisconnected) + ) + ) ), + stop=tenacity.stop_after_attempt(MAX_CONNECTION_TRIES), + reraise=True, )