From 14a70f5b4bf28c4fea25a5007af08f9d3b87db85 Mon Sep 17 00:00:00 2001 From: Peng Ren Date: Sat, 18 Apr 2026 18:27:46 -0400 Subject: [PATCH 1/4] Disable retry by default --- pymongosql/retry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongosql/retry.py b/pymongosql/retry.py index 39b9241..e8fd4d3 100644 --- a/pymongosql/retry.py +++ b/pymongosql/retry.py @@ -19,14 +19,14 @@ @dataclass(frozen=True) class RetryConfig: - enabled: bool = True + enabled: bool = False attempts: int = 3 wait_min: float = 0.1 wait_max: float = 1.0 @classmethod def from_kwargs(cls, kwargs: dict) -> "RetryConfig": - enabled = bool(kwargs.pop("retry_enabled", True)) + enabled = bool(kwargs.pop("retry_enabled", False)) attempts = int(kwargs.pop("retry_attempts", 3)) wait_min = float(kwargs.pop("retry_wait_min", 0.1)) wait_max = float(kwargs.pop("retry_wait_max", 1.0)) From b6971deced4837427106b8afcf7ae81254b51b81 Mon Sep 17 00:00:00 2001 From: Peng Ren Date: Sat, 18 Apr 2026 18:28:33 -0400 Subject: [PATCH 2/4] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1dcb7f..378e31b 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,7 @@ PyMongoSQL supports retrying transient, system-level MongoDB failures (for examp ```python connection = connect( host="mongodb://localhost:27017/database", - retry_enabled=True, # default: True + retry_enabled=False, # default: False retry_attempts=3, # default: 3 retry_wait_min=0.1, # default: 0.1 seconds retry_wait_max=1.0, # default: 1.0 seconds From 3594dba750601314b202e9a2a1b1a9e73f150079 Mon Sep 17 00:00:00 2001 From: Peng Ren Date: Sat, 18 Apr 2026 18:56:12 -0400 Subject: [PATCH 3/4] Make tenacity as a optional dependency --- README.md | 12 ++++++----- pymongosql/retry.py | 15 +++++++++++++- pyproject.toml | 2 +- requirements-optional.txt | 3 +++ requirements.txt | 3 +-- tests/test_retry.py | 42 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 378e31b..a28c883 100644 --- a/README.md +++ b/README.md @@ -49,13 +49,15 @@ PyMongoSQL implements the DB API 2.0 interfaces to provide SQL-like access to Mo - **JMESPath** (JSON/Dict Path Query) - jmespath >= 1.0.0 -- **Tenacity** (Transient Failure Retry) - - tenacity >= 9.0.0 - ### Optional Dependencies +- **Tenacity** (Transient Failure Retry) + - tenacity >= 9.0.0 + - Install with: `pip install pymongosql[retry]` + - **SQLAlchemy** (for ORM/Core support) - sqlalchemy >= 1.4.0 (SQLAlchemy 1.4+ and 2.0+ supported) + - Install with: `pip install pymongosql[sqlalchemy]` ## Installation @@ -212,12 +214,12 @@ Parameters are substituted into the MongoDB filter during execution, providing p ### Retry on Transient System Errors -PyMongoSQL supports retrying transient, system-level MongoDB failures (for example connection timeout and reconnect errors) using Tenacity. +PyMongoSQL supports retrying transient, system-level MongoDB failures (for example connection timeout and reconnect errors) using [Tenacity](https://github.com/jd/tenacity). This feature requires the optional `tenacity` package — install it with `pip install pymongosql[retry]`. If retry is enabled but tenacity is not installed, operations will proceed without retry. ```python connection = connect( host="mongodb://localhost:27017/database", - retry_enabled=False, # default: False + retry_enabled=False, # default: False retry_attempts=3, # default: 3 retry_wait_min=0.1, # default: 0.1 seconds retry_wait_max=1.0, # default: 1.0 seconds diff --git a/pymongosql/retry.py b/pymongosql/retry.py index e8fd4d3..d31161f 100644 --- a/pymongosql/retry.py +++ b/pymongosql/retry.py @@ -4,7 +4,13 @@ from typing import Any, Callable, Optional, Tuple, TypeVar from pymongo.errors import AutoReconnect, ConnectionFailure, NetworkTimeout, PyMongoError, ServerSelectionTimeoutError -from tenacity import Retrying, retry_if_exception_type, stop_after_attempt, wait_exponential + +try: + from tenacity import Retrying, retry_if_exception_type, stop_after_attempt, wait_exponential + + _has_tenacity = True +except ImportError: + _has_tenacity = False _logger = logging.getLogger(__name__) _T = TypeVar("_T") @@ -57,6 +63,13 @@ def execute_with_retry( if not config.enabled or config.attempts <= 1: return operation() + if not _has_tenacity: + _logger.warning( + "Retry is enabled but 'tenacity' package is not installed. " + "Falling back to no-retry. Install it with: pip install pymongosql[retry]" + ) + return operation() + def _before_sleep(retry_state: Any) -> None: error = retry_state.outcome.exception() if retry_state.outcome else None _logger.warning( diff --git a/pyproject.toml b/pyproject.toml index 5857c80..1c34df9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,10 +35,10 @@ dependencies = [ "pymongo>=4.15.0", "antlr4-python3-runtime>=4.13.0", "jmespath>=1.0.0", - "tenacity>=9.0.0", ] [project.optional-dependencies] +retry = ["tenacity>=9.0.0"] sqlalchemy = ["sqlalchemy>=1.4.0"] dev = [ "pytest>=7.0.0", diff --git a/requirements-optional.txt b/requirements-optional.txt index c57c163..3ebd3bb 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,2 +1,5 @@ +# Retry support (optional) +tenacity>=9.0.0 + # SQLAlchemy support (optional) - supports 1.4+ and 2.x sqlalchemy>=1.4.0,<3.0.0 diff --git a/requirements.txt b/requirements.txt index b7bc282..2b5da98 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ antlr4-python3-runtime>=4.13.0 pymongo>=4.15.0 -jmespath>=1.0.0 -tenacity>=9.0.0 \ No newline at end of file +jmespath>=1.0.0 \ No newline at end of file diff --git a/tests/test_retry.py b/tests/test_retry.py index 194d248..a65c4ac 100644 --- a/tests/test_retry.py +++ b/tests/test_retry.py @@ -162,3 +162,45 @@ def command(self, command_payload): assert row is not None assert row[0] == "retry-user" assert state["calls"] == 3 + + +def test_execute_with_retry_works_without_tenacity(monkeypatch): + """When tenacity is not installed, retry-enabled calls fall back to no-retry.""" + monkeypatch.setattr("pymongosql.retry._has_tenacity", False) + + state = {"calls": 0} + + def operation(): + state["calls"] += 1 + return "ok" + + result = execute_with_retry( + operation, + RetryConfig(enabled=True, attempts=3, wait_min=0.0, wait_max=0.0), + "no-tenacity fallback", + ) + + assert result == "ok" + assert state["calls"] == 1 # no retry, just one direct call + + +def test_retry_module_imports_without_tenacity(monkeypatch): + """The retry module can be imported even if tenacity is absent.""" + import importlib + import sys + + monkeypatch.setitem(sys.modules, "tenacity", None) # block tenacity import + + import pymongosql.retry + + importlib.reload(pymongosql.retry) + + assert pymongosql.retry._has_tenacity is False + + # Still usable with retry disabled + result = pymongosql.retry.execute_with_retry( + lambda: 42, + pymongosql.retry.RetryConfig(enabled=False), + "import test", + ) + assert result == 42 From 726bb934b41be8200ad98e7d90ffd4dcfb63bdeb Mon Sep 17 00:00:00 2001 From: Peng Ren Date: Sat, 18 Apr 2026 19:25:04 -0400 Subject: [PATCH 4/4] Bump the version to 0.5.0 --- pymongosql/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymongosql/__init__.py b/pymongosql/__init__.py index 1b444f7..eefeaeb 100644 --- a/pymongosql/__init__.py +++ b/pymongosql/__init__.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from .connection import Connection -__version__: str = "0.4.8" +__version__: str = "0.5.0" # Globals https://www.python.org/dev/peps/pep-0249/#globals apilevel: str = "2.0"