Skip to content

Commit 6d6472d

Browse files
committed
Restrict retries to status-code responses only, fix raise_on_status and config validation
1 parent 36a5aeb commit 6d6472d

4 files changed

Lines changed: 49 additions & 1 deletion

File tree

src/nextcloud_mcp/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,15 @@ async def _get_session(self) -> niquests.AsyncSession:
8282
if self._config.retry_max > 0:
8383
kwargs["retries"] = Retry(
8484
total=self._config.retry_max,
85+
connect=0,
86+
read=0,
87+
other=0,
88+
redirect=0,
8589
status_forcelist=[429, 503],
8690
backoff_factor=1.0,
8791
respect_retry_after_header=True,
8892
allowed_methods=None,
93+
raise_on_status=False,
8994
)
9095
self._session = niquests.AsyncSession(**kwargs) # type: ignore[arg-type]
9196
return self._session

src/nextcloud_mcp/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ def from_env(cls) -> "Config":
4646

4747
host = os.environ.get("NEXTCLOUD_MCP_HOST", "0.0.0.0")
4848
port = int(os.environ.get("NEXTCLOUD_MCP_PORT", "8100"))
49-
retry_max = int(os.environ.get("NEXTCLOUD_MCP_RETRY_MAX", "3"))
49+
retry_raw = os.environ.get("NEXTCLOUD_MCP_RETRY_MAX", "3")
50+
try:
51+
retry_max = int(retry_raw)
52+
except ValueError:
53+
raise ValueError(f"Invalid NEXTCLOUD_MCP_RETRY_MAX='{retry_raw}'. Expected integer >= 0.") from None
5054

5155
return cls(
5256
nextcloud_url=url,

tests/test_client_retry.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,33 @@ async def test_retry_backoff_factor(self) -> None:
8686
assert adapter.max_retries.backoff_factor == 1.0
8787
finally:
8888
await client.close()
89+
90+
@pytest.mark.asyncio
91+
async def test_retry_no_connect_retries(self) -> None:
92+
client = NextcloudClient(_make_config())
93+
session = await client._get_session()
94+
try:
95+
adapter = session.get_adapter("http://localhost")
96+
assert adapter.max_retries.connect == 0
97+
finally:
98+
await client.close()
99+
100+
@pytest.mark.asyncio
101+
async def test_retry_no_read_retries(self) -> None:
102+
client = NextcloudClient(_make_config())
103+
session = await client._get_session()
104+
try:
105+
adapter = session.get_adapter("http://localhost")
106+
assert adapter.max_retries.read == 0
107+
finally:
108+
await client.close()
109+
110+
@pytest.mark.asyncio
111+
async def test_retry_raise_on_status_disabled(self) -> None:
112+
client = NextcloudClient(_make_config())
113+
session = await client._get_session()
114+
try:
115+
adapter = session.get_adapter("http://localhost")
116+
assert adapter.max_retries.raise_on_status is False
117+
finally:
118+
await client.close()

tests/test_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ def test_retry_max_negative_clamped_to_zero(self, monkeypatch: pytest.MonkeyPatc
9595
config = Config.from_env()
9696
assert config.retry_max == 0
9797

98+
def test_retry_max_invalid_raises(self, monkeypatch: pytest.MonkeyPatch) -> None:
99+
monkeypatch.setenv("NEXTCLOUD_URL", "http://localhost")
100+
monkeypatch.setenv("NEXTCLOUD_USER", "admin")
101+
monkeypatch.setenv("NEXTCLOUD_PASSWORD", "admin")
102+
monkeypatch.setenv("NEXTCLOUD_MCP_RETRY_MAX", "abc")
103+
104+
with pytest.raises(ValueError, match="Invalid NEXTCLOUD_MCP_RETRY_MAX"):
105+
Config.from_env()
106+
98107
def test_case_insensitive_permission(self, monkeypatch: pytest.MonkeyPatch) -> None:
99108
monkeypatch.setenv("NEXTCLOUD_URL", "http://localhost")
100109
monkeypatch.setenv("NEXTCLOUD_USER", "admin")

0 commit comments

Comments
 (0)