Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions mpt_api_client/resources/webhooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""HTTP service for managing MPT webhook subscriptions."""

from typing import Any, ClassVar

import httpx

Comment on lines +3 to +6
JsonDict = dict[str, Any]
EventCache = dict[str, list[str]]


class WebhookService:
"""Service for creating, reading and deleting webhook subscriptions."""

event_cache: ClassVar[EventCache] = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Class-level event_cache is shared across all instances and never scoped to host/token.

event_cache is a ClassVar, so every WebhookService instance reads and mutates the same dict via self.event_cache["events"] = names. Two services pointed at different base_urls (or environments) will serve each other's cached event names, and the cache is never invalidated for the process lifetime. Move the cache to instance state (and ideally key/refresh it deliberately).

🛠️ Proposed fix — per-instance cache
-    event_cache: ClassVar[EventCache] = {}
-
     def __init__(self, base_url: str, token: str, timeout: float = 20.0) -> None:
@@
         self._client = httpx.Client(base_url=base_url, timeout=timeout)
         self._token = token
+        self._event_cache: EventCache = {}
-        if self.event_cache.get("events"):
-            return self.event_cache["events"]
+        if self._event_cache.get("events"):
+            return self._event_cache["events"]
         response = self._client.get("/webhooks/events", headers=self._headers())
         names = [event["name"] for event in response.json()]
-        self.event_cache["events"] = names
+        self._event_cache["events"] = names
         return names

Also applies to: 80-84

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` at line 14, WebhookService.event_cache
is currently a ClassVar, so all instances share the same cached event names
across different base_url/token contexts. Move event_cache to per-instance state
in WebhookService (initialize it on each instance, likely in __init__), and
update the event-loading path that writes self.event_cache["events"] so it only
uses that instance’s cache; if caching should persist, scope it by host/token
and add an explicit refresh/invalidation strategy.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The cache is a mutable class variable shared by all service instances, so different tokens/base URLs/process contexts will read and overwrite the same cache entry, and concurrent access is unsynchronized. Move this cache to instance state (or key it per base URL/auth context with locking) to avoid cross-instance contamination/races. [race condition]

Severity Level: Major ⚠️
- ❌ Event-type cache shared across different base URLs and tokens.
- ⚠️ Stale or incorrect event lists in multi-tenant processes.
- ⚠️ Subtle cross-environment data leakage between service instances.
Steps of Reproduction ✅
1. Note that `WebhookService` defines `event_cache: ClassVar[EventCache] = {}` at
`mpt_api_client/resources/webhooks.py:14`, making `event_cache` a mutable class variable
shared by all instances.

2. In one part of an application, create
`WebhookService(base_url=\"https://api.s1.show/public\", token=\"token-a\")` and call
`list_event_types()` at lines 72-85; this fetches `"/webhooks/events"` at line 82, builds
`names` at line 83, and stores them under `self.event_cache["events"]` at line 84.

3. In the same process, create a second `WebhookService` instance with a different
environment or base URL, e.g. `WebhookService(base_url=\"https://staging.s1.show/public\",
token=\"token-b\")`, and call `list_event_types()` again.

4. Because `event_cache` is shared at the class level, the `if
self.event_cache.get("events")` check at line 80 will return the cached list from the
first instance, so the second instance never hits its own `/webhooks/events` endpoint and
instead reuses event types from a different base URL/auth context, leading to
cross-environment data leakage and stale results; concurrent access from multiple threads
would also read/write this shared dict without any synchronization.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 14:14
**Comment:**
	*Race Condition: The cache is a mutable class variable shared by all service instances, so different tokens/base URLs/process contexts will read and overwrite the same cache entry, and concurrent access is unsynchronized. Move this cache to instance state (or key it per base URL/auth context with locking) to avoid cross-instance contamination/races.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


def __init__(self, base_url: str, token: str, timeout: float = 20.0) -> None:
"""Initialize the webhook service.

Args:
base_url: Base URL of the MPT API.
token: Bearer token used to authenticate requests.
timeout: Request timeout in seconds.
"""
self._client = httpx.Client(base_url=base_url, timeout=timeout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: A dedicated httpx.Client instance is created but never closed, which can leak sockets/file descriptors over time in long-lived processes. Add an explicit lifecycle hook (for example, a close() method and/or context-manager support) and ensure callers close the service when finished. [resource leak]

Severity Level: Major ⚠️
- ⚠️ Long-lived processes can accumulate unused HTTP connections.
- ⚠️ Potential exhaustion of file descriptors under repeated instantiation.
- ⚠️ Harder to reason about client lifecycle versus main HTTPClient.
Steps of Reproduction ✅
1. In your application code, import `WebhookService` from
`mpt_api_client/resources/webhooks.py:11` and instantiate it in a loop or per-request
(e.g. inside a web handler) with different tokens:
`WebhookService(base_url="https://api.s1.show/public", token="<token>")`.

2. Each instantiation executes `__init__` at `mpt_api_client/resources/webhooks.py:16-24`,
which constructs a new `httpx.Client` and stores it in `self._client` (line 24) without
any corresponding `close()` or context manager.

3. Call `create_webhook()` (defined at `mpt_api_client/resources/webhooks.py:27-43`) or
other methods on each instance so that `httpx` establishes TCP connections and maintains
them in its internal connection pool.

4. Discard the `WebhookService` instances without ever closing `self._client`; over time
in a long-lived process, repeated instantiation leads to many live `httpx.Client` objects
whose underlying sockets/file descriptors are only cleaned up by non-deterministic GC,
risking exhaustion of available connections and OS file descriptors.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 24:24
**Comment:**
	*Resource Leak: A dedicated `httpx.Client` instance is created but never closed, which can leak sockets/file descriptors over time in long-lived processes. Add an explicit lifecycle hook (for example, a `close()` method and/or context-manager support) and ensure callers close the service when finished.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

self._token = token
Comment on lines +11 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Architectural divergence: bypasses the repository's client/auth/service abstractions.

This module rolls its own httpx.Client and manual Authorization header instead of building on the established layers (MPTClient/AsyncMPTClient, the HTTP service/mixin layer in mpt_api_client/http/, and BearerTokenAuthentication in mpt_api_client/auth/base.py, which already attaches Bearer <token> via httpx's auth flow). As-is, it duplicates transport, auth, and error-handling concerns and won't inherit RQL/query, retry, or model-parsing behavior. Prefer composing the existing client and resource-service patterns.

As per coding guidelines, the repository provides sync/async clients "built on httpx, with typed resource services, mixin-based HTTP operations," and a repository-wide bearer-token auth contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` around lines 11 - 25, The
WebhookService currently bypasses the repository’s existing client/auth/service
stack by creating its own httpx.Client and storing a raw token, which duplicates
transport and auth concerns. Refactor WebhookService to compose the established
MPTClient/AsyncMPTClient and the HTTP service/mixin layer in
mpt_api_client/http/, and use BearerTokenAuthentication from
mpt_api_client/auth/base.py for token handling. Keep the webhook logic within
the existing resource-service pattern so it inherits the shared retry, parsing,
and request behavior.

Source: Coding guidelines

Comment on lines +24 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

httpx.Client is never closed — connection/resource leak.

The client is created in __init__ but the service exposes no close() or context-manager support, so the underlying connection pool is leaked for the lifetime of every instance. Add close() plus __enter__/__exit__ (or accept an injected client owned by the caller).

🧹 Proposed fix
     def __init__(self, base_url: str, token: str, timeout: float = 20.0) -> None:
@@
         self._client = httpx.Client(base_url=base_url, timeout=timeout)
         self._token = token
+
+    def close(self) -> None:
+        """Close the underlying HTTP client."""
+        self._client.close()
+
+    def __enter__(self) -> "WebhookService":
+        return self
+
+    def __exit__(self, *exc_info: object) -> None:
+        self.close()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self._client = httpx.Client(base_url=base_url, timeout=timeout)
self._token = token
self._client = httpx.Client(base_url=base_url, timeout=timeout)
self._token = token
def close(self) -> None:
"""Close the underlying HTTP client."""
self._client.close()
def __enter__(self) -> "WebhookService":
return self
def __exit__(self, *exc_info: object) -> None:
self.close()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` around lines 24 - 25, The Webhooks
client in the __init__-created self._client leaks resources because it is never
closed. Add lifecycle management to the Webhooks class by introducing a close()
method that closes self._client, and support context-manager usage via __enter__
and __exit__ so callers can ensure cleanup; if you prefer ownership inversion,
allow an injected httpx.Client and only close clients created internally.

Comment on lines +16 to +25

def create_webhook(self, url: str, events: list[str] | None = None) -> JsonDict:
"""Create a webhook subscription.

Args:
url: Callback URL that will receive webhook deliveries.
events: Event names to subscribe to.

Comment on lines +27 to +33
Returns:
The created webhook as returned by the API.

Raises:
MPTError: If the API responds with an error status.
"""
payload = {"url": url, "events": events or []}
response = self._client.post("/webhooks", json=payload, headers=self._headers())
created: JsonDict = response.json()
return created
Comment on lines +41 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

No error handling despite Raises: MPTError in docstrings.

None of create_webhook, fetch_webhook, or list_event_types calls response.raise_for_status(), so a 4xx/5xx response is silently parsed as JSON. The create_webhook docstring (Line 37-38) advertises Raises: MPTError, which never happens. On an error body, response.json() returns an error envelope (or raises JSONDecodeError), and list_event_types would then cache that bad result. Raise on error before parsing.

🛡️ Example for create_webhook
         response = self._client.post("/webhooks", json=payload, headers=self._headers())
+        response.raise_for_status()
         created: JsonDict = response.json()
         return created

Also applies to: 54-56, 82-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` around lines 41 - 43, Add HTTP error
handling before JSON parsing in create_webhook, fetch_webhook, and
list_event_types: each method should call response.raise_for_status()
immediately after the POST/GET request and before response.json(), so 4xx/5xx
responses surface as MPTError instead of being parsed or cached as success.
Update the create_webhook flow and the related fetch_webhook/list_event_types
paths consistently so the documented Raises: MPTError behavior is actually
triggered.

Comment on lines +40 to +43
Comment on lines +41 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The method parses and returns the response body without validating HTTP status, so 4xx/5xx responses can be treated as successful results or raise unexpected JSON parsing errors instead of the documented client exception flow. Validate status before decoding/returning the payload. [api mismatch]

Severity Level: Critical 🚨
- ❌ Webhook creation errors not surfaced as `MPTError`.
- ❌ Callers may treat error payloads as valid webhook objects.
- ⚠️ Inconsistent error handling versus other client services.
- ⚠️ Harder debugging due to missing unified exception types.
Steps of Reproduction ✅
1. Instantiate `WebhookService` from `mpt_api_client/resources/webhooks.py:11` in user
code with an invalid or expired token, e.g.
`WebhookService(base_url="https://api.s1.show/public", token="invalid")`, which sets up
`self._client` in `__init__` at lines 16-24.

2. Call `create_webhook()` at `mpt_api_client/resources/webhooks.py:27-43` with any URL
and events; this method issues `self._client.post("/webhooks", ...)` at line 41.

3. When the upstream API returns a 4xx/5xx (e.g. 401 Unauthorized for the bad token),
`create_webhook()` still executes `response.json()` at line 42 and returns that payload at
line 43 without calling `response.raise_for_status()` or any equivalent error handling.

4. Observe that, contrary to the docstring at lines 37-39 ("Raises: MPTError: If the API
responds with an error status.") and the library-wide pattern where `HTTPClient.request()`
in `mpt_api_client/http/client.py:95-115` applies `handle_response_http_error()` to raise
`MPTError` subclasses, `create_webhook()` silently returns an error payload or even raises
a raw JSON parsing error instead of the documented `MPTError`.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 41:43
**Comment:**
	*Api Mismatch: The method parses and returns the response body without validating HTTP status, so 4xx/5xx responses can be treated as successful results or raise unexpected JSON parsing errors instead of the documented client exception flow. Validate status before decoding/returning the payload.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


def fetch_webhook(self, webhook_id: str) -> JsonDict:
"""Retrieve a single webhook by its identifier.

Args:
webhook_id: Identifier of the webhook to retrieve.

Returns:
The webhook as returned by the API.
"""
response = self._client.get(f"/webhooks/{webhook_id}", headers=self._headers())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The webhook routes are hardcoded without the /public/v1 API prefix used throughout this client, which will hit different endpoints and can produce 404/contract mismatches against the configured API base convention. Align these paths with the existing versioned endpoint pattern. [api mismatch]

Severity Level: Critical 🚨
- ❌ Webhook methods hit non-versioned, likely non-existent endpoints.
- ❌ All webhook operations likely return 404 or invalid responses.
- ⚠️ Inconsistent routing versus other `/public/v1`-based services.
- ⚠️ Confusing divergence from documented OpenAPI endpoint structure.
Steps of Reproduction ✅
1. Configure the main client following `docs/usage.md:56-63`, using
`base_url="https://api.s1.show/public"`, and instantiate
`WebhookService(base_url=\"https://api.s1.show/public\", token=\"<token>\")` from
`mpt_api_client/resources/webhooks.py:11-24`.

2. Call `fetch_webhook("WH-123")` at `mpt_api_client/resources/webhooks.py:45-56`; this
issues `self._client.get(f"/webhooks/{webhook_id}", ...)` at line 54, which resolves to
`https://api.s1.show/public/webhooks/WH-123` given the base URL.

3. Compare this with the established pattern for other resources, e.g.
`BatchesServiceConfig._endpoint = "/public/v1/notifications/batches"` at
`mpt_api_client/resources/notifications/batches.py:22-29` and similar `/public/v1/...`
endpoints throughout `mpt_api_client/resources/notifications/*.py`, which rely on the HTTP
transport stripping `/public` from the base URL and adding `/public/v1` via the
`_endpoint`.

4. Because `WebhookService` bypasses the shared `HTTPClient` and uses raw `httpx.Client`
without `validate_base_url()` or a `/public/v1/...` path, its requests target
`/public/webhooks` instead of the versioned `/public/v1/...` notification webhook
endpoints defined by the upstream OpenAPI spec (`README.md:23-24`,
`docs/architecture.md:11-13`), leading to 404s or contract mismatches for all webhook
operations (create, fetch, delete, list events).

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 54:54
**Comment:**
	*Api Mismatch: The webhook routes are hardcoded without the `/public/v1` API prefix used throughout this client, which will hit different endpoints and can produce 404/contract mismatches against the configured API base convention. Align these paths with the existing versioned endpoint pattern.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

webhook: JsonDict = response.json()
Comment on lines +24 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Architect Review — HIGH

Webhook operations use a standalone httpx.Client with direct .post()/.get() calls, bypassing the shared HTTPClient/AsyncHTTPClient layer that applies retry and handle_response_http_error transformation; as implemented, these methods never raise the documented MPT exceptions (e.g. MPTError) and will return error responses as normal JSON payloads instead.

Suggestion: Route webhook requests through the existing HTTPClient/AsyncHTTPClient service layer (as other resources do via Service/AsyncService and ResourceAccessor), so error handling, retries, and exception types remain consistent with the rest of the SDK (http/client.py, http/async_client.py, http/request_response_utils.py).

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is an **Architect / Logical Review** comment left during a code review. These reviews are first-class, important findings — not optional suggestions. Do NOT dismiss this as a 'big architectural change' just because the title says architect review; most of these can be resolved with a small, localized fix once the intent is understood.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 24:55
**Comment:**
	*HIGH: Webhook operations use a standalone `httpx.Client` with direct `.post()`/`.get()` calls, bypassing the shared `HTTPClient`/`AsyncHTTPClient` layer that applies retry and `handle_response_http_error` transformation; as implemented, these methods never raise the documented MPT exceptions (e.g. `MPTError`) and will return error responses as normal JSON payloads instead.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
If a suggested approach is provided above, use it as the authoritative instruction. If no explicit code suggestion is given, you MUST still draft and apply your own minimal, localized fix — do not punt back with 'no suggestion provided, review manually'. Keep the change as small as possible: add a guard clause, gate on a loading state, reorder an await, wrap in a conditional, etc. Do not refactor surrounding code or expand scope beyond the finding.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix

return webhook
Comment on lines +54 to +56
Comment on lines +40 to +56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. No http error raising 🐞 Bug ☼ Reliability

create_webhook, fetch_webhook, and list_event_types call response.json() without
raise_for_status(), so API errors will not raise MPTError as documented and may be silently
treated as success or crash with JSON decode errors.
Agent Prompt
### Issue description
WebhookService parses JSON bodies without checking HTTP status codes. This violates the docstring promise to raise `MPTError` and diverges from the rest of the client, which translates HTTP status errors into `MPTError`/`MPTAPIError`.

### Issue Context
The standard HTTP layer calls `handle_response_http_error()` which wraps `response.raise_for_status()` and raises transformed MPT exceptions.

### Fix Focus Areas
- mpt_api_client/resources/webhooks.py[40-56]

### Suggested fix
- After each request, call `response.raise_for_status()` and translate `httpx.HTTPStatusError` via the existing exception transformer OR (preferably) refactor WebhookService to use `mpt_api_client.http.client.HTTPClient` so error handling is consistent.
- Ensure the methods’ docstrings match actual behavior (raising `MPTError` on non-2xx).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


def delete_webhook(self, webhook_id: str) -> bool:
"""Delete a webhook subscription.

Args:
webhook_id: Identifier of the webhook to delete.

Returns:
True if the webhook was deleted successfully.
"""
response = self._client.request(
"DELETE", f"/webhooks/{webhook_id}", headers=self._headers()
)
return bool(response.status_code == httpx.codes.OK)
Comment on lines +67 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

delete_webhook reports failure for a 204 No Content success.

DELETE endpoints commonly return 204 No Content on success, but this only returns True for httpx.codes.OK (200). A successful deletion would therefore return False. Check for any success status instead.

🐛 Proposed fix
         response = self._client.request(
             "DELETE", f"/webhooks/{webhook_id}", headers=self._headers()
         )
-        return bool(response.status_code == httpx.codes.OK)
+        response.raise_for_status()
+        return response.is_success
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response = self._client.request(
"DELETE", f"/webhooks/{webhook_id}", headers=self._headers()
)
return bool(response.status_code == httpx.codes.OK)
response = self._client.request(
"DELETE", f"/webhooks/{webhook_id}", headers=self._headers()
)
response.raise_for_status()
return response.is_success
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` around lines 67 - 70, The
`delete_webhook` method is too strict by only treating `httpx.codes.OK` as
success, so a valid `204 No Content` DELETE response is reported as failure.
Update `delete_webhook` in the webhook client to return `True` for any
successful 2xx response, using the existing `response.status_code` check after
`self._client.request(...)` instead of matching only 200.

Comment on lines +67 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Deletion success is restricted to HTTP 200, but REST delete endpoints commonly return 204 No Content for successful deletion; this will incorrectly report failure on valid responses. Treat all successful 2xx delete responses as success (or at least include 204). [api mismatch]

Severity Level: Major ⚠️
- ❌ Successful deletions reported as failures to callers.
- ⚠️ Client code may retry or log incorrect error states.
- ⚠️ Inconsistent semantics with other HTTP operations using 2xx.
Steps of Reproduction ✅
1. In user code, instantiate `WebhookService` from
`mpt_api_client/resources/webhooks.py:11` with a valid base URL and token, so that
`__init__` at lines 16-24 sets up `self._client`.

2. Call `delete_webhook()` at `mpt_api_client/resources/webhooks.py:58-70` with an
existing webhook ID, triggering `self._client.request("DELETE", f"/webhooks/{webhook_id}",
...)` at lines 67-68.

3. If the upstream API follows common REST conventions and returns `204 No Content` (or
another 2xx such as 202) on successful deletion, `response.status_code` will be 204, not
`httpx.codes.OK` (200).

4. The method executes `return bool(response.status_code == httpx.codes.OK)` at line 70,
which evaluates to `False` for 204/202, causing `delete_webhook()` to report failure even
though the resource was deleted successfully on the server.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** mpt_api_client/resources/webhooks.py
**Line:** 70:70
**Comment:**
	*Api Mismatch: Deletion success is restricted to HTTP 200, but REST delete endpoints commonly return 204 No Content for successful deletion; this will incorrectly report failure on valid responses. Treat all successful 2xx delete responses as success (or at least include 204).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎


def list_event_types(self) -> list[str]:
"""Return the names of all supported webhook event types.

Results are cached after the first successful call.

Returns:
The list of supported event type names.
"""
if self.event_cache.get("events"):
return self.event_cache["events"]
response = self._client.get("/webhooks/events", headers=self._headers())
names = [event["name"] for event in response.json()]
self.event_cache["events"] = names
return names
Comment on lines +80 to +85
Comment on lines +40 to +85

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Wrong webhook url prefix 🐞 Bug ≡ Correctness

WebhookService calls /webhooks... paths, but this client’s base URL is normalized to strip
/public/v1 and other services’ endpoints explicitly include /public/v1, so these requests will
likely hit the wrong route and fail (e.g., 404).
Agent Prompt
### Issue description
`WebhookService` hardcodes request paths like `/webhooks` and `/webhooks/events`. In this codebase, the HTTP client normalizes `base_url` by removing `/public` and `/public/v1`, and resource services define endpoints including `/public/v1/...`. As a result, webhook requests are very likely routed incorrectly.

### Issue Context
The repository’s `validate_base_url` explicitly strips `/public` and `/public/v1` from configured base URLs, and unit tests assert resource calls use `/public/v1/...` paths.

### Fix Focus Areas
- mpt_api_client/resources/webhooks.py[40-85]

### Suggested fix
- Change webhook endpoint paths to include the expected `/public/v1` prefix (e.g., `/public/v1/webhooks`, `/public/v1/webhooks/events`).
- Prefer using `join_url_path()` (or the existing Service/Accessor pattern) when building resource URLs to avoid subtle slash issues.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


def is_active(self, webhook: JsonDict) -> bool:
"""Return whether a webhook subscription is currently active.

Args:
webhook: A webhook payload returned by the API.

Returns:
True if the webhook status is active.
"""
return bool(webhook["status"] == "active")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

is_active raises KeyError when status is absent.

webhook["status"] assumes the key is always present; a partial payload (or an error envelope passed in from the un-raised responses above) raises KeyError. Use .get() for a safe comparison.

🛡️ Proposed fix
-        return bool(webhook["status"] == "active")
+        return webhook.get("status") == "active"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return bool(webhook["status"] == "active")
return webhook.get("status") == "active"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mpt_api_client/resources/webhooks.py` at line 96, The is_active helper in the
webhook handling logic can raise KeyError when status is missing from a partial
or error payload. Update the comparison in is_active to use a safe lookup on the
webhook object (for example via get) and treat missing status as inactive,
keeping the behavior aligned with the rest of the webhook response handling.


def _headers(self) -> dict[str, str]:
"""Build the authorization headers for a request."""
return {"Authorization": f"Bearer {self._token}"}
Comment on lines +98 to +100
Loading