diff --git a/.stats.yml b/.stats.yml index baa431fa..5f9fc2ab 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1 +1 @@ -configured_endpoints: 240 +configured_endpoints: 258 diff --git a/api.md b/api.md index c6d94692..b33bcc2a 100644 --- a/api.md +++ b/api.md @@ -145,6 +145,7 @@ from mobilerun_sdk.types import ( DeviceFingerprintResponse, DeviceRetrieveCapabilitiesResponse, DeviceSetNameResponse, + DeviceSummaryResponse, DeviceWaitReadyResponse, ) ``` @@ -162,6 +163,7 @@ Methods: - client.devices.retrieve_capabilities(device_id) -> DeviceRetrieveCapabilitiesResponse - client.devices.set_name(device_id, \*\*params) -> DeviceSetNameResponse - client.devices.stop(device_id) -> None +- client.devices.summary(\*\*params) -> DeviceSummaryResponse - client.devices.terminate(device_id, \*\*params) -> None - client.devices.wait_ready(device_id) -> DeviceWaitReadyResponse @@ -530,8 +532,10 @@ Types: ```python from mobilerun_sdk.types.connect import ( ProxyRetrieveResponse, + ProxyUpdateResponse, ProxyListResponse, ProxyBuyResponse, + ProxyEnsureHealthyResponse, ProxyListConnectionsResponse, ProxyPingResponse, ) @@ -540,9 +544,11 @@ from mobilerun_sdk.types.connect import ( Methods: - client.connect.proxies.retrieve(id) -> ProxyRetrieveResponse +- client.connect.proxies.update(id, \*\*params) -> ProxyUpdateResponse - client.connect.proxies.list(\*\*params) -> ProxyListResponse - client.connect.proxies.buy(\*\*params) -> ProxyBuyResponse - client.connect.proxies.cancel(id) -> None +- client.connect.proxies.ensure_healthy(id) -> ProxyEnsureHealthyResponse - client.connect.proxies.list_connections(id, \*\*params) -> ProxyListConnectionsResponse - client.connect.proxies.ping(id) -> ProxyPingResponse @@ -708,11 +714,17 @@ from mobilerun_sdk.types.workflows import ( FlowUpdateResponse, FlowListResponse, FlowDeleteResponse, + FlowActivateResponse, FlowCapacityResponse, FlowCloneResponse, + FlowDeliveryOptionsResponse, FlowDryRunResponse, FlowListRepairsResponse, + FlowRunResponse, + FlowTemplateContextResponse, FlowUnblockResponse, + FlowValidateResponse, + FlowVerifyResponse, ) ``` @@ -723,11 +735,17 @@ Methods: - client.workflows.flows.update(flow_id, \*\*params) -> FlowUpdateResponse - client.workflows.flows.list(\*\*params) -> FlowListResponse - client.workflows.flows.delete(flow_id) -> FlowDeleteResponse +- client.workflows.flows.activate(flow_id, \*\*params) -> FlowActivateResponse - client.workflows.flows.capacity() -> FlowCapacityResponse - client.workflows.flows.clone(flow_id, \*\*params) -> FlowCloneResponse +- client.workflows.flows.delivery_options(flow_id) -> FlowDeliveryOptionsResponse - client.workflows.flows.dry_run(flow_id, \*\*params) -> FlowDryRunResponse - client.workflows.flows.list_repairs(flow_id) -> FlowListRepairsResponse +- client.workflows.flows.run(flow_id, \*\*params) -> FlowRunResponse +- client.workflows.flows.template_context(\*\*params) -> FlowTemplateContextResponse - client.workflows.flows.unblock(flow_id) -> FlowUnblockResponse +- client.workflows.flows.validate(\*\*params) -> FlowValidateResponse +- client.workflows.flows.verify(flow_id, \*\*params) -> FlowVerifyResponse ### Actions @@ -777,10 +795,16 @@ from mobilerun_sdk.types.workflows import ( Methods: -- client.workflows.executions.retrieve(execution_id) -> ExecutionRetrieveResponse -- client.workflows.executions.list(\*\*params) -> ExecutionListResponse -- client.workflows.executions.abort(execution_id) -> ExecutionAbortResponse -- client.workflows.executions.get_metrics(\*\*params) -> ExecutionGetMetricsResponse +- client.workflows.executions.retrieve(execution_id) -> ExecutionRetrieveResponse +- client.workflows.executions.list(\*\*params) -> ExecutionListResponse +- client.workflows.executions.abort(execution_id) -> ExecutionAbortResponse +- client.workflows.executions.get_metrics(\*\*params) -> ExecutionGetMetricsResponse + +### Screenshots + +Methods: + +- client.workflows.executions.screenshots.retrieve(screenshot_id, \*, execution_id) -> None ## Timezones @@ -821,6 +845,19 @@ Methods: - client.webhooks.rotate_secret(id) -> WebhookRotateSecretResponse - client.webhooks.test_delivery(id) -> WebhookTestDeliveryResponse +## Integrations + +Types: + +```python +from mobilerun_sdk.types.webhooks import IntegrationListResponse, IntegrationListTargetsResponse +``` + +Methods: + +- client.webhooks.integrations.list() -> IntegrationListResponse +- client.webhooks.integrations.list_targets(capability_id, \*\*params) -> IntegrationListTargetsResponse + ## Deliveries Types: @@ -853,6 +890,7 @@ from mobilerun_sdk.types import ( MailboxListResponse, MailboxDeleteResponse, MailboxCapacityResponse, + MailboxDisconnectResponse, MailboxOtpResponse, MailboxRestartResponse, MailboxUncancelResponse, @@ -867,10 +905,24 @@ Methods: - client.mailboxes.list(\*\*params) -> MailboxListResponse - client.mailboxes.delete(mailbox_id) -> MailboxDeleteResponse - client.mailboxes.capacity() -> MailboxCapacityResponse +- client.mailboxes.disconnect(mailbox_id) -> MailboxDisconnectResponse - client.mailboxes.otp(mailbox_id, \*\*params) -> MailboxOtpResponse - client.mailboxes.restart(mailbox_id, \*\*params) -> MailboxRestartResponse - client.mailboxes.uncancel(mailbox_id) -> MailboxUncancelResponse +## Connections + +Types: + +```python +from mobilerun_sdk.types.mailboxes import ConnectionCreateResponse, ConnectionRetrieveResponse +``` + +Methods: + +- client.mailboxes.connections.create(\*\*params) -> ConnectionCreateResponse +- client.mailboxes.connections.retrieve(connection_id) -> ConnectionRetrieveResponse + ## Messages Types: @@ -975,6 +1027,7 @@ Types: ```python from mobilerun_sdk.types import ( + NotificationApplyPreferencesPresetResponse, NotificationCatalogResponse, NotificationGetPreferencesResponse, NotificationUpdatePreferencesResponse, @@ -983,6 +1036,7 @@ from mobilerun_sdk.types import ( Methods: +- client.notifications.apply_preferences_preset(\*\*params) -> NotificationApplyPreferencesPresetResponse - client.notifications.catalog() -> NotificationCatalogResponse - client.notifications.get_preferences() -> NotificationGetPreferencesResponse - client.notifications.update_preferences(\*\*params) -> NotificationUpdatePreferencesResponse @@ -992,11 +1046,12 @@ Methods: Types: ```python -from mobilerun_sdk.types import MessageListResponse +from mobilerun_sdk.types import MessageRetrieveResponse, MessageListResponse ``` Methods: +- client.messages.retrieve(id) -> MessageRetrieveResponse - client.messages.list(\*\*params) -> MessageListResponse ## Conversations @@ -1045,12 +1100,13 @@ Methods: Types: ```python -from mobilerun_sdk.types.numbers import MessageListResponse +from mobilerun_sdk.types.numbers import MessageListResponse, MessageSendResponse ``` Methods: - client.numbers.messages.list(id, \*\*params) -> MessageListResponse +- client.numbers.messages.send(id, \*\*params) -> MessageSendResponse # Store diff --git a/src/mobilerun_sdk/_client.py b/src/mobilerun_sdk/_client.py index cb8ba65a..2a60300b 100644 --- a/src/mobilerun_sdk/_client.py +++ b/src/mobilerun_sdk/_client.py @@ -149,6 +149,8 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self._idempotency_header = "Idempotency-Key" + @cached_property def apps(self) -> AppsResource: from .resources.apps import AppsResource @@ -449,6 +451,8 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self._idempotency_header = "Idempotency-Key" + @cached_property def apps(self) -> AsyncAppsResource: from .resources.apps import AsyncAppsResource diff --git a/src/mobilerun_sdk/resources/app_events/app_events.py b/src/mobilerun_sdk/resources/app_events/app_events.py index 85c4260b..4e6f6161 100644 --- a/src/mobilerun_sdk/resources/app_events/app_events.py +++ b/src/mobilerun_sdk/resources/app_events/app_events.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Union +from datetime import datetime from typing_extensions import Literal import httpx @@ -96,11 +97,11 @@ def list( *, device_id: str | Omit = omit, event_type: str | Omit = omit, - from_: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, source: Literal["app", "system", "device", "webhook"] | Omit = omit, - to: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -210,11 +211,11 @@ async def list( *, device_id: str | Omit = omit, event_type: str | Omit = omit, - from_: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, source: Literal["app", "system", "device", "webhook"] | Omit = omit, - to: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, diff --git a/src/mobilerun_sdk/resources/apps.py b/src/mobilerun_sdk/resources/apps.py index 3f0f323f..b77cd0ea 100644 --- a/src/mobilerun_sdk/resources/apps.py +++ b/src/mobilerun_sdk/resources/apps.py @@ -146,6 +146,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppDeleteResponse: """Deletes an uploaded app by ID. @@ -160,13 +161,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._delete( path_template("/apps/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppDeleteResponse, ) @@ -181,6 +188,7 @@ def confirm_upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppConfirmUploadResponse: """ Verifies the uploaded files in R2 and sets the app version status to available. @@ -195,13 +203,19 @@ def confirm_upload( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( path_template("/apps/{id}/confirm-upload", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppConfirmUploadResponse, ) @@ -226,6 +240,7 @@ def create_signed_upload_url( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppCreateSignedUploadURLResponse: """ Creates or updates an app and returns pre-signed Cloudflare R2 upload URLs for @@ -241,6 +256,8 @@ def create_signed_upload_url( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/apps/create-signed-upload-url", @@ -261,7 +278,11 @@ def create_signed_upload_url( app_create_signed_upload_url_params.AppCreateSignedUploadURLParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppCreateSignedUploadURLResponse, ) @@ -310,6 +331,7 @@ def mark_failed( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppMarkFailedResponse: """ Sets the app status to failed. @@ -322,13 +344,19 @@ def mark_failed( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( path_template("/apps/{id}/mark-failed", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppMarkFailedResponse, ) @@ -344,8 +372,11 @@ def storage_usage( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AppStorageUsageResponse: """ - Returns the user’s total storage quota, bytes used, and remaining bytes — the - reliable maximum size for the next upload. + Returns the user’s storage allowance from Autumn storage_mb (decimal bytes): + bytes used, bytes included in the plan, the hard maximum (included + max + purchasable overage; null = unlimited) and whether usage above the included + amount is billed as overage. With storage billing off there is no limit: only + usedBytes is set. """ return self._get( "/apps/storage-usage", @@ -471,6 +502,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppDeleteResponse: """Deletes an uploaded app by ID. @@ -485,13 +517,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._delete( path_template("/apps/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppDeleteResponse, ) @@ -506,6 +544,7 @@ async def confirm_upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppConfirmUploadResponse: """ Verifies the uploaded files in R2 and sets the app version status to available. @@ -520,13 +559,19 @@ async def confirm_upload( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( path_template("/apps/{id}/confirm-upload", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppConfirmUploadResponse, ) @@ -551,6 +596,7 @@ async def create_signed_upload_url( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppCreateSignedUploadURLResponse: """ Creates or updates an app and returns pre-signed Cloudflare R2 upload URLs for @@ -566,6 +612,8 @@ async def create_signed_upload_url( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/apps/create-signed-upload-url", @@ -586,7 +634,11 @@ async def create_signed_upload_url( app_create_signed_upload_url_params.AppCreateSignedUploadURLParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppCreateSignedUploadURLResponse, ) @@ -635,6 +687,7 @@ async def mark_failed( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppMarkFailedResponse: """ Sets the app status to failed. @@ -647,13 +700,19 @@ async def mark_failed( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( path_template("/apps/{id}/mark-failed", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppMarkFailedResponse, ) @@ -669,8 +728,11 @@ async def storage_usage( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> AppStorageUsageResponse: """ - Returns the user’s total storage quota, bytes used, and remaining bytes — the - reliable maximum size for the next upload. + Returns the user’s storage allowance from Autumn storage_mb (decimal bytes): + bytes used, bytes included in the plan, the hard maximum (included + max + purchasable overage; null = unlimited) and whether usage above the included + amount is billed as overage. With storage billing off there is no limit: only + usedBytes is set. """ return await self._get( "/apps/storage-usage", diff --git a/src/mobilerun_sdk/resources/assistant/conversations.py b/src/mobilerun_sdk/resources/assistant/conversations.py index 3747f5dd..ef5c5e6b 100644 --- a/src/mobilerun_sdk/resources/assistant/conversations.py +++ b/src/mobilerun_sdk/resources/assistant/conversations.py @@ -8,7 +8,7 @@ import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -71,26 +71,23 @@ def create( title: str, agent: str | Omit = omit, description: str | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationCreateResponse: """Creates a titled agent session. Setup may occur on the first prompt. Idempotent via the `Idempotency-Key` header — a duplicate submit by the same authenticated caller within the 24-hour idempotency window returns the already-created session - instead of a second one. + instead of a second one. Reusing the same key with a different request body is a + 422 `idempotency_key_reused`. Args: - idempotency_key: Optional client key. Reusing the same key with the same request body by the same - authenticated caller within 24 hours returns the already-created session instead - of a second one. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -98,8 +95,9 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return self._post( "/assistant/chat/sessions", body=maybe_transform( @@ -111,7 +109,11 @@ def create( conversation_create_params.ConversationCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationCreateResponse, ) @@ -130,6 +132,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationUpdateResponse: """Rename, change status, and/or pin. @@ -148,6 +151,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -163,7 +168,11 @@ def update( conversation_update_params.ConversationUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationUpdateResponse, ) @@ -228,6 +237,7 @@ def abort( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAbortResponse: """Abort the in-flight chat turn owned by `sessionId`. @@ -242,6 +252,8 @@ def abort( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/assistant/chat/abort", @@ -253,7 +265,11 @@ def abort( conversation_abort_params.ConversationAbortParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAbortResponse, ) @@ -269,6 +285,7 @@ def answer_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAnswerPermissionResponse: """Deliver a HITL approval/rejection for an in-flight turn. @@ -287,6 +304,8 @@ def answer_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/assistant/chat/permission", @@ -298,7 +317,11 @@ def answer_permission( conversation_answer_permission_params.ConversationAnswerPermissionParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAnswerPermissionResponse, ) @@ -314,6 +337,7 @@ def answer_question( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAnswerQuestionResponse: """Deliver the user's answers to the agent's pending question for an in-flight turn. @@ -328,6 +352,8 @@ def answer_question( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/assistant/chat/question", @@ -339,7 +365,11 @@ def answer_question( conversation_answer_question_params.ConversationAnswerQuestionParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAnswerQuestionResponse, ) @@ -398,6 +428,7 @@ def reject_question( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationRejectQuestionResponse: """Dismiss the agent's pending question. @@ -412,6 +443,8 @@ def reject_question( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/assistant/chat/question/reject", @@ -419,7 +452,11 @@ def reject_question( {"question_id": question_id}, conversation_reject_question_params.ConversationRejectQuestionParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationRejectQuestionResponse, ) @@ -436,6 +473,7 @@ def send( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationSendResponse: """Send a single user message. @@ -456,6 +494,8 @@ def send( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/assistant/chat/message", @@ -468,7 +508,11 @@ def send( conversation_send_params.ConversationSendParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationSendResponse, ) @@ -543,26 +587,23 @@ async def create( title: str, agent: str | Omit = omit, description: str | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationCreateResponse: """Creates a titled agent session. Setup may occur on the first prompt. Idempotent via the `Idempotency-Key` header — a duplicate submit by the same authenticated caller within the 24-hour idempotency window returns the already-created session - instead of a second one. + instead of a second one. Reusing the same key with a different request body is a + 422 `idempotency_key_reused`. Args: - idempotency_key: Optional client key. Reusing the same key with the same request body by the same - authenticated caller within 24 hours returns the already-created session instead - of a second one. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -570,8 +611,9 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return await self._post( "/assistant/chat/sessions", body=await async_maybe_transform( @@ -583,7 +625,11 @@ async def create( conversation_create_params.ConversationCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationCreateResponse, ) @@ -602,6 +648,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationUpdateResponse: """Rename, change status, and/or pin. @@ -620,6 +667,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -635,7 +684,11 @@ async def update( conversation_update_params.ConversationUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationUpdateResponse, ) @@ -700,6 +753,7 @@ async def abort( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAbortResponse: """Abort the in-flight chat turn owned by `sessionId`. @@ -714,6 +768,8 @@ async def abort( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/assistant/chat/abort", @@ -725,7 +781,11 @@ async def abort( conversation_abort_params.ConversationAbortParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAbortResponse, ) @@ -741,6 +801,7 @@ async def answer_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAnswerPermissionResponse: """Deliver a HITL approval/rejection for an in-flight turn. @@ -759,6 +820,8 @@ async def answer_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/assistant/chat/permission", @@ -770,7 +833,11 @@ async def answer_permission( conversation_answer_permission_params.ConversationAnswerPermissionParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAnswerPermissionResponse, ) @@ -786,6 +853,7 @@ async def answer_question( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationAnswerQuestionResponse: """Deliver the user's answers to the agent's pending question for an in-flight turn. @@ -800,6 +868,8 @@ async def answer_question( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/assistant/chat/question", @@ -811,7 +881,11 @@ async def answer_question( conversation_answer_question_params.ConversationAnswerQuestionParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationAnswerQuestionResponse, ) @@ -870,6 +944,7 @@ async def reject_question( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationRejectQuestionResponse: """Dismiss the agent's pending question. @@ -884,6 +959,8 @@ async def reject_question( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/assistant/chat/question/reject", @@ -891,7 +968,11 @@ async def reject_question( {"question_id": question_id}, conversation_reject_question_params.ConversationRejectQuestionParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationRejectQuestionResponse, ) @@ -908,6 +989,7 @@ async def send( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationSendResponse: """Send a single user message. @@ -928,6 +1010,8 @@ async def send( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/assistant/chat/message", @@ -940,7 +1024,11 @@ async def send( conversation_send_params.ConversationSendParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationSendResponse, ) diff --git a/src/mobilerun_sdk/resources/carriers.py b/src/mobilerun_sdk/resources/carriers.py index a674b8f8..ef389bf1 100644 --- a/src/mobilerun_sdk/resources/carriers.py +++ b/src/mobilerun_sdk/resources/carriers.py @@ -73,6 +73,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierCreateResponse: """ Create a new carrier @@ -117,6 +118,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/carriers", @@ -142,7 +145,11 @@ def create( carrier_create_params.CarrierCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierCreateResponse, ) @@ -204,6 +211,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierUpdateResponse: """ Update a carrier @@ -246,6 +254,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._patch( path_template("/carriers/{carrier_id}", carrier_id=carrier_id), @@ -269,7 +279,11 @@ def update( carrier_update_params.CarrierUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierUpdateResponse, ) @@ -346,6 +360,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierDeleteResponse: """ Delete a carrier @@ -360,11 +375,17 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._delete( path_template("/carriers/{carrier_id}", carrier_id=carrier_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierDeleteResponse, ) @@ -461,6 +482,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierCreateResponse: """ Create a new carrier @@ -505,6 +527,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/carriers", @@ -530,7 +554,11 @@ async def create( carrier_create_params.CarrierCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierCreateResponse, ) @@ -592,6 +620,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierUpdateResponse: """ Update a carrier @@ -634,6 +663,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._patch( path_template("/carriers/{carrier_id}", carrier_id=carrier_id), @@ -657,7 +688,11 @@ async def update( carrier_update_params.CarrierUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierUpdateResponse, ) @@ -734,6 +769,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CarrierDeleteResponse: """ Delete a carrier @@ -748,11 +784,17 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._delete( path_template("/carriers/{carrier_id}", carrier_id=carrier_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CarrierDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/connect/proxies.py b/src/mobilerun_sdk/resources/connect/proxies.py index 718dc6d6..34ea73fd 100644 --- a/src/mobilerun_sdk/resources/connect/proxies.py +++ b/src/mobilerun_sdk/resources/connect/proxies.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import Union +from typing import Union, Optional from datetime import datetime from typing_extensions import Literal import httpx from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -19,11 +19,13 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.connect import proxy_buy_params, proxy_list_params, proxy_list_connections_params +from ...types.connect import proxy_buy_params, proxy_list_params, proxy_update_params, proxy_list_connections_params from ...types.connect.proxy_buy_response import ProxyBuyResponse from ...types.connect.proxy_list_response import ProxyListResponse from ...types.connect.proxy_ping_response import ProxyPingResponse +from ...types.connect.proxy_update_response import ProxyUpdateResponse from ...types.connect.proxy_retrieve_response import ProxyRetrieveResponse +from ...types.connect.proxy_ensure_healthy_response import ProxyEnsureHealthyResponse from ...types.connect.proxy_list_connections_response import ProxyListConnectionsResponse __all__ = ["ProxiesResource", "AsyncProxiesResource"] @@ -84,6 +86,53 @@ def retrieve( cast_to=ProxyRetrieveResponse, ) + def update( + self, + id: str, + *, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ProxyUpdateResponse: + """ + Renames the proxy identified by the path ID. + + Args: + name: New display name, up to 64 characters excluding surrounding whitespace, and + containing no NUL. Send null (or an empty/whitespace-only string) to drop a + custom name and go back to the generated label. Omit to leave the name + unchanged. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + path_template("/connect/proxies/{id}", id=id), + body=maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ProxyUpdateResponse, + ) + def list( self, *, @@ -140,13 +189,14 @@ def buy( *, country: str, type: Literal["dedicated_residential", "residential", "mobile"], - idempotency_key: str | Omit = omit, + name: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyBuyResponse: """ Provisions a proxy of the requested type for the caller in the selected country. @@ -154,6 +204,10 @@ def buy( Args: country: ISO 3166-1 alpha-2 country code to provision the proxy in. + name: Display name for the proxy, up to 64 characters excluding surrounding + whitespace, and containing no NUL. Omit it (or send only whitespace) to get a + generated label built from the country, type, and id. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -161,19 +215,25 @@ def buy( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return self._post( "/connect/proxies", body=maybe_transform( { "country": country, "type": type, + "name": name, }, proxy_buy_params.ProxyBuyParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyBuyResponse, ) @@ -188,6 +248,7 @@ def cancel( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the proxy identified by the path ID and releases its provisioning. @@ -201,6 +262,8 @@ def cancel( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -208,11 +271,62 @@ def cancel( return self._delete( path_template("/connect/proxies/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) + def ensure_healthy( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ProxyEnsureHealthyResponse: + """ + Checks right now that the proxy's upstream accepts a connection through its + current session. If the upstream refuses the session and the proxy is a rotating + (sticky-session) proxy, the proxy is moved onto a fresh session that has been + checked to work; its credentials and country stay the same, its exit IP changes. + Intended for callers that are about to depend on the proxy (e.g. while + provisioning a phone). After `rotated`, the gateway uses the new session within + about 30 seconds. A `refused` or `unreachable` result is not an error: the + response describes the proxy's state. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/connect/proxies/{id}/ensure-healthy", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ProxyEnsureHealthyResponse, + ) + def list_connections( self, id: str, @@ -451,6 +565,53 @@ async def retrieve( cast_to=ProxyRetrieveResponse, ) + async def update( + self, + id: str, + *, + name: Optional[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ProxyUpdateResponse: + """ + Renames the proxy identified by the path ID. + + Args: + name: New display name, up to 64 characters excluding surrounding whitespace, and + containing no NUL. Send null (or an empty/whitespace-only string) to drop a + custom name and go back to the generated label. Omit to leave the name + unchanged. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + path_template("/connect/proxies/{id}", id=id), + body=await async_maybe_transform({"name": name}, proxy_update_params.ProxyUpdateParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ProxyUpdateResponse, + ) + async def list( self, *, @@ -507,13 +668,14 @@ async def buy( *, country: str, type: Literal["dedicated_residential", "residential", "mobile"], - idempotency_key: str | Omit = omit, + name: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyBuyResponse: """ Provisions a proxy of the requested type for the caller in the selected country. @@ -521,6 +683,10 @@ async def buy( Args: country: ISO 3166-1 alpha-2 country code to provision the proxy in. + name: Display name for the proxy, up to 64 characters excluding surrounding + whitespace, and containing no NUL. Omit it (or send only whitespace) to get a + generated label built from the country, type, and id. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -528,19 +694,25 @@ async def buy( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return await self._post( "/connect/proxies", body=await async_maybe_transform( { "country": country, "type": type, + "name": name, }, proxy_buy_params.ProxyBuyParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyBuyResponse, ) @@ -555,6 +727,7 @@ async def cancel( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the proxy identified by the path ID and releases its provisioning. @@ -568,6 +741,8 @@ async def cancel( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -575,11 +750,62 @@ async def cancel( return await self._delete( path_template("/connect/proxies/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) + async def ensure_healthy( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ProxyEnsureHealthyResponse: + """ + Checks right now that the proxy's upstream accepts a connection through its + current session. If the upstream refuses the session and the proxy is a rotating + (sticky-session) proxy, the proxy is moved onto a fresh session that has been + checked to work; its credentials and country stay the same, its exit IP changes. + Intended for callers that are about to depend on the proxy (e.g. while + provisioning a phone). After `rotated`, the gateway uses the new session within + about 30 seconds. A `refused` or `unreachable` result is not an error: the + response describes the proxy's state. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/connect/proxies/{id}/ensure-healthy", id=id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ProxyEnsureHealthyResponse, + ) + async def list_connections( self, id: str, @@ -770,6 +996,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.retrieve = to_raw_response_wrapper( proxies.retrieve, ) + self.update = to_raw_response_wrapper( + proxies.update, + ) self.list = to_raw_response_wrapper( proxies.list, ) @@ -779,6 +1008,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.cancel = to_raw_response_wrapper( proxies.cancel, ) + self.ensure_healthy = to_raw_response_wrapper( + proxies.ensure_healthy, + ) self.list_connections = to_raw_response_wrapper( proxies.list_connections, ) @@ -794,6 +1026,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.retrieve = async_to_raw_response_wrapper( proxies.retrieve, ) + self.update = async_to_raw_response_wrapper( + proxies.update, + ) self.list = async_to_raw_response_wrapper( proxies.list, ) @@ -803,6 +1038,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.cancel = async_to_raw_response_wrapper( proxies.cancel, ) + self.ensure_healthy = async_to_raw_response_wrapper( + proxies.ensure_healthy, + ) self.list_connections = async_to_raw_response_wrapper( proxies.list_connections, ) @@ -818,6 +1056,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.retrieve = to_streamed_response_wrapper( proxies.retrieve, ) + self.update = to_streamed_response_wrapper( + proxies.update, + ) self.list = to_streamed_response_wrapper( proxies.list, ) @@ -827,6 +1068,9 @@ def __init__(self, proxies: ProxiesResource) -> None: self.cancel = to_streamed_response_wrapper( proxies.cancel, ) + self.ensure_healthy = to_streamed_response_wrapper( + proxies.ensure_healthy, + ) self.list_connections = to_streamed_response_wrapper( proxies.list_connections, ) @@ -842,6 +1086,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.retrieve = async_to_streamed_response_wrapper( proxies.retrieve, ) + self.update = async_to_streamed_response_wrapper( + proxies.update, + ) self.list = async_to_streamed_response_wrapper( proxies.list, ) @@ -851,6 +1098,9 @@ def __init__(self, proxies: AsyncProxiesResource) -> None: self.cancel = async_to_streamed_response_wrapper( proxies.cancel, ) + self.ensure_healthy = async_to_streamed_response_wrapper( + proxies.ensure_healthy, + ) self.list_connections = async_to_streamed_response_wrapper( proxies.list_connections, ) diff --git a/src/mobilerun_sdk/resources/connect/users.py b/src/mobilerun_sdk/resources/connect/users.py index 2a030aae..ea55b1f3 100644 --- a/src/mobilerun_sdk/resources/connect/users.py +++ b/src/mobilerun_sdk/resources/connect/users.py @@ -63,6 +63,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> UserCreateResponse: """ Creates a SOCKS5 credential, optionally bound to a proxy for dedicated routing. @@ -82,6 +83,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/connect/users", @@ -94,7 +97,11 @@ def create( user_create_params.UserCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=UserCreateResponse, ) @@ -145,6 +152,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> UserUpdateResponse: """ Rebind the user to a different proxy (or detach it by passing null). @@ -160,6 +168,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -167,7 +177,11 @@ def update( path_template("/connect/users/{id}", id=id), body=maybe_transform({"proxy_id": proxy_id}, user_update_params.UserUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=UserUpdateResponse, ) @@ -234,6 +248,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the SOCKS5 user identified by the path ID, revoking its credentials and @@ -247,6 +262,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -254,7 +271,11 @@ def delete( return self._delete( path_template("/connect/users/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -440,6 +461,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> UserCreateResponse: """ Creates a SOCKS5 credential, optionally bound to a proxy for dedicated routing. @@ -459,6 +481,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/connect/users", @@ -471,7 +495,11 @@ async def create( user_create_params.UserCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=UserCreateResponse, ) @@ -522,6 +550,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> UserUpdateResponse: """ Rebind the user to a different proxy (or detach it by passing null). @@ -537,6 +566,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -544,7 +575,11 @@ async def update( path_template("/connect/users/{id}", id=id), body=await async_maybe_transform({"proxy_id": proxy_id}, user_update_params.UserUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=UserUpdateResponse, ) @@ -611,6 +646,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the SOCKS5 user identified by the path ID, revoking its credentials and @@ -624,6 +660,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -631,7 +669,11 @@ async def delete( return await self._delete( path_template("/connect/users/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py b/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py index afbe1bca..80de6019 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py +++ b/src/mobilerun_sdk/resources/credentials/packages/credentials/credentials.py @@ -69,6 +69,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CredentialCreateResponse: """ Creates a credential under the given package with a `credentialName` and at @@ -83,6 +84,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -96,7 +99,11 @@ def create( credential_create_params.CredentialCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CredentialCreateResponse, ) @@ -153,6 +160,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CredentialDeleteResponse: """ Permanently deletes the credential identified by `packageName` and @@ -166,6 +174,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -178,7 +188,11 @@ def delete( credential_name=credential_name, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CredentialDeleteResponse, ) @@ -220,6 +234,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CredentialCreateResponse: """ Creates a credential under the given package with a `credentialName` and at @@ -234,6 +249,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -247,7 +264,11 @@ async def create( credential_create_params.CredentialCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CredentialCreateResponse, ) @@ -304,6 +325,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> CredentialDeleteResponse: """ Permanently deletes the credential identified by `packageName` and @@ -317,6 +339,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -329,7 +353,11 @@ async def delete( credential_name=credential_name, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=CredentialDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py b/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py index 34acdff4..d687a94c 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py +++ b/src/mobilerun_sdk/resources/credentials/packages/credentials/fields.py @@ -60,6 +60,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldCreateResponse: """Adds a single field to an existing credential. @@ -75,6 +76,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -94,7 +97,11 @@ def create( field_create_params.FieldCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldCreateResponse, ) @@ -114,6 +121,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldUpdateResponse: """ Updates the value of an existing field on a credential, identified by @@ -128,6 +136,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -144,7 +154,11 @@ def update( ), body=maybe_transform({"value": value}, field_update_params.FieldUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldUpdateResponse, ) @@ -163,6 +177,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldDeleteResponse: """ Removes a single field of the given `fieldType` from the specified credential @@ -176,6 +191,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -191,7 +208,11 @@ def delete( field_type=field_type, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldDeleteResponse, ) @@ -232,6 +253,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldCreateResponse: """Adds a single field to an existing credential. @@ -247,6 +269,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -266,7 +290,11 @@ async def create( field_create_params.FieldCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldCreateResponse, ) @@ -286,6 +314,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldUpdateResponse: """ Updates the value of an existing field on a credential, identified by @@ -300,6 +329,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -316,7 +347,11 @@ async def update( ), body=await async_maybe_transform({"value": value}, field_update_params.FieldUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldUpdateResponse, ) @@ -335,6 +370,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FieldDeleteResponse: """ Removes a single field of the given `fieldType` from the specified credential @@ -348,6 +384,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not package_name: raise ValueError(f"Expected a non-empty value for `package_name` but received {package_name!r}") @@ -363,7 +401,11 @@ async def delete( field_type=field_type, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FieldDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/credentials/packages/packages.py b/src/mobilerun_sdk/resources/credentials/packages/packages.py index ba676828..3476d7f5 100644 --- a/src/mobilerun_sdk/resources/credentials/packages/packages.py +++ b/src/mobilerun_sdk/resources/credentials/packages/packages.py @@ -65,6 +65,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> PackageCreateResponse: """ Creates a new package (identified by `packageName`) under which credentials can @@ -79,12 +80,18 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/credentials/packages", body=maybe_transform({"package_name": package_name}, package_create_params.PackageCreateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=PackageCreateResponse, ) @@ -182,6 +189,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> PackageCreateResponse: """ Creates a new package (identified by `packageName`) under which credentials can @@ -196,12 +204,18 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/credentials/packages", body=await async_maybe_transform({"package_name": package_name}, package_create_params.PackageCreateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=PackageCreateResponse, ) diff --git a/src/mobilerun_sdk/resources/devices/actions.py b/src/mobilerun_sdk/resources/devices/actions.py index 8ebefb67..2290a2b8 100644 --- a/src/mobilerun_sdk/resources/devices/actions.py +++ b/src/mobilerun_sdk/resources/devices/actions.py @@ -58,6 +58,7 @@ def global_( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Performs a global system action on the device, such as navigating back or going @@ -71,6 +72,8 @@ def global_( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -85,7 +88,11 @@ def global_( path_template("/devices/{device_id}/global", device_id=device_id), body=maybe_transform({"action": action}, action_global_params.ActionGlobalParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -142,6 +149,7 @@ def set_overlay_visible( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Shows or hides the accessibility overlay on the device based on the visibility @@ -155,6 +163,8 @@ def set_overlay_visible( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -169,7 +179,11 @@ def set_overlay_visible( path_template("/devices/{device_id}/overlay", device_id=device_id), body=maybe_transform({"visible": visible}, action_set_overlay_visible_params.ActionSetOverlayVisibleParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -191,6 +205,7 @@ def swipe( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Swipes from a start coordinate to an end coordinate over the given duration in @@ -207,6 +222,8 @@ def swipe( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -231,7 +248,11 @@ def swipe( action_swipe_params.ActionSwipeParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -250,6 +271,7 @@ def tap( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Taps the device screen at the given x/y coordinates. @@ -264,6 +286,8 @@ def tap( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -285,7 +309,11 @@ def tap( action_tap_params.ActionTapParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -323,6 +351,7 @@ async def global_( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Performs a global system action on the device, such as navigating back or going @@ -336,6 +365,8 @@ async def global_( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -350,7 +381,11 @@ async def global_( path_template("/devices/{device_id}/global", device_id=device_id), body=await async_maybe_transform({"action": action}, action_global_params.ActionGlobalParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -407,6 +442,7 @@ async def set_overlay_visible( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Shows or hides the accessibility overlay on the device based on the visibility @@ -420,6 +456,8 @@ async def set_overlay_visible( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -436,7 +474,11 @@ async def set_overlay_visible( {"visible": visible}, action_set_overlay_visible_params.ActionSetOverlayVisibleParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -458,6 +500,7 @@ async def swipe( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Swipes from a start coordinate to an end coordinate over the given duration in @@ -474,6 +517,8 @@ async def swipe( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -498,7 +543,11 @@ async def swipe( action_swipe_params.ActionSwipeParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -517,6 +566,7 @@ async def tap( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Taps the device screen at the given x/y coordinates. @@ -531,6 +581,8 @@ async def tap( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -552,7 +604,11 @@ async def tap( action_tap_params.ActionTapParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/apps.py b/src/mobilerun_sdk/resources/devices/apps.py index e672a017..2f069d0a 100644 --- a/src/mobilerun_sdk/resources/devices/apps.py +++ b/src/mobilerun_sdk/resources/devices/apps.py @@ -111,6 +111,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Uninstalls the app identified by the path package name from the device. @@ -124,6 +125,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -139,7 +142,11 @@ def delete( return self._delete( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -157,6 +164,7 @@ def grant_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Grants an Android runtime permission to the package named in the path. @@ -173,6 +181,8 @@ def grant_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -195,7 +205,11 @@ def grant_permission( permission=permission, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -217,6 +231,7 @@ def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Requests an app install on the device. @@ -253,6 +268,8 @@ def install( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -273,6 +290,7 @@ def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Requests an app install on the device. @@ -309,6 +327,8 @@ def install( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -329,6 +349,7 @@ def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -352,7 +373,11 @@ def install( app_install_params.AppInstallParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -414,6 +439,7 @@ def revoke_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Revokes an Android runtime permission from the package named in the path. @@ -430,6 +456,8 @@ def revoke_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -452,7 +480,11 @@ def revoke_permission( permission=permission, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -470,6 +502,7 @@ def start( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Launches the app identified by the path package name, optionally starting a @@ -484,6 +517,8 @@ def start( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -500,7 +535,11 @@ def start( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), body=maybe_transform({"activity": activity}, app_start_params.AppStartParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -518,6 +557,7 @@ def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Force-stops the app identified by the path package name. @@ -535,6 +575,8 @@ def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -551,7 +593,11 @@ def stop( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), body=maybe_transform({"clear_data": clear_data}, app_stop_params.AppStopParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -643,6 +689,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Uninstalls the app identified by the path package name from the device. @@ -656,6 +703,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -671,7 +720,11 @@ async def delete( return await self._delete( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -689,6 +742,7 @@ async def grant_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Grants an Android runtime permission to the package named in the path. @@ -705,6 +759,8 @@ async def grant_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -727,7 +783,11 @@ async def grant_permission( permission=permission, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -749,6 +809,7 @@ async def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Requests an app install on the device. @@ -785,6 +846,8 @@ async def install( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -805,6 +868,7 @@ async def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Requests an app install on the device. @@ -841,6 +905,8 @@ async def install( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -861,6 +927,7 @@ async def install( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -884,7 +951,11 @@ async def install( app_install_params.AppInstallParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -946,6 +1017,7 @@ async def revoke_permission( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Revokes an Android runtime permission from the package named in the path. @@ -962,6 +1034,8 @@ async def revoke_permission( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -984,7 +1058,11 @@ async def revoke_permission( permission=permission, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1002,6 +1080,7 @@ async def start( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Launches the app identified by the path package name, optionally starting a @@ -1016,6 +1095,8 @@ async def start( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1032,7 +1113,11 @@ async def start( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), body=await async_maybe_transform({"activity": activity}, app_start_params.AppStartParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1050,6 +1135,7 @@ async def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Force-stops the app identified by the path package name. @@ -1067,6 +1153,8 @@ async def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1083,7 +1171,11 @@ async def stop( path_template("/devices/{device_id}/apps/{package_name}", device_id=device_id, package_name=package_name), body=await async_maybe_transform({"clear_data": clear_data}, app_stop_params.AppStopParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/browser.py b/src/mobilerun_sdk/resources/devices/browser.py index 42eca5bb..7a15fb92 100644 --- a/src/mobilerun_sdk/resources/devices/browser.py +++ b/src/mobilerun_sdk/resources/devices/browser.py @@ -53,11 +53,18 @@ def execute_script( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> BrowserExecuteScriptResponse: """ - Evaluates a JavaScript expression in the device's foreground Chrome tab via the - Chrome DevTools Protocol and returns its JSON-serialized result. Devices without - browser support return an unsupported-feature error. + Evaluates a JavaScript expression exactly once in the device's single, confirmed + foreground Chrome tab via the Chrome DevTools Protocol and returns its + JSON-serialized result. A responsive browser state without exactly one visible + page returns DEVICE_NO_BROWSER_TARGET (400). Transient discovery, page-dial, or + visibility-probe transport failures return CDP_TRANSPORT_UNAVAILABLE (503) with + Retry-After and an executionOutcome; the service may start Chrome once and retry + selection before Runtime.evaluate, but dial or probe recovery is skipped for a + backgrounded Chrome, and a user script is never retried after its write or read. + Devices without browser support return an unsupported-feature error. Args: script: JavaScript expression to evaluate in the device's foreground Chrome tab (CDP @@ -72,6 +79,8 @@ def execute_script( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -85,7 +94,11 @@ def execute_script( path_template("/devices/{device_id}/browser/execute-script", device_id=device_id), body=maybe_transform({"script": script}, browser_execute_script_params.BrowserExecuteScriptParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=BrowserExecuteScriptResponse, ) @@ -123,11 +136,18 @@ async def execute_script( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> BrowserExecuteScriptResponse: """ - Evaluates a JavaScript expression in the device's foreground Chrome tab via the - Chrome DevTools Protocol and returns its JSON-serialized result. Devices without - browser support return an unsupported-feature error. + Evaluates a JavaScript expression exactly once in the device's single, confirmed + foreground Chrome tab via the Chrome DevTools Protocol and returns its + JSON-serialized result. A responsive browser state without exactly one visible + page returns DEVICE_NO_BROWSER_TARGET (400). Transient discovery, page-dial, or + visibility-probe transport failures return CDP_TRANSPORT_UNAVAILABLE (503) with + Retry-After and an executionOutcome; the service may start Chrome once and retry + selection before Runtime.evaluate, but dial or probe recovery is skipped for a + backgrounded Chrome, and a user script is never retried after its write or read. + Devices without browser support return an unsupported-feature error. Args: script: JavaScript expression to evaluate in the device's foreground Chrome tab (CDP @@ -142,6 +162,8 @@ async def execute_script( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -157,7 +179,11 @@ async def execute_script( {"script": script}, browser_execute_script_params.BrowserExecuteScriptParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=BrowserExecuteScriptResponse, ) diff --git a/src/mobilerun_sdk/resources/devices/clipboard.py b/src/mobilerun_sdk/resources/devices/clipboard.py index 7c641500..f3f1a041 100644 --- a/src/mobilerun_sdk/resources/devices/clipboard.py +++ b/src/mobilerun_sdk/resources/devices/clipboard.py @@ -95,6 +95,7 @@ def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Replaces the device's clipboard content with the text in the request body; an @@ -109,6 +110,8 @@ def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -123,7 +126,11 @@ def set( path_template("/devices/{device_id}/clipboard", device_id=device_id), body=maybe_transform({"text": text}, clipboard_set_params.ClipboardSetParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -203,6 +210,7 @@ async def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Replaces the device's clipboard content with the text in the request body; an @@ -217,6 +225,8 @@ async def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -231,7 +241,11 @@ async def set( path_template("/devices/{device_id}/clipboard", device_id=device_id), body=await async_maybe_transform({"text": text}, clipboard_set_params.ClipboardSetParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/deep_link.py b/src/mobilerun_sdk/resources/devices/deep_link.py index ceee3bb9..cc5cbd45 100644 --- a/src/mobilerun_sdk/resources/devices/deep_link.py +++ b/src/mobilerun_sdk/resources/devices/deep_link.py @@ -55,6 +55,7 @@ def execute_deep_link( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Opens a deep link on the device. @@ -80,6 +81,8 @@ def execute_deep_link( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -102,7 +105,11 @@ def execute_deep_link( deep_link_execute_deep_link_params.DeepLinkExecuteDeepLinkParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -143,6 +150,7 @@ async def execute_deep_link( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Opens a deep link on the device. @@ -168,6 +176,8 @@ async def execute_deep_link( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -190,7 +200,11 @@ async def execute_deep_link( deep_link_execute_deep_link_params.DeepLinkExecuteDeepLinkParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/devices.py b/src/mobilerun_sdk/resources/devices/devices.py index beb5138b..a3aac3f3 100644 --- a/src/mobilerun_sdk/resources/devices/devices.py +++ b/src/mobilerun_sdk/resources/devices/devices.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import List, Union, Optional from datetime import datetime from typing_extensions import Literal @@ -56,7 +57,13 @@ TasksResourceWithStreamingResponse, AsyncTasksResourceWithStreamingResponse, ) -from ...types import device_list_params, device_create_params, device_set_name_params, device_terminate_params +from ...types import ( + device_list_params, + device_create_params, + device_summary_params, + device_set_name_params, + device_terminate_params, +) from .actions import ( ActionsResource, AsyncActionsResource, @@ -184,6 +191,7 @@ from ...types.device_count_response import DeviceCountResponse from ...types.device_create_response import DeviceCreateResponse from ...types.shared_params.location import Location +from ...types.device_summary_response import DeviceSummaryResponse from ...types.device_retrieve_response import DeviceRetrieveResponse from ...types.device_set_name_response import DeviceSetNameResponse from ...types.device_wait_ready_response import DeviceWaitReadyResponse @@ -300,14 +308,7 @@ def create( *, billing: Literal["auto", "subscription", "minute"] | Omit = omit, query_country: str | Omit = omit, - device_type: Literal[ - "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", - ] - | Omit = omit, + device_type: str | Omit = omit, profile_id: str | Omit = omit, android_version: int | Omit = omit, apps: Optional[SequenceNotStr[str]] | Omit = omit, @@ -326,28 +327,31 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> DeviceCreateResponse: - """ - Requests a new device for the authenticated user from the device spec in the - request body. Optional query parameters select the canonical device type, target - country, billing mode, and a profile to use as the base spec; deprecated - device-type aliases remain accepted only during the documented compatibility - grace period. The response returns the device and its stream token. + """Requests a new device from the specification in the request body. + + Optional query + parameters select the canonical device type, country, billing mode, and base + profile. Returns the device, its resolved billing strategy, and its stream + token. Args: - billing: Billing mode. 'auto' uses a subscription slot when available and otherwise bills - per minute; 'subscription' requires an available subscription slot; 'minute' - bills per minute. Only cloud phone and cloud emulator devices support per-minute - billing. + billing: Billing mode. 'auto' tries subscription first, then minute billing if no + subscription entitlement exists or all subscription slots are in use, provided + minute billing is enabled. When subscription billing is disabled, auto uses + minutes directly. Billing-service failures never trigger fallback. + 'subscription' requires an available subscription slot and never falls back. + 'minute' uses minute billing only, subject to balance and concurrency checks. + Modes depend on the device type's billing configuration. query_country: ISO 3166-1 alpha-2 country code. If omitted the system picks the country with the most availability. - device_type: - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + device_type: Use android*cloud_phone for a cloud Android phone. Only canonical identifiers + are accepted. Other backends are deployment-specific; recognized but unavailable + types return DEVICE_TYPE_UNAVAILABLE (422). Retired dedicated*\\** aliases are + rejected with a canonical replacement. profile_id: Profile ID to use as device spec @@ -358,6 +362,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/devices", @@ -382,6 +388,7 @@ def create( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=maybe_transform( { "billing": billing, @@ -441,6 +448,7 @@ def list( order_by_direction: Literal["asc", "desc"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, + platform: Literal["android", "ios"] | Omit = omit, provider_id: str | Omit = omit, state: Optional[ List[ @@ -461,10 +469,11 @@ def list( | Omit = omit, type: Literal[ "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", + "android_physical_phone", + "ios_stealth_phone", + "android_emulator", + "ios_simulator", + "device_slot", ] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -483,11 +492,11 @@ def list( mine: When true, only return devices created by the calling user (resolved from X-User-ID, never a client-supplied id). - type: - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + platform: Filter by the device's platform as served in the platform field (runtime, + announced, else type-derived). + + type: Canonical device type. Retired dedicated\\__\\** aliases are no longer accepted. + Availability depends on the deployment. extra_headers: Send extra headers @@ -514,6 +523,7 @@ def list( "order_by_direction": order_by_direction, "page": page, "page_size": page_size, + "platform": platform, "provider_id": provider_id, "state": state, "type": type, @@ -524,6 +534,7 @@ def list( cast_to=DeviceListResponse, ) + @typing_extensions.deprecated("deprecated") def count( self, *, @@ -534,7 +545,12 @@ def count( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DeviceCountResponse: - """Returns the number of claimed devices for the user, broken down by device type.""" + """Deprecated: use GET /devices/summary instead. + + Returns the number of active + claimed devices for the user, broken down by device type, in the legacy response + shape. + """ return self._get( "/devices/count", options=make_request_options( @@ -595,6 +611,7 @@ def reboot( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Triggers a reboot of the device. @@ -609,6 +626,8 @@ def reboot( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -616,7 +635,11 @@ def reboot( return self._post( path_template("/devices/{device_id}/reboot", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -631,6 +654,7 @@ def reset( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Resets the device back to a clean state, clearing installed apps and user data @@ -645,6 +669,8 @@ def reset( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -652,7 +678,11 @@ def reset( return self._post( path_template("/devices/{device_id}/reset", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -667,6 +697,7 @@ def resume( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Wakes a parked device: backend readiness and any required capacity are @@ -681,6 +712,8 @@ def resume( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -688,7 +721,11 @@ def resume( return self._post( path_template("/devices/{device_id}/resume", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -742,6 +779,7 @@ def set_name( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> DeviceSetNameResponse: """ Sets the display name for a device from the name in the request body and returns @@ -755,6 +793,8 @@ def set_name( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -762,7 +802,11 @@ def set_name( path_template("/devices/{device_id}/name", device_id=device_id), body=maybe_transform({"name": name}, device_set_name_params.DeviceSetNameParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=DeviceSetNameResponse, ) @@ -777,6 +821,7 @@ def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Parks the device: its data, apps and identity are kept, but nothing runs and @@ -791,6 +836,8 @@ def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -798,11 +845,67 @@ def stop( return self._post( path_template("/devices/{device_id}/stop", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) + def summary( + self, + *, + state: Optional[ + List[ + Literal[ + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "stopped", + "unknown", + ] + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeviceSummaryResponse: + """ + Returns the total number of the user's devices and counts grouped by current + state and device type. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get( + "/devices/summary", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"state": state}, device_summary_params.DeviceSummaryParams), + ), + cast_to=DeviceSummaryResponse, + ) + def terminate( self, device_id: str, @@ -815,6 +918,7 @@ def terminate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Terminates the device and releases its resources. @@ -830,6 +934,8 @@ def terminate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -844,7 +950,11 @@ def terminate( device_terminate_params.DeviceTerminateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -990,14 +1100,7 @@ async def create( *, billing: Literal["auto", "subscription", "minute"] | Omit = omit, query_country: str | Omit = omit, - device_type: Literal[ - "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", - ] - | Omit = omit, + device_type: str | Omit = omit, profile_id: str | Omit = omit, android_version: int | Omit = omit, apps: Optional[SequenceNotStr[str]] | Omit = omit, @@ -1016,28 +1119,31 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> DeviceCreateResponse: - """ - Requests a new device for the authenticated user from the device spec in the - request body. Optional query parameters select the canonical device type, target - country, billing mode, and a profile to use as the base spec; deprecated - device-type aliases remain accepted only during the documented compatibility - grace period. The response returns the device and its stream token. + """Requests a new device from the specification in the request body. + + Optional query + parameters select the canonical device type, country, billing mode, and base + profile. Returns the device, its resolved billing strategy, and its stream + token. Args: - billing: Billing mode. 'auto' uses a subscription slot when available and otherwise bills - per minute; 'subscription' requires an available subscription slot; 'minute' - bills per minute. Only cloud phone and cloud emulator devices support per-minute - billing. + billing: Billing mode. 'auto' tries subscription first, then minute billing if no + subscription entitlement exists or all subscription slots are in use, provided + minute billing is enabled. When subscription billing is disabled, auto uses + minutes directly. Billing-service failures never trigger fallback. + 'subscription' requires an available subscription slot and never falls back. + 'minute' uses minute billing only, subject to balance and concurrency checks. + Modes depend on the device type's billing configuration. query_country: ISO 3166-1 alpha-2 country code. If omitted the system picks the country with the most availability. - device_type: - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + device_type: Use android*cloud_phone for a cloud Android phone. Only canonical identifiers + are accepted. Other backends are deployment-specific; recognized but unavailable + types return DEVICE_TYPE_UNAVAILABLE (422). Retired dedicated*\\** aliases are + rejected with a canonical replacement. profile_id: Profile ID to use as device spec @@ -1048,6 +1154,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/devices", @@ -1072,6 +1180,7 @@ async def create( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=await async_maybe_transform( { "billing": billing, @@ -1131,6 +1240,7 @@ async def list( order_by_direction: Literal["asc", "desc"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, + platform: Literal["android", "ios"] | Omit = omit, provider_id: str | Omit = omit, state: Optional[ List[ @@ -1151,10 +1261,11 @@ async def list( | Omit = omit, type: Literal[ "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", + "android_physical_phone", + "ios_stealth_phone", + "android_emulator", + "ios_simulator", + "device_slot", ] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1173,11 +1284,11 @@ async def list( mine: When true, only return devices created by the calling user (resolved from X-User-ID, never a client-supplied id). - type: - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + platform: Filter by the device's platform as served in the platform field (runtime, + announced, else type-derived). + + type: Canonical device type. Retired dedicated\\__\\** aliases are no longer accepted. + Availability depends on the deployment. extra_headers: Send extra headers @@ -1204,6 +1315,7 @@ async def list( "order_by_direction": order_by_direction, "page": page, "page_size": page_size, + "platform": platform, "provider_id": provider_id, "state": state, "type": type, @@ -1214,6 +1326,7 @@ async def list( cast_to=DeviceListResponse, ) + @typing_extensions.deprecated("deprecated") async def count( self, *, @@ -1224,7 +1337,12 @@ async def count( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> DeviceCountResponse: - """Returns the number of claimed devices for the user, broken down by device type.""" + """Deprecated: use GET /devices/summary instead. + + Returns the number of active + claimed devices for the user, broken down by device type, in the legacy response + shape. + """ return await self._get( "/devices/count", options=make_request_options( @@ -1285,6 +1403,7 @@ async def reboot( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Triggers a reboot of the device. @@ -1299,6 +1418,8 @@ async def reboot( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1306,7 +1427,11 @@ async def reboot( return await self._post( path_template("/devices/{device_id}/reboot", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1321,6 +1446,7 @@ async def reset( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Resets the device back to a clean state, clearing installed apps and user data @@ -1335,6 +1461,8 @@ async def reset( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1342,7 +1470,11 @@ async def reset( return await self._post( path_template("/devices/{device_id}/reset", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1357,6 +1489,7 @@ async def resume( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Wakes a parked device: backend readiness and any required capacity are @@ -1371,6 +1504,8 @@ async def resume( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1378,7 +1513,11 @@ async def resume( return await self._post( path_template("/devices/{device_id}/resume", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1432,6 +1571,7 @@ async def set_name( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> DeviceSetNameResponse: """ Sets the display name for a device from the name in the request body and returns @@ -1445,6 +1585,8 @@ async def set_name( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1452,7 +1594,11 @@ async def set_name( path_template("/devices/{device_id}/name", device_id=device_id), body=await async_maybe_transform({"name": name}, device_set_name_params.DeviceSetNameParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=DeviceSetNameResponse, ) @@ -1467,6 +1613,7 @@ async def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Parks the device: its data, apps and identity are kept, but nothing runs and @@ -1481,6 +1628,8 @@ async def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1488,11 +1637,67 @@ async def stop( return await self._post( path_template("/devices/{device_id}/stop", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) + async def summary( + self, + *, + state: Optional[ + List[ + Literal[ + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "stopped", + "unknown", + ] + ] + ] + | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> DeviceSummaryResponse: + """ + Returns the total number of the user's devices and counts grouped by current + state and device type. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/devices/summary", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform({"state": state}, device_summary_params.DeviceSummaryParams), + ), + cast_to=DeviceSummaryResponse, + ) + async def terminate( self, device_id: str, @@ -1505,6 +1710,7 @@ async def terminate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Terminates the device and releases its resources. @@ -1520,6 +1726,8 @@ async def terminate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -1534,7 +1742,11 @@ async def terminate( device_terminate_params.DeviceTerminateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -1588,8 +1800,10 @@ def __init__(self, devices: DevicesResource) -> None: self.list = to_raw_response_wrapper( devices.list, ) - self.count = to_raw_response_wrapper( - devices.count, + self.count = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + devices.count, # pyright: ignore[reportDeprecated], + ) ) self.fingerprint = to_raw_response_wrapper( devices.fingerprint, @@ -1612,6 +1826,9 @@ def __init__(self, devices: DevicesResource) -> None: self.stop = to_raw_response_wrapper( devices.stop, ) + self.summary = to_raw_response_wrapper( + devices.summary, + ) self.terminate = to_raw_response_wrapper( devices.terminate, ) @@ -1713,8 +1930,10 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.list = async_to_raw_response_wrapper( devices.list, ) - self.count = async_to_raw_response_wrapper( - devices.count, + self.count = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + devices.count, # pyright: ignore[reportDeprecated], + ) ) self.fingerprint = async_to_raw_response_wrapper( devices.fingerprint, @@ -1737,6 +1956,9 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.stop = async_to_raw_response_wrapper( devices.stop, ) + self.summary = async_to_raw_response_wrapper( + devices.summary, + ) self.terminate = async_to_raw_response_wrapper( devices.terminate, ) @@ -1838,8 +2060,10 @@ def __init__(self, devices: DevicesResource) -> None: self.list = to_streamed_response_wrapper( devices.list, ) - self.count = to_streamed_response_wrapper( - devices.count, + self.count = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + devices.count, # pyright: ignore[reportDeprecated], + ) ) self.fingerprint = to_streamed_response_wrapper( devices.fingerprint, @@ -1862,6 +2086,9 @@ def __init__(self, devices: DevicesResource) -> None: self.stop = to_streamed_response_wrapper( devices.stop, ) + self.summary = to_streamed_response_wrapper( + devices.summary, + ) self.terminate = to_streamed_response_wrapper( devices.terminate, ) @@ -1963,8 +2190,10 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.list = async_to_streamed_response_wrapper( devices.list, ) - self.count = async_to_streamed_response_wrapper( - devices.count, + self.count = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + devices.count, # pyright: ignore[reportDeprecated], + ) ) self.fingerprint = async_to_streamed_response_wrapper( devices.fingerprint, @@ -1987,6 +2216,9 @@ def __init__(self, devices: AsyncDevicesResource) -> None: self.stop = async_to_streamed_response_wrapper( devices.stop, ) + self.summary = async_to_streamed_response_wrapper( + devices.summary, + ) self.terminate = async_to_streamed_response_wrapper( devices.terminate, ) diff --git a/src/mobilerun_sdk/resources/devices/esim/apn.py b/src/mobilerun_sdk/resources/devices/esim/apn.py index 38703c1f..1ca493a8 100644 --- a/src/mobilerun_sdk/resources/devices/esim/apn.py +++ b/src/mobilerun_sdk/resources/devices/esim/apn.py @@ -97,6 +97,7 @@ def select( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Marks an existing APN, identified by apnId, as the preferred APN for the given @@ -110,6 +111,8 @@ def select( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -130,7 +133,11 @@ def select( apn_select_params.ApnSelectParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -154,6 +161,7 @@ def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Creates an access point name (APN) from the request body and applies it to the @@ -168,6 +176,8 @@ def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -194,7 +204,11 @@ def set( apn_set_params.ApnSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -274,6 +288,7 @@ async def select( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Marks an existing APN, identified by apnId, as the preferred APN for the given @@ -287,6 +302,8 @@ async def select( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -307,7 +324,11 @@ async def select( apn_select_params.ApnSelectParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -331,6 +352,7 @@ async def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Creates an access point name (APN) from the request body and applies it to the @@ -345,6 +367,8 @@ async def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -371,7 +395,11 @@ async def set( apn_set_params.ApnSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/esim/esim.py b/src/mobilerun_sdk/resources/devices/esim/esim.py index 924d895c..99a01d8f 100644 --- a/src/mobilerun_sdk/resources/devices/esim/esim.py +++ b/src/mobilerun_sdk/resources/devices/esim/esim.py @@ -112,6 +112,7 @@ def activate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EsimActivateResponse: """ Download profile and/or enable subscription. @@ -128,6 +129,8 @@ def activate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -149,7 +152,11 @@ def activate( esim_activate_params.EsimActivateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EsimActivateResponse, ) @@ -166,6 +173,7 @@ def enable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Enables the eSIM subscription identified by the subId in the request body so it @@ -179,6 +187,8 @@ def enable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -193,7 +203,11 @@ def enable( path_template("/devices/{device_id}/esim", device_id=device_id), body=maybe_transform({"sub_id": sub_id}, esim_enable_params.EsimEnableParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -210,6 +224,7 @@ def remove( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the eSIM subscription identified by the subId query parameter from the @@ -223,6 +238,8 @@ def remove( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -240,6 +257,7 @@ def remove( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=maybe_transform({"sub_id": sub_id}, esim_remove_params.EsimRemoveParams), ), cast_to=NoneType, @@ -257,6 +275,7 @@ def set_roaming( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Enables or disables data roaming for the device's eSIM based on the enabled flag @@ -270,6 +289,8 @@ def set_roaming( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -284,7 +305,11 @@ def set_roaming( path_template("/devices/{device_id}/esim/roaming", device_id=device_id), body=maybe_transform({"enabled": enabled}, esim_set_roaming_params.EsimSetRoamingParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -409,6 +434,7 @@ async def activate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EsimActivateResponse: """ Download profile and/or enable subscription. @@ -425,6 +451,8 @@ async def activate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -446,7 +474,11 @@ async def activate( esim_activate_params.EsimActivateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EsimActivateResponse, ) @@ -463,6 +495,7 @@ async def enable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Enables the eSIM subscription identified by the subId in the request body so it @@ -476,6 +509,8 @@ async def enable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -490,7 +525,11 @@ async def enable( path_template("/devices/{device_id}/esim", device_id=device_id), body=await async_maybe_transform({"sub_id": sub_id}, esim_enable_params.EsimEnableParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -507,6 +546,7 @@ async def remove( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the eSIM subscription identified by the subId query parameter from the @@ -520,6 +560,8 @@ async def remove( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -537,6 +579,7 @@ async def remove( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=await async_maybe_transform({"sub_id": sub_id}, esim_remove_params.EsimRemoveParams), ), cast_to=NoneType, @@ -554,6 +597,7 @@ async def set_roaming( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Enables or disables data roaming for the device's eSIM based on the enabled flag @@ -567,6 +611,8 @@ async def set_roaming( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -581,7 +627,11 @@ async def set_roaming( path_template("/devices/{device_id}/esim/roaming", device_id=device_id), body=await async_maybe_transform({"enabled": enabled}, esim_set_roaming_params.EsimSetRoamingParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/files.py b/src/mobilerun_sdk/resources/devices/files.py index d39f4a3d..3568090d 100644 --- a/src/mobilerun_sdk/resources/devices/files.py +++ b/src/mobilerun_sdk/resources/devices/files.py @@ -102,6 +102,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the file at the path given in the path query parameter from the device. @@ -114,6 +115,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -131,6 +134,7 @@ def delete( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=maybe_transform({"path": path}, file_delete_params.FileDeleteParams), ), cast_to=NoneType, @@ -195,6 +199,7 @@ def upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Uploads a file to the device via multipart form data, writing it into the @@ -208,6 +213,8 @@ def upload( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -233,6 +240,7 @@ def upload( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=maybe_transform({"path": path}, file_upload_params.FileUploadParams), ), cast_to=NoneType, @@ -317,6 +325,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Deletes the file at the path given in the path query parameter from the device. @@ -329,6 +338,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -346,6 +357,7 @@ async def delete( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=await async_maybe_transform({"path": path}, file_delete_params.FileDeleteParams), ), cast_to=NoneType, @@ -410,6 +422,7 @@ async def upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Uploads a file to the device via multipart form data, writing it into the @@ -423,6 +436,8 @@ async def upload( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -448,6 +463,7 @@ async def upload( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + idempotency_key=idempotency_key, query=await async_maybe_transform({"path": path}, file_upload_params.FileUploadParams), ), cast_to=NoneType, diff --git a/src/mobilerun_sdk/resources/devices/keyboard.py b/src/mobilerun_sdk/resources/devices/keyboard.py index dd5439ce..aaacee83 100644 --- a/src/mobilerun_sdk/resources/devices/keyboard.py +++ b/src/mobilerun_sdk/resources/devices/keyboard.py @@ -53,6 +53,7 @@ def clear( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Clears the contents of the currently focused text input field. @@ -65,6 +66,8 @@ def clear( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -78,7 +81,11 @@ def clear( return self._delete( path_template("/devices/{device_id}/keyboard", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -95,6 +102,7 @@ def key( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Sends a single Android key event to the device, identified by its key code. @@ -107,6 +115,8 @@ def key( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -121,7 +131,11 @@ def key( path_template("/devices/{device_id}/keyboard", device_id=device_id), body=maybe_transform({"key": key}, keyboard_key_params.KeyboardKeyParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -143,6 +157,7 @@ def write( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Types text into the focused input field. @@ -166,6 +181,8 @@ def write( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -190,7 +207,11 @@ def write( keyboard_write_params.KeyboardWriteParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -227,6 +248,7 @@ async def clear( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Clears the contents of the currently focused text input field. @@ -239,6 +261,8 @@ async def clear( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -252,7 +276,11 @@ async def clear( return await self._delete( path_template("/devices/{device_id}/keyboard", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -269,6 +297,7 @@ async def key( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Sends a single Android key event to the device, identified by its key code. @@ -281,6 +310,8 @@ async def key( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -295,7 +326,11 @@ async def key( path_template("/devices/{device_id}/keyboard", device_id=device_id), body=await async_maybe_transform({"key": key}, keyboard_key_params.KeyboardKeyParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -317,6 +352,7 @@ async def write( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Types text into the focused input field. @@ -340,6 +376,8 @@ async def write( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -364,7 +402,11 @@ async def write( keyboard_write_params.KeyboardWriteParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/kiosk.py b/src/mobilerun_sdk/resources/devices/kiosk.py index 8739ddca..ea95d93f 100644 --- a/src/mobilerun_sdk/resources/devices/kiosk.py +++ b/src/mobilerun_sdk/resources/devices/kiosk.py @@ -51,6 +51,7 @@ def disable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Disables Android lock-task (kiosk) mode on the device, releasing it from the @@ -64,6 +65,8 @@ def disable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -77,7 +80,11 @@ def disable( return self._delete( path_template("/devices/{device_id}/kiosk", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -94,6 +101,7 @@ def enable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Locks the device to the package named in the request body using Android @@ -109,6 +117,8 @@ def enable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -123,7 +133,11 @@ def enable( path_template("/devices/{device_id}/kiosk", device_id=device_id), body=maybe_transform({"package_name": package_name}, kiosk_enable_params.KioskEnableParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -160,6 +174,7 @@ async def disable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Disables Android lock-task (kiosk) mode on the device, releasing it from the @@ -173,6 +188,8 @@ async def disable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -186,7 +203,11 @@ async def disable( return await self._delete( path_template("/devices/{device_id}/kiosk", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -203,6 +224,7 @@ async def enable( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Locks the device to the package named in the request body using Android @@ -218,6 +240,8 @@ async def enable( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -232,7 +256,11 @@ async def enable( path_template("/devices/{device_id}/kiosk", device_id=device_id), body=await async_maybe_transform({"package_name": package_name}, kiosk_enable_params.KioskEnableParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/language.py b/src/mobilerun_sdk/resources/devices/language.py index 608f612a..5b49b5e4 100644 --- a/src/mobilerun_sdk/resources/devices/language.py +++ b/src/mobilerun_sdk/resources/devices/language.py @@ -94,6 +94,7 @@ def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Sets the device language/locale to the BCP-47 locale in the request body. @@ -115,6 +116,8 @@ def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -135,7 +138,11 @@ def set( language_set_params.LanguageSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -214,6 +221,7 @@ async def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Sets the device language/locale to the BCP-47 locale in the request body. @@ -235,6 +243,8 @@ async def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -255,7 +265,11 @@ async def set( language_set_params.LanguageSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/location.py b/src/mobilerun_sdk/resources/devices/location.py index c63dd1d7..3975a8d1 100644 --- a/src/mobilerun_sdk/resources/devices/location.py +++ b/src/mobilerun_sdk/resources/devices/location.py @@ -93,6 +93,7 @@ def reset( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Clears any simulated GPS location and restores the device's default location @@ -106,6 +107,8 @@ def reset( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -119,7 +122,11 @@ def reset( return self._delete( path_template("/devices/{device_id}/location", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -137,6 +144,7 @@ def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Sets the device's simulated GPS location to the latitude and longitude in the @@ -150,6 +158,8 @@ def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -170,7 +180,11 @@ def set( location_set_params.LocationSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -248,6 +262,7 @@ async def reset( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Clears any simulated GPS location and restores the device's default location @@ -261,6 +276,8 @@ async def reset( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -274,7 +291,11 @@ async def reset( return await self._delete( path_template("/devices/{device_id}/location", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -292,6 +313,7 @@ async def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Sets the device's simulated GPS location to the latitude and longitude in the @@ -305,6 +327,8 @@ async def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -325,7 +349,11 @@ async def set( location_set_params.LocationSetParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/media_sessions.py b/src/mobilerun_sdk/resources/devices/media_sessions.py index ab9161e2..6255b573 100644 --- a/src/mobilerun_sdk/resources/devices/media_sessions.py +++ b/src/mobilerun_sdk/resources/devices/media_sessions.py @@ -55,6 +55,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MediaSessionCreateResponse: """ Allocates an exclusive relay path and returns one-time publish and control @@ -73,6 +74,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -86,7 +89,11 @@ def create( media_session_create_params.MediaSessionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MediaSessionCreateResponse, ) @@ -103,6 +110,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Immediately revokes relay authorization, detaches virtual media inputs, and @@ -116,6 +124,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -128,7 +138,11 @@ def delete( "/devices/{device_id}/media-sessions/{session_id}", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -145,6 +159,7 @@ def activate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MediaSessionActivateResponse: """ Validates relay codecs and attaches one private RTSP source to either the @@ -158,6 +173,8 @@ def activate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -169,7 +186,11 @@ def activate( "/devices/{device_id}/media-sessions/{session_id}/activate", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MediaSessionActivateResponse, ) @@ -242,6 +263,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MediaSessionCreateResponse: """ Allocates an exclusive relay path and returns one-time publish and control @@ -260,6 +282,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -273,7 +297,11 @@ async def create( media_session_create_params.MediaSessionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MediaSessionCreateResponse, ) @@ -290,6 +318,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Immediately revokes relay authorization, detaches virtual media inputs, and @@ -303,6 +332,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -315,7 +346,11 @@ async def delete( "/devices/{device_id}/media-sessions/{session_id}", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -332,6 +367,7 @@ async def activate( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MediaSessionActivateResponse: """ Validates relay codecs and attaches one private RTSP source to either the @@ -345,6 +381,8 @@ async def activate( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -356,7 +394,11 @@ async def activate( "/devices/{device_id}/media-sessions/{session_id}/activate", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MediaSessionActivateResponse, ) diff --git a/src/mobilerun_sdk/resources/devices/profile.py b/src/mobilerun_sdk/resources/devices/profile.py index d48b0924..c2b5ad54 100644 --- a/src/mobilerun_sdk/resources/devices/profile.py +++ b/src/mobilerun_sdk/resources/devices/profile.py @@ -52,6 +52,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Apply a profile to a device @@ -66,6 +67,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -80,7 +83,11 @@ def update( path_template("/devices/{device_id}/profile", device_id=device_id), body=maybe_transform({"profile_id": profile_id}, profile_update_params.ProfileUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -118,6 +125,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Apply a profile to a device @@ -132,6 +140,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -146,7 +156,11 @@ async def update( path_template("/devices/{device_id}/profile", device_id=device_id), body=await async_maybe_transform({"profile_id": profile_id}, profile_update_params.ProfileUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/proxy.py b/src/mobilerun_sdk/resources/devices/proxy.py index f3cc14b3..2b978aba 100644 --- a/src/mobilerun_sdk/resources/devices/proxy.py +++ b/src/mobilerun_sdk/resources/devices/proxy.py @@ -60,6 +60,7 @@ def connect( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Routes the device's traffic through a SOCKS5 proxy supplied in the request body, @@ -82,6 +83,8 @@ def connect( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -108,7 +111,11 @@ def connect( proxy_connect_params.ProxyConnectParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -124,6 +131,7 @@ def disconnect( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Disconnects the device's active proxy connection and clears its stored proxy @@ -137,6 +145,8 @@ def disconnect( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -150,7 +160,11 @@ def disconnect( return self._delete( path_template("/devices/{device_id}/proxy", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -236,6 +250,7 @@ async def connect( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Routes the device's traffic through a SOCKS5 proxy supplied in the request body, @@ -258,6 +273,8 @@ async def connect( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -284,7 +301,11 @@ async def connect( proxy_connect_params.ProxyConnectParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -300,6 +321,7 @@ async def disconnect( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Disconnects the device's active proxy connection and clears its stored proxy @@ -313,6 +335,8 @@ async def disconnect( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -326,7 +350,11 @@ async def disconnect( return await self._delete( path_template("/devices/{device_id}/proxy", device_id=device_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/recordings.py b/src/mobilerun_sdk/resources/devices/recordings.py index 1591a3b8..7d9b1e38 100644 --- a/src/mobilerun_sdk/resources/devices/recordings.py +++ b/src/mobilerun_sdk/resources/devices/recordings.py @@ -102,6 +102,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Delete a device recording @@ -114,6 +115,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -125,7 +128,11 @@ def delete( "/devices/{device_id}/recordings/{recording_id}", device_id=device_id, recording_id=recording_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -144,6 +151,7 @@ def start( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> RecordingStartResponse: """ Start a device recording @@ -153,9 +161,11 @@ def start( device's full quality. Honored by devices recording through the portal stream bridge. - types: Artifacts to capture: trajectory (input actions), video, and audio (captured + types: Artifacts to capture: trajectory (input actions; on portal stream-bridge devices + only when the handset announces trajectory capture), video, and audio (captured into the video artifact, so it requires video; honored by portal stream-bridge - recorders). Defaults to trajectory and video. + recorders). Defaults to trajectory and video, narrowed to what the device + produces. extra_headers: Send extra headers @@ -164,6 +174,8 @@ def start( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -179,7 +191,11 @@ def start( recording_start_params.RecordingStartParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=RecordingStartResponse, ) @@ -233,6 +249,7 @@ def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> RecordingStopResponse: """ Stop a device recording @@ -245,6 +262,8 @@ def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -255,7 +274,11 @@ def stop( "/devices/{device_id}/recordings/{recording_id}", device_id=device_id, recording_id=recording_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=RecordingStopResponse, ) @@ -417,6 +440,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """ Delete a device recording @@ -429,6 +453,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -440,7 +466,11 @@ async def delete( "/devices/{device_id}/recordings/{recording_id}", device_id=device_id, recording_id=recording_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -459,6 +489,7 @@ async def start( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> RecordingStartResponse: """ Start a device recording @@ -468,9 +499,11 @@ async def start( device's full quality. Honored by devices recording through the portal stream bridge. - types: Artifacts to capture: trajectory (input actions), video, and audio (captured + types: Artifacts to capture: trajectory (input actions; on portal stream-bridge devices + only when the handset announces trajectory capture), video, and audio (captured into the video artifact, so it requires video; honored by portal stream-bridge - recorders). Defaults to trajectory and video. + recorders). Defaults to trajectory and video, narrowed to what the device + produces. extra_headers: Send extra headers @@ -479,6 +512,8 @@ async def start( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -494,7 +529,11 @@ async def start( recording_start_params.RecordingStartParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=RecordingStartResponse, ) @@ -548,6 +587,7 @@ async def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> RecordingStopResponse: """ Stop a device recording @@ -560,6 +600,8 @@ async def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -570,7 +612,11 @@ async def stop( "/devices/{device_id}/recordings/{recording_id}", device_id=device_id, recording_id=recording_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=RecordingStopResponse, ) diff --git a/src/mobilerun_sdk/resources/devices/timezone.py b/src/mobilerun_sdk/resources/devices/timezone.py index 0ac8240b..1e79eeb2 100644 --- a/src/mobilerun_sdk/resources/devices/timezone.py +++ b/src/mobilerun_sdk/resources/devices/timezone.py @@ -95,6 +95,7 @@ def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Sets the device timezone to the identifier in the request body. @@ -109,6 +110,8 @@ def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -123,7 +126,11 @@ def set( path_template("/devices/{device_id}/timezone", device_id=device_id), body=maybe_transform({"timezone": timezone}, timezone_set_params.TimezoneSetParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -203,6 +210,7 @@ async def set( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Sets the device timezone to the identifier in the request body. @@ -217,6 +225,8 @@ async def set( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -231,7 +241,11 @@ async def set( path_template("/devices/{device_id}/timezone", device_id=device_id), body=await async_maybe_transform({"timezone": timezone}, timezone_set_params.TimezoneSetParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) diff --git a/src/mobilerun_sdk/resources/devices/traffic_sessions.py b/src/mobilerun_sdk/resources/devices/traffic_sessions.py index 3a23caaf..866aa93c 100644 --- a/src/mobilerun_sdk/resources/devices/traffic_sessions.py +++ b/src/mobilerun_sdk/resources/devices/traffic_sessions.py @@ -48,7 +48,6 @@ def create( self, device_id: str, *, - idempotency_key: str, expires_in_seconds: int | Omit = omit, max_body_bytes: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -57,6 +56,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TrafficSessionCreateResponse: """ Starts one live-only decoded HTTP/1.1, HTTP/2, HTTP/3 and WebSocket traffic @@ -70,10 +70,11 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") - extra_headers = {"Idempotency-Key": idempotency_key, **(extra_headers or {})} return self._post( path_template("/devices/{device_id}/traffic/sessions", device_id=device_id), body=maybe_transform( @@ -84,7 +85,11 @@ def create( traffic_session_create_params.TrafficSessionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TrafficSessionCreateResponse, ) @@ -183,6 +188,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TrafficSessionDeleteResponse: """ Stop device traffic inspection @@ -195,6 +201,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -205,7 +213,11 @@ def delete( "/devices/{device_id}/traffic/sessions/{session_id}", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TrafficSessionDeleteResponse, ) @@ -235,7 +247,6 @@ async def create( self, device_id: str, *, - idempotency_key: str, expires_in_seconds: int | Omit = omit, max_body_bytes: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -244,6 +255,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TrafficSessionCreateResponse: """ Starts one live-only decoded HTTP/1.1, HTTP/2, HTTP/3 and WebSocket traffic @@ -257,10 +269,11 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") - extra_headers = {"Idempotency-Key": idempotency_key, **(extra_headers or {})} return await self._post( path_template("/devices/{device_id}/traffic/sessions", device_id=device_id), body=await async_maybe_transform( @@ -271,7 +284,11 @@ async def create( traffic_session_create_params.TrafficSessionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TrafficSessionCreateResponse, ) @@ -370,6 +387,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TrafficSessionDeleteResponse: """ Stop device traffic inspection @@ -382,6 +400,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not device_id: raise ValueError(f"Expected a non-empty value for `device_id` but received {device_id!r}") @@ -392,7 +412,11 @@ async def delete( "/devices/{device_id}/traffic/sessions/{session_id}", device_id=device_id, session_id=session_id ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TrafficSessionDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/files.py b/src/mobilerun_sdk/resources/files.py index 739ba6f8..93d9b9b4 100644 --- a/src/mobilerun_sdk/resources/files.py +++ b/src/mobilerun_sdk/resources/files.py @@ -9,7 +9,7 @@ from ..types import file_list_params, file_update_params, file_upload_url_params from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given -from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform +from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -61,6 +61,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileUpdateResponse: """Partial update of `displayName` and/or `enabled`. @@ -75,6 +76,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") @@ -88,7 +91,11 @@ def update( file_update_params.FileUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileUpdateResponse, ) @@ -138,6 +145,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileDeleteResponse: """ Hard-delete a file @@ -150,13 +158,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._delete( path_template("/agents/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileDeleteResponse, ) @@ -171,6 +185,7 @@ def cancel_pending( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileCancelPendingResponse: """Soft-cancels an in-flight upload before confirm. @@ -186,13 +201,19 @@ def cancel_pending( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._delete( path_template("/agents/files/{file_id}/pending", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileCancelPendingResponse, ) @@ -207,6 +228,7 @@ def confirm( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileConfirmResponse: """ Confirm a file upload by server-side HEAD validation @@ -219,13 +241,19 @@ def confirm( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return self._post( path_template("/agents/files/{file_id}/confirm", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileConfirmResponse, ) @@ -273,13 +301,13 @@ def upload_url( mime_type: str, size_bytes: int, zone: Literal["user", "skills"] | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileUploadURLResponse: """ Mint a presigned PUT URL for a user file upload @@ -292,8 +320,9 @@ def upload_url( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return self._post( "/agents/files/upload-url", body=maybe_transform( @@ -306,7 +335,11 @@ def upload_url( file_upload_url_params.FileUploadURLParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileUploadURLResponse, ) @@ -344,6 +377,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileUpdateResponse: """Partial update of `displayName` and/or `enabled`. @@ -358,6 +392,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") @@ -371,7 +407,11 @@ async def update( file_update_params.FileUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileUpdateResponse, ) @@ -421,6 +461,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileDeleteResponse: """ Hard-delete a file @@ -433,13 +474,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._delete( path_template("/agents/files/{file_id}", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileDeleteResponse, ) @@ -454,6 +501,7 @@ async def cancel_pending( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileCancelPendingResponse: """Soft-cancels an in-flight upload before confirm. @@ -469,13 +517,19 @@ async def cancel_pending( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._delete( path_template("/agents/files/{file_id}/pending", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileCancelPendingResponse, ) @@ -490,6 +544,7 @@ async def confirm( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileConfirmResponse: """ Confirm a file upload by server-side HEAD validation @@ -502,13 +557,19 @@ async def confirm( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not file_id: raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}") return await self._post( path_template("/agents/files/{file_id}/confirm", file_id=file_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileConfirmResponse, ) @@ -556,13 +617,13 @@ async def upload_url( mime_type: str, size_bytes: int, zone: Literal["user", "skills"] | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FileUploadURLResponse: """ Mint a presigned PUT URL for a user file upload @@ -575,8 +636,9 @@ async def upload_url( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return await self._post( "/agents/files/upload-url", body=await async_maybe_transform( @@ -589,7 +651,11 @@ async def upload_url( file_upload_url_params.FileUploadURLParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FileUploadURLResponse, ) diff --git a/src/mobilerun_sdk/resources/mailboxes/__init__.py b/src/mobilerun_sdk/resources/mailboxes/__init__.py index 92f91ec6..da0041b1 100644 --- a/src/mobilerun_sdk/resources/mailboxes/__init__.py +++ b/src/mobilerun_sdk/resources/mailboxes/__init__.py @@ -16,8 +16,22 @@ MailboxesResourceWithStreamingResponse, AsyncMailboxesResourceWithStreamingResponse, ) +from .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, +) __all__ = [ + "ConnectionsResource", + "AsyncConnectionsResource", + "ConnectionsResourceWithRawResponse", + "AsyncConnectionsResourceWithRawResponse", + "ConnectionsResourceWithStreamingResponse", + "AsyncConnectionsResourceWithStreamingResponse", "MessagesResource", "AsyncMessagesResource", "MessagesResourceWithRawResponse", diff --git a/src/mobilerun_sdk/resources/mailboxes/connections.py b/src/mobilerun_sdk/resources/mailboxes/connections.py new file mode 100644 index 00000000..8ec82762 --- /dev/null +++ b/src/mobilerun_sdk/resources/mailboxes/connections.py @@ -0,0 +1,298 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal + +import httpx + +from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.mailboxes import connection_create_params +from ...types.mailboxes.connection_create_response import ConnectionCreateResponse +from ...types.mailboxes.connection_retrieve_response import ConnectionRetrieveResponse + +__all__ = ["ConnectionsResource", "AsyncConnectionsResource"] + + +class ConnectionsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ConnectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return ConnectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConnectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return ConnectionsResourceWithStreamingResponse(self) + + def create( + self, + *, + provider: Literal["gmail"], + binding_hash: str | Omit = omit, + client_request_id: str | Omit = omit, + label: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ConnectionCreateResponse: + """ + Links the caller’s Gmail account through the shared integrations connect. + Replaying the same Idempotency-Key (or, during migration, the deprecated + `clientRequestId` body field) returns the same mailbox. When Gmail is already + active, redirectUrl is null and the mailbox is usable immediately. + + Args: + binding_hash: sha256 hex of the browser-held connect binding secret; forwarded to integrations + connect + + client_request_id: Use the Idempotency-Key header. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/mailboxes/connections", + body=maybe_transform( + { + "provider": provider, + "binding_hash": binding_hash, + "client_request_id": client_request_id, + "label": label, + }, + connection_create_params.ConnectionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ConnectionCreateResponse, + ) + + def retrieve( + self, + connection_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConnectionRetrieveResponse: + """ + Reports pending, active (with the Gmail address), expired, or removed. + connectionId is the mailbox id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not connection_id: + raise ValueError(f"Expected a non-empty value for `connection_id` but received {connection_id!r}") + return self._get( + path_template("/mailboxes/connections/{connection_id}", connection_id=connection_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectionRetrieveResponse, + ) + + +class AsyncConnectionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncConnectionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncConnectionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConnectionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncConnectionsResourceWithStreamingResponse(self) + + async def create( + self, + *, + provider: Literal["gmail"], + binding_hash: str | Omit = omit, + client_request_id: str | Omit = omit, + label: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> ConnectionCreateResponse: + """ + Links the caller’s Gmail account through the shared integrations connect. + Replaying the same Idempotency-Key (or, during migration, the deprecated + `clientRequestId` body field) returns the same mailbox. When Gmail is already + active, redirectUrl is null and the mailbox is usable immediately. + + Args: + binding_hash: sha256 hex of the browser-held connect binding secret; forwarded to integrations + connect + + client_request_id: Use the Idempotency-Key header. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/mailboxes/connections", + body=await async_maybe_transform( + { + "provider": provider, + "binding_hash": binding_hash, + "client_request_id": client_request_id, + "label": label, + }, + connection_create_params.ConnectionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=ConnectionCreateResponse, + ) + + async def retrieve( + self, + connection_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ConnectionRetrieveResponse: + """ + Reports pending, active (with the Gmail address), expired, or removed. + connectionId is the mailbox id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not connection_id: + raise ValueError(f"Expected a non-empty value for `connection_id` but received {connection_id!r}") + return await self._get( + path_template("/mailboxes/connections/{connection_id}", connection_id=connection_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectionRetrieveResponse, + ) + + +class ConnectionsResourceWithRawResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections + + self.create = to_raw_response_wrapper( + connections.create, + ) + self.retrieve = to_raw_response_wrapper( + connections.retrieve, + ) + + +class AsyncConnectionsResourceWithRawResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections + + self.create = async_to_raw_response_wrapper( + connections.create, + ) + self.retrieve = async_to_raw_response_wrapper( + connections.retrieve, + ) + + +class ConnectionsResourceWithStreamingResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections + + self.create = to_streamed_response_wrapper( + connections.create, + ) + self.retrieve = to_streamed_response_wrapper( + connections.retrieve, + ) + + +class AsyncConnectionsResourceWithStreamingResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections + + self.create = async_to_streamed_response_wrapper( + connections.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + connections.retrieve, + ) diff --git a/src/mobilerun_sdk/resources/mailboxes/mailboxes.py b/src/mobilerun_sdk/resources/mailboxes/mailboxes.py index 50e03570..a3b0df77 100644 --- a/src/mobilerun_sdk/resources/mailboxes/mailboxes.py +++ b/src/mobilerun_sdk/resources/mailboxes/mailboxes.py @@ -33,6 +33,14 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) +from .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, +) from ..._base_client import make_request_options from ...types.mailbox_otp_response import MailboxOtpResponse from ...types.mailbox_list_response import MailboxListResponse @@ -43,11 +51,16 @@ from ...types.mailbox_capacity_response import MailboxCapacityResponse from ...types.mailbox_retrieve_response import MailboxRetrieveResponse from ...types.mailbox_uncancel_response import MailboxUncancelResponse +from ...types.mailbox_disconnect_response import MailboxDisconnectResponse __all__ = ["MailboxesResource", "AsyncMailboxesResource"] class MailboxesResource(SyncAPIResource): + @cached_property + def connections(self) -> ConnectionsResource: + return ConnectionsResource(self._client) + @cached_property def messages(self) -> MessagesResource: return MessagesResource(self._client) @@ -74,7 +87,6 @@ def with_streaming_response(self) -> MailboxesResourceWithStreamingResponse: def create( self, *, - client_request_id: str, billing_preference: Literal["included", "included_only", "rent"] | Omit = omit, domain_id: str | Omit = omit, label: str | Omit = omit, @@ -85,13 +97,15 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxCreateResponse: """Creates a mailbox on the default domain or a connected custom domain. An - optional `localPart` selects the address. Replaying the same `clientRequestId` - and payload returns the original mailbox. Poll the mailbox when a 202 response - does not yet include a checkout URL. + optional `localPart` selects the address. Replaying the same Idempotency-Key + (or, during migration, the deprecated `clientRequestId` body field) and payload + returns the original mailbox. Poll the mailbox when a 202 response does not yet + include a checkout URL. Args: billing_preference: included uses package capacity when available and otherwise starts paid @@ -110,12 +124,13 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/mailboxes", body=maybe_transform( { - "client_request_id": client_request_id, "billing_preference": billing_preference, "domain_id": domain_id, "label": label, @@ -124,7 +139,11 @@ def create( mailbox_create_params.MailboxCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxCreateResponse, ) @@ -173,6 +192,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxUpdateResponse: """ Updates the label of a mailbox. @@ -185,6 +205,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") @@ -192,7 +214,11 @@ def update( path_template("/mailboxes/{mailbox_id}", mailbox_id=mailbox_id), body=maybe_transform({"label": label}, mailbox_update_params.MailboxUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxUpdateResponse, ) @@ -249,10 +275,12 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxDeleteResponse: """ Cancels a pending mailbox or schedules an active paid mailbox for cancellation. - Existing addresses and messages are retained. Repeating the request is safe. + Existing addresses and messages are retained. Repeating the request is safe. An + external inbox (Gmail) cannot be cancelled here; disconnect the link instead. Args: extra_headers: Send extra headers @@ -262,13 +290,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") return self._delete( path_template("/mailboxes/{mailbox_id}", mailbox_id=mailbox_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxDeleteResponse, ) @@ -292,6 +326,47 @@ def capacity( cast_to=MailboxCapacityResponse, ) + def disconnect( + self, + mailbox_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> MailboxDisconnectResponse: + """Removes only this mailbox link. + + The agent Composio connection stays in place. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not mailbox_id: + raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") + return self._post( + path_template("/mailboxes/{mailbox_id}/disconnect", mailbox_id=mailbox_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MailboxDisconnectResponse, + ) + def otp( self, mailbox_id: str, @@ -354,6 +429,7 @@ def restart( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxRestartResponse: """Restarts an archived mailbox with the same address. @@ -372,6 +448,8 @@ def restart( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") @@ -381,7 +459,11 @@ def restart( {"billing_preference": billing_preference}, mailbox_restart_params.MailboxRestartParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxRestartResponse, ) @@ -396,6 +478,7 @@ def uncancel( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxUncancelResponse: """Withdraws a scheduled cancellation. @@ -410,19 +493,29 @@ def uncancel( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") return self._post( path_template("/mailboxes/{mailbox_id}/uncancel", mailbox_id=mailbox_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxUncancelResponse, ) class AsyncMailboxesResource(AsyncAPIResource): + @cached_property + def connections(self) -> AsyncConnectionsResource: + return AsyncConnectionsResource(self._client) + @cached_property def messages(self) -> AsyncMessagesResource: return AsyncMessagesResource(self._client) @@ -449,7 +542,6 @@ def with_streaming_response(self) -> AsyncMailboxesResourceWithStreamingResponse async def create( self, *, - client_request_id: str, billing_preference: Literal["included", "included_only", "rent"] | Omit = omit, domain_id: str | Omit = omit, label: str | Omit = omit, @@ -460,13 +552,15 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxCreateResponse: """Creates a mailbox on the default domain or a connected custom domain. An - optional `localPart` selects the address. Replaying the same `clientRequestId` - and payload returns the original mailbox. Poll the mailbox when a 202 response - does not yet include a checkout URL. + optional `localPart` selects the address. Replaying the same Idempotency-Key + (or, during migration, the deprecated `clientRequestId` body field) and payload + returns the original mailbox. Poll the mailbox when a 202 response does not yet + include a checkout URL. Args: billing_preference: included uses package capacity when available and otherwise starts paid @@ -485,12 +579,13 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/mailboxes", body=await async_maybe_transform( { - "client_request_id": client_request_id, "billing_preference": billing_preference, "domain_id": domain_id, "label": label, @@ -499,7 +594,11 @@ async def create( mailbox_create_params.MailboxCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxCreateResponse, ) @@ -548,6 +647,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxUpdateResponse: """ Updates the label of a mailbox. @@ -560,6 +660,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") @@ -567,7 +669,11 @@ async def update( path_template("/mailboxes/{mailbox_id}", mailbox_id=mailbox_id), body=await async_maybe_transform({"label": label}, mailbox_update_params.MailboxUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxUpdateResponse, ) @@ -624,10 +730,12 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxDeleteResponse: """ Cancels a pending mailbox or schedules an active paid mailbox for cancellation. - Existing addresses and messages are retained. Repeating the request is safe. + Existing addresses and messages are retained. Repeating the request is safe. An + external inbox (Gmail) cannot be cancelled here; disconnect the link instead. Args: extra_headers: Send extra headers @@ -637,13 +745,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") return await self._delete( path_template("/mailboxes/{mailbox_id}", mailbox_id=mailbox_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxDeleteResponse, ) @@ -667,6 +781,47 @@ async def capacity( cast_to=MailboxCapacityResponse, ) + async def disconnect( + self, + mailbox_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> MailboxDisconnectResponse: + """Removes only this mailbox link. + + The agent Composio connection stays in place. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not mailbox_id: + raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") + return await self._post( + path_template("/mailboxes/{mailbox_id}/disconnect", mailbox_id=mailbox_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MailboxDisconnectResponse, + ) + async def otp( self, mailbox_id: str, @@ -729,6 +884,7 @@ async def restart( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxRestartResponse: """Restarts an archived mailbox with the same address. @@ -747,6 +903,8 @@ async def restart( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") @@ -756,7 +914,11 @@ async def restart( {"billing_preference": billing_preference}, mailbox_restart_params.MailboxRestartParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxRestartResponse, ) @@ -771,6 +933,7 @@ async def uncancel( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> MailboxUncancelResponse: """Withdraws a scheduled cancellation. @@ -785,13 +948,19 @@ async def uncancel( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not mailbox_id: raise ValueError(f"Expected a non-empty value for `mailbox_id` but received {mailbox_id!r}") return await self._post( path_template("/mailboxes/{mailbox_id}/uncancel", mailbox_id=mailbox_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=MailboxUncancelResponse, ) @@ -819,6 +988,9 @@ def __init__(self, mailboxes: MailboxesResource) -> None: self.capacity = to_raw_response_wrapper( mailboxes.capacity, ) + self.disconnect = to_raw_response_wrapper( + mailboxes.disconnect, + ) self.otp = to_raw_response_wrapper( mailboxes.otp, ) @@ -829,6 +1001,10 @@ def __init__(self, mailboxes: MailboxesResource) -> None: mailboxes.uncancel, ) + @cached_property + def connections(self) -> ConnectionsResourceWithRawResponse: + return ConnectionsResourceWithRawResponse(self._mailboxes.connections) + @cached_property def messages(self) -> MessagesResourceWithRawResponse: return MessagesResourceWithRawResponse(self._mailboxes.messages) @@ -856,6 +1032,9 @@ def __init__(self, mailboxes: AsyncMailboxesResource) -> None: self.capacity = async_to_raw_response_wrapper( mailboxes.capacity, ) + self.disconnect = async_to_raw_response_wrapper( + mailboxes.disconnect, + ) self.otp = async_to_raw_response_wrapper( mailboxes.otp, ) @@ -866,6 +1045,10 @@ def __init__(self, mailboxes: AsyncMailboxesResource) -> None: mailboxes.uncancel, ) + @cached_property + def connections(self) -> AsyncConnectionsResourceWithRawResponse: + return AsyncConnectionsResourceWithRawResponse(self._mailboxes.connections) + @cached_property def messages(self) -> AsyncMessagesResourceWithRawResponse: return AsyncMessagesResourceWithRawResponse(self._mailboxes.messages) @@ -893,6 +1076,9 @@ def __init__(self, mailboxes: MailboxesResource) -> None: self.capacity = to_streamed_response_wrapper( mailboxes.capacity, ) + self.disconnect = to_streamed_response_wrapper( + mailboxes.disconnect, + ) self.otp = to_streamed_response_wrapper( mailboxes.otp, ) @@ -903,6 +1089,10 @@ def __init__(self, mailboxes: MailboxesResource) -> None: mailboxes.uncancel, ) + @cached_property + def connections(self) -> ConnectionsResourceWithStreamingResponse: + return ConnectionsResourceWithStreamingResponse(self._mailboxes.connections) + @cached_property def messages(self) -> MessagesResourceWithStreamingResponse: return MessagesResourceWithStreamingResponse(self._mailboxes.messages) @@ -930,6 +1120,9 @@ def __init__(self, mailboxes: AsyncMailboxesResource) -> None: self.capacity = async_to_streamed_response_wrapper( mailboxes.capacity, ) + self.disconnect = async_to_streamed_response_wrapper( + mailboxes.disconnect, + ) self.otp = async_to_streamed_response_wrapper( mailboxes.otp, ) @@ -940,6 +1133,10 @@ def __init__(self, mailboxes: AsyncMailboxesResource) -> None: mailboxes.uncancel, ) + @cached_property + def connections(self) -> AsyncConnectionsResourceWithStreamingResponse: + return AsyncConnectionsResourceWithStreamingResponse(self._mailboxes.connections) + @cached_property def messages(self) -> AsyncMessagesResourceWithStreamingResponse: return AsyncMessagesResourceWithStreamingResponse(self._mailboxes.messages) diff --git a/src/mobilerun_sdk/resources/messages/conversations.py b/src/mobilerun_sdk/resources/messages/conversations.py index 397a5a3e..50d032e8 100644 --- a/src/mobilerun_sdk/resources/messages/conversations.py +++ b/src/mobilerun_sdk/resources/messages/conversations.py @@ -107,6 +107,7 @@ def mark_read( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationMarkReadResponse: """ Marks inbound messages in a conversation as read through the supplied cursor. @@ -125,6 +126,8 @@ def mark_read( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/numbers/messages/conversations/read", @@ -137,7 +140,11 @@ def mark_read( conversation_mark_read_params.ConversationMarkReadParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationMarkReadResponse, ) @@ -225,6 +232,7 @@ async def mark_read( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ConversationMarkReadResponse: """ Marks inbound messages in a conversation as read through the supplied cursor. @@ -243,6 +251,8 @@ async def mark_read( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/numbers/messages/conversations/read", @@ -255,7 +265,11 @@ async def mark_read( conversation_mark_read_params.ConversationMarkReadParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ConversationMarkReadResponse, ) diff --git a/src/mobilerun_sdk/resources/messages/messages.py b/src/mobilerun_sdk/resources/messages/messages.py index 49cc99a2..3d78e80a 100644 --- a/src/mobilerun_sdk/resources/messages/messages.py +++ b/src/mobilerun_sdk/resources/messages/messages.py @@ -8,7 +8,7 @@ from ...types import message_list_params from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -27,6 +27,7 @@ ) from ..._base_client import make_request_options from ...types.message_list_response import MessageListResponse +from ...types.message_retrieve_response import MessageRetrieveResponse __all__ = ["MessagesResource", "AsyncMessagesResource"] @@ -55,6 +56,42 @@ def with_streaming_response(self) -> MessagesResourceWithStreamingResponse: """ return MessagesResourceWithStreamingResponse(self) + def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> MessageRetrieveResponse: + """Returns one message. + + For an outbound message that is not yet final, the status + may be refreshed once (at most every 30 s) before it is returned; a failed + refresh returns the stored message. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/numbers/messages/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MessageRetrieveResponse, + ) + def list( self, *, @@ -138,6 +175,42 @@ def with_streaming_response(self) -> AsyncMessagesResourceWithStreamingResponse: """ return AsyncMessagesResourceWithStreamingResponse(self) + async def retrieve( + self, + id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> MessageRetrieveResponse: + """Returns one message. + + For an outbound message that is not yet final, the status + may be refreshed once (at most every 30 s) before it is returned; a failed + refresh returns the stored message. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/numbers/messages/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MessageRetrieveResponse, + ) + async def list( self, *, @@ -201,6 +274,9 @@ class MessagesResourceWithRawResponse: def __init__(self, messages: MessagesResource) -> None: self._messages = messages + self.retrieve = to_raw_response_wrapper( + messages.retrieve, + ) self.list = to_raw_response_wrapper( messages.list, ) @@ -214,6 +290,9 @@ class AsyncMessagesResourceWithRawResponse: def __init__(self, messages: AsyncMessagesResource) -> None: self._messages = messages + self.retrieve = async_to_raw_response_wrapper( + messages.retrieve, + ) self.list = async_to_raw_response_wrapper( messages.list, ) @@ -227,6 +306,9 @@ class MessagesResourceWithStreamingResponse: def __init__(self, messages: MessagesResource) -> None: self._messages = messages + self.retrieve = to_streamed_response_wrapper( + messages.retrieve, + ) self.list = to_streamed_response_wrapper( messages.list, ) @@ -240,6 +322,9 @@ class AsyncMessagesResourceWithStreamingResponse: def __init__(self, messages: AsyncMessagesResource) -> None: self._messages = messages + self.retrieve = async_to_streamed_response_wrapper( + messages.retrieve, + ) self.list = async_to_streamed_response_wrapper( messages.list, ) diff --git a/src/mobilerun_sdk/resources/notifications.py b/src/mobilerun_sdk/resources/notifications.py index 80c615c8..f20283ed 100644 --- a/src/mobilerun_sdk/resources/notifications.py +++ b/src/mobilerun_sdk/resources/notifications.py @@ -2,9 +2,11 @@ from __future__ import annotations +from typing_extensions import Literal + import httpx -from ..types import notification_update_preferences_params +from ..types import notification_update_preferences_params, notification_apply_preferences_preset_params from .._types import Body, Query, Headers, NotGiven, SequenceNotStr, not_given from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -19,6 +21,7 @@ from ..types.notification_catalog_response import NotificationCatalogResponse from ..types.notification_get_preferences_response import NotificationGetPreferencesResponse from ..types.notification_update_preferences_response import NotificationUpdatePreferencesResponse +from ..types.notification_apply_preferences_preset_response import NotificationApplyPreferencesPresetResponse __all__ = ["NotificationsResource", "AsyncNotificationsResource"] @@ -43,6 +46,51 @@ def with_streaming_response(self) -> NotificationsResourceWithStreamingResponse: """ return NotificationsResourceWithStreamingResponse(self) + def apply_preferences_preset( + self, + *, + preset: Literal["recommended", "failures", "everything"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> NotificationApplyPreferencesPresetResponse: + """ + Replaces your muted event types with a server-computed preset: `recommended` + mutes the mobilerun-default noisy lifecycle/progress events, `failures` mutes + everything except failures/timeouts/invalid runs, and `everything` unmutes all. + The response returns the muted types that were actually stored. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return self._post( + "/notifications/preferences/apply-preset", + body=maybe_transform( + {"preset": preset}, + notification_apply_preferences_preset_params.NotificationApplyPreferencesPresetParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NotificationApplyPreferencesPresetResponse, + ) + def catalog( self, *, @@ -100,6 +148,7 @@ def update_preferences( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NotificationUpdatePreferencesResponse: """Replaces your set of muted event types with the supplied list. @@ -115,6 +164,8 @@ def update_preferences( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._patch( "/notifications/preferences", @@ -122,7 +173,11 @@ def update_preferences( {"muted_types": muted_types}, notification_update_preferences_params.NotificationUpdatePreferencesParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NotificationUpdatePreferencesResponse, ) @@ -148,6 +203,51 @@ def with_streaming_response(self) -> AsyncNotificationsResourceWithStreamingResp """ return AsyncNotificationsResourceWithStreamingResponse(self) + async def apply_preferences_preset( + self, + *, + preset: Literal["recommended", "failures", "everything"], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> NotificationApplyPreferencesPresetResponse: + """ + Replaces your muted event types with a server-computed preset: `recommended` + mutes the mobilerun-default noisy lifecycle/progress events, `failures` mutes + everything except failures/timeouts/invalid runs, and `everything` unmutes all. + The response returns the muted types that were actually stored. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/notifications/preferences/apply-preset", + body=await async_maybe_transform( + {"preset": preset}, + notification_apply_preferences_preset_params.NotificationApplyPreferencesPresetParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=NotificationApplyPreferencesPresetResponse, + ) + async def catalog( self, *, @@ -205,6 +305,7 @@ async def update_preferences( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NotificationUpdatePreferencesResponse: """Replaces your set of muted event types with the supplied list. @@ -220,6 +321,8 @@ async def update_preferences( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._patch( "/notifications/preferences", @@ -227,7 +330,11 @@ async def update_preferences( {"muted_types": muted_types}, notification_update_preferences_params.NotificationUpdatePreferencesParams ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NotificationUpdatePreferencesResponse, ) @@ -237,6 +344,9 @@ class NotificationsResourceWithRawResponse: def __init__(self, notifications: NotificationsResource) -> None: self._notifications = notifications + self.apply_preferences_preset = to_raw_response_wrapper( + notifications.apply_preferences_preset, + ) self.catalog = to_raw_response_wrapper( notifications.catalog, ) @@ -252,6 +362,9 @@ class AsyncNotificationsResourceWithRawResponse: def __init__(self, notifications: AsyncNotificationsResource) -> None: self._notifications = notifications + self.apply_preferences_preset = async_to_raw_response_wrapper( + notifications.apply_preferences_preset, + ) self.catalog = async_to_raw_response_wrapper( notifications.catalog, ) @@ -267,6 +380,9 @@ class NotificationsResourceWithStreamingResponse: def __init__(self, notifications: NotificationsResource) -> None: self._notifications = notifications + self.apply_preferences_preset = to_streamed_response_wrapper( + notifications.apply_preferences_preset, + ) self.catalog = to_streamed_response_wrapper( notifications.catalog, ) @@ -282,6 +398,9 @@ class AsyncNotificationsResourceWithStreamingResponse: def __init__(self, notifications: AsyncNotificationsResource) -> None: self._notifications = notifications + self.apply_preferences_preset = async_to_streamed_response_wrapper( + notifications.apply_preferences_preset, + ) self.catalog = async_to_streamed_response_wrapper( notifications.catalog, ) diff --git a/src/mobilerun_sdk/resources/numbers/messages.py b/src/mobilerun_sdk/resources/numbers/messages.py index 123fdd0a..34d2b978 100644 --- a/src/mobilerun_sdk/resources/numbers/messages.py +++ b/src/mobilerun_sdk/resources/numbers/messages.py @@ -19,8 +19,9 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.numbers import message_list_params +from ...types.numbers import message_list_params, message_send_params from ...types.numbers.message_list_response import MessageListResponse +from ...types.numbers.message_send_response import MessageSendResponse __all__ = ["MessagesResource", "AsyncMessagesResource"] @@ -96,6 +97,79 @@ def list( cast_to=MessageListResponse, ) + def send( + self, + id: str, + *, + body: str, + to: str, + client_request_id: str | Omit = omit, + delivery_report: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> MessageSendResponse: + """Queues an SMS from one of the caller's numbers that can send. + + Same idempotency + contract as the eSIM send: replaying an Idempotency-Key with the same payload + returns the original message, reusing it with a different payload returns 422. + 409 capability_unavailable: this number cannot send right now. 422 + invalid_recipient / unsupported_destination / body_too_long: the recipient or + text does not fit the number. 429 daily_limit_reached: the rolling 24 h limit + for the account is used up; 429 rate_limited: too many sends in a short window. + 403 send_disabled: self-service SMS send is switched off. + + Args: + body: SMS body text, up to 1600 characters (rejected with 400 beyond that, before + hashing). A number's own limit may be lower and is answered 422 body_too_long. + + to: Recipient phone number, as E.164 or a US 10/11-digit number. Destinations the + number cannot send to are rejected with 422 unsupported_destination, non-numbers + with 422 invalid_recipient. + + client_request_id: Deprecated: use the Idempotency-Key header instead. Optional idempotency key. + Replaying the same key and payload returns the original send. + + delivery_report: Request a delivery report. Defaults to false. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/numbers/phones/{id}/messages", id=id), + body=maybe_transform( + { + "body": body, + "to": to, + "client_request_id": client_request_id, + "delivery_report": delivery_report, + }, + message_send_params.MessageSendParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MessageSendResponse, + ) + class AsyncMessagesResource(AsyncAPIResource): @cached_property @@ -168,6 +242,79 @@ async def list( cast_to=MessageListResponse, ) + async def send( + self, + id: str, + *, + body: str, + to: str, + client_request_id: str | Omit = omit, + delivery_report: bool | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> MessageSendResponse: + """Queues an SMS from one of the caller's numbers that can send. + + Same idempotency + contract as the eSIM send: replaying an Idempotency-Key with the same payload + returns the original message, reusing it with a different payload returns 422. + 409 capability_unavailable: this number cannot send right now. 422 + invalid_recipient / unsupported_destination / body_too_long: the recipient or + text does not fit the number. 429 daily_limit_reached: the rolling 24 h limit + for the account is used up; 429 rate_limited: too many sends in a short window. + 403 send_disabled: self-service SMS send is switched off. + + Args: + body: SMS body text, up to 1600 characters (rejected with 400 beyond that, before + hashing). A number's own limit may be lower and is answered 422 body_too_long. + + to: Recipient phone number, as E.164 or a US 10/11-digit number. Destinations the + number cannot send to are rejected with 422 unsupported_destination, non-numbers + with 422 invalid_recipient. + + client_request_id: Deprecated: use the Idempotency-Key header instead. Optional idempotency key. + Replaying the same key and payload returns the original send. + + delivery_report: Request a delivery report. Defaults to false. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/numbers/phones/{id}/messages", id=id), + body=await async_maybe_transform( + { + "body": body, + "to": to, + "client_request_id": client_request_id, + "delivery_report": delivery_report, + }, + message_send_params.MessageSendParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=MessageSendResponse, + ) + class MessagesResourceWithRawResponse: def __init__(self, messages: MessagesResource) -> None: @@ -176,6 +323,9 @@ def __init__(self, messages: MessagesResource) -> None: self.list = to_raw_response_wrapper( messages.list, ) + self.send = to_raw_response_wrapper( + messages.send, + ) class AsyncMessagesResourceWithRawResponse: @@ -185,6 +335,9 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.list = async_to_raw_response_wrapper( messages.list, ) + self.send = async_to_raw_response_wrapper( + messages.send, + ) class MessagesResourceWithStreamingResponse: @@ -194,6 +347,9 @@ def __init__(self, messages: MessagesResource) -> None: self.list = to_streamed_response_wrapper( messages.list, ) + self.send = to_streamed_response_wrapper( + messages.send, + ) class AsyncMessagesResourceWithStreamingResponse: @@ -203,3 +359,6 @@ def __init__(self, messages: AsyncMessagesResource) -> None: self.list = async_to_streamed_response_wrapper( messages.list, ) + self.send = async_to_streamed_response_wrapper( + messages.send, + ) diff --git a/src/mobilerun_sdk/resources/numbers/numbers.py b/src/mobilerun_sdk/resources/numbers/numbers.py index 76972e69..b0e9e904 100644 --- a/src/mobilerun_sdk/resources/numbers/numbers.py +++ b/src/mobilerun_sdk/resources/numbers/numbers.py @@ -9,7 +9,7 @@ from ...types import number_list_params, number_create_params, number_update_params, number_capacity_params from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from .messages import ( MessagesResource, AsyncMessagesResource, @@ -70,13 +70,13 @@ def create( country: str | Omit = omit, label: Optional[str] | Omit = omit, purpose: str | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberCreateResponse: """Starts a phone-number purchase. @@ -96,8 +96,6 @@ def create( purpose: Optional purpose from GET /numbers/phones/purposes. - idempotency_key: Optional request idempotency key. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -105,8 +103,9 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return self._post( "/numbers/phones", body=maybe_transform( @@ -119,7 +118,11 @@ def create( number_create_params.NumberCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberCreateResponse, ) @@ -168,6 +171,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberUpdateResponse: """Updates the display label. @@ -186,6 +190,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -193,7 +199,11 @@ def update( path_template("/numbers/phones/{id}", id=id), body=maybe_transform({"label": label}, number_update_params.NumberUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberUpdateResponse, ) @@ -250,6 +260,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberDeleteResponse: """ Cancels a pending purchase or schedules cancellation of an active paid phone @@ -264,13 +275,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._delete( path_template("/numbers/phones/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberDeleteResponse, ) @@ -384,13 +401,13 @@ async def create( country: str | Omit = omit, label: Optional[str] | Omit = omit, purpose: str | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberCreateResponse: """Starts a phone-number purchase. @@ -410,8 +427,6 @@ async def create( purpose: Optional purpose from GET /numbers/phones/purposes. - idempotency_key: Optional request idempotency key. - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -419,8 +434,9 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return await self._post( "/numbers/phones", body=await async_maybe_transform( @@ -433,7 +449,11 @@ async def create( number_create_params.NumberCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberCreateResponse, ) @@ -482,6 +502,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberUpdateResponse: """Updates the display label. @@ -500,6 +521,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -507,7 +530,11 @@ async def update( path_template("/numbers/phones/{id}", id=id), body=await async_maybe_transform({"label": label}, number_update_params.NumberUpdateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberUpdateResponse, ) @@ -564,6 +591,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> NumberDeleteResponse: """ Cancels a pending purchase or schedules cancellation of an active paid phone @@ -578,13 +606,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._delete( path_template("/numbers/phones/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NumberDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/profiles.py b/src/mobilerun_sdk/resources/profiles.py index 00eb2114..f36e0758 100644 --- a/src/mobilerun_sdk/resources/profiles.py +++ b/src/mobilerun_sdk/resources/profiles.py @@ -59,6 +59,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileCreateResponse: """ Create a new device profile @@ -75,6 +76,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/profiles", @@ -86,7 +89,11 @@ def create( profile_create_params.ProfileCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileCreateResponse, ) @@ -136,6 +143,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileUpdateResponse: """ Update a device profile @@ -152,6 +160,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not profile_id: raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") @@ -165,7 +175,11 @@ def update( profile_update_params.ProfileUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileUpdateResponse, ) @@ -233,6 +247,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileDeleteResponse: """ Delete a device profile @@ -245,13 +260,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not profile_id: raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") return self._delete( path_template("/profiles/{profile_id}", profile_id=profile_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileDeleteResponse, ) @@ -288,6 +309,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileCreateResponse: """ Create a new device profile @@ -304,6 +326,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/profiles", @@ -315,7 +339,11 @@ async def create( profile_create_params.ProfileCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileCreateResponse, ) @@ -365,6 +393,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileUpdateResponse: """ Update a device profile @@ -381,6 +410,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not profile_id: raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") @@ -394,7 +425,11 @@ async def update( profile_update_params.ProfileUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileUpdateResponse, ) @@ -462,6 +497,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProfileDeleteResponse: """ Delete a device profile @@ -474,13 +510,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not profile_id: raise ValueError(f"Expected a non-empty value for `profile_id` but received {profile_id!r}") return await self._delete( path_template("/profiles/{profile_id}", profile_id=profile_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProfileDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/proxies.py b/src/mobilerun_sdk/resources/proxies.py index 45a990d5..9a5b7f3e 100644 --- a/src/mobilerun_sdk/resources/proxies.py +++ b/src/mobilerun_sdk/resources/proxies.py @@ -64,6 +64,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: """Creates a proxy config. @@ -80,6 +81,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -96,6 +99,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: """Creates a proxy config. @@ -112,6 +116,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -132,6 +138,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: return self._post( "/proxies", @@ -148,7 +155,11 @@ def create( proxy_create_params.ProxyCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyCreateResponse, ) @@ -206,6 +217,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: """Replaces the proxy config identified by `proxyId` with the provided body. @@ -221,6 +233,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -238,6 +252,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: """Replaces the proxy config identified by `proxyId` with the provided body. @@ -253,6 +268,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -274,6 +291,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: if not proxy_id: raise ValueError(f"Expected a non-empty value for `proxy_id` but received {proxy_id!r}") @@ -292,7 +310,11 @@ def update( proxy_update_params.ProxyUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyUpdateResponse, ) @@ -344,6 +366,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyDeleteResponse: """ Permanently deletes the proxy config identified by `proxyId` and returns the @@ -357,13 +380,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not proxy_id: raise ValueError(f"Expected a non-empty value for `proxy_id` but received {proxy_id!r}") return self._delete( path_template("/proxies/{proxy_id}", proxy_id=proxy_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyDeleteResponse, ) @@ -378,6 +407,7 @@ def lookup( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyLookupResponse: """ Lookup proxy location @@ -392,12 +422,18 @@ def lookup( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/proxies/lookup", body=maybe_transform({"socks5": socks5}, proxy_lookup_params.ProxyLookupParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyLookupResponse, ) @@ -439,6 +475,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: """Creates a proxy config. @@ -455,6 +492,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -471,6 +510,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: """Creates a proxy config. @@ -487,6 +527,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -507,6 +549,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyCreateResponse: return await self._post( "/proxies", @@ -523,7 +566,11 @@ async def create( proxy_create_params.ProxyCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyCreateResponse, ) @@ -581,6 +628,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: """Replaces the proxy config identified by `proxyId` with the provided body. @@ -596,6 +644,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -613,6 +663,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: """Replaces the proxy config identified by `proxyId` with the provided body. @@ -628,6 +679,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ ... @@ -649,6 +702,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyUpdateResponse: if not proxy_id: raise ValueError(f"Expected a non-empty value for `proxy_id` but received {proxy_id!r}") @@ -667,7 +721,11 @@ async def update( proxy_update_params.ProxyUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyUpdateResponse, ) @@ -719,6 +777,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyDeleteResponse: """ Permanently deletes the proxy config identified by `proxyId` and returns the @@ -732,13 +791,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not proxy_id: raise ValueError(f"Expected a non-empty value for `proxy_id` but received {proxy_id!r}") return await self._delete( path_template("/proxies/{proxy_id}", proxy_id=proxy_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyDeleteResponse, ) @@ -753,6 +818,7 @@ async def lookup( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ProxyLookupResponse: """ Lookup proxy location @@ -767,12 +833,18 @@ async def lookup( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/proxies/lookup", body=await async_maybe_transform({"socks5": socks5}, proxy_lookup_params.ProxyLookupParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ProxyLookupResponse, ) diff --git a/src/mobilerun_sdk/resources/store/apps.py b/src/mobilerun_sdk/resources/store/apps.py index 8dc41c15..fcfb3198 100644 --- a/src/mobilerun_sdk/resources/store/apps.py +++ b/src/mobilerun_sdk/resources/store/apps.py @@ -134,6 +134,7 @@ def add_to_workspace( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppAddToWorkspaceResponse: """ Installs a published store app into the caller's workspace — the existing @@ -147,13 +148,19 @@ def add_to_workspace( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not app_id: raise ValueError(f"Expected a non-empty value for `app_id` but received {app_id!r}") return self._post( path_template("/store/apps/{app_id}/add", app_id=app_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppAddToWorkspaceResponse, ) @@ -270,6 +277,7 @@ async def add_to_workspace( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> AppAddToWorkspaceResponse: """ Installs a published store app into the caller's workspace — the existing @@ -283,13 +291,19 @@ async def add_to_workspace( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not app_id: raise ValueError(f"Expected a non-empty value for `app_id` but received {app_id!r}") return await self._post( path_template("/store/apps/{app_id}/add", app_id=app_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=AppAddToWorkspaceResponse, ) diff --git a/src/mobilerun_sdk/resources/tasks/tasks.py b/src/mobilerun_sdk/resources/tasks/tasks.py index c6656997..c93af9c6 100644 --- a/src/mobilerun_sdk/resources/tasks/tasks.py +++ b/src/mobilerun_sdk/resources/tasks/tasks.py @@ -9,7 +9,7 @@ from ...types import task_run_params, task_list_params, task_run_streamed_params, task_send_message_params from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform +from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property from .ui_states import ( UiStatesResource, @@ -301,13 +301,13 @@ def run( temperature: float | Omit = omit, vision: bool | Omit = omit, vpn_country: Optional[Literal["US", "BR", "FR", "DE", "IN", "JP", "KR", "ZA"]] | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskRunResponse: """Create and dispatch a new agent task. @@ -321,7 +321,7 @@ def run( execution_timeout: Maximum agent execution time in seconds (1–2700). - llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna') + llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna') recording_enabled: Record device video for the whole task and persist a retrievable reference @@ -339,8 +339,9 @@ def run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return self._post( "/tasks", body=maybe_transform( @@ -369,7 +370,11 @@ def run( task_run_params.TaskRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskRunResponse, ) @@ -403,6 +408,7 @@ def run_streamed( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> object: """ Create and dispatch a new agent task, returning an SSE stream of task events. @@ -415,7 +421,7 @@ def run_streamed( execution_timeout: Maximum agent execution time in seconds (1–2700). - llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna') + llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna') recording_enabled: Record device video for the whole task and persist a retrievable reference @@ -433,6 +439,8 @@ def run_streamed( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/tasks/stream", @@ -462,7 +470,11 @@ def run_streamed( task_run_streamed_params.TaskRunStreamedParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=object, ) @@ -478,6 +490,7 @@ def send_message( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskSendMessageResponse: """Send a message to a running agent task. @@ -494,6 +507,8 @@ def send_message( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not task_id: raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") @@ -501,7 +516,11 @@ def send_message( path_template("/tasks/{task_id}/message", task_id=task_id), body=maybe_transform({"message": message}, task_send_message_params.TaskSendMessageParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskSendMessageResponse, ) @@ -516,6 +535,7 @@ def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskStopResponse: """Cancel a running task. @@ -530,13 +550,19 @@ def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not task_id: raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") return self._post( path_template("/tasks/{task_id}/cancel", task_id=task_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskStopResponse, ) @@ -797,13 +823,13 @@ async def run( temperature: float | Omit = omit, vision: bool | Omit = omit, vpn_country: Optional[Literal["US", "BR", "FR", "DE", "IN", "JP", "KR", "ZA"]] | Omit = omit, - idempotency_key: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskRunResponse: """Create and dispatch a new agent task. @@ -817,7 +843,7 @@ async def run( execution_timeout: Maximum agent execution time in seconds (1–2700). - llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna') + llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna') recording_enabled: Record device video for the whole task and persist a retrievable reference @@ -835,8 +861,9 @@ async def run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})} return await self._post( "/tasks", body=await async_maybe_transform( @@ -865,7 +892,11 @@ async def run( task_run_params.TaskRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskRunResponse, ) @@ -899,6 +930,7 @@ async def run_streamed( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> object: """ Create and dispatch a new agent task, returning an SSE stream of task events. @@ -911,7 +943,7 @@ async def run_streamed( execution_timeout: Maximum agent execution time in seconds (1–2700). - llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna') + llm_model: The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna') recording_enabled: Record device video for the whole task and persist a retrievable reference @@ -929,6 +961,8 @@ async def run_streamed( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/tasks/stream", @@ -958,7 +992,11 @@ async def run_streamed( task_run_streamed_params.TaskRunStreamedParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=object, ) @@ -974,6 +1012,7 @@ async def send_message( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskSendMessageResponse: """Send a message to a running agent task. @@ -990,6 +1029,8 @@ async def send_message( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not task_id: raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") @@ -997,7 +1038,11 @@ async def send_message( path_template("/tasks/{task_id}/message", task_id=task_id), body=await async_maybe_transform({"message": message}, task_send_message_params.TaskSendMessageParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskSendMessageResponse, ) @@ -1012,6 +1057,7 @@ async def stop( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TaskStopResponse: """Cancel a running task. @@ -1026,13 +1072,19 @@ async def stop( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not task_id: raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") return await self._post( path_template("/tasks/{task_id}/cancel", task_id=task_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TaskStopResponse, ) diff --git a/src/mobilerun_sdk/resources/webhooks/__init__.py b/src/mobilerun_sdk/resources/webhooks/__init__.py index ad7a4a4a..61ffbe4b 100644 --- a/src/mobilerun_sdk/resources/webhooks/__init__.py +++ b/src/mobilerun_sdk/resources/webhooks/__init__.py @@ -16,8 +16,22 @@ DeliveriesResourceWithStreamingResponse, AsyncDeliveriesResourceWithStreamingResponse, ) +from .integrations import ( + IntegrationsResource, + AsyncIntegrationsResource, + IntegrationsResourceWithRawResponse, + AsyncIntegrationsResourceWithRawResponse, + IntegrationsResourceWithStreamingResponse, + AsyncIntegrationsResourceWithStreamingResponse, +) __all__ = [ + "IntegrationsResource", + "AsyncIntegrationsResource", + "IntegrationsResourceWithRawResponse", + "AsyncIntegrationsResourceWithRawResponse", + "IntegrationsResourceWithStreamingResponse", + "AsyncIntegrationsResourceWithStreamingResponse", "DeliveriesResource", "AsyncDeliveriesResource", "DeliveriesResourceWithRawResponse", diff --git a/src/mobilerun_sdk/resources/webhooks/deliveries.py b/src/mobilerun_sdk/resources/webhooks/deliveries.py index 294b8848..4b54486b 100644 --- a/src/mobilerun_sdk/resources/webhooks/deliveries.py +++ b/src/mobilerun_sdk/resources/webhooks/deliveries.py @@ -52,6 +52,7 @@ def list( self, *, event_id: str | Omit = omit, + kind: Literal["http", "integration"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, since: Union[str, datetime] | Omit = omit, @@ -67,11 +68,14 @@ def list( Returns a paginated feed of webhook deliveries across all of your subscriptions, with the originating endpoint URL included on each record. Results can be filtered by delivery status (pending, success, skipped, or dead), by a `since` - timestamp, and by `eventId` (exact match against the originating event id). + timestamp, by `eventId` (exact match against the originating event id), and/or + by endpoint delivery `kind`. Args: event_id: Exact text match against the originating event id. + kind: Only include deliveries to endpoints of this kind. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -90,6 +94,7 @@ def list( query=maybe_transform( { "event_id": event_id, + "kind": kind, "page": page, "page_size": page_size, "since": since, @@ -194,6 +199,7 @@ def retrieve_attempts( def stats( self, *, + kind: Literal["http", "integration"] | Omit = omit, since: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -205,10 +211,12 @@ def stats( """ Returns aggregate delivery statistics across all of your webhooks, including the total count, a breakdown by status (pending, success, skipped, dead), and the - overall success rate. An optional `since` timestamp narrows the reporting - window. + overall success rate. Optional `since` and endpoint delivery `kind` filters + narrow the reporting window. Args: + kind: Only include deliveries to endpoints of this kind. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -224,7 +232,13 @@ def stats( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"since": since}, delivery_stats_params.DeliveryStatsParams), + query=maybe_transform( + { + "kind": kind, + "since": since, + }, + delivery_stats_params.DeliveryStatsParams, + ), ), cast_to=DeliveryStatsResponse, ) @@ -254,6 +268,7 @@ async def list( self, *, event_id: str | Omit = omit, + kind: Literal["http", "integration"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, since: Union[str, datetime] | Omit = omit, @@ -269,11 +284,14 @@ async def list( Returns a paginated feed of webhook deliveries across all of your subscriptions, with the originating endpoint URL included on each record. Results can be filtered by delivery status (pending, success, skipped, or dead), by a `since` - timestamp, and by `eventId` (exact match against the originating event id). + timestamp, by `eventId` (exact match against the originating event id), and/or + by endpoint delivery `kind`. Args: event_id: Exact text match against the originating event id. + kind: Only include deliveries to endpoints of this kind. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -292,6 +310,7 @@ async def list( query=await async_maybe_transform( { "event_id": event_id, + "kind": kind, "page": page, "page_size": page_size, "since": since, @@ -396,6 +415,7 @@ async def retrieve_attempts( async def stats( self, *, + kind: Literal["http", "integration"] | Omit = omit, since: Union[str, datetime] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -407,10 +427,12 @@ async def stats( """ Returns aggregate delivery statistics across all of your webhooks, including the total count, a breakdown by status (pending, success, skipped, dead), and the - overall success rate. An optional `since` timestamp narrows the reporting - window. + overall success rate. Optional `since` and endpoint delivery `kind` filters + narrow the reporting window. Args: + kind: Only include deliveries to endpoints of this kind. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -426,7 +448,13 @@ async def stats( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform({"since": since}, delivery_stats_params.DeliveryStatsParams), + query=await async_maybe_transform( + { + "kind": kind, + "since": since, + }, + delivery_stats_params.DeliveryStatsParams, + ), ), cast_to=DeliveryStatsResponse, ) diff --git a/src/mobilerun_sdk/resources/webhooks/integrations.py b/src/mobilerun_sdk/resources/webhooks/integrations.py new file mode 100644 index 00000000..34fe9d84 --- /dev/null +++ b/src/mobilerun_sdk/resources/webhooks/integrations.py @@ -0,0 +1,244 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.webhooks import integration_list_targets_params +from ...types.webhooks.integration_list_response import IntegrationListResponse +from ...types.webhooks.integration_list_targets_response import IntegrationListTargetsResponse + +__all__ = ["IntegrationsResource", "AsyncIntegrationsResource"] + + +class IntegrationsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> IntegrationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return IntegrationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> IntegrationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return IntegrationsResourceWithStreamingResponse(self) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> IntegrationListResponse: + """ + Returns the integrations that can receive webhook events as channel messages + (e.g. Slack), with your org's connection state for each. Connect an integration + in the Cloud app before creating a webhook for it. + """ + return self._get( + "/webhooks/integrations", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=IntegrationListResponse, + ) + + def list_targets( + self, + capability_id: str, + *, + revision: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> IntegrationListTargetsResponse: + """Returns the live list of targets (e.g. + + Slack channels the connected account is a + member of) for an integration. Pass a target's `args` together with the + integration `capability` to `POST /webhooks` with `kind: "integration"`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not capability_id: + raise ValueError(f"Expected a non-empty value for `capability_id` but received {capability_id!r}") + return self._get( + path_template("/webhooks/integrations/{capability_id}/targets", capability_id=capability_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"revision": revision}, integration_list_targets_params.IntegrationListTargetsParams + ), + ), + cast_to=IntegrationListTargetsResponse, + ) + + +class AsyncIntegrationsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncIntegrationsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncIntegrationsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncIntegrationsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncIntegrationsResourceWithStreamingResponse(self) + + async def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> IntegrationListResponse: + """ + Returns the integrations that can receive webhook events as channel messages + (e.g. Slack), with your org's connection state for each. Connect an integration + in the Cloud app before creating a webhook for it. + """ + return await self._get( + "/webhooks/integrations", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=IntegrationListResponse, + ) + + async def list_targets( + self, + capability_id: str, + *, + revision: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> IntegrationListTargetsResponse: + """Returns the live list of targets (e.g. + + Slack channels the connected account is a + member of) for an integration. Pass a target's `args` together with the + integration `capability` to `POST /webhooks` with `kind: "integration"`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not capability_id: + raise ValueError(f"Expected a non-empty value for `capability_id` but received {capability_id!r}") + return await self._get( + path_template("/webhooks/integrations/{capability_id}/targets", capability_id=capability_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"revision": revision}, integration_list_targets_params.IntegrationListTargetsParams + ), + ), + cast_to=IntegrationListTargetsResponse, + ) + + +class IntegrationsResourceWithRawResponse: + def __init__(self, integrations: IntegrationsResource) -> None: + self._integrations = integrations + + self.list = to_raw_response_wrapper( + integrations.list, + ) + self.list_targets = to_raw_response_wrapper( + integrations.list_targets, + ) + + +class AsyncIntegrationsResourceWithRawResponse: + def __init__(self, integrations: AsyncIntegrationsResource) -> None: + self._integrations = integrations + + self.list = async_to_raw_response_wrapper( + integrations.list, + ) + self.list_targets = async_to_raw_response_wrapper( + integrations.list_targets, + ) + + +class IntegrationsResourceWithStreamingResponse: + def __init__(self, integrations: IntegrationsResource) -> None: + self._integrations = integrations + + self.list = to_streamed_response_wrapper( + integrations.list, + ) + self.list_targets = to_streamed_response_wrapper( + integrations.list_targets, + ) + + +class AsyncIntegrationsResourceWithStreamingResponse: + def __init__(self, integrations: AsyncIntegrationsResource) -> None: + self._integrations = integrations + + self.list = async_to_streamed_response_wrapper( + integrations.list, + ) + self.list_targets = async_to_streamed_response_wrapper( + integrations.list_targets, + ) diff --git a/src/mobilerun_sdk/resources/webhooks/webhooks.py b/src/mobilerun_sdk/resources/webhooks/webhooks.py index dd33580a..12dabe0f 100644 --- a/src/mobilerun_sdk/resources/webhooks/webhooks.py +++ b/src/mobilerun_sdk/resources/webhooks/webhooks.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import Optional -from typing_extensions import Literal +from typing import Dict, Optional +from typing_extensions import Literal, overload import httpx from ...types import webhook_list_params, webhook_create_params, webhook_update_params from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform +from ..._utils import path_template, required_args, maybe_transform, async_maybe_transform from ..._compat import cached_property from .deliveries import ( DeliveriesResource, @@ -26,6 +26,14 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) +from .integrations import ( + IntegrationsResource, + AsyncIntegrationsResource, + IntegrationsResourceWithRawResponse, + AsyncIntegrationsResourceWithRawResponse, + IntegrationsResourceWithStreamingResponse, + AsyncIntegrationsResourceWithStreamingResponse, +) from ..._base_client import make_request_options from ...types.webhook_list_response import WebhookListResponse from ...types.webhook_create_response import WebhookCreateResponse @@ -39,6 +47,10 @@ class WebhooksResource(SyncAPIResource): + @cached_property + def integrations(self) -> IntegrationsResource: + return IntegrationsResource(self._client) + @cached_property def deliveries(self) -> DeliveriesResource: return DeliveriesResource(self._client) @@ -62,26 +74,36 @@ def with_streaming_response(self) -> WebhooksResourceWithStreamingResponse: """ return WebhooksResourceWithStreamingResponse(self) + @overload def create( self, *, url: str, description: str | Omit = omit, event_types: SequenceNotStr[str] | Omit = omit, + kind: Literal["http"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookCreateResponse: """ - Creates a webhook subscription with a delivery URL and an optional list of event - types to subscribe to (defaults to all when omitted). The response includes the - generated signing secret, which is returned only once at creation time and - cannot be retrieved later. + Creates a webhook subscription and an optional list of event types to subscribe + to (defaults to all when omitted). `kind: "http"` (the default) delivers signed + JSON to a URL; the response includes the generated signing secret, which is + returned only once at creation time and cannot be retrieved later. + `kind: "integration"` posts each event as a message into a connected integration + target (e.g. a Slack channel): pick the `capability` from + `GET /webhooks/integrations` and the `args` from + `GET /webhooks/integrations/{capabilityId}/targets`. Integration webhooks have + no signing secret. Args: + kind: Delivery transport. Omitted ⇒ `http`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -89,7 +111,73 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + ... + + @overload + def create( + self, + *, + args: Dict[str, object], + capability: webhook_create_params.Variant1Capability, + kind: Literal["integration"], + description: str | Omit = omit, + event_types: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> WebhookCreateResponse: """ + Creates a webhook subscription and an optional list of event types to subscribe + to (defaults to all when omitted). `kind: "http"` (the default) delivers signed + JSON to a URL; the response includes the generated signing secret, which is + returned only once at creation time and cannot be retrieved later. + `kind: "integration"` posts each event as a message into a connected integration + target (e.g. a Slack channel): pick the `capability` from + `GET /webhooks/integrations` and the `args` from + `GET /webhooks/integrations/{capabilityId}/targets`. Integration webhooks have + no signing secret. + + Args: + args: Target args exactly as returned by + `GET /webhooks/integrations/{capabilityId}/targets`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + ... + + @required_args(["url"], ["args", "capability", "kind"]) + def create( + self, + *, + url: str | Omit = omit, + description: str | Omit = omit, + event_types: SequenceNotStr[str] | Omit = omit, + kind: Literal["http"] | Literal["integration"] | Omit = omit, + args: Dict[str, object] | Omit = omit, + capability: webhook_create_params.Variant1Capability | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> WebhookCreateResponse: return self._post( "/webhooks", body=maybe_transform( @@ -97,11 +185,18 @@ def create( "url": url, "description": description, "event_types": event_types, + "kind": kind, + "args": args, + "capability": capability, }, webhook_create_params.WebhookCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookCreateResponse, ) @@ -118,9 +213,9 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookRetrieveResponse: """ - Returns a single webhook subscription by id, including its URL, subscribed event - types, state, and system-observed delivery health. The signing secret is never - included. + Returns a single webhook subscription by id, including its URL (or integration + target), subscribed event types, state, and system-observed delivery health. The + signing secret is never included. Args: extra_headers: Send extra headers @@ -154,13 +249,15 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookUpdateResponse: """Updates a webhook subscription. Any combination of the subscribed event types, state (ACTIVE or DISABLED), and description may be changed, and at least one field must be supplied. Setting state to ACTIVE re-enables a subscription that - was auto-blocked after sustained delivery failures. + was auto-blocked after sustained delivery failures. The URL or integration + target cannot be changed; delete and recreate the webhook instead. Args: extra_headers: Send extra headers @@ -170,6 +267,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -184,7 +283,11 @@ def update( webhook_update_params.WebhookUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookUpdateResponse, ) @@ -193,6 +296,7 @@ def list( self, *, created_by: str | Omit = omit, + kind: Literal["http", "integration"] | Omit = omit, mine: Literal["true", "false"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, @@ -207,13 +311,16 @@ def list( ) -> WebhookListResponse: """ Returns a paginated list of your webhook subscriptions, optionally filtered by - status (active, failing, blocked, or disabled) and/or by `search` (a - case-insensitive substring match against the URL or description). The response - also includes per-status counts across all of your subscriptions. + status (active, failing, blocked, or disabled), by `search` (a case-insensitive + substring match against the URL or description), and/or by delivery `kind`. The + response also includes per-status counts across all of your subscriptions (of + the requested `kind`, if given; the other filters do not apply to the counts). Args: created_by: Only include webhooks created by this actor id. Mutually exclusive with `mine`. + kind: Only include webhooks of this delivery kind. + mine: When true, only include webhooks created by you (not just owned by your org). search: Case-insensitive substring match against the URL or description. @@ -236,6 +343,7 @@ def list( query=maybe_transform( { "created_by": created_by, + "kind": kind, "mine": mine, "page": page, "page_size": page_size, @@ -258,6 +366,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Deletes a webhook subscription so it stops receiving deliveries. @@ -272,6 +381,8 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -279,7 +390,11 @@ def delete( return self._delete( path_template("/webhooks/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -317,11 +432,13 @@ def rotate_secret( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookRotateSecretResponse: """ Generates a new signing secret for the webhook subscription and returns it once in the response. The previous secret is replaced immediately, so any signature - verification on your endpoint must be updated to use the new value. + verification on your endpoint must be updated to use the new value. Only `http` + webhooks have a signing secret; for `integration` webhooks this returns 400. Args: extra_headers: Send extra headers @@ -331,13 +448,19 @@ def rotate_secret( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( path_template("/webhooks/{id}/rotate-secret", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookRotateSecretResponse, ) @@ -352,11 +475,12 @@ def test_delivery( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookTestDeliveryResponse: """ - Sends a single test payload to the webhook subscription URL to verify - connectivity. The response reports whether the attempt succeeded along with the - returned HTTP status code or error, if any. + Sends a single test payload to the webhook subscription URL (or a test message + to its integration target) to verify connectivity. The response reports whether + the attempt succeeded along with the returned HTTP status code or error, if any. Args: extra_headers: Send extra headers @@ -366,19 +490,29 @@ def test_delivery( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._post( path_template("/webhooks/{id}/test", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookTestDeliveryResponse, ) class AsyncWebhooksResource(AsyncAPIResource): + @cached_property + def integrations(self) -> AsyncIntegrationsResource: + return AsyncIntegrationsResource(self._client) + @cached_property def deliveries(self) -> AsyncDeliveriesResource: return AsyncDeliveriesResource(self._client) @@ -402,26 +536,36 @@ def with_streaming_response(self) -> AsyncWebhooksResourceWithStreamingResponse: """ return AsyncWebhooksResourceWithStreamingResponse(self) + @overload async def create( self, *, url: str, description: str | Omit = omit, event_types: SequenceNotStr[str] | Omit = omit, + kind: Literal["http"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookCreateResponse: """ - Creates a webhook subscription with a delivery URL and an optional list of event - types to subscribe to (defaults to all when omitted). The response includes the - generated signing secret, which is returned only once at creation time and - cannot be retrieved later. + Creates a webhook subscription and an optional list of event types to subscribe + to (defaults to all when omitted). `kind: "http"` (the default) delivers signed + JSON to a URL; the response includes the generated signing secret, which is + returned only once at creation time and cannot be retrieved later. + `kind: "integration"` posts each event as a message into a connected integration + target (e.g. a Slack channel): pick the `capability` from + `GET /webhooks/integrations` and the `args` from + `GET /webhooks/integrations/{capabilityId}/targets`. Integration webhooks have + no signing secret. Args: + kind: Delivery transport. Omitted ⇒ `http`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -429,7 +573,73 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ + ... + + @overload + async def create( + self, + *, + args: Dict[str, object], + capability: webhook_create_params.Variant1Capability, + kind: Literal["integration"], + description: str | Omit = omit, + event_types: SequenceNotStr[str] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> WebhookCreateResponse: + """ + Creates a webhook subscription and an optional list of event types to subscribe + to (defaults to all when omitted). `kind: "http"` (the default) delivers signed + JSON to a URL; the response includes the generated signing secret, which is + returned only once at creation time and cannot be retrieved later. + `kind: "integration"` posts each event as a message into a connected integration + target (e.g. a Slack channel): pick the `capability` from + `GET /webhooks/integrations` and the `args` from + `GET /webhooks/integrations/{capabilityId}/targets`. Integration webhooks have + no signing secret. + + Args: + args: Target args exactly as returned by + `GET /webhooks/integrations/{capabilityId}/targets`. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + ... + + @required_args(["url"], ["args", "capability", "kind"]) + async def create( + self, + *, + url: str | Omit = omit, + description: str | Omit = omit, + event_types: SequenceNotStr[str] | Omit = omit, + kind: Literal["http"] | Literal["integration"] | Omit = omit, + args: Dict[str, object] | Omit = omit, + capability: webhook_create_params.Variant1Capability | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> WebhookCreateResponse: return await self._post( "/webhooks", body=await async_maybe_transform( @@ -437,11 +647,18 @@ async def create( "url": url, "description": description, "event_types": event_types, + "kind": kind, + "args": args, + "capability": capability, }, webhook_create_params.WebhookCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookCreateResponse, ) @@ -458,9 +675,9 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> WebhookRetrieveResponse: """ - Returns a single webhook subscription by id, including its URL, subscribed event - types, state, and system-observed delivery health. The signing secret is never - included. + Returns a single webhook subscription by id, including its URL (or integration + target), subscribed event types, state, and system-observed delivery health. The + signing secret is never included. Args: extra_headers: Send extra headers @@ -494,13 +711,15 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookUpdateResponse: """Updates a webhook subscription. Any combination of the subscribed event types, state (ACTIVE or DISABLED), and description may be changed, and at least one field must be supplied. Setting state to ACTIVE re-enables a subscription that - was auto-blocked after sustained delivery failures. + was auto-blocked after sustained delivery failures. The URL or integration + target cannot be changed; delete and recreate the webhook instead. Args: extra_headers: Send extra headers @@ -510,6 +729,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -524,7 +745,11 @@ async def update( webhook_update_params.WebhookUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookUpdateResponse, ) @@ -533,6 +758,7 @@ async def list( self, *, created_by: str | Omit = omit, + kind: Literal["http", "integration"] | Omit = omit, mine: Literal["true", "false"] | Omit = omit, page: int | Omit = omit, page_size: int | Omit = omit, @@ -547,13 +773,16 @@ async def list( ) -> WebhookListResponse: """ Returns a paginated list of your webhook subscriptions, optionally filtered by - status (active, failing, blocked, or disabled) and/or by `search` (a - case-insensitive substring match against the URL or description). The response - also includes per-status counts across all of your subscriptions. + status (active, failing, blocked, or disabled), by `search` (a case-insensitive + substring match against the URL or description), and/or by delivery `kind`. The + response also includes per-status counts across all of your subscriptions (of + the requested `kind`, if given; the other filters do not apply to the counts). Args: created_by: Only include webhooks created by this actor id. Mutually exclusive with `mine`. + kind: Only include webhooks of this delivery kind. + mine: When true, only include webhooks created by you (not just owned by your org). search: Case-insensitive substring match against the URL or description. @@ -576,6 +805,7 @@ async def list( query=await async_maybe_transform( { "created_by": created_by, + "kind": kind, "mine": mine, "page": page, "page_size": page_size, @@ -598,6 +828,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> None: """Deletes a webhook subscription so it stops receiving deliveries. @@ -612,6 +843,8 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") @@ -619,7 +852,11 @@ async def delete( return await self._delete( path_template("/webhooks/{id}", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=NoneType, ) @@ -657,11 +894,13 @@ async def rotate_secret( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookRotateSecretResponse: """ Generates a new signing secret for the webhook subscription and returns it once in the response. The previous secret is replaced immediately, so any signature - verification on your endpoint must be updated to use the new value. + verification on your endpoint must be updated to use the new value. Only `http` + webhooks have a signing secret; for `integration` webhooks this returns 400. Args: extra_headers: Send extra headers @@ -671,13 +910,19 @@ async def rotate_secret( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( path_template("/webhooks/{id}/rotate-secret", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookRotateSecretResponse, ) @@ -692,11 +937,12 @@ async def test_delivery( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> WebhookTestDeliveryResponse: """ - Sends a single test payload to the webhook subscription URL to verify - connectivity. The response reports whether the attempt succeeded along with the - returned HTTP status code or error, if any. + Sends a single test payload to the webhook subscription URL (or a test message + to its integration target) to verify connectivity. The response reports whether + the attempt succeeded along with the returned HTTP status code or error, if any. Args: extra_headers: Send extra headers @@ -706,13 +952,19 @@ async def test_delivery( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._post( path_template("/webhooks/{id}/test", id=id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=WebhookTestDeliveryResponse, ) @@ -747,6 +999,10 @@ def __init__(self, webhooks: WebhooksResource) -> None: webhooks.test_delivery, ) + @cached_property + def integrations(self) -> IntegrationsResourceWithRawResponse: + return IntegrationsResourceWithRawResponse(self._webhooks.integrations) + @cached_property def deliveries(self) -> DeliveriesResourceWithRawResponse: return DeliveriesResourceWithRawResponse(self._webhooks.deliveries) @@ -781,6 +1037,10 @@ def __init__(self, webhooks: AsyncWebhooksResource) -> None: webhooks.test_delivery, ) + @cached_property + def integrations(self) -> AsyncIntegrationsResourceWithRawResponse: + return AsyncIntegrationsResourceWithRawResponse(self._webhooks.integrations) + @cached_property def deliveries(self) -> AsyncDeliveriesResourceWithRawResponse: return AsyncDeliveriesResourceWithRawResponse(self._webhooks.deliveries) @@ -815,6 +1075,10 @@ def __init__(self, webhooks: WebhooksResource) -> None: webhooks.test_delivery, ) + @cached_property + def integrations(self) -> IntegrationsResourceWithStreamingResponse: + return IntegrationsResourceWithStreamingResponse(self._webhooks.integrations) + @cached_property def deliveries(self) -> DeliveriesResourceWithStreamingResponse: return DeliveriesResourceWithStreamingResponse(self._webhooks.deliveries) @@ -849,6 +1113,10 @@ def __init__(self, webhooks: AsyncWebhooksResource) -> None: webhooks.test_delivery, ) + @cached_property + def integrations(self) -> AsyncIntegrationsResourceWithStreamingResponse: + return AsyncIntegrationsResourceWithStreamingResponse(self._webhooks.integrations) + @cached_property def deliveries(self) -> AsyncDeliveriesResourceWithStreamingResponse: return AsyncDeliveriesResourceWithStreamingResponse(self._webhooks.deliveries) diff --git a/src/mobilerun_sdk/resources/workflows/action_catalog.py b/src/mobilerun_sdk/resources/workflows/action_catalog.py index da75896c..2fc99f7d 100644 --- a/src/mobilerun_sdk/resources/workflows/action_catalog.py +++ b/src/mobilerun_sdk/resources/workflows/action_catalog.py @@ -83,7 +83,7 @@ def list( *, page: int | Omit = omit, page_size: int | Omit = omit, - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] | Omit = omit, + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -184,7 +184,7 @@ async def list( *, page: int | Omit = omit, page_size: int | Omit = omit, - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] | Omit = omit, + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, diff --git a/src/mobilerun_sdk/resources/workflows/actions/actions.py b/src/mobilerun_sdk/resources/workflows/actions/actions.py index d99bbf4e..6d9048ed 100644 --- a/src/mobilerun_sdk/resources/workflows/actions/actions.py +++ b/src/mobilerun_sdk/resources/workflows/actions/actions.py @@ -73,12 +73,17 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionCreateResponse: """ Create a reusable action from a catalog entry (`catalogEntryId`), with an optional `params` object supplying the values for that entry's service method. Returns 400 if the params are invalid for the chosen catalog entry. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: extra_headers: Send extra headers @@ -87,6 +92,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/actions", @@ -100,7 +107,11 @@ def create( action_create_params.ActionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionCreateResponse, ) @@ -152,6 +163,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionUpdateResponse: """ Partially update an action's name, description, or params; all fields are @@ -165,6 +177,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not action_id: raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") @@ -179,7 +193,11 @@ def update( action_update_params.ActionUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionUpdateResponse, ) @@ -192,7 +210,7 @@ def list( page: int | Omit = omit, page_size: int | Omit = omit, search: str | Omit = omit, - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] | Omit = omit, + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -246,6 +264,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionDeleteResponse: """Delete an action by its ID. @@ -259,13 +278,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not action_id: raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") return self._delete( path_template("/actions/{action_id}", action_id=action_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionDeleteResponse, ) @@ -308,12 +333,17 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionCreateResponse: """ Create a reusable action from a catalog entry (`catalogEntryId`), with an optional `params` object supplying the values for that entry's service method. Returns 400 if the params are invalid for the chosen catalog entry. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: extra_headers: Send extra headers @@ -322,6 +352,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/actions", @@ -335,7 +367,11 @@ async def create( action_create_params.ActionCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionCreateResponse, ) @@ -387,6 +423,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionUpdateResponse: """ Partially update an action's name, description, or params; all fields are @@ -400,6 +437,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not action_id: raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") @@ -414,7 +453,11 @@ async def update( action_update_params.ActionUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionUpdateResponse, ) @@ -427,7 +470,7 @@ async def list( page: int | Omit = omit, page_size: int | Omit = omit, search: str | Omit = omit, - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] | Omit = omit, + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -481,6 +524,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionDeleteResponse: """Delete an action by its ID. @@ -494,13 +538,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not action_id: raise ValueError(f"Expected a non-empty value for `action_id` but received {action_id!r}") return await self._delete( path_template("/actions/{action_id}", action_id=action_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionDeleteResponse, ) diff --git a/src/mobilerun_sdk/resources/workflows/events.py b/src/mobilerun_sdk/resources/workflows/events.py index b585ec6d..d60f0f8d 100644 --- a/src/mobilerun_sdk/resources/workflows/events.py +++ b/src/mobilerun_sdk/resources/workflows/events.py @@ -56,6 +56,7 @@ def dry_run( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EventDryRunResponse: """Simulate an event against all configured flows. @@ -70,6 +71,8 @@ def dry_run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/events/dry-run", @@ -82,7 +85,11 @@ def dry_run( event_dry_run_params.EventDryRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EventDryRunResponse, ) @@ -99,6 +106,7 @@ def ingest( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EventIngestResponse: """Ingest an event for trigger evaluation. @@ -112,6 +120,8 @@ def ingest( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/events/ingest", @@ -124,7 +134,11 @@ def ingest( event_ingest_params.EventIngestParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EventIngestResponse, ) @@ -162,6 +176,7 @@ async def dry_run( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EventDryRunResponse: """Simulate an event against all configured flows. @@ -176,6 +191,8 @@ async def dry_run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/events/dry-run", @@ -188,7 +205,11 @@ async def dry_run( event_dry_run_params.EventDryRunParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EventDryRunResponse, ) @@ -205,6 +226,7 @@ async def ingest( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> EventIngestResponse: """Ingest an event for trigger evaluation. @@ -218,6 +240,8 @@ async def ingest( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/events/ingest", @@ -230,7 +254,11 @@ async def ingest( event_ingest_params.EventIngestParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=EventIngestResponse, ) diff --git a/src/mobilerun_sdk/resources/workflows/executions/__init__.py b/src/mobilerun_sdk/resources/workflows/executions/__init__.py new file mode 100644 index 00000000..7364e926 --- /dev/null +++ b/src/mobilerun_sdk/resources/workflows/executions/__init__.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .executions import ( + ExecutionsResource, + AsyncExecutionsResource, + ExecutionsResourceWithRawResponse, + AsyncExecutionsResourceWithRawResponse, + ExecutionsResourceWithStreamingResponse, + AsyncExecutionsResourceWithStreamingResponse, +) +from .screenshots import ( + ScreenshotsResource, + AsyncScreenshotsResource, + ScreenshotsResourceWithRawResponse, + AsyncScreenshotsResourceWithRawResponse, + ScreenshotsResourceWithStreamingResponse, + AsyncScreenshotsResourceWithStreamingResponse, +) + +__all__ = [ + "ScreenshotsResource", + "AsyncScreenshotsResource", + "ScreenshotsResourceWithRawResponse", + "AsyncScreenshotsResourceWithRawResponse", + "ScreenshotsResourceWithStreamingResponse", + "AsyncScreenshotsResourceWithStreamingResponse", + "ExecutionsResource", + "AsyncExecutionsResource", + "ExecutionsResourceWithRawResponse", + "AsyncExecutionsResourceWithRawResponse", + "ExecutionsResourceWithStreamingResponse", + "AsyncExecutionsResourceWithStreamingResponse", +] diff --git a/src/mobilerun_sdk/resources/workflows/executions.py b/src/mobilerun_sdk/resources/workflows/executions/executions.py similarity index 85% rename from src/mobilerun_sdk/resources/workflows/executions.py rename to src/mobilerun_sdk/resources/workflows/executions/executions.py index cc3f03bd..8c5eafe4 100644 --- a/src/mobilerun_sdk/resources/workflows/executions.py +++ b/src/mobilerun_sdk/resources/workflows/executions/executions.py @@ -2,32 +2,45 @@ from __future__ import annotations -from typing import Optional +from typing import Union +from datetime import datetime from typing_extensions import Literal import httpx -from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import path_template, maybe_transform, async_maybe_transform -from ..._compat import cached_property -from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( +from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ...._utils import path_template, maybe_transform, async_maybe_transform +from ...._compat import cached_property +from .screenshots import ( + ScreenshotsResource, + AsyncScreenshotsResource, + ScreenshotsResourceWithRawResponse, + AsyncScreenshotsResourceWithRawResponse, + ScreenshotsResourceWithStreamingResponse, + AsyncScreenshotsResourceWithStreamingResponse, +) +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( to_raw_response_wrapper, to_streamed_response_wrapper, async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ..._base_client import make_request_options -from ...types.workflows import execution_list_params, execution_get_metrics_params -from ...types.workflows.execution_list_response import ExecutionListResponse -from ...types.workflows.execution_abort_response import ExecutionAbortResponse -from ...types.workflows.execution_retrieve_response import ExecutionRetrieveResponse -from ...types.workflows.execution_get_metrics_response import ExecutionGetMetricsResponse +from ...._base_client import make_request_options +from ....types.workflows import execution_list_params, execution_get_metrics_params +from ....types.workflows.execution_list_response import ExecutionListResponse +from ....types.workflows.execution_abort_response import ExecutionAbortResponse +from ....types.workflows.execution_retrieve_response import ExecutionRetrieveResponse +from ....types.workflows.execution_get_metrics_response import ExecutionGetMetricsResponse __all__ = ["ExecutionsResource", "AsyncExecutionsResource"] class ExecutionsResource(SyncAPIResource): + @cached_property + def screenshots(self) -> ScreenshotsResource: + return ScreenshotsResource(self._client) + @cached_property def with_raw_response(self) -> ExecutionsResourceWithRawResponse: """ @@ -85,7 +98,7 @@ def list( self, *, flow_id: str | Omit = omit, - from_: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, invocation_id: str | Omit = omit, order_by: Literal["startedAt", "finishedAt", "status"] | Omit = omit, order_by_direction: Literal["asc", "desc"] | Omit = omit, @@ -93,7 +106,7 @@ def list( page_size: int | Omit = omit, search: str | Omit = omit, status: Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"] | Omit = omit, - to: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, trigger_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -155,6 +168,7 @@ def abort( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ExecutionAbortResponse: """ Signals the worker to stop the execution between steps and marks it cancelled. @@ -168,13 +182,19 @@ def abort( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return self._post( path_template("/executions/{execution_id}/abort", execution_id=execution_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ExecutionAbortResponse, ) @@ -183,8 +203,8 @@ def get_metrics( self, *, flow_id: str | Omit = omit, - from_: Optional[str] | Omit = omit, - to: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, trigger_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -229,6 +249,10 @@ def get_metrics( class AsyncExecutionsResource(AsyncAPIResource): + @cached_property + def screenshots(self) -> AsyncScreenshotsResource: + return AsyncScreenshotsResource(self._client) + @cached_property def with_raw_response(self) -> AsyncExecutionsResourceWithRawResponse: """ @@ -286,7 +310,7 @@ async def list( self, *, flow_id: str | Omit = omit, - from_: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, invocation_id: str | Omit = omit, order_by: Literal["startedAt", "finishedAt", "status"] | Omit = omit, order_by_direction: Literal["asc", "desc"] | Omit = omit, @@ -294,7 +318,7 @@ async def list( page_size: int | Omit = omit, search: str | Omit = omit, status: Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"] | Omit = omit, - to: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, trigger_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -356,6 +380,7 @@ async def abort( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ExecutionAbortResponse: """ Signals the worker to stop the execution between steps and marks it cancelled. @@ -369,13 +394,19 @@ async def abort( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not execution_id: raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") return await self._post( path_template("/executions/{execution_id}/abort", execution_id=execution_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ExecutionAbortResponse, ) @@ -384,8 +415,8 @@ async def get_metrics( self, *, flow_id: str | Omit = omit, - from_: Optional[str] | Omit = omit, - to: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, trigger_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -446,6 +477,10 @@ def __init__(self, executions: ExecutionsResource) -> None: executions.get_metrics, ) + @cached_property + def screenshots(self) -> ScreenshotsResourceWithRawResponse: + return ScreenshotsResourceWithRawResponse(self._executions.screenshots) + class AsyncExecutionsResourceWithRawResponse: def __init__(self, executions: AsyncExecutionsResource) -> None: @@ -464,6 +499,10 @@ def __init__(self, executions: AsyncExecutionsResource) -> None: executions.get_metrics, ) + @cached_property + def screenshots(self) -> AsyncScreenshotsResourceWithRawResponse: + return AsyncScreenshotsResourceWithRawResponse(self._executions.screenshots) + class ExecutionsResourceWithStreamingResponse: def __init__(self, executions: ExecutionsResource) -> None: @@ -482,6 +521,10 @@ def __init__(self, executions: ExecutionsResource) -> None: executions.get_metrics, ) + @cached_property + def screenshots(self) -> ScreenshotsResourceWithStreamingResponse: + return ScreenshotsResourceWithStreamingResponse(self._executions.screenshots) + class AsyncExecutionsResourceWithStreamingResponse: def __init__(self, executions: AsyncExecutionsResource) -> None: @@ -499,3 +542,7 @@ def __init__(self, executions: AsyncExecutionsResource) -> None: self.get_metrics = async_to_streamed_response_wrapper( executions.get_metrics, ) + + @cached_property + def screenshots(self) -> AsyncScreenshotsResourceWithStreamingResponse: + return AsyncScreenshotsResourceWithStreamingResponse(self._executions.screenshots) diff --git a/src/mobilerun_sdk/resources/workflows/executions/screenshots.py b/src/mobilerun_sdk/resources/workflows/executions/screenshots.py new file mode 100644 index 00000000..7242b73a --- /dev/null +++ b/src/mobilerun_sdk/resources/workflows/executions/screenshots.py @@ -0,0 +1,183 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ...._types import Body, Query, Headers, NoneType, NotGiven, not_given +from ...._utils import path_template +from ...._compat import cached_property +from ...._resource import SyncAPIResource, AsyncAPIResource +from ...._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ...._base_client import make_request_options + +__all__ = ["ScreenshotsResource", "AsyncScreenshotsResource"] + + +class ScreenshotsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ScreenshotsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return ScreenshotsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ScreenshotsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return ScreenshotsResourceWithStreamingResponse(self) + + def retrieve( + self, + screenshot_id: str, + *, + execution_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Redirects (302) to a freshly signed, time-limited image URL for a screenshot + captured during this execution. Owner-gated: 404 if the execution or the + screenshot does not belong to the caller. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not execution_id: + raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + if not screenshot_id: + raise ValueError(f"Expected a non-empty value for `screenshot_id` but received {screenshot_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._get( + path_template( + "/executions/{execution_id}/screenshots/{screenshot_id}", + execution_id=execution_id, + screenshot_id=screenshot_id, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncScreenshotsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncScreenshotsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncScreenshotsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncScreenshotsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncScreenshotsResourceWithStreamingResponse(self) + + async def retrieve( + self, + screenshot_id: str, + *, + execution_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Redirects (302) to a freshly signed, time-limited image URL for a screenshot + captured during this execution. Owner-gated: 404 if the execution or the + screenshot does not belong to the caller. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not execution_id: + raise ValueError(f"Expected a non-empty value for `execution_id` but received {execution_id!r}") + if not screenshot_id: + raise ValueError(f"Expected a non-empty value for `screenshot_id` but received {screenshot_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._get( + path_template( + "/executions/{execution_id}/screenshots/{screenshot_id}", + execution_id=execution_id, + screenshot_id=screenshot_id, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class ScreenshotsResourceWithRawResponse: + def __init__(self, screenshots: ScreenshotsResource) -> None: + self._screenshots = screenshots + + self.retrieve = to_raw_response_wrapper( + screenshots.retrieve, + ) + + +class AsyncScreenshotsResourceWithRawResponse: + def __init__(self, screenshots: AsyncScreenshotsResource) -> None: + self._screenshots = screenshots + + self.retrieve = async_to_raw_response_wrapper( + screenshots.retrieve, + ) + + +class ScreenshotsResourceWithStreamingResponse: + def __init__(self, screenshots: ScreenshotsResource) -> None: + self._screenshots = screenshots + + self.retrieve = to_streamed_response_wrapper( + screenshots.retrieve, + ) + + +class AsyncScreenshotsResourceWithStreamingResponse: + def __init__(self, screenshots: AsyncScreenshotsResource) -> None: + self._screenshots = screenshots + + self.retrieve = async_to_streamed_response_wrapper( + screenshots.retrieve, + ) diff --git a/src/mobilerun_sdk/resources/workflows/flows/actions.py b/src/mobilerun_sdk/resources/workflows/flows/actions.py index 26ca8773..5b490435 100644 --- a/src/mobilerun_sdk/resources/workflows/flows/actions.py +++ b/src/mobilerun_sdk/resources/workflows/flows/actions.py @@ -88,6 +88,7 @@ def add( position: int, children: Iterable[action_add_params.Child] | Omit = omit, continue_on_error: bool | Omit = omit, + key: str | Omit = omit, name_override: str | Omit = omit, overrides: Optional[action_add_params.Overrides] | Omit = omit, parent_flow_action_id: Optional[str] | Omit = omit, @@ -98,6 +99,7 @@ def add( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionAddResponse: """ Append a single action to a flow at the given `position`, optionally nesting it @@ -105,7 +107,17 @@ def add( `nameOverride`, param `overrides`, and `continueOnError`. Returns 404 if the flow does not exist. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: + key: Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -113,6 +125,8 @@ def add( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -124,6 +138,7 @@ def add( "position": position, "children": children, "continue_on_error": continue_on_error, + "key": key, "name_override": name_override, "overrides": overrides, "parent_flow_action_id": parent_flow_action_id, @@ -132,7 +147,11 @@ def add( action_add_params.ActionAddParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionAddResponse, ) @@ -148,6 +167,7 @@ def remove( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionRemoveResponse: """Remove a single action from a flow by its `flowActionId`. @@ -162,6 +182,8 @@ def remove( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -170,7 +192,11 @@ def remove( return self._delete( path_template("/flows/{flow_id}/actions/{flow_action_id}", flow_id=flow_id, flow_action_id=flow_action_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionRemoveResponse, ) @@ -186,6 +212,7 @@ def replace( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionReplaceResponse: """ Replace a flow's entire action list with the supplied set (at least one @@ -201,6 +228,8 @@ def replace( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -208,7 +237,11 @@ def replace( path_template("/flows/{flow_id}/actions", flow_id=flow_id), body=maybe_transform({"actions": actions}, action_replace_params.ActionReplaceParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionReplaceResponse, ) @@ -276,6 +309,7 @@ async def add( position: int, children: Iterable[action_add_params.Child] | Omit = omit, continue_on_error: bool | Omit = omit, + key: str | Omit = omit, name_override: str | Omit = omit, overrides: Optional[action_add_params.Overrides] | Omit = omit, parent_flow_action_id: Optional[str] | Omit = omit, @@ -286,6 +320,7 @@ async def add( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionAddResponse: """ Append a single action to a flow at the given `position`, optionally nesting it @@ -293,7 +328,17 @@ async def add( `nameOverride`, param `overrides`, and `continueOnError`. Returns 404 if the flow does not exist. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: + key: Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -301,6 +346,8 @@ async def add( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -312,6 +359,7 @@ async def add( "position": position, "children": children, "continue_on_error": continue_on_error, + "key": key, "name_override": name_override, "overrides": overrides, "parent_flow_action_id": parent_flow_action_id, @@ -320,7 +368,11 @@ async def add( action_add_params.ActionAddParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionAddResponse, ) @@ -336,6 +388,7 @@ async def remove( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionRemoveResponse: """Remove a single action from a flow by its `flowActionId`. @@ -350,6 +403,8 @@ async def remove( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -358,7 +413,11 @@ async def remove( return await self._delete( path_template("/flows/{flow_id}/actions/{flow_action_id}", flow_id=flow_id, flow_action_id=flow_action_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionRemoveResponse, ) @@ -374,6 +433,7 @@ async def replace( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> ActionReplaceResponse: """ Replace a flow's entire action list with the supplied set (at least one @@ -389,6 +449,8 @@ async def replace( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -396,7 +458,11 @@ async def replace( path_template("/flows/{flow_id}/actions", flow_id=flow_id), body=await async_maybe_transform({"actions": actions}, action_replace_params.ActionReplaceParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=ActionReplaceResponse, ) diff --git a/src/mobilerun_sdk/resources/workflows/flows/flows.py b/src/mobilerun_sdk/resources/workflows/flows/flows.py index 1faf5913..5ac7830c 100644 --- a/src/mobilerun_sdk/resources/workflows/flows/flows.py +++ b/src/mobilerun_sdk/resources/workflows/flows/flows.py @@ -27,22 +27,33 @@ ) from ...._base_client import make_request_options from ....types.workflows import ( + flow_run_params, flow_list_params, flow_clone_params, flow_create_params, flow_update_params, + flow_verify_params, flow_dry_run_params, + flow_activate_params, + flow_validate_params, + flow_template_context_params, ) +from ....types.workflows.flow_run_response import FlowRunResponse from ....types.workflows.flow_list_response import FlowListResponse from ....types.workflows.flow_clone_response import FlowCloneResponse from ....types.workflows.flow_create_response import FlowCreateResponse from ....types.workflows.flow_delete_response import FlowDeleteResponse from ....types.workflows.flow_update_response import FlowUpdateResponse +from ....types.workflows.flow_verify_response import FlowVerifyResponse from ....types.workflows.flow_dry_run_response import FlowDryRunResponse from ....types.workflows.flow_unblock_response import FlowUnblockResponse +from ....types.workflows.flow_activate_response import FlowActivateResponse from ....types.workflows.flow_capacity_response import FlowCapacityResponse from ....types.workflows.flow_retrieve_response import FlowRetrieveResponse +from ....types.workflows.flow_validate_response import FlowValidateResponse from ....types.workflows.flow_list_repairs_response import FlowListRepairsResponse +from ....types.workflows.flow_delivery_options_response import FlowDeliveryOptionsResponse +from ....types.workflows.flow_template_context_response import FlowTemplateContextResponse __all__ = ["FlowsResource", "AsyncFlowsResource"] @@ -79,6 +90,7 @@ def create( trigger_id: str, cooldown_scope: Literal["flow", "device"] | Omit = omit, cooldown_seconds: Optional[int] | Omit = omit, + delivery: flow_create_params.Delivery | Omit = omit, description: str | Omit = omit, device_ids: SequenceNotStr[str] | Omit = omit, enabled: bool | Omit = omit, @@ -96,6 +108,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowCreateResponse: """ Create a flow that binds a trigger (`triggerId`) to an ordered list of actions, @@ -103,10 +116,11 @@ def create( a cooldown (`cooldownSeconds`/`cooldownScope`), and webhook notifications on success or failure. - Args: - recording_enabled: Deprecated compatibility field. true maps to recordingPolicy.mode="flow"; false - maps to "off". + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -114,6 +128,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/flows", @@ -124,6 +140,7 @@ def create( "trigger_id": trigger_id, "cooldown_scope": cooldown_scope, "cooldown_seconds": cooldown_seconds, + "delivery": delivery, "description": description, "device_ids": device_ids, "enabled": enabled, @@ -139,7 +156,11 @@ def create( flow_create_params.FlowCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowCreateResponse, ) @@ -184,6 +205,7 @@ def update( *, cooldown_scope: Literal["flow", "device"] | Omit = omit, cooldown_seconds: Optional[int] | Omit = omit, + delivery: Optional[flow_update_params.Delivery] | Omit = omit, description: str | Omit = omit, device_ids: SequenceNotStr[str] | Omit = omit, enabled: bool | Omit = omit, @@ -204,6 +226,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowUpdateResponse: """ Partially update a flow's settings — name, trigger binding, enabled state, @@ -224,6 +247,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -233,6 +258,7 @@ def update( { "cooldown_scope": cooldown_scope, "cooldown_seconds": cooldown_seconds, + "delivery": delivery, "description": description, "device_ids": device_ids, "enabled": enabled, @@ -251,7 +277,11 @@ def update( flow_update_params.FlowUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowUpdateResponse, ) @@ -332,6 +362,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowDeleteResponse: """Terminally archive a flow by its ID. @@ -346,17 +377,69 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") return self._delete( path_template("/flows/{flow_id}", flow_id=flow_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowDeleteResponse, ) + def activate( + self, + flow_id: str, + *, + verification_execution_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowActivateResponse: + """ + Activate a disabled flow only when the supplied verification execution succeeded + and the flow graph has not changed since verification. Repeating activation is + idempotent for an already-enabled flow. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return self._post( + path_template("/flows/{flow_id}/activate", flow_id=flow_id), + body=maybe_transform( + {"verification_execution_id": verification_execution_id}, flow_activate_params.FlowActivateParams + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowActivateResponse, + ) + def capacity( self, *, @@ -393,6 +476,7 @@ def clone( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowCloneResponse: """Create a copy of an existing flow, including its actions and settings. @@ -400,6 +484,10 @@ def clone( optional body can override the new flow's `name` and target `deviceIds`. Returns 404 if the source flow does not exist. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: extra_headers: Send extra headers @@ -408,6 +496,8 @@ def clone( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -421,11 +511,52 @@ def clone( flow_clone_params.FlowCloneParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowCloneResponse, ) + def delivery_options( + self, + flow_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FlowDeliveryOptionsResponse: + """ + Return the recording/delivery readiness for a flow (recording mode, whether + delivery can include the recording, whether a files.upload action exists, the + first direct OneDrive/Google Drive upload step if any) plus, per destination, + whether the flow owner has an active integrations-api connection. Returns 404 if + the flow does not exist. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return self._get( + path_template("/flows/{flow_id}/delivery-options", flow_id=flow_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FlowDeliveryOptionsResponse, + ) + def dry_run( self, flow_id: str, @@ -437,6 +568,7 @@ def dry_run( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowDryRunResponse: """ Simulate this flow firing without storing events, enqueuing jobs, or consuming @@ -462,6 +594,8 @@ def dry_run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -469,7 +603,11 @@ def dry_run( path_template("/flows/{flow_id}/dry-run", flow_id=flow_id), body=maybe_transform({"payload": payload}, flow_dry_run_params.FlowDryRunParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowDryRunResponse, ) @@ -507,21 +645,28 @@ def list_repairs( cast_to=FlowListRepairsResponse, ) - def unblock( + def run( self, flow_id: str, *, + payload: Dict[str, object] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> FlowUnblockResponse: - """Clear a flow's blocked status after fixing the underlying issue. + idempotency_key: str | None = None, + ) -> FlowRunResponse: + """ + Immediately enqueue one live execution on every device bound to the flow, + regardless of whether its trigger is event-based, custom, or scheduled. - Idempotent — - safe to call on already-healthy flows. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). The run identity is derived from the key, so a retry never starts a + second run. Replays with the same key and an identical body return the original + status and the original `executionIds`; a changed body under the same key + returns 422 `idempotency_key_reused`. Args: extra_headers: Send extra headers @@ -531,78 +676,39 @@ def unblock( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") return self._post( - path_template("/flows/{flow_id}/unblock", flow_id=flow_id), + path_template("/flows/{flow_id}/run", flow_id=flow_id), + body=maybe_transform({"payload": payload}, flow_run_params.FlowRunParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), - cast_to=FlowUnblockResponse, + cast_to=FlowRunResponse, ) - -class AsyncFlowsResource(AsyncAPIResource): - @cached_property - def actions(self) -> AsyncActionsResource: - return AsyncActionsResource(self._client) - - @cached_property - def with_raw_response(self) -> AsyncFlowsResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers - """ - return AsyncFlowsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncFlowsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response - """ - return AsyncFlowsResourceWithStreamingResponse(self) - - async def create( + def template_context( self, *, - actions: Iterable[flow_create_params.Action], - name: str, - trigger_id: str, - cooldown_scope: Literal["flow", "device"] | Omit = omit, - cooldown_seconds: Optional[int] | Omit = omit, - description: str | Omit = omit, - device_ids: SequenceNotStr[str] | Omit = omit, - enabled: bool | Omit = omit, - health_monitoring_enabled: bool | Omit = omit, - notify_on_failure: bool | Omit = omit, - notify_on_success: bool | Omit = omit, - notify_webhook_id: Optional[str] | Omit = omit, - recording_enabled: bool | Omit = omit, - recording_policy: flow_create_params.RecordingPolicy | Omit = omit, - self_healing_enabled: bool | Omit = omit, - self_healing_max_attempts: int | Omit = omit, + template_resolution_version: int | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> FlowCreateResponse: + ) -> FlowTemplateContextResponse: """ - Create a flow that binds a trigger (`triggerId`) to an ordered list of actions, - with at least one action required. Optional settings include target `deviceIds`, - a cooldown (`cooldownSeconds`/`cooldownScope`), and webhook notifications on - success or failure. + Get flow template context Args: - recording_enabled: Deprecated compatibility field. true maps to recordingPolicy.mode="flow"; false - maps to "off". - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -611,36 +717,22 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/flows", - body=await async_maybe_transform( - { - "actions": actions, - "name": name, - "trigger_id": trigger_id, - "cooldown_scope": cooldown_scope, - "cooldown_seconds": cooldown_seconds, - "description": description, - "device_ids": device_ids, - "enabled": enabled, - "health_monitoring_enabled": health_monitoring_enabled, - "notify_on_failure": notify_on_failure, - "notify_on_success": notify_on_success, - "notify_webhook_id": notify_webhook_id, - "recording_enabled": recording_enabled, - "recording_policy": recording_policy, - "self_healing_enabled": self_healing_enabled, - "self_healing_max_attempts": self_healing_max_attempts, - }, - flow_create_params.FlowCreateParams, - ), + return self._get( + "/flows/template-context", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + {"template_resolution_version": template_resolution_version}, + flow_template_context_params.FlowTemplateContextParams, + ), ), - cast_to=FlowCreateResponse, + cast_to=FlowTemplateContextResponse, ) - async def retrieve( + def unblock( self, flow_id: str, *, @@ -650,10 +742,12 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> FlowRetrieveResponse: - """ - Fetch a single flow by its ID, including its trigger binding, configuration, and - current status. Returns 404 if no flow matches. + idempotency_key: str | None = None, + ) -> FlowUnblockResponse: + """Clear a flow's blocked status after fixing the underlying issue. + + Idempotent — + safe to call on already-healthy flows. Args: extra_headers: Send extra headers @@ -663,34 +757,40 @@ async def retrieve( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") - return await self._get( - path_template("/flows/{flow_id}", flow_id=flow_id), + return self._post( + path_template("/flows/{flow_id}/unblock", flow_id=flow_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), - cast_to=FlowRetrieveResponse, + cast_to=FlowUnblockResponse, ) - async def update( + def validate( self, - flow_id: str, *, + actions: Iterable[flow_validate_params.Action], + name: str, cooldown_scope: Literal["flow", "device"] | Omit = omit, cooldown_seconds: Optional[int] | Omit = omit, + delivery: flow_validate_params.Delivery | Omit = omit, description: str | Omit = omit, device_ids: SequenceNotStr[str] | Omit = omit, enabled: bool | Omit = omit, health_monitoring_enabled: bool | Omit = omit, - lifecycle_status: Literal["enabled", "disabled"] | Omit = omit, - name: str | Omit = omit, notify_on_failure: bool | Omit = omit, notify_on_success: bool | Omit = omit, notify_webhook_id: Optional[str] | Omit = omit, recording_enabled: bool | Omit = omit, - recording_policy: flow_update_params.RecordingPolicy | Omit = omit, + recording_policy: flow_validate_params.RecordingPolicy | Omit = omit, self_healing_enabled: bool | Omit = omit, self_healing_max_attempts: int | Omit = omit, trigger_id: str | Omit = omit, @@ -700,19 +800,12 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> FlowUpdateResponse: + idempotency_key: str | None = None, + ) -> FlowValidateResponse: """ - Partially update a flow's settings — name, trigger binding, enabled state, - target devices, cooldown, or notifications; all fields are optional. Actions are - managed through the flow-actions endpoints, not here. Returns 404 if the flow - does not exist. + Validate a flow Args: - lifecycle_status: Set the visible agent lifecycle. Archive remains available only through DELETE. - - recording_enabled: Deprecated compatibility field. true maps to recordingPolicy.mode="flow"; false - maps to "off". - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -720,21 +813,22 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ - if not flow_id: - raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") - return await self._patch( - path_template("/flows/{flow_id}", flow_id=flow_id), - body=await async_maybe_transform( + return self._post( + "/flows/validate", + body=maybe_transform( { + "actions": actions, + "name": name, "cooldown_scope": cooldown_scope, "cooldown_seconds": cooldown_seconds, + "delivery": delivery, "description": description, "device_ids": device_ids, "enabled": enabled, "health_monitoring_enabled": health_monitoring_enabled, - "lifecycle_status": lifecycle_status, - "name": name, "notify_on_failure": notify_on_failure, "notify_on_success": notify_on_success, "notify_webhook_id": notify_webhook_id, @@ -744,19 +838,329 @@ async def update( "self_healing_max_attempts": self_healing_max_attempts, "trigger_id": trigger_id, }, - flow_update_params.FlowUpdateParams, + flow_validate_params.FlowValidateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), - cast_to=FlowUpdateResponse, + cast_to=FlowValidateResponse, ) - async def list( + def verify( self, + flow_id: str, *, - created_by: str | Omit = omit, - enabled: Literal["true", "false"] | Omit = omit, + invocation_id: str, + device_id: str | Omit = omit, + payload: Dict[str, object] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowVerifyResponse: + """ + Run a bound, deduplicated **verification** of this flow on exactly one device + through the real worker. + + Unlike a normal trigger firing, verification: + + - runs a flow that is `enabled` OR `disabled` (an archived flow returns 404); + - targets one device (`deviceId`, or the single bound device); + - is idempotent per `invocationId`: a repeat returns the existing run + (`deduplicated: true`, HTTP 200) instead of enqueuing another (HTTP 202); + - never mutates flow health, `lastTriggeredAt`, or the repair loop. + + Per-activation payload gate: + + - `custom`: validates the payload against the custom payload schema (422 on + failure). + - `event`: validates the payload against the event catalog schema and evaluates + the trigger conditions; a failing gate returns 409 `conditions_not_met` with + the dry-run report. + - `schedule`: the payload is ignored. + + Args: + invocation_id: Client-supplied idempotency key. A repeat request for the same (flow, + invocationId) returns the existing verification run (`deduplicated: true`, + HTTP 200) instead of enqueuing another. + + device_id: Device to run the verification on. Must be one the flow is bound to. Optional + only when the flow is bound to exactly one device (that device is used); + otherwise required. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return self._post( + path_template("/flows/{flow_id}/verify", flow_id=flow_id), + body=maybe_transform( + { + "invocation_id": invocation_id, + "device_id": device_id, + "payload": payload, + }, + flow_verify_params.FlowVerifyParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowVerifyResponse, + ) + + +class AsyncFlowsResource(AsyncAPIResource): + @cached_property + def actions(self) -> AsyncActionsResource: + return AsyncActionsResource(self._client) + + @cached_property + def with_raw_response(self) -> AsyncFlowsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncFlowsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncFlowsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/droidrun/mobilerun-sdk-python#with_streaming_response + """ + return AsyncFlowsResourceWithStreamingResponse(self) + + async def create( + self, + *, + actions: Iterable[flow_create_params.Action], + name: str, + trigger_id: str, + cooldown_scope: Literal["flow", "device"] | Omit = omit, + cooldown_seconds: Optional[int] | Omit = omit, + delivery: flow_create_params.Delivery | Omit = omit, + description: str | Omit = omit, + device_ids: SequenceNotStr[str] | Omit = omit, + enabled: bool | Omit = omit, + health_monitoring_enabled: bool | Omit = omit, + notify_on_failure: bool | Omit = omit, + notify_on_success: bool | Omit = omit, + notify_webhook_id: Optional[str] | Omit = omit, + recording_enabled: bool | Omit = omit, + recording_policy: flow_create_params.RecordingPolicy | Omit = omit, + self_healing_enabled: bool | Omit = omit, + self_healing_max_attempts: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowCreateResponse: + """ + Create a flow that binds a trigger (`triggerId`) to an ordered list of actions, + with at least one action required. Optional settings include target `deviceIds`, + a cooldown (`cooldownSeconds`/`cooldownScope`), and webhook notifications on + success or failure. + + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/flows", + body=await async_maybe_transform( + { + "actions": actions, + "name": name, + "trigger_id": trigger_id, + "cooldown_scope": cooldown_scope, + "cooldown_seconds": cooldown_seconds, + "delivery": delivery, + "description": description, + "device_ids": device_ids, + "enabled": enabled, + "health_monitoring_enabled": health_monitoring_enabled, + "notify_on_failure": notify_on_failure, + "notify_on_success": notify_on_success, + "notify_webhook_id": notify_webhook_id, + "recording_enabled": recording_enabled, + "recording_policy": recording_policy, + "self_healing_enabled": self_healing_enabled, + "self_healing_max_attempts": self_healing_max_attempts, + }, + flow_create_params.FlowCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowCreateResponse, + ) + + async def retrieve( + self, + flow_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FlowRetrieveResponse: + """ + Fetch a single flow by its ID, including its trigger binding, configuration, and + current status. Returns 404 if no flow matches. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._get( + path_template("/flows/{flow_id}", flow_id=flow_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FlowRetrieveResponse, + ) + + async def update( + self, + flow_id: str, + *, + cooldown_scope: Literal["flow", "device"] | Omit = omit, + cooldown_seconds: Optional[int] | Omit = omit, + delivery: Optional[flow_update_params.Delivery] | Omit = omit, + description: str | Omit = omit, + device_ids: SequenceNotStr[str] | Omit = omit, + enabled: bool | Omit = omit, + health_monitoring_enabled: bool | Omit = omit, + lifecycle_status: Literal["enabled", "disabled"] | Omit = omit, + name: str | Omit = omit, + notify_on_failure: bool | Omit = omit, + notify_on_success: bool | Omit = omit, + notify_webhook_id: Optional[str] | Omit = omit, + recording_enabled: bool | Omit = omit, + recording_policy: flow_update_params.RecordingPolicy | Omit = omit, + self_healing_enabled: bool | Omit = omit, + self_healing_max_attempts: int | Omit = omit, + trigger_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowUpdateResponse: + """ + Partially update a flow's settings — name, trigger binding, enabled state, + target devices, cooldown, or notifications; all fields are optional. Actions are + managed through the flow-actions endpoints, not here. Returns 404 if the flow + does not exist. + + Args: + lifecycle_status: Set the visible agent lifecycle. Archive remains available only through DELETE. + + recording_enabled: Deprecated compatibility field. true maps to recordingPolicy.mode="flow"; false + maps to "off". + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._patch( + path_template("/flows/{flow_id}", flow_id=flow_id), + body=await async_maybe_transform( + { + "cooldown_scope": cooldown_scope, + "cooldown_seconds": cooldown_seconds, + "delivery": delivery, + "description": description, + "device_ids": device_ids, + "enabled": enabled, + "health_monitoring_enabled": health_monitoring_enabled, + "lifecycle_status": lifecycle_status, + "name": name, + "notify_on_failure": notify_on_failure, + "notify_on_success": notify_on_success, + "notify_webhook_id": notify_webhook_id, + "recording_enabled": recording_enabled, + "recording_policy": recording_policy, + "self_healing_enabled": self_healing_enabled, + "self_healing_max_attempts": self_healing_max_attempts, + "trigger_id": trigger_id, + }, + flow_update_params.FlowUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowUpdateResponse, + ) + + async def list( + self, + *, + created_by: str | Omit = omit, + enabled: Literal["true", "false"] | Omit = omit, mine: Literal["true", "false"] | Omit = omit, order_by: Literal["name", "createdAt", "updatedAt"] | Omit = omit, order_by_direction: Literal["asc", "desc"] | Omit = omit, @@ -828,6 +1232,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowDeleteResponse: """Terminally archive a flow by its ID. @@ -842,15 +1247,67 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._delete( + path_template("/flows/{flow_id}", flow_id=flow_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowDeleteResponse, + ) + + async def activate( + self, + flow_id: str, + *, + verification_execution_id: str, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowActivateResponse: + """ + Activate a disabled flow only when the supplied verification execution succeeded + and the flow graph has not changed since verification. Repeating activation is + idempotent for an already-enabled flow. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") - return await self._delete( - path_template("/flows/{flow_id}", flow_id=flow_id), + return await self._post( + path_template("/flows/{flow_id}/activate", flow_id=flow_id), + body=await async_maybe_transform( + {"verification_execution_id": verification_execution_id}, flow_activate_params.FlowActivateParams + ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), - cast_to=FlowDeleteResponse, + cast_to=FlowActivateResponse, ) async def capacity( @@ -889,6 +1346,7 @@ async def clone( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowCloneResponse: """Create a copy of an existing flow, including its actions and settings. @@ -896,6 +1354,10 @@ async def clone( optional body can override the new flow's `name` and target `deviceIds`. Returns 404 if the source flow does not exist. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: extra_headers: Send extra headers @@ -904,6 +1366,8 @@ async def clone( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -917,11 +1381,52 @@ async def clone( flow_clone_params.FlowCloneParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowCloneResponse, ) + async def delivery_options( + self, + flow_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FlowDeliveryOptionsResponse: + """ + Return the recording/delivery readiness for a flow (recording mode, whether + delivery can include the recording, whether a files.upload action exists, the + first direct OneDrive/Google Drive upload step if any) plus, per destination, + whether the flow owner has an active integrations-api connection. Returns 404 if + the flow does not exist. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._get( + path_template("/flows/{flow_id}/delivery-options", flow_id=flow_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=FlowDeliveryOptionsResponse, + ) + async def dry_run( self, flow_id: str, @@ -933,6 +1438,7 @@ async def dry_run( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowDryRunResponse: """ Simulate this flow firing without storing events, enqueuing jobs, or consuming @@ -958,6 +1464,8 @@ async def dry_run( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") @@ -965,7 +1473,11 @@ async def dry_run( path_template("/flows/{flow_id}/dry-run", flow_id=flow_id), body=await async_maybe_transform({"payload": payload}, flow_dry_run_params.FlowDryRunParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowDryRunResponse, ) @@ -1003,6 +1515,93 @@ async def list_repairs( cast_to=FlowListRepairsResponse, ) + async def run( + self, + flow_id: str, + *, + payload: Dict[str, object] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowRunResponse: + """ + Immediately enqueue one live execution on every device bound to the flow, + regardless of whether its trigger is event-based, custom, or scheduled. + + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). The run identity is derived from the key, so a retry never starts a + second run. Replays with the same key and an identical body return the original + status and the original `executionIds`; a changed body under the same key + returns 422 `idempotency_key_reused`. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._post( + path_template("/flows/{flow_id}/run", flow_id=flow_id), + body=await async_maybe_transform({"payload": payload}, flow_run_params.FlowRunParams), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowRunResponse, + ) + + async def template_context( + self, + *, + template_resolution_version: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> FlowTemplateContextResponse: + """ + Get flow template context + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._get( + "/flows/template-context", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"template_resolution_version": template_resolution_version}, + flow_template_context_params.FlowTemplateContextParams, + ), + ), + cast_to=FlowTemplateContextResponse, + ) + async def unblock( self, flow_id: str, @@ -1013,6 +1612,7 @@ async def unblock( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> FlowUnblockResponse: """Clear a flow's blocked status after fixing the underlying issue. @@ -1027,17 +1627,176 @@ async def unblock( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not flow_id: raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") return await self._post( path_template("/flows/{flow_id}/unblock", flow_id=flow_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=FlowUnblockResponse, ) + async def validate( + self, + *, + actions: Iterable[flow_validate_params.Action], + name: str, + cooldown_scope: Literal["flow", "device"] | Omit = omit, + cooldown_seconds: Optional[int] | Omit = omit, + delivery: flow_validate_params.Delivery | Omit = omit, + description: str | Omit = omit, + device_ids: SequenceNotStr[str] | Omit = omit, + enabled: bool | Omit = omit, + health_monitoring_enabled: bool | Omit = omit, + notify_on_failure: bool | Omit = omit, + notify_on_success: bool | Omit = omit, + notify_webhook_id: Optional[str] | Omit = omit, + recording_enabled: bool | Omit = omit, + recording_policy: flow_validate_params.RecordingPolicy | Omit = omit, + self_healing_enabled: bool | Omit = omit, + self_healing_max_attempts: int | Omit = omit, + trigger_id: str | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowValidateResponse: + """ + Validate a flow + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + return await self._post( + "/flows/validate", + body=await async_maybe_transform( + { + "actions": actions, + "name": name, + "cooldown_scope": cooldown_scope, + "cooldown_seconds": cooldown_seconds, + "delivery": delivery, + "description": description, + "device_ids": device_ids, + "enabled": enabled, + "health_monitoring_enabled": health_monitoring_enabled, + "notify_on_failure": notify_on_failure, + "notify_on_success": notify_on_success, + "notify_webhook_id": notify_webhook_id, + "recording_enabled": recording_enabled, + "recording_policy": recording_policy, + "self_healing_enabled": self_healing_enabled, + "self_healing_max_attempts": self_healing_max_attempts, + "trigger_id": trigger_id, + }, + flow_validate_params.FlowValidateParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowValidateResponse, + ) + + async def verify( + self, + flow_id: str, + *, + invocation_id: str, + device_id: str | Omit = omit, + payload: Dict[str, object] | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, + ) -> FlowVerifyResponse: + """ + Run a bound, deduplicated **verification** of this flow on exactly one device + through the real worker. + + Unlike a normal trigger firing, verification: + + - runs a flow that is `enabled` OR `disabled` (an archived flow returns 404); + - targets one device (`deviceId`, or the single bound device); + - is idempotent per `invocationId`: a repeat returns the existing run + (`deduplicated: true`, HTTP 200) instead of enqueuing another (HTTP 202); + - never mutates flow health, `lastTriggeredAt`, or the repair loop. + + Per-activation payload gate: + + - `custom`: validates the payload against the custom payload schema (422 on + failure). + - `event`: validates the payload against the event catalog schema and evaluates + the trigger conditions; a failing gate returns 409 `conditions_not_met` with + the dry-run report. + - `schedule`: the payload is ignored. + + Args: + invocation_id: Client-supplied idempotency key. A repeat request for the same (flow, + invocationId) returns the existing verification run (`deduplicated: true`, + HTTP 200) instead of enqueuing another. + + device_id: Device to run the verification on. Must be one the flow is bound to. Optional + only when the flow is bound to exactly one device (that device is used); + otherwise required. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request + """ + if not flow_id: + raise ValueError(f"Expected a non-empty value for `flow_id` but received {flow_id!r}") + return await self._post( + path_template("/flows/{flow_id}/verify", flow_id=flow_id), + body=await async_maybe_transform( + { + "invocation_id": invocation_id, + "device_id": device_id, + "payload": payload, + }, + flow_verify_params.FlowVerifyParams, + ), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, + ), + cast_to=FlowVerifyResponse, + ) + class FlowsResourceWithRawResponse: def __init__(self, flows: FlowsResource) -> None: @@ -1058,21 +1817,39 @@ def __init__(self, flows: FlowsResource) -> None: self.delete = to_raw_response_wrapper( flows.delete, ) + self.activate = to_raw_response_wrapper( + flows.activate, + ) self.capacity = to_raw_response_wrapper( flows.capacity, ) self.clone = to_raw_response_wrapper( flows.clone, ) + self.delivery_options = to_raw_response_wrapper( + flows.delivery_options, + ) self.dry_run = to_raw_response_wrapper( flows.dry_run, ) self.list_repairs = to_raw_response_wrapper( flows.list_repairs, ) + self.run = to_raw_response_wrapper( + flows.run, + ) + self.template_context = to_raw_response_wrapper( + flows.template_context, + ) self.unblock = to_raw_response_wrapper( flows.unblock, ) + self.validate = to_raw_response_wrapper( + flows.validate, + ) + self.verify = to_raw_response_wrapper( + flows.verify, + ) @cached_property def actions(self) -> ActionsResourceWithRawResponse: @@ -1098,21 +1875,39 @@ def __init__(self, flows: AsyncFlowsResource) -> None: self.delete = async_to_raw_response_wrapper( flows.delete, ) + self.activate = async_to_raw_response_wrapper( + flows.activate, + ) self.capacity = async_to_raw_response_wrapper( flows.capacity, ) self.clone = async_to_raw_response_wrapper( flows.clone, ) + self.delivery_options = async_to_raw_response_wrapper( + flows.delivery_options, + ) self.dry_run = async_to_raw_response_wrapper( flows.dry_run, ) self.list_repairs = async_to_raw_response_wrapper( flows.list_repairs, ) + self.run = async_to_raw_response_wrapper( + flows.run, + ) + self.template_context = async_to_raw_response_wrapper( + flows.template_context, + ) self.unblock = async_to_raw_response_wrapper( flows.unblock, ) + self.validate = async_to_raw_response_wrapper( + flows.validate, + ) + self.verify = async_to_raw_response_wrapper( + flows.verify, + ) @cached_property def actions(self) -> AsyncActionsResourceWithRawResponse: @@ -1138,21 +1933,39 @@ def __init__(self, flows: FlowsResource) -> None: self.delete = to_streamed_response_wrapper( flows.delete, ) + self.activate = to_streamed_response_wrapper( + flows.activate, + ) self.capacity = to_streamed_response_wrapper( flows.capacity, ) self.clone = to_streamed_response_wrapper( flows.clone, ) + self.delivery_options = to_streamed_response_wrapper( + flows.delivery_options, + ) self.dry_run = to_streamed_response_wrapper( flows.dry_run, ) self.list_repairs = to_streamed_response_wrapper( flows.list_repairs, ) + self.run = to_streamed_response_wrapper( + flows.run, + ) + self.template_context = to_streamed_response_wrapper( + flows.template_context, + ) self.unblock = to_streamed_response_wrapper( flows.unblock, ) + self.validate = to_streamed_response_wrapper( + flows.validate, + ) + self.verify = to_streamed_response_wrapper( + flows.verify, + ) @cached_property def actions(self) -> ActionsResourceWithStreamingResponse: @@ -1178,21 +1991,39 @@ def __init__(self, flows: AsyncFlowsResource) -> None: self.delete = async_to_streamed_response_wrapper( flows.delete, ) + self.activate = async_to_streamed_response_wrapper( + flows.activate, + ) self.capacity = async_to_streamed_response_wrapper( flows.capacity, ) self.clone = async_to_streamed_response_wrapper( flows.clone, ) + self.delivery_options = async_to_streamed_response_wrapper( + flows.delivery_options, + ) self.dry_run = async_to_streamed_response_wrapper( flows.dry_run, ) self.list_repairs = async_to_streamed_response_wrapper( flows.list_repairs, ) + self.run = async_to_streamed_response_wrapper( + flows.run, + ) + self.template_context = async_to_streamed_response_wrapper( + flows.template_context, + ) self.unblock = async_to_streamed_response_wrapper( flows.unblock, ) + self.validate = async_to_streamed_response_wrapper( + flows.validate, + ) + self.verify = async_to_streamed_response_wrapper( + flows.verify, + ) @cached_property def actions(self) -> AsyncActionsResourceWithStreamingResponse: diff --git a/src/mobilerun_sdk/resources/workflows/triggers.py b/src/mobilerun_sdk/resources/workflows/triggers.py index 752cd1dd..8de43931 100644 --- a/src/mobilerun_sdk/resources/workflows/triggers.py +++ b/src/mobilerun_sdk/resources/workflows/triggers.py @@ -66,6 +66,7 @@ def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerCreateResponse: """ Create a trigger with an activation type of `event`, `schedule`, or `custom`. @@ -73,6 +74,10 @@ def create( for events, `scheduleRule` and `timezone` for schedules, `customPayloadSchema` for custom triggers); mismatched fields are rejected. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: custom_payload_schema: Optional JSON Schema for validating payloads sent to this custom trigger @@ -83,6 +88,8 @@ def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return self._post( "/triggers", @@ -100,7 +107,11 @@ def create( trigger_create_params.TriggerCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerCreateResponse, ) @@ -157,6 +168,7 @@ def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerUpdateResponse: """Partially update a trigger; all fields are optional. @@ -174,6 +186,8 @@ def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") @@ -193,7 +207,11 @@ def update( trigger_update_params.TriggerUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerUpdateResponse, ) @@ -262,6 +280,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerDeleteResponse: """Delete a trigger by its ID. @@ -275,13 +294,19 @@ def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") return self._delete( path_template("/triggers/{trigger_id}", trigger_id=trigger_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerDeleteResponse, ) @@ -291,13 +316,13 @@ def fire( trigger_id: str, *, payload: Dict[str, object], - invocation_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerFireResponse: """ Invoke a custom trigger directly with an arbitrary JSON payload. @@ -318,15 +343,21 @@ def fire( Only triggers with `activation = "custom"` can be fired through this endpoint; event and schedule triggers return 409. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters), which maps to the derived `invocationId` used for fan-out + deduplication (the deprecated `invocationId` body field wins when both are + supplied, and a mismatch between them is logged). The payload is bound to the + key on the first accepted fire, before fan-out. A repeat with the same key and + an identical payload returns 202 with `Idempotent-Replayed: true` + (`deduplicated: true` when flows were skipped; `enqueuedCount` counts only + executions enqueued by that call); a changed payload under the same key returns + 422 `idempotency_key_reused`. + Args: payload: Arbitrary JSON object forwarded to every flow attached to this trigger. Validated against the trigger's customPayloadSchema when one is configured; otherwise only "must be a JSON object" is enforced. - invocation_id: Optional client-supplied idempotency key. When provided, a flow that already has - an execution for this (flow, invocationId) is skipped and `deduplicated` is - true. When omitted a fresh server-side id is generated (no dedup). - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -334,20 +365,20 @@ def fire( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") return self._post( path_template("/triggers/{trigger_id}/fire", trigger_id=trigger_id), - body=maybe_transform( - { - "payload": payload, - "invocation_id": invocation_id, - }, - trigger_fire_params.TriggerFireParams, - ), + body=maybe_transform({"payload": payload}, trigger_fire_params.TriggerFireParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerFireResponse, ) @@ -390,6 +421,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerCreateResponse: """ Create a trigger with an activation type of `event`, `schedule`, or `custom`. @@ -397,6 +429,10 @@ async def create( for events, `scheduleRule` and `timezone` for schedules, `customPayloadSchema` for custom triggers); mismatched fields are rejected. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters). Replays with the same key and an identical body return the original + 201; a changed body under the same key returns 422 `idempotency_key_reused`. + Args: custom_payload_schema: Optional JSON Schema for validating payloads sent to this custom trigger @@ -407,6 +443,8 @@ async def create( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ return await self._post( "/triggers", @@ -424,7 +462,11 @@ async def create( trigger_create_params.TriggerCreateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerCreateResponse, ) @@ -481,6 +523,7 @@ async def update( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerUpdateResponse: """Partially update a trigger; all fields are optional. @@ -498,6 +541,8 @@ async def update( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") @@ -517,7 +562,11 @@ async def update( trigger_update_params.TriggerUpdateParams, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerUpdateResponse, ) @@ -586,6 +635,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerDeleteResponse: """Delete a trigger by its ID. @@ -599,13 +649,19 @@ async def delete( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") return await self._delete( path_template("/triggers/{trigger_id}", trigger_id=trigger_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerDeleteResponse, ) @@ -615,13 +671,13 @@ async def fire( trigger_id: str, *, payload: Dict[str, object], - invocation_id: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, + idempotency_key: str | None = None, ) -> TriggerFireResponse: """ Invoke a custom trigger directly with an arbitrary JSON payload. @@ -642,15 +698,21 @@ async def fire( Only triggers with `activation = "custom"` can be fired through this endpoint; event and schedule triggers return 409. + Supports an optional `Idempotency-Key` header (1-255 printable ASCII + characters), which maps to the derived `invocationId` used for fan-out + deduplication (the deprecated `invocationId` body field wins when both are + supplied, and a mismatch between them is logged). The payload is bound to the + key on the first accepted fire, before fan-out. A repeat with the same key and + an identical payload returns 202 with `Idempotent-Replayed: true` + (`deduplicated: true` when flows were skipped; `enqueuedCount` counts only + executions enqueued by that call); a changed payload under the same key returns + 422 `idempotency_key_reused`. + Args: payload: Arbitrary JSON object forwarded to every flow attached to this trigger. Validated against the trigger's customPayloadSchema when one is configured; otherwise only "must be a JSON object" is enforced. - invocation_id: Optional client-supplied idempotency key. When provided, a flow that already has - an execution for this (flow, invocationId) is skipped and `deduplicated` is - true. When omitted a fresh server-side id is generated (no dedup). - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -658,20 +720,20 @@ async def fire( extra_body: Add additional JSON properties to the request timeout: Override the client-level default timeout for this request, in seconds + + idempotency_key: Specify a custom idempotency key for this request """ if not trigger_id: raise ValueError(f"Expected a non-empty value for `trigger_id` but received {trigger_id!r}") return await self._post( path_template("/triggers/{trigger_id}/fire", trigger_id=trigger_id), - body=await async_maybe_transform( - { - "payload": payload, - "invocation_id": invocation_id, - }, - trigger_fire_params.TriggerFireParams, - ), + body=await async_maybe_transform({"payload": payload}, trigger_fire_params.TriggerFireParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + idempotency_key=idempotency_key, ), cast_to=TriggerFireResponse, ) diff --git a/src/mobilerun_sdk/resources/workflows/workflows.py b/src/mobilerun_sdk/resources/workflows/workflows.py index 000d4bf7..f85c7513 100644 --- a/src/mobilerun_sdk/resources/workflows/workflows.py +++ b/src/mobilerun_sdk/resources/workflows/workflows.py @@ -27,14 +27,6 @@ TimezonesResourceWithStreamingResponse, AsyncTimezonesResourceWithStreamingResponse, ) -from .executions import ( - ExecutionsResource, - AsyncExecutionsResource, - ExecutionsResourceWithRawResponse, - AsyncExecutionsResourceWithRawResponse, - ExecutionsResourceWithStreamingResponse, - AsyncExecutionsResourceWithStreamingResponse, -) from ..._resource import SyncAPIResource, AsyncAPIResource from .flows.flows import ( FlowsResource, @@ -60,6 +52,14 @@ ActionsResourceWithStreamingResponse, AsyncActionsResourceWithStreamingResponse, ) +from .executions.executions import ( + ExecutionsResource, + AsyncExecutionsResource, + ExecutionsResourceWithRawResponse, + AsyncExecutionsResourceWithRawResponse, + ExecutionsResourceWithStreamingResponse, + AsyncExecutionsResourceWithStreamingResponse, +) __all__ = ["WorkflowsResource", "AsyncWorkflowsResource"] diff --git a/src/mobilerun_sdk/types/__init__.py b/src/mobilerun_sdk/types/__init__.py index 5bdbbffc..227578f6 100644 --- a/src/mobilerun_sdk/types/__init__.py +++ b/src/mobilerun_sdk/types/__init__.py @@ -55,6 +55,7 @@ from .carrier_lookup_params import CarrierLookupParams as CarrierLookupParams from .carrier_update_params import CarrierUpdateParams as CarrierUpdateParams from .device_count_response import DeviceCountResponse as DeviceCountResponse +from .device_summary_params import DeviceSummaryParams as DeviceSummaryParams from .file_confirm_response import FileConfirmResponse as FileConfirmResponse from .mailbox_create_params import MailboxCreateParams as MailboxCreateParams from .mailbox_list_response import MailboxListResponse as MailboxListResponse @@ -85,6 +86,7 @@ from .carrier_delete_response import CarrierDeleteResponse as CarrierDeleteResponse from .carrier_lookup_response import CarrierLookupResponse as CarrierLookupResponse from .carrier_update_response import CarrierUpdateResponse as CarrierUpdateResponse +from .device_summary_response import DeviceSummaryResponse as DeviceSummaryResponse from .device_terminate_params import DeviceTerminateParams as DeviceTerminateParams from .mailbox_create_response import MailboxCreateResponse as MailboxCreateResponse from .mailbox_delete_response import MailboxDeleteResponse as MailboxDeleteResponse @@ -111,6 +113,7 @@ from .mailbox_capacity_response import MailboxCapacityResponse as MailboxCapacityResponse from .mailbox_retrieve_response import MailboxRetrieveResponse as MailboxRetrieveResponse from .mailbox_uncancel_response import MailboxUncancelResponse as MailboxUncancelResponse +from .message_retrieve_response import MessageRetrieveResponse as MessageRetrieveResponse from .number_countries_response import NumberCountriesResponse as NumberCountriesResponse from .profile_retrieve_response import ProfileRetrieveResponse as ProfileRetrieveResponse from .store_categories_response import StoreCategoriesResponse as StoreCategoriesResponse @@ -122,6 +125,7 @@ from .app_confirm_upload_response import AppConfirmUploadResponse as AppConfirmUploadResponse from .app_event_retrieve_response import AppEventRetrieveResponse as AppEventRetrieveResponse from .device_fingerprint_response import DeviceFingerprintResponse as DeviceFingerprintResponse +from .mailbox_disconnect_response import MailboxDisconnectResponse as MailboxDisconnectResponse from .file_cancel_pending_response import FileCancelPendingResponse as FileCancelPendingResponse from .task_get_trajectory_response import TaskGetTrajectoryResponse as TaskGetTrajectoryResponse from .webhook_event_types_response import WebhookEventTypesResponse as WebhookEventTypesResponse @@ -142,6 +146,12 @@ from .notification_update_preferences_response import ( NotificationUpdatePreferencesResponse as NotificationUpdatePreferencesResponse, ) +from .notification_apply_preferences_preset_params import ( + NotificationApplyPreferencesPresetParams as NotificationApplyPreferencesPresetParams, +) +from .notification_apply_preferences_preset_response import ( + NotificationApplyPreferencesPresetResponse as NotificationApplyPreferencesPresetResponse, +) # Rebuild cyclical models only after all modules are imported. # This ensures that, when building the deferred (due to cyclical references) model schema, diff --git a/src/mobilerun_sdk/types/app_event_list_params.py b/src/mobilerun_sdk/types/app_event_list_params.py index 4bae2116..9647cadb 100644 --- a/src/mobilerun_sdk/types/app_event_list_params.py +++ b/src/mobilerun_sdk/types/app_event_list_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Union +from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo @@ -15,7 +16,7 @@ class AppEventListParams(TypedDict, total=False): event_type: Annotated[str, PropertyInfo(alias="eventType")] - from_: Annotated[Optional[str], PropertyInfo(alias="from")] + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] page: int @@ -23,4 +24,4 @@ class AppEventListParams(TypedDict, total=False): source: Literal["app", "system", "device", "webhook"] - to: Optional[str] + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] diff --git a/src/mobilerun_sdk/types/app_event_list_response.py b/src/mobilerun_sdk/types/app_event_list_response.py index 97b2e452..dbb90325 100644 --- a/src/mobilerun_sdk/types/app_event_list_response.py +++ b/src/mobilerun_sdk/types/app_event_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -14,7 +15,7 @@ class Item(BaseModel): id: str - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -22,7 +23,7 @@ class Item(BaseModel): event_type: str = FieldInfo(alias="eventType") - occurred_at: Optional[str] = FieldInfo(alias="occurredAt", default=None) + occurred_at: Optional[datetime] = FieldInfo(alias="occurredAt", default=None) owner_id: str = FieldInfo(alias="ownerId") diff --git a/src/mobilerun_sdk/types/app_event_retrieve_response.py b/src/mobilerun_sdk/types/app_event_retrieve_response.py index 15666c6e..a2f8b79e 100644 --- a/src/mobilerun_sdk/types/app_event_retrieve_response.py +++ b/src/mobilerun_sdk/types/app_event_retrieve_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -13,7 +14,7 @@ class Data(BaseModel): id: str - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -21,7 +22,7 @@ class Data(BaseModel): event_type: str = FieldInfo(alias="eventType") - occurred_at: Optional[str] = FieldInfo(alias="occurredAt", default=None) + occurred_at: Optional[datetime] = FieldInfo(alias="occurredAt", default=None) owner_id: str = FieldInfo(alias="ownerId") diff --git a/src/mobilerun_sdk/types/app_list_response.py b/src/mobilerun_sdk/types/app_list_response.py index c7088c0c..5d85ac9c 100644 --- a/src/mobilerun_sdk/types/app_list_response.py +++ b/src/mobilerun_sdk/types/app_list_response.py @@ -292,7 +292,7 @@ class ItemVersion(BaseModel): size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) - source: Literal["user", "system", "portal", "store", "catalog"] + source: Literal["user", "system", "portal", "store"] status: Literal["queued", "available", "failed"] diff --git a/src/mobilerun_sdk/types/app_list_versions_response.py b/src/mobilerun_sdk/types/app_list_versions_response.py index a4181ed7..a2bfedbd 100644 --- a/src/mobilerun_sdk/types/app_list_versions_response.py +++ b/src/mobilerun_sdk/types/app_list_versions_response.py @@ -281,7 +281,7 @@ class Data(BaseModel): size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) - source: Literal["user", "system", "portal", "store", "catalog"] + source: Literal["user", "system", "portal", "store"] status: Literal["queued", "available", "failed"] diff --git a/src/mobilerun_sdk/types/app_storage_usage_response.py b/src/mobilerun_sdk/types/app_storage_usage_response.py index f9dd47c4..a60c1137 100644 --- a/src/mobilerun_sdk/types/app_storage_usage_response.py +++ b/src/mobilerun_sdk/types/app_storage_usage_response.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional + from pydantic import Field as FieldInfo from .._models import BaseModel @@ -8,25 +10,27 @@ class Data(BaseModel): - available_bytes: float = FieldInfo(alias="availableBytes") - """Remaining bytes — the reliable maximum TOTAL size for the next upload. + included_bytes: Optional[float] = FieldInfo(alias="includedBytes", default=None) + """Bytes included in the plan (Autumn granted). - Advisory snapshot: the quota is enforced under a lock at confirm, so concurrent - uploads may reduce actual headroom. + Null when unlimited, unknown, or storage billing is off. """ - max_file_bytes: float = FieldInfo(alias="maxFileBytes") - """Per-file upload cap in bytes (env.MAX_UPLOAD_FILE_BYTES). - - A single file larger than this is rejected at confirm even when it fits the - remaining quota. Source of truth for the client-side per-file limit. + max_bytes: Optional[float] = FieldInfo(alias="maxBytes", default=None) + """ + Hard admission bound in bytes: included + max purchasable overage, or included + when overage is not allowed. Null = unlimited, unknown, or storage billing is + off. """ - quota_bytes: float = FieldInfo(alias="quotaBytes") - """Total storage allowance for the user, in bytes""" + overage_allowed: bool = FieldInfo(alias="overageAllowed") + """True when usage above includedBytes is billed as overage instead of blocked""" used_bytes: float = FieldInfo(alias="usedBytes") - """Bytes currently consumed across all of the user’s app versions""" + """ + Bytes currently used (decimal: Autumn storage_mb × 1,000,000; the local sum of + the user’s app versions when storage billing is off) + """ class AppStorageUsageResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/assistant/conversation_create_params.py b/src/mobilerun_sdk/types/assistant/conversation_create_params.py index 96755aa4..e3937f4d 100644 --- a/src/mobilerun_sdk/types/assistant/conversation_create_params.py +++ b/src/mobilerun_sdk/types/assistant/conversation_create_params.py @@ -2,9 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict - -from ..._utils import PropertyInfo +from typing_extensions import Required, TypedDict __all__ = ["ConversationCreateParams"] @@ -15,10 +13,3 @@ class ConversationCreateParams(TypedDict, total=False): agent: str description: str - - idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] - """Optional client key. - - Reusing the same key with the same request body by the same authenticated caller - within 24 hours returns the already-created session instead of a second one. - """ diff --git a/src/mobilerun_sdk/types/assistant/conversation_create_response.py b/src/mobilerun_sdk/types/assistant/conversation_create_response.py index 34341512..9c701f3f 100644 --- a/src/mobilerun_sdk/types/assistant/conversation_create_response.py +++ b/src/mobilerun_sdk/types/assistant/conversation_create_response.py @@ -25,6 +25,8 @@ class Session(BaseModel): created_at: str = FieldInfo(alias="createdAt") + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + description: Optional[str] = None last_active_at: str = FieldInfo(alias="lastActiveAt") diff --git a/src/mobilerun_sdk/types/assistant/conversation_list_response.py b/src/mobilerun_sdk/types/assistant/conversation_list_response.py index 3f3ba16a..efa428bd 100644 --- a/src/mobilerun_sdk/types/assistant/conversation_list_response.py +++ b/src/mobilerun_sdk/types/assistant/conversation_list_response.py @@ -25,6 +25,8 @@ class SessionUnionMember0(BaseModel): created_at: str = FieldInfo(alias="createdAt") + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + description: Optional[str] = None episode: int @@ -69,6 +71,8 @@ class SessionUnionMember1(BaseModel): created_at: str = FieldInfo(alias="createdAt") + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + description: Optional[str] = None episode: int diff --git a/src/mobilerun_sdk/types/assistant/conversation_update_response.py b/src/mobilerun_sdk/types/assistant/conversation_update_response.py index 514fad09..7cd37fe1 100644 --- a/src/mobilerun_sdk/types/assistant/conversation_update_response.py +++ b/src/mobilerun_sdk/types/assistant/conversation_update_response.py @@ -25,6 +25,8 @@ class Session(BaseModel): created_at: str = FieldInfo(alias="createdAt") + credits_used: Optional[float] = FieldInfo(alias="creditsUsed", default=None) + description: Optional[str] = None last_active_at: str = FieldInfo(alias="lastActiveAt") diff --git a/src/mobilerun_sdk/types/connect/__init__.py b/src/mobilerun_sdk/types/connect/__init__.py index bc194ece..1c5d9034 100644 --- a/src/mobilerun_sdk/types/connect/__init__.py +++ b/src/mobilerun_sdk/types/connect/__init__.py @@ -12,12 +12,15 @@ from .country_list_params import CountryListParams as CountryListParams from .proxy_list_response import ProxyListResponse as ProxyListResponse from .proxy_ping_response import ProxyPingResponse as ProxyPingResponse +from .proxy_update_params import ProxyUpdateParams as ProxyUpdateParams from .user_create_response import UserCreateResponse as UserCreateResponse from .user_update_response import UserUpdateResponse as UserUpdateResponse from .country_list_response import CountryListResponse as CountryListResponse +from .proxy_update_response import ProxyUpdateResponse as ProxyUpdateResponse from .user_retrieve_response import UserRetrieveResponse as UserRetrieveResponse from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse from .user_list_connections_params import UserListConnectionsParams as UserListConnectionsParams +from .proxy_ensure_healthy_response import ProxyEnsureHealthyResponse as ProxyEnsureHealthyResponse from .proxy_list_connections_params import ProxyListConnectionsParams as ProxyListConnectionsParams from .user_list_connections_response import UserListConnectionsResponse as UserListConnectionsResponse from .proxy_list_connections_response import ProxyListConnectionsResponse as ProxyListConnectionsResponse diff --git a/src/mobilerun_sdk/types/connect/proxy_buy_params.py b/src/mobilerun_sdk/types/connect/proxy_buy_params.py index 8426ebeb..b228449f 100644 --- a/src/mobilerun_sdk/types/connect/proxy_buy_params.py +++ b/src/mobilerun_sdk/types/connect/proxy_buy_params.py @@ -2,9 +2,7 @@ from __future__ import annotations -from typing_extensions import Literal, Required, Annotated, TypedDict - -from ..._utils import PropertyInfo +from typing_extensions import Literal, Required, TypedDict __all__ = ["ProxyBuyParams"] @@ -15,4 +13,9 @@ class ProxyBuyParams(TypedDict, total=False): type: Required[Literal["dedicated_residential", "residential", "mobile"]] - idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] + name: str + """ + Display name for the proxy, up to 64 characters excluding surrounding + whitespace, and containing no NUL. Omit it (or send only whitespace) to get a + generated label built from the country, type, and id. + """ diff --git a/src/mobilerun_sdk/types/connect/proxy_buy_response.py b/src/mobilerun_sdk/types/connect/proxy_buy_response.py index d512bbef..410cd95f 100644 --- a/src/mobilerun_sdk/types/connect/proxy_buy_response.py +++ b/src/mobilerun_sdk/types/connect/proxy_buy_response.py @@ -23,6 +23,14 @@ class ProxyBuyResponse(BaseModel): host: str + name: str + """Display name for the proxy. + + Set it on create or change it with PATCH; when no name has been set, a label + generated from the proxy's country, type, and id is returned instead — so this + is never empty. + """ + password: str port: int diff --git a/src/mobilerun_sdk/types/connect/proxy_ensure_healthy_response.py b/src/mobilerun_sdk/types/connect/proxy_ensure_healthy_response.py new file mode 100644 index 00000000..8221e19f --- /dev/null +++ b/src/mobilerun_sdk/types/connect/proxy_ensure_healthy_response.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ProxyEnsureHealthyResponse"] + + +class ProxyEnsureHealthyResponse(BaseModel): + """The outcome of an ensure-healthy check.""" + + proxy_id: str = FieldInfo(alias="proxyId") + + status: Literal["healthy", "rotated", "refused", "unreachable"] + """`healthy`: the upstream accepted the current session. + + `rotated`: the session was refused and the proxy now uses a fresh, checked + session; retry through the proxy. `refused`: the session was refused and could + not be replaced. `unreachable`: the upstream could not be reached; the session + was left unchanged. + """ diff --git a/src/mobilerun_sdk/types/connect/proxy_list_response.py b/src/mobilerun_sdk/types/connect/proxy_list_response.py index e68fd457..04aba83a 100644 --- a/src/mobilerun_sdk/types/connect/proxy_list_response.py +++ b/src/mobilerun_sdk/types/connect/proxy_list_response.py @@ -23,6 +23,14 @@ class Item(BaseModel): host: str + name: str + """Display name for the proxy. + + Set it on create or change it with PATCH; when no name has been set, a label + generated from the proxy's country, type, and id is returned instead — so this + is never empty. + """ + port: int status: Literal["checking", "pending_payment", "provisioning", "active", "cancelling", "ended", "error"] diff --git a/src/mobilerun_sdk/types/connect/proxy_retrieve_response.py b/src/mobilerun_sdk/types/connect/proxy_retrieve_response.py index 41014606..d4607b03 100644 --- a/src/mobilerun_sdk/types/connect/proxy_retrieve_response.py +++ b/src/mobilerun_sdk/types/connect/proxy_retrieve_response.py @@ -23,6 +23,14 @@ class ProxyRetrieveResponse(BaseModel): host: str + name: str + """Display name for the proxy. + + Set it on create or change it with PATCH; when no name has been set, a label + generated from the proxy's country, type, and id is returned instead — so this + is never empty. + """ + password: str port: int diff --git a/src/mobilerun_sdk/types/connect/proxy_update_params.py b/src/mobilerun_sdk/types/connect/proxy_update_params.py new file mode 100644 index 00000000..bb88f21b --- /dev/null +++ b/src/mobilerun_sdk/types/connect/proxy_update_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["ProxyUpdateParams"] + + +class ProxyUpdateParams(TypedDict, total=False): + name: Optional[str] + """ + New display name, up to 64 characters excluding surrounding whitespace, and + containing no NUL. Send null (or an empty/whitespace-only string) to drop a + custom name and go back to the generated label. Omit to leave the name + unchanged. + """ diff --git a/src/mobilerun_sdk/types/connect/proxy_update_response.py b/src/mobilerun_sdk/types/connect/proxy_update_response.py new file mode 100644 index 00000000..9bc78fa4 --- /dev/null +++ b/src/mobilerun_sdk/types/connect/proxy_update_response.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ProxyUpdateResponse"] + + +class ProxyUpdateResponse(BaseModel): + """A provisioned proxy without its credentials.""" + + id: str + + country: str + """ISO 3166-1 alpha-2 country code (lowercase).""" + + created_at: datetime = FieldInfo(alias="createdAt") + + host: str + + name: str + """Display name for the proxy. + + Set it on create or change it with PATCH; when no name has been set, a label + generated from the proxy's country, type, and id is returned instead — so this + is never empty. + """ + + port: int + + status: Literal["checking", "pending_payment", "provisioning", "active", "cancelling", "ended", "error"] + """Lifecycle of a proxy. + + A freshly created proxy is `checking` while its billing identity is being + resolved — clients should poll until a `paymentUrl` or a later status appears — + then `provisioning` — or `pending_payment` until the customer completes checkout + — and becomes `active` once its upstream is assigned. `cancelling` retains full + access through the paid period; when the subscription expires the proxy is + `ended`. `error` marks a failed provisioning attempt. + """ + + type: Literal["dedicated_residential", "residential", "mobile"] + + username: str + + billing_mode: Optional[Literal["included", "standalone_paid"]] = FieldInfo(alias="billingMode", default=None) diff --git a/src/mobilerun_sdk/types/device_create_params.py b/src/mobilerun_sdk/types/device_create_params.py index 4596899e..c8169523 100644 --- a/src/mobilerun_sdk/types/device_create_params.py +++ b/src/mobilerun_sdk/types/device_create_params.py @@ -18,9 +18,13 @@ class DeviceCreateParams(TypedDict, total=False): billing: Literal["auto", "subscription", "minute"] """Billing mode. - 'auto' uses a subscription slot when available and otherwise bills per minute; - 'subscription' requires an available subscription slot; 'minute' bills per - minute. Only cloud phone and cloud emulator devices support per-minute billing. + 'auto' tries subscription first, then minute billing if no subscription + entitlement exists or all subscription slots are in use, provided minute billing + is enabled. When subscription billing is disabled, auto uses minutes directly. + Billing-service failures never trigger fallback. 'subscription' requires an + available subscription slot and never falls back. 'minute' uses minute billing + only, subject to balance and concurrency checks. Modes depend on the device + type's billing configuration. """ query_country: Annotated[str, PropertyInfo(alias="country")] @@ -29,21 +33,12 @@ class DeviceCreateParams(TypedDict, total=False): If omitted the system picks the country with the most availability. """ - device_type: Annotated[ - Literal[ - "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", - ], - PropertyInfo(alias="deviceType"), - ] - """ - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + device_type: Annotated[str, PropertyInfo(alias="deviceType")] + """Use android_cloud_phone for a cloud Android phone. + + Only canonical identifiers are accepted. Other backends are deployment-specific; + recognized but unavailable types return DEVICE*TYPE_UNAVAILABLE (422). Retired + dedicated*\\** aliases are rejected with a canonical replacement. """ profile_id: Annotated[str, PropertyInfo(alias="profileId")] diff --git a/src/mobilerun_sdk/types/device_list_params.py b/src/mobilerun_sdk/types/device_list_params.py index 2f102f82..a0889e72 100644 --- a/src/mobilerun_sdk/types/device_list_params.py +++ b/src/mobilerun_sdk/types/device_list_params.py @@ -32,6 +32,12 @@ class DeviceListParams(TypedDict, total=False): page_size: Annotated[int, PropertyInfo(alias="pageSize")] + platform: Literal["android", "ios"] + """ + Filter by the device's platform as served in the platform field (runtime, + announced, else type-derived). + """ + provider_id: Annotated[str, PropertyInfo(alias="providerId")] state: Optional[ @@ -53,14 +59,14 @@ class DeviceListParams(TypedDict, total=False): type: Literal[ "android_cloud_phone", - "dedicated_premium_device", - "dedicated_physical_device", - "dedicated_ios_device", - "dedicated_emulated_device", + "android_physical_phone", + "ios_stealth_phone", + "android_emulator", + "ios_simulator", + "device_slot", ] - """ - Deprecated device type aliases are accepted during a compatibility grace period: - dedicated_premium_device maps to android_cloud_phone, dedicated_physical_device - maps to android_physical_phone, dedicated_ios_device maps to ios_stealth_phone, - and dedicated_emulated_device maps to android_emulator. + """Canonical device type. + + Retired dedicated\\__\\** aliases are no longer accepted. Availability depends on + the deployment. """ diff --git a/src/mobilerun_sdk/types/device_summary_params.py b/src/mobilerun_sdk/types/device_summary_params.py new file mode 100644 index 00000000..d7b8e50e --- /dev/null +++ b/src/mobilerun_sdk/types/device_summary_params.py @@ -0,0 +1,27 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["DeviceSummaryParams"] + + +class DeviceSummaryParams(TypedDict, total=False): + state: Optional[ + List[ + Literal[ + "creating", + "assigned", + "ready", + "rebooting", + "migrating", + "resetting", + "terminated", + "maintenance", + "stopped", + "unknown", + ] + ] + ] diff --git a/src/mobilerun_sdk/types/device_summary_response.py b/src/mobilerun_sdk/types/device_summary_response.py new file mode 100644 index 00000000..a5022915 --- /dev/null +++ b/src/mobilerun_sdk/types/device_summary_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["DeviceSummaryResponse"] + + +class DeviceSummaryResponse(BaseModel): + by_state: Dict[str, int] = FieldInfo(alias="byState") + + by_type: Dict[str, int] = FieldInfo(alias="byType") + + total: int + + schema_: Optional[str] = FieldInfo(alias="$schema", default=None) + """A URL to the JSON Schema for this object.""" diff --git a/src/mobilerun_sdk/types/devices/recording_start_params.py b/src/mobilerun_sdk/types/devices/recording_start_params.py index 007181d3..f2b6fefc 100644 --- a/src/mobilerun_sdk/types/devices/recording_start_params.py +++ b/src/mobilerun_sdk/types/devices/recording_start_params.py @@ -25,7 +25,9 @@ class RecordingStartParams(TypedDict, total=False): types: Optional[SequenceNotStr[str]] """ - Artifacts to capture: trajectory (input actions), video, and audio (captured + Artifacts to capture: trajectory (input actions; on portal stream-bridge devices + only when the handset announces trajectory capture), video, and audio (captured into the video artifact, so it requires video; honored by portal stream-bridge - recorders). Defaults to trajectory and video. + recorders). Defaults to trajectory and video, narrowed to what the device + produces. """ diff --git a/src/mobilerun_sdk/types/devices/traffic_session_create_params.py b/src/mobilerun_sdk/types/devices/traffic_session_create_params.py index 20d0df0a..39f0eb3e 100644 --- a/src/mobilerun_sdk/types/devices/traffic_session_create_params.py +++ b/src/mobilerun_sdk/types/devices/traffic_session_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from ..._utils import PropertyInfo @@ -10,8 +10,6 @@ class TrafficSessionCreateParams(TypedDict, total=False): - idempotency_key: Required[Annotated[str, PropertyInfo(alias="Idempotency-Key")]] - expires_in_seconds: Annotated[int, PropertyInfo(alias="expiresInSeconds")] max_body_bytes: Annotated[int, PropertyInfo(alias="maxBodyBytes")] diff --git a/src/mobilerun_sdk/types/file_list_response.py b/src/mobilerun_sdk/types/file_list_response.py index b3ccd796..c7152560 100644 --- a/src/mobilerun_sdk/types/file_list_response.py +++ b/src/mobilerun_sdk/types/file_list_response.py @@ -77,9 +77,13 @@ class File(BaseModel): class Quota(BaseModel): - current_bytes: int = FieldInfo(alias="currentBytes") + included_bytes: Optional[int] = FieldInfo(alias="includedBytes", default=None) - quota_bytes: int = FieldInfo(alias="quotaBytes") + max_bytes: Optional[int] = FieldInfo(alias="maxBytes", default=None) + + overage_allowed: bool = FieldInfo(alias="overageAllowed") + + used_bytes: int = FieldInfo(alias="usedBytes") class FileListResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/file_upload_url_params.py b/src/mobilerun_sdk/types/file_upload_url_params.py index 98c7f0c0..7207b9ac 100644 --- a/src/mobilerun_sdk/types/file_upload_url_params.py +++ b/src/mobilerun_sdk/types/file_upload_url_params.py @@ -17,5 +17,3 @@ class FileUploadURLParams(TypedDict, total=False): size_bytes: Required[Annotated[int, PropertyInfo(alias="sizeBytes")]] zone: Literal["user", "skills"] - - idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] diff --git a/src/mobilerun_sdk/types/mailbox_create_params.py b/src/mobilerun_sdk/types/mailbox_create_params.py index 68cf68f8..77cc7737 100644 --- a/src/mobilerun_sdk/types/mailbox_create_params.py +++ b/src/mobilerun_sdk/types/mailbox_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing_extensions import Literal, Required, Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from .._utils import PropertyInfo @@ -10,8 +10,6 @@ class MailboxCreateParams(TypedDict, total=False): - client_request_id: Required[Annotated[str, PropertyInfo(alias="clientRequestId")]] - billing_preference: Annotated[Literal["included", "included_only", "rent"], PropertyInfo(alias="billingPreference")] """ included uses package capacity when available and otherwise starts paid diff --git a/src/mobilerun_sdk/types/mailbox_create_response.py b/src/mobilerun_sdk/types/mailbox_create_response.py index 2bb8b3c9..e900323c 100644 --- a/src/mobilerun_sdk/types/mailbox_create_response.py +++ b/src/mobilerun_sdk/types/mailbox_create_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxCreateResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_delete_response.py b/src/mobilerun_sdk/types/mailbox_delete_response.py index ac6cd9b8..4ef6563d 100644 --- a/src/mobilerun_sdk/types/mailbox_delete_response.py +++ b/src/mobilerun_sdk/types/mailbox_delete_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxDeleteResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_disconnect_response.py b/src/mobilerun_sdk/types/mailbox_disconnect_response.py new file mode 100644 index 00000000..c1148322 --- /dev/null +++ b/src/mobilerun_sdk/types/mailbox_disconnect_response.py @@ -0,0 +1,63 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["MailboxDisconnectResponse", "Data", "DataInboundMessages"] + + +class DataInboundMessages(BaseModel): + exhausted: bool + + included: int + + resets_at: Optional[datetime] = FieldInfo(alias="resetsAt", default=None) + + used: int + + +class Data(BaseModel): + id: str + + address: Optional[str] = None + + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") + + cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") + + checkout_expires_at: Optional[datetime] = FieldInfo(alias="checkoutExpiresAt", default=None) + + checkout_url: Optional[str] = FieldInfo(alias="checkoutUrl", default=None) + + created_at: datetime = FieldInfo(alias="createdAt") + + current_period_end: Optional[datetime] = FieldInfo(alias="currentPeriodEnd", default=None) + + domain_id: Optional[str] = FieldInfo(alias="domainId", default=None) + + inbound_messages: DataInboundMessages = FieldInfo(alias="inboundMessages") + + label: Optional[str] = None + + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] + + +class MailboxDisconnectResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/mailbox_list_response.py b/src/mobilerun_sdk/types/mailbox_list_response.py index 70ace920..8bde5500 100644 --- a/src/mobilerun_sdk/types/mailbox_list_response.py +++ b/src/mobilerun_sdk/types/mailbox_list_response.py @@ -27,7 +27,7 @@ class Item(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -45,7 +45,19 @@ class Item(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxListResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_restart_response.py b/src/mobilerun_sdk/types/mailbox_restart_response.py index ab9e1d79..316f0c79 100644 --- a/src/mobilerun_sdk/types/mailbox_restart_response.py +++ b/src/mobilerun_sdk/types/mailbox_restart_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxRestartResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_retrieve_response.py b/src/mobilerun_sdk/types/mailbox_retrieve_response.py index ed490495..d9106311 100644 --- a/src/mobilerun_sdk/types/mailbox_retrieve_response.py +++ b/src/mobilerun_sdk/types/mailbox_retrieve_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxRetrieveResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_uncancel_response.py b/src/mobilerun_sdk/types/mailbox_uncancel_response.py index fc6b588e..9c613702 100644 --- a/src/mobilerun_sdk/types/mailbox_uncancel_response.py +++ b/src/mobilerun_sdk/types/mailbox_uncancel_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxUncancelResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailbox_update_response.py b/src/mobilerun_sdk/types/mailbox_update_response.py index 99de186b..43419175 100644 --- a/src/mobilerun_sdk/types/mailbox_update_response.py +++ b/src/mobilerun_sdk/types/mailbox_update_response.py @@ -26,7 +26,7 @@ class Data(BaseModel): address: Optional[str] = None - billing_mode: Literal["rent", "included", "domain"] = FieldInfo(alias="billingMode") + billing_mode: Literal["rent", "included", "domain", "external"] = FieldInfo(alias="billingMode") cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") @@ -44,7 +44,19 @@ class Data(BaseModel): label: Optional[str] = None - status: Literal["provisioning", "awaiting_payment", "active", "cancel_scheduled", "archived", "billing_error"] + provider: Literal["matix", "gmail"] + + status: Literal[ + "provisioning", + "awaiting_payment", + "active", + "cancel_scheduled", + "archived", + "billing_error", + "pending_connection", + "connection_expired", + "connection_removed", + ] class MailboxUpdateResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/mailboxes/__init__.py b/src/mobilerun_sdk/types/mailboxes/__init__.py index bc05907a..930243b3 100644 --- a/src/mobilerun_sdk/types/mailboxes/__init__.py +++ b/src/mobilerun_sdk/types/mailboxes/__init__.py @@ -4,4 +4,7 @@ from .message_list_params import MessageListParams as MessageListParams from .message_list_response import MessageListResponse as MessageListResponse +from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams from .message_retrieve_response import MessageRetrieveResponse as MessageRetrieveResponse +from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse +from .connection_retrieve_response import ConnectionRetrieveResponse as ConnectionRetrieveResponse diff --git a/src/mobilerun_sdk/types/mailboxes/connection_create_params.py b/src/mobilerun_sdk/types/mailboxes/connection_create_params.py new file mode 100644 index 00000000..ed31aa5a --- /dev/null +++ b/src/mobilerun_sdk/types/mailboxes/connection_create_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["ConnectionCreateParams"] + + +class ConnectionCreateParams(TypedDict, total=False): + provider: Required[Literal["gmail"]] + + binding_hash: Annotated[str, PropertyInfo(alias="bindingHash")] + """ + sha256 hex of the browser-held connect binding secret; forwarded to integrations + connect + """ + + client_request_id: Annotated[str, PropertyInfo(alias="clientRequestId")] + """Use the Idempotency-Key header.""" + + label: str diff --git a/src/mobilerun_sdk/types/mailboxes/connection_create_response.py b/src/mobilerun_sdk/types/mailboxes/connection_create_response.py new file mode 100644 index 00000000..e7cc4150 --- /dev/null +++ b/src/mobilerun_sdk/types/mailboxes/connection_create_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ConnectionCreateResponse"] + + +class ConnectionCreateResponse(BaseModel): + address: Optional[str] = None + + connection_id: str = FieldInfo(alias="connectionId") + + mailbox_id: str = FieldInfo(alias="mailboxId") + + redirect_url: Optional[str] = FieldInfo(alias="redirectUrl", default=None) + + status: Literal["pending", "active", "expired", "removed"] diff --git a/src/mobilerun_sdk/types/mailboxes/connection_retrieve_response.py b/src/mobilerun_sdk/types/mailboxes/connection_retrieve_response.py new file mode 100644 index 00000000..8278cf78 --- /dev/null +++ b/src/mobilerun_sdk/types/mailboxes/connection_retrieve_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ConnectionRetrieveResponse"] + + +class ConnectionRetrieveResponse(BaseModel): + address: Optional[str] = None + + connection_id: str = FieldInfo(alias="connectionId") + + mailbox_id: str = FieldInfo(alias="mailboxId") + + redirect_url: Optional[str] = FieldInfo(alias="redirectUrl", default=None) + + status: Literal["pending", "active", "expired", "removed"] diff --git a/src/mobilerun_sdk/types/message_retrieve_response.py b/src/mobilerun_sdk/types/message_retrieve_response.py new file mode 100644 index 00000000..3fbc8e1f --- /dev/null +++ b/src/mobilerun_sdk/types/message_retrieve_response.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["MessageRetrieveResponse", "Data"] + + +class Data(BaseModel): + id: str + + body: Optional[str] = None + + created_at: datetime = FieldInfo(alias="createdAt") + + delivery_status: Optional[str] = FieldInfo(alias="deliveryStatus", default=None) + + detected_sender: Optional[str] = FieldInfo(alias="detectedSender", default=None) + + direction: Literal["inbound", "outbound"] + + esim_id: Optional[str] = FieldInfo(alias="esimId", default=None) + + occurred_at: datetime = FieldInfo(alias="occurredAt") + + peer_key: Optional[str] = FieldInfo(alias="peerKey", default=None) + + peer_number: Optional[str] = FieldInfo(alias="peerNumber", default=None) + + provider_code: Optional[str] = FieldInfo(alias="providerCode", default=None) + + status: Literal["received", "queued", "claimed", "sending", "sent", "sent_unconfirmed", "delivered", "failed"] + + +class MessageRetrieveResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/notification_apply_preferences_preset_params.py b/src/mobilerun_sdk/types/notification_apply_preferences_preset_params.py new file mode 100644 index 00000000..43702897 --- /dev/null +++ b/src/mobilerun_sdk/types/notification_apply_preferences_preset_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +__all__ = ["NotificationApplyPreferencesPresetParams"] + + +class NotificationApplyPreferencesPresetParams(TypedDict, total=False): + preset: Required[Literal["recommended", "failures", "everything"]] diff --git a/src/mobilerun_sdk/types/notification_apply_preferences_preset_response.py b/src/mobilerun_sdk/types/notification_apply_preferences_preset_response.py new file mode 100644 index 00000000..58962a91 --- /dev/null +++ b/src/mobilerun_sdk/types/notification_apply_preferences_preset_response.py @@ -0,0 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["NotificationApplyPreferencesPresetResponse", "Data"] + + +class Data(BaseModel): + muted_types: List[str] = FieldInfo(alias="mutedTypes") + + +class NotificationApplyPreferencesPresetResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/number_create_params.py b/src/mobilerun_sdk/types/number_create_params.py index 353cd5fb..1d190a8e 100644 --- a/src/mobilerun_sdk/types/number_create_params.py +++ b/src/mobilerun_sdk/types/number_create_params.py @@ -32,6 +32,3 @@ class NumberCreateParams(TypedDict, total=False): purpose: str """Optional purpose from GET /numbers/phones/purposes.""" - - idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] - """Optional request idempotency key.""" diff --git a/src/mobilerun_sdk/types/number_delete_response.py b/src/mobilerun_sdk/types/number_delete_response.py index 4e9351eb..6a04fe4c 100644 --- a/src/mobilerun_sdk/types/number_delete_response.py +++ b/src/mobilerun_sdk/types/number_delete_response.py @@ -8,17 +8,37 @@ from .._models import BaseModel -__all__ = ["NumberDeleteResponse", "Data"] +__all__ = ["NumberDeleteResponse", "Data", "DataActions"] + + +class DataActions(BaseModel): + """Actions currently available for this phone number.""" + + cancel: bool + """True when DELETE /numbers/phones/{id} would currently succeed.""" + + send: bool + """ + True when this number passes the send gate of POST + /numbers/phones/{id}/messages: self-service sending is switched on and the + number is active and able to send SMS. Daily and burst limits and recipient/body + checks still apply; the send endpoint stays the final judge. + """ class Data(BaseModel): id: str + actions: DataActions + """Actions currently available for this phone number.""" + cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") cancellable: bool + """Deprecated: use `actions.cancel`""" can_send: bool = FieldInfo(alias="canSend") + """Deprecated: use `actions.send`""" capabilities: Optional[List[Literal["sms", "voice"]]] = None diff --git a/src/mobilerun_sdk/types/number_list_response.py b/src/mobilerun_sdk/types/number_list_response.py index 59a3eccd..030ad80a 100644 --- a/src/mobilerun_sdk/types/number_list_response.py +++ b/src/mobilerun_sdk/types/number_list_response.py @@ -9,17 +9,37 @@ from .._models import BaseModel from .shared.pagination import Pagination -__all__ = ["NumberListResponse", "Item"] +__all__ = ["NumberListResponse", "Item", "ItemActions"] + + +class ItemActions(BaseModel): + """Actions currently available for this phone number.""" + + cancel: bool + """True when DELETE /numbers/phones/{id} would currently succeed.""" + + send: bool + """ + True when this number passes the send gate of POST + /numbers/phones/{id}/messages: self-service sending is switched on and the + number is active and able to send SMS. Daily and burst limits and recipient/body + checks still apply; the send endpoint stays the final judge. + """ class Item(BaseModel): id: str + actions: ItemActions + """Actions currently available for this phone number.""" + cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") cancellable: bool + """Deprecated: use `actions.cancel`""" can_send: bool = FieldInfo(alias="canSend") + """Deprecated: use `actions.send`""" capabilities: Optional[List[Literal["sms", "voice"]]] = None diff --git a/src/mobilerun_sdk/types/number_retrieve_response.py b/src/mobilerun_sdk/types/number_retrieve_response.py index dcd5843f..916523d9 100644 --- a/src/mobilerun_sdk/types/number_retrieve_response.py +++ b/src/mobilerun_sdk/types/number_retrieve_response.py @@ -8,17 +8,37 @@ from .._models import BaseModel -__all__ = ["NumberRetrieveResponse", "Data"] +__all__ = ["NumberRetrieveResponse", "Data", "DataActions"] + + +class DataActions(BaseModel): + """Actions currently available for this phone number.""" + + cancel: bool + """True when DELETE /numbers/phones/{id} would currently succeed.""" + + send: bool + """ + True when this number passes the send gate of POST + /numbers/phones/{id}/messages: self-service sending is switched on and the + number is active and able to send SMS. Daily and burst limits and recipient/body + checks still apply; the send endpoint stays the final judge. + """ class Data(BaseModel): id: str + actions: DataActions + """Actions currently available for this phone number.""" + cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") cancellable: bool + """Deprecated: use `actions.cancel`""" can_send: bool = FieldInfo(alias="canSend") + """Deprecated: use `actions.send`""" capabilities: Optional[List[Literal["sms", "voice"]]] = None diff --git a/src/mobilerun_sdk/types/number_update_response.py b/src/mobilerun_sdk/types/number_update_response.py index e6166cb1..5a2a2a3b 100644 --- a/src/mobilerun_sdk/types/number_update_response.py +++ b/src/mobilerun_sdk/types/number_update_response.py @@ -8,17 +8,37 @@ from .._models import BaseModel -__all__ = ["NumberUpdateResponse", "Data"] +__all__ = ["NumberUpdateResponse", "Data", "DataActions"] + + +class DataActions(BaseModel): + """Actions currently available for this phone number.""" + + cancel: bool + """True when DELETE /numbers/phones/{id} would currently succeed.""" + + send: bool + """ + True when this number passes the send gate of POST + /numbers/phones/{id}/messages: self-service sending is switched on and the + number is active and able to send SMS. Daily and burst limits and recipient/body + checks still apply; the send endpoint stays the final judge. + """ class Data(BaseModel): id: str + actions: DataActions + """Actions currently available for this phone number.""" + cancel_at_period_end: bool = FieldInfo(alias="cancelAtPeriodEnd") cancellable: bool + """Deprecated: use `actions.cancel`""" can_send: bool = FieldInfo(alias="canSend") + """Deprecated: use `actions.send`""" capabilities: Optional[List[Literal["sms", "voice"]]] = None diff --git a/src/mobilerun_sdk/types/numbers/__init__.py b/src/mobilerun_sdk/types/numbers/__init__.py index 867594a6..d059c99c 100644 --- a/src/mobilerun_sdk/types/numbers/__init__.py +++ b/src/mobilerun_sdk/types/numbers/__init__.py @@ -3,4 +3,6 @@ from __future__ import annotations from .message_list_params import MessageListParams as MessageListParams +from .message_send_params import MessageSendParams as MessageSendParams from .message_list_response import MessageListResponse as MessageListResponse +from .message_send_response import MessageSendResponse as MessageSendResponse diff --git a/src/mobilerun_sdk/types/numbers/message_send_params.py b/src/mobilerun_sdk/types/numbers/message_send_params.py new file mode 100644 index 00000000..25d2ddb3 --- /dev/null +++ b/src/mobilerun_sdk/types/numbers/message_send_params.py @@ -0,0 +1,34 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["MessageSendParams"] + + +class MessageSendParams(TypedDict, total=False): + body: Required[str] + """ + SMS body text, up to 1600 characters (rejected with 400 beyond that, before + hashing). A number's own limit may be lower and is answered 422 body_too_long. + """ + + to: Required[str] + """Recipient phone number, as E.164 or a US 10/11-digit number. + + Destinations the number cannot send to are rejected with 422 + unsupported_destination, non-numbers with 422 invalid_recipient. + """ + + client_request_id: Annotated[str, PropertyInfo(alias="clientRequestId")] + """Deprecated: use the Idempotency-Key header instead. + + Optional idempotency key. Replaying the same key and payload returns the + original send. + """ + + delivery_report: Annotated[bool, PropertyInfo(alias="deliveryReport")] + """Request a delivery report. Defaults to false.""" diff --git a/src/mobilerun_sdk/types/numbers/message_send_response.py b/src/mobilerun_sdk/types/numbers/message_send_response.py new file mode 100644 index 00000000..3671250e --- /dev/null +++ b/src/mobilerun_sdk/types/numbers/message_send_response.py @@ -0,0 +1,39 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["MessageSendResponse", "Data"] + + +class Data(BaseModel): + id: str + + body: Optional[str] = None + + created_at: datetime = FieldInfo(alias="createdAt") + + delivery_status: Optional[str] = FieldInfo(alias="deliveryStatus", default=None) + + detected_sender: Optional[str] = FieldInfo(alias="detectedSender", default=None) + + direction: Literal["inbound", "outbound"] + + esim_id: Optional[str] = FieldInfo(alias="esimId", default=None) + + occurred_at: datetime = FieldInfo(alias="occurredAt") + + peer_key: Optional[str] = FieldInfo(alias="peerKey", default=None) + + peer_number: Optional[str] = FieldInfo(alias="peerNumber", default=None) + + status: Literal["received", "queued", "claimed", "sending", "sent", "sent_unconfirmed", "delivered", "failed"] + + +class MessageSendResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/store/app_list_response.py b/src/mobilerun_sdk/types/store/app_list_response.py index c8dce76d..7ee43700 100644 --- a/src/mobilerun_sdk/types/store/app_list_response.py +++ b/src/mobilerun_sdk/types/store/app_list_response.py @@ -282,7 +282,7 @@ class ItemVersion(BaseModel): size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) - source: Literal["user", "system", "portal", "store", "catalog"] + source: Literal["user", "system", "portal", "store"] status: Literal["queued", "available", "failed"] diff --git a/src/mobilerun_sdk/types/store/app_retrieve_response.py b/src/mobilerun_sdk/types/store/app_retrieve_response.py index c9461110..a7e430e6 100644 --- a/src/mobilerun_sdk/types/store/app_retrieve_response.py +++ b/src/mobilerun_sdk/types/store/app_retrieve_response.py @@ -281,7 +281,7 @@ class DataVersion(BaseModel): size_bytes: Optional[int] = FieldInfo(alias="sizeBytes", default=None) - source: Literal["user", "system", "portal", "store", "catalog"] + source: Literal["user", "system", "portal", "store"] status: Literal["queued", "available", "failed"] @@ -324,6 +324,8 @@ class Data(BaseModel): version: DataVersion + versions: List[DataVersion] + class AppRetrieveResponse(BaseModel): data: Data diff --git a/src/mobilerun_sdk/types/task_get_trajectory_response.py b/src/mobilerun_sdk/types/task_get_trajectory_response.py index b078d881..2afe64a6 100644 --- a/src/mobilerun_sdk/types/task_get_trajectory_response.py +++ b/src/mobilerun_sdk/types/task_get_trajectory_response.py @@ -298,6 +298,9 @@ class TrajectoryTrajectoryFastAgentResponseEventData(BaseModel): code: Optional[str] = None + tool_call_status: Optional[Literal["valid", "no_markup", "malformed"]] = None + """Classification of tool-call markup in an LLM response.""" + usage: Optional[TrajectoryTrajectoryFastAgentResponseEventDataUsage] = None diff --git a/src/mobilerun_sdk/types/task_run_params.py b/src/mobilerun_sdk/types/task_run_params.py index e0b99bc9..4251b359 100644 --- a/src/mobilerun_sdk/types/task_run_params.py +++ b/src/mobilerun_sdk/types/task_run_params.py @@ -34,7 +34,7 @@ class TaskRunParams(TypedDict, total=False): files: SequenceNotStr[str] llm_model: Annotated[str, PropertyInfo(alias="llmModel")] - """The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna')""" + """The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna')""" max_steps: Annotated[int, PropertyInfo(alias="maxSteps")] @@ -65,8 +65,6 @@ class TaskRunParams(TypedDict, total=False): Optional[Literal["US", "BR", "FR", "DE", "IN", "JP", "KR", "ZA"]], PropertyInfo(alias="vpnCountry") ] - idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")] - class Credential(TypedDict, total=False): credential_names: Required[Annotated[SequenceNotStr[str], PropertyInfo(alias="credentialNames")]] diff --git a/src/mobilerun_sdk/types/task_run_streamed_params.py b/src/mobilerun_sdk/types/task_run_streamed_params.py index 5de0656e..64267509 100644 --- a/src/mobilerun_sdk/types/task_run_streamed_params.py +++ b/src/mobilerun_sdk/types/task_run_streamed_params.py @@ -34,7 +34,7 @@ class TaskRunStreamedParams(TypedDict, total=False): files: SequenceNotStr[str] llm_model: Annotated[str, PropertyInfo(alias="llmModel")] - """The LLM model identifier to use for the task (e.g. 'openai/gpt-5.6-luna')""" + """The LLM model identifier to use for the task (e.g. 'openai/gpt-6-luna')""" max_steps: Annotated[int, PropertyInfo(alias="maxSteps")] diff --git a/src/mobilerun_sdk/types/webhook_create_params.py b/src/mobilerun_sdk/types/webhook_create_params.py index 292e5846..84ab930e 100644 --- a/src/mobilerun_sdk/types/webhook_create_params.py +++ b/src/mobilerun_sdk/types/webhook_create_params.py @@ -2,17 +2,46 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict, Union +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict from .._types import SequenceNotStr from .._utils import PropertyInfo -__all__ = ["WebhookCreateParams"] +__all__ = ["WebhookCreateParams", "Variant0", "Variant1", "Variant1Capability"] -class WebhookCreateParams(TypedDict, total=False): +class Variant0(TypedDict, total=False): url: Required[str] description: str event_types: Annotated[SequenceNotStr[str], PropertyInfo(alias="eventTypes")] + + kind: Literal["http"] + """Delivery transport. Omitted ⇒ `http`.""" + + +class Variant1(TypedDict, total=False): + args: Required[Dict[str, object]] + """ + Target args exactly as returned by + `GET /webhooks/integrations/{capabilityId}/targets`. + """ + + capability: Required[Variant1Capability] + + kind: Required[Literal["integration"]] + + description: str + + event_types: Annotated[SequenceNotStr[str], PropertyInfo(alias="eventTypes")] + + +class Variant1Capability(TypedDict, total=False): + id: Required[str] + + revision: Required[int] + + +WebhookCreateParams: TypeAlias = Union[Variant0, Variant1] diff --git a/src/mobilerun_sdk/types/webhook_create_response.py b/src/mobilerun_sdk/types/webhook_create_response.py index 5cc8e292..7887f2c9 100644 --- a/src/mobilerun_sdk/types/webhook_create_response.py +++ b/src/mobilerun_sdk/types/webhook_create_response.py @@ -1,13 +1,30 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["WebhookCreateResponse", "Data"] +__all__ = ["WebhookCreateResponse", "Data", "DataIntegration", "DataIntegrationCapability"] + + +class DataIntegrationCapability(BaseModel): + id: str + + revision: int + + +class DataIntegration(BaseModel): + """Integration target; null for `http` webhooks.""" + + args: Dict[str, object] + + capability: DataIntegrationCapability + + label: str + """Target name at creation time, e.g. `#ops`.""" class Data(BaseModel): @@ -16,6 +33,11 @@ class Data(BaseModel): blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) blocked_reason: Optional[str] = FieldInfo(alias="blockedReason", default=None) + """Why the webhook was blocked, e.g. + + `integration_disconnected` (reconnect the integration) or + `integration_target_invalid` (the channel is gone or not allowed). + """ created_at: str = FieldInfo(alias="createdAt") @@ -33,16 +55,30 @@ class Data(BaseModel): state=ACTIVE to re-enable. """ - secret: str - """Signing secret — shown only once. Store it now.""" + integration: Optional[DataIntegration] = None + """Integration target; null for `http` webhooks.""" + + kind: Literal["http", "integration"] + """ + `http` posts signed JSON to `url`; `integration` posts a message through a + connected integration (e.g. a Slack channel). + """ signing_enabled: bool = FieldInfo(alias="signingEnabled") + """Always false for `integration` webhooks.""" state: Literal["ACTIVE", "DISABLED", "DELETED"] updated_at: str = FieldInfo(alias="updatedAt") - url: str + url: Optional[str] = None + """Delivery URL; null for `integration` webhooks.""" + + secret: Optional[str] = None + """Signing secret for `http` webhooks — shown only once. + + Store it now. Absent for `integration` webhooks. + """ class WebhookCreateResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/webhook_event_types_response.py b/src/mobilerun_sdk/types/webhook_event_types_response.py index 9c7439d7..1efc69ed 100644 --- a/src/mobilerun_sdk/types/webhook_event_types_response.py +++ b/src/mobilerun_sdk/types/webhook_event_types_response.py @@ -19,12 +19,17 @@ class DataSourceEventSurfaces(BaseModel): agent: Optional[bool] = None + live: Optional[bool] = None + class DataSourceEvent(BaseModel): description: str surfaces: DataSourceEventSurfaces + title: str + """Short human title, as used in integration messages.""" + type: str diff --git a/src/mobilerun_sdk/types/webhook_list_params.py b/src/mobilerun_sdk/types/webhook_list_params.py index 34f27b8d..fb8b70e8 100644 --- a/src/mobilerun_sdk/types/webhook_list_params.py +++ b/src/mobilerun_sdk/types/webhook_list_params.py @@ -13,6 +13,9 @@ class WebhookListParams(TypedDict, total=False): created_by: Annotated[str, PropertyInfo(alias="createdBy")] """Only include webhooks created by this actor id. Mutually exclusive with `mine`.""" + kind: Literal["http", "integration"] + """Only include webhooks of this delivery kind.""" + mine: Literal["true", "false"] """When true, only include webhooks created by you (not just owned by your org).""" diff --git a/src/mobilerun_sdk/types/webhook_list_response.py b/src/mobilerun_sdk/types/webhook_list_response.py index 9d8520ca..2c4d0fb7 100644 --- a/src/mobilerun_sdk/types/webhook_list_response.py +++ b/src/mobilerun_sdk/types/webhook_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -8,7 +8,7 @@ from .._models import BaseModel from .shared.pagination import Pagination -__all__ = ["WebhookListResponse", "Counts", "Item"] +__all__ = ["WebhookListResponse", "Counts", "Item", "ItemIntegration", "ItemIntegrationCapability"] class Counts(BaseModel): @@ -23,12 +23,34 @@ class Counts(BaseModel): total: float +class ItemIntegrationCapability(BaseModel): + id: str + + revision: int + + +class ItemIntegration(BaseModel): + """Integration target; null for `http` webhooks.""" + + args: Dict[str, object] + + capability: ItemIntegrationCapability + + label: str + """Target name at creation time, e.g. `#ops`.""" + + class Item(BaseModel): id: str blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) blocked_reason: Optional[str] = FieldInfo(alias="blockedReason", default=None) + """Why the webhook was blocked, e.g. + + `integration_disconnected` (reconnect the integration) or + `integration_target_invalid` (the channel is gone or not allowed). + """ created_at: str = FieldInfo(alias="createdAt") @@ -46,13 +68,24 @@ class Item(BaseModel): state=ACTIVE to re-enable. """ + integration: Optional[ItemIntegration] = None + """Integration target; null for `http` webhooks.""" + + kind: Literal["http", "integration"] + """ + `http` posts signed JSON to `url`; `integration` posts a message through a + connected integration (e.g. a Slack channel). + """ + signing_enabled: bool = FieldInfo(alias="signingEnabled") + """Always false for `integration` webhooks.""" state: Literal["ACTIVE", "DISABLED", "DELETED"] updated_at: str = FieldInfo(alias="updatedAt") - url: str + url: Optional[str] = None + """Delivery URL; null for `integration` webhooks.""" class WebhookListResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/webhook_retrieve_response.py b/src/mobilerun_sdk/types/webhook_retrieve_response.py index 36979260..82e78968 100644 --- a/src/mobilerun_sdk/types/webhook_retrieve_response.py +++ b/src/mobilerun_sdk/types/webhook_retrieve_response.py @@ -1,13 +1,30 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["WebhookRetrieveResponse", "Data"] +__all__ = ["WebhookRetrieveResponse", "Data", "DataIntegration", "DataIntegrationCapability"] + + +class DataIntegrationCapability(BaseModel): + id: str + + revision: int + + +class DataIntegration(BaseModel): + """Integration target; null for `http` webhooks.""" + + args: Dict[str, object] + + capability: DataIntegrationCapability + + label: str + """Target name at creation time, e.g. `#ops`.""" class Data(BaseModel): @@ -16,6 +33,11 @@ class Data(BaseModel): blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) blocked_reason: Optional[str] = FieldInfo(alias="blockedReason", default=None) + """Why the webhook was blocked, e.g. + + `integration_disconnected` (reconnect the integration) or + `integration_target_invalid` (the channel is gone or not allowed). + """ created_at: str = FieldInfo(alias="createdAt") @@ -33,13 +55,24 @@ class Data(BaseModel): state=ACTIVE to re-enable. """ + integration: Optional[DataIntegration] = None + """Integration target; null for `http` webhooks.""" + + kind: Literal["http", "integration"] + """ + `http` posts signed JSON to `url`; `integration` posts a message through a + connected integration (e.g. a Slack channel). + """ + signing_enabled: bool = FieldInfo(alias="signingEnabled") + """Always false for `integration` webhooks.""" state: Literal["ACTIVE", "DISABLED", "DELETED"] updated_at: str = FieldInfo(alias="updatedAt") - url: str + url: Optional[str] = None + """Delivery URL; null for `integration` webhooks.""" class WebhookRetrieveResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/webhook_rotate_secret_response.py b/src/mobilerun_sdk/types/webhook_rotate_secret_response.py index 8378caa3..cb809d3b 100644 --- a/src/mobilerun_sdk/types/webhook_rotate_secret_response.py +++ b/src/mobilerun_sdk/types/webhook_rotate_secret_response.py @@ -1,13 +1,30 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["WebhookRotateSecretResponse", "Data"] +__all__ = ["WebhookRotateSecretResponse", "Data", "DataIntegration", "DataIntegrationCapability"] + + +class DataIntegrationCapability(BaseModel): + id: str + + revision: int + + +class DataIntegration(BaseModel): + """Integration target; null for `http` webhooks.""" + + args: Dict[str, object] + + capability: DataIntegrationCapability + + label: str + """Target name at creation time, e.g. `#ops`.""" class Data(BaseModel): @@ -16,6 +33,11 @@ class Data(BaseModel): blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) blocked_reason: Optional[str] = FieldInfo(alias="blockedReason", default=None) + """Why the webhook was blocked, e.g. + + `integration_disconnected` (reconnect the integration) or + `integration_target_invalid` (the channel is gone or not allowed). + """ created_at: str = FieldInfo(alias="createdAt") @@ -33,16 +55,27 @@ class Data(BaseModel): state=ACTIVE to re-enable. """ + integration: Optional[DataIntegration] = None + """Integration target; null for `http` webhooks.""" + + kind: Literal["http", "integration"] + """ + `http` posts signed JSON to `url`; `integration` posts a message through a + connected integration (e.g. a Slack channel). + """ + secret: str """Signing secret — shown only once. Store it now.""" signing_enabled: bool = FieldInfo(alias="signingEnabled") + """Always false for `integration` webhooks.""" state: Literal["ACTIVE", "DISABLED", "DELETED"] updated_at: str = FieldInfo(alias="updatedAt") - url: str + url: Optional[str] = None + """Delivery URL; null for `integration` webhooks.""" class WebhookRotateSecretResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/webhook_update_response.py b/src/mobilerun_sdk/types/webhook_update_response.py index 8e1ec087..12276d18 100644 --- a/src/mobilerun_sdk/types/webhook_update_response.py +++ b/src/mobilerun_sdk/types/webhook_update_response.py @@ -1,13 +1,30 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from typing_extensions import Literal from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["WebhookUpdateResponse", "Data"] +__all__ = ["WebhookUpdateResponse", "Data", "DataIntegration", "DataIntegrationCapability"] + + +class DataIntegrationCapability(BaseModel): + id: str + + revision: int + + +class DataIntegration(BaseModel): + """Integration target; null for `http` webhooks.""" + + args: Dict[str, object] + + capability: DataIntegrationCapability + + label: str + """Target name at creation time, e.g. `#ops`.""" class Data(BaseModel): @@ -16,6 +33,11 @@ class Data(BaseModel): blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) blocked_reason: Optional[str] = FieldInfo(alias="blockedReason", default=None) + """Why the webhook was blocked, e.g. + + `integration_disconnected` (reconnect the integration) or + `integration_target_invalid` (the channel is gone or not allowed). + """ created_at: str = FieldInfo(alias="createdAt") @@ -33,13 +55,24 @@ class Data(BaseModel): state=ACTIVE to re-enable. """ + integration: Optional[DataIntegration] = None + """Integration target; null for `http` webhooks.""" + + kind: Literal["http", "integration"] + """ + `http` posts signed JSON to `url`; `integration` posts a message through a + connected integration (e.g. a Slack channel). + """ + signing_enabled: bool = FieldInfo(alias="signingEnabled") + """Always false for `integration` webhooks.""" state: Literal["ACTIVE", "DISABLED", "DELETED"] updated_at: str = FieldInfo(alias="updatedAt") - url: str + url: Optional[str] = None + """Delivery URL; null for `integration` webhooks.""" class WebhookUpdateResponse(BaseModel): diff --git a/src/mobilerun_sdk/types/webhooks/__init__.py b/src/mobilerun_sdk/types/webhooks/__init__.py index eb842e88..f319e4b8 100644 --- a/src/mobilerun_sdk/types/webhooks/__init__.py +++ b/src/mobilerun_sdk/types/webhooks/__init__.py @@ -6,6 +6,9 @@ from .delivery_stats_params import DeliveryStatsParams as DeliveryStatsParams from .delivery_list_response import DeliveryListResponse as DeliveryListResponse from .delivery_stats_response import DeliveryStatsResponse as DeliveryStatsResponse +from .integration_list_response import IntegrationListResponse as IntegrationListResponse +from .integration_list_targets_params import IntegrationListTargetsParams as IntegrationListTargetsParams from .delivery_list_for_webhook_params import DeliveryListForWebhookParams as DeliveryListForWebhookParams +from .integration_list_targets_response import IntegrationListTargetsResponse as IntegrationListTargetsResponse from .delivery_list_for_webhook_response import DeliveryListForWebhookResponse as DeliveryListForWebhookResponse from .delivery_retrieve_attempts_response import DeliveryRetrieveAttemptsResponse as DeliveryRetrieveAttemptsResponse diff --git a/src/mobilerun_sdk/types/webhooks/delivery_list_params.py b/src/mobilerun_sdk/types/webhooks/delivery_list_params.py index 4baa28a3..56e19b4e 100644 --- a/src/mobilerun_sdk/types/webhooks/delivery_list_params.py +++ b/src/mobilerun_sdk/types/webhooks/delivery_list_params.py @@ -15,6 +15,9 @@ class DeliveryListParams(TypedDict, total=False): event_id: Annotated[str, PropertyInfo(alias="eventId")] """Exact text match against the originating event id.""" + kind: Literal["http", "integration"] + """Only include deliveries to endpoints of this kind.""" + page: int page_size: Annotated[int, PropertyInfo(alias="pageSize")] diff --git a/src/mobilerun_sdk/types/webhooks/delivery_list_response.py b/src/mobilerun_sdk/types/webhooks/delivery_list_response.py index 2839960a..31f60445 100644 --- a/src/mobilerun_sdk/types/webhooks/delivery_list_response.py +++ b/src/mobilerun_sdk/types/webhooks/delivery_list_response.py @@ -30,7 +30,16 @@ class Item(BaseModel): endpoint_id: str = FieldInfo(alias="endpointId") - endpoint_url: str = FieldInfo(alias="endpointUrl") + endpoint_kind: Literal["http", "integration"] = FieldInfo(alias="endpointKind") + + endpoint_label: Optional[str] = FieldInfo(alias="endpointLabel", default=None) + """Integration target label of the parent webhook (e.g. + + `#ops`); null for `http` webhooks. + """ + + endpoint_url: Optional[str] = FieldInfo(alias="endpointUrl", default=None) + """Delivery URL of the parent webhook; null for `integration` webhooks.""" event_id: str = FieldInfo(alias="eventId") diff --git a/src/mobilerun_sdk/types/webhooks/delivery_stats_params.py b/src/mobilerun_sdk/types/webhooks/delivery_stats_params.py index f9d570ab..1a7f4bd4 100644 --- a/src/mobilerun_sdk/types/webhooks/delivery_stats_params.py +++ b/src/mobilerun_sdk/types/webhooks/delivery_stats_params.py @@ -4,7 +4,7 @@ from typing import Union from datetime import datetime -from typing_extensions import Annotated, TypedDict +from typing_extensions import Literal, Annotated, TypedDict from ..._utils import PropertyInfo @@ -12,4 +12,7 @@ class DeliveryStatsParams(TypedDict, total=False): + kind: Literal["http", "integration"] + """Only include deliveries to endpoints of this kind.""" + since: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] diff --git a/src/mobilerun_sdk/types/webhooks/integration_list_response.py b/src/mobilerun_sdk/types/webhooks/integration_list_response.py new file mode 100644 index 00000000..6b07f1c9 --- /dev/null +++ b/src/mobilerun_sdk/types/webhooks/integration_list_response.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["IntegrationListResponse", "Data", "DataCapability"] + + +class DataCapability(BaseModel): + id: str + + revision: int + + +class Data(BaseModel): + capability: DataCapability + + connection: Literal["connected", "not_connected", "expired"] + """Connection state for your org. + + Only `connected` integrations can be used as a destination. + """ + + connect_key: str = FieldInfo(alias="connectKey") + """Opaque key for the Cloud connect dialog.""" + + deprecated: bool + """True for the previous revision during a switch; prefer the non-deprecated one.""" + + icon_url: Optional[str] = FieldInfo(alias="iconUrl", default=None) + """Server-resolved brand logo URL; null when unknown. + + Show this instead of deriving a logo from connectKey. + """ + + label: str + + supports_targets: bool = FieldInfo(alias="supportsTargets") + """True when targets (e.g. + + channels) can be listed via `GET /webhooks/integrations/{capabilityId}/targets`. + """ + + +class IntegrationListResponse(BaseModel): + data: List[Data] diff --git a/src/mobilerun_sdk/types/webhooks/integration_list_targets_params.py b/src/mobilerun_sdk/types/webhooks/integration_list_targets_params.py new file mode 100644 index 00000000..284db448 --- /dev/null +++ b/src/mobilerun_sdk/types/webhooks/integration_list_targets_params.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["IntegrationListTargetsParams"] + + +class IntegrationListTargetsParams(TypedDict, total=False): + revision: Required[int] diff --git a/src/mobilerun_sdk/types/webhooks/integration_list_targets_response.py b/src/mobilerun_sdk/types/webhooks/integration_list_targets_response.py new file mode 100644 index 00000000..e1c33356 --- /dev/null +++ b/src/mobilerun_sdk/types/webhooks/integration_list_targets_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["IntegrationListTargetsResponse", "Data"] + + +class Data(BaseModel): + args: Dict[str, object] + """Pass as `args` when creating an `integration` webhook.""" + + is_private: bool = FieldInfo(alias="isPrivate") + + label: str + + +class IntegrationListTargetsResponse(BaseModel): + data: List[Data] diff --git a/src/mobilerun_sdk/types/workflows/__init__.py b/src/mobilerun_sdk/types/workflows/__init__.py index 1de48081..f1180111 100644 --- a/src/mobilerun_sdk/types/workflows/__init__.py +++ b/src/mobilerun_sdk/types/workflows/__init__.py @@ -2,12 +2,15 @@ from __future__ import annotations +from .flow_run_params import FlowRunParams as FlowRunParams from .flow_list_params import FlowListParams as FlowListParams from .flow_clone_params import FlowCloneParams as FlowCloneParams +from .flow_run_response import FlowRunResponse as FlowRunResponse from .action_list_params import ActionListParams as ActionListParams from .flow_create_params import FlowCreateParams as FlowCreateParams from .flow_list_response import FlowListResponse as FlowListResponse from .flow_update_params import FlowUpdateParams as FlowUpdateParams +from .flow_verify_params import FlowVerifyParams as FlowVerifyParams from .event_ingest_params import EventIngestParams as EventIngestParams from .flow_clone_response import FlowCloneResponse as FlowCloneResponse from .flow_dry_run_params import FlowDryRunParams as FlowDryRunParams @@ -17,9 +20,12 @@ from .action_list_response import ActionListResponse as ActionListResponse from .action_update_params import ActionUpdateParams as ActionUpdateParams from .event_dry_run_params import EventDryRunParams as EventDryRunParams +from .flow_activate_params import FlowActivateParams as FlowActivateParams from .flow_create_response import FlowCreateResponse as FlowCreateResponse from .flow_delete_response import FlowDeleteResponse as FlowDeleteResponse from .flow_update_response import FlowUpdateResponse as FlowUpdateResponse +from .flow_validate_params import FlowValidateParams as FlowValidateParams +from .flow_verify_response import FlowVerifyResponse as FlowVerifyResponse from .event_ingest_response import EventIngestResponse as EventIngestResponse from .execution_list_params import ExecutionListParams as ExecutionListParams from .flow_dry_run_response import FlowDryRunResponse as FlowDryRunResponse @@ -32,8 +38,10 @@ from .action_delete_response import ActionDeleteResponse as ActionDeleteResponse from .action_update_response import ActionUpdateResponse as ActionUpdateResponse from .event_dry_run_response import EventDryRunResponse as EventDryRunResponse +from .flow_activate_response import FlowActivateResponse as FlowActivateResponse from .flow_capacity_response import FlowCapacityResponse as FlowCapacityResponse from .flow_retrieve_response import FlowRetrieveResponse as FlowRetrieveResponse +from .flow_validate_response import FlowValidateResponse as FlowValidateResponse from .timezone_list_response import TimezoneListResponse as TimezoneListResponse from .execution_list_response import ExecutionListResponse as ExecutionListResponse from .trigger_create_response import TriggerCreateResponse as TriggerCreateResponse @@ -47,5 +55,8 @@ from .execution_retrieve_response import ExecutionRetrieveResponse as ExecutionRetrieveResponse from .action_catalog_list_response import ActionCatalogListResponse as ActionCatalogListResponse from .execution_get_metrics_params import ExecutionGetMetricsParams as ExecutionGetMetricsParams +from .flow_template_context_params import FlowTemplateContextParams as FlowTemplateContextParams from .execution_get_metrics_response import ExecutionGetMetricsResponse as ExecutionGetMetricsResponse +from .flow_delivery_options_response import FlowDeliveryOptionsResponse as FlowDeliveryOptionsResponse +from .flow_template_context_response import FlowTemplateContextResponse as FlowTemplateContextResponse from .action_catalog_retrieve_response import ActionCatalogRetrieveResponse as ActionCatalogRetrieveResponse diff --git a/src/mobilerun_sdk/types/workflows/action_catalog_list_params.py b/src/mobilerun_sdk/types/workflows/action_catalog_list_params.py index 4e756a96..1e8cfead 100644 --- a/src/mobilerun_sdk/types/workflows/action_catalog_list_params.py +++ b/src/mobilerun_sdk/types/workflows/action_catalog_list_params.py @@ -14,4 +14,4 @@ class ActionCatalogListParams(TypedDict, total=False): page_size: Annotated[int, PropertyInfo(alias="pageSize")] - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] diff --git a/src/mobilerun_sdk/types/workflows/action_catalog_list_response.py b/src/mobilerun_sdk/types/workflows/action_catalog_list_response.py index 231b0240..5241bfd8 100644 --- a/src/mobilerun_sdk/types/workflows/action_catalog_list_response.py +++ b/src/mobilerun_sdk/types/workflows/action_catalog_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -14,7 +15,7 @@ class Item(BaseModel): id: str - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) description: Optional[str] = None @@ -22,9 +23,13 @@ class Item(BaseModel): name: str - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + provider: Literal["mobilerun", "oneDrive", "googleDrive"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] + + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + + output_schema: Optional[object] = FieldInfo(alias="outputSchema", default=None) params_schema: Optional[object] = FieldInfo(alias="paramsSchema", default=None) diff --git a/src/mobilerun_sdk/types/workflows/action_catalog_retrieve_response.py b/src/mobilerun_sdk/types/workflows/action_catalog_retrieve_response.py index 8769f574..36556c71 100644 --- a/src/mobilerun_sdk/types/workflows/action_catalog_retrieve_response.py +++ b/src/mobilerun_sdk/types/workflows/action_catalog_retrieve_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -13,7 +14,7 @@ class Data(BaseModel): id: str - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) description: Optional[str] = None @@ -21,9 +22,13 @@ class Data(BaseModel): name: str - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + provider: Literal["mobilerun", "oneDrive", "googleDrive"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] + + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + + output_schema: Optional[object] = FieldInfo(alias="outputSchema", default=None) params_schema: Optional[object] = FieldInfo(alias="paramsSchema", default=None) diff --git a/src/mobilerun_sdk/types/workflows/action_create_response.py b/src/mobilerun_sdk/types/workflows/action_create_response.py index 3a44b0a6..9b85e093 100644 --- a/src/mobilerun_sdk/types/workflows/action_create_response.py +++ b/src/mobilerun_sdk/types/workflows/action_create_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -15,7 +16,7 @@ class Data(BaseModel): catalog_entry_id: str = FieldInfo(alias="catalogEntryId") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -27,9 +28,9 @@ class Data(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/action_list_params.py b/src/mobilerun_sdk/types/workflows/action_list_params.py index 9873843d..a5c3ac22 100644 --- a/src/mobilerun_sdk/types/workflows/action_list_params.py +++ b/src/mobilerun_sdk/types/workflows/action_list_params.py @@ -20,4 +20,4 @@ class ActionListParams(TypedDict, total=False): search: str - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] diff --git a/src/mobilerun_sdk/types/workflows/action_list_response.py b/src/mobilerun_sdk/types/workflows/action_list_response.py index 9fc8ce86..d323af4f 100644 --- a/src/mobilerun_sdk/types/workflows/action_list_response.py +++ b/src/mobilerun_sdk/types/workflows/action_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -16,7 +17,7 @@ class Item(BaseModel): catalog_entry_id: str = FieldInfo(alias="catalogEntryId") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -28,9 +29,9 @@ class Item(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/action_retrieve_response.py b/src/mobilerun_sdk/types/workflows/action_retrieve_response.py index 61d45f65..23f16e01 100644 --- a/src/mobilerun_sdk/types/workflows/action_retrieve_response.py +++ b/src/mobilerun_sdk/types/workflows/action_retrieve_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -15,7 +16,7 @@ class Data(BaseModel): catalog_entry_id: str = FieldInfo(alias="catalogEntryId") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -27,9 +28,9 @@ class Data(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/action_update_response.py b/src/mobilerun_sdk/types/workflows/action_update_response.py index 1b5a53b4..14abf5ba 100644 --- a/src/mobilerun_sdk/types/workflows/action_update_response.py +++ b/src/mobilerun_sdk/types/workflows/action_update_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -15,7 +16,7 @@ class Data(BaseModel): catalog_entry_id: str = FieldInfo(alias="catalogEntryId") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -27,9 +28,9 @@ class Data(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/event_dry_run_response.py b/src/mobilerun_sdk/types/workflows/event_dry_run_response.py index e7bb6eee..d1f9567d 100644 --- a/src/mobilerun_sdk/types/workflows/event_dry_run_response.py +++ b/src/mobilerun_sdk/types/workflows/event_dry_run_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -13,9 +14,13 @@ "DataMatchedFlow", "DataMatchedFlowAction", "DataMatchedFlowFlow", + "DataMatchedFlowFlowDelivery", + "DataMatchedFlowFlowDeliveryRecording", "DataMatchedFlowFlowRecordingPolicy", "DataMatchedFlowGates", "DataMatchedFlowTrigger", + "DataMatchedFlowTriggerScheduleRule", + "DataMatchedFlowTriggerScheduleRuleJitter", "DataValidation", "DataValidationError", ] @@ -26,13 +31,15 @@ class DataMatchedFlowAction(BaseModel): flow_action_id: str = FieldInfo(alias="flowActionId") + key: str + method: str name: str recording_enabled: bool = FieldInfo(alias="recordingEnabled") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] children: Optional[List[object]] = None """ @@ -43,6 +50,20 @@ class DataMatchedFlowAction(BaseModel): params: Optional[Dict[str, object]] = None +class DataMatchedFlowFlowDeliveryRecording(BaseModel): + filename: str + + +class DataMatchedFlowFlowDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataMatchedFlowFlowDeliveryRecording] = None + + screenshots: Optional[object] = None + + class DataMatchedFlowFlowRecordingPolicy(BaseModel): mode: Literal["off", "flow", "selected_steps"] @@ -50,9 +71,9 @@ class DataMatchedFlowFlowRecordingPolicy(BaseModel): class DataMatchedFlowFlow(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -60,10 +81,12 @@ class DataMatchedFlowFlow(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataMatchedFlowFlowDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -76,13 +99,13 @@ class DataMatchedFlowFlow(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -114,13 +137,15 @@ class DataMatchedFlowFlow(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" @@ -138,12 +163,36 @@ class DataMatchedFlowGates(BaseModel): enabled: bool +class DataMatchedFlowTriggerScheduleRuleJitter(BaseModel): + """Optional per-occurrence random window around the nominal schedule time""" + + after_minutes: Optional[int] = FieldInfo(alias="afterMinutes", default=None) + + before_minutes: Optional[int] = FieldInfo(alias="beforeMinutes", default=None) + + +class DataMatchedFlowTriggerScheduleRule(BaseModel): + type: Literal["once", "cron", "recurring"] + + date_time: Optional[str] = FieldInfo(alias="dateTime", default=None) + """ISO 8601 datetime (for type=once)""" + + expression: Optional[str] = None + """Cron expression (for type=cron)""" + + jitter: Optional[DataMatchedFlowTriggerScheduleRuleJitter] = None + """Optional per-occurrence random window around the nominal schedule time""" + + rrule: Optional[str] = None + """RRULE string (for type=recurring)""" + + class DataMatchedFlowTrigger(BaseModel): id: str activation: Literal["event", "schedule", "custom"] - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -157,11 +206,11 @@ class DataMatchedFlowTrigger(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - schedule_rule: object = FieldInfo(alias="scheduleRule") + schedule_rule: DataMatchedFlowTriggerScheduleRule = FieldInfo(alias="scheduleRule") timezone: Optional[str] = None - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/execution_abort_response.py b/src/mobilerun_sdk/types/workflows/execution_abort_response.py index 8ef853a1..d8a9a024 100644 --- a/src/mobilerun_sdk/types/workflows/execution_abort_response.py +++ b/src/mobilerun_sdk/types/workflows/execution_abort_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -23,7 +24,7 @@ class Data(BaseModel): event_id: Optional[str] = FieldInfo(alias="eventId", default=None) - finished_at: Optional[str] = FieldInfo(alias="finishedAt", default=None) + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) flow_id: str = FieldInfo(alias="flowId") @@ -47,7 +48,7 @@ class Data(BaseModel): the recording failed to start. """ - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Optional[Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"]] = None diff --git a/src/mobilerun_sdk/types/workflows/execution_get_metrics_params.py b/src/mobilerun_sdk/types/workflows/execution_get_metrics_params.py index b5d8cdae..191726c8 100644 --- a/src/mobilerun_sdk/types/workflows/execution_get_metrics_params.py +++ b/src/mobilerun_sdk/types/workflows/execution_get_metrics_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Union +from datetime import datetime from typing_extensions import Annotated, TypedDict from ..._utils import PropertyInfo @@ -13,8 +14,8 @@ class ExecutionGetMetricsParams(TypedDict, total=False): flow_id: Annotated[str, PropertyInfo(alias="flowId")] - from_: Annotated[Optional[str], PropertyInfo(alias="from")] + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] - to: Optional[str] + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] trigger_id: Annotated[str, PropertyInfo(alias="triggerId")] diff --git a/src/mobilerun_sdk/types/workflows/execution_get_metrics_response.py b/src/mobilerun_sdk/types/workflows/execution_get_metrics_response.py index cb796ac4..56a6e502 100644 --- a/src/mobilerun_sdk/types/workflows/execution_get_metrics_response.py +++ b/src/mobilerun_sdk/types/workflows/execution_get_metrics_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from datetime import datetime from pydantic import Field as FieldInfo @@ -30,7 +31,7 @@ class Data(BaseModel): by_status: DataByStatus = FieldInfo(alias="byStatus") - last_execution_at: Optional[str] = FieldInfo(alias="lastExecutionAt", default=None) + last_execution_at: Optional[datetime] = FieldInfo(alias="lastExecutionAt", default=None) total: int diff --git a/src/mobilerun_sdk/types/workflows/execution_list_params.py b/src/mobilerun_sdk/types/workflows/execution_list_params.py index 83e83d88..31719969 100644 --- a/src/mobilerun_sdk/types/workflows/execution_list_params.py +++ b/src/mobilerun_sdk/types/workflows/execution_list_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Optional +from typing import Union +from datetime import datetime from typing_extensions import Literal, Annotated, TypedDict from ..._utils import PropertyInfo @@ -13,7 +14,7 @@ class ExecutionListParams(TypedDict, total=False): flow_id: Annotated[str, PropertyInfo(alias="flowId")] - from_: Annotated[Optional[str], PropertyInfo(alias="from")] + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] invocation_id: Annotated[str, PropertyInfo(alias="invocationId")] @@ -29,6 +30,6 @@ class ExecutionListParams(TypedDict, total=False): status: Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"] - to: Optional[str] + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] trigger_id: Annotated[str, PropertyInfo(alias="triggerId")] diff --git a/src/mobilerun_sdk/types/workflows/execution_list_response.py b/src/mobilerun_sdk/types/workflows/execution_list_response.py index f3d62b4c..15b4fcee 100644 --- a/src/mobilerun_sdk/types/workflows/execution_list_response.py +++ b/src/mobilerun_sdk/types/workflows/execution_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -32,13 +33,13 @@ class ItemRecording(BaseModel): scope: Literal["flow", "step"] - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Literal["starting", "recording", "stopping", "stopped", "failed"] step_index: int = FieldInfo(alias="stepIndex") - stopped_at: Optional[str] = FieldInfo(alias="stoppedAt", default=None) + stopped_at: Optional[datetime] = FieldInfo(alias="stoppedAt", default=None) class Item(BaseModel): @@ -56,7 +57,7 @@ class Item(BaseModel): event_id: Optional[str] = FieldInfo(alias="eventId", default=None) - finished_at: Optional[str] = FieldInfo(alias="finishedAt", default=None) + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) flow_id: str = FieldInfo(alias="flowId") @@ -86,7 +87,7 @@ class Item(BaseModel): Whole-flow recordings use -1 for every coordinate. """ - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Optional[Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"]] = None diff --git a/src/mobilerun_sdk/types/workflows/execution_retrieve_response.py b/src/mobilerun_sdk/types/workflows/execution_retrieve_response.py index 9af89a42..66e14c02 100644 --- a/src/mobilerun_sdk/types/workflows/execution_retrieve_response.py +++ b/src/mobilerun_sdk/types/workflows/execution_retrieve_response.py @@ -1,13 +1,45 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["ExecutionRetrieveResponse", "Data", "DataFile", "DataRecording"] +__all__ = [ + "ExecutionRetrieveResponse", + "Data", + "DataDelivery", + "DataFile", + "DataProgress", + "DataProgressStep", + "DataRecording", + "DataScreenshot", +] + + +class DataDelivery(BaseModel): + artifact: Literal["recording", "file", "screenshot"] + + destination: Literal["one_drive", "google_drive"] + + error_code: Optional[str] = FieldInfo(alias="errorCode", default=None) + + filename: str + + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) + + folder: Optional[str] = None + + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) + + status: Literal["waiting", "uploading", "succeeded", "failed", "unknown", "cancelled"] + + step_index: Optional[int] = FieldInfo(alias="stepIndex", default=None) + + web_url: Optional[str] = FieldInfo(alias="webUrl", default=None) class DataFile(BaseModel): @@ -20,6 +52,36 @@ class DataFile(BaseModel): size_bytes: int = FieldInfo(alias="sizeBytes") +class DataProgressStep(BaseModel): + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) + + index: int + + method: str + + name: str + + service: str + + session_id: Optional[str] = FieldInfo(alias="sessionId", default=None) + + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) + + status: Literal["pending", "running", "success", "failed", "skipped", "cancelled"] + + +class DataProgress(BaseModel): + """ + Live progress read from step_progress; null for runs started before this feature. + """ + + current_index: Optional[int] = FieldInfo(alias="currentIndex", default=None) + + steps: List[DataProgressStep] + + total: int + + class DataRecording(BaseModel): id: str @@ -41,13 +103,31 @@ class DataRecording(BaseModel): scope: Literal["flow", "step"] - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Literal["starting", "recording", "stopping", "stopped", "failed"] step_index: int = FieldInfo(alias="stepIndex") - stopped_at: Optional[str] = FieldInfo(alias="stoppedAt", default=None) + stopped_at: Optional[datetime] = FieldInfo(alias="stoppedAt", default=None) + + +class DataScreenshot(BaseModel): + id: str + + captured_at: Optional[datetime] = FieldInfo(alias="capturedAt", default=None) + + iteration_index: int = FieldInfo(alias="iterationIndex") + + mime_type: str = FieldInfo(alias="mimeType") + + seq: int + + source: Literal["task", "agent"] + + step_index: int = FieldInfo(alias="stepIndex") + + step_name: str = FieldInfo(alias="stepName") class Data(BaseModel): @@ -55,6 +135,13 @@ class Data(BaseModel): created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + deliveries: List[DataDelivery] + """ + OneDrive/Google Drive delivery lifecycle for this run's recording, file, and + screenshot uploads, ordered by startedAt. Empty when the flow has no delivery + configured. + """ + device_id: Optional[str] = FieldInfo(alias="deviceId", default=None) """Device this execution targets (the job's deviceId). @@ -72,7 +159,7 @@ class Data(BaseModel): the turn); derived server-side at read time. """ - finished_at: Optional[str] = FieldInfo(alias="finishedAt", default=None) + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) flow_id: str = FieldInfo(alias="flowId") @@ -87,6 +174,12 @@ class Data(BaseModel): kind: Literal["live", "dry_run", "verification"] + progress: Optional[DataProgress] = None + """ + Live progress read from step_progress; null for runs started before this + feature. + """ + recording_device_id: Optional[str] = FieldInfo(alias="recordingDeviceId", default=None) recording_id: Optional[str] = FieldInfo(alias="recordingId", default=None) @@ -102,7 +195,14 @@ class Data(BaseModel): Whole-flow recordings use -1 for every coordinate. """ - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + screenshots: List[DataScreenshot] + """Screenshots captured by tasks.run/agent.run steps, ordered by seq. + + Image bytes are never returned here — fetch a fresh signed URL via GET + /executions/{id}/screenshots/{screenshotId}. + """ + + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Optional[Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"]] = None diff --git a/src/mobilerun_sdk/types/workflows/executions/__init__.py b/src/mobilerun_sdk/types/workflows/executions/__init__.py new file mode 100644 index 00000000..f8ee8b14 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/executions/__init__.py @@ -0,0 +1,3 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations diff --git a/src/mobilerun_sdk/types/workflows/flow_activate_params.py b/src/mobilerun_sdk/types/workflows/flow_activate_params.py new file mode 100644 index 00000000..52fe51fd --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_activate_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FlowActivateParams"] + + +class FlowActivateParams(TypedDict, total=False): + verification_execution_id: Required[Annotated[str, PropertyInfo(alias="verificationExecutionId")]] diff --git a/src/mobilerun_sdk/types/workflows/flow_activate_response.py b/src/mobilerun_sdk/types/workflows/flow_activate_response.py new file mode 100644 index 00000000..24192bf4 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_activate_response.py @@ -0,0 +1,116 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowActivateResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None + + +class DataRecordingPolicy(BaseModel): + mode: Literal["off", "flow", "selected_steps"] + + +class Data(BaseModel): + id: str + + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) + + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) + + consecutive_failures: int = FieldInfo(alias="consecutiveFailures") + + cooldown_scope: Literal["flow", "device"] = FieldInfo(alias="cooldownScope") + + cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) + + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) + + created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + + delivery: Optional[DataDelivery] = None + + description: Optional[str] = None + + device_ids: List[str] = FieldInfo(alias="deviceIds") + + enabled: bool + """ + Compatibility projection of lifecycleStatus; true only when lifecycleStatus is + enabled. + """ + + health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") + + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) + + last_failure_code: Optional[ + Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] + ] = FieldInfo(alias="lastFailureCode", default=None) + + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) + + lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") + + name: str + + notify_on_failure: bool = FieldInfo(alias="notifyOnFailure") + + notify_on_success: bool = FieldInfo(alias="notifyOnSuccess") + + notify_webhook_id: Optional[str] = FieldInfo(alias="notifyWebhookId", default=None) + + owner_id: str = FieldInfo(alias="ownerId") + + recording_enabled: bool = FieldInfo(alias="recordingEnabled") + """ + Deprecated: use recordingPolicy.mode ("flow" = recordingEnabled=true, "off" = + recordingEnabled=false). + """ + + recording_policy: DataRecordingPolicy = FieldInfo(alias="recordingPolicy") + + self_healing_enabled: bool = FieldInfo(alias="selfHealingEnabled") + + self_healing_max_attempts: int = FieldInfo(alias="selfHealingMaxAttempts") + + status: Literal["healthy", "failing", "blocked"] + + template_resolution_version: int = FieldInfo(alias="templateResolutionVersion") + """Template-resolver semantics this flow runs under (MVA-23). + + 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. + """ + + trigger_id: str = FieldInfo(alias="triggerId") + + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) + + user_id: str = FieldInfo(alias="userId") + """Deprecated: use ownerId (tenancy) / createdBy (actor).""" + + +class FlowActivateResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flow_clone_response.py b/src/mobilerun_sdk/types/workflows/flow_clone_response.py index e08b73dd..e31e4a31 100644 --- a/src/mobilerun_sdk/types/workflows/flow_clone_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_clone_response.py @@ -1,13 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["FlowCloneResponse", "Data", "DataRecordingPolicy"] +__all__ = ["FlowCloneResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None class DataRecordingPolicy(BaseModel): @@ -17,9 +32,9 @@ class DataRecordingPolicy(BaseModel): class Data(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -27,10 +42,12 @@ class Data(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -43,13 +60,13 @@ class Data(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -81,13 +98,15 @@ class Data(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_create_params.py b/src/mobilerun_sdk/types/workflows/flow_create_params.py index 1a292cf5..796dbd21 100644 --- a/src/mobilerun_sdk/types/workflows/flow_create_params.py +++ b/src/mobilerun_sdk/types/workflows/flow_create_params.py @@ -8,7 +8,16 @@ from ..._types import SequenceNotStr from ..._utils import PropertyInfo -__all__ = ["FlowCreateParams", "Action", "ActionChild", "ActionChildOverrides", "ActionOverrides", "RecordingPolicy"] +__all__ = [ + "FlowCreateParams", + "Action", + "ActionChild", + "ActionChildOverrides", + "ActionOverrides", + "Delivery", + "DeliveryRecording", + "RecordingPolicy", +] class FlowCreateParams(TypedDict, total=False): @@ -22,6 +31,8 @@ class FlowCreateParams(TypedDict, total=False): cooldown_seconds: Annotated[Optional[int], PropertyInfo(alias="cooldownSeconds")] + delivery: Delivery + description: str device_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="deviceIds")] @@ -37,10 +48,6 @@ class FlowCreateParams(TypedDict, total=False): notify_webhook_id: Annotated[Optional[str], PropertyInfo(alias="notifyWebhookId")] recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] - """Deprecated compatibility field. - - true maps to recordingPolicy.mode="flow"; false maps to "off". - """ recording_policy: Annotated[RecordingPolicy, PropertyInfo(alias="recordingPolicy")] @@ -60,6 +67,15 @@ class ActionChild(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[ActionChildOverrides] @@ -80,6 +96,15 @@ class Action(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[ActionOverrides] @@ -87,5 +112,19 @@ class Action(TypedDict, total=False): recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] +class DeliveryRecording(TypedDict, total=False): + filename: Required[str] + + +class Delivery(TypedDict, total=False): + destination: Required[Literal["one_drive", "google_drive"]] + + folder: str + + recording: DeliveryRecording + + screenshots: object + + class RecordingPolicy(TypedDict, total=False): mode: Required[Literal["off", "flow", "selected_steps"]] diff --git a/src/mobilerun_sdk/types/workflows/flow_create_response.py b/src/mobilerun_sdk/types/workflows/flow_create_response.py index d4129117..b869c678 100644 --- a/src/mobilerun_sdk/types/workflows/flow_create_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_create_response.py @@ -1,13 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["FlowCreateResponse", "Data", "DataRecordingPolicy"] +__all__ = ["FlowCreateResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None class DataRecordingPolicy(BaseModel): @@ -17,9 +32,9 @@ class DataRecordingPolicy(BaseModel): class Data(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -27,10 +42,12 @@ class Data(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -43,13 +60,13 @@ class Data(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -81,13 +98,15 @@ class Data(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_delivery_options_response.py b/src/mobilerun_sdk/types/workflows/flow_delivery_options_response.py new file mode 100644 index 00000000..30605f08 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_delivery_options_response.py @@ -0,0 +1,45 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowDeliveryOptionsResponse", "Data", "DataDestination", "DataDirectUploadStep"] + + +class DataDestination(BaseModel): + connection: Literal["connected", "not_connected", "expired", "unknown"] + + connect_key: str = FieldInfo(alias="connectKey") + + icon_url: Optional[str] = FieldInfo(alias="iconUrl", default=None) + + key: Literal["one_drive", "google_drive"] + + label: str + + +class DataDirectUploadStep(BaseModel): + flow_action_id: str = FieldInfo(alias="flowActionId") + + name: str + + +class Data(BaseModel): + can_deliver_recording: bool = FieldInfo(alias="canDeliverRecording") + + destinations: List[DataDestination] + """Delivery destinations in registry order.""" + + direct_upload_step: Optional[DataDirectUploadStep] = FieldInfo(alias="directUploadStep", default=None) + + has_files_upload: bool = FieldInfo(alias="hasFilesUpload") + + recording_mode: Literal["off", "flow", "selected_steps"] = FieldInfo(alias="recordingMode") + + +class FlowDeliveryOptionsResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flow_dry_run_response.py b/src/mobilerun_sdk/types/workflows/flow_dry_run_response.py index 5592add2..18419570 100644 --- a/src/mobilerun_sdk/types/workflows/flow_dry_run_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_dry_run_response.py @@ -15,13 +15,15 @@ class DataAction(BaseModel): flow_action_id: str = FieldInfo(alias="flowActionId") + key: str + method: str name: str recording_enabled: bool = FieldInfo(alias="recordingEnabled") - service: Literal["tasks_api", "devices_api", "agents_api", "webhooks"] + service: Literal["tasks_api", "devices_api", "agents_api", "webhooks", "integrations_api"] children: Optional[List[object]] = None """ diff --git a/src/mobilerun_sdk/types/workflows/flow_list_repairs_response.py b/src/mobilerun_sdk/types/workflows/flow_list_repairs_response.py index df02dcc0..5f87fdf5 100644 --- a/src/mobilerun_sdk/types/workflows/flow_list_repairs_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_list_repairs_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -29,7 +30,7 @@ class Data(BaseModel): chat_session_id: Optional[str] = FieldInfo(alias="chatSessionId", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) device_id: Optional[str] = FieldInfo(alias="deviceId", default=None) @@ -39,7 +40,7 @@ class Data(BaseModel): failed_step_index: int = FieldInfo(alias="failedStepIndex") - finished_at: Optional[str] = FieldInfo(alias="finishedAt", default=None) + finished_at: Optional[datetime] = FieldInfo(alias="finishedAt", default=None) flow_id: str = FieldInfo(alias="flowId") @@ -49,11 +50,11 @@ class Data(BaseModel): source_execution_id: str = FieldInfo(alias="sourceExecutionId") - started_at: Optional[str] = FieldInfo(alias="startedAt", default=None) + started_at: Optional[datetime] = FieldInfo(alias="startedAt", default=None) status: Literal["pending", "running", "canary", "promoting", "repaired", "failed", "escalated"] - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) verdict: Optional[DataVerdict] = None diff --git a/src/mobilerun_sdk/types/workflows/flow_list_response.py b/src/mobilerun_sdk/types/workflows/flow_list_response.py index 01705f9f..3f53b1be 100644 --- a/src/mobilerun_sdk/types/workflows/flow_list_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -8,7 +9,21 @@ from ..._models import BaseModel from ..shared.pagination import Pagination -__all__ = ["FlowListResponse", "Item", "ItemRecordingPolicy"] +__all__ = ["FlowListResponse", "Item", "ItemDelivery", "ItemDeliveryRecording", "ItemRecordingPolicy"] + + +class ItemDeliveryRecording(BaseModel): + filename: str + + +class ItemDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[ItemDeliveryRecording] = None + + screenshots: Optional[object] = None class ItemRecordingPolicy(BaseModel): @@ -18,9 +33,9 @@ class ItemRecordingPolicy(BaseModel): class Item(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -28,10 +43,12 @@ class Item(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[ItemDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -44,13 +61,13 @@ class Item(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -82,13 +99,15 @@ class Item(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_retrieve_response.py b/src/mobilerun_sdk/types/workflows/flow_retrieve_response.py index b4cd5806..ff8b5162 100644 --- a/src/mobilerun_sdk/types/workflows/flow_retrieve_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_retrieve_response.py @@ -1,13 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["FlowRetrieveResponse", "Data", "DataRecordingPolicy"] +__all__ = ["FlowRetrieveResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None class DataRecordingPolicy(BaseModel): @@ -17,9 +32,9 @@ class DataRecordingPolicy(BaseModel): class Data(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -27,10 +42,12 @@ class Data(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -43,13 +60,13 @@ class Data(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -81,13 +98,15 @@ class Data(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_run_params.py b/src/mobilerun_sdk/types/workflows/flow_run_params.py new file mode 100644 index 00000000..9ca18caa --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_run_params.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import TypedDict + +__all__ = ["FlowRunParams"] + + +class FlowRunParams(TypedDict, total=False): + payload: Dict[str, object] diff --git a/src/mobilerun_sdk/types/workflows/flow_run_response.py b/src/mobilerun_sdk/types/workflows/flow_run_response.py new file mode 100644 index 00000000..54c1c961 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_run_response.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowRunResponse", "Data"] + + +class Data(BaseModel): + enqueued_count: int = FieldInfo(alias="enqueuedCount") + + execution_ids: List[str] = FieldInfo(alias="executionIds") + """Ids of the executions this run created. + + An idempotent replay returns the ids of the original run. + """ + + +class FlowRunResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flow_template_context_params.py b/src/mobilerun_sdk/types/workflows/flow_template_context_params.py new file mode 100644 index 00000000..4637bd9b --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_template_context_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FlowTemplateContextParams"] + + +class FlowTemplateContextParams(TypedDict, total=False): + template_resolution_version: Annotated[int, PropertyInfo(alias="templateResolutionVersion")] diff --git a/src/mobilerun_sdk/types/workflows/flow_template_context_response.py b/src/mobilerun_sdk/types/workflows/flow_template_context_response.py new file mode 100644 index 00000000..c02bf7a7 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_template_context_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowTemplateContextResponse", "Data", "DataLoop", "DataRoot", "DataStepReference"] + + +class DataLoop(BaseModel): + context: List[str] + + item_variable_default: str = FieldInfo(alias="itemVariableDefault") + + name: str + + +class DataRoot(BaseModel): + description: str + + name: str + + shape: Optional[object] = None + + +class DataStepReference(BaseModel): + note: str + + pattern: str + + key_rule: Optional[str] = FieldInfo(alias="keyRule", default=None) + + slug_rule: Optional[str] = FieldInfo(alias="slugRule", default=None) + + +class Data(BaseModel): + grammar: str + + loop: DataLoop + + payload_aliases: List[str] = FieldInfo(alias="payloadAliases") + + roots: List[DataRoot] + + step_reference: DataStepReference = FieldInfo(alias="stepReference") + + template_resolution_version: int = FieldInfo(alias="templateResolutionVersion") + + +class FlowTemplateContextResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flow_unblock_response.py b/src/mobilerun_sdk/types/workflows/flow_unblock_response.py index d71392de..e0afe210 100644 --- a/src/mobilerun_sdk/types/workflows/flow_unblock_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_unblock_response.py @@ -1,13 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["FlowUnblockResponse", "Data", "DataRecordingPolicy"] +__all__ = ["FlowUnblockResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None class DataRecordingPolicy(BaseModel): @@ -17,9 +32,9 @@ class DataRecordingPolicy(BaseModel): class Data(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -27,10 +42,12 @@ class Data(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -43,13 +60,13 @@ class Data(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -81,13 +98,15 @@ class Data(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_update_params.py b/src/mobilerun_sdk/types/workflows/flow_update_params.py index 9f32e4bb..7d925975 100644 --- a/src/mobilerun_sdk/types/workflows/flow_update_params.py +++ b/src/mobilerun_sdk/types/workflows/flow_update_params.py @@ -8,7 +8,7 @@ from ..._types import SequenceNotStr from ..._utils import PropertyInfo -__all__ = ["FlowUpdateParams", "RecordingPolicy"] +__all__ = ["FlowUpdateParams", "Delivery", "DeliveryRecording", "RecordingPolicy"] class FlowUpdateParams(TypedDict, total=False): @@ -16,6 +16,8 @@ class FlowUpdateParams(TypedDict, total=False): cooldown_seconds: Annotated[Optional[int], PropertyInfo(alias="cooldownSeconds")] + delivery: Optional[Delivery] + description: str device_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="deviceIds")] @@ -50,5 +52,19 @@ class FlowUpdateParams(TypedDict, total=False): trigger_id: Annotated[str, PropertyInfo(alias="triggerId")] +class DeliveryRecording(TypedDict, total=False): + filename: Required[str] + + +class Delivery(TypedDict, total=False): + destination: Required[Literal["one_drive", "google_drive"]] + + folder: str + + recording: DeliveryRecording + + screenshots: object + + class RecordingPolicy(TypedDict, total=False): mode: Required[Literal["off", "flow", "selected_steps"]] diff --git a/src/mobilerun_sdk/types/workflows/flow_update_response.py b/src/mobilerun_sdk/types/workflows/flow_update_response.py index 511e934a..241412cf 100644 --- a/src/mobilerun_sdk/types/workflows/flow_update_response.py +++ b/src/mobilerun_sdk/types/workflows/flow_update_response.py @@ -1,13 +1,28 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["FlowUpdateResponse", "Data", "DataRecordingPolicy"] +__all__ = ["FlowUpdateResponse", "Data", "DataDelivery", "DataDeliveryRecording", "DataRecordingPolicy"] + + +class DataDeliveryRecording(BaseModel): + filename: str + + +class DataDelivery(BaseModel): + destination: Literal["one_drive", "google_drive"] + + folder: Optional[str] = None + + recording: Optional[DataDeliveryRecording] = None + + screenshots: Optional[object] = None class DataRecordingPolicy(BaseModel): @@ -17,9 +32,9 @@ class DataRecordingPolicy(BaseModel): class Data(BaseModel): id: str - archived_at: Optional[str] = FieldInfo(alias="archivedAt", default=None) + archived_at: Optional[datetime] = FieldInfo(alias="archivedAt", default=None) - blocked_at: Optional[str] = FieldInfo(alias="blockedAt", default=None) + blocked_at: Optional[datetime] = FieldInfo(alias="blockedAt", default=None) consecutive_failures: int = FieldInfo(alias="consecutiveFailures") @@ -27,10 +42,12 @@ class Data(BaseModel): cooldown_seconds: Optional[int] = FieldInfo(alias="cooldownSeconds", default=None) - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) + delivery: Optional[DataDelivery] = None + description: Optional[str] = None device_ids: List[str] = FieldInfo(alias="deviceIds") @@ -43,13 +60,13 @@ class Data(BaseModel): health_monitoring_enabled: bool = FieldInfo(alias="healthMonitoringEnabled") - last_failure_at: Optional[str] = FieldInfo(alias="lastFailureAt", default=None) + last_failure_at: Optional[datetime] = FieldInfo(alias="lastFailureAt", default=None) last_failure_code: Optional[ Literal["device_not_found", "permission_denied", "client_error", "transient", "logic", "invalid_config"] ] = FieldInfo(alias="lastFailureCode", default=None) - last_triggered_at: Optional[str] = FieldInfo(alias="lastTriggeredAt", default=None) + last_triggered_at: Optional[datetime] = FieldInfo(alias="lastTriggeredAt", default=None) lifecycle_status: Literal["enabled", "disabled", "archived"] = FieldInfo(alias="lifecycleStatus") @@ -81,13 +98,15 @@ class Data(BaseModel): """Template-resolver semantics this flow runs under (MVA-23). 1 = legacy (missing/forbidden/null all resolve to ''). 2 = typed - (missing/forbidden throw, a whole-token null stays JSON null). Existing flows - stay 1; new flows default to 2. + (missing/forbidden throw, a whole-token null stays JSON null). 3 = typed, + key-based (steps are addressed as {{steps....}} instead of by name; + trigger.payload is not available). Existing flows stay 1; new flows default to + the current version. """ trigger_id: str = FieldInfo(alias="triggerId") - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/flow_validate_params.py b/src/mobilerun_sdk/types/workflows/flow_validate_params.py new file mode 100644 index 00000000..ea9d82ea --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_validate_params.py @@ -0,0 +1,174 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Iterable, Optional +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from ..._types import SequenceNotStr +from ..._utils import PropertyInfo + +__all__ = [ + "FlowValidateParams", + "Action", + "ActionFlowActionInput", + "ActionFlowActionInputChild", + "ActionFlowActionInputChildOverrides", + "ActionFlowActionInputOverrides", + "ActionFlowDraftActionInput", + "ActionFlowDraftActionInputDraft", + "ActionFlowDraftActionInputOverrides", + "Delivery", + "DeliveryRecording", + "RecordingPolicy", +] + + +class FlowValidateParams(TypedDict, total=False): + actions: Required[Iterable[Action]] + + name: Required[str] + + cooldown_scope: Annotated[Literal["flow", "device"], PropertyInfo(alias="cooldownScope")] + + cooldown_seconds: Annotated[Optional[int], PropertyInfo(alias="cooldownSeconds")] + + delivery: Delivery + + description: str + + device_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="deviceIds")] + + enabled: bool + + health_monitoring_enabled: Annotated[bool, PropertyInfo(alias="healthMonitoringEnabled")] + + notify_on_failure: Annotated[bool, PropertyInfo(alias="notifyOnFailure")] + + notify_on_success: Annotated[bool, PropertyInfo(alias="notifyOnSuccess")] + + notify_webhook_id: Annotated[Optional[str], PropertyInfo(alias="notifyWebhookId")] + + recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] + + recording_policy: Annotated[RecordingPolicy, PropertyInfo(alias="recordingPolicy")] + + self_healing_enabled: Annotated[bool, PropertyInfo(alias="selfHealingEnabled")] + + self_healing_max_attempts: Annotated[int, PropertyInfo(alias="selfHealingMaxAttempts")] + + trigger_id: Annotated[str, PropertyInfo(alias="triggerId")] + + +class ActionFlowActionInputChildOverrides(TypedDict, total=False): + params: Dict[str, object] + + +class ActionFlowActionInputChild(TypedDict, total=False): + action_id: Required[Annotated[str, PropertyInfo(alias="actionId")]] + + position: Required[int] + + continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] + + overrides: Optional[ActionFlowActionInputChildOverrides] + + recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] + + +class ActionFlowActionInputOverrides(TypedDict, total=False): + params: Dict[str, object] + + +class ActionFlowActionInput(TypedDict, total=False): + action_id: Required[Annotated[str, PropertyInfo(alias="actionId")]] + + position: Required[int] + + children: Iterable[ActionFlowActionInputChild] + + continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] + + overrides: Optional[ActionFlowActionInputOverrides] + + recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] + + +class ActionFlowDraftActionInputDraft(TypedDict, total=False): + method: Required[str] + + name: Required[str] + + service: Required[str] + + params: Dict[str, object] + + +class ActionFlowDraftActionInputOverrides(TypedDict, total=False): + params: Dict[str, object] + + +class ActionFlowDraftActionInput(TypedDict, total=False): + draft: Required[ActionFlowDraftActionInputDraft] + + position: Required[int] + + continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] + + overrides: Optional[ActionFlowDraftActionInputOverrides] + + recording_enabled: Annotated[bool, PropertyInfo(alias="recordingEnabled")] + + +Action: TypeAlias = Union[ActionFlowActionInput, ActionFlowDraftActionInput] + + +class DeliveryRecording(TypedDict, total=False): + filename: Required[str] + + +class Delivery(TypedDict, total=False): + destination: Required[Literal["one_drive", "google_drive"]] + + folder: str + + recording: DeliveryRecording + + screenshots: object + + +class RecordingPolicy(TypedDict, total=False): + mode: Required[Literal["off", "flow", "selected_steps"]] diff --git a/src/mobilerun_sdk/types/workflows/flow_validate_response.py b/src/mobilerun_sdk/types/workflows/flow_validate_response.py new file mode 100644 index 00000000..f634c1e8 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_validate_response.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowValidateResponse", "Data", "DataError", "DataWarning"] + + +class DataError(BaseModel): + code: str + + field: str + + message: str + + known_steps: Optional[List[str]] = FieldInfo(alias="knownSteps", default=None) + + reference: Optional[str] = None + + +class DataWarning(BaseModel): + code: str + + message: str + + connect_key: Optional[str] = FieldInfo(alias="connectKey", default=None) + + destination: Optional[str] = None + + +class Data(BaseModel): + errors: List[DataError] + + valid: bool + + warnings: List[DataWarning] + + +class FlowValidateResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flow_verify_params.py b/src/mobilerun_sdk/types/workflows/flow_verify_params.py new file mode 100644 index 00000000..1225218f --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_verify_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo + +__all__ = ["FlowVerifyParams"] + + +class FlowVerifyParams(TypedDict, total=False): + invocation_id: Required[Annotated[str, PropertyInfo(alias="invocationId")]] + """Client-supplied idempotency key. + + A repeat request for the same (flow, invocationId) returns the existing + verification run (`deduplicated: true`, HTTP 200) instead of enqueuing another. + """ + + device_id: Annotated[str, PropertyInfo(alias="deviceId")] + """Device to run the verification on. + + Must be one the flow is bound to. Optional only when the flow is bound to + exactly one device (that device is used); otherwise required. + """ + + payload: Dict[str, object] diff --git a/src/mobilerun_sdk/types/workflows/flow_verify_response.py b/src/mobilerun_sdk/types/workflows/flow_verify_response.py new file mode 100644 index 00000000..36812f21 --- /dev/null +++ b/src/mobilerun_sdk/types/workflows/flow_verify_response.py @@ -0,0 +1,40 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["FlowVerifyResponse", "Data"] + + +class Data(BaseModel): + deduplicated: bool + """ + True when this run already existed for the (flow, invocationId) key (HTTP 200); + false for a newly enqueued run (HTTP 202). + """ + + device_id: Optional[str] = FieldInfo(alias="deviceId", default=None) + """ + Device the verification runs on; null for a device-free verification (a 0-device + flow whose actions are all device-free allowlisted). + """ + + execution_id: str = FieldInfo(alias="executionId") + + flow_id: str = FieldInfo(alias="flowId") + + invocation_id: str = FieldInfo(alias="invocationId") + + kind: Literal["verification"] + + status: Literal["pending", "running", "success", "failed", "cancelled", "skipped", "invalid"] + + trigger_id: str = FieldInfo(alias="triggerId") + + +class FlowVerifyResponse(BaseModel): + data: Data diff --git a/src/mobilerun_sdk/types/workflows/flows/action_add_params.py b/src/mobilerun_sdk/types/workflows/flows/action_add_params.py index 88af2ccf..605e1be1 100644 --- a/src/mobilerun_sdk/types/workflows/flows/action_add_params.py +++ b/src/mobilerun_sdk/types/workflows/flows/action_add_params.py @@ -19,6 +19,15 @@ class ActionAddParams(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[Overrides] @@ -39,6 +48,15 @@ class Child(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[ChildOverrides] diff --git a/src/mobilerun_sdk/types/workflows/flows/action_add_response.py b/src/mobilerun_sdk/types/workflows/flows/action_add_response.py index 420a30e5..7d224757 100644 --- a/src/mobilerun_sdk/types/workflows/flows/action_add_response.py +++ b/src/mobilerun_sdk/types/workflows/flows/action_add_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime from pydantic import Field as FieldInfo @@ -20,10 +21,12 @@ class Data(BaseModel): continue_on_error: bool = FieldInfo(alias="continueOnError") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) flow_id: str = FieldInfo(alias="flowId") + key: str + name_override: Optional[str] = FieldInfo(alias="nameOverride", default=None) overrides: Optional[DataOverrides] = None diff --git a/src/mobilerun_sdk/types/workflows/flows/action_list_response.py b/src/mobilerun_sdk/types/workflows/flows/action_list_response.py index 1ff8dd43..a2339928 100644 --- a/src/mobilerun_sdk/types/workflows/flows/action_list_response.py +++ b/src/mobilerun_sdk/types/workflows/flows/action_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from datetime import datetime from pydantic import Field as FieldInfo @@ -20,10 +21,12 @@ class Data(BaseModel): continue_on_error: bool = FieldInfo(alias="continueOnError") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) flow_id: str = FieldInfo(alias="flowId") + key: str + name_override: Optional[str] = FieldInfo(alias="nameOverride", default=None) overrides: Optional[DataOverrides] = None diff --git a/src/mobilerun_sdk/types/workflows/flows/action_replace_params.py b/src/mobilerun_sdk/types/workflows/flows/action_replace_params.py index fd89af25..dda425a0 100644 --- a/src/mobilerun_sdk/types/workflows/flows/action_replace_params.py +++ b/src/mobilerun_sdk/types/workflows/flows/action_replace_params.py @@ -25,6 +25,15 @@ class ActionChild(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[ActionChildOverrides] @@ -45,6 +54,15 @@ class Action(TypedDict, total=False): continue_on_error: Annotated[bool, PropertyInfo(alias="continueOnError")] + key: str + """ + Stable identifier used by template resolution v2+ to address this step as + {{key.field}}. Omitted keys are derived from the step name whenever a flow is + created. For template resolution v3, replacing an existing flow's full action + tree requires every step to carry an explicit key — a missing key is rejected + there, not derived, so a name change can never silently move a step's key. + """ + name_override: Annotated[str, PropertyInfo(alias="nameOverride")] overrides: Optional[ActionOverrides] diff --git a/src/mobilerun_sdk/types/workflows/flows/action_replace_response.py b/src/mobilerun_sdk/types/workflows/flows/action_replace_response.py index a9939a84..fda0f098 100644 --- a/src/mobilerun_sdk/types/workflows/flows/action_replace_response.py +++ b/src/mobilerun_sdk/types/workflows/flows/action_replace_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from datetime import datetime from pydantic import Field as FieldInfo @@ -20,10 +21,12 @@ class Data(BaseModel): continue_on_error: bool = FieldInfo(alias="continueOnError") - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) flow_id: str = FieldInfo(alias="flowId") + key: str + name_override: Optional[str] = FieldInfo(alias="nameOverride", default=None) overrides: Optional[DataOverrides] = None diff --git a/src/mobilerun_sdk/types/workflows/trigger_create_response.py b/src/mobilerun_sdk/types/workflows/trigger_create_response.py index 4d903395..830a3dde 100644 --- a/src/mobilerun_sdk/types/workflows/trigger_create_response.py +++ b/src/mobilerun_sdk/types/workflows/trigger_create_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -39,7 +40,7 @@ class Data(BaseModel): activation: Literal["event", "schedule", "custom"] - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -57,7 +58,7 @@ class Data(BaseModel): timezone: Optional[str] = None - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/trigger_fire_params.py b/src/mobilerun_sdk/types/workflows/trigger_fire_params.py index 1a1f266d..03847b71 100644 --- a/src/mobilerun_sdk/types/workflows/trigger_fire_params.py +++ b/src/mobilerun_sdk/types/workflows/trigger_fire_params.py @@ -3,9 +3,7 @@ from __future__ import annotations from typing import Dict -from typing_extensions import Required, Annotated, TypedDict - -from ..._utils import PropertyInfo +from typing_extensions import Required, TypedDict __all__ = ["TriggerFireParams"] @@ -17,11 +15,3 @@ class TriggerFireParams(TypedDict, total=False): Validated against the trigger's customPayloadSchema when one is configured; otherwise only "must be a JSON object" is enforced. """ - - invocation_id: Annotated[str, PropertyInfo(alias="invocationId")] - """Optional client-supplied idempotency key. - - When provided, a flow that already has an execution for this (flow, - invocationId) is skipped and `deduplicated` is true. When omitted a fresh - server-side id is generated (no dedup). - """ diff --git a/src/mobilerun_sdk/types/workflows/trigger_list_response.py b/src/mobilerun_sdk/types/workflows/trigger_list_response.py index 134ed792..0cf27356 100644 --- a/src/mobilerun_sdk/types/workflows/trigger_list_response.py +++ b/src/mobilerun_sdk/types/workflows/trigger_list_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -8,7 +9,31 @@ from ..._models import BaseModel from ..shared.pagination import Pagination -__all__ = ["TriggerListResponse", "Item"] +__all__ = ["TriggerListResponse", "Item", "ItemScheduleRule", "ItemScheduleRuleJitter"] + + +class ItemScheduleRuleJitter(BaseModel): + """Optional per-occurrence random window around the nominal schedule time""" + + after_minutes: Optional[int] = FieldInfo(alias="afterMinutes", default=None) + + before_minutes: Optional[int] = FieldInfo(alias="beforeMinutes", default=None) + + +class ItemScheduleRule(BaseModel): + type: Literal["once", "cron", "recurring"] + + date_time: Optional[str] = FieldInfo(alias="dateTime", default=None) + """ISO 8601 datetime (for type=once)""" + + expression: Optional[str] = None + """Cron expression (for type=cron)""" + + jitter: Optional[ItemScheduleRuleJitter] = None + """Optional per-occurrence random window around the nominal schedule time""" + + rrule: Optional[str] = None + """RRULE string (for type=recurring)""" class Item(BaseModel): @@ -16,7 +41,7 @@ class Item(BaseModel): activation: Literal["event", "schedule", "custom"] - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -30,11 +55,11 @@ class Item(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - schedule_rule: object = FieldInfo(alias="scheduleRule") + schedule_rule: ItemScheduleRule = FieldInfo(alias="scheduleRule") timezone: Optional[str] = None - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/trigger_retrieve_response.py b/src/mobilerun_sdk/types/workflows/trigger_retrieve_response.py index fad954ba..0ee64198 100644 --- a/src/mobilerun_sdk/types/workflows/trigger_retrieve_response.py +++ b/src/mobilerun_sdk/types/workflows/trigger_retrieve_response.py @@ -1,13 +1,38 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["TriggerRetrieveResponse", "Data"] +__all__ = ["TriggerRetrieveResponse", "Data", "DataScheduleRule", "DataScheduleRuleJitter"] + + +class DataScheduleRuleJitter(BaseModel): + """Optional per-occurrence random window around the nominal schedule time""" + + after_minutes: Optional[int] = FieldInfo(alias="afterMinutes", default=None) + + before_minutes: Optional[int] = FieldInfo(alias="beforeMinutes", default=None) + + +class DataScheduleRule(BaseModel): + type: Literal["once", "cron", "recurring"] + + date_time: Optional[str] = FieldInfo(alias="dateTime", default=None) + """ISO 8601 datetime (for type=once)""" + + expression: Optional[str] = None + """Cron expression (for type=cron)""" + + jitter: Optional[DataScheduleRuleJitter] = None + """Optional per-occurrence random window around the nominal schedule time""" + + rrule: Optional[str] = None + """RRULE string (for type=recurring)""" class Data(BaseModel): @@ -15,7 +40,7 @@ class Data(BaseModel): activation: Literal["event", "schedule", "custom"] - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -29,11 +54,11 @@ class Data(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - schedule_rule: object = FieldInfo(alias="scheduleRule") + schedule_rule: DataScheduleRule = FieldInfo(alias="scheduleRule") timezone: Optional[str] = None - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/src/mobilerun_sdk/types/workflows/trigger_update_response.py b/src/mobilerun_sdk/types/workflows/trigger_update_response.py index 5a6ea712..5ec9bdcd 100644 --- a/src/mobilerun_sdk/types/workflows/trigger_update_response.py +++ b/src/mobilerun_sdk/types/workflows/trigger_update_response.py @@ -1,13 +1,38 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime from typing_extensions import Literal from pydantic import Field as FieldInfo from ..._models import BaseModel -__all__ = ["TriggerUpdateResponse", "Data"] +__all__ = ["TriggerUpdateResponse", "Data", "DataScheduleRule", "DataScheduleRuleJitter"] + + +class DataScheduleRuleJitter(BaseModel): + """Optional per-occurrence random window around the nominal schedule time""" + + after_minutes: Optional[int] = FieldInfo(alias="afterMinutes", default=None) + + before_minutes: Optional[int] = FieldInfo(alias="beforeMinutes", default=None) + + +class DataScheduleRule(BaseModel): + type: Literal["once", "cron", "recurring"] + + date_time: Optional[str] = FieldInfo(alias="dateTime", default=None) + """ISO 8601 datetime (for type=once)""" + + expression: Optional[str] = None + """Cron expression (for type=cron)""" + + jitter: Optional[DataScheduleRuleJitter] = None + """Optional per-occurrence random window around the nominal schedule time""" + + rrule: Optional[str] = None + """RRULE string (for type=recurring)""" class Data(BaseModel): @@ -15,7 +40,7 @@ class Data(BaseModel): activation: Literal["event", "schedule", "custom"] - created_at: Optional[str] = FieldInfo(alias="createdAt", default=None) + created_at: Optional[datetime] = FieldInfo(alias="createdAt", default=None) created_by: Optional[str] = FieldInfo(alias="createdBy", default=None) @@ -29,11 +54,11 @@ class Data(BaseModel): owner_id: str = FieldInfo(alias="ownerId") - schedule_rule: object = FieldInfo(alias="scheduleRule") + schedule_rule: DataScheduleRule = FieldInfo(alias="scheduleRule") timezone: Optional[str] = None - updated_at: Optional[str] = FieldInfo(alias="updatedAt", default=None) + updated_at: Optional[datetime] = FieldInfo(alias="updatedAt", default=None) user_id: str = FieldInfo(alias="userId") """Deprecated: use ownerId (tenancy) / createdBy (actor).""" diff --git a/tests/api_resources/assistant/test_conversations.py b/tests/api_resources/assistant/test_conversations.py index 4652e4ad..eec108db 100644 --- a/tests/api_resources/assistant/test_conversations.py +++ b/tests/api_resources/assistant/test_conversations.py @@ -42,7 +42,6 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: title="x", agent="x", description="description", - idempotency_key="Idempotency-Key", ) assert_matches_type(ConversationCreateResponse, conversation, path=["response"]) @@ -459,7 +458,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) title="x", agent="x", description="description", - idempotency_key="Idempotency-Key", ) assert_matches_type(ConversationCreateResponse, conversation, path=["response"]) diff --git a/tests/api_resources/connect/test_proxies.py b/tests/api_resources/connect/test_proxies.py index f2e62efb..04e0080b 100644 --- a/tests/api_resources/connect/test_proxies.py +++ b/tests/api_resources/connect/test_proxies.py @@ -14,7 +14,9 @@ ProxyBuyResponse, ProxyListResponse, ProxyPingResponse, + ProxyUpdateResponse, ProxyRetrieveResponse, + ProxyEnsureHealthyResponse, ProxyListConnectionsResponse, ) @@ -66,6 +68,57 @@ def test_path_params_retrieve(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Mobilerun) -> None: + proxy = client.connect.proxies.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Mobilerun) -> None: + proxy = client.connect.proxies.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Mobilerun) -> None: + response = client.connect.proxies.with_raw_response.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = response.parse() + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Mobilerun) -> None: + with client.connect.proxies.with_streaming_response.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = response.parse() + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.connect.proxies.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Mobilerun) -> None: @@ -119,7 +172,7 @@ def test_method_buy_with_all_params(self, client: Mobilerun) -> None: proxy = client.connect.proxies.buy( country="country", type="dedicated_residential", - idempotency_key="Idempotency-Key", + name="name", ) assert_matches_type(ProxyBuyResponse, proxy, path=["response"]) @@ -193,6 +246,48 @@ def test_path_params_cancel(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_ensure_healthy(self, client: Mobilerun) -> None: + proxy = client.connect.proxies.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_ensure_healthy(self, client: Mobilerun) -> None: + response = client.connect.proxies.with_raw_response.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = response.parse() + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_ensure_healthy(self, client: Mobilerun) -> None: + with client.connect.proxies.with_streaming_response.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = response.parse() + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_ensure_healthy(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.connect.proxies.with_raw_response.ensure_healthy( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_connections(self, client: Mobilerun) -> None: @@ -358,6 +453,57 @@ async def test_path_params_retrieve(self, async_client: AsyncMobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncMobilerun) -> None: + proxy = await async_client.connect.proxies.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) -> None: + proxy = await async_client.connect.proxies.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + name="name", + ) + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncMobilerun) -> None: + response = await async_client.connect.proxies.with_raw_response.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = await response.parse() + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncMobilerun) -> None: + async with async_client.connect.proxies.with_streaming_response.update( + id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = await response.parse() + assert_matches_type(ProxyUpdateResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_update(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.connect.proxies.with_raw_response.update( + id="", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncMobilerun) -> None: @@ -411,7 +557,7 @@ async def test_method_buy_with_all_params(self, async_client: AsyncMobilerun) -> proxy = await async_client.connect.proxies.buy( country="country", type="dedicated_residential", - idempotency_key="Idempotency-Key", + name="name", ) assert_matches_type(ProxyBuyResponse, proxy, path=["response"]) @@ -485,6 +631,48 @@ async def test_path_params_cancel(self, async_client: AsyncMobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_ensure_healthy(self, async_client: AsyncMobilerun) -> None: + proxy = await async_client.connect.proxies.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_ensure_healthy(self, async_client: AsyncMobilerun) -> None: + response = await async_client.connect.proxies.with_raw_response.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + proxy = await response.parse() + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_ensure_healthy(self, async_client: AsyncMobilerun) -> None: + async with async_client.connect.proxies.with_streaming_response.ensure_healthy( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + proxy = await response.parse() + assert_matches_type(ProxyEnsureHealthyResponse, proxy, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_ensure_healthy(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.connect.proxies.with_raw_response.ensure_healthy( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_connections(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/devices/test_traffic_sessions.py b/tests/api_resources/devices/test_traffic_sessions.py index 458eddf1..01a2256a 100644 --- a/tests/api_resources/devices/test_traffic_sessions.py +++ b/tests/api_resources/devices/test_traffic_sessions.py @@ -27,7 +27,6 @@ class TestTrafficSessions: def test_method_create(self, client: Mobilerun) -> None: traffic_session = client.devices.traffic_sessions.create( device_id="deviceId", - idempotency_key="x", ) assert_matches_type(TrafficSessionCreateResponse, traffic_session, path=["response"]) @@ -36,7 +35,6 @@ def test_method_create(self, client: Mobilerun) -> None: def test_method_create_with_all_params(self, client: Mobilerun) -> None: traffic_session = client.devices.traffic_sessions.create( device_id="deviceId", - idempotency_key="x", expires_in_seconds=60, max_body_bytes=0, ) @@ -47,7 +45,6 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: def test_raw_response_create(self, client: Mobilerun) -> None: response = client.devices.traffic_sessions.with_raw_response.create( device_id="deviceId", - idempotency_key="x", ) assert response.is_closed is True @@ -60,7 +57,6 @@ def test_raw_response_create(self, client: Mobilerun) -> None: def test_streaming_response_create(self, client: Mobilerun) -> None: with client.devices.traffic_sessions.with_streaming_response.create( device_id="deviceId", - idempotency_key="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -76,7 +72,6 @@ def test_path_params_create(self, client: Mobilerun) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): client.devices.traffic_sessions.with_raw_response.create( device_id="", - idempotency_key="x", ) @pytest.mark.skip(reason="Mock server tests are disabled") @@ -246,7 +241,6 @@ class TestAsyncTrafficSessions: async def test_method_create(self, async_client: AsyncMobilerun) -> None: traffic_session = await async_client.devices.traffic_sessions.create( device_id="deviceId", - idempotency_key="x", ) assert_matches_type(TrafficSessionCreateResponse, traffic_session, path=["response"]) @@ -255,7 +249,6 @@ async def test_method_create(self, async_client: AsyncMobilerun) -> None: async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: traffic_session = await async_client.devices.traffic_sessions.create( device_id="deviceId", - idempotency_key="x", expires_in_seconds=60, max_body_bytes=0, ) @@ -266,7 +259,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: response = await async_client.devices.traffic_sessions.with_raw_response.create( device_id="deviceId", - idempotency_key="x", ) assert response.is_closed is True @@ -279,7 +271,6 @@ async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> None: async with async_client.devices.traffic_sessions.with_streaming_response.create( device_id="deviceId", - idempotency_key="x", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -295,7 +286,6 @@ async def test_path_params_create(self, async_client: AsyncMobilerun) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `device_id` but received ''"): await async_client.devices.traffic_sessions.with_raw_response.create( device_id="", - idempotency_key="x", ) @pytest.mark.skip(reason="Mock server tests are disabled") diff --git a/tests/api_resources/mailboxes/test_connections.py b/tests/api_resources/mailboxes/test_connections.py new file mode 100644 index 00000000..edd8f97e --- /dev/null +++ b/tests/api_resources/mailboxes/test_connections.py @@ -0,0 +1,198 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk.types.mailboxes import ConnectionCreateResponse, ConnectionRetrieveResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConnections: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create(self, client: Mobilerun) -> None: + connection = client.mailboxes.connections.create( + provider="gmail", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Mobilerun) -> None: + connection = client.mailboxes.connections.create( + provider="gmail", + binding_hash="ecc2efdd09bd231a9ad9bd2aada37aa7ccc3d38a03cc25beb6c28aa0aaa316be", + client_request_id="x", + label="label", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Mobilerun) -> None: + response = client.mailboxes.connections.with_raw_response.create( + provider="gmail", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Mobilerun) -> None: + with client.mailboxes.connections.with_streaming_response.create( + provider="gmail", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Mobilerun) -> None: + connection = client.mailboxes.connections.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Mobilerun) -> None: + response = client.mailboxes.connections.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Mobilerun) -> None: + with client.mailboxes.connections.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): + client.mailboxes.connections.with_raw_response.retrieve( + "", + ) + + +class TestAsyncConnections: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create(self, async_client: AsyncMobilerun) -> None: + connection = await async_client.mailboxes.connections.create( + provider="gmail", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: + connection = await async_client.mailboxes.connections.create( + provider="gmail", + binding_hash="ecc2efdd09bd231a9ad9bd2aada37aa7ccc3d38a03cc25beb6c28aa0aaa316be", + client_request_id="x", + label="label", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: + response = await async_client.mailboxes.connections.with_raw_response.create( + provider="gmail", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> None: + async with async_client.mailboxes.connections.with_streaming_response.create( + provider="gmail", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: + connection = await async_client.mailboxes.connections.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMobilerun) -> None: + response = await async_client.mailboxes.connections.with_raw_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMobilerun) -> None: + async with async_client.mailboxes.connections.with_streaming_response.retrieve( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionRetrieveResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): + await async_client.mailboxes.connections.with_raw_response.retrieve( + "", + ) diff --git a/tests/api_resources/numbers/test_messages.py b/tests/api_resources/numbers/test_messages.py index eb691c13..c271d467 100644 --- a/tests/api_resources/numbers/test_messages.py +++ b/tests/api_resources/numbers/test_messages.py @@ -10,7 +10,10 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun from mobilerun_sdk._utils import parse_datetime -from mobilerun_sdk.types.numbers import MessageListResponse +from mobilerun_sdk.types.numbers import ( + MessageListResponse, + MessageSendResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -72,6 +75,68 @@ def test_path_params_list(self, client: Mobilerun) -> None: id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_send(self, client: Mobilerun) -> None: + message = client.numbers.messages.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_send_with_all_params(self, client: Mobilerun) -> None: + message = client.numbers.messages.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + client_request_id="x", + delivery_report=True, + ) + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_send(self, client: Mobilerun) -> None: + response = client.numbers.messages.with_raw_response.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_send(self, client: Mobilerun) -> None: + with client.numbers.messages.with_streaming_response.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(MessageSendResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_send(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.numbers.messages.with_raw_response.send( + id="", + body="x", + to="+15551230001", + ) + class TestAsyncMessages: parametrize = pytest.mark.parametrize( @@ -131,3 +196,65 @@ async def test_path_params_list(self, async_client: AsyncMobilerun) -> None: await async_client.numbers.messages.with_raw_response.list( id="", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_send(self, async_client: AsyncMobilerun) -> None: + message = await async_client.numbers.messages.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_send_with_all_params(self, async_client: AsyncMobilerun) -> None: + message = await async_client.numbers.messages.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + client_request_id="x", + delivery_report=True, + ) + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_send(self, async_client: AsyncMobilerun) -> None: + response = await async_client.numbers.messages.with_raw_response.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = await response.parse() + assert_matches_type(MessageSendResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_send(self, async_client: AsyncMobilerun) -> None: + async with async_client.numbers.messages.with_streaming_response.send( + id="550e8400-e29b-41d4-a716-446655440000", + body="x", + to="+15551230001", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(MessageSendResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_send(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.numbers.messages.with_raw_response.send( + id="", + body="x", + to="+15551230001", + ) diff --git a/tests/api_resources/test_app_events.py b/tests/api_resources/test_app_events.py index 041d503c..0dfcee3b 100644 --- a/tests/api_resources/test_app_events.py +++ b/tests/api_resources/test_app_events.py @@ -10,6 +10,7 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun from mobilerun_sdk.types import AppEventListResponse, AppEventRetrieveResponse +from mobilerun_sdk._utils import parse_datetime base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -71,11 +72,11 @@ def test_method_list_with_all_params(self, client: Mobilerun) -> None: app_event = client.app_events.list( device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", event_type="eventType", - from_="from", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), page=1, page_size=1, source="app", - to="to", + to=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert_matches_type(AppEventListResponse, app_event, path=["response"]) @@ -161,11 +162,11 @@ async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) - app_event = await async_client.app_events.list( device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", event_type="eventType", - from_="from", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), page=1, page_size=1, source="app", - to="to", + to=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert_matches_type(AppEventListResponse, app_event, path=["response"]) diff --git a/tests/api_resources/test_devices.py b/tests/api_resources/test_devices.py index 19b6067c..72f28b40 100644 --- a/tests/api_resources/test_devices.py +++ b/tests/api_resources/test_devices.py @@ -14,6 +14,7 @@ DeviceCountResponse, DeviceCreateResponse, DeviceSetNameResponse, + DeviceSummaryResponse, DeviceRetrieveResponse, DeviceWaitReadyResponse, DeviceFingerprintResponse, @@ -21,6 +22,8 @@ ) from mobilerun_sdk._utils import parse_datetime +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -176,6 +179,7 @@ def test_method_list_with_all_params(self, client: Mobilerun) -> None: order_by_direction="asc", page=0, page_size=0, + platform="android", provider_id="providerId", state=["creating"], type="android_cloud_phone", @@ -207,13 +211,16 @@ def test_streaming_response_list(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_count(self, client: Mobilerun) -> None: - device = client.devices.count() + with pytest.warns(DeprecationWarning): + device = client.devices.count() + assert_matches_type(DeviceCountResponse, device, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_count(self, client: Mobilerun) -> None: - response = client.devices.with_raw_response.count() + with pytest.warns(DeprecationWarning): + response = client.devices.with_raw_response.count() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -223,12 +230,13 @@ def test_raw_response_count(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_count(self, client: Mobilerun) -> None: - with client.devices.with_streaming_response.count() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.devices.with_streaming_response.count() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - device = response.parse() - assert_matches_type(DeviceCountResponse, device, path=["response"]) + device = response.parse() + assert_matches_type(DeviceCountResponse, device, path=["response"]) assert cast(Any, response.is_closed) is True @@ -539,6 +547,42 @@ def test_path_params_stop(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_summary(self, client: Mobilerun) -> None: + device = client.devices.summary() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_summary_with_all_params(self, client: Mobilerun) -> None: + device = client.devices.summary( + state=["creating"], + ) + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_summary(self, client: Mobilerun) -> None: + response = client.devices.with_raw_response.summary() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = response.parse() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_summary(self, client: Mobilerun) -> None: + with client.devices.with_streaming_response.summary() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = response.parse() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_terminate(self, client: Mobilerun) -> None: @@ -788,6 +832,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) - order_by_direction="asc", page=0, page_size=0, + platform="android", provider_id="providerId", state=["creating"], type="android_cloud_phone", @@ -819,13 +864,16 @@ async def test_streaming_response_list(self, async_client: AsyncMobilerun) -> No @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_count(self, async_client: AsyncMobilerun) -> None: - device = await async_client.devices.count() + with pytest.warns(DeprecationWarning): + device = await async_client.devices.count() + assert_matches_type(DeviceCountResponse, device, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_count(self, async_client: AsyncMobilerun) -> None: - response = await async_client.devices.with_raw_response.count() + with pytest.warns(DeprecationWarning): + response = await async_client.devices.with_raw_response.count() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -835,12 +883,13 @@ async def test_raw_response_count(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_count(self, async_client: AsyncMobilerun) -> None: - async with async_client.devices.with_streaming_response.count() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.devices.with_streaming_response.count() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - device = await response.parse() - assert_matches_type(DeviceCountResponse, device, path=["response"]) + device = await response.parse() + assert_matches_type(DeviceCountResponse, device, path=["response"]) assert cast(Any, response.is_closed) is True @@ -1151,6 +1200,42 @@ async def test_path_params_stop(self, async_client: AsyncMobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_summary(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.summary() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_summary_with_all_params(self, async_client: AsyncMobilerun) -> None: + device = await async_client.devices.summary( + state=["creating"], + ) + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_summary(self, async_client: AsyncMobilerun) -> None: + response = await async_client.devices.with_raw_response.summary() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + device = await response.parse() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_summary(self, async_client: AsyncMobilerun) -> None: + async with async_client.devices.with_streaming_response.summary() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + device = await response.parse() + assert_matches_type(DeviceSummaryResponse, device, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_terminate(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index 47decef9..28417b3f 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -298,7 +298,6 @@ def test_method_upload_url_with_all_params(self, client: Mobilerun) -> None: mime_type="x", size_bytes=1, zone="user", - idempotency_key="x", ) assert_matches_type(FileUploadURLResponse, file, path=["response"]) @@ -612,7 +611,6 @@ async def test_method_upload_url_with_all_params(self, async_client: AsyncMobile mime_type="x", size_bytes=1, zone="user", - idempotency_key="x", ) assert_matches_type(FileUploadURLResponse, file, path=["response"]) diff --git a/tests/api_resources/test_mailboxes.py b/tests/api_resources/test_mailboxes.py index 45d04734..cb7a539a 100644 --- a/tests/api_resources/test_mailboxes.py +++ b/tests/api_resources/test_mailboxes.py @@ -19,6 +19,7 @@ MailboxCapacityResponse, MailboxRetrieveResponse, MailboxUncancelResponse, + MailboxDisconnectResponse, ) from mobilerun_sdk._utils import parse_datetime @@ -31,16 +32,13 @@ class TestMailboxes: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create(self, client: Mobilerun) -> None: - mailbox = client.mailboxes.create( - client_request_id="x", - ) + mailbox = client.mailboxes.create() assert_matches_type(MailboxCreateResponse, mailbox, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_create_with_all_params(self, client: Mobilerun) -> None: mailbox = client.mailboxes.create( - client_request_id="x", billing_preference="included", domain_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", label="label", @@ -51,9 +49,7 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Mobilerun) -> None: - response = client.mailboxes.with_raw_response.create( - client_request_id="x", - ) + response = client.mailboxes.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -63,9 +59,7 @@ def test_raw_response_create(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_create(self, client: Mobilerun) -> None: - with client.mailboxes.with_streaming_response.create( - client_request_id="x", - ) as response: + with client.mailboxes.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -269,6 +263,48 @@ def test_streaming_response_capacity(self, client: Mobilerun) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_disconnect(self, client: Mobilerun) -> None: + mailbox = client.mailboxes.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_disconnect(self, client: Mobilerun) -> None: + response = client.mailboxes.with_raw_response.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mailbox = response.parse() + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_disconnect(self, client: Mobilerun) -> None: + with client.mailboxes.with_streaming_response.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mailbox = response.parse() + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_disconnect(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `mailbox_id` but received ''"): + client.mailboxes.with_raw_response.disconnect( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_otp(self, client: Mobilerun) -> None: @@ -425,16 +461,13 @@ class TestAsyncMailboxes: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncMobilerun) -> None: - mailbox = await async_client.mailboxes.create( - client_request_id="x", - ) + mailbox = await async_client.mailboxes.create() assert_matches_type(MailboxCreateResponse, mailbox, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: mailbox = await async_client.mailboxes.create( - client_request_id="x", billing_preference="included", domain_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", label="label", @@ -445,9 +478,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: - response = await async_client.mailboxes.with_raw_response.create( - client_request_id="x", - ) + response = await async_client.mailboxes.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -457,9 +488,7 @@ async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> None: - async with async_client.mailboxes.with_streaming_response.create( - client_request_id="x", - ) as response: + async with async_client.mailboxes.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -663,6 +692,48 @@ async def test_streaming_response_capacity(self, async_client: AsyncMobilerun) - assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_disconnect(self, async_client: AsyncMobilerun) -> None: + mailbox = await async_client.mailboxes.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_disconnect(self, async_client: AsyncMobilerun) -> None: + response = await async_client.mailboxes.with_raw_response.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + mailbox = await response.parse() + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_disconnect(self, async_client: AsyncMobilerun) -> None: + async with async_client.mailboxes.with_streaming_response.disconnect( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + mailbox = await response.parse() + assert_matches_type(MailboxDisconnectResponse, mailbox, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_disconnect(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `mailbox_id` but received ''"): + await async_client.mailboxes.with_raw_response.disconnect( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_otp(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py index a3d35670..92e3811e 100644 --- a/tests/api_resources/test_messages.py +++ b/tests/api_resources/test_messages.py @@ -9,7 +9,7 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun -from mobilerun_sdk.types import MessageListResponse +from mobilerun_sdk.types import MessageListResponse, MessageRetrieveResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -17,6 +17,48 @@ class TestMessages: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Mobilerun) -> None: + message = client.messages.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Mobilerun) -> None: + response = client.messages.with_raw_response.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = response.parse() + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Mobilerun) -> None: + with client.messages.with_streaming_response.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = response.parse() + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.messages.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Mobilerun) -> None: @@ -66,6 +108,48 @@ class TestAsyncMessages: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: + message = await async_client.messages.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMobilerun) -> None: + response = await async_client.messages.with_raw_response.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + message = await response.parse() + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMobilerun) -> None: + async with async_client.messages.with_streaming_response.retrieve( + "550e8400-e29b-41d4-a716-446655440000", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + message = await response.parse() + assert_matches_type(MessageRetrieveResponse, message, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.messages.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/test_notifications.py b/tests/api_resources/test_notifications.py index c8180d64..8c253707 100644 --- a/tests/api_resources/test_notifications.py +++ b/tests/api_resources/test_notifications.py @@ -13,6 +13,7 @@ NotificationCatalogResponse, NotificationGetPreferencesResponse, NotificationUpdatePreferencesResponse, + NotificationApplyPreferencesPresetResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,6 +22,40 @@ class TestNotifications: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_apply_preferences_preset(self, client: Mobilerun) -> None: + notification = client.notifications.apply_preferences_preset( + preset="recommended", + ) + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_apply_preferences_preset(self, client: Mobilerun) -> None: + response = client.notifications.with_raw_response.apply_preferences_preset( + preset="recommended", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + notification = response.parse() + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_apply_preferences_preset(self, client: Mobilerun) -> None: + with client.notifications.with_streaming_response.apply_preferences_preset( + preset="recommended", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + notification = response.parse() + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_catalog(self, client: Mobilerun) -> None: @@ -117,6 +152,40 @@ class TestAsyncNotifications: "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_apply_preferences_preset(self, async_client: AsyncMobilerun) -> None: + notification = await async_client.notifications.apply_preferences_preset( + preset="recommended", + ) + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_apply_preferences_preset(self, async_client: AsyncMobilerun) -> None: + response = await async_client.notifications.with_raw_response.apply_preferences_preset( + preset="recommended", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + notification = await response.parse() + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_apply_preferences_preset(self, async_client: AsyncMobilerun) -> None: + async with async_client.notifications.with_streaming_response.apply_preferences_preset( + preset="recommended", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + notification = await response.parse() + assert_matches_type(NotificationApplyPreferencesPresetResponse, notification, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_catalog(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/api_resources/test_numbers.py b/tests/api_resources/test_numbers.py index 957aab3c..94ecf75d 100644 --- a/tests/api_resources/test_numbers.py +++ b/tests/api_resources/test_numbers.py @@ -40,7 +40,6 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: country="de", label="Support line", purpose="telegram", - idempotency_key="x", ) assert_matches_type(NumberCreateResponse, number, path=["response"]) @@ -348,7 +347,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) country="de", label="Support line", purpose="telegram", - idempotency_key="x", ) assert_matches_type(NumberCreateResponse, number, path=["response"]) diff --git a/tests/api_resources/test_tasks.py b/tests/api_resources/test_tasks.py index 8ff81d06..cb29c095 100644 --- a/tests/api_resources/test_tasks.py +++ b/tests/api_resources/test_tasks.py @@ -275,7 +275,6 @@ def test_method_run_with_all_params(self, client: Mobilerun) -> None: temperature=0, vision=True, vpn_country="US", - idempotency_key="x", ) assert_matches_type(TaskRunResponse, task, path=["response"]) @@ -720,7 +719,6 @@ async def test_method_run_with_all_params(self, async_client: AsyncMobilerun) -> temperature=0, vision=True, vpn_country="US", - idempotency_key="x", ) assert_matches_type(TaskRunResponse, task, path=["response"]) diff --git a/tests/api_resources/test_webhooks.py b/tests/api_resources/test_webhooks.py index 054ff026..a21e6362 100644 --- a/tests/api_resources/test_webhooks.py +++ b/tests/api_resources/test_webhooks.py @@ -27,7 +27,7 @@ class TestWebhooks: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create(self, client: Mobilerun) -> None: + def test_method_create_overload_1(self, client: Mobilerun) -> None: webhook = client.webhooks.create( url="https://example.com/webhooks/droidrun", ) @@ -35,17 +35,18 @@ def test_method_create(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_with_all_params(self, client: Mobilerun) -> None: + def test_method_create_with_all_params_overload_1(self, client: Mobilerun) -> None: webhook = client.webhooks.create( url="https://example.com/webhooks/droidrun", description="description", event_types=["task.run.completed", "task.run.failed"], + kind="http", ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create(self, client: Mobilerun) -> None: + def test_raw_response_create_overload_1(self, client: Mobilerun) -> None: response = client.webhooks.with_raw_response.create( url="https://example.com/webhooks/droidrun", ) @@ -57,7 +58,7 @@ def test_raw_response_create(self, client: Mobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create(self, client: Mobilerun) -> None: + def test_streaming_response_create_overload_1(self, client: Mobilerun) -> None: with client.webhooks.with_streaming_response.create( url="https://example.com/webhooks/droidrun", ) as response: @@ -69,6 +70,70 @@ def test_streaming_response_create(self, client: Mobilerun) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_overload_2(self, client: Mobilerun) -> None: + webhook = client.webhooks.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params_overload_2(self, client: Mobilerun) -> None: + webhook = client.webhooks.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + description="description", + event_types=["task.run.completed", "task.run.failed"], + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_overload_2(self, client: Mobilerun) -> None: + response = client.webhooks.with_raw_response.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_overload_2(self, client: Mobilerun) -> None: + with client.webhooks.with_streaming_response.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_retrieve(self, client: Mobilerun) -> None: @@ -175,6 +240,7 @@ def test_method_list(self, client: Mobilerun) -> None: def test_method_list_with_all_params(self, client: Mobilerun) -> None: webhook = client.webhooks.list( created_by="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + kind="http", mine="true", page=1, page_size=1, @@ -367,7 +433,7 @@ class TestAsyncWebhooks: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create(self, async_client: AsyncMobilerun) -> None: + async def test_method_create_overload_1(self, async_client: AsyncMobilerun) -> None: webhook = await async_client.webhooks.create( url="https://example.com/webhooks/droidrun", ) @@ -375,17 +441,18 @@ async def test_method_create(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) -> None: + async def test_method_create_with_all_params_overload_1(self, async_client: AsyncMobilerun) -> None: webhook = await async_client.webhooks.create( url="https://example.com/webhooks/droidrun", description="description", event_types=["task.run.completed", "task.run.failed"], + kind="http", ) assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: + async def test_raw_response_create_overload_1(self, async_client: AsyncMobilerun) -> None: response = await async_client.webhooks.with_raw_response.create( url="https://example.com/webhooks/droidrun", ) @@ -397,7 +464,7 @@ async def test_raw_response_create(self, async_client: AsyncMobilerun) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> None: + async def test_streaming_response_create_overload_1(self, async_client: AsyncMobilerun) -> None: async with async_client.webhooks.with_streaming_response.create( url="https://example.com/webhooks/droidrun", ) as response: @@ -409,6 +476,70 @@ async def test_streaming_response_create(self, async_client: AsyncMobilerun) -> assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_overload_2(self, async_client: AsyncMobilerun) -> None: + webhook = await async_client.webhooks.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params_overload_2(self, async_client: AsyncMobilerun) -> None: + webhook = await async_client.webhooks.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + description="description", + event_types=["task.run.completed", "task.run.failed"], + ) + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_overload_2(self, async_client: AsyncMobilerun) -> None: + response = await async_client.webhooks.with_raw_response.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + webhook = await response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_overload_2(self, async_client: AsyncMobilerun) -> None: + async with async_client.webhooks.with_streaming_response.create( + args={"channelId": "bar"}, + capability={ + "id": "slack.post_message", + "revision": 1, + }, + kind="integration", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + webhook = await response.parse() + assert_matches_type(WebhookCreateResponse, webhook, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: @@ -515,6 +646,7 @@ async def test_method_list(self, async_client: AsyncMobilerun) -> None: async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) -> None: webhook = await async_client.webhooks.list( created_by="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + kind="http", mine="true", page=1, page_size=1, diff --git a/tests/api_resources/webhooks/test_deliveries.py b/tests/api_resources/webhooks/test_deliveries.py index b3838416..108eb707 100644 --- a/tests/api_resources/webhooks/test_deliveries.py +++ b/tests/api_resources/webhooks/test_deliveries.py @@ -34,6 +34,7 @@ def test_method_list(self, client: Mobilerun) -> None: def test_method_list_with_all_params(self, client: Mobilerun) -> None: delivery = client.webhooks.deliveries.list( event_id="x", + kind="http", page=1, page_size=1, since=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -178,6 +179,7 @@ def test_method_stats(self, client: Mobilerun) -> None: @parametrize def test_method_stats_with_all_params(self, client: Mobilerun) -> None: delivery = client.webhooks.deliveries.stats( + kind="http", since=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert_matches_type(DeliveryStatsResponse, delivery, path=["response"]) @@ -221,6 +223,7 @@ async def test_method_list(self, async_client: AsyncMobilerun) -> None: async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) -> None: delivery = await async_client.webhooks.deliveries.list( event_id="x", + kind="http", page=1, page_size=1, since=parse_datetime("2019-12-27T18:11:19.117Z"), @@ -365,6 +368,7 @@ async def test_method_stats(self, async_client: AsyncMobilerun) -> None: @parametrize async def test_method_stats_with_all_params(self, async_client: AsyncMobilerun) -> None: delivery = await async_client.webhooks.deliveries.stats( + kind="http", since=parse_datetime("2019-12-27T18:11:19.117Z"), ) assert_matches_type(DeliveryStatsResponse, delivery, path=["response"]) diff --git a/tests/api_resources/webhooks/test_integrations.py b/tests/api_resources/webhooks/test_integrations.py new file mode 100644 index 00000000..894a34f7 --- /dev/null +++ b/tests/api_resources/webhooks/test_integrations.py @@ -0,0 +1,175 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from tests.utils import assert_matches_type +from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk.types.webhooks import ( + IntegrationListResponse, + IntegrationListTargetsResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestIntegrations: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Mobilerun) -> None: + integration = client.webhooks.integrations.list() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Mobilerun) -> None: + response = client.webhooks.integrations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + integration = response.parse() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Mobilerun) -> None: + with client.webhooks.integrations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + integration = response.parse() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_targets(self, client: Mobilerun) -> None: + integration = client.webhooks.integrations.list_targets( + capability_id="slack.post_message", + revision=1, + ) + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list_targets(self, client: Mobilerun) -> None: + response = client.webhooks.integrations.with_raw_response.list_targets( + capability_id="slack.post_message", + revision=1, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + integration = response.parse() + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list_targets(self, client: Mobilerun) -> None: + with client.webhooks.integrations.with_streaming_response.list_targets( + capability_id="slack.post_message", + revision=1, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + integration = response.parse() + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_list_targets(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `capability_id` but received ''"): + client.webhooks.integrations.with_raw_response.list_targets( + capability_id="", + revision=1, + ) + + +class TestAsyncIntegrations: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncMobilerun) -> None: + integration = await async_client.webhooks.integrations.list() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncMobilerun) -> None: + response = await async_client.webhooks.integrations.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + integration = await response.parse() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncMobilerun) -> None: + async with async_client.webhooks.integrations.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + integration = await response.parse() + assert_matches_type(IntegrationListResponse, integration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_targets(self, async_client: AsyncMobilerun) -> None: + integration = await async_client.webhooks.integrations.list_targets( + capability_id="slack.post_message", + revision=1, + ) + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list_targets(self, async_client: AsyncMobilerun) -> None: + response = await async_client.webhooks.integrations.with_raw_response.list_targets( + capability_id="slack.post_message", + revision=1, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + integration = await response.parse() + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list_targets(self, async_client: AsyncMobilerun) -> None: + async with async_client.webhooks.integrations.with_streaming_response.list_targets( + capability_id="slack.post_message", + revision=1, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + integration = await response.parse() + assert_matches_type(IntegrationListTargetsResponse, integration, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_list_targets(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `capability_id` but received ''"): + await async_client.webhooks.integrations.with_raw_response.list_targets( + capability_id="", + revision=1, + ) diff --git a/tests/api_resources/workflows/executions/__init__.py b/tests/api_resources/workflows/executions/__init__.py new file mode 100644 index 00000000..fd8019a9 --- /dev/null +++ b/tests/api_resources/workflows/executions/__init__.py @@ -0,0 +1 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. diff --git a/tests/api_resources/workflows/executions/test_screenshots.py b/tests/api_resources/workflows/executions/test_screenshots.py new file mode 100644 index 00000000..81520ccb --- /dev/null +++ b/tests/api_resources/workflows/executions/test_screenshots.py @@ -0,0 +1,126 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from mobilerun_sdk import Mobilerun, AsyncMobilerun + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestScreenshots: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: Mobilerun) -> None: + screenshot = client.workflows.executions.screenshots.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert screenshot is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Mobilerun) -> None: + response = client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + screenshot = response.parse() + assert screenshot is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Mobilerun) -> None: + with client.workflows.executions.screenshots.with_streaming_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + screenshot = response.parse() + assert screenshot is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_retrieve(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): + client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `screenshot_id` but received ''"): + client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + +class TestAsyncScreenshots: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_retrieve(self, async_client: AsyncMobilerun) -> None: + screenshot = await async_client.workflows.executions.screenshots.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert screenshot is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + screenshot = await response.parse() + assert screenshot is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.executions.screenshots.with_streaming_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + screenshot = await response.parse() + assert screenshot is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_retrieve(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `execution_id` but received ''"): + await async_client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + execution_id="", + ) + + with pytest.raises(ValueError, match=r"Expected a non-empty value for `screenshot_id` but received ''"): + await async_client.workflows.executions.screenshots.with_raw_response.retrieve( + screenshot_id="", + execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/workflows/flows/test_actions.py b/tests/api_resources/workflows/flows/test_actions.py index 21f7ca88..047f77bc 100644 --- a/tests/api_resources/workflows/flows/test_actions.py +++ b/tests/api_resources/workflows/flows/test_actions.py @@ -86,12 +86,14 @@ def test_method_add_with_all_params(self, client: Mobilerun) -> None: "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "position": 0, "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, } ], continue_on_error=True, + key="key", name_override="x", overrides={"params": {"foo": "bar"}}, parent_flow_action_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", @@ -327,12 +329,14 @@ async def test_method_add_with_all_params(self, async_client: AsyncMobilerun) -> "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "position": 0, "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, } ], continue_on_error=True, + key="key", name_override="x", overrides={"params": {"foo": "bar"}}, parent_flow_action_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", diff --git a/tests/api_resources/workflows/test_executions.py b/tests/api_resources/workflows/test_executions.py index 20711476..5cb2e9dc 100644 --- a/tests/api_resources/workflows/test_executions.py +++ b/tests/api_resources/workflows/test_executions.py @@ -9,6 +9,7 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun +from mobilerun_sdk._utils import parse_datetime from mobilerun_sdk.types.workflows import ( ExecutionListResponse, ExecutionAbortResponse, @@ -75,7 +76,7 @@ def test_method_list(self, client: Mobilerun) -> None: def test_method_list_with_all_params(self, client: Mobilerun) -> None: execution = client.workflows.executions.list( flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - from_="from", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", order_by="startedAt", order_by_direction="asc", @@ -83,7 +84,7 @@ def test_method_list_with_all_params(self, client: Mobilerun) -> None: page_size=1, search="x", status="pending", - to="to", + to=parse_datetime("2019-12-27T18:11:19.117Z"), trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExecutionListResponse, execution, path=["response"]) @@ -163,8 +164,8 @@ def test_method_get_metrics(self, client: Mobilerun) -> None: def test_method_get_metrics_with_all_params(self, client: Mobilerun) -> None: execution = client.workflows.executions.get_metrics( flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - from_="from", - to="to", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), + to=parse_datetime("2019-12-27T18:11:19.117Z"), trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExecutionGetMetricsResponse, execution, path=["response"]) @@ -250,7 +251,7 @@ async def test_method_list(self, async_client: AsyncMobilerun) -> None: async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) -> None: execution = await async_client.workflows.executions.list( flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - from_="from", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", order_by="startedAt", order_by_direction="asc", @@ -258,7 +259,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncMobilerun) - page_size=1, search="x", status="pending", - to="to", + to=parse_datetime("2019-12-27T18:11:19.117Z"), trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExecutionListResponse, execution, path=["response"]) @@ -338,8 +339,8 @@ async def test_method_get_metrics(self, async_client: AsyncMobilerun) -> None: async def test_method_get_metrics_with_all_params(self, async_client: AsyncMobilerun) -> None: execution = await async_client.workflows.executions.get_metrics( flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - from_="from", - to="to", + from_=parse_datetime("2019-12-27T18:11:19.117Z"), + to=parse_datetime("2019-12-27T18:11:19.117Z"), trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", ) assert_matches_type(ExecutionGetMetricsResponse, execution, path=["response"]) diff --git a/tests/api_resources/workflows/test_flows.py b/tests/api_resources/workflows/test_flows.py index eb527497..de22e233 100644 --- a/tests/api_resources/workflows/test_flows.py +++ b/tests/api_resources/workflows/test_flows.py @@ -10,16 +10,22 @@ from tests.utils import assert_matches_type from mobilerun_sdk import Mobilerun, AsyncMobilerun from mobilerun_sdk.types.workflows import ( + FlowRunResponse, FlowListResponse, FlowCloneResponse, FlowCreateResponse, FlowDeleteResponse, FlowDryRunResponse, FlowUpdateResponse, + FlowVerifyResponse, FlowUnblockResponse, + FlowActivateResponse, FlowCapacityResponse, FlowRetrieveResponse, + FlowValidateResponse, FlowListRepairsResponse, + FlowDeliveryOptionsResponse, + FlowTemplateContextResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -56,12 +62,14 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "position": 0, "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, } ], "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, @@ -71,6 +79,12 @@ def test_method_create_with_all_params(self, client: Mobilerun) -> None: trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", cooldown_scope="flow", cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, description="description", device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], enabled=True, @@ -182,6 +196,12 @@ def test_method_update_with_all_params(self, client: Mobilerun) -> None: flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", cooldown_scope="flow", cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, description="description", device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], enabled=True, @@ -320,6 +340,52 @@ def test_path_params_delete(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_activate(self, client: Mobilerun) -> None: + flow = client.workflows.flows.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_activate(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_activate(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_activate(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + client.workflows.flows.with_raw_response.activate( + flow_id="", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_capacity(self, client: Mobilerun) -> None: @@ -400,6 +466,48 @@ def test_path_params_clone(self, client: Mobilerun) -> None: flow_id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delivery_options(self, client: Mobilerun) -> None: + flow = client.workflows.flows.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delivery_options(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delivery_options(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delivery_options(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + client.workflows.flows.with_raw_response.delivery_options( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_dry_run(self, client: Mobilerun) -> None: @@ -493,6 +601,93 @@ def test_path_params_list_repairs(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run(self, client: Mobilerun) -> None: + flow = client.workflows.flows.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run_with_all_params(self, client: Mobilerun) -> None: + flow = client.workflows.flows.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payload={"foo": "bar"}, + ) + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_run(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_run(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_run(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + client.workflows.flows.with_raw_response.run( + flow_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_template_context(self, client: Mobilerun) -> None: + flow = client.workflows.flows.template_context() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_template_context_with_all_params(self, client: Mobilerun) -> None: + flow = client.workflows.flows.template_context( + template_resolution_version=2, + ) + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_template_context(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.template_context() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_template_context(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.template_context() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_unblock(self, client: Mobilerun) -> None: @@ -535,6 +730,165 @@ def test_path_params_unblock(self, client: Mobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate(self, client: Mobilerun) -> None: + flow = client.workflows.flows.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_validate_with_all_params(self, client: Mobilerun) -> None: + flow = client.workflows.flows.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + "children": [ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + "continue_on_error": True, + "key": "key", + "name_override": "x", + "overrides": {"params": {"foo": "bar"}}, + "recording_enabled": True, + } + ], + "continue_on_error": True, + "key": "key", + "name_override": "x", + "overrides": {"params": {"foo": "bar"}}, + "recording_enabled": True, + } + ], + name="x", + cooldown_scope="flow", + cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, + description="description", + device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + enabled=True, + health_monitoring_enabled=True, + notify_on_failure=True, + notify_on_success=True, + notify_webhook_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + recording_enabled=True, + recording_policy={"mode": "off"}, + self_healing_enabled=True, + self_healing_max_attempts=1, + trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_validate(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_validate(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_verify(self, client: Mobilerun) -> None: + flow = client.workflows.flows.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_verify_with_all_params(self, client: Mobilerun) -> None: + flow = client.workflows.flows.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payload={"foo": "bar"}, + ) + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_verify(self, client: Mobilerun) -> None: + response = client.workflows.flows.with_raw_response.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = response.parse() + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_verify(self, client: Mobilerun) -> None: + with client.workflows.flows.with_streaming_response.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = response.parse() + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_verify(self, client: Mobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + client.workflows.flows.with_raw_response.verify( + flow_id="", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + class TestAsyncFlows: parametrize = pytest.mark.parametrize( @@ -569,12 +923,14 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", "position": 0, "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, } ], "continue_on_error": True, + "key": "key", "name_override": "x", "overrides": {"params": {"foo": "bar"}}, "recording_enabled": True, @@ -584,6 +940,12 @@ async def test_method_create_with_all_params(self, async_client: AsyncMobilerun) trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", cooldown_scope="flow", cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, description="description", device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], enabled=True, @@ -695,6 +1057,12 @@ async def test_method_update_with_all_params(self, async_client: AsyncMobilerun) flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", cooldown_scope="flow", cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, description="description", device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], enabled=True, @@ -833,6 +1201,52 @@ async def test_path_params_delete(self, async_client: AsyncMobilerun) -> None: "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_activate(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_activate(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_activate(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.activate( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowActivateResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_activate(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + await async_client.workflows.flows.with_raw_response.activate( + flow_id="", + verification_execution_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_capacity(self, async_client: AsyncMobilerun) -> None: @@ -913,6 +1327,48 @@ async def test_path_params_clone(self, async_client: AsyncMobilerun) -> None: flow_id="", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delivery_options(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delivery_options(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delivery_options(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.delivery_options( + "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowDeliveryOptionsResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_delivery_options(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + await async_client.workflows.flows.with_raw_response.delivery_options( + "", + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_dry_run(self, async_client: AsyncMobilerun) -> None: @@ -1006,6 +1462,93 @@ async def test_path_params_list_repairs(self, async_client: AsyncMobilerun) -> N "", ) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run_with_all_params(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payload={"foo": "bar"}, + ) + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_run(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_run(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.run( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowRunResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_run(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + await async_client.workflows.flows.with_raw_response.run( + flow_id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_template_context(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.template_context() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_template_context_with_all_params(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.template_context( + template_resolution_version=2, + ) + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_template_context(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.template_context() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_template_context(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.template_context() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowTemplateContextResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_unblock(self, async_client: AsyncMobilerun) -> None: @@ -1047,3 +1590,162 @@ async def test_path_params_unblock(self, async_client: AsyncMobilerun) -> None: await async_client.workflows.flows.with_raw_response.unblock( "", ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_validate_with_all_params(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + "children": [ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + "continue_on_error": True, + "key": "key", + "name_override": "x", + "overrides": {"params": {"foo": "bar"}}, + "recording_enabled": True, + } + ], + "continue_on_error": True, + "key": "key", + "name_override": "x", + "overrides": {"params": {"foo": "bar"}}, + "recording_enabled": True, + } + ], + name="x", + cooldown_scope="flow", + cooldown_seconds=0, + delivery={ + "destination": "one_drive", + "folder": "x", + "recording": {"filename": "x"}, + "screenshots": {}, + }, + description="description", + device_ids=["182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e"], + enabled=True, + health_monitoring_enabled=True, + notify_on_failure=True, + notify_on_success=True, + notify_webhook_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + recording_enabled=True, + recording_policy={"mode": "off"}, + self_healing_enabled=True, + self_healing_max_attempts=1, + trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_validate(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_validate(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.validate( + actions=[ + { + "action_id": "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + "position": 0, + } + ], + name="x", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowValidateResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_verify(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_verify_with_all_params(self, async_client: AsyncMobilerun) -> None: + flow = await async_client.workflows.flows.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + device_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + payload={"foo": "bar"}, + ) + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_verify(self, async_client: AsyncMobilerun) -> None: + response = await async_client.workflows.flows.with_raw_response.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + flow = await response.parse() + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_verify(self, async_client: AsyncMobilerun) -> None: + async with async_client.workflows.flows.with_streaming_response.verify( + flow_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + flow = await response.parse() + assert_matches_type(FlowVerifyResponse, flow, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_verify(self, async_client: AsyncMobilerun) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `flow_id` but received ''"): + await async_client.workflows.flows.with_raw_response.verify( + flow_id="", + invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", + ) diff --git a/tests/api_resources/workflows/test_triggers.py b/tests/api_resources/workflows/test_triggers.py index 7149612e..c29dff8d 100644 --- a/tests/api_resources/workflows/test_triggers.py +++ b/tests/api_resources/workflows/test_triggers.py @@ -293,16 +293,6 @@ def test_method_fire(self, client: Mobilerun) -> None: ) assert_matches_type(TriggerFireResponse, trigger, path=["response"]) - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_fire_with_all_params(self, client: Mobilerun) -> None: - trigger = client.workflows.triggers.fire( - trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - payload={"foo": "bar"}, - invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(TriggerFireResponse, trigger, path=["response"]) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_fire(self, client: Mobilerun) -> None: @@ -615,16 +605,6 @@ async def test_method_fire(self, async_client: AsyncMobilerun) -> None: ) assert_matches_type(TriggerFireResponse, trigger, path=["response"]) - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_fire_with_all_params(self, async_client: AsyncMobilerun) -> None: - trigger = await async_client.workflows.triggers.fire( - trigger_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - payload={"foo": "bar"}, - invocation_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e", - ) - assert_matches_type(TriggerFireResponse, trigger, path=["response"]) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_fire(self, async_client: AsyncMobilerun) -> None: diff --git a/tests/test_client.py b/tests/test_client.py index 9af846f0..95b93e25 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -709,6 +709,37 @@ class Model(BaseModel): assert isinstance(response, Model) assert response.foo == 2 + @pytest.mark.respx(base_url=base_url) + def test_idempotency_header_options(self, respx_mock: MockRouter, client: Mobilerun) -> None: + respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={})) + + response = client.post("/foo", cast_to=httpx.Response) + + header = response.request.headers.get("Idempotency-Key") + assert header is not None + assert header.startswith("stainless-python-retry") + + # explicit header + response = client.post( + "/foo", + cast_to=httpx.Response, + options=make_request_options(extra_headers={"Idempotency-Key": "custom-key"}), + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + + response = client.post( + "/foo", + cast_to=httpx.Response, + options=make_request_options(extra_headers={"idempotency-key": "custom-key"}), + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + + # custom argument + response = client.post( + "/foo", cast_to=httpx.Response, options=make_request_options(idempotency_key="custom-key") + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + def test_base_url_setter(self) -> None: client = Mobilerun(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" @@ -1637,6 +1668,37 @@ class Model(BaseModel): assert isinstance(response, Model) assert response.foo == 2 + @pytest.mark.respx(base_url=base_url) + async def test_idempotency_header_options(self, respx_mock: MockRouter, async_client: AsyncMobilerun) -> None: + respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={})) + + response = await async_client.post("/foo", cast_to=httpx.Response) + + header = response.request.headers.get("Idempotency-Key") + assert header is not None + assert header.startswith("stainless-python-retry") + + # explicit header + response = await async_client.post( + "/foo", + cast_to=httpx.Response, + options=make_request_options(extra_headers={"Idempotency-Key": "custom-key"}), + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + + response = await async_client.post( + "/foo", + cast_to=httpx.Response, + options=make_request_options(extra_headers={"idempotency-key": "custom-key"}), + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + + # custom argument + response = await async_client.post( + "/foo", cast_to=httpx.Response, options=make_request_options(idempotency_key="custom-key") + ) + assert response.request.headers.get("Idempotency-Key") == "custom-key" + async def test_base_url_setter(self) -> None: client = AsyncMobilerun( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True