refactor(webhooks): bound queue waits, route URLs through dynaconf
Addresses round-3 review feedback on PR #747: - webhook_receiver: wrap send_stream.send() in anyio.fail_after(1.0) and return 503 with reason="queue full" if the queue is saturated. Avoids pinning the handler until NC's outbound timeout fires; the 503 retry contract is the same as the existing "sync not running" branch. - webhook_receiver: revise the compare_digest comment to match what the function actually guarantees — it avoids the per-character short-circuit of `==` but is not fully constant-time across length differences. - _get_webhook_uri: read WEBHOOK_INTERNAL_URL and NEXTCLOUD_MCP_SERVER_URL via dynaconf so operators using settings.toml (rather than env vars) aren't silently routed into the docker/localhost fallback. Adds webhook_internal_url to Settings/_DEFAULTS/_field_map; nextcloud_mcp_server_url already existed. Docker-detection markers stay on os.getenv since they're container-runtime signals, not user-facing config. - webhook_routes: sweep remaining f-string logger calls to lazy %s formatting per CLAUDE.md. - client/webhooks: modernise full file's type hints to dict / list / | None per CLAUDE.md. Tests: - New test_returns_503_when_queue_is_full exercises the timeout branch with a saturated buffer and a shortened deadline. - test_webhook_uri tests now patch get_settings (matching the auth-pair tests in the same file) instead of monkeypatching env vars directly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
42675e7c20
commit
c1368b9a7f
@@ -1,6 +1,6 @@
|
||||
"""Client for Nextcloud Webhook Listeners API operations."""
|
||||
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from nextcloud_mcp_server.client.base import BaseNextcloudClient
|
||||
|
||||
@@ -11,15 +11,15 @@ class WebhooksClient(BaseNextcloudClient):
|
||||
app_name = "webhooks"
|
||||
|
||||
def _get_webhook_headers(
|
||||
self, additional_headers: Optional[Dict[str, str]] = None
|
||||
) -> Dict[str, str]:
|
||||
self, additional_headers: dict[str, str] | None = None
|
||||
) -> dict[str, str]:
|
||||
"""Get standard headers required for Webhook Listeners API calls."""
|
||||
headers = {"OCS-APIRequest": "true", "Accept": "application/json"}
|
||||
if additional_headers:
|
||||
headers.update(additional_headers)
|
||||
return headers
|
||||
|
||||
async def list_webhooks(self) -> List[Dict[str, Any]]:
|
||||
async def list_webhooks(self) -> list[dict[str, Any]]:
|
||||
"""List all registered webhooks for the current user.
|
||||
|
||||
Returns:
|
||||
@@ -40,10 +40,10 @@ class WebhooksClient(BaseNextcloudClient):
|
||||
uri: str,
|
||||
http_method: str = "POST",
|
||||
auth_method: str = "none",
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
headers: dict[str, str] | None = None,
|
||||
auth_data: dict[str, str] | None = None,
|
||||
event_filter: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
event_filter: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Register a new webhook for the specified event.
|
||||
|
||||
Args:
|
||||
@@ -63,7 +63,7 @@ class WebhooksClient(BaseNextcloudClient):
|
||||
Returns:
|
||||
Webhook registration details including webhook ID
|
||||
"""
|
||||
data: Dict[str, Any] = {
|
||||
data: dict[str, Any] = {
|
||||
"httpMethod": http_method,
|
||||
"uri": uri,
|
||||
"event": event,
|
||||
@@ -101,7 +101,7 @@ class WebhooksClient(BaseNextcloudClient):
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
async def get_webhook(self, webhook_id: int) -> Dict[str, Any]:
|
||||
async def get_webhook(self, webhook_id: int) -> dict[str, Any]:
|
||||
"""Get details of a specific webhook registration.
|
||||
|
||||
Args:
|
||||
|
||||
Reference in New Issue
Block a user