diff --git a/README.md b/README.md index 60ee536..6f6b5cc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ See the [API reference](https://activitysmith.com/docs/api-reference/introductio - [Start & Update Live Activity](#start--update-live-activity) - [End Live Activity](#end-live-activity) - [Live Activity Action](#live-activity-action) + - [Icons and Badges](#icons-and-badges) + - [Live Activity Colors](#live-activity-colors) - [Channels](#channels) - [Widgets](#widgets) @@ -266,14 +268,6 @@ activitysmith.live_activities.stream( ) ``` -The `icon.symbol` value is an Apple SF Symbol name. Browse the catalog with one of these tools: - -- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use -- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app -- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography - -`icon` and `badge` are optional. If you omit either one, that element is not shown in the Live Activity. - ### End Live Activity Call `end_stream(...)` with the same `stream_key` to dismiss the Live Activity. You can include final values before it is removed. By default, iOS removes the Live Activity after two minutes. Set `auto_dismiss_minutes` to choose a different dismissal time, including `0` for immediate dismissal. @@ -354,6 +348,67 @@ activitysmith.live_activities.stream( ) ``` +### Icons and Badges + +Add more context to Live Activities with icons and badges. + +#### Icon + +Supported Live Activity types: `stats`, `metrics`, `progress`, `segmented_progress`, and `alert`. + +

+ Metrics Live Activity with an SF Symbol icon on the iPhone Lock Screen +

+ +```python +activitysmith.live_activities.stream( + "prod-web-1", + content_state=content_state( + title="Server Health", + subtitle="prod-web-1", + type=activitysmith.live_activities.TYPE_METRICS, + icon=alert_icon("server.rack", color="blue"), + metrics=[ + metric(label="CPU", value=18, unit="%"), + metric(label="MEM", value=42, unit="%"), + ], + ), +) +``` + +The `icon.symbol` value is an Apple SF Symbol name. Browse the catalog with one of these tools: + +- [ActivitySmith app](https://apps.apple.com/us/app/activitysmith/id6752254835) - Open Settings -> SF Symbols to browse 45 hand-picked icons ready to use +- [SF Symbols](https://developer.apple.com/sf-symbols/) - Apple's official macOS app +- [Interactful](https://apps.apple.com/app/interactful/id1528095640) - free third-party iOS app listing all SF Symbols under Foundations -> Iconography + +#### Badge + +Badges are supported by `alert`, `progress`, and `segmented_progress` Live Activities. + +

+ Progress Live Activity with a badge on the iPhone Lock Screen +

+ +```python +activitysmith.live_activities.stream( + "nightly-database-backup", + content_state=content_state( + title="Nightly Database Backup", + subtitle="verify restore", + type=activitysmith.live_activities.TYPE_PROGRESS, + badge=alert_badge("S3", color="cyan"), + percentage=62, + ), +) +``` + +### Live Activity Colors + +Choose from these colors for the Live Activity accent, including progress bars and action buttons, or apply them to an individual icon or badge: + +`lime`, `green`, `cyan`, `blue`, `purple`, `magenta`, `red`, `orange`, `yellow`, `gray` + ## Channels Channels are used to target specific team members or devices. Can be used for both push notifications and live activities. diff --git a/activitysmith_openapi/docs/ContentStateEnd.md b/activitysmith_openapi/docs/ContentStateEnd.md index 65bc34e..c608784 100644 --- a/activitysmith_openapi/docs/ContentStateEnd.md +++ b/activitysmith_openapi/docs/ContentStateEnd.md @@ -1,6 +1,6 @@ # ContentStateEnd -End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. +End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. ## Properties @@ -15,8 +15,8 @@ Name | Type | Description | Notes **upper_limit** | **float** | Maximum progress value. Use with value for type=progress. | [optional] **metrics** | [**List[ActivityMetric]**](ActivityMetric.md) | Use for type=metrics or type=stats. | [optional] **message** | **str** | Alert message. Use for type=alert. | [optional] -**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon for type=alert. | [optional] -**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge for type=alert. | [optional] +**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. | [optional] +**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge. Supported by alert, progress, and segmented_progress. | [optional] **type** | **str** | Optional. When omitted, the API uses the existing Live Activity type. | [optional] **color** | **str** | Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included. | [optional] **step_color** | **str** | Optional. Overrides color for the current step. Only applies to type=segmented_progress. | [optional] diff --git a/activitysmith_openapi/docs/ContentStateStart.md b/activitysmith_openapi/docs/ContentStateStart.md index 779b022..6036e3f 100644 --- a/activitysmith_openapi/docs/ContentStateStart.md +++ b/activitysmith_openapi/docs/ContentStateStart.md @@ -1,6 +1,6 @@ # ContentStateStart -Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. +Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. ## Properties @@ -15,8 +15,8 @@ Name | Type | Description | Notes **upper_limit** | **float** | Maximum progress value. Use with value for type=progress. | [optional] **metrics** | [**List[ActivityMetric]**](ActivityMetric.md) | Use for type=metrics or type=stats. | [optional] **message** | **str** | Required for type=alert. | [optional] -**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon for type=alert. | [optional] -**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge for type=alert. | [optional] +**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. | [optional] +**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge. Supported by alert, progress, and segmented_progress. | [optional] **type** | **str** | | **color** | **str** | Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included. | [optional] **step_color** | **str** | Optional. Overrides color for the current step. Only applies to type=segmented_progress. | [optional] diff --git a/activitysmith_openapi/docs/ContentStateUpdate.md b/activitysmith_openapi/docs/ContentStateUpdate.md index 8653ffe..d506517 100644 --- a/activitysmith_openapi/docs/ContentStateUpdate.md +++ b/activitysmith_openapi/docs/ContentStateUpdate.md @@ -1,6 +1,6 @@ # ContentStateUpdate -Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. +Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. ## Properties @@ -15,8 +15,8 @@ Name | Type | Description | Notes **upper_limit** | **float** | Maximum progress value. Use with value for type=progress. | [optional] **metrics** | [**List[ActivityMetric]**](ActivityMetric.md) | Use for type=metrics or type=stats. | [optional] **message** | **str** | Alert message. Use for type=alert. | [optional] -**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon for type=alert. | [optional] -**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge for type=alert. | [optional] +**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. | [optional] +**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge. Supported by alert, progress, and segmented_progress. | [optional] **type** | **str** | Optional. When omitted, the API uses the existing Live Activity type. | [optional] **color** | **str** | Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included. | [optional] **step_color** | **str** | Optional. Overrides color for the current step. Only applies to type=segmented_progress. | [optional] diff --git a/activitysmith_openapi/docs/LiveActivityAction.md b/activitysmith_openapi/docs/LiveActivityAction.md index cdfb25a..324c623 100644 --- a/activitysmith_openapi/docs/LiveActivityAction.md +++ b/activitysmith_openapi/docs/LiveActivityAction.md @@ -8,7 +8,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **title** | **str** | Button title displayed in the Live Activity UI. | **type** | [**LiveActivityActionType**](LiveActivityActionType.md) | | -**url** | **str** | HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. | +**url** | **str** | Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. | **method** | [**LiveActivityWebhookMethod**](LiveActivityWebhookMethod.md) | Webhook HTTP method. Used only when type=webhook. | [optional] [default to LiveActivityWebhookMethod.POST] **body** | **Dict[str, object]** | Optional webhook payload body. Used only when type=webhook. | [optional] diff --git a/activitysmith_openapi/docs/LiveActivityAlertBadge.md b/activitysmith_openapi/docs/LiveActivityAlertBadge.md index 81fc3e0..a1b109c 100644 --- a/activitysmith_openapi/docs/LiveActivityAlertBadge.md +++ b/activitysmith_openapi/docs/LiveActivityAlertBadge.md @@ -1,6 +1,6 @@ # LiveActivityAlertBadge -Optional badge for Alert Live Activities. +Optional badge for Live Activities. ## Properties diff --git a/activitysmith_openapi/docs/LiveActivityAlertIcon.md b/activitysmith_openapi/docs/LiveActivityAlertIcon.md index fa25371..c6d0f9f 100644 --- a/activitysmith_openapi/docs/LiveActivityAlertIcon.md +++ b/activitysmith_openapi/docs/LiveActivityAlertIcon.md @@ -1,6 +1,6 @@ # LiveActivityAlertIcon -Optional SF Symbol icon for Alert Live Activities. +Optional SF Symbol icon for Live Activities. ## Properties diff --git a/activitysmith_openapi/docs/PushNotificationAction.md b/activitysmith_openapi/docs/PushNotificationAction.md index ce17011..1bebd5c 100644 --- a/activitysmith_openapi/docs/PushNotificationAction.md +++ b/activitysmith_openapi/docs/PushNotificationAction.md @@ -7,7 +7,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **title** | **str** | Button title displayed in iOS expanded notification UI. | **type** | [**PushNotificationActionType**](PushNotificationActionType.md) | | -**url** | **str** | HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend. | +**url** | **str** | Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend. | **method** | [**PushNotificationWebhookMethod**](PushNotificationWebhookMethod.md) | Webhook HTTP method. Used only when type=webhook. | [optional] [default to PushNotificationWebhookMethod.POST] **body** | **Dict[str, object]** | Optional webhook payload body. Used only when type=webhook. | [optional] diff --git a/activitysmith_openapi/docs/PushNotificationRequest.md b/activitysmith_openapi/docs/PushNotificationRequest.md index ef00359..5cde2eb 100644 --- a/activitysmith_openapi/docs/PushNotificationRequest.md +++ b/activitysmith_openapi/docs/PushNotificationRequest.md @@ -9,7 +9,7 @@ Name | Type | Description | Notes **message** | **str** | | [optional] **subtitle** | **str** | | [optional] **media** | **str** | Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`. | [optional] -**redirection** | **str** | Optional HTTPS URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. | [optional] +**redirection** | **str** | Optional HTTPS or shortcuts:// URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided. | [optional] **actions** | [**List[PushNotificationAction]**](PushNotificationAction.md) | Optional interactive actions shown when users expand the notification. Cannot be combined with `media`. | [optional] **payload** | **Dict[str, object]** | | [optional] **badge** | **int** | | [optional] diff --git a/activitysmith_openapi/docs/StreamContentState.md b/activitysmith_openapi/docs/StreamContentState.md index 78f993c..72ae196 100644 --- a/activitysmith_openapi/docs/StreamContentState.md +++ b/activitysmith_openapi/docs/StreamContentState.md @@ -19,8 +19,8 @@ Name | Type | Description | Notes **step_colors** | **List[str]** | Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step. | [optional] **metrics** | [**List[ActivityMetric]**](ActivityMetric.md) | Use for metrics and stats activities. | [optional] **message** | **str** | Required for type=alert. | [optional] -**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon for type=alert. | [optional] -**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge for type=alert. | [optional] +**icon** | [**LiveActivityAlertIcon**](LiveActivityAlertIcon.md) | Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats. | [optional] +**badge** | [**LiveActivityAlertBadge**](LiveActivityAlertBadge.md) | Optional badge. Supported by alert, progress, and segmented_progress. | [optional] **auto_dismiss_seconds** | **int** | Optional. Seconds before the ended Live Activity is dismissed. | [optional] **auto_dismiss_minutes** | **int** | Optional. Minutes before the ended Live Activity is dismissed. | [optional] diff --git a/activitysmith_openapi/models/content_state_end.py b/activitysmith_openapi/models/content_state_end.py index 6d0c67b..2e28aa1 100644 --- a/activitysmith_openapi/models/content_state_end.py +++ b/activitysmith_openapi/models/content_state_end.py @@ -28,7 +28,7 @@ class ContentStateEnd(BaseModel): """ - End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. + End payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when ending an existing activity. You can send an updated number_of_steps here if the workflow changed after start. """ # noqa: E501 title: StrictStr subtitle: Optional[StrictStr] = None @@ -39,8 +39,8 @@ class ContentStateEnd(BaseModel): upper_limit: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Maximum progress value. Use with value for type=progress.") metrics: Optional[Annotated[List[ActivityMetric], Field(min_length=1, max_length=8)]] = Field(default=None, description="Use for type=metrics or type=stats.") message: Optional[Annotated[str, Field(min_length=1, strict=True)]] = Field(default=None, description="Alert message. Use for type=alert.") - icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon for type=alert.") - badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge for type=alert.") + icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats.") + badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge. Supported by alert, progress, and segmented_progress.") type: Optional[StrictStr] = Field(default=None, description="Optional. When omitted, the API uses the existing Live Activity type.") color: Optional[StrictStr] = Field(default=None, description="Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included.") step_color: Optional[StrictStr] = Field(default=None, description="Optional. Overrides color for the current step. Only applies to type=segmented_progress.") diff --git a/activitysmith_openapi/models/content_state_start.py b/activitysmith_openapi/models/content_state_start.py index 1055dfb..11d2cc0 100644 --- a/activitysmith_openapi/models/content_state_start.py +++ b/activitysmith_openapi/models/content_state_start.py @@ -28,7 +28,7 @@ class ContentStateStart(BaseModel): """ - Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. + Start payload requires title and type. For segmented_progress include number_of_steps and current_step. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. For segmented_progress, number_of_steps is not locked and can be changed in later update or end calls. """ # noqa: E501 title: StrictStr subtitle: Optional[StrictStr] = None @@ -39,8 +39,8 @@ class ContentStateStart(BaseModel): upper_limit: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Maximum progress value. Use with value for type=progress.") metrics: Optional[Annotated[List[ActivityMetric], Field(min_length=1, max_length=8)]] = Field(default=None, description="Use for type=metrics or type=stats.") message: Optional[Annotated[str, Field(min_length=1, strict=True)]] = Field(default=None, description="Required for type=alert.") - icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon for type=alert.") - badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge for type=alert.") + icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats.") + badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge. Supported by alert, progress, and segmented_progress.") type: StrictStr color: Optional[StrictStr] = Field(default=None, description="Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included.") step_color: Optional[StrictStr] = Field(default=None, description="Optional. Overrides color for the current step. Only applies to type=segmented_progress.") diff --git a/activitysmith_openapi/models/content_state_update.py b/activitysmith_openapi/models/content_state_update.py index d1f0c3d..08c7972 100644 --- a/activitysmith_openapi/models/content_state_update.py +++ b/activitysmith_openapi/models/content_state_update.py @@ -28,7 +28,7 @@ class ContentStateUpdate(BaseModel): """ - Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message, with optional icon and badge. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. + Update payload requires title. For segmented_progress include current_step and optionally number_of_steps. For progress include percentage or value with upper_limit. For metrics and stats include a non-empty metrics array. For alert include message. Optional icon is supported by all Live Activity types. Optional badge is supported by alert, progress, and segmented_progress. Type is optional when updating an existing activity. You can increase or decrease number_of_steps during updates. """ # noqa: E501 title: StrictStr subtitle: Optional[StrictStr] = None @@ -39,8 +39,8 @@ class ContentStateUpdate(BaseModel): upper_limit: Optional[Union[StrictFloat, StrictInt]] = Field(default=None, description="Maximum progress value. Use with value for type=progress.") metrics: Optional[Annotated[List[ActivityMetric], Field(min_length=1, max_length=8)]] = Field(default=None, description="Use for type=metrics or type=stats.") message: Optional[Annotated[str, Field(min_length=1, strict=True)]] = Field(default=None, description="Alert message. Use for type=alert.") - icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon for type=alert.") - badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge for type=alert.") + icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats.") + badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge. Supported by alert, progress, and segmented_progress.") type: Optional[StrictStr] = Field(default=None, description="Optional. When omitted, the API uses the existing Live Activity type.") color: Optional[StrictStr] = Field(default=None, description="Optional. Accent color for progress, segmented_progress, and metrics Live Activities. For Alert Live Activities, this tints the action button when action is included.") step_color: Optional[StrictStr] = Field(default=None, description="Optional. Overrides color for the current step. Only applies to type=segmented_progress.") diff --git a/activitysmith_openapi/models/live_activity_action.py b/activitysmith_openapi/models/live_activity_action.py index f75137c..544bf0a 100644 --- a/activitysmith_openapi/models/live_activity_action.py +++ b/activitysmith_openapi/models/live_activity_action.py @@ -17,9 +17,8 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictStr, model_validator from typing import Any, ClassVar, Dict, List, Optional -from typing_extensions import Annotated from activitysmith_openapi.models.live_activity_action_type import LiveActivityActionType from activitysmith_openapi.models.live_activity_webhook_method import LiveActivityWebhookMethod from typing import Optional, Set @@ -31,18 +30,11 @@ class LiveActivityAction(BaseModel): """ # noqa: E501 title: StrictStr = Field(description="Button title displayed in the Live Activity UI.") type: LiveActivityActionType - url: Annotated[str, Field(strict=True)] = Field(description="HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend.") + url: StrictStr = Field(description="Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend.") method: Optional[LiveActivityWebhookMethod] = Field(default=LiveActivityWebhookMethod.POST, description="Webhook HTTP method. Used only when type=webhook.") body: Optional[Dict[str, Any]] = Field(default=None, description="Optional webhook payload body. Used only when type=webhook.") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["title", "type", "url", "method", "body"] - - @field_validator('url') - def url_validate_regular_expression(cls, value): - """Validates the regular expression""" - if not re.match(r"^https:\/\/", value): - raise ValueError(r"must validate the regular expression /^https:\/\//") - return value + __properties: ClassVar[List[str]] = [] model_config = ConfigDict( populate_by_name=True, @@ -50,6 +42,14 @@ def url_validate_regular_expression(cls, value): protected_namespaces=(), ) + @model_validator(mode="after") + def validate_url_for_type(self) -> Self: + if self.type == LiveActivityActionType.OPEN_URL and not (self.url.startswith("https://") or self.url.startswith("shortcuts://")): + raise ValueError("open_url action url must use https or shortcuts") + if self.type == LiveActivityActionType.WEBHOOK and not self.url.startswith("https://"): + raise ValueError("webhook action url must use https") + return self + def to_str(self) -> str: """Returns the string representation of the model using alias""" @@ -102,11 +102,6 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "title": obj.get("title"), - "type": obj.get("type"), - "url": obj.get("url"), - "method": obj.get("method") if obj.get("method") is not None else LiveActivityWebhookMethod.POST, - "body": obj.get("body") }) # store additional fields in additional_properties for _key in obj.keys(): @@ -115,4 +110,3 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return _obj - diff --git a/activitysmith_openapi/models/live_activity_alert_badge.py b/activitysmith_openapi/models/live_activity_alert_badge.py index c499df7..e4e4d0a 100644 --- a/activitysmith_openapi/models/live_activity_alert_badge.py +++ b/activitysmith_openapi/models/live_activity_alert_badge.py @@ -26,7 +26,7 @@ class LiveActivityAlertBadge(BaseModel): """ - Optional badge for Alert Live Activities. + Optional badge for Live Activities. """ # noqa: E501 title: Annotated[str, Field(min_length=1, strict=True)] color: Optional[LiveActivityColor] = Field(default=None, description="Optional badge color.") diff --git a/activitysmith_openapi/models/live_activity_alert_icon.py b/activitysmith_openapi/models/live_activity_alert_icon.py index 06a1ed2..1f46b7c 100644 --- a/activitysmith_openapi/models/live_activity_alert_icon.py +++ b/activitysmith_openapi/models/live_activity_alert_icon.py @@ -26,7 +26,7 @@ class LiveActivityAlertIcon(BaseModel): """ - Optional SF Symbol icon for Alert Live Activities. + Optional SF Symbol icon for Live Activities. """ # noqa: E501 symbol: Annotated[str, Field(min_length=1, strict=True)] = Field(description="Apple SF Symbol name.") color: Optional[LiveActivityColor] = Field(default=None, description="Optional icon color.") diff --git a/activitysmith_openapi/models/push_notification_action.py b/activitysmith_openapi/models/push_notification_action.py index 8a09f6b..8822862 100644 --- a/activitysmith_openapi/models/push_notification_action.py +++ b/activitysmith_openapi/models/push_notification_action.py @@ -17,9 +17,8 @@ import re # noqa: F401 import json -from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator +from pydantic import BaseModel, ConfigDict, Field, StrictStr, model_validator from typing import Any, ClassVar, Dict, List, Optional -from typing_extensions import Annotated from activitysmith_openapi.models.push_notification_action_type import PushNotificationActionType from activitysmith_openapi.models.push_notification_webhook_method import PushNotificationWebhookMethod from typing import Optional, Set @@ -31,18 +30,11 @@ class PushNotificationAction(BaseModel): """ # noqa: E501 title: StrictStr = Field(description="Button title displayed in iOS expanded notification UI.") type: PushNotificationActionType - url: Annotated[str, Field(strict=True)] = Field(description="HTTPS URL. For open_url it is opened in browser. For webhook it is called by ActivitySmith backend.") + url: StrictStr = Field(description="Action URL. For open_url, use an HTTPS or shortcuts:// URL. For webhook, use an HTTPS URL called by the ActivitySmith backend.") method: Optional[PushNotificationWebhookMethod] = Field(default=PushNotificationWebhookMethod.POST, description="Webhook HTTP method. Used only when type=webhook.") body: Optional[Dict[str, Any]] = Field(default=None, description="Optional webhook payload body. Used only when type=webhook.") additional_properties: Dict[str, Any] = {} - __properties: ClassVar[List[str]] = ["title", "type", "url", "method", "body"] - - @field_validator('url') - def url_validate_regular_expression(cls, value): - """Validates the regular expression""" - if not re.match(r"^https:\/\/", value): - raise ValueError(r"must validate the regular expression /^https:\/\//") - return value + __properties: ClassVar[List[str]] = [] model_config = ConfigDict( populate_by_name=True, @@ -50,6 +42,14 @@ def url_validate_regular_expression(cls, value): protected_namespaces=(), ) + @model_validator(mode="after") + def validate_url_for_type(self) -> Self: + if self.type == PushNotificationActionType.OPEN_URL and not (self.url.startswith("https://") or self.url.startswith("shortcuts://")): + raise ValueError("open_url action url must use https or shortcuts") + if self.type == PushNotificationActionType.WEBHOOK and not self.url.startswith("https://"): + raise ValueError("webhook action url must use https") + return self + def to_str(self) -> str: """Returns the string representation of the model using alias""" @@ -102,11 +102,6 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "title": obj.get("title"), - "type": obj.get("type"), - "url": obj.get("url"), - "method": obj.get("method") if obj.get("method") is not None else PushNotificationWebhookMethod.POST, - "body": obj.get("body") }) # store additional fields in additional_properties for _key in obj.keys(): @@ -115,4 +110,3 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return _obj - diff --git a/activitysmith_openapi/models/push_notification_request.py b/activitysmith_openapi/models/push_notification_request.py index 78a0f1d..a056f4d 100644 --- a/activitysmith_openapi/models/push_notification_request.py +++ b/activitysmith_openapi/models/push_notification_request.py @@ -33,7 +33,7 @@ class PushNotificationRequest(BaseModel): message: Optional[StrictStr] = None subtitle: Optional[StrictStr] = None media: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`.") - redirection: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="Optional HTTPS URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided.") + redirection: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="Optional HTTPS or shortcuts:// URL opened when user taps the notification body. Overrides the default tap target from `media` when both are provided.") actions: Optional[Annotated[List[PushNotificationAction], Field(max_length=4)]] = Field(default=None, description="Optional interactive actions shown when users expand the notification. Cannot be combined with `media`.") payload: Optional[Dict[str, Any]] = None badge: Optional[StrictInt] = None @@ -58,8 +58,8 @@ def redirection_validate_regular_expression(cls, value): if value is None: return value - if not re.match(r"^https:\/\/", value): - raise ValueError(r"must validate the regular expression /^https:\/\//") + if not re.match(r"^(https|shortcuts):\/\/", value): + raise ValueError(r"must validate the regular expression /^(https|shortcuts):\/\//") return value model_config = ConfigDict( diff --git a/activitysmith_openapi/models/stream_content_state.py b/activitysmith_openapi/models/stream_content_state.py index 7c0e0ab..c07f8ad 100644 --- a/activitysmith_openapi/models/stream_content_state.py +++ b/activitysmith_openapi/models/stream_content_state.py @@ -43,8 +43,8 @@ class StreamContentState(BaseModel): step_colors: Optional[List[StrictStr]] = Field(default=None, description="Optional. Colors for completed steps. When used with segmented_progress, the array length should match current_step.") metrics: Optional[Annotated[List[ActivityMetric], Field(min_length=1, max_length=8)]] = Field(default=None, description="Use for metrics and stats activities.") message: Optional[Annotated[str, Field(min_length=1, strict=True)]] = Field(default=None, description="Required for type=alert.") - icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon for type=alert.") - badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge for type=alert.") + icon: Optional[LiveActivityAlertIcon] = Field(default=None, description="Optional SF Symbol icon. Supported by alert, progress, segmented_progress, metrics, and stats.") + badge: Optional[LiveActivityAlertBadge] = Field(default=None, description="Optional badge. Supported by alert, progress, and segmented_progress.") auto_dismiss_seconds: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=None, description="Optional. Seconds before the ended Live Activity is dismissed.") auto_dismiss_minutes: Optional[Annotated[int, Field(strict=True, ge=0)]] = Field(default=None, description="Optional. Minutes before the ended Live Activity is dismissed.") additional_properties: Dict[str, Any] = {} diff --git a/activitysmith_openapi/test/test_live_activity_action.py b/activitysmith_openapi/test/test_live_activity_action.py index 731ebee..e88a849 100644 --- a/activitysmith_openapi/test/test_live_activity_action.py +++ b/activitysmith_openapi/test/test_live_activity_action.py @@ -15,6 +15,7 @@ import unittest from activitysmith_openapi.models.live_activity_action import LiveActivityAction +from activitysmith_openapi.models.live_activity_action_type import LiveActivityActionType class TestLiveActivityAction(unittest.TestCase): """LiveActivityAction unit test stubs""" @@ -37,7 +38,7 @@ def make_instance(self, include_optional) -> LiveActivityAction: return LiveActivityAction( title = '', type = 'open_url', - url = 'https:/', + url = '', method = 'POST', body = { } ) @@ -45,7 +46,7 @@ def make_instance(self, include_optional) -> LiveActivityAction: return LiveActivityAction( title = '', type = 'open_url', - url = 'https:/', + url = '', ) """ @@ -54,5 +55,22 @@ def testLiveActivityAction(self): # inst_req_only = self.make_instance(include_optional=False) # inst_req_and_optional = self.make_instance(include_optional=True) + def test_open_url_allows_shortcuts_url(self): + action = LiveActivityAction( + title="Chat", + type=LiveActivityActionType.OPEN_URL, + url="shortcuts://run-shortcut?name=JARVIS", + ) + + self.assertEqual("shortcuts://run-shortcut?name=JARVIS", action.url) + + def test_webhook_rejects_shortcuts_url(self): + with self.assertRaises(ValueError): + LiveActivityAction( + title="Chat", + type=LiveActivityActionType.WEBHOOK, + url="shortcuts://run-shortcut?name=JARVIS", + ) + if __name__ == '__main__': unittest.main() diff --git a/activitysmith_openapi/test/test_push_notification_action.py b/activitysmith_openapi/test/test_push_notification_action.py index 7f29c03..e670103 100644 --- a/activitysmith_openapi/test/test_push_notification_action.py +++ b/activitysmith_openapi/test/test_push_notification_action.py @@ -15,6 +15,7 @@ import unittest from activitysmith_openapi.models.push_notification_action import PushNotificationAction +from activitysmith_openapi.models.push_notification_action_type import PushNotificationActionType class TestPushNotificationAction(unittest.TestCase): """PushNotificationAction unit test stubs""" @@ -37,7 +38,7 @@ def make_instance(self, include_optional) -> PushNotificationAction: return PushNotificationAction( title = '', type = 'open_url', - url = 'https:/', + url = '', method = 'POST', body = { } ) @@ -45,7 +46,7 @@ def make_instance(self, include_optional) -> PushNotificationAction: return PushNotificationAction( title = '', type = 'open_url', - url = 'https:/', + url = '', ) """ @@ -54,5 +55,22 @@ def testPushNotificationAction(self): # inst_req_only = self.make_instance(include_optional=False) # inst_req_and_optional = self.make_instance(include_optional=True) + def test_open_url_allows_shortcuts_url(self): + action = PushNotificationAction( + title="Chat", + type=PushNotificationActionType.OPEN_URL, + url="shortcuts://run-shortcut?name=JARVIS", + ) + + self.assertEqual("shortcuts://run-shortcut?name=JARVIS", action.url) + + def test_webhook_rejects_shortcuts_url(self): + with self.assertRaises(ValueError): + PushNotificationAction( + title="Chat", + type=PushNotificationActionType.WEBHOOK, + url="shortcuts://run-shortcut?name=JARVIS", + ) + if __name__ == '__main__': unittest.main() diff --git a/activitysmith_openapi/test/test_push_notification_request.py b/activitysmith_openapi/test/test_push_notification_request.py index 7c7e961..e339292 100644 --- a/activitysmith_openapi/test/test_push_notification_request.py +++ b/activitysmith_openapi/test/test_push_notification_request.py @@ -39,7 +39,7 @@ def make_instance(self, include_optional) -> PushNotificationRequest: message = '', subtitle = '', media = 'https:/', - redirection = 'https:/', + redirection = 'shortcuts:/', actions = [ { 'key' : null diff --git a/tests/test_resources.py b/tests/test_resources.py index 8db8a4f..6d87606 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -119,7 +119,7 @@ def test_push_action_helper(monkeypatch): action( title="Open CRM Profile", type="open_url", - url="https://crm.example.com/customers/cus_9f3a1d", + url="shortcuts://run-shortcut?name=Open%20CRM", ) ], ) @@ -132,7 +132,7 @@ def test_push_action_helper(monkeypatch): { "title": "Open CRM Profile", "type": "open_url", - "url": "https://crm.example.com/customers/cus_9f3a1d", + "url": "shortcuts://run-shortcut?name=Open%20CRM", } ], } @@ -170,9 +170,19 @@ def test_notifications_preserve_media_and_redirection(monkeypatch): } client.notifications.send(payload) + client.notifications.send( + title="Run Shortcut", + redirection="shortcuts://run-shortcut?name=Jarvis", + ) assert client.notifications._api.calls == [ {"push_notification_request": payload}, + { + "push_notification_request": { + "title": "Run Shortcut", + "redirection": "shortcuts://run-shortcut?name=Jarvis", + } + }, ] @@ -383,6 +393,68 @@ def test_live_activities_support_alert_helpers(monkeypatch): ] +def test_live_activities_support_icon_and_badge_on_non_alert_types(monkeypatch): + monkeypatch.setattr(client_module, "PushNotificationsApi", FakePushNotificationsApi) + monkeypatch.setattr(client_module, "LiveActivitiesApi", FakeLiveActivitiesApi) + monkeypatch.setattr(client_module, "MetricsApi", FakeMetricsApi) + + client = ActivitySmith(api_key="x") + + client.live_activities.stream( + "prod-web-1", + content_state=content_state( + title="Server Health", + subtitle="prod-web-1", + type=client.live_activities.TYPE_METRICS, + icon=alert_icon("server.rack", color="blue"), + metrics=[metric(label="CPU", value=18, unit="%")], + ), + ) + client.live_activities.stream( + "nightly-database-backup", + content_state=content_state( + title="Nightly Database Backup", + subtitle="verify restore", + type=client.live_activities.TYPE_PROGRESS, + badge=alert_badge("S3", color="cyan"), + percentage=62, + ), + ) + + assert client.live_activities._api.calls == [ + ( + "stream", + { + "stream_key": "prod-web-1", + "live_activity_stream_request": { + "content_state": { + "title": "Server Health", + "subtitle": "prod-web-1", + "type": client.live_activities.TYPE_METRICS, + "icon": {"symbol": "server.rack", "color": "blue"}, + "metrics": [{"label": "CPU", "value": 18, "unit": "%"}], + }, + }, + }, + ), + ( + "stream", + { + "stream_key": "nightly-database-backup", + "live_activity_stream_request": { + "content_state": { + "title": "Nightly Database Backup", + "subtitle": "verify restore", + "type": client.live_activities.TYPE_PROGRESS, + "badge": {"title": "S3", "color": "cyan"}, + "percentage": 62, + }, + }, + }, + ), + ] + + def test_live_activities_build_requests_from_named_fields(monkeypatch): monkeypatch.setattr(client_module, "PushNotificationsApi", FakePushNotificationsApi) monkeypatch.setattr(client_module, "LiveActivitiesApi", FakeLiveActivitiesApi) @@ -396,7 +468,7 @@ def test_live_activities_build_requests_from_named_fields(monkeypatch): action_payload = action( title="Open Dashboard", type="open_url", - url="https://ops.example.com/servers/prod-web-1", + url="shortcuts://run-shortcut?name=Open%20Dashboard", ) state_payload = content_state( title="Server Health", @@ -563,7 +635,7 @@ def test_live_activities_pass_action_payloads_through(monkeypatch): "action": { "title": "Open Workflow", "type": "open_url", - "url": "https://github.com/acme/payments-api/actions/runs/1234567890", + "url": "shortcuts://run-shortcut?name=Deploy%20Status", }, } @@ -595,7 +667,7 @@ def test_live_activities_pass_action_payloads_through(monkeypatch): "action": { "title": "Open Workflow", "type": "open_url", - "url": "https://github.com/acme/payments-api/actions/runs/1234567890", + "url": "shortcuts://run-shortcut?name=Deploy%20Status", }, }